diff options
author | kumvijaya <kuvmijaya@gmail.com> | 2024-09-26 11:31:07 +0530 |
---|---|---|
committer | kumvijaya <kuvmijaya@gmail.com> | 2024-09-26 11:31:07 +0530 |
commit | a950059053f7394acfb453cc0d8194aa3dc721fa (patch) | |
tree | eb0acf278f649b5d1417e18e34d728efcd16e745 | |
parent | f0815f3e9b212f424f5adb0c572a71119ad4a8a0 (diff) | |
download | vyos-workflow-test-temp-a950059053f7394acfb453cc0d8194aa3dc721fa.tar.gz vyos-workflow-test-temp-a950059053f7394acfb453cc0d8194aa3dc721fa.zip |
T6732: added same as vyos 1x
2020 files changed, 306800 insertions, 0 deletions
diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..21a6829 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,23 @@ +// Copyright (C) 2020-2021 VyOS maintainers and contributors +// +// This program is free software; you can redistribute it and/or modify +// in order to easy exprort images built to "external" world +// it under the terms of the GNU General Public License version 2 or later as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +@NonCPS + +// Using a version specifier library, use 'current' branch. The underscore (_) +// is not a typo! You need this underscore if the line immediately after the +// @Library annotation is not an import statement! +@Library('vyos-build@current')_ + +// Start package build using library function from https://github.com/vyos/vyos-build +buildPackage(null, null, null, true) diff --git a/LICENSE.GPL b/LICENSE.GPL new file mode 100644 index 0000000..23cb790 --- /dev/null +++ b/LICENSE.GPL @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/LICENSE.LGPL b/LICENSE.LGPL new file mode 100644 index 0000000..4362b49 --- /dev/null +++ b/LICENSE.LGPL @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..411399c --- /dev/null +++ b/Makefile @@ -0,0 +1,123 @@ +TMPL_DIR := templates-cfg +OP_TMPL_DIR := templates-op +BUILD_DIR := build +DATA_DIR := data +SHIM_DIR := src/shim +LIBS := -lzmq +CFLAGS := +BUILD_ARCH := $(shell dpkg-architecture -q DEB_BUILD_ARCH) +J2LINT := $(shell command -v j2lint 2> /dev/null) +PYLINT_FILES := $(shell git ls-files *.py src/migration-scripts) + +config_xml_src = $(wildcard interface-definitions/*.xml.in) +config_xml_obj = $(config_xml_src:.xml.in=.xml) +op_xml_src = $(wildcard op-mode-definitions/*.xml.in) +op_xml_obj = $(op_xml_src:.xml.in=.xml) + +%.xml: %.xml.in + @echo Generating $(BUILD_DIR)/$@ from $< + mkdir -p $(BUILD_DIR)/$(dir $@) + $(CURDIR)/scripts/transclude-template $< > $(BUILD_DIR)/$@ + +.PHONY: interface_definitions +.ONESHELL: +interface_definitions: $(config_xml_obj) + mkdir -p $(TMPL_DIR) + + $(CURDIR)/scripts/override-default $(BUILD_DIR)/interface-definitions + + find $(BUILD_DIR)/interface-definitions -type f -name "*.xml" | xargs -I {} $(CURDIR)/scripts/build-command-templates {} $(CURDIR)/schema/interface_definition.rng $(TMPL_DIR) || exit 1 + + $(CURDIR)/python/vyos/xml_ref/generate_cache.py --xml-dir $(BUILD_DIR)/interface-definitions || exit 1 + + # XXX: delete top level node.def's that now live in other packages + # IPSec VPN EAP-RADIUS does not support source-address + rm -rf $(TMPL_DIR)/vpn/ipsec/remote-access/radius/source-address + + # T2472 - EIGRP support + rm -rf $(TMPL_DIR)/protocols/eigrp + # T2773 - EIGRP support for VRF + rm -rf $(TMPL_DIR)/vrf/name/node.tag/protocols/eigrp + + # XXX: test if there are empty node.def files - this is not allowed as these + # could mask help strings or mandatory priority statements + find $(TMPL_DIR) -name node.def -type f -empty -exec false {} + || sh -c 'echo "There are empty node.def files! Check your interface definitions." && exit 1' + +ifeq ($(BUILD_ARCH),arm64) + # There is currently no telegraf support in VyOS for ARM64, remove CLI definitions + rm -rf $(TMPL_DIR)/service/monitoring/telegraf +endif + +.PHONY: op_mode_definitions +.ONESHELL: +op_mode_definitions: $(op_xml_obj) + mkdir -p $(OP_TMPL_DIR) + + find $(BUILD_DIR)/op-mode-definitions/ -type f -name "*.xml" | xargs -I {} $(CURDIR)/scripts/build-command-op-templates {} $(CURDIR)/schema/op-mode-definition.rng $(OP_TMPL_DIR) || exit 1 + + $(CURDIR)/python/vyos/xml_ref/generate_op_cache.py --xml-dir $(BUILD_DIR)/op-mode-definitions || exit 1 + + # XXX: tcpdump, ping, traceroute and mtr must be able to recursivly call themselves as the + # options are provided from the scripts themselves + ln -s ../node.tag $(OP_TMPL_DIR)/ping/node.tag/node.tag/ + ln -s ../node.tag $(OP_TMPL_DIR)/traceroute/node.tag/node.tag/ + ln -s ../node.tag $(OP_TMPL_DIR)/mtr/node.tag/node.tag/ + ln -s ../node.tag $(OP_TMPL_DIR)/monitor/traceroute/node.tag/node.tag/ + ln -s ../node.tag $(OP_TMPL_DIR)/monitor/traffic/interface/node.tag/node.tag/ + ln -s ../node.tag $(OP_TMPL_DIR)/execute/port-scan/host/node.tag/node.tag/ + + # XXX: test if there are empty node.def files - this is not allowed as these + # could mask help strings or mandatory priority statements + find $(OP_TMPL_DIR) -name node.def -type f -empty -exec false {} + || sh -c 'echo "There are empty node.def files! Check your interface definitions." && exit 1' + +.PHONY: vyshim +vyshim: + $(MAKE) -C $(SHIM_DIR) + +.PHONY: all +all: clean interface_definitions op_mode_definitions test j2lint vyshim generate-configd-include-json + +.PHONY: clean +clean: + rm -rf $(BUILD_DIR) + rm -rf $(TMPL_DIR) + rm -rf $(OP_TMPL_DIR) + $(MAKE) -C $(SHIM_DIR) clean + +.PHONY: test +test: generate-configd-include-json + set -e; python3 -m compileall -q -x '/vmware-tools/scripts/, /ppp/' . + PYTHONPATH=python/ python3 -m "nose" --with-xunit src --with-coverage --cover-erase --cover-xml --cover-package src/conf_mode,src/op_mode,src/completion,src/helpers,src/validators,src/tests --verbose + +.PHONY: check_migration_scripts_executable +.ONESHELL: +check_migration_scripts_executable: + @echo "Checking if migration scripts have executable bit set..." + find src/migration-scripts -type f -not -executable -print -exec false {} + || sh -c 'echo "Found files that are not executable! Add permissions." && exit 1' + +.PHONY: j2lint +j2lint: +ifndef J2LINT + $(error "j2lint binary not found, consider installing: pip install git+https://github.com/aristanetworks/j2lint.git@341b5d5db86") +endif + $(J2LINT) data/ + +.PHONY: sonar +sonar: + sonar-scanner -X -Dsonar.login=${SONAR_TOKEN} + +.PHONY: unused-imports +unused-imports: + @pylint --disable=all --enable=W0611 $(PYLINT_FILES) + +deb: + dpkg-buildpackage -uc -us -tc -b + +.PHONY: generate-configd-include-json +generate-configd-include-json: + @scripts/generate-configd-include-json.py + +.PHONY: schema +schema: + trang -I rnc -O rng schema/interface_definition.rnc schema/interface_definition.rng + trang -I rnc -O rng schema/op-mode-definition.rnc schema/op-mode-definition.rng diff --git a/data/config.boot.default b/data/config.boot.default new file mode 100644 index 0000000..93369d9 --- /dev/null +++ b/data/config.boot.default @@ -0,0 +1,53 @@ +interfaces { + loopback lo { + } +} +service { + ntp { + allow-client { + address "127.0.0.0/8" + address "169.254.0.0/16" + address "10.0.0.0/8" + address "172.16.0.0/12" + address "192.168.0.0/16" + address "::1/128" + address "fe80::/10" + address "fc00::/7" + } + server time1.vyos.net { + } + server time2.vyos.net { + } + server time3.vyos.net { + } + } +} +system { + config-management { + commit-revisions "100" + } + console { + device ttyS0 { + speed "115200" + } + } + host-name "vyos" + login { + user vyos { + authentication { + encrypted-password "$6$QxPS.uk6mfo$9QBSo8u1FkH16gMyAVhus6fU3LOzvLR9Z9.82m3tiHFAxTtIkhaZSWssSgzt4v4dGAL8rhVQxTg0oAG9/q11h/" + plaintext-password "" + } + } + } + syslog { + global { + facility all { + level "info" + } + facility local7 { + level "debug" + } + } + } +} diff --git a/data/templates/firewall/sysctl-firewall.conf.j2 b/data/templates/firewall/sysctl-firewall.conf.j2 new file mode 100644 index 0000000..6c33ffd --- /dev/null +++ b/data/templates/firewall/sysctl-firewall.conf.j2 @@ -0,0 +1,36 @@ +# Autogenerated by firewall.py + +# gloabl options +net.ipv4.icmp_echo_ignore_all = {{ 0 if global_options.all_ping == 'enable' else 1 }} +net.ipv4.icmp_echo_ignore_broadcasts = {{ 0 if global_options.broadcast_ping == 'enable' else 1 }} +net.ipv4.conf.all.bc_forwarding = {{ 1 if global_options.directed_broadcast == 'enable' else 0 }} +net.ipv4.conf.*.accept_source_route = {{ 1 if global_options.ip_src_route == 'enable' else 0 }} +net.ipv6.conf.*.accept_redirects = {{ 1 if global_options.ipv6_receive_redirects == 'enable' else 0 }} +net.ipv6.conf.*.accept_source_route = {{ 0 if global_options.ipv6_src_route == 'enable' else -1 }} +net.ipv4.conf.all.log_martians = {{ 1 if global_options.log_martians == 'enable' else 0 }} +net.ipv4.conf.*.accept_redirects = {{ 1 if global_options.receive_redirects == 'enable' else 0 }} +net.ipv4.conf.*.send_redirects = {{ 1 if global_options.send_redirects == 'enable' else 0 }} +net.ipv4.tcp_syncookies = {{ 1 if global_options.syn_cookies == 'enable' else 0 }} +net.ipv4.tcp_rfc1337 = {{ 1 if global_options.twa_hazards_protection == 'enable' else 0 }} + +{% if global_options.apply_to_bridged_traffic is vyos_defined %} +net.bridge.bridge-nf-call-iptables = {{ 1 if global_options.apply_to_bridged_traffic.ipv4 is vyos_defined else 0 }} +net.bridge.bridge-nf-call-ip6tables = {{ 1 if global_options.apply_to_bridged_traffic.ipv6 is vyos_defined else 0 }} +{% else %} +net.bridge.bridge-nf-call-iptables = 0 +net.bridge.bridge-nf-call-ip6tables = 0 +{% endif %} + +## Timeout values: +net.netfilter.nf_conntrack_icmp_timeout = {{ global_options.timeout.icmp }} +net.netfilter.nf_conntrack_generic_timeout = {{ global_options.timeout.other }} +net.netfilter.nf_conntrack_tcp_timeout_close_wait = {{ global_options.timeout.tcp.close_wait }} +net.netfilter.nf_conntrack_tcp_timeout_close = {{ global_options.timeout.tcp.close }} +net.netfilter.nf_conntrack_tcp_timeout_established = {{ global_options.timeout.tcp.established }} +net.netfilter.nf_conntrack_tcp_timeout_fin_wait = {{ global_options.timeout.tcp.fin_wait }} +net.netfilter.nf_conntrack_tcp_timeout_last_ack = {{ global_options.timeout.tcp.last_ack }} +net.netfilter.nf_conntrack_tcp_timeout_syn_recv = {{ global_options.timeout.tcp.syn_recv }} +net.netfilter.nf_conntrack_tcp_timeout_syn_sent = {{ global_options.timeout.tcp.syn_sent }} +net.netfilter.nf_conntrack_tcp_timeout_time_wait = {{ global_options.timeout.tcp.time_wait }} +net.netfilter.nf_conntrack_udp_timeout = {{ global_options.timeout.udp.other }} +net.netfilter.nf_conntrack_udp_timeout_stream = {{ global_options.timeout.udp.stream }} diff --git a/data/templates/frr/fabricd.frr.j2 b/data/templates/frr/fabricd.frr.j2 new file mode 100644 index 0000000..8f2ae64 --- /dev/null +++ b/data/templates/frr/fabricd.frr.j2 @@ -0,0 +1,72 @@ +! +{% for name, router_config in domain.items() %} +{% if router_config.interface is vyos_defined %} +{% for iface, iface_config in router_config.interface.items() %} +interface {{ iface }} +{% if iface_config.address_family.ipv4 is vyos_defined %} + ip router openfabric {{ name }} +{% endif %} +{% if iface_config.address_family.ipv6 is vyos_defined %} + ipv6 router openfabric {{ name }} +{% endif %} +{% if iface_config.csnp_interval is vyos_defined %} + openfabric csnp-interval {{ iface_config.csnp_interval }} +{% endif %} +{% if iface_config.hello_interval is vyos_defined %} + openfabric hello-interval {{ iface_config.hello_interval }} +{% endif %} +{% if iface_config.hello_multiplier is vyos_defined %} + openfabric hello-multiplier {{ iface_config.hello_multiplier }} +{% endif %} +{% if iface_config.metric is vyos_defined %} + openfabric metric {{ iface_config.metric }} +{% endif %} +{% if iface_config.passive is vyos_defined or iface == 'lo' %} + openfabric passive +{% endif %} +{% if iface_config.password.md5 is vyos_defined %} + openfabric password md5 {{ iface_config.password.md5 }} +{% elif iface_config.password.plaintext_password is vyos_defined %} + openfabric password clear {{ iface_config.password.plaintext_password }} +{% endif %} +{% if iface_config.psnp_interval is vyos_defined %} + openfabric psnp-interval {{ iface_config.psnp_interval }} +{% endif %} +exit +! +{% endfor %} +{% endif %} +router openfabric {{ name }} + net {{ net }} +{% if router_config.domain_password.md5 is vyos_defined %} + domain-password md5 {{ router_config.domain_password.plaintext_password }} +{% elif router_config.domain_password.plaintext_password is vyos_defined %} + domain-password clear {{ router_config.domain_password.plaintext_password }} +{% endif %} +{% if router_config.log_adjacency_changes is vyos_defined %} + log-adjacency-changes +{% endif %} +{% if router_config.set_overload_bit is vyos_defined %} + set-overload-bit +{% endif %} +{% if router_config.purge_originator is vyos_defined %} + purge-originator +{% endif %} +{% if router_config.fabric_tier is vyos_defined %} + fabric-tier {{ router_config.fabric_tier }} +{% endif %} +{% if router_config.lsp_gen_interval is vyos_defined %} + lsp-gen-interval {{ router_config.lsp_gen_interval }} +{% endif %} +{% if router_config.lsp_refresh_interval is vyos_defined %} + lsp-refresh-interval {{ router_config.lsp_refresh_interval }} +{% endif %} +{% if router_config.max_lsp_lifetime is vyos_defined %} + max-lsp-lifetime {{ router_config.max_lsp_lifetime }} +{% endif %} +{% if router_config.spf_interval is vyos_defined %} + spf-interval {{ router_config.spf_interval }} +{% endif %} +exit +! +{% endfor %} diff --git a/data/templates/ids/suricata.j2 b/data/templates/ids/suricata.j2 new file mode 100644 index 0000000..d76994c --- /dev/null +++ b/data/templates/ids/suricata.j2 @@ -0,0 +1,1280 @@ +%YAML 1.1 +--- + +# Suricata configuration file. In addition to the comments describing all +# options in this file, full documentation can be found at: +# https://suricata.readthedocs.io/en/latest/configuration/suricata-yaml.html +# +# This configuration file generated by: +# Suricata 6.0.10 + +## +## Step 1: Inform Suricata about your network +## + +vars: + # more specific is better for alert accuracy and performance + address-groups: +{% for (name, value) in suricata['address_group'] %} + {{ name }}: "[{{ value | join(',') }}]" +{% endfor %} + + port-groups: +{% for (name, value) in suricata['port_group'] %} + {{ name }}: "[{{ value | join(',') }}]" +{% endfor %} + +## +## Step 2: Select outputs to enable +## + +# The default logging directory. Any log or output file will be +# placed here if it's not specified with a full path name. This can be +# overridden with the -l command line parameter. +default-log-dir: /var/log/suricata/ + +# Configure the type of alert (and other) logging you would like. +{% if suricata.log is vyos_defined %} +outputs: +{% if suricata.log.eve is vyos_defined %} + # Extensible Event Format (nicknamed EVE) event log in JSON format + - eve-log: + enabled: yes + filetype: {{ suricata.log.eve.filetype }} #regular|syslog|unix_dgram|unix_stream|redis + filename: {{ suricata.log.eve.filename }} + + types: +{% if suricata.log.eve.type is not vyos_defined or "alert" in suricata.log.eve.type %} + - alert: + tagged-packets: yes +{% endif %} +{% if "http" in suricata.log.eve.type %} + - http: + enabled: yes + extended: yes +{% endif %} +{% if "tls" in suricata.log.eve.type %} + - tls: + enabled: yes + extended: yes # enable this for extended logging information +{% endif %} +{% for protocol in suricata.log.eve.type %} +{% if protocol not in ["alert","http","tls"] %} + - {{ protocol }}: + enabled: yes +{% endif %} +{% endfor %} +{% endif %} +{% endif %} + +## +## Step 3: Configure common capture settings +## +## See "Advanced Capture Options" below for more options, including Netmap +## and PF_RING. +## + +# Linux high speed capture support +af-packet: +{% for interface in suricata.interface %} + - interface: {{ interface }} + # Default clusterid. AF_PACKET will load balance packets based on flow. + cluster-id: {{ 100 - loop.index }} + # Default AF_PACKET cluster type. AF_PACKET can load balance per flow or per hash. + # This is only supported for Linux kernel > 3.1 + # possible value are: + # * cluster_flow: all packets of a given flow are sent to the same socket + # * cluster_cpu: all packets treated in kernel by a CPU are sent to the same socket + # * cluster_qm: all packets linked by network card to a RSS queue are sent to the same + # socket. Requires at least Linux 3.14. + # * cluster_ebpf: eBPF file load balancing. See doc/userguide/capture-hardware/ebpf-xdp.rst for + # more info. + # Recommended modes are cluster_flow on most boxes and cluster_cpu or cluster_qm on system + # with capture card using RSS (requires cpu affinity tuning and system IRQ tuning) + cluster-type: cluster_flow + # In some fragmentation cases, the hash can not be computed. If "defrag" is set + # to yes, the kernel will do the needed defragmentation before sending the packets. + defrag: yes +{% endfor %} + +# Cross platform libpcap capture support +pcap: +{% for interface in suricata.interface %} + - interface: {{ interface }} +{% endfor %} + +# Settings for reading pcap files +pcap-file: + # Possible values are: + # - yes: checksum validation is forced + # - no: checksum validation is disabled + # - auto: Suricata uses a statistical approach to detect when + # checksum off-loading is used. (default) + # Warning: 'checksum-validation' must be set to yes to have checksum tested + checksum-checks: auto + +# See "Advanced Capture Options" below for more options, including Netmap +# and PF_RING. + + +## +## Step 4: App Layer Protocol configuration +## + +# Configure the app-layer parsers. +# +# The error-policy setting applies to all app-layer parsers. Values can be +# "drop-flow", "pass-flow", "bypass", "drop-packet", "pass-packet", "reject" or +# "ignore" (the default). +# +# The protocol's section details each protocol. +# +# The option "enabled" takes 3 values - "yes", "no", "detection-only". +# "yes" enables both detection and the parser, "no" disables both, and +# "detection-only" enables protocol detection only (parser disabled). +app-layer: + # error-policy: ignore + protocols: + rfb: + enabled: yes + detection-ports: + dp: 5900, 5901, 5902, 5903, 5904, 5905, 5906, 5907, 5908, 5909 + # MQTT, disabled by default. + mqtt: + enabled: yes + # max-msg-length: 1mb + # subscribe-topic-match-limit: 100 + # unsubscribe-topic-match-limit: 100 + # Maximum number of live MQTT transactions per flow + # max-tx: 4096 + krb5: + enabled: yes + snmp: + enabled: yes + ikev2: + enabled: yes + tls: + enabled: yes + detection-ports: + dp: 443 + + # Generate JA3 fingerprint from client hello. If not specified it + # will be disabled by default, but enabled if rules require it. + #ja3-fingerprints: auto + + # What to do when the encrypted communications start: + # - default: keep tracking TLS session, check for protocol anomalies, + # inspect tls_* keywords. Disables inspection of unmodified + # 'content' signatures. + # - bypass: stop processing this flow as much as possible. No further + # TLS parsing and inspection. Offload flow bypass to kernel + # or hardware if possible. + # - full: keep tracking and inspection as normal. Unmodified content + # keyword signatures are inspected as well. + # + # For best performance, select 'bypass'. + # + #encryption-handling: default + + dcerpc: + enabled: yes + ftp: + enabled: yes + # memcap: 64mb + rdp: + enabled: yes + ssh: + enabled: yes + #hassh: yes + # HTTP2: Experimental HTTP 2 support. Disabled by default. + http2: + enabled: no + # use http keywords on HTTP2 traffic + http1-rules: no + smtp: + enabled: yes + raw-extraction: no + # Configure SMTP-MIME Decoder + mime: + # Decode MIME messages from SMTP transactions + # (may be resource intensive) + # This field supersedes all others because it turns the entire + # process on or off + decode-mime: yes + + # Decode MIME entity bodies (ie. Base64, quoted-printable, etc.) + decode-base64: yes + decode-quoted-printable: yes + + # Maximum bytes per header data value stored in the data structure + # (default is 2000) + header-value-depth: 2000 + + # Extract URLs and save in state data structure + extract-urls: yes + # Set to yes to compute the md5 of the mail body. You will then + # be able to journalize it. + body-md5: no + # Configure inspected-tracker for file_data keyword + inspected-tracker: + content-limit: 100000 + content-inspect-min-size: 32768 + content-inspect-window: 4096 + imap: + enabled: detection-only + smb: + enabled: yes + detection-ports: + dp: 139, 445 + + # Stream reassembly size for SMB streams. By default track it completely. + #stream-depth: 0 + + nfs: + enabled: yes + tftp: + enabled: yes + dns: + tcp: + enabled: yes + detection-ports: + dp: 53 + udp: + enabled: yes + detection-ports: + dp: 53 + http: + enabled: yes + # memcap: Maximum memory capacity for HTTP + # Default is unlimited, values can be 64mb, e.g. + + # default-config: Used when no server-config matches + # personality: List of personalities used by default + # request-body-limit: Limit reassembly of request body for inspection + # by http_client_body & pcre /P option. + # response-body-limit: Limit reassembly of response body for inspection + # by file_data, http_server_body & pcre /Q option. + # + # For advanced options, see the user guide + + + # server-config: List of server configurations to use if address matches + # address: List of IP addresses or networks for this block + # personality: List of personalities used by this block + # + # Then, all the fields from default-config can be overloaded + # + # Currently Available Personalities: + # Minimal, Generic, IDS (default), IIS_4_0, IIS_5_0, IIS_5_1, IIS_6_0, + # IIS_7_0, IIS_7_5, Apache_2 + libhtp: + default-config: + personality: IDS + + # Can be specified in kb, mb, gb. Just a number indicates + # it's in bytes. + request-body-limit: 100kb + response-body-limit: 100kb + + # inspection limits + request-body-minimal-inspect-size: 32kb + request-body-inspect-window: 4kb + response-body-minimal-inspect-size: 40kb + response-body-inspect-window: 16kb + + # response body decompression (0 disables) + response-body-decompress-layer-limit: 2 + + # auto will use http-body-inline mode in IPS mode, yes or no set it statically + http-body-inline: auto + + # Decompress SWF files. + # Two types: 'deflate', 'lzma', 'both' will decompress deflate and lzma + # compress-depth: + # Specifies the maximum amount of data to decompress, + # set 0 for unlimited. + # decompress-depth: + # Specifies the maximum amount of decompressed data to obtain, + # set 0 for unlimited. + swf-decompression: + enabled: yes + type: both + compress-depth: 100kb + decompress-depth: 100kb + + # Use a random value for inspection sizes around the specified value. + # This lowers the risk of some evasion techniques but could lead + # to detection change between runs. It is set to 'yes' by default. + #randomize-inspection-sizes: yes + # If "randomize-inspection-sizes" is active, the value of various + # inspection size will be chosen from the [1 - range%, 1 + range%] + # range + # Default value of "randomize-inspection-range" is 10. + #randomize-inspection-range: 10 + + # decoding + double-decode-path: no + double-decode-query: no + + # Can enable LZMA decompression + #lzma-enabled: false + # Memory limit usage for LZMA decompression dictionary + # Data is decompressed until dictionary reaches this size + #lzma-memlimit: 1mb + # Maximum decompressed size with a compression ratio + # above 2048 (only LZMA can reach this ratio, deflate cannot) + #compression-bomb-limit: 1mb + # Maximum time spent decompressing a single transaction in usec + #decompression-time-limit: 100000 + + server-config: + + #- apache: + # address: [192.168.1.0/24, 127.0.0.0/8, "::1"] + # personality: Apache_2 + # # Can be specified in kb, mb, gb. Just a number indicates + # # it's in bytes. + # request-body-limit: 4096 + # response-body-limit: 4096 + # double-decode-path: no + # double-decode-query: no + + #- iis7: + # address: + # - 192.168.0.0/24 + # - 192.168.10.0/24 + # personality: IIS_7_0 + # # Can be specified in kb, mb, gb. Just a number indicates + # # it's in bytes. + # request-body-limit: 4096 + # response-body-limit: 4096 + # double-decode-path: no + # double-decode-query: no + + # Note: Modbus probe parser is minimalist due to the limited usage in the field. + # Only Modbus message length (greater than Modbus header length) + # and protocol ID (equal to 0) are checked in probing parser + # It is important to enable detection port and define Modbus port + # to avoid false positives + modbus: + # How many unanswered Modbus requests are considered a flood. + # If the limit is reached, the app-layer-event:modbus.flooded; will match. + #request-flood: 500 + + enabled: no + detection-ports: + dp: 502 + # According to MODBUS Messaging on TCP/IP Implementation Guide V1.0b, it + # is recommended to keep the TCP connection opened with a remote device + # and not to open and close it for each MODBUS/TCP transaction. In that + # case, it is important to set the depth of the stream reassembling as + # unlimited (stream.reassembly.depth: 0) + + # Stream reassembly size for modbus. By default track it completely. + stream-depth: 0 + + # DNP3 + dnp3: + enabled: no + detection-ports: + dp: 20000 + + # SCADA EtherNet/IP and CIP protocol support + enip: + enabled: no + detection-ports: + dp: 44818 + sp: 44818 + + ntp: + enabled: yes + + dhcp: + enabled: yes + + sip: + enabled: yes + +# Limit for the maximum number of asn1 frames to decode (default 256) +asn1-max-frames: 256 + +# Datasets default settings +# datasets: +# # Default fallback memcap and hashsize values for datasets in case these +# # were not explicitly defined. +# defaults: +# memcap: 100mb +# hashsize: 2048 + +############################################################################## +## +## Advanced settings below +## +############################################################################## + +## +## Run Options +## + +# Run Suricata with a specific user-id and group-id: +#run-as: +# user: suri +# group: suri + +# Some logging modules will use that name in event as identifier. The default +# value is the hostname +#sensor-name: suricata + +# Default location of the pid file. The pid file is only used in +# daemon mode (start Suricata with -D). If not running in daemon mode +# the --pidfile command line option must be used to create a pid file. +#pid-file: /var/run/suricata.pid + +# Daemon working directory +# Suricata will change directory to this one if provided +# Default: "/" +#daemon-directory: "/" + +# Umask. +# Suricata will use this umask if it is provided. By default it will use the +# umask passed on by the shell. +#umask: 022 + +# Suricata core dump configuration. Limits the size of the core dump file to +# approximately max-dump. The actual core dump size will be a multiple of the +# page size. Core dumps that would be larger than max-dump are truncated. On +# Linux, the actual core dump size may be a few pages larger than max-dump. +# Setting max-dump to 0 disables core dumping. +# Setting max-dump to 'unlimited' will give the full core dump file. +# On 32-bit Linux, a max-dump value >= ULONG_MAX may cause the core dump size +# to be 'unlimited'. + +coredump: + max-dump: unlimited + +# If the Suricata box is a router for the sniffed networks, set it to 'router'. If +# it is a pure sniffing setup, set it to 'sniffer-only'. +# If set to auto, the variable is internally switched to 'router' in IPS mode +# and 'sniffer-only' in IDS mode. +# This feature is currently only used by the reject* keywords. +host-mode: auto + +# Number of packets preallocated per thread. The default is 1024. A higher number +# will make sure each CPU will be more easily kept busy, but may negatively +# impact caching. +#max-pending-packets: 1024 + +# Runmode the engine should use. Please check --list-runmodes to get the available +# runmodes for each packet acquisition method. Default depends on selected capture +# method. 'workers' generally gives best performance. +#runmode: autofp + +# Specifies the kind of flow load balancer used by the flow pinned autofp mode. +# +# Supported schedulers are: +# +# hash - Flow assigned to threads using the 5-7 tuple hash. +# ippair - Flow assigned to threads using addresses only. +# +#autofp-scheduler: hash + +# Preallocated size for each packet. Default is 1514 which is the classical +# size for pcap on Ethernet. You should adjust this value to the highest +# packet size (MTU + hardware header) on your system. +#default-packet-size: 1514 + +# Unix command socket that can be used to pass commands to Suricata. +# An external tool can then connect to get information from Suricata +# or trigger some modifications of the engine. Set enabled to yes +# to activate the feature. In auto mode, the feature will only be +# activated in live capture mode. You can use the filename variable to set +# the file name of the socket. +unix-command: + enabled: yes + filename: /run/suricata/suricata.socket + +# Magic file. The extension .mgc is added to the value here. +#magic-file: /usr/share/file/magic +#magic-file: + +# GeoIP2 database file. Specify path and filename of GeoIP2 database +# if using rules with "geoip" rule option. +#geoip-database: /usr/local/share/GeoLite2/GeoLite2-Country.mmdb + +legacy: + uricontent: enabled + +## +## Detection settings +## + +# Set the order of alerts based on actions +# The default order is pass, drop, reject, alert +# action-order: +# - pass +# - drop +# - reject +# - alert + +# Define maximum number of possible alerts that can be triggered for the same +# packet. Default is 15 +#packet-alert-max: 15 + +# IP Reputation +#reputation-categories-file: /etc/suricata/iprep/categories.txt +#default-reputation-path: /etc/suricata/iprep +#reputation-files: +# - reputation.list + +# When run with the option --engine-analysis, the engine will read each of +# the parameters below, and print reports for each of the enabled sections +# and exit. The reports are printed to a file in the default log dir +# given by the parameter "default-log-dir", with engine reporting +# subsection below printing reports in its own report file. +engine-analysis: + # enables printing reports for fast-pattern for every rule. + rules-fast-pattern: yes + # enables printing reports for each rule + rules: yes + +#recursion and match limits for PCRE where supported +pcre: + match-limit: 3500 + match-limit-recursion: 1500 + +## +## Advanced Traffic Tracking and Reconstruction Settings +## + +# Host specific policies for defragmentation and TCP stream +# reassembly. The host OS lookup is done using a radix tree, just +# like a routing table so the most specific entry matches. +host-os-policy: + # Make the default policy windows. + windows: [0.0.0.0/0] + bsd: [] + bsd-right: [] + old-linux: [] + linux: [] + old-solaris: [] + solaris: [] + hpux10: [] + hpux11: [] + irix: [] + macos: [] + vista: [] + windows2k3: [] + +# Defrag settings: + +# The memcap-policy value can be "drop-flow", "pass-flow", "bypass", +# "drop-packet", "pass-packet", "reject" or "ignore" (which is the default). +defrag: + memcap: 32mb + # memcap-policy: ignore + hash-size: 65536 + trackers: 65535 # number of defragmented flows to follow + max-frags: 65535 # number of fragments to keep (higher than trackers) + prealloc: yes + timeout: 60 + +# Enable defrag per host settings +# host-config: +# +# - dmz: +# timeout: 30 +# address: [192.168.1.0/24, 127.0.0.0/8, 1.1.1.0/24, 2.2.2.0/24, "1.1.1.1", "2.2.2.2", "::1"] +# +# - lan: +# timeout: 45 +# address: +# - 192.168.0.0/24 +# - 192.168.10.0/24 +# - 172.16.14.0/24 + +# Flow settings: +# By default, the reserved memory (memcap) for flows is 32MB. This is the limit +# for flow allocation inside the engine. You can change this value to allow +# more memory usage for flows. +# The hash-size determines the size of the hash used to identify flows inside +# the engine, and by default the value is 65536. +# At startup, the engine can preallocate a number of flows, to get better +# performance. The number of flows preallocated is 10000 by default. +# emergency-recovery is the percentage of flows that the engine needs to +# prune before clearing the emergency state. The emergency state is activated +# when the memcap limit is reached, allowing new flows to be created, but +# pruning them with the emergency timeouts (they are defined below). +# If the memcap is reached, the engine will try to prune flows +# with the default timeouts. If it doesn't find a flow to prune, it will set +# the emergency bit and it will try again with more aggressive timeouts. +# If that doesn't work, then it will try to kill the oldest flows using +# last time seen flows. +# The memcap can be specified in kb, mb, gb. Just a number indicates it's +# in bytes. +# The memcap-policy can be "drop-flow", "pass-flow", "bypass", "drop-packet", +# "pass-packet", "reject" or "ignore" (which is the default). + +flow: + memcap: 128mb + #memcap-policy: ignore + hash-size: 65536 + prealloc: 10000 + emergency-recovery: 30 + #managers: 1 # default to one flow manager + #recyclers: 1 # default to one flow recycler thread + +# This option controls the use of VLAN ids in the flow (and defrag) +# hashing. Normally this should be enabled, but in some (broken) +# setups where both sides of a flow are not tagged with the same VLAN +# tag, we can ignore the VLAN id's in the flow hashing. +vlan: + use-for-tracking: true + +# Specific timeouts for flows. Here you can specify the timeouts that the +# active flows will wait to transit from the current state to another, on each +# protocol. The value of "new" determines the seconds to wait after a handshake or +# stream startup before the engine frees the data of that flow it doesn't +# change the state to established (usually if we don't receive more packets +# of that flow). The value of "established" is the amount of +# seconds that the engine will wait to free the flow if that time elapses +# without receiving new packets or closing the connection. "closed" is the +# amount of time to wait after a flow is closed (usually zero). "bypassed" +# timeout controls locally bypassed flows. For these flows we don't do any other +# tracking. If no packets have been seen after this timeout, the flow is discarded. +# +# There's an emergency mode that will become active under attack circumstances, +# making the engine to check flow status faster. This configuration variables +# use the prefix "emergency-" and work similar as the normal ones. +# Some timeouts doesn't apply to all the protocols, like "closed", for udp and +# icmp. + +flow-timeouts: + + default: + new: 30 + established: 300 + closed: 0 + bypassed: 100 + emergency-new: 10 + emergency-established: 100 + emergency-closed: 0 + emergency-bypassed: 50 + tcp: + new: 60 + established: 600 + closed: 60 + bypassed: 100 + emergency-new: 5 + emergency-established: 100 + emergency-closed: 10 + emergency-bypassed: 50 + udp: + new: 30 + established: 300 + bypassed: 100 + emergency-new: 10 + emergency-established: 100 + emergency-bypassed: 50 + icmp: + new: 30 + established: 300 + bypassed: 100 + emergency-new: 10 + emergency-established: 100 + emergency-bypassed: 50 + +# Stream engine settings. Here the TCP stream tracking and reassembly +# engine is configured. +# +# stream: +# memcap: 64mb # Can be specified in kb, mb, gb. Just a +# # number indicates it's in bytes. +# memcap-policy: ignore # Can be "drop-flow", "pass-flow", "bypass", +# # "drop-packet", "pass-packet", "reject" or +# # "ignore" default is "ignore" +# checksum-validation: yes # To validate the checksum of received +# # packet. If csum validation is specified as +# # "yes", then packets with invalid csum values will not +# # be processed by the engine stream/app layer. +# # Warning: locally generated traffic can be +# # generated without checksum due to hardware offload +# # of checksum. You can control the handling of checksum +# # on a per-interface basis via the 'checksum-checks' +# # option +# prealloc-sessions: 2k # 2k sessions prealloc'd per stream thread +# midstream: false # don't allow midstream session pickups +# midstream-policy: ignore # Can be "drop-flow", "pass-flow", "bypass", +# # "drop-packet", "pass-packet", "reject" or +# # "ignore" default is "ignore" +# async-oneside: false # don't enable async stream handling +# inline: no # stream inline mode +# drop-invalid: yes # in inline mode, drop packets that are invalid with regards to streaming engine +# max-synack-queued: 5 # Max different SYN/ACKs to queue +# bypass: no # Bypass packets when stream.reassembly.depth is reached. +# # Warning: first side to reach this triggers +# # the bypass. +# +# reassembly: +# memcap: 256mb # Can be specified in kb, mb, gb. Just a number +# # indicates it's in bytes. +# memcap-policy: ignore # Can be "drop-flow", "pass-flow", "bypass", +# # "drop-packet", "pass-packet", "reject" or +# # "ignore" default is "ignore" +# depth: 1mb # Can be specified in kb, mb, gb. Just a number +# # indicates it's in bytes. +# toserver-chunk-size: 2560 # inspect raw stream in chunks of at least +# # this size. Can be specified in kb, mb, +# # gb. Just a number indicates it's in bytes. +# toclient-chunk-size: 2560 # inspect raw stream in chunks of at least +# # this size. Can be specified in kb, mb, +# # gb. Just a number indicates it's in bytes. +# randomize-chunk-size: yes # Take a random value for chunk size around the specified value. +# # This lowers the risk of some evasion techniques but could lead +# # to detection change between runs. It is set to 'yes' by default. +# randomize-chunk-range: 10 # If randomize-chunk-size is active, the value of chunk-size is +# # a random value between (1 - randomize-chunk-range/100)*toserver-chunk-size +# # and (1 + randomize-chunk-range/100)*toserver-chunk-size and the same +# # calculation for toclient-chunk-size. +# # Default value of randomize-chunk-range is 10. +# +# raw: yes # 'Raw' reassembly enabled or disabled. +# # raw is for content inspection by detection +# # engine. +# +# segment-prealloc: 2048 # number of segments preallocated per thread +# +# check-overlap-different-data: true|false +# # check if a segment contains different data +# # than what we've already seen for that +# # position in the stream. +# # This is enabled automatically if inline mode +# # is used or when stream-event:reassembly_overlap_different_data; +# # is used in a rule. +# +stream: + memcap: 64mb + #memcap-policy: ignore + checksum-validation: yes # reject incorrect csums + #midstream: false + #midstream-policy: ignore + inline: auto # auto will use inline mode in IPS mode, yes or no set it statically + reassembly: + memcap: 256mb + #memcap-policy: ignore + depth: 1mb # reassemble 1mb into a stream + toserver-chunk-size: 2560 + toclient-chunk-size: 2560 + randomize-chunk-size: yes + #randomize-chunk-range: 10 + #raw: yes + #segment-prealloc: 2048 + #check-overlap-different-data: true + +# Host table: +# +# Host table is used by the tagging and per host thresholding subsystems. +# +host: + hash-size: 4096 + prealloc: 1000 + memcap: 32mb + +# IP Pair table: +# +# Used by xbits 'ippair' tracking. +# +#ippair: +# hash-size: 4096 +# prealloc: 1000 +# memcap: 32mb + +# Decoder settings + +decoder: + # Teredo decoder is known to not be completely accurate + # as it will sometimes detect non-teredo as teredo. + teredo: + enabled: true + # ports to look for Teredo. Max 4 ports. If no ports are given, or + # the value is set to 'any', Teredo detection runs on _all_ UDP packets. + ports: $TEREDO_PORTS # syntax: '[3544, 1234]' or '3533' or 'any'. + + # VXLAN decoder is assigned to up to 4 UDP ports. By default only the + # IANA assigned port 4789 is enabled. + vxlan: + enabled: true + ports: $VXLAN_PORTS # syntax: '[8472, 4789]' or '4789'. + + # VNTag decode support + vntag: + enabled: false + + # Geneve decoder is assigned to up to 4 UDP ports. By default only the + # IANA assigned port 6081 is enabled. + geneve: + enabled: true + ports: $GENEVE_PORTS # syntax: '[6081, 1234]' or '6081'. + + # maximum number of decoder layers for a packet + # max-layers: 16 + +## +## Performance tuning and profiling +## + +# The detection engine builds internal groups of signatures. The engine +# allows us to specify the profile to use for them, to manage memory in an +# efficient way keeping good performance. For the profile keyword you +# can use the words "low", "medium", "high" or "custom". If you use custom, +# make sure to define the values in the "custom-values" section. +# Usually you would prefer medium/high/low. +# +# "sgh mpm-context", indicates how the staging should allot mpm contexts for +# the signature groups. "single" indicates the use of a single context for +# all the signature group heads. "full" indicates a mpm-context for each +# group head. "auto" lets the engine decide the distribution of contexts +# based on the information the engine gathers on the patterns from each +# group head. +# +# The option inspection-recursion-limit is used to limit the recursive calls +# in the content inspection code. For certain payload-sig combinations, we +# might end up taking too much time in the content inspection code. +# If the argument specified is 0, the engine uses an internally defined +# default limit. When a value is not specified, there are no limits on the recursion. +detect: + profile: medium + custom-values: + toclient-groups: 3 + toserver-groups: 25 + sgh-mpm-context: auto + inspection-recursion-limit: 3000 + # If set to yes, the loading of signatures will be made after the capture + # is started. This will limit the downtime in IPS mode. + #delayed-detect: yes + + prefilter: + # default prefiltering setting. "mpm" only creates MPM/fast_pattern + # engines. "auto" also sets up prefilter engines for other keywords. + # Use --list-keywords=all to see which keywords support prefiltering. + default: mpm + + # the grouping values above control how many groups are created per + # direction. Port whitelisting forces that port to get its own group. + # Very common ports will benefit, as well as ports with many expensive + # rules. + grouping: + #tcp-whitelist: 53, 80, 139, 443, 445, 1433, 3306, 3389, 6666, 6667, 8080 + #udp-whitelist: 53, 135, 5060 + + profiling: + # Log the rules that made it past the prefilter stage, per packet + # default is off. The threshold setting determines how many rules + # must have made it past pre-filter for that rule to trigger the + # logging. + #inspect-logging-threshold: 200 + grouping: + dump-to-disk: false + include-rules: false # very verbose + include-mpm-stats: false + +# Select the multi pattern algorithm you want to run for scan/search the +# in the engine. +# +# The supported algorithms are: +# "ac" - Aho-Corasick, default implementation +# "ac-bs" - Aho-Corasick, reduced memory implementation +# "ac-ks" - Aho-Corasick, "Ken Steele" variant +# "hs" - Hyperscan, available when built with Hyperscan support +# +# The default mpm-algo value of "auto" will use "hs" if Hyperscan is +# available, "ac" otherwise. +# +# The mpm you choose also decides the distribution of mpm contexts for +# signature groups, specified by the conf - "detect.sgh-mpm-context". +# Selecting "ac" as the mpm would require "detect.sgh-mpm-context" +# to be set to "single", because of ac's memory requirements, unless the +# ruleset is small enough to fit in memory, in which case one can +# use "full" with "ac". The rest of the mpms can be run in "full" mode. + +mpm-algo: auto + +# Select the matching algorithm you want to use for single-pattern searches. +# +# Supported algorithms are "bm" (Boyer-Moore) and "hs" (Hyperscan, only +# available if Suricata has been built with Hyperscan support). +# +# The default of "auto" will use "hs" if available, otherwise "bm". + +spm-algo: auto + +# Suricata is multi-threaded. Here the threading can be influenced. +threading: + set-cpu-affinity: no + # Tune cpu affinity of threads. Each family of threads can be bound + # to specific CPUs. + # + # These 2 apply to the all runmodes: + # management-cpu-set is used for flow timeout handling, counters + # worker-cpu-set is used for 'worker' threads + # + # Additionally, for autofp these apply: + # receive-cpu-set is used for capture threads + # verdict-cpu-set is used for IPS verdict threads + # + cpu-affinity: + - management-cpu-set: + cpu: [ 0 ] # include only these CPUs in affinity settings + - receive-cpu-set: + cpu: [ 0 ] # include only these CPUs in affinity settings + - worker-cpu-set: + cpu: [ "all" ] + mode: "exclusive" + # Use explicitly 3 threads and don't compute number by using + # detect-thread-ratio variable: + # threads: 3 + prio: + low: [ 0 ] + medium: [ "1-2" ] + high: [ 3 ] + default: "medium" + #- verdict-cpu-set: + # cpu: [ 0 ] + # prio: + # default: "high" + # + # By default Suricata creates one "detect" thread per available CPU/CPU core. + # This setting allows controlling this behaviour. A ratio setting of 2 will + # create 2 detect threads for each CPU/CPU core. So for a dual core CPU this + # will result in 4 detect threads. If values below 1 are used, less threads + # are created. So on a dual core CPU a setting of 0.5 results in 1 detect + # thread being created. Regardless of the setting at a minimum 1 detect + # thread will always be created. + # + detect-thread-ratio: 1.0 + # + # By default, the per-thread stack size is left to its default setting. If + # the default thread stack size is too small, use the following configuration + # setting to change the size. Note that if any thread's stack size cannot be + # set to this value, a fatal error occurs. + # + # Generally, the per-thread stack-size should not exceed 8MB. + #stack-size: 8mb + +# Luajit has a strange memory requirement, its 'states' need to be in the +# first 2G of the process' memory. +# +# 'luajit.states' is used to control how many states are preallocated. +# State use: per detect script: 1 per detect thread. Per output script: 1 per +# script. +luajit: + states: 128 + +# Profiling settings. Only effective if Suricata has been built with +# the --enable-profiling configure flag. +# +profiling: + # Run profiling for every X-th packet. The default is 1, which means we + # profile every packet. If set to 1000, one packet is profiled for every + # 1000 received. + #sample-rate: 1000 + + # rule profiling + rules: + + # Profiling can be disabled here, but it will still have a + # performance impact if compiled in. + enabled: yes + filename: rule_perf.log + append: yes + + # Sort options: ticks, avgticks, checks, matches, maxticks + # If commented out all the sort options will be used. + #sort: avgticks + + # Limit the number of sids for which stats are shown at exit (per sort). + limit: 10 + + # output to json + json: yes + + # per keyword profiling + keywords: + enabled: yes + filename: keyword_perf.log + append: yes + + prefilter: + enabled: yes + filename: prefilter_perf.log + append: yes + + # per rulegroup profiling + rulegroups: + enabled: yes + filename: rule_group_perf.log + append: yes + + # packet profiling + packets: + + # Profiling can be disabled here, but it will still have a + # performance impact if compiled in. + enabled: yes + filename: packet_stats.log + append: yes + + # per packet csv output + csv: + + # Output can be disabled here, but it will still have a + # performance impact if compiled in. + enabled: no + filename: packet_stats.csv + + # profiling of locking. Only available when Suricata was built with + # --enable-profiling-locks. + locks: + enabled: no + filename: lock_stats.log + append: yes + + pcap-log: + enabled: no + filename: pcaplog_stats.log + append: yes + +## +## Netfilter integration +## + +# When running in NFQ inline mode, it is possible to use a simulated +# non-terminal NFQUEUE verdict. +# This permits sending all needed packet to Suricata via this rule: +# iptables -I FORWARD -m mark ! --mark $MARK/$MASK -j NFQUEUE +# And below, you can have your standard filtering ruleset. To activate +# this mode, you need to set mode to 'repeat' +# If you want a packet to be sent to another queue after an ACCEPT decision +# set the mode to 'route' and set next-queue value. +# On Linux >= 3.1, you can set batchcount to a value > 1 to improve performance +# by processing several packets before sending a verdict (worker runmode only). +# On Linux >= 3.6, you can set the fail-open option to yes to have the kernel +# accept the packet if Suricata is not able to keep pace. +# bypass mark and mask can be used to implement NFQ bypass. If bypass mark is +# set then the NFQ bypass is activated. Suricata will set the bypass mark/mask +# on packet of a flow that need to be bypassed. The Nefilter ruleset has to +# directly accept all packets of a flow once a packet has been marked. +nfq: +# mode: accept +# repeat-mark: 1 +# repeat-mask: 1 +# bypass-mark: 1 +# bypass-mask: 1 +# route-queue: 2 +# batchcount: 20 +# fail-open: yes + +#nflog support +nflog: + # netlink multicast group + # (the same as the iptables --nflog-group param) + # Group 0 is used by the kernel, so you can't use it + - group: 2 + # netlink buffer size + buffer-size: 18432 + # put default value here + - group: default + # set number of packets to queue inside kernel + qthreshold: 1 + # set the delay before flushing packet in the kernel's queue + qtimeout: 100 + # netlink max buffer size + max-size: 20000 + +## +## Advanced Capture Options +## + +# General settings affecting packet capture +capture: + # disable NIC offloading. It's restored when Suricata exits. + # Enabled by default. + #disable-offloading: false + # + # disable checksum validation. Same as setting '-k none' on the + # commandline. + #checksum-validation: none + +# Netmap support +# +# Netmap operates with NIC directly in driver, so you need FreeBSD 11+ which has +# built-in Netmap support or compile and install the Netmap module and appropriate +# NIC driver for your Linux system. +# To reach maximum throughput disable all receive-, segmentation-, +# checksum- offloading on your NIC (using ethtool or similar). +# Disabling TX checksum offloading is *required* for connecting OS endpoint +# with NIC endpoint. +# You can find more information at https://github.com/luigirizzo/netmap +# +netmap: + - interface: default + +# PF_RING configuration: for use with native PF_RING support +# for more info see http://www.ntop.org/products/pf_ring/ +pfring: + - interface: default + #threads: 2 + +# For FreeBSD ipfw(8) divert(4) support. +# Please make sure you have ipfw_load="YES" and ipdivert_load="YES" +# in /etc/loader.conf or kldload'ing the appropriate kernel modules. +# Additionally, you need to have an ipfw rule for the engine to see +# the packets from ipfw. For Example: +# +# ipfw add 100 divert 8000 ip from any to any +# +# N.B. This example uses "8000" -- this number must mach the values +# you passed on the command line, i.e., -d 8000 +# +ipfw: + + # Reinject packets at the specified ipfw rule number. This config + # option is the ipfw rule number AT WHICH rule processing continues + # in the ipfw processing system after the engine has finished + # inspecting the packet for acceptance. If no rule number is specified, + # accepted packets are reinjected at the divert rule which they entered + # and IPFW rule processing continues. No check is done to verify + # this will rule makes sense so care must be taken to avoid loops in ipfw. + # + ## The following example tells the engine to reinject packets + # back into the ipfw firewall AT rule number 5500: + # + # ipfw-reinjection-rule-number: 5500 + + +napatech: + # When use_all_streams is set to "yes" the initialization code will query + # the Napatech service for all configured streams and listen on all of them. + # When set to "no" the streams config array will be used. + # + # This option necessitates running the appropriate NTPL commands to create + # the desired streams prior to running Suricata. + #use-all-streams: no + + # The streams to listen on when auto-config is disabled or when and threading + # cpu-affinity is disabled. This can be either: + # an individual stream (e.g. streams: [0]) + # or + # a range of streams (e.g. streams: ["0-3"]) + # + streams: ["0-3"] + + # Stream stats can be enabled to provide fine grain packet and byte counters + # for each thread/stream that is configured. + # + enable-stream-stats: no + + # When auto-config is enabled the streams will be created and assigned + # automatically to the NUMA node where the thread resides. If cpu-affinity + # is enabled in the threading section. Then the streams will be created + # according to the number of worker threads specified in the worker-cpu-set. + # Otherwise, the streams array is used to define the streams. + # + # This option is intended primarily to support legacy configurations. + # + # This option cannot be used simultaneously with either "use-all-streams" + # or "hardware-bypass". + # + auto-config: yes + + # Enable hardware level flow bypass. + # + hardware-bypass: yes + + # Enable inline operation. When enabled traffic arriving on a given port is + # automatically forwarded out its peer port after analysis by Suricata. + # + inline: no + + # Ports indicates which Napatech ports are to be used in auto-config mode. + # these are the port IDs of the ports that will be merged prior to the + # traffic being distributed to the streams. + # + # When hardware-bypass is enabled the ports must be configured as a segment. + # specify the port(s) on which upstream and downstream traffic will arrive. + # This information is necessary for the hardware to properly process flows. + # + # When using a tap configuration one of the ports will receive inbound traffic + # for the network and the other will receive outbound traffic. The two ports on a + # given segment must reside on the same network adapter. + # + # When using a SPAN-port configuration the upstream and downstream traffic + # arrives on a single port. This is configured by setting the two sides of the + # segment to reference the same port. (e.g. 0-0 to configure a SPAN port on + # port 0). + # + # port segments are specified in the form: + # ports: [0-1,2-3,4-5,6-6,7-7] + # + # For legacy systems when hardware-bypass is disabled this can be specified in any + # of the following ways: + # + # a list of individual ports (e.g. ports: [0,1,2,3]) + # + # a range of ports (e.g. ports: [0-3]) + # + # "all" to indicate that all ports are to be merged together + # (e.g. ports: [all]) + # + # This parameter has no effect if auto-config is disabled. + # + ports: [0-1,2-3] + + # When auto-config is enabled the hashmode specifies the algorithm for + # determining to which stream a given packet is to be delivered. + # This can be any valid Napatech NTPL hashmode command. + # + # The most common hashmode commands are: hash2tuple, hash2tuplesorted, + # hash5tuple, hash5tuplesorted and roundrobin. + # + # See Napatech NTPL documentation other hashmodes and details on their use. + # + # This parameter has no effect if auto-config is disabled. + # + hashmode: hash5tuplesorted + +## +## Configure Suricata to load Suricata-Update managed rules. +## + +# As VyOS leverages suricata-update, the default rule path points to the +# generated rules instead of the built-in rules. +# +# default-rule-path: /etc/suricata/rules +default-rule-path: /var/lib/suricata/rules + +rule-files: + - suricata.rules + +## +## Auxiliary configuration files. +## + +# As VyOS leverages suricata-update, the classification file points to the +# generated classification instead of the built-in one. +# +# classification-file: /etc/suricata/classification.config +classification-file: /var/lib/suricata/rules/classification.config +reference-config-file: /etc/suricata/reference.config +# threshold-file: /etc/suricata/threshold.config + +## +## Include other configs +## + +# Includes: Files included here will be handled as if they were in-lined +# in this configuration file. Files with relative pathnames will be +# searched for in the same directory as this configuration file. You may +# use absolute pathnames too. +# You can specify more than 2 configuration files, if needed. +#include: include1.yaml +#include: include2.yaml diff --git a/data/templates/ids/suricata_logrotate.j2 b/data/templates/ids/suricata_logrotate.j2 new file mode 100644 index 0000000..62773fc --- /dev/null +++ b/data/templates/ids/suricata_logrotate.j2 @@ -0,0 +1,17 @@ +{% for filename in [(log.eve.filename | default("eve.json"))] %} +{{ filename if filename.startswith("/") else ("/var/log/suricata/" + filename) }} +{% endfor %}{ + weekly + dateext + dateformat _%Y-%m-%d_%H-%M-%S + maxsize 10M + rotate 10 + missingok + nocompress + nocreate + nomail + sharedscripts + postrotate + /bin/kill -HUP `cat /run/suricata/suricata.pid 2>/dev/null` 2>/dev/null || true + endscript +} diff --git a/data/templates/stunnel/stunnel_config.j2 b/data/templates/stunnel/stunnel_config.j2 new file mode 100644 index 0000000..52c289f --- /dev/null +++ b/data/templates/stunnel/stunnel_config.j2 @@ -0,0 +1,118 @@ +; Autogenerated by service_stunnel.py + +; Example https://www.stunnel.org/config_unix.html# +; ************************************************************************** +; * Global options * +; ************************************************************************** + +; PID file is created inside the chroot jail (if enabled) +pid = {{ config_file | replace('.conf', '.pid') }} + +; Debugging stuff (may be useful for troubleshooting) +;foreground = yes + +{% if log is vyos_defined %} +debug = {{ log.level }} +{% endif %} + +;output = /usr/local/var/log/stunnel.log + + +; ************************************************************************** +; * Service definitions * +; ************************************************************************** + +; ***************************************** Client mode services *********** + +{% if client is vyos_defined %} +{% for name, config in client.items() %} +[{{ name }}] +client = yes +{% if config.listen.address is vyos_defined %} +accept = {{ config.listen.address }}:{{ config.listen.port }} +{% else %} +accept = {{ config.listen.port }} +{% endif %} +{% if config.connect is vyos_defined %} +{% if config.connect.address is vyos_defined %} +connect = {{ config.connect.address }}:{{ config.connect.port }} +{% else %} +connect = {{ config.connect.port }} +{% endif %} +{% endif %} +{% if config.protocol is vyos_defined %} +protocol = {{ config.protocol }} +{% endif %} +{% if config.options is vyos_defined %} +{% if config.options.authentication is vyos_defined %} +protocolAuthentication = {{ config.options.authentication }} +{% endif %} +{% if config.options.domain is vyos_defined %} +protocolDomain = {{ config.options.domain }} +{% endif %} +{% if config.options.host is vyos_defined %} +protocolHost = {{ config.options.host.address }}:{{ config.options.host.port }} +{% endif %} +{% if config.options.password is vyos_defined %} +protocolPassword = {{ config.options.password }} +{% endif %} +{% if config.options.username is vyos_defined %} +protocolUsername = {{ config.options.username }} +{% endif %} +{% endif %} +{% if config.ssl.ca_path is vyos_defined %} +CApath = {{ config.ssl.ca_path }} +{% endif %} +{% if config.ssl.ca_file is vyos_defined %} +CAfile = {{ config.ssl.ca_file }} +{% endif %} +{% if config.ssl.cert is vyos_defined %} +cert = {{ config.ssl.cert }} +{% endif %} +{% if config.ssl.cert_key is vyos_defined %} +key = {{ config.ssl.cert_key }} +{% endif %} +{% if config.psk.file is vyos_defined %} +PSKsecrets = {{ config.psk.file }} +{% endif %} +{% endfor %} +{% endif %} + + +; ***************************************** Server mode services *********** + +{% if server is vyos_defined %} +{% for name, config in server.items() %} +[{{ name }}] +{% if config.listen.address is vyos_defined %} +accept = {{ config.listen.address }}:{{ config.listen.port }} +{% else %} +accept = {{ config.listen.port }} +{% endif %} +{% if config.connect is vyos_defined %} +{% if config.connect.address is vyos_defined %} +connect = {{ config.connect.address }}:{{ config.connect.port }} +{% else %} +connect = {{ config.connect.port }} +{% endif %} +{% endif %} +{% if config.protocol is vyos_defined %} +protocol = {{ config.protocol }} +{% endif %} +{% if config.ssl.ca_path is vyos_defined %} +CApath = {{ config.ssl.ca_path }} +{% endif %} +{% if config.ssl.ca_file is vyos_defined %} +CAfile = {{ config.ssl.ca_file }} +{% endif %} +{% if config.ssl.cert is vyos_defined %} +cert = {{ config.ssl.cert }} +{% endif %} +{% if config.ssl.cert_key is vyos_defined %} +key = {{ config.ssl.cert_key }} +{% endif %} +{% if config.psk.file is vyos_defined %} +PSKsecrets = {{ config.psk.file }} +{% endif %} +{% endfor %} +{% endif %} diff --git a/data/templates/system/40_usb_autosuspend.j2 b/data/templates/system/40_usb_autosuspend.j2 new file mode 100644 index 0000000..01ba864 --- /dev/null +++ b/data/templates/system/40_usb_autosuspend.j2 @@ -0,0 +1,5 @@ +{% set autosuspend = "auto" %} +{% if disable_usb_autosuspend is vyos_defined %} +{% set autosuspend = "on" %} +{% endif %} +ACTION=="add", SUBSYSTEM=="usb", TEST=="power/control", ATTR{power/control}="{{ autosuspend }}" diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..d64c668 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,10 @@ +vyos-1x (1.5dev0) unstable; urgency=medium + + * Dummy changelog entry for vyos-1x repository + This is a internal VyOS package and the VyOS package process does not use + the debian package changelog for its changes, please refer to the + GitHub commitlog and the vyos release-notes for more details. + The correct verion number of this package is auto-generated by GIT + on build-time + + -- VyOS maintainers and contributors <maintainers@vyos.io> Sun, 10 Sep 2023 15:42:53 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..48082f7 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +12 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..f8cfb87 --- /dev/null +++ b/debian/control @@ -0,0 +1,384 @@ +Source: vyos-1x +Section: contrib/net +Priority: extra +Maintainer: VyOS Package Maintainers <maintainers@vyos.net> +Build-Depends: + debhelper (>= 9), + dh-python, + fakeroot, + gcc, + iproute2, + libvyosconfig0 (>= 0.0.7), + libzmq3-dev, + python3 (>= 3.10), +# For QA + pylint, +# For generating command definitions + python3-lxml, + python3-xmltodict, +# For running tests + python3-coverage, + python3-hurry.filesize, + python3-netaddr, + python3-netifaces, + python3-nose, + python3-jinja2, + python3-paramiko, + python3-passlib, + python3-psutil, + python3-requests, + python3-setuptools, + python3-tabulate, + python3-zmq, + quilt, + whois +Standards-Version: 3.9.6 + +Package: vyos-1x +Architecture: amd64 arm64 +Pre-Depends: + libpam-runtime [amd64], + libnss-tacplus [amd64], + libpam-tacplus [amd64], + libpam-radius-auth [amd64] +Depends: +## Fundamentals + ${python3:Depends} (>= 3.10), + dialog, + libvyosconfig0, + libpam-cap, + bash-completion, + ipvsadm, + udev, + less, + at, + rsync, + vyatta-bash, + vyatta-biosdevname, + vyatta-cfg, + vyos-http-api-tools, + vyos-utils, +## End of Fundamentals +## Python libraries used in multiple modules and scripts + python3, + python3-cryptography, + python3-hurry.filesize, + python3-inotify, + python3-jinja2, + python3-jmespath, + python3-netaddr, + python3-netifaces, + python3-paramiko, + python3-passlib, + python3-pyroute2, + python3-psutil, + python3-pyhumps, + python3-pystache, + python3-pyudev, + python3-six, + python3-tabulate, + python3-voluptuous, + python3-xmltodict, + python3-zmq, +## End of Python libraries +## Basic System services and utilities + coreutils, + sudo, + systemd, + bsdmainutils, + openssl, + curl, + dbus, + file, + iproute2 (>= 6.0.0), + linux-cpupower, +# ipaddrcheck is widely used in IP value validators + ipaddrcheck, + ethtool (>= 6.10), + lm-sensors, + procps, + netplug, + sed, + ssl-cert, + tuned, + beep, + wide-dhcpv6-client, +# Generic colorizer + grc, +## End of System services and utilities +## For the installer + fdisk, + gdisk, + mdadm, + efibootmgr, + libefivar1, + dosfstools, + grub-efi-amd64-signed [amd64], + grub-efi-arm64-bin [arm64], + mokutil [amd64], + shim-signed [amd64], + sbsigntool [amd64], +# Image signature verification tool + minisign, +# Live filesystem tools + squashfs-tools, + fuse-overlayfs, +## End installer + auditd, + iputils-arping, + iputils-ping, + isc-dhcp-client, +# For "vpn pptp", "vpn l2tp", "vpn sstp", "service ipoe-server" + accel-ppp, +# End "vpn pptp", "vpn l2tp", "vpn sstp", "service ipoe-server" + avahi-daemon, + conntrack, + conntrackd, +## Conf mode features +# For "interfaces wireless" + hostapd, + hsflowd, + iw, + wireless-regdb, + wpasupplicant (>= 0.6.7), +# End "interfaces wireless" +# For "interfaces wwan" + modemmanager, + usb-modeswitch, + libqmi-utils, +# End "interfaces wwan" +# For "interfaces openvpn" + openvpn, + openvpn-auth-ldap, + openvpn-auth-radius, + openvpn-otp, + openvpn-dco, + libpam-google-authenticator, +# End "interfaces openvpn" +# For "interfaces wireguard" + wireguard-tools, + qrencode, +# End "interfaces wireguard" +# For "interfaces pppoe" + pppoe, +# End "interfaces pppoe" +# For "interfaces sstpc" + sstp-client, +# End "interfaces sstpc" +# For "protocols *" + frr (>= 9.1), + frr-pythontools, + frr-rpki-rtrlib, + frr-snmp, +# End "protocols *" +# For "protocols nhrp" (part of DMVPN) + opennhrp, +# End "protocols nhrp" +# For "protocols igmp-proxy" + igmpproxy, +# End "protocols igmp-proxy" +# For "pki" + certbot, +# End "pki" +# For "service console-server" + conserver-client, + conserver-server, + console-data, + dropbear, +# End "service console-server" +# For "service aws glb" + aws-gwlbtun, +# For "service dns dynamic" + ddclient (>= 3.11.1), +# End "service dns dynamic" +# # For "service ids" + fastnetmon [amd64], + suricata, + suricata-update, +# End "service ids" +# # For "service ndp-proxy" + ndppd, +# End "service ndp-proxy" +# For "service router-advert" + radvd, +# End "service route-advert" +# For "load-balancing reverse-proxy" + haproxy, +# End "load-balancing reverse-proxy" +# For "load-balancing wan" + vyatta-wanloadbalance, +# End "load-balancing wan" +# For "service dhcp-relay" + isc-dhcp-relay, +# For "service dhcp-server" + kea, +# End "service dhcp-server" +# For "service lldp" + lldpd, +# End "service lldp" +# For "service https" + nginx-light, +# End "service https" +# For "service ssh" + openssh-server, + sshguard, +# End "service ssh" +# For "service salt-minion" + salt-minion, +# End "service salt-minion" +# For "service snmp" + snmp, + snmpd, +# End "service snmp" +# For "service webproxy" + squid, + squidclient, + squidguard, +# End "service webproxy" +# For "service monitoring telegraf" + telegraf (>= 1.20), +# End "service monitoring telegraf" +# For "service monitoring zabbix-agent" + zabbix-agent2, +# End "service monitoring zabbix-agent" +# For "service tftp-server" + tftpd-hpa, +# End "service tftp-server" +# For "service dns forwarding" + pdns-recursor, +# End "service dns forwarding" +# For "service sla owamp" + owamp-client, + owamp-server, +# End "service sla owamp" +# For "service sla twamp" + twamp-client, + twamp-server, +# End "service sla twamp" +# For "service broadcast-relay" + udp-broadcast-relay, +# End "service broadcast-relay" +# For "high-availability vrrp" + keepalived (>=2.0.5), +# End "high-availability-vrrp" +# For "system console" + util-linux, +# End "system console" +# For "system task-scheduler" + cron, +# End "system task-scheduler" +# For "system lcd" + lcdproc, + lcdproc-extra-drivers, +# End "system lcd" +# For "system config-management commit-archive" + git, +# End "system config-management commit-archive" +# For firewall + libndp-tools, + libnetfilter-conntrack3, + libnfnetlink0, + nfct, + nftables (>= 0.9.3), +# For "vpn ipsec" + strongswan (>= 5.9), + strongswan-swanctl (>= 5.9), + charon-systemd, + libcharon-extra-plugins (>=5.9), + libcharon-extauth-plugins (>=5.9), + libstrongswan-extra-plugins (>=5.9), + libstrongswan-standard-plugins (>=5.9), + python3-vici (>= 5.7.2), +# End "vpn ipsec" +# For "nat64" + jool, +# End "nat64" +# For "system conntrack modules rtsp" + nat-rtsp, +# End "system conntrack modules rtsp" +# For "service ntp" + chrony, +# End "system ntp" +# For "vpn openconnect" + ocserv, +# End "vpn openconnect" +# For "system flow-accounting" + pmacct (>= 1.6.0), +# End "system flow-accounting" +# For "system syslog" + rsyslog, +# End "system syslog" +# For "system option keyboard-layout" + kbd, +# End "system option keyboard-layout" +# For "container" + podman (>=4.9.5), + netavark, + aardvark-dns, +# iptables is only used for containers now, not the the firewall CLI + iptables, +# End container +## End Configuration mode +## Operational mode +# Used for hypervisor model in "run show version" + hvinfo, +# For "run traceroute" + traceroute, +# For "run monitor traffic" + tcpdump, +# End "run monitor traffic" +# For "show hardware dmi" + dmidecode, +# For "run show hardware storage smart" + smartmontools, +# For "run show hardware scsi" + lsscsi, +# For "run show hardware pci" + pciutils, +# For "show hardware usb" + usbutils, +# For "run show hardware storage nvme" + nvme-cli, +# For "run monitor bandwidth-test" + iperf, + iperf3, +# End "run monitor bandwidth-test" +# For "run wake-on-lan" + etherwake, +# For "run force ipv6-nd" + ndisc6, +# For "run monitor bandwidth" + bmon, +# For "run format disk" + parted, +# End Operational mode +## TPM tools + cryptsetup, + tpm2-tools, +## End TPM tools +## Optional utilities + easy-rsa, + tcptraceroute, + mtr-tiny, + telnet, + stunnel4, + uidmap +## End optional utilities +Description: VyOS configuration scripts and data + VyOS configuration scripts, interface definitions, and everything + +Package: vyos-1x-vmware +Architecture: amd64 +Depends: + vyos-1x, + open-vm-tools +Description: VyOS configuration scripts and data for VMware + Adds configuration files required for VyOS running on VMware hosts. + +Package: vyos-1x-smoketest +Architecture: all +Depends: + skopeo, + snmp, + vyos-1x +Description: VyOS build sanity checking toolkit diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..20704c4 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,35 @@ +This package was debianized by Daniil Baturin <daniil@baturin.org> on +Thu, 17 Aug 2017 20:17:04 -0400 + +It's original content from the GIT repository <http://github.com/vyos/vyos-1x> + +Upstream Author: + + <maintainers@vyos.net> + +Copyright: + + Copyright (C) 2017 VyOS maintainers and contributors + All Rights Reserved. + +License: + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +A copy of the GNU General Public License is available as +`/usr/share/common-licenses/GPL' in the Debian GNU/Linux distribution +or on the World Wide Web at `http://www.gnu.org/copyleft/gpl.html'. +You can also obtain it by writing to the Free Software Foundation, +Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, +MA 02110-1301, USA. + +The Debian packaging is (C) 2017, Daniil Baturin <daniil@baturin.org> and +is licensed under the GPL, see above. diff --git a/debian/lintian-overrides b/debian/lintian-overrides new file mode 100644 index 0000000..6c5d671 --- /dev/null +++ b/debian/lintian-overrides @@ -0,0 +1,6 @@ +# It's FSH compliant! +vyos-1x: file-in-unusual-dir usr/libexec/* +vyos-1x: non-standard-dir-in-usr usr/libexec/ + +# Nothing we can do about that right now +vyos-1x: dir-or-file-in-opt diff --git a/debian/rules b/debian/rules new file mode 100644 index 0000000..df1d9e7 --- /dev/null +++ b/debian/rules @@ -0,0 +1,143 @@ +#!/usr/bin/make -f + +DIR := debian/tmp +VYOS_SBIN_DIR := usr/sbin +VYOS_BIN_DIR := usr/bin +VYOS_LIBEXEC_DIR := usr/libexec/vyos +VYOS_DATA_DIR := usr/share/vyos +VYOS_CFG_TMPL_DIR := opt/vyatta/share/vyatta-cfg/templates +VYOS_OP_TMPL_DIR := opt/vyatta/share/vyatta-op/templates +VYOS_MIBS_DIR := usr/share/snmp/mibs +VYOS_LOCALUI_DIR := srv/localui + +MIGRATION_SCRIPTS_DIR := opt/vyatta/etc/config-migrate/migrate +ACTIVATION_SCRIPTS_DIR := usr/libexec/vyos/activate +SYSTEM_SCRIPTS_DIR := usr/libexec/vyos/system +SERVICES_DIR := usr/libexec/vyos/services + +DEB_TARGET_ARCH := $(shell dpkg-architecture -qDEB_TARGET_ARCH) + +%: + dh $@ --with python3, --with quilt + +# Skip dh_strip_nondeterminism - this is very time consuming +# and we have no non deterministic output (yet) +override_dh_strip_nondeterminism: + +override_dh_gencontrol: + dh_gencontrol -- -v$(shell (git describe --tags --long --match 'vyos/*' --match '1.4.*' --dirty 2>/dev/null || echo 0.0-no.git.tag) | sed -E 's%vyos/%%' | sed -E 's%-dirty%+dirty%') + +override_dh_auto_build: + make all + +override_dh_auto_install: + dh_auto_install + + cd python; python3 setup.py install --install-layout=deb --root ../$(DIR); cd .. + + # Install scripts + mkdir -p $(DIR)/$(VYOS_SBIN_DIR) + mkdir -p $(DIR)/$(VYOS_BIN_DIR) + cp -r src/utils/* $(DIR)/$(VYOS_BIN_DIR) + cp src/shim/vyshim $(DIR)/$(VYOS_SBIN_DIR) + + # Install conf mode scripts + mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/conf_mode + cp -r src/conf_mode/* $(DIR)/$(VYOS_LIBEXEC_DIR)/conf_mode + + # Install op mode scripts + mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/op_mode + cp -r src/op_mode/* $(DIR)/$(VYOS_LIBEXEC_DIR)/op_mode + + # Install op mode scripts + mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/init + cp -r src/init/* $(DIR)/$(VYOS_LIBEXEC_DIR)/init + + # Install validators + mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/validators + cp -r src/validators/* $(DIR)/$(VYOS_LIBEXEC_DIR)/validators + + # Install completion helpers + mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/completion + cp -r src/completion/* $(DIR)/$(VYOS_LIBEXEC_DIR)/completion + + # Install helper scripts + cp -r src/helpers/* $(DIR)/$(VYOS_LIBEXEC_DIR)/ + + # Install migration scripts + mkdir -p $(DIR)/$(MIGRATION_SCRIPTS_DIR) + cp -r src/migration-scripts/* $(DIR)/$(MIGRATION_SCRIPTS_DIR) + + # Install activation scripts + mkdir -p $(DIR)/$(ACTIVATION_SCRIPTS_DIR) + cp -r src/activation-scripts/* $(DIR)/$(ACTIVATION_SCRIPTS_DIR) + + # Install system scripts + mkdir -p $(DIR)/$(SYSTEM_SCRIPTS_DIR) + cp -r src/system/* $(DIR)/$(SYSTEM_SCRIPTS_DIR) + + # Install system services + mkdir -p $(DIR)/$(SERVICES_DIR) + cp -r src/services/* $(DIR)/$(SERVICES_DIR) + + # Install configuration command definitions + mkdir -p $(DIR)/$(VYOS_CFG_TMPL_DIR) + cp -r templates-cfg/* $(DIR)/$(VYOS_CFG_TMPL_DIR) + + # Install operational command definitions + mkdir -p $(DIR)/$(VYOS_OP_TMPL_DIR) + cp -r templates-op/* $(DIR)/$(VYOS_OP_TMPL_DIR) + + # Install data files + mkdir -p $(DIR)/$(VYOS_DATA_DIR) + cp -r data/* $(DIR)/$(VYOS_DATA_DIR) + + # Create localui dir + mkdir -p $(DIR)/$(VYOS_LOCALUI_DIR) + + # Install SNMP MIBs + mkdir -p $(DIR)/$(VYOS_MIBS_DIR) + cp -d mibs/* $(DIR)/$(VYOS_MIBS_DIR) + + # Install etc configuration files + mkdir -p $(DIR)/etc + cp -r src/etc/* $(DIR)/etc + + # Install legacy Vyatta files + mkdir -p $(DIR)/opt + cp -r src/opt/* $(DIR)/opt + + # Install PAM configuration snippets + mkdir -p $(DIR)/usr/share/pam-configs + cp -r src/pam-configs/* $(DIR)/usr/share/pam-configs + + # Install systemd service units + mkdir -p $(DIR)/lib/systemd/system + cp -r src/systemd/* $(DIR)/lib/systemd/system + + # Make directory for generated configuration file + mkdir -p $(DIR)/etc/vyos + + # Install smoke test scripts + mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/smoke/ + cp -r smoketest/scripts/* $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/smoke + + # Install smoke test configs + mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config/ + cp -r smoketest/configs/* $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config + + # Install smoke test config tests + mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config-tests/ + cp -r smoketest/config-tests/* $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config-tests + + # Install system programs + mkdir -p $(DIR)/$(VYOS_BIN_DIR) + cp -r smoketest/bin/* $(DIR)/$(VYOS_BIN_DIR) + + # Install udev script + mkdir -p $(DIR)/usr/lib/udev + cp src/helpers/vyos_net_name $(DIR)/usr/lib/udev + +override_dh_installsystemd: + dh_installsystemd -pvyos-1x --name vyos-router vyos-router.service + dh_installsystemd -pvyos-1x --name vyos vyos.target diff --git a/debian/vyos-1x-smoketest.install b/debian/vyos-1x-smoketest.install new file mode 100644 index 0000000..739cb18 --- /dev/null +++ b/debian/vyos-1x-smoketest.install @@ -0,0 +1,6 @@ +usr/bin/vyos-smoketest +usr/bin/vyos-configtest +usr/bin/vyos-configtest-pki +usr/libexec/vyos/tests/smoke +usr/libexec/vyos/tests/config +usr/libexec/vyos/tests/config-tests diff --git a/debian/vyos-1x-smoketest.postinst b/debian/vyos-1x-smoketest.postinst new file mode 100644 index 0000000..1861280 --- /dev/null +++ b/debian/vyos-1x-smoketest.postinst @@ -0,0 +1,10 @@ +#!/bin/sh -e + +BUSYBOX_TAG="docker.io/library/busybox:stable" +OUTPUT_PATH="/usr/share/vyos/busybox-stable.tar" + +if [[ -f $OUTPUT_PATH ]]; then + rm -f $OUTPUT_PATH +fi + +skopeo copy --additional-tag "$BUSYBOX_TAG" "docker://$BUSYBOX_TAG" "docker-archive:/$OUTPUT_PATH" diff --git a/debian/vyos-1x-vmware.install b/debian/vyos-1x-vmware.install new file mode 100644 index 0000000..7150156 --- /dev/null +++ b/debian/vyos-1x-vmware.install @@ -0,0 +1 @@ +etc/vmware-tools diff --git a/debian/vyos-1x-vmware.preinst b/debian/vyos-1x-vmware.preinst new file mode 100644 index 0000000..2e61252 --- /dev/null +++ b/debian/vyos-1x-vmware.preinst @@ -0,0 +1 @@ +dpkg-divert --package vyos-1x-vmware --add --rename /etc/vmware-tools/tools.conf diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install new file mode 100644 index 0000000..7171911 --- /dev/null +++ b/debian/vyos-1x.install @@ -0,0 +1,43 @@ +etc/bash_completion.d +etc/commit +etc/default +etc/dhcp +etc/ipsec.d +etc/logrotate.d +etc/netplug +etc/opennhrp +etc/modprobe.d +etc/ppp +etc/rsyslog.conf +etc/securetty +etc/security +etc/skel +etc/sudoers.d +etc/systemd +etc/sysctl.d +etc/telegraf +etc/udev +etc/update-motd.d +etc/vyos +lib/ +opt/ +srv/localui +usr/sbin +usr/bin/config-mgmt +usr/bin/initial-setup +usr/bin/vyos-config-file-query +usr/bin/vyos-config-to-commands +usr/bin/vyos-config-to-json +usr/bin/vyos-hostsd-client +usr/lib +usr/libexec/vyos/activate +usr/libexec/vyos/completion +usr/libexec/vyos/conf_mode +usr/libexec/vyos/init +usr/libexec/vyos/op_mode +usr/libexec/vyos/services +usr/libexec/vyos/system +usr/libexec/vyos/validators +usr/libexec/vyos/*.py +usr/libexec/vyos/*.sh +usr/share diff --git a/debian/vyos-1x.links b/debian/vyos-1x.links new file mode 100644 index 0000000..402c913 --- /dev/null +++ b/debian/vyos-1x.links @@ -0,0 +1,2 @@ +/etc/netplug/linkup.d/vyos-python-helper /etc/netplug/linkdown.d/vyos-python-helper +/usr/libexec/vyos/system/standalone_root_pw_reset /opt/vyatta/sbin/standalone_root_pw_reset diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst new file mode 100644 index 0000000..dc8ada2 --- /dev/null +++ b/debian/vyos-1x.postinst @@ -0,0 +1,264 @@ +#!/bin/bash + +# Turn off Debian default for %sudo +sed -i -e '/^%sudo/d' /etc/sudoers || true + +# Add minion user for salt-minion +if ! grep -q '^minion' /etc/passwd; then + adduser --quiet --firstuid 100 --system --disabled-login --ingroup vyattacfg \ + --gecos "salt minion user" --shell /bin/vbash minion + adduser --quiet minion frrvty + adduser --quiet minion sudo + adduser --quiet minion adm + adduser --quiet minion dip + adduser --quiet minion disk + adduser --quiet minion users + adduser --quiet minion frr +fi + +# OpenVPN should get its own user +if ! grep -q '^openvpn' /etc/passwd; then + adduser --quiet --firstuid 100 --system --group --shell /usr/sbin/nologin openvpn +fi + +# We need to have a group for RADIUS service users to use it inside PAM rules +if ! grep -q '^radius' /etc/group; then + addgroup --firstgid 1000 --quiet radius +fi + +# Remove TACACS user added by base package - we use our own UID range and group +# assignments - see below +if grep -q '^tacacs' /etc/passwd; then + if [ $(id -u tacacs0) -ge 1000 ]; then + level=0 + vyos_group=vyattaop + while [ $level -lt 16 ]; do + userdel tacacs${level} || true + rm -rf /home/tacacs${level} || true + level=$(( level+1 )) + done 2>&1 + fi +fi + +# Remove TACACS+ PAM default profile +if [[ -e /usr/share/pam-configs/tacplus ]]; then + rm /usr/share/pam-configs/tacplus +fi + +# Add TACACS system users required for TACACS based system authentication +if ! grep -q '^tacacs' /etc/passwd; then + # Add the tacacs group and all 16 possible tacacs privilege-level users to + # the password file, home directories, etc. The accounts are not enabled + # for local login, since they are only used to provide uid/gid/homedir for + # the mapped TACACS+ logins (and lookups against them). The tacacs15 user + # is also added to the sudo group, and vyattacfg group rather than vyattaop + # (used for tacacs0-14). + level=0 + vyos_group=vyattaop + while [ $level -lt 16 ]; do + adduser --quiet --system --firstuid 900 --disabled-login --ingroup tacacs \ + --no-create-home --gecos "TACACS+ mapped user at privilege level ${level}" \ + --shell /bin/vbash tacacs${level} + adduser --quiet tacacs${level} frrvty + adduser --quiet tacacs${level} adm + adduser --quiet tacacs${level} dip + adduser --quiet tacacs${level} users + if [ $level -lt 15 ]; then + adduser --quiet tacacs${level} vyattaop + adduser --quiet tacacs${level} operator + else + adduser --quiet tacacs${level} vyattacfg + adduser --quiet tacacs${level} sudo + adduser --quiet tacacs${level} disk + adduser --quiet tacacs${level} frr + adduser --quiet tacacs${level} _kea + fi + level=$(( level+1 )) + done 2>&1 | grep -v "User tacacs${level} already exists" +fi + +# Add RADIUS operator user for RADIUS authenticated users to map to +if ! grep -q '^radius_user' /etc/passwd; then + adduser --quiet --firstuid 1000 --disabled-login --ingroup radius \ + --no-create-home --gecos "RADIUS mapped user at privilege level operator" \ + --shell /sbin/radius_shell radius_user + adduser --quiet radius_user frrvty + adduser --quiet radius_user vyattaop + adduser --quiet radius_user operator + adduser --quiet radius_user adm + adduser --quiet radius_user dip + adduser --quiet radius_user users +fi + +# Add RADIUS admin user for RADIUS authenticated users to map to +if ! grep -q '^radius_priv_user' /etc/passwd; then + adduser --quiet --firstuid 1000 --disabled-login --ingroup radius \ + --no-create-home --gecos "RADIUS mapped user at privilege level admin" \ + --shell /sbin/radius_shell radius_priv_user + adduser --quiet radius_priv_user frrvty + adduser --quiet radius_priv_user vyattacfg + adduser --quiet radius_priv_user sudo + adduser --quiet radius_priv_user adm + adduser --quiet radius_priv_user dip + adduser --quiet radius_priv_user disk + adduser --quiet radius_priv_user users + adduser --quiet radius_priv_user frr + adduser --quiet radius_priv_user _kea +fi + +# add hostsd group for vyos-hostsd +if ! grep -q '^hostsd' /etc/group; then + addgroup --quiet --system hostsd +fi + +# Add _kea user for kea-dhcp{4,6}-server to vyattacfg +# The user should exist via kea-common installed as transitive dependency +if grep -q '^_kea' /etc/passwd; then + adduser --quiet _kea vyattacfg +fi + +# ensure the proxy user has a proper shell +chsh -s /bin/sh proxy + +# Set file capabilities +setcap cap_net_admin=pe /sbin/ethtool +setcap cap_net_admin=pe /sbin/tc +setcap cap_net_admin=pe /bin/ip +setcap cap_net_admin=pe /sbin/xtables-legacy-multi +setcap cap_net_admin=pe /sbin/xtables-nft-multi +setcap cap_net_admin=pe /usr/sbin/conntrack +setcap cap_net_admin=pe /usr/sbin/arp +setcap cap_net_raw=pe /usr/bin/tcpdump +setcap cap_net_admin,cap_sys_admin=pe /sbin/sysctl +setcap cap_sys_module=pe /bin/kmod +setcap cap_sys_time=pe /bin/date + +# create needed directories +mkdir -p /var/log/user +mkdir -p /var/core +mkdir -p /opt/vyatta/etc/config/auth +mkdir -p /opt/vyatta/etc/config/scripts +mkdir -p /opt/vyatta/etc/config/user-data +mkdir -p /opt/vyatta/etc/config/support +chown -R root:vyattacfg /opt/vyatta/etc/config +chmod -R 775 /opt/vyatta/etc/config +mkdir -p /opt/vyatta/etc/logrotate +mkdir -p /opt/vyatta/etc/netdevice.d + +touch /etc/environment + +if [ ! -f /etc/bash_completion ]; then + echo "source /etc/bash_completion.d/10vyatta-op" > /etc/bash_completion + echo "source /etc/bash_completion.d/20vyatta-cfg" >> /etc/bash_completion +fi + +sed -i 's/^set /builtin set /' /etc/bash_completion + +# Fix up PAM configuration for login so that invalid users are prompted +# for password +sed -i 's/requisite[ \t][ \t]*pam_securetty.so/required pam_securetty.so/' $rootfsdir/etc/pam.d/login + +# Change default shell for new accounts +sed -i -e ':^DSHELL:s:/bin/bash:/bin/vbash:' /etc/adduser.conf + +# Do not allow users to change full name field (controlled by vyos-1x) +sed -i -e 's/^CHFN_RESTRICT/#&/' /etc/login.defs + +# Only allow root to use passwd command +if ! grep -q 'pam_succeed_if.so' /etc/pam.d/passwd ; then + sed -i -e '/^@include/i \ +password requisite pam_succeed_if.so user = root +' /etc/pam.d/passwd +fi + +# remove unnecessary ddclient script in /etc/ppp/ip-up.d/ +# this logs unnecessary messages trying to start ddclient +rm -f /etc/ppp/ip-up.d/ddclient + +# create /opt/vyatta/etc/config/scripts/vyos-preconfig-bootup.script +PRECONFIG_SCRIPT=/opt/vyatta/etc/config/scripts/vyos-preconfig-bootup.script +if [ ! -x $PRECONFIG_SCRIPT ]; then + mkdir -p $(dirname $PRECONFIG_SCRIPT) + touch $PRECONFIG_SCRIPT + chmod 755 $PRECONFIG_SCRIPT + cat <<EOF >>$PRECONFIG_SCRIPT +#!/bin/sh +# This script is executed at boot time before VyOS configuration is applied. +# Any modifications required to work around unfixed bugs or use +# services not available through the VyOS CLI system can be placed here. + +EOF +fi + +# create /opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script +POSTCONFIG_SCRIPT=/opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script +if [ ! -x $POSTCONFIG_SCRIPT ]; then + mkdir -p $(dirname $POSTCONFIG_SCRIPT) + touch $POSTCONFIG_SCRIPT + chmod 755 $POSTCONFIG_SCRIPT + cat <<EOF >>$POSTCONFIG_SCRIPT +#!/bin/sh +# This script is executed at boot time after VyOS configuration is fully applied. +# Any modifications required to work around unfixed bugs +# or use services not available through the VyOS CLI system can be placed here. + +EOF +fi + +# symlink destination is deleted during ISO assembly - this generates some noise +# when the system boots: systemd-sysv-generator[1881]: stat() failed on +# /etc/init.d/README, ignoring: No such file or directory. Thus we simply drop +# the file. +if [ -L /etc/init.d/README ]; then + rm -f /etc/init.d/README +fi + +# Remove unwanted daemon files from /etc +# conntackd +# pmacct +# fastnetmon +# ntp +DELETE="/etc/logrotate.d/conntrackd.distrib /etc/init.d/conntrackd /etc/default/conntrackd + /etc/default/pmacctd /etc/pmacct + /etc/networks_list /etc/networks_whitelist /etc/fastnetmon.conf + /etc/ntp.conf /etc/default/ssh /etc/avahi/avahi-daemon.conf /etc/avahi/hosts + /etc/powerdns /etc/default/pdns-recursor + /etc/ppp/ip-up.d/0000usepeerdns /etc/ppp/ip-down.d/0000usepeerdns" +for tmp in $DELETE; do + if [ -e ${tmp} ]; then + rm -rf ${tmp} + fi +done + +# Remove logrotate items controlled via CLI and VyOS defaults +sed -i '/^\/var\/log\/messages$/d' /etc/logrotate.d/rsyslog +sed -i '/^\/var\/log\/auth.log$/d' /etc/logrotate.d/rsyslog + +# Fix FRR pam.d "vtysh_pam" vtysh_pam: Failed in account validation T5110 +if test -f /etc/pam.d/frr; then + if grep -q 'pam_rootok.so' /etc/pam.d/frr; then + sed -i -re 's/rootok/permit/' /etc/pam.d/frr + fi +fi + +# Enable Cloud-init pre-configuration service +systemctl enable vyos-config-cloud-init.service + +# Enable Podman API +systemctl enable podman.service + +# Generate API GraphQL schema +/usr/libexec/vyos/services/api/graphql/generate/generate_schema.py + +# Update XML cache +python3 /usr/lib/python3/dist-packages/vyos/xml_ref/update_cache.py + +# Generate hardlinks for systemd units for multi VRF support +# as softlinks will fail in systemd: +# symlink target name type "ssh.service" does not match source, rejecting. +if [ ! -f /lib/systemd/system/ssh@.service ]; then + ln /lib/systemd/system/ssh.service /lib/systemd/system/ssh@.service +fi + +# T4287 - as we have a non-signed kernel use the upstream wireless reulatory database +update-alternatives --set regulatory.db /lib/firmware/regulatory.db-upstream diff --git a/debian/vyos-1x.preinst b/debian/vyos-1x.preinst new file mode 100644 index 0000000..fbfc855 --- /dev/null +++ b/debian/vyos-1x.preinst @@ -0,0 +1,11 @@ +dpkg-divert --package vyos-1x --add --no-rename /etc/securetty +dpkg-divert --package vyos-1x --add --no-rename /etc/security/capability.conf +dpkg-divert --package vyos-1x --add --no-rename /lib/systemd/system/lcdproc.service +dpkg-divert --package vyos-1x --add --no-rename /etc/logrotate.d/conntrackd +dpkg-divert --package vyos-1x --add --no-rename /etc/rsyslog.conf +dpkg-divert --package vyos-1x --add --no-rename /etc/skel/.bashrc +dpkg-divert --package vyos-1x --add --no-rename /etc/skel/.profile +dpkg-divert --package vyos-1x --add --no-rename /etc/netplug/netplugd.conf +dpkg-divert --package vyos-1x --add --no-rename /etc/netplug/netplug +dpkg-divert --package vyos-1x --add --no-rename /etc/rsyslog.d/45-frr.conf +dpkg-divert --package vyos-1x --add --no-rename /lib/udev/rules.d/99-systemd.rules diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in new file mode 100644 index 0000000..3dd1b32 --- /dev/null +++ b/interface-definitions/container.xml.in @@ -0,0 +1,543 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="container" owner="${vyos_conf_scripts_dir}/container.py"> + <properties> + <help>Container applications</help> + <priority>450</priority> + </properties> + <children> + <tagNode name="name"> + <properties> + <help>Container name</help> + <constraint> + <regex>[-a-zA-Z0-9]+</regex> + </constraint> + <constraintErrorMessage>Container name must be alphanumeric and can contain hyphens</constraintErrorMessage> + </properties> + <children> + <leafNode name="allow-host-pid"> + <properties> + <help>Allow sharing host process namespace with container</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="allow-host-networks"> + <properties> + <help>Allow sharing host networking with container</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="capability"> + <properties> + <help>Grant individual Linux capability to container instance</help> + <completionHelp> + <list>net-admin net-bind-service net-raw setpcap sys-admin sys-module sys-nice sys-time</list> + </completionHelp> + <valueHelp> + <format>net-admin</format> + <description>Network operations (interface, firewall, routing tables)</description> + </valueHelp> + <valueHelp> + <format>net-bind-service</format> + <description>Bind a socket to privileged ports (port numbers less than 1024)</description> + </valueHelp> + <valueHelp> + <format>net-raw</format> + <description>Permission to create raw network sockets</description> + </valueHelp> + <valueHelp> + <format>setpcap</format> + <description>Capability sets (from bounded or inherited set)</description> + </valueHelp> + <valueHelp> + <format>sys-admin</format> + <description>Administation operations (quotactl, mount, sethostname, setdomainame)</description> + </valueHelp> + <valueHelp> + <format>sys-module</format> + <description>Load, unload and delete kernel modules</description> + </valueHelp> + <valueHelp> + <format>sys-nice</format> + <description>Permission to set process nice value</description> + </valueHelp> + <valueHelp> + <format>sys-time</format> + <description>Permission to set system clock</description> + </valueHelp> + <constraint> + <regex>(net-admin|net-bind-service|net-raw|setpcap|sys-admin|sys-module|sys-nice|sys-time)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + <node name="sysctl"> + <properties> + <help>Configure namespaced kernel parameters of the container</help> + </properties> + <children> + <tagNode name="parameter"> + <properties> + <help>Sysctl key name</help> + <completionHelp> + <script>${vyos_completion_dir}/list_container_sysctl_parameters.sh</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Sysctl key name</description> + </valueHelp> + <constraint> + <validator name="sysctl"/> + </constraint> + </properties> + <children> + <leafNode name="value"> + <properties> + <help>Sysctl configuration value</help> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + #include <include/generic-description.xml.i> + <tagNode name="device"> + <properties> + <help>Add a host device to the container</help> + </properties> + <children> + <leafNode name="source"> + <properties> + <help>Source device (Example: "/dev/x")</help> + <valueHelp> + <format>txt</format> + <description>Source device</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="destination"> + <properties> + <help>Destination container device (Example: "/dev/x")</help> + <valueHelp> + <format>txt</format> + <description>Destination container device</description> + </valueHelp> + </properties> + </leafNode> + </children> + </tagNode> + #include <include/generic-disable-node.xml.i> + <tagNode name="environment"> + <properties> + <help>Add custom environment variables</help> + <constraint> + <regex>[-_a-zA-Z0-9]+</regex> + </constraint> + <constraintErrorMessage>Environment variable name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage> + </properties> + <children> + <leafNode name="value"> + <properties> + <help>Set environment option value</help> + <valueHelp> + <format>txt</format> + <description>Set environment option value</description> + </valueHelp> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="entrypoint"> + <properties> + <help>Override the default ENTRYPOINT from the image</help> + <constraint> + <regex>[ !#-%&(-~]+</regex> + </constraint> + <constraintErrorMessage>Entrypoint must be ASCII characters, use &quot; and &apos for double and single quotes respectively</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="host-name"> + <properties> + <help>Container host name</help> + <constraint> + #include <include/constraint/host-name.xml.i> + </constraint> + <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="image"> + <properties> + <help>Container image to use</help> + <completionHelp> + <script>sudo podman image list --format "{{.Repository}}:{{.Tag}}"</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Image name in the hub-registry</description> + </valueHelp> + <constraint> + <regex>[[:ascii:]]{1,255}</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="command"> + <properties> + <help>Override the default CMD from the image</help> + <constraint> + <regex>[ !#-%&(-~]+</regex> + </constraint> + <constraintErrorMessage>Command must be ASCII characters, use &quot; and &apos for double and single quotes respectively</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="arguments"> + <properties> + <help>The command's arguments for this container</help> + <constraint> + <regex>[ !#-%&(-~]+</regex> + </constraint> + <constraintErrorMessage>The command's arguments must be ASCII characters, use &quot; and &apos for double and single quotes respectively</constraintErrorMessage> + </properties> + </leafNode> + <tagNode name="label"> + <properties> + <help>Add label variables</help> + <constraint> + <regex>[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?</regex> + </constraint> + <constraintErrorMessage>Label variable name must be alphanumeric and can contain hyphen, dots and underscores</constraintErrorMessage> + </properties> + <children> + <leafNode name="value"> + <properties> + <help>Set label option value</help> + <valueHelp> + <format>txt</format> + <description>Set label option value</description> + </valueHelp> + <constraint> + <regex>[[:ascii:]]{1,255}</regex> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="cpu-quota"> + <properties> + <help>This limits the number of CPU resources the container can use</help> + <valueHelp> + <format>u32:0</format> + <description>Unlimited</description> + </valueHelp> + <valueHelp> + <format>txt</format> + <description>Amount of CPU time the container can use in amount of cores (up to three decimals)</description> + </valueHelp> + <constraint> + <regex>(0|[1-9]\d*)(\.\d{1,3})?</regex> + </constraint> + <constraintErrorMessage>Container CPU limit must be a (decimal) number in range 0 to number of threads</constraintErrorMessage> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="memory"> + <properties> + <help>Memory (RAM) available to this container</help> + <valueHelp> + <format>u32:0</format> + <description>Unlimited</description> + </valueHelp> + <valueHelp> + <format>u32:1-16384</format> + <description>Container memory in megabytes (MB)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16384"/> + </constraint> + <constraintErrorMessage>Container memory must be in range 0 to 16384 MB</constraintErrorMessage> + </properties> + <defaultValue>512</defaultValue> + </leafNode> + <leafNode name="shared-memory"> + <properties> + <help>Shared memory available to this container</help> + <valueHelp> + <format>u32:0</format> + <description>Unlimited</description> + </valueHelp> + <valueHelp> + <format>u32:1-8192</format> + <description>Container memory in megabytes (MB)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-8192"/> + </constraint> + <constraintErrorMessage>Container memory must be in range 0 to 8192 MB</constraintErrorMessage> + </properties> + <defaultValue>64</defaultValue> + </leafNode> + <tagNode name="network"> + <properties> + <help>Attach user defined network to container</help> + <completionHelp> + <path>container network</path> + </completionHelp> + #include <include/constraint/container-network.xml.i> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>Assign static IP address to container</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="port"> + <properties> + <help>Publish port to the container</help> + </properties> + <children> + #include <include/listen-address.xml.i> + <leafNode name="source"> + <properties> + <help>Source host port</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Source host port</description> + </valueHelp> + <valueHelp> + <format>start-end</format> + <description>Source host port range (e.g. 10025-10030)</description> + </valueHelp> + <constraint> + <validator name="port-range"/> + </constraint> + </properties> + </leafNode> + <leafNode name="destination"> + <properties> + <help>Destination container port</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Destination container port</description> + </valueHelp> + <valueHelp> + <format>start-end</format> + <description>Destination container port range (e.g. 10025-10030)</description> + </valueHelp> + <constraint> + <validator name="port-range"/> + </constraint> + </properties> + </leafNode> + <leafNode name="protocol"> + <properties> + <help>Transport protocol used for port mapping</help> + <completionHelp> + <list>tcp udp</list> + </completionHelp> + <valueHelp> + <format>tcp</format> + <description>Use Transmission Control Protocol for given port</description> + </valueHelp> + <valueHelp> + <format>udp</format> + <description>Use User Datagram Protocol for given port</description> + </valueHelp> + <constraint> + <regex>(tcp|udp)</regex> + </constraint> + </properties> + <defaultValue>tcp</defaultValue> + </leafNode> + </children> + </tagNode> + <leafNode name="restart"> + <properties> + <help>Restart options for container</help> + <completionHelp> + <list>no on-failure always</list> + </completionHelp> + <valueHelp> + <format>no</format> + <description>Do not restart containers on exit</description> + </valueHelp> + <valueHelp> + <format>on-failure</format> + <description>Restart containers when they exit with a non-zero exit code, retrying indefinitely</description> + </valueHelp> + <valueHelp> + <format>always</format> + <description>Restart containers when they exit, regardless of status, retrying indefinitely</description> + </valueHelp> + <constraint> + <regex>(no|on-failure|always)</regex> + </constraint> + </properties> + <defaultValue>on-failure</defaultValue> + </leafNode> + <leafNode name="uid"> + <properties> + <help>User ID this container will run as</help> + <valueHelp> + <format>u32:0-65535</format> + <description>User ID this container will run as</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="gid"> + <properties> + <help>Group ID this container will run as</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Group ID this container will run as</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + </leafNode> + <tagNode name="volume"> + <properties> + <help>Mount a volume into the container</help> + </properties> + <children> + <leafNode name="source"> + <properties> + <help>Source host directory</help> + <valueHelp> + <format>txt</format> + <description>Source host directory</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="destination"> + <properties> + <help>Destination container directory</help> + <valueHelp> + <format>txt</format> + <description>Destination container directory</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="mode"> + <properties> + <help>Volume access mode ro/rw</help> + <completionHelp> + <list>ro rw</list> + </completionHelp> + <valueHelp> + <format>ro</format> + <description>Volume mounted into the container as read-only</description> + </valueHelp> + <valueHelp> + <format>rw</format> + <description>Volume mounted into the container as read-write</description> + </valueHelp> + <constraint> + <regex>(ro|rw)</regex> + </constraint> + </properties> + <defaultValue>rw</defaultValue> + </leafNode> + <leafNode name="propagation"> + <properties> + <help>Volume bind propagation</help> + <completionHelp> + <list>shared slave private rshared rslave rprivate</list> + </completionHelp> + <valueHelp> + <format>shared</format> + <description>Sub-mounts of the original mount are exposed to replica mounts</description> + </valueHelp> + <valueHelp> + <format>slave</format> + <description>Allow replica mount to see sub-mount from the original mount but not vice versa</description> + </valueHelp> + <valueHelp> + <format>private</format> + <description>Sub-mounts within a mount are not visible to replica mounts or the original mount</description> + </valueHelp> + <valueHelp> + <format>rshared</format> + <description>Allows sharing of mount points and their nested mount points between both the original and replica mounts</description> + </valueHelp> + <valueHelp> + <format>rslave</format> + <description>Allows mount point and their nested mount points between original an replica mounts</description> + </valueHelp> + <valueHelp> + <format>rprivate</format> + <description>No mount points within original or replica mounts in any direction</description> + </valueHelp> + <constraint> + <regex>(shared|slave|private|rshared|rslave|rprivate)</regex> + </constraint> + </properties> + <defaultValue>rprivate</defaultValue> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="network"> + <properties> + <help>Network name</help> + #include <include/constraint/container-network.xml.i> + </properties> + <children> + #include <include/generic-description.xml.i> + <leafNode name="prefix"> + <properties> + <help>Prefix which allocated to that network</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 network prefix</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 network prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv6-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="no-name-server"> + <properties> + <help>Disable Domain Name System (DNS) plugin for this network</help> + <valueless/> + </properties> + </leafNode> + #include <include/interface/vrf.xml.i> + </children> + </tagNode> + <tagNode name="registry"> + <properties> + <help>Registry Name</help> + </properties> + <defaultValue>docker.io quay.io</defaultValue> + <children> + #include <include/interface/authentication.xml.i> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in new file mode 100644 index 0000000..07c88f7 --- /dev/null +++ b/interface-definitions/firewall.xml.in @@ -0,0 +1,544 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="firewall" owner="${vyos_conf_scripts_dir}/firewall.py"> + <properties> + <priority>489</priority> + <help>Firewall</help> + </properties> + <children> + #include <include/firewall/global-options.xml.i> + <tagNode name="flowtable"> + <properties> + <help>Flowtable</help> + <constraint> + <regex>[a-zA-Z0-9][\w\-\.]*</regex> + </constraint> + </properties> + <children> + #include <include/generic-description.xml.i> + <leafNode name="interface"> + <properties> + <help>Interfaces to use this flowtable</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <multi/> + </properties> + </leafNode> + <leafNode name="offload"> + <properties> + <help>Offloading method</help> + <completionHelp> + <list>hardware software</list> + </completionHelp> + <valueHelp> + <format>hardware</format> + <description>Hardware offload</description> + </valueHelp> + <valueHelp> + <format>software</format> + <description>Software offload</description> + </valueHelp> + <constraint> + <regex>(hardware|software)</regex> + </constraint> + </properties> + <defaultValue>software</defaultValue> + </leafNode> + </children> + </tagNode> + <node name="group"> + <properties> + <help>Firewall group</help> + </properties> + <children> + <tagNode name="address-group"> + <properties> + <help>Firewall address-group</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>Address-group member</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4range</format> + <description>IPv4 range to match (e.g. 10.0.0.1-10.0.0.200)</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv4-range"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="include"> + <properties> + <help>Include another address-group</help> + <completionHelp> + <path>firewall group address-group</path> + </completionHelp> + <multi/> + </properties> + </leafNode> + #include <include/generic-description.xml.i> + </children> + </tagNode> + <tagNode name="domain-group"> + <properties> + <help>Firewall domain-group</help> + <constraint> + <regex>[a-zA-Z_][a-zA-Z0-9]?[\w\-\.]*</regex> + </constraint> + <constraintErrorMessage>Name of domain-group can only contain alphanumeric letters, hyphen, underscores and not start with numeric</constraintErrorMessage> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>Domain-group member</help> + <valueHelp> + <format>txt</format> + <description>Domain address to match</description> + </valueHelp> + <constraint> + <validator name="fqdn"/> + </constraint> + <multi/> + </properties> + </leafNode> + #include <include/generic-description.xml.i> + </children> + </tagNode> + <node name="dynamic-group"> + <properties> + <help>Firewall dynamic group</help> + </properties> + <children> + <tagNode name="address-group"> + <properties> + <help>Firewall dynamic address group</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + </children> + </tagNode> + <tagNode name="ipv6-address-group"> + <properties> + <help>Firewall dynamic IPv6 address group</help> + <constraint> + <regex>[a-zA-Z0-9][\w\-\.]*</regex> + </constraint> + </properties> + <children> + #include <include/generic-description.xml.i> + </children> + </tagNode> + </children> + </node> + <tagNode name="interface-group"> + <properties> + <help>Firewall interface-group</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> + </properties> + <children> + <leafNode name="interface"> + <properties> + <help>Interface-group member</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <multi/> + </properties> + </leafNode> + <leafNode name="include"> + <properties> + <help>Include another interface-group</help> + <completionHelp> + <path>firewall group interface-group</path> + </completionHelp> + <multi/> + </properties> + </leafNode> + #include <include/generic-description.xml.i> + </children> + </tagNode> + <tagNode name="ipv6-address-group"> + <properties> + <help>Firewall ipv6-address-group</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>Address-group member</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv6range</format> + <description>IPv6 range to match (e.g. 2002::1-2002::ff)</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + <validator name="ipv6-range"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="include"> + <properties> + <help>Include another ipv6-address-group</help> + <completionHelp> + <path>firewall group ipv6-address-group</path> + </completionHelp> + <multi/> + </properties> + </leafNode> + #include <include/generic-description.xml.i> + </children> + </tagNode> + <tagNode name="ipv6-network-group"> + <properties> + <help>Firewall ipv6-network-group</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <leafNode name="network"> + <properties> + <help>Network-group member</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address to match</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="include"> + <properties> + <help>Include another ipv6-network-group</help> + <completionHelp> + <path>firewall group ipv6-network-group</path> + </completionHelp> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="mac-group"> + <properties> + <help>Firewall mac-group</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <leafNode name="mac-address"> + <properties> + <help>Mac-group member</help> + <valueHelp> + <format>macaddr</format> + <description>MAC address to match</description> + </valueHelp> + <constraint> + <validator name="mac-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="include"> + <properties> + <help>Include another mac-group</help> + <completionHelp> + <path>firewall group mac-group</path> + </completionHelp> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="network-group"> + <properties> + <help>Firewall network-group</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <leafNode name="network"> + <properties> + <help>Network-group member</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 Subnet to match</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="include"> + <properties> + <help>Include another network-group</help> + <completionHelp> + <path>firewall group network-group</path> + </completionHelp> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="port-group"> + <properties> + <help>Firewall port-group</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <leafNode name="port"> + <properties> + <help>Port-group member</help> + <valueHelp> + <format>txt</format> + <description>Named port (any name in /etc/services, e.g., http)</description> + </valueHelp> + <valueHelp> + <format>u32:1-65535</format> + <description>Numbered port</description> + </valueHelp> + <valueHelp> + <format>start-end</format> + <description>Numbered port range (e.g. 1001-1050)</description> + </valueHelp> + <multi/> + <constraint> + <validator name="port-range"/> + </constraint> + </properties> + </leafNode> + <leafNode name="include"> + <properties> + <help>Include another port-group</help> + <completionHelp> + <path>firewall group port-group</path> + </completionHelp> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + <node name="bridge"> + <properties> + <help>Bridge firewall</help> + </properties> + <children> + #include <include/firewall/bridge-hook-forward.xml.i> + #include <include/firewall/bridge-hook-input.xml.i> + #include <include/firewall/bridge-hook-output.xml.i> + #include <include/firewall/bridge-hook-prerouting.xml.i> + #include <include/firewall/bridge-custom-name.xml.i> + </children> + </node> + <node name="ipv4"> + <properties> + <help>IPv4 firewall</help> + </properties> + <children> + #include <include/firewall/ipv4-hook-forward.xml.i> + #include <include/firewall/ipv4-hook-input.xml.i> + #include <include/firewall/ipv4-hook-output.xml.i> + #include <include/firewall/ipv4-hook-prerouting.xml.i> + #include <include/firewall/ipv4-custom-name.xml.i> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 firewall</help> + </properties> + <children> + #include <include/firewall/ipv6-hook-forward.xml.i> + #include <include/firewall/ipv6-hook-input.xml.i> + #include <include/firewall/ipv6-hook-output.xml.i> + #include <include/firewall/ipv6-hook-prerouting.xml.i> + #include <include/firewall/ipv6-custom-name.xml.i> + </children> + </node> + <tagNode name="zone"> + <properties> + <help>Zone-policy</help> + <valueHelp> + <format>txt</format> + <description>Zone name</description> + </valueHelp> + <constraint> + <regex>[a-zA-Z0-9][\w\-\.]*</regex> + </constraint> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/firewall/default-log.xml.i> + <leafNode name="default-action"> + <properties> + <help>Default-action for traffic coming into this zone</help> + <completionHelp> + <list>drop reject</list> + </completionHelp> + <valueHelp> + <format>drop</format> + <description>Drop silently</description> + </valueHelp> + <valueHelp> + <format>reject</format> + <description>Drop and notify source</description> + </valueHelp> + <constraint> + <regex>(drop|reject)</regex> + </constraint> + </properties> + <defaultValue>drop</defaultValue> + </leafNode> + <tagNode name="from"> + <properties> + <help>Zone from which to filter traffic</help> + <completionHelp> + <path>firewall zone</path> + </completionHelp> + </properties> + <children> + <node name="firewall"> + <properties> + <help>Firewall options</help> + </properties> + <children> + <leafNode name="ipv6-name"> + <properties> + <help>IPv6 firewall ruleset</help> + <completionHelp> + <path>firewall ipv6 name</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="name"> + <properties> + <help>IPv4 firewall ruleset</help> + <completionHelp> + <path>firewall ipv4 name</path> + </completionHelp> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + <leafNode name="interface"> + <properties> + <help>Interface associated with zone</help> + <valueHelp> + <format>txt</format> + <description>Interface associated with zone</description> + </valueHelp> + <valueHelp> + <format>vrf</format> + <description>VRF associated with zone</description> + </valueHelp> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + <path>vrf name</path> + </completionHelp> + <multi/> + </properties> + </leafNode> + <node name="intra-zone-filtering"> + <properties> + <help>Intra-zone filtering</help> + </properties> + <children> + <leafNode name="action"> + <properties> + <help>Action for intra-zone traffic</help> + <completionHelp> + <list>accept drop</list> + </completionHelp> + <valueHelp> + <format>accept</format> + <description>Accept traffic</description> + </valueHelp> + <valueHelp> + <format>drop</format> + <description>Drop silently</description> + </valueHelp> + <constraint> + <regex>(accept|drop)</regex> + </constraint> + </properties> + </leafNode> + <node name="firewall"> + <properties> + <help>Use the specified firewall chain</help> + </properties> + <children> + <leafNode name="ipv6-name"> + <properties> + <help>IPv6 firewall ruleset</help> + <completionHelp> + <path>firewall ipv6 name</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="name"> + <properties> + <help>IPv4 firewall ruleset</help> + <completionHelp> + <path>firewall ipv4 name</path> + </completionHelp> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="local-zone"> + <properties> + <help>Zone to be local-zone</help> + <valueless/> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in new file mode 100644 index 0000000..7108aa0 --- /dev/null +++ b/interface-definitions/high-availability.xml.in @@ -0,0 +1,568 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="high-availability" owner="${vyos_conf_scripts_dir}/high-availability.py"> + <properties> + <priority>800</priority> <!-- after all interfaces and conntrack-sync --> + <help>High availability settings</help> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <node name="vrrp"> + <properties> + <help>Virtual Router Redundancy Protocol settings</help> + </properties> + <children> + <leafNode name="snmp"> + <properties> + <valueless/> + <help>Enable SNMP</help> + </properties> + </leafNode> + <node name="global-parameters"> + <properties> + <help>VRRP global parameters</help> + </properties> + <children> + #include <include/vrrp/garp.xml.i> + <leafNode name="startup-delay"> + <properties> + <help>Time VRRP startup process (in seconds)</help> + <valueHelp> + <format>u32:1-600</format> + <description>Interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-600"/> + </constraint> + </properties> + </leafNode> + <leafNode name="version"> + <properties> + <help>Default VRRP version to use, IPv6 always uses VRRP version 3</help> + <valueHelp> + <format>2</format> + <description>VRRP version 2</description> + </valueHelp> + <valueHelp> + <format>3</format> + <description>VRRP version 3</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 2-3"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <tagNode name="group"> + <properties> + <help>VRRP group</help> + </properties> + <children> + #include <include/generic-interface-broadcast.xml.i> + #include <include/vrrp/garp.xml.i> + <leafNode name="advertise-interval"> + <properties> + <help>Advertise interval</help> + <valueHelp> + <format>u32:1-255</format> + <description>Advertise interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <defaultValue>1</defaultValue> + </leafNode> + <node name="authentication"> + <properties> + <help>VRRP authentication</help> + </properties> + <children> + <leafNode name="password"> + <properties> + <help>VRRP password</help> + <valueHelp> + <format>txt</format> + <description>Password string (up to 8 characters)</description> + </valueHelp> + <constraint> + <regex>.{1,8}</regex> + </constraint> + <constraintErrorMessage>Password must not be longer than 8 characters</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="type"> + <properties> + <help>Authentication type</help> + <completionHelp> + <list>plaintext-password ah</list> + </completionHelp> + <valueHelp> + <format>plaintext-password</format> + <description>Simple password string</description> + </valueHelp> + <valueHelp> + <format>ah</format> + <description>AH - IPSEC (not recommended)</description> + </valueHelp> + <constraint> + <regex>(plaintext-password|ah)</regex> + </constraint> + <constraintErrorMessage>Authentication type must be plaintext-password or ah</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + #include <include/generic-description.xml.i> + #include <include/generic-disable-node.xml.i> + <node name="health-check"> + <properties> + <help>Health check</help> + </properties> + <children> + <leafNode name="failure-count"> + <properties> + <help>Health check failure count required for transition to fault</help> + <constraint> + <validator name="numeric" argument="--positive" /> + </constraint> + </properties> + <defaultValue>3</defaultValue> + </leafNode> + <leafNode name="interval"> + <properties> + <help>Health check execution interval in seconds</help> + <constraint> + <validator name="numeric" argument="--positive"/> + </constraint> + </properties> + <defaultValue>60</defaultValue> + </leafNode> + <leafNode name="ping"> + <properties> + <help>ICMP ping health check</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 ping target address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 ping target address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="script"> + <properties> + <help>Health check script file</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <leafNode name="hello-source-address"> + <properties> + <help>VRRP hello source address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 hello source address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 hello source address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="peer-address"> + <properties> + <help>Unicast VRRP peer address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 unicast peer address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 unicast peer address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="no-preempt"> + <properties> + <valueless/> + <help>Disable master preemption</help> + </properties> + </leafNode> + <leafNode name="preempt-delay"> + <properties> + <help>Preempt delay (in seconds)</help> + <valueHelp> + <format>u32:0-1000</format> + <description>preempt delay</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-1000"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="priority"> + <properties> + <help>Router priority</help> + <valueHelp> + <format>u32:1-255</format> + <description>Router priority</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <defaultValue>100</defaultValue> + </leafNode> + <leafNode name="rfc3768-compatibility"> + <properties> + <help>Use VRRP virtual MAC address as per RFC3768</help> + <valueless/> + </properties> + </leafNode> + <node name="track"> + <properties> + <help>Track settings</help> + </properties> + <children> + <leafNode name="exclude-vrrp-interface"> + <properties> + <valueless/> + <help>Disable track state of main interface</help> + </properties> + </leafNode> + <leafNode name="interface"> + <properties> + <help>Interface name state check</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --broadcast</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + #include <include/vrrp-transition-script.xml.i> + <tagNode name="address"> + <properties> + <help>Virtual IP address</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 address and prefix length</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-host"/> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + #include <include/generic-interface-broadcast.xml.i> + </children> + </tagNode> + <tagNode name="excluded-address"> + <properties> + <help>Virtual address (If you need additional IPv4 and IPv6 in same group)</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 address and prefix length</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-host"/> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + #include <include/generic-interface-broadcast.xml.i> + </children> + </tagNode> + <leafNode name="vrid"> + <properties> + <help>Virtual router identifier</help> + <valueHelp> + <format>u32:1-255</format> + <description>Virtual router identifier</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="sync-group"> + <properties> + <help>VRRP sync group</help> + </properties> + <children> + <leafNode name="member"> + <properties> + <multi/> + <help>Sync group member</help> + <valueHelp> + <format>txt</format> + <description>VRRP group name</description> + </valueHelp> + <completionHelp> + <path>high-availability vrrp group</path> + </completionHelp> + </properties> + </leafNode> + <node name="health-check"> + <properties> + <help>Health check</help> + </properties> + <children> + <leafNode name="failure-count"> + <properties> + <help>Health check failure count required for transition to fault</help> + <constraint> + <validator name="numeric" argument="--positive" /> + </constraint> + </properties> + <defaultValue>3</defaultValue> + </leafNode> + <leafNode name="interval"> + <properties> + <help>Health check execution interval in seconds</help> + <constraint> + <validator name="numeric" argument="--positive"/> + </constraint> + </properties> + <defaultValue>60</defaultValue> + </leafNode> + <leafNode name="ping"> + <properties> + <help>ICMP ping health check</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 ping target address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 ping target address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="script"> + <properties> + <help>Health check script file</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + #include <include/vrrp-transition-script.xml.i> + </children> + </tagNode> + </children> + </node> + <tagNode name="virtual-server"> + <properties> + <help>Load-balancing virtual server alias</help> + </properties> + <children> + #include <include/address-ipv4-ipv6-single.xml.i> + <leafNode name="algorithm"> + <properties> + <help>Schedule algorithm (default - least-connection)</help> + <completionHelp> + <list>round-robin weighted-round-robin least-connection weighted-least-connection source-hashing destination-hashing locality-based-least-connection</list> + </completionHelp> + <valueHelp> + <format>round-robin</format> + <description>Round robin</description> + </valueHelp> + <valueHelp> + <format>weighted-round-robin</format> + <description>Weighted round robin</description> + </valueHelp> + <valueHelp> + <format>least-connection</format> + <description>Least connection</description> + </valueHelp> + <valueHelp> + <format>weighted-least-connection</format> + <description>Weighted least connection</description> + </valueHelp> + <valueHelp> + <format>source-hashing</format> + <description>Source hashing</description> + </valueHelp> + <valueHelp> + <format>destination-hashing</format> + <description>Destination hashing</description> + </valueHelp> + <valueHelp> + <format>locality-based-least-connection</format> + <description>Locality-Based least connection</description> + </valueHelp> + <constraint> + <regex>(round-robin|weighted-round-robin|least-connection|weighted-least-connection|source-hashing|destination-hashing|locality-based-least-connection)</regex> + </constraint> + </properties> + <defaultValue>least-connection</defaultValue> + </leafNode> + <leafNode name="delay-loop"> + <properties> + <help>Interval between health-checks (in seconds)</help> + <valueHelp> + <format>u32:1-600</format> + <description>Interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-3600"/> + </constraint> + </properties> + <defaultValue>10</defaultValue> + </leafNode> + <leafNode name="forward-method"> + <properties> + <help>Forwarding method</help> + <completionHelp> + <list>direct nat tunnel</list> + </completionHelp> + <valueHelp> + <format>direct</format> + <description>Direct routing</description> + </valueHelp> + <valueHelp> + <format>nat</format> + <description>NAT</description> + </valueHelp> + <valueHelp> + <format>tunnel</format> + <description>Tunneling</description> + </valueHelp> + <constraint> + <regex>(direct|nat|tunnel)</regex> + </constraint> + </properties> + <defaultValue>nat</defaultValue> + </leafNode> + #include <include/firewall/fwmark.xml.i> + #include <include/port-number-start-zero.xml.i> + <leafNode name="persistence-timeout"> + <properties> + <help>Timeout for persistent connections</help> + <valueHelp> + <format>u32:1-86400</format> + <description>Timeout for persistent connections</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-86400"/> + </constraint> + </properties> + <defaultValue>300</defaultValue> + </leafNode> + <leafNode name="protocol"> + <properties> + <help>Protocol for port checks</help> + <completionHelp> + <list>tcp udp</list> + </completionHelp> + <valueHelp> + <format>tcp</format> + <description>TCP</description> + </valueHelp> + <valueHelp> + <format>udp</format> + <description>UDP</description> + </valueHelp> + <constraint> + <regex>(tcp|udp)</regex> + </constraint> + </properties> + <defaultValue>tcp</defaultValue> + </leafNode> + <tagNode name="real-server"> + <properties> + <help>Real server address</help> + </properties> + <children> + #include <include/port-number-start-zero.xml.i> + <leafNode name="connection-timeout"> + <properties> + <help>Server connection timeout</help> + <valueHelp> + <format>u32:1-86400</format> + <description>Connection timeout to remote server</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-86400"/> + </constraint> + </properties> + </leafNode> + <node name="health-check"> + <properties> + <help>Health check script</help> + </properties> + <children> + <leafNode name="script"> + <properties> + <help>Health check script file</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/include/accel-ppp/auth-local-users.xml.i b/interface-definitions/include/accel-ppp/auth-local-users.xml.i new file mode 100644 index 0000000..1b40a9e --- /dev/null +++ b/interface-definitions/include/accel-ppp/auth-local-users.xml.i @@ -0,0 +1,54 @@ +<!-- include start from accel-ppp/auth-local-users.xml.i --> +<node name="local-users"> + <properties> + <help>Local user authentication for PPPoE server</help> + </properties> + <children> + <tagNode name="username"> + <properties> + <help>User name for authentication</help> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <leafNode name="password"> + <properties> + <help>Password for authentication</help> + </properties> + </leafNode> + <leafNode name="static-ip"> + <properties> + <help>Static client IP address</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <defaultValue>*</defaultValue> + </leafNode> + <node name="rate-limit"> + <properties> + <help>Upload/Download speed limits</help> + </properties> + <children> + <leafNode name="upload"> + <properties> + <help>Upload bandwidth limit in kbits/sec</help> + <constraint> + <validator name="numeric" argument="--range 1-10000000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="download"> + <properties> + <help>Download bandwidth limit in kbits/sec</help> + <constraint> + <validator name="numeric" argument="--range 1-10000000"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/auth-mode.xml.i b/interface-definitions/include/accel-ppp/auth-mode.xml.i new file mode 100644 index 0000000..ccaed6f --- /dev/null +++ b/interface-definitions/include/accel-ppp/auth-mode.xml.i @@ -0,0 +1,26 @@ +<!-- include start from accel-ppp/auth-mode.xml.i --> +<leafNode name="mode"> + <properties> + <help>Authentication mode used by this server</help> + <valueHelp> + <format>local</format> + <description>Use local username/password configuration</description> + </valueHelp> + <valueHelp> + <format>radius</format> + <description>Use RADIUS server for user autentication</description> + </valueHelp> + <valueHelp> + <format>noauth</format> + <description>Authentication disabled</description> + </valueHelp> + <constraint> + <regex>(local|radius|noauth)</regex> + </constraint> + <completionHelp> + <list>local radius noauth</list> + </completionHelp> + </properties> + <defaultValue>local</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/auth-protocols.xml.i b/interface-definitions/include/accel-ppp/auth-protocols.xml.i new file mode 100644 index 0000000..4ab4753 --- /dev/null +++ b/interface-definitions/include/accel-ppp/auth-protocols.xml.i @@ -0,0 +1,31 @@ +<!-- include start from accel-ppp/auth-protocols.xml.i --> +<leafNode name="protocols"> + <properties> + <help>Authentication protocol for remote access peer</help> + <completionHelp> + <list>pap chap mschap mschap-v2</list> + </completionHelp> + <valueHelp> + <format>pap</format> + <description>Authentication via PAP (Password Authentication Protocol)</description> + </valueHelp> + <valueHelp> + <format>chap</format> + <description>Authentication via CHAP (Challenge Handshake Authentication Protocol)</description> + </valueHelp> + <valueHelp> + <format>mschap</format> + <description>Authentication via MS-CHAP (Microsoft Challenge Handshake Authentication Protocol)</description> + </valueHelp> + <valueHelp> + <format>mschap-v2</format> + <description>Authentication via MS-CHAPv2 (Microsoft Challenge Handshake Authentication Protocol, version 2)</description> + </valueHelp> + <constraint> + <regex>(pap|chap|mschap|mschap-v2)</regex> + </constraint> + <multi/> + </properties> + <defaultValue>pap chap mschap mschap-v2</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/client-ip-pool.xml.i b/interface-definitions/include/accel-ppp/client-ip-pool.xml.i new file mode 100644 index 0000000..b30a5ee --- /dev/null +++ b/interface-definitions/include/accel-ppp/client-ip-pool.xml.i @@ -0,0 +1,50 @@ +<!-- include start from accel-ppp/client-ip-pool.xml.i --> +<tagNode name="client-ip-pool"> + <properties> + <help>Client IP pool</help> + <valueHelp> + <format>txt</format> + <description>Name of IP pool</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + </properties> + <children> + <leafNode name="range"> + <properties> + <help>Range of IP addresses</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix</description> + </valueHelp> + <valueHelp> + <format>ipv4range</format> + <description>IPv4 address range inside /24 network</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv4-host"/> + <validator name="ipv4-range-mask" argument="-m 24 -r"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="next-pool"> + <properties> + <help>Next pool name</help> + <completionHelp> + <path>${COMP_WORDS[@]:1:${#COMP_WORDS[@]}-4}</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of IP pool</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + </properties> + </leafNode> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/client-ipv6-pool.xml.i b/interface-definitions/include/accel-ppp/client-ipv6-pool.xml.i new file mode 100644 index 0000000..0c8c2e3 --- /dev/null +++ b/interface-definitions/include/accel-ppp/client-ipv6-pool.xml.i @@ -0,0 +1,69 @@ +<!-- include start from accel-ppp/client-ipv6-pool.xml.i --> +<tagNode name="client-ipv6-pool"> + <properties> + <help>Pool of client IPv6 addresses</help> + <valueHelp> + <format>txt</format> + <description>Name of IPv6 pool</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + </properties> + <children> + <tagNode name="prefix"> + <properties> + <help>Pool of addresses used to assign to clients</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="mask"> + <properties> + <help>Prefix length used for individual client</help> + <valueHelp> + <format>u32:48-128</format> + <description>Client prefix length</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 48-128"/> + </constraint> + </properties> + <defaultValue>64</defaultValue> + </leafNode> + </children> + </tagNode> + <tagNode name="delegate"> + <properties> + <help>Subnet used to delegate prefix through DHCPv6-PD (RFC3633)</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="delegation-prefix"> + <properties> + <help>Prefix length delegated to client</help> + <valueHelp> + <format>u32:32-64</format> + <description>Delegated prefix length</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 32-64"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/default-ipv6-pool.xml.i b/interface-definitions/include/accel-ppp/default-ipv6-pool.xml.i new file mode 100644 index 0000000..1093f67 --- /dev/null +++ b/interface-definitions/include/accel-ppp/default-ipv6-pool.xml.i @@ -0,0 +1,17 @@ +<!-- include start from accel-ppp/default-pool.xml.i --> +<leafNode name="default-ipv6-pool"> + <properties> + <help>Default client IPv6 pool name</help> + <completionHelp> + <path>${COMP_WORDS[@]:1:${#COMP_WORDS[@]}-3} client-ipv6-pool</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Default IPv6 pool</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/default-pool.xml.i b/interface-definitions/include/accel-ppp/default-pool.xml.i new file mode 100644 index 0000000..e06642c --- /dev/null +++ b/interface-definitions/include/accel-ppp/default-pool.xml.i @@ -0,0 +1,17 @@ +<!-- include start from accel-ppp/default-pool.xml.i --> +<leafNode name="default-pool"> + <properties> + <help>Default client IP pool name</help> + <completionHelp> + <path>${COMP_WORDS[@]:1:${#COMP_WORDS[@]}-3} client-ip-pool</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Default IP pool</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/extended-scripts.xml.i b/interface-definitions/include/accel-ppp/extended-scripts.xml.i new file mode 100644 index 0000000..53ff6d5 --- /dev/null +++ b/interface-definitions/include/accel-ppp/extended-scripts.xml.i @@ -0,0 +1,41 @@ +<!-- include start from accel-ppp/extended-scripts.xml.i --> +<node name="extended-scripts"> + <properties> + <help>Extended script execution</help> + </properties> + <children> + <leafNode name="on-pre-up"> + <properties> + <help>Script to run before session interface comes up</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + <leafNode name="on-up"> + <properties> + <help>Script to run when session interface is completely configured and started</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + <leafNode name="on-down"> + <properties> + <help>Script to run when session interface going to terminate</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + <leafNode name="on-change"> + <properties> + <help>Script to run when session interface changed by RADIUS CoA handling</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/gateway-address-multi.xml.i b/interface-definitions/include/accel-ppp/gateway-address-multi.xml.i new file mode 100644 index 0000000..dcc58b9 --- /dev/null +++ b/interface-definitions/include/accel-ppp/gateway-address-multi.xml.i @@ -0,0 +1,17 @@ +<!-- include start from accel-ppp/gateway-address-multi.xml.i --> +<leafNode name="gateway-address"> + <properties> + <help>Gateway IP address</help> + <constraintErrorMessage>invalid IPv4 address</constraintErrorMessage> + <valueHelp> + <format>ipv4net</format> + <description>Default Gateway, mask send to the client</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv4-host"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/gateway-address.xml.i b/interface-definitions/include/accel-ppp/gateway-address.xml.i new file mode 100644 index 0000000..59f8b50 --- /dev/null +++ b/interface-definitions/include/accel-ppp/gateway-address.xml.i @@ -0,0 +1,15 @@ +<!-- include start from accel-ppp/gateway-address.xml.i --> +<leafNode name="gateway-address"> + <properties> + <help>Gateway IP address</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <constraintErrorMessage>invalid IPv4 address</constraintErrorMessage> + <valueHelp> + <format>ipv4</format> + <description>Default Gateway send to the client</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/lcp-echo-interval-failure.xml.i b/interface-definitions/include/accel-ppp/lcp-echo-interval-failure.xml.i new file mode 100644 index 0000000..dd7ae12 --- /dev/null +++ b/interface-definitions/include/accel-ppp/lcp-echo-interval-failure.xml.i @@ -0,0 +1,20 @@ +<!-- include start from accel-ppp/lcp-echo-interval-failure.xml.i --> +<leafNode name="lcp-echo-interval"> + <properties> + <help>LCP echo-requests/sec</help> + <constraint> + <validator name="numeric" argument="--positive"/> + </constraint> + </properties> + <defaultValue>30</defaultValue> +</leafNode> +<leafNode name="lcp-echo-failure"> + <properties> + <help>Maximum number of Echo-Requests may be sent without valid reply</help> + <constraint> + <validator name="numeric" argument="--positive"/> + </constraint> + </properties> + <defaultValue>3</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/lcp-echo-timeout.xml.i b/interface-definitions/include/accel-ppp/lcp-echo-timeout.xml.i new file mode 100644 index 0000000..a630bec --- /dev/null +++ b/interface-definitions/include/accel-ppp/lcp-echo-timeout.xml.i @@ -0,0 +1,11 @@ +<!-- include start from accel-ppp/lcp-echo-timeout.xml.i --> +<leafNode name="lcp-echo-timeout"> + <properties> + <help>Timeout in seconds to wait for any peer activity. If this option specified it turns on adaptive lcp echo functionality and "lcp-echo-failure" is not used.</help> + <constraint> + <validator name="numeric" argument="--positive"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/limits.xml.i b/interface-definitions/include/accel-ppp/limits.xml.i new file mode 100644 index 0000000..df72b79 --- /dev/null +++ b/interface-definitions/include/accel-ppp/limits.xml.i @@ -0,0 +1,28 @@ +<!-- include start from accel-ppp/limits.xml.i --> +<node name="limits"> + <properties> + <help>Limits the connection rate from a single source</help> + </properties> + <children> + <leafNode name="connection-limit"> + <properties> + <help>Acceptable rate of connections (e.g. 1/min, 60/sec)</help> + <constraint> + <regex>[0-9]+\/(min|sec)</regex> + </constraint> + <constraintErrorMessage>illegal value</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="burst"> + <properties> + <help>Burst count</help> + </properties> + </leafNode> + <leafNode name="timeout"> + <properties> + <help>Timeout in seconds</help> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/log.xml.i b/interface-definitions/include/accel-ppp/log.xml.i new file mode 100644 index 0000000..96ce93f --- /dev/null +++ b/interface-definitions/include/accel-ppp/log.xml.i @@ -0,0 +1,42 @@ +<!-- include start from accel-ppp/log.xml.i --> +<node name="log"> + <properties> + <help>Server logging </help> + </properties> + <children> + <leafNode name="level"> + <properties> + <help>Specifies log level</help> + <valueHelp> + <format>0</format> + <description>Turn off logging</description> + </valueHelp> + <valueHelp> + <format>1</format> + <description>Log only error messages</description> + </valueHelp> + <valueHelp> + <format>2</format> + <description>Log error and warning messages</description> + </valueHelp> + <valueHelp> + <format>3</format> + <description>Log error, warning and minimum information messages</description> + </valueHelp> + <valueHelp> + <format>4</format> + <description>Log error, warning and full information messages</description> + </valueHelp> + <valueHelp> + <format>5</format> + <description>Log all messages including debug messages</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-5"/> + </constraint> + </properties> + <defaultValue>3</defaultValue> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/max-concurrent-sessions.xml.i b/interface-definitions/include/accel-ppp/max-concurrent-sessions.xml.i new file mode 100644 index 0000000..f6ef410 --- /dev/null +++ b/interface-definitions/include/accel-ppp/max-concurrent-sessions.xml.i @@ -0,0 +1,15 @@ +<!-- include start from accel-ppp/max-concurrent-sessions.xml.i --> +<leafNode name="max-concurrent-sessions"> + <properties> + <help>Maximum number of concurrent session start attempts</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Maximum number of concurrent session start attempts</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 0-65535"/> + </constraint> + <constraintErrorMessage>Maximum concurent sessions must be in range 0-65535</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/mtu-128-16384.xml.i b/interface-definitions/include/accel-ppp/mtu-128-16384.xml.i new file mode 100644 index 0000000..5661bdc --- /dev/null +++ b/interface-definitions/include/accel-ppp/mtu-128-16384.xml.i @@ -0,0 +1,11 @@ +<!-- include start from accel-ppp/mtu-128-16384.xml.i --> +<leafNode name="mtu"> + <properties> + <help>Maximum Transmission Unit (MTU)</help> + <constraint> + <validator name="numeric" argument="--range 128-16384"/> + </constraint> + </properties> + <defaultValue>1492</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/ppp-interface-cache.xml.i b/interface-definitions/include/accel-ppp/ppp-interface-cache.xml.i new file mode 100644 index 0000000..019601c --- /dev/null +++ b/interface-definitions/include/accel-ppp/ppp-interface-cache.xml.i @@ -0,0 +1,14 @@ +<!-- include start from accel-ppp/ppp-interface-cache.xml.i --> +<leafNode name="interface-cache"> + <properties> + <help>PPP interface cache</help> + <valueHelp> + <format>u32:1-256000</format> + <description>Count of interfaces to keep in cache</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-256000"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/ppp-mppe.xml.i b/interface-definitions/include/accel-ppp/ppp-mppe.xml.i new file mode 100644 index 0000000..4c2e84c --- /dev/null +++ b/interface-definitions/include/accel-ppp/ppp-mppe.xml.i @@ -0,0 +1,26 @@ +<!-- include start from accel-ppp/ppp-mppe.xml.i --> +<leafNode name="mppe"> + <properties> + <help>Specifies mppe negotiation preferences</help> + <completionHelp> + <list>require prefer deny</list> + </completionHelp> + <valueHelp> + <format>require</format> + <description>send mppe request, if client rejects, drop the connection</description> + </valueHelp> + <valueHelp> + <format>prefer</format> + <description>send mppe request, if client rejects continue</description> + </valueHelp> + <valueHelp> + <format>deny</format> + <description>drop all mppe</description> + </valueHelp> + <constraint> + <regex>(require|prefer|deny)</regex> + </constraint> + </properties> + <defaultValue>prefer</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/ppp-options-ipv4.xml.i b/interface-definitions/include/accel-ppp/ppp-options-ipv4.xml.i new file mode 100644 index 0000000..a45390f --- /dev/null +++ b/interface-definitions/include/accel-ppp/ppp-options-ipv4.xml.i @@ -0,0 +1,23 @@ +<!-- include start from accel-ppp/ppp-options-ipv4.xml.i --> +<leafNode name="ipv4"> + <properties> + <help>IPv4 negotiation algorithm</help> + <constraint> + <regex>(deny|allow)</regex> + </constraint> + <constraintErrorMessage>invalid value</constraintErrorMessage> + <valueHelp> + <format>deny</format> + <description>Do not negotiate IPv4</description> + </valueHelp> + <valueHelp> + <format>allow</format> + <description>Negotiate IPv4 only if client requests</description> + </valueHelp> + <completionHelp> + <list>deny allow</list> + </completionHelp> + </properties> + <defaultValue>allow</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/ppp-options-ipv6-interface-id.xml.i b/interface-definitions/include/accel-ppp/ppp-options-ipv6-interface-id.xml.i new file mode 100644 index 0000000..c4cf0a4 --- /dev/null +++ b/interface-definitions/include/accel-ppp/ppp-options-ipv6-interface-id.xml.i @@ -0,0 +1,54 @@ +<!-- include start from accel-ppp/ppp-options-ipv6-interface-id.xml.i --> +<leafNode name="ipv6-interface-id"> + <properties> + <help>Fixed or random interface identifier for IPv6</help> + <completionHelp> + <list>random</list> + </completionHelp> + <valueHelp> + <format>random</format> + <description>Random interface identifier for IPv6</description> + </valueHelp> + <valueHelp> + <format>x:x:x:x</format> + <description>specify interface identifier for IPv6</description> + </valueHelp> + <constraint> + <regex>(random|((\d+){1,4}:){3}(\d+){1,4})</regex> + </constraint> + </properties> +</leafNode> +<leafNode name="ipv6-peer-interface-id"> + <properties> + <help>Peer interface identifier for IPv6</help> + <completionHelp> + <list>random calling-sid ipv4-addr</list> + </completionHelp> + <valueHelp> + <format>x:x:x:x</format> + <description>Interface identifier for IPv6</description> + </valueHelp> + <valueHelp> + <format>random</format> + <description>Use a random interface identifier for IPv6</description> + </valueHelp> + <valueHelp> + <format>ipv4-addr</format> + <description>Calculate interface identifier from IPv4 address, for example 192:168:0:1</description> + </valueHelp> + <valueHelp> + <format>calling-sid</format> + <description>Calculate interface identifier from calling-station-id</description> + </valueHelp> + <constraint> + <regex>(random|calling-sid|ipv4-addr|((\d+){1,4}:){3}(\d+){1,4})</regex> + </constraint> + </properties> +</leafNode> +<leafNode name="ipv6-accept-peer-interface-id"> + <properties> + <help>Accept peer interface identifier</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/ppp-options-ipv6.xml.i b/interface-definitions/include/accel-ppp/ppp-options-ipv6.xml.i new file mode 100644 index 0000000..98abc11 --- /dev/null +++ b/interface-definitions/include/accel-ppp/ppp-options-ipv6.xml.i @@ -0,0 +1,31 @@ +<!-- include start from accel-ppp/ppp-options-ipv6.xml.i --> +<leafNode name="ipv6"> + <properties> + <help>IPv6 (IPCP6) negotiation algorithm</help> + <constraint> + <regex>(deny|allow|prefer|require)</regex> + </constraint> + <constraintErrorMessage>invalid value</constraintErrorMessage> + <valueHelp> + <format>deny</format> + <description>Do not negotiate IPv6</description> + </valueHelp> + <valueHelp> + <format>allow</format> + <description>Negotiate IPv6 only if client requests</description> + </valueHelp> + <valueHelp> + <format>prefer</format> + <description>Ask client for IPv6 negotiation, do not fail if it rejects</description> + </valueHelp> + <valueHelp> + <format>require</format> + <description>Require IPv6 negotiation</description> + </valueHelp> + <completionHelp> + <list>deny allow prefer require</list> + </completionHelp> + </properties> + <defaultValue>deny</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/ppp-options.xml.i b/interface-definitions/include/accel-ppp/ppp-options.xml.i new file mode 100644 index 0000000..9b4f1d0 --- /dev/null +++ b/interface-definitions/include/accel-ppp/ppp-options.xml.i @@ -0,0 +1,65 @@ +<!-- include start from accel-ppp/ppp-options.xml.i --> +<node name="ppp-options"> + <properties> + <help>Advanced protocol options</help> + </properties> + <children> + <leafNode name="min-mtu"> + <properties> + <help>Minimum acceptable MTU (68-65535)</help> + <constraint> + <validator name="numeric" argument="--range 68-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="mru"> + <properties> + <help>Preferred MRU (68-65535)</help> + <constraint> + <validator name="numeric" argument="--range 68-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="disable-ccp"> + <properties> + <help>Disable Compression Control Protocol (CCP)</help> + <valueless /> + </properties> + </leafNode> + #include <include/accel-ppp/ppp-mppe.xml.i> + #include <include/accel-ppp/lcp-echo-interval-failure.xml.i> + #include <include/accel-ppp/lcp-echo-timeout.xml.i> + #include <include/accel-ppp/ppp-interface-cache.xml.i> + <leafNode name="ipv4"> + <properties> + <help>IPv4 (IPCP) negotiation algorithm</help> + <constraint> + <regex>(deny|allow|prefer|require)</regex> + </constraint> + <constraintErrorMessage>invalid value</constraintErrorMessage> + <valueHelp> + <format>deny</format> + <description>Do not negotiate IPv4</description> + </valueHelp> + <valueHelp> + <format>allow</format> + <description>Negotiate IPv4 only if client requests</description> + </valueHelp> + <valueHelp> + <format>prefer</format> + <description>Ask client for IPv4 negotiation, do not fail if it rejects</description> + </valueHelp> + <valueHelp> + <format>require</format> + <description>Require IPv4 negotiation</description> + </valueHelp> + <completionHelp> + <list>deny allow prefer require</list> + </completionHelp> + </properties> + </leafNode> + #include <include/accel-ppp/ppp-options-ipv6.xml.i> + #include <include/accel-ppp/ppp-options-ipv6-interface-id.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/radius-accounting-interim-interval.xml.i b/interface-definitions/include/accel-ppp/radius-accounting-interim-interval.xml.i new file mode 100644 index 0000000..311ef96 --- /dev/null +++ b/interface-definitions/include/accel-ppp/radius-accounting-interim-interval.xml.i @@ -0,0 +1,15 @@ +<!-- include start from accel-ppp/radius-accounting-interim-interval.xml.i --> +<leafNode name="accounting-interim-interval"> + <properties> + <help>Interval in seconds to send accounting information</help> + <valueHelp> + <format>u32:1-3600</format> + <description>Interval in seconds to send accounting information</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-3600"/> + </constraint> + <constraintErrorMessage>Interval value must be between 1 and 3600 seconds</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/radius-additions-disable-accounting.xml.i b/interface-definitions/include/accel-ppp/radius-additions-disable-accounting.xml.i new file mode 100644 index 0000000..c723c31 --- /dev/null +++ b/interface-definitions/include/accel-ppp/radius-additions-disable-accounting.xml.i @@ -0,0 +1,8 @@ +<!-- include start from accel-ppp/radius-additions-disable-accounting.xml.i --> +<leafNode name="disable-accounting"> + <properties> + <help>Disable accounting</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/radius-additions-rate-limit.xml.i b/interface-definitions/include/accel-ppp/radius-additions-rate-limit.xml.i new file mode 100644 index 0000000..c0367b8 --- /dev/null +++ b/interface-definitions/include/accel-ppp/radius-additions-rate-limit.xml.i @@ -0,0 +1,40 @@ +<!-- include start from accel-ppp/radius-additions-rate-limit.xml.i --> +<node name="rate-limit"> + <properties> + <help>Upload/Download speed limits</help> + </properties> + <children> + <leafNode name="attribute"> + <properties> + <help>RADIUS attribute that contains rate information</help> + </properties> + <defaultValue>Filter-Id</defaultValue> + </leafNode> + <leafNode name="vendor"> + <properties> + <help>Vendor dictionary</help> + </properties> + </leafNode> + <leafNode name="enable"> + <properties> + <help>Enable bandwidth shaping via RADIUS</help> + <valueless /> + </properties> + </leafNode> + <leafNode name="multiplier"> + <properties> + <help>Shaper multiplier</help> + <valueHelp> + <format><0.001-1000></format> + <description>Shaper multiplier</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0.001-1000 --float"/> + </constraint> + <constraintErrorMessage>Multiplier needs to be between 0.001 and 1000</constraintErrorMessage> + </properties> + <defaultValue>1</defaultValue> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/radius-additions.xml.i b/interface-definitions/include/accel-ppp/radius-additions.xml.i new file mode 100644 index 0000000..5222ba8 --- /dev/null +++ b/interface-definitions/include/accel-ppp/radius-additions.xml.i @@ -0,0 +1,158 @@ +<!-- include start from accel-ppp/radius-additions.xml.i --> +<node name="radius"> + <children> + <leafNode name="accounting-interim-interval"> + <properties> + <help>Interval in seconds to send accounting information</help> + <valueHelp> + <format>u32:1-3600</format> + <description>Interval in seconds to send accounting information</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-3600"/> + </constraint> + <constraintErrorMessage>Interval value must be between 1 and 3600 seconds</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="acct-interim-jitter"> + <properties> + <help>Maximum jitter value in seconds to be applied to accounting information interval</help> + <valueHelp> + <format>u32:1-60</format> + <description>Maximum jitter value in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-60"/> + </constraint> + <constraintErrorMessage>Jitter value must be between 1 and 60 seconds</constraintErrorMessage> + </properties> + </leafNode> + <tagNode name="server"> + <children> + <leafNode name="acct-port"> + <properties> + <help>Accounting port</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>1813</defaultValue> + </leafNode> + #include <include/accel-ppp/radius-additions-disable-accounting.xml.i> + <leafNode name="fail-time"> + <properties> + <help>Mark server unavailable for <n> seconds on failure</help> + <valueHelp> + <format>u32:0-600</format> + <description>Fail time penalty</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-600"/> + </constraint> + <constraintErrorMessage>Fail time must be between 0 and 600 seconds</constraintErrorMessage> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + #include <include/radius-priority.xml.i> + <leafNode name="backup"> + <properties> + <help>Use backup server if other servers are not available</help> + <valueless/> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="timeout"> + <properties> + <help>Timeout in seconds to wait response from RADIUS server</help> + <valueHelp> + <format>u32:1-60</format> + <description>Timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-60"/> + </constraint> + <constraintErrorMessage>Timeout must be between 1 and 60 seconds</constraintErrorMessage> + </properties> + <defaultValue>3</defaultValue> + </leafNode> + <leafNode name="acct-timeout"> + <properties> + <help>Timeout for Interim-Update packets, terminate session afterwards</help> + <valueHelp> + <format>u32:0-60</format> + <description>Timeout in seconds, 0 to keep active</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-60"/> + </constraint> + <constraintErrorMessage>Timeout must be between 0 and 60 seconds</constraintErrorMessage> + </properties> + <defaultValue>3</defaultValue> + </leafNode> + <leafNode name="max-try"> + <properties> + <help>Number of tries to send Access-Request/Accounting-Request queries</help> + <valueHelp> + <format>u32:1-20</format> + <description>Maximum tries</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-20"/> + </constraint> + <constraintErrorMessage>Maximum tries must be between 1 and 20</constraintErrorMessage> + </properties> + <defaultValue>3</defaultValue> + </leafNode> + #include <include/radius-nas-identifier.xml.i> + #include <include/radius-nas-ip-address.xml.i> + <leafNode name="preallocate-vif"> + <properties> + <help>Enable attribute NAS-Port-Id in Access-Request</help> + <valueless/> + </properties> + </leafNode> + <node name="dynamic-author"> + <properties> + <help>Dynamic Authorization Extension/Change of Authorization server</help> + </properties> + <children> + <leafNode name="server"> + <properties> + <help>IP address for Dynamic Authorization Extension server (DM/CoA)</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address for dynamic authorization server</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="port"> + <properties> + <help>Port for Dynamic Authorization Extension server (DM/CoA)</help> + <valueHelp> + <format>u32:1-65535</format> + <description>TCP port</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>1700</defaultValue> + </leafNode> + <leafNode name="key"> + <properties> + <help>Shared secret for Dynamic Authorization Extension server</help> + </properties> + </leafNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/shaper.xml.i b/interface-definitions/include/accel-ppp/shaper.xml.i new file mode 100644 index 0000000..b4f9536 --- /dev/null +++ b/interface-definitions/include/accel-ppp/shaper.xml.i @@ -0,0 +1,21 @@ +<!-- include start from accel-ppp/shaper.xml.i --> +<node name="shaper"> + <properties> + <help>Traffic shaper bandwidth parameters</help> + </properties> + <children> + <leafNode name="fwmark"> + <properties> + <help>Firewall mark value for traffic that excludes from shaping</help> + <valueHelp> + <format>u32:1-2147483647</format> + <description>Match firewall mark value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2147483647"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/snmp.xml.i b/interface-definitions/include/accel-ppp/snmp.xml.i new file mode 100644 index 0000000..373ced1 --- /dev/null +++ b/interface-definitions/include/accel-ppp/snmp.xml.i @@ -0,0 +1,15 @@ +<!-- include start from accel-ppp/snmp.xml.i --> +<node name="snmp"> + <properties> + <help>Enable SNMP</help> + </properties> + <children> + <leafNode name="master-agent"> + <properties> + <help>Enable SNMP master agent mode</help> + <valueless /> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/vlan-mon.xml.i b/interface-definitions/include/accel-ppp/vlan-mon.xml.i new file mode 100644 index 0000000..d5bacb0 --- /dev/null +++ b/interface-definitions/include/accel-ppp/vlan-mon.xml.i @@ -0,0 +1,8 @@ +<!-- include start from accel-ppp/vlan-mon.xml.i --> +<leafNode name="vlan-mon"> + <properties> + <help>Automatically create VLAN interfaces</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/vlan.xml.i b/interface-definitions/include/accel-ppp/vlan.xml.i new file mode 100644 index 0000000..5ef4de6 --- /dev/null +++ b/interface-definitions/include/accel-ppp/vlan.xml.i @@ -0,0 +1,20 @@ +<!-- include start from accel-ppp/vlan.xml.i --> +<leafNode name="vlan"> + <properties> + <help>VLAN monitor for automatic creation of VLAN interfaces</help> + <valueHelp> + <format>u32:1-4094</format> + <description>VLAN for automatic creation</description> + </valueHelp> + <valueHelp> + <format>start-end</format> + <description>VLAN range for automatic creation (e.g. 1-4094)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 1-4094"/> + </constraint> + <constraintErrorMessage>VLAN IDs need to be in range 1-4094</constraintErrorMessage> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/wins-server.xml.i b/interface-definitions/include/accel-ppp/wins-server.xml.i new file mode 100644 index 0000000..f7f483f --- /dev/null +++ b/interface-definitions/include/accel-ppp/wins-server.xml.i @@ -0,0 +1,15 @@ +<!-- include start from accel-ppp/wins-server.xml.i --> +<leafNode name="wins-server"> + <properties> + <help>Windows Internet Name Service (WINS) servers propagated to client</help> + <valueHelp> + <format>ipv4</format> + <description>Domain Name Server (DNS) IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/address-ipv4-ipv6-single.xml.i b/interface-definitions/include/address-ipv4-ipv6-single.xml.i new file mode 100644 index 0000000..dc3d6fc --- /dev/null +++ b/interface-definitions/include/address-ipv4-ipv6-single.xml.i @@ -0,0 +1,18 @@ +<!-- include start from interface/address-ipv4-ipv6.xml.i --> +<leafNode name="address"> + <properties> + <help>IP address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/allow-client.xml.i b/interface-definitions/include/allow-client.xml.i new file mode 100644 index 0000000..1b06e2c --- /dev/null +++ b/interface-definitions/include/allow-client.xml.i @@ -0,0 +1,35 @@ +<!-- include start from allow-client.xml.i --> +<node name="allow-client"> + <properties> + <help>Restrict to allowed IP client addresses</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>Allowed IP client addresses</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 address and prefix length</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + <validator name="ip-cidr"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/arp-ndp-table-size.xml.i b/interface-definitions/include/arp-ndp-table-size.xml.i new file mode 100644 index 0000000..dec86e9 --- /dev/null +++ b/interface-definitions/include/arp-ndp-table-size.xml.i @@ -0,0 +1,14 @@ +<!-- include start from arp-ndp-table-size.xml.i --> +<leafNode name="table-size"> + <properties> + <help>Maximum number of entries to keep in the cache</help> + <completionHelp> + <list>1024 2048 4096 8192 16384 32768</list> + </completionHelp> + <constraint> + <regex>(1024|2048|4096|8192|16384|32768)</regex> + </constraint> + </properties> + <defaultValue>8192</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/auth-local-users.xml.i b/interface-definitions/include/auth-local-users.xml.i new file mode 100644 index 0000000..9fb5074 --- /dev/null +++ b/interface-definitions/include/auth-local-users.xml.i @@ -0,0 +1,26 @@ +<!-- include start from auth-local-users.xml.i --> +<node name="local-users"> + <properties> + <help>Local user authentication</help> + </properties> + <children> + <tagNode name="username"> + <properties> + <help>Username used for authentication</help> + <valueHelp> + <format>txt</format> + <description>Username used for authentication</description> + </valueHelp> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <leafNode name="password"> + <properties> + <help>Password used for authentication</help> + </properties> + </leafNode> + </children> + </tagNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/babel/interface.xml.i b/interface-definitions/include/babel/interface.xml.i new file mode 100644 index 0000000..a122ef0 --- /dev/null +++ b/interface-definitions/include/babel/interface.xml.i @@ -0,0 +1,187 @@ +<!-- include start from babel/interface.xml.i --> +<tagNode name="interface"> + <properties> + <help>Interface name</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + <leafNode name="type"> + <properties> + <help>Interface type</help> + <completionHelp> + <list>auto wired wireless</list> + </completionHelp> + <valueHelp> + <format>auto</format> + <description>Automatically detect interface type</description> + </valueHelp> + <valueHelp> + <format>wired</format> + <description>Wired interface</description> + </valueHelp> + <valueHelp> + <format>wireless</format> + <description>Wireless interface</description> + </valueHelp> + <constraint> + <regex>(auto|wired|wireless)</regex> + </constraint> + </properties> + <defaultValue>auto</defaultValue> + </leafNode> + <leafNode name="split-horizon"> + <properties> + <help>Split horizon parameters</help> + <completionHelp> + <list>default enable disable</list> + </completionHelp> + <valueHelp> + <format>default</format> + <description>Enable on wired interfaces, and disable on wireless interfaces</description> + </valueHelp> + <valueHelp> + <format>enable</format> + <description>Enable split horizon processing</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable split horizon processing</description> + </valueHelp> + <constraint> + <regex>(default|enable|disable)</regex> + </constraint> + </properties> + <defaultValue>default</defaultValue> + </leafNode> + <leafNode name="hello-interval"> + <properties> + <help>Time between scheduled hellos</help> + <valueHelp> + <format>u32:20-655340</format> + <description>Milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 20-655340"/> + </constraint> + </properties> + <defaultValue>4000</defaultValue> + </leafNode> + <leafNode name="update-interval"> + <properties> + <help>Time between scheduled updates</help> + <valueHelp> + <format>u32:20-655340</format> + <description>Milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 20-655340"/> + </constraint> + </properties> + <defaultValue>20000</defaultValue> + </leafNode> + <leafNode name="rxcost"> + <properties> + <help>Base receive cost for this interface</help> + <valueHelp> + <format>u32:1-65534</format> + <description>Base receive cost</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65534"/> + </constraint> + </properties> + </leafNode> + <leafNode name="rtt-decay"> + <properties> + <help>Decay factor for exponential moving average of RTT samples</help> + <valueHelp> + <format>u32:1-256</format> + <description>Decay factor, in units of 1/256</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-256"/> + </constraint> + </properties> + <defaultValue>42</defaultValue> + </leafNode> + <leafNode name="rtt-min"> + <properties> + <help>Minimum RTT</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>10</defaultValue> + </leafNode> + <leafNode name="rtt-max"> + <properties> + <help>Maximum RTT</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>120</defaultValue> + </leafNode> + <leafNode name="max-rtt-penalty"> + <properties> + <help>Maximum additional cost due to RTT</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Milliseconds (0 to disable the use of RTT-based cost)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + <defaultValue>150</defaultValue> + </leafNode> + <leafNode name="enable-timestamps"> + <properties> + <help>Enable timestamps with each Hello and IHU message in order to compute RTT values</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="channel"> + <properties> + <help>Channel number for diversity routing</help> + <completionHelp> + <list>interfering non-interfering</list> + </completionHelp> + <valueHelp> + <format>u32:1-254</format> + <description>Interfaces with a channel number interfere with interfering interfaces and interfaces with the same channel number</description> + </valueHelp> + <valueHelp> + <format>interfering</format> + <description>Interfering interfaces are assumed to interfere with all other channels except non-interfering channels</description> + </valueHelp> + <valueHelp> + <format>non-interfering</format> + <description>Non-interfering interfaces only interfere with themselves</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-254"/> + <regex>(interfering|non-interfering)</regex> + </constraint> + </properties> + </leafNode> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/bfd/bfd.xml.i b/interface-definitions/include/bfd/bfd.xml.i new file mode 100644 index 0000000..022956d --- /dev/null +++ b/interface-definitions/include/bfd/bfd.xml.i @@ -0,0 +1,10 @@ +<!-- include start from bfd/bfd.xml.i --> +<node name="bfd"> + <properties> + <help>Enable Bidirectional Forwarding Detection (BFD)</help> + </properties> + <children> + #include <include/bfd/profile.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bfd/common.xml.i b/interface-definitions/include/bfd/common.xml.i new file mode 100644 index 0000000..8e6999d --- /dev/null +++ b/interface-definitions/include/bfd/common.xml.i @@ -0,0 +1,90 @@ +<!-- include start from bfd/common.xml.i --> +<leafNode name="echo-mode"> + <properties> + <help>Enables the echo transmission mode</help> + <valueless/> + </properties> +</leafNode> +<node name="interval"> + <properties> + <help>Configure timer intervals</help> + </properties> + <children> + <leafNode name="receive"> + <properties> + <help>Minimum interval of receiving control packets</help> + <valueHelp> + <format>u32:10-60000</format> + <description>Interval in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 10-60000"/> + </constraint> + </properties> + <defaultValue>300</defaultValue> + </leafNode> + <leafNode name="transmit"> + <properties> + <help>Minimum interval of transmitting control packets</help> + <valueHelp> + <format>u32:10-60000</format> + <description>Interval in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 10-60000"/> + </constraint> + </properties> + <defaultValue>300</defaultValue> + </leafNode> + <leafNode name="multiplier"> + <properties> + <help>Multiplier to determine packet loss</help> + <valueHelp> + <format>u32:2-255</format> + <description>Remote transmission interval will be multiplied by this value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 2-255"/> + </constraint> + </properties> + <defaultValue>3</defaultValue> + </leafNode> + <leafNode name="echo-interval"> + <properties> + <help>Echo receive transmission interval</help> + <valueHelp> + <format>u32:10-60000</format> + <description>The minimal echo receive transmission interval that this system is capable of handling</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 10-60000"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<leafNode name="minimum-ttl"> + <properties> + <help>Expect packets with at least this TTL</help> + <valueHelp> + <format>u32:1-254</format> + <description>Minimum TTL expected</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-254"/> + </constraint> + </properties> +</leafNode> +<leafNode name="passive"> + <properties> + <help>Do not attempt to start sessions</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="shutdown"> + <properties> + <help>Disable this peer</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bfd/profile.xml.i b/interface-definitions/include/bfd/profile.xml.i new file mode 100644 index 0000000..5ff0572 --- /dev/null +++ b/interface-definitions/include/bfd/profile.xml.i @@ -0,0 +1,14 @@ +<!-- include start from bfd/profile.xml.i --> +<leafNode name="profile"> + <properties> + <help>Use settings from BFD profile</help> + <completionHelp> + <path>protocols bfd profile</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>BFD profile name</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-aggregate-address.xml.i b/interface-definitions/include/bgp/afi-aggregate-address.xml.i new file mode 100644 index 0000000..c1b7958 --- /dev/null +++ b/interface-definitions/include/bgp/afi-aggregate-address.xml.i @@ -0,0 +1,15 @@ +<!-- include start from bgp/afi-aggregate-address.xml.i --> +<leafNode name="as-set"> + <properties> + <help>Generate AS-set path information for this aggregate address</help> + <valueless/> + </properties> +</leafNode> +#include <include/route-map.xml.i> +<leafNode name="summary-only"> + <properties> + <help>Announce the aggregate summary network only</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-allowas-in.xml.i b/interface-definitions/include/bgp/afi-allowas-in.xml.i new file mode 100644 index 0000000..2df4b85 --- /dev/null +++ b/interface-definitions/include/bgp/afi-allowas-in.xml.i @@ -0,0 +1,21 @@ +<!-- include start from bgp/afi-allowas-in.xml.i --> +<node name="allowas-in"> + <properties> + <help>Accept route that contains the local-as in the as-path</help> + </properties> + <children> + <leafNode name="number"> + <properties> + <help>Number of occurrences of AS number</help> + <valueHelp> + <format>u32:1-10</format> + <description>Number of times AS is allowed in path</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-10"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-attribute-unchanged.xml.i b/interface-definitions/include/bgp/afi-attribute-unchanged.xml.i new file mode 100644 index 0000000..6d39e45 --- /dev/null +++ b/interface-definitions/include/bgp/afi-attribute-unchanged.xml.i @@ -0,0 +1,27 @@ +<!-- include start from bgp/afi-attribute-unchanged.xml.i --> +<node name="attribute-unchanged"> + <properties> + <help>BGP attributes are sent unchanged</help> + </properties> + <children> + <leafNode name="as-path"> + <properties> + <help>Send AS path unchanged</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="med"> + <properties> + <help>Send multi-exit discriminator unchanged</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="next-hop"> + <properties> + <help>Send nexthop unchanged</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-capability-orf.xml.i b/interface-definitions/include/bgp/afi-capability-orf.xml.i new file mode 100644 index 0000000..05c3368 --- /dev/null +++ b/interface-definitions/include/bgp/afi-capability-orf.xml.i @@ -0,0 +1,28 @@ +<!-- include start from bgp/afi-capability-orf.xml.i --> +<node name="orf"> + <properties> + <help>Advertise ORF capability to this peer</help> + </properties> + <children> + <node name="prefix-list"> + <properties> + <help>Advertise prefix-list ORF capability to this peer</help> + </properties> + <children> + <leafNode name="receive"> + <properties> + <help>Capability to receive the ORF</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="send"> + <properties> + <help>Capability to send the ORF</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-common-flowspec.xml.i b/interface-definitions/include/bgp/afi-common-flowspec.xml.i new file mode 100644 index 0000000..fb3308e --- /dev/null +++ b/interface-definitions/include/bgp/afi-common-flowspec.xml.i @@ -0,0 +1,7 @@ +<!-- include start from bgp/afi-common-flowspec.xml.i --> +#include <include/bgp/afi-filter-list.xml.i> +#include <include/bgp/afi-route-map.xml.i> +#include <include/bgp/afi-route-reflector-client.xml.i> +#include <include/bgp/afi-route-server-client.xml.i> +#include <include/bgp/afi-soft-reconfiguration.xml.i> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-default-originate.xml.i b/interface-definitions/include/bgp/afi-default-originate.xml.i new file mode 100644 index 0000000..ba1ec57 --- /dev/null +++ b/interface-definitions/include/bgp/afi-default-originate.xml.i @@ -0,0 +1,10 @@ +<!-- include start from bgp/afi-default-originate.xml.i --> +<node name="default-originate"> + <properties> + <help>Originate default route to this peer</help> + </properties> + <children> + #include <include/route-map.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-export-import.xml.i b/interface-definitions/include/bgp/afi-export-import.xml.i new file mode 100644 index 0000000..5223af0 --- /dev/null +++ b/interface-definitions/include/bgp/afi-export-import.xml.i @@ -0,0 +1,42 @@ +<!-- include start from bgp/afi-export-import.xml.i --> +<node name="export"> + <properties> + <help>Export routes from this address-family</help> + </properties> + <children> + <leafNode name="vpn"> + <properties> + <help>to/from default instance VPN RIB</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<node name="import"> + <properties> + <help>Import routes to this address-family</help> + </properties> + <children> + <leafNode name="vpn"> + <properties> + <help>to/from default instance VPN RIB</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="vrf"> + <properties> + <help>VRF to import from</help> + <valueHelp> + <format>txt</format> + <description>VRF instance name</description> + </valueHelp> + <completionHelp> + <path>vrf name</path> + <list>default</list> + </completionHelp> + <multi/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-filter-list.xml.i b/interface-definitions/include/bgp/afi-filter-list.xml.i new file mode 100644 index 0000000..df7619a --- /dev/null +++ b/interface-definitions/include/bgp/afi-filter-list.xml.i @@ -0,0 +1,25 @@ +<!-- include start from bgp/afi-filter-list.xml.i --> +<node name="filter-list"> + <properties> + <help>as-path-list to filter route updates to/from this peer</help> + </properties> + <children> + <leafNode name="export"> + <properties> + <help>As-path-list to filter outgoing route updates to this peer</help> + <completionHelp> + <path>policy as-path-list</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="import"> + <properties> + <help>As-path-list to filter incoming route updates from this peer</help> + <completionHelp> + <path>policy as-path-list</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-ipv4-prefix-list.xml.i b/interface-definitions/include/bgp/afi-ipv4-prefix-list.xml.i new file mode 100644 index 0000000..0f760da --- /dev/null +++ b/interface-definitions/include/bgp/afi-ipv4-prefix-list.xml.i @@ -0,0 +1,41 @@ +<!-- include start from bgp/afi-ipv4-prefix-list.xml.i --> +<node name="prefix-list"> + <properties> + <help>IPv4-Prefix-list to filter route updates to/from this peer</help> + </properties> + <children> + <leafNode name="export"> + <properties> + <help>IPv4-Prefix-list to filter outgoing route updates to this peer</help> + <completionHelp> + <path>policy prefix-list</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of IPv4 prefix-list</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Name of prefix-list can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="import"> + <properties> + <help>IPv4-Prefix-list to filter incoming route updates from this peer</help> + <completionHelp> + <path>policy prefix-list</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of IPv4 prefix-list</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Name of prefix-list can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-ipv6-nexthop-local.xml.i b/interface-definitions/include/bgp/afi-ipv6-nexthop-local.xml.i new file mode 100644 index 0000000..c232545 --- /dev/null +++ b/interface-definitions/include/bgp/afi-ipv6-nexthop-local.xml.i @@ -0,0 +1,15 @@ +<!-- include start from bgp/afi-ipv6-nexthop-local.xml.i --> + <node name="nexthop-local"> + <properties> + <help>Nexthop attributes</help> + </properties> + <children> + <leafNode name="unchanged"> + <properties> + <help>Leave link-local nexthop unchanged for this peer</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-ipv6-prefix-list.xml.i b/interface-definitions/include/bgp/afi-ipv6-prefix-list.xml.i new file mode 100644 index 0000000..268d9cb --- /dev/null +++ b/interface-definitions/include/bgp/afi-ipv6-prefix-list.xml.i @@ -0,0 +1,41 @@ +<!-- include start from bgp/afi-ipv6-prefix-list.xml.i --> +<node name="prefix-list"> + <properties> + <help>Prefix-list to filter route updates to/from this peer</help> + </properties> + <children> + <leafNode name="export"> + <properties> + <help>Prefix-list to filter outgoing route updates to this peer</help> + <completionHelp> + <path>policy prefix-list6</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of IPv6 prefix-list</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Name of prefix-list6 can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="import"> + <properties> + <help>Prefix-list to filter incoming route updates from this peer</help> + <completionHelp> + <path>policy prefix-list6</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of IPv6 prefix-list</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Name of prefix-list6 can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-l2vpn-advertise.xml.i b/interface-definitions/include/bgp/afi-l2vpn-advertise.xml.i new file mode 100644 index 0000000..caf0b6b --- /dev/null +++ b/interface-definitions/include/bgp/afi-l2vpn-advertise.xml.i @@ -0,0 +1,10 @@ +<!-- include start from bgp/bgp-afi-l2vpn-advertise.xml.i --> +<node name="unicast"> + <properties> + <help>IPv4 address family</help> + </properties> + <children> + #include <include/route-map.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-l2vpn-common.xml.i b/interface-definitions/include/bgp/afi-l2vpn-common.xml.i new file mode 100644 index 0000000..fef3daf --- /dev/null +++ b/interface-definitions/include/bgp/afi-l2vpn-common.xml.i @@ -0,0 +1,61 @@ +<!-- include start from bgp/afi-l2vpn-common.xml.i --> +<leafNode name="advertise-default-gw"> + <properties> + <help>Advertise All default g/w mac-ip routes in EVPN</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="advertise-svi-ip"> + <properties> + <help>Advertise svi mac-ip routes in EVPN</help> + <valueless/> + </properties> +</leafNode> +#include <include/bgp/route-distinguisher.xml.i> +<node name="route-target"> + <properties> + <help>Route Target</help> + </properties> + <children> + <leafNode name="both"> + <properties> + <help>Route Target both import and export</help> + <valueHelp> + <format>txt</format> + <description>Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN)</description> + </valueHelp> + <constraint> + <validator name="bgp-rd-rt" argument="--route-target"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="import"> + <properties> + <help>Route Target import</help> + <valueHelp> + <format>txt</format> + <description>Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN)</description> + </valueHelp> + <constraint> + <validator name="bgp-rd-rt" argument="--route-target"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="export"> + <properties> + <help>Route Target export</help> + <valueHelp> + <format>txt</format> + <description>Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN)</description> + </valueHelp> + <constraint> + <validator name="bgp-rd-rt" argument="--route-target"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-label.xml.i b/interface-definitions/include/bgp/afi-label.xml.i new file mode 100644 index 0000000..2c5eed1 --- /dev/null +++ b/interface-definitions/include/bgp/afi-label.xml.i @@ -0,0 +1,49 @@ +<!-- include start from bgp/afi-label.xml.i --> +<node name="label"> + <properties> + <help>Label value for VRF</help> + </properties> + <children> + <node name="vpn"> + <properties> + <help>Between current address-family and VPN</help> + </properties> + <children> + <leafNode name="export"> + <properties> + <help>For routes leaked from current address-family to VPN</help> + <completionHelp> + <list>auto</list> + </completionHelp> + <valueHelp> + <format>auto</format> + <description>Automatically assign a label</description> + </valueHelp> + <valueHelp> + <format>u32:0-1048575</format> + <description>Label Value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-1048575"/> + <regex>(auto)</regex> + </constraint> + </properties> + </leafNode> + <node name="allocation-mode"> + <properties> + <help>Label allocation mode</help> + </properties> + <children> + <leafNode name="per-nexthop"> + <properties> + <help>Allocate a label per connected next-hop in the VRF</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-maximum-paths.xml.i b/interface-definitions/include/bgp/afi-maximum-paths.xml.i new file mode 100644 index 0000000..5358bb7 --- /dev/null +++ b/interface-definitions/include/bgp/afi-maximum-paths.xml.i @@ -0,0 +1,33 @@ +<!-- include start from bgp/afi-maximum-paths.xml.i --> +<node name="maximum-paths"> + <properties> + <help>Forward packets over multiple paths</help> + </properties> + <children> + <leafNode name="ebgp"> + <properties> + <help>eBGP maximum paths</help> + <valueHelp> + <format>u32:1-256</format> + <description>Number of paths to consider</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-256"/> + </constraint> + </properties> + </leafNode> + <leafNode name="ibgp"> + <properties> + <help>iBGP maximum paths</help> + <valueHelp> + <format>u32:1-256</format> + <description>Number of paths to consider</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-256"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-nexthop-self.xml.i b/interface-definitions/include/bgp/afi-nexthop-self.xml.i new file mode 100644 index 0000000..36a7512 --- /dev/null +++ b/interface-definitions/include/bgp/afi-nexthop-self.xml.i @@ -0,0 +1,15 @@ +<!-- include start from bgp/afi-nexthop-self.xml.i --> +<node name="nexthop-self"> + <properties> + <help>Disable the next hop calculation for this peer</help> + </properties> + <children> + <leafNode name="force"> + <properties> + <help>Set the next hop to self for reflected routes</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-nexthop-vpn-export.xml.i b/interface-definitions/include/bgp/afi-nexthop-vpn-export.xml.i new file mode 100644 index 0000000..d90597f --- /dev/null +++ b/interface-definitions/include/bgp/afi-nexthop-vpn-export.xml.i @@ -0,0 +1,32 @@ +<!-- include start from bgp/afi-nexthop-vpn-export.xml.i --> +<node name="nexthop"> + <properties> + <help>Specify next hop to use for VRF advertised prefixes</help> + </properties> + <children> + <node name="vpn"> + <properties> + <help>Between current address-family and vpn</help> + </properties> + <children> + <leafNode name="export"> + <properties> + <help>For routes leaked from current address-family to vpn</help> + <valueHelp> + <format>ipv4</format> + <description>BGP neighbor IP address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>BGP neighbor IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> +</node> + <!-- include end --> diff --git a/interface-definitions/include/bgp/afi-path-limit.xml.i b/interface-definitions/include/bgp/afi-path-limit.xml.i new file mode 100644 index 0000000..e3d630a --- /dev/null +++ b/interface-definitions/include/bgp/afi-path-limit.xml.i @@ -0,0 +1,14 @@ +<!-- include start from bgp/afi-path-limit.xml.i --> +<leafNode name="path-limit"> + <properties> + <help>AS-path hopcount limit</help> + <valueHelp> + <format>u32:0-255</format> + <description>AS path hop count limit</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-rd.xml.i b/interface-definitions/include/bgp/afi-rd.xml.i new file mode 100644 index 0000000..beb1447 --- /dev/null +++ b/interface-definitions/include/bgp/afi-rd.xml.i @@ -0,0 +1,28 @@ +<!-- include start from bgp/afi-rd.xml.i --> +<node name="rd"> + <properties> + <help>Specify route distinguisher</help> + </properties> + <children> + <node name="vpn"> + <properties> + <help>Between current address-family and VPN</help> + </properties> + <children> + <leafNode name="export"> + <properties> + <help>For routes leaked from current address-family to VPN</help> + <valueHelp> + <format>ASN:NN_OR_IP-ADDRESS:NN</format> + <description>Route Distinguisher, (x.x.x.x:yyy|xxxx:yyyy)</description> + </valueHelp> + <constraint> + <validator name="bgp-rd-rt" argument="--route-distinguisher"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-redistribute-metric-route-map.xml.i b/interface-definitions/include/bgp/afi-redistribute-metric-route-map.xml.i new file mode 100644 index 0000000..d4c7ac4 --- /dev/null +++ b/interface-definitions/include/bgp/afi-redistribute-metric-route-map.xml.i @@ -0,0 +1,12 @@ +<!-- include start from bgp/afi-redistribute-metric-route-map.xml.i --> +<leafNode name="metric"> + <properties> + <help>Metric for redistributed routes</help> + <valueHelp> + <format>u32:1-4294967295</format> + <description>Metric for redistributed routes</description> + </valueHelp> + </properties> +</leafNode> +#include <include/route-map.xml.i> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-route-map-export-import.xml.i b/interface-definitions/include/bgp/afi-route-map-export-import.xml.i new file mode 100644 index 0000000..3889912 --- /dev/null +++ b/interface-definitions/include/bgp/afi-route-map-export-import.xml.i @@ -0,0 +1,34 @@ +<!-- include start from bgp/afi-route-map.xml.i --> +<leafNode name="export"> + <properties> + <help>Route-map to filter outgoing route updates</help> + <completionHelp> + <path>policy route-map</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Route map name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of route-map can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> +</leafNode> +<leafNode name="import"> + <properties> + <help>Route-map to filter incoming route updates</help> + <completionHelp> + <path>policy route-map</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Route map name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of route-map can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-route-map-vpn.xml.i b/interface-definitions/include/bgp/afi-route-map-vpn.xml.i new file mode 100644 index 0000000..e6be113 --- /dev/null +++ b/interface-definitions/include/bgp/afi-route-map-vpn.xml.i @@ -0,0 +1,17 @@ +<!-- include start from bgp/afi-route-map-vpn.xml.i --> +<node name="route-map"> + <properties> + <help>Route-map to filter route updates to/from this peer</help> + </properties> + <children> + <node name="vpn"> + <properties> + <help>Between current address-family and VPN</help> + </properties> + <children> + #include <include/bgp/afi-route-map-export-import.xml.i> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-route-map.xml.i b/interface-definitions/include/bgp/afi-route-map.xml.i new file mode 100644 index 0000000..0b61781 --- /dev/null +++ b/interface-definitions/include/bgp/afi-route-map.xml.i @@ -0,0 +1,10 @@ +<!-- include start from bgp/afi-route-map.xml.i --> +<node name="route-map"> + <properties> + <help>Route-map to filter route updates to/from this peer</help> + </properties> + <children> + #include <include/bgp/afi-route-map-export-import.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-route-reflector-client.xml.i b/interface-definitions/include/bgp/afi-route-reflector-client.xml.i new file mode 100644 index 0000000..dcb2d18 --- /dev/null +++ b/interface-definitions/include/bgp/afi-route-reflector-client.xml.i @@ -0,0 +1,8 @@ +<!-- include start from bgp/afi-route-reflector-client.xml.i --> +<leafNode name="route-reflector-client"> + <properties> + <help>Peer is a route reflector client</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-route-server-client.xml.i b/interface-definitions/include/bgp/afi-route-server-client.xml.i new file mode 100644 index 0000000..9bb628e --- /dev/null +++ b/interface-definitions/include/bgp/afi-route-server-client.xml.i @@ -0,0 +1,8 @@ +<!-- include start from bgp/afi-route-server-client.xml.i --> +<leafNode name="route-server-client"> + <properties> + <help>Peer is a route server client</help> + <valueless/> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/bgp/afi-route-target-vpn.xml.i b/interface-definitions/include/bgp/afi-route-target-vpn.xml.i new file mode 100644 index 0000000..5784f9e --- /dev/null +++ b/interface-definitions/include/bgp/afi-route-target-vpn.xml.i @@ -0,0 +1,52 @@ +<!-- include start from bgp/route-target-both.xml.i --> +<node name="route-target"> + <properties> + <help>Specify route target list</help> + </properties> + <children> + <node name="vpn"> + <properties> + <help>Between current address-family and VPN</help> + </properties> + <children> + <leafNode name="both"> + <properties> + <help>Route Target both import and export</help> + <valueHelp> + <format>txt</format> + <description>Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)</description> + </valueHelp> + <constraint> + <validator name="bgp-rd-rt" argument="--route-target-multi"/> + </constraint> + </properties> + </leafNode> + <leafNode name="import"> + <properties> + <help>Route Target import</help> + <valueHelp> + <format>txt</format> + <description>Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)</description> + </valueHelp> + <constraint> + <validator name="bgp-rd-rt" argument="--route-target-multi"/> + </constraint> + </properties> + </leafNode> + <leafNode name="export"> + <properties> + <help>Route Target export</help> + <valueHelp> + <format>txt</format> + <description>Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)</description> + </valueHelp> + <constraint> + <validator name="bgp-rd-rt" argument="--route-target-multi"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-sid.xml.i b/interface-definitions/include/bgp/afi-sid.xml.i new file mode 100644 index 0000000..38a3dcf --- /dev/null +++ b/interface-definitions/include/bgp/afi-sid.xml.i @@ -0,0 +1,36 @@ +<!-- include start from bgp/sid.xml.i --> +<node name="sid"> + <properties> + <help>SID value for VRF</help> + </properties> + <children> + <node name="vpn"> + <properties> + <help>Between current VRF and VPN</help> + </properties> + <children> + <leafNode name="export"> + <properties> + <help>For routes leaked from current VRF to VPN</help> + <completionHelp> + <list>auto</list> + </completionHelp> + <valueHelp> + <format>u32:1-1048575</format> + <description>SID allocation index</description> + </valueHelp> + <valueHelp> + <format>auto</format> + <description>Automatically assign a label</description> + </valueHelp> + <constraint> + <regex>auto</regex> + <validator name="numeric" argument="--range 1-1048575"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <!-- include end --> diff --git a/interface-definitions/include/bgp/afi-soft-reconfiguration.xml.i b/interface-definitions/include/bgp/afi-soft-reconfiguration.xml.i new file mode 100644 index 0000000..4933671 --- /dev/null +++ b/interface-definitions/include/bgp/afi-soft-reconfiguration.xml.i @@ -0,0 +1,15 @@ +<!-- include start from bgp/afi-soft-reconfiguration.xml.i --> +<node name="soft-reconfiguration"> + <properties> + <help>Soft reconfiguration for peer</help> + </properties> + <children> + <leafNode name="inbound"> + <properties> + <help>Enable inbound soft reconfiguration</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-vpn-label.xml.i b/interface-definitions/include/bgp/afi-vpn-label.xml.i new file mode 100644 index 0000000..6c7e73d --- /dev/null +++ b/interface-definitions/include/bgp/afi-vpn-label.xml.i @@ -0,0 +1,14 @@ +<!-- include start from bgp/afi-vpn-label.xml.i --> +<leafNode name="label"> + <properties> + <help>MPLS label value assigned to route</help> + <valueHelp> + <format>u32:0-1048575</format> + <description>MPLS label value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-1048575"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/bmp-monitor-afi-policy.xml.i b/interface-definitions/include/bgp/bmp-monitor-afi-policy.xml.i new file mode 100644 index 0000000..261d602 --- /dev/null +++ b/interface-definitions/include/bgp/bmp-monitor-afi-policy.xml.i @@ -0,0 +1,14 @@ +<!-- include start from bgp/bmp-monitor-afi-policy.xml.i --> +<leafNode name="pre-policy"> + <properties> + <help>Send state before policy and filter processing</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="post-policy"> + <properties> + <help>Send state with policy and filters applied</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-flowspec.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-flowspec.xml.i new file mode 100644 index 0000000..2f0ed72 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-flowspec.xml.i @@ -0,0 +1,11 @@ +<!-- include start from bgp/neighbor-afi-ipv4-flowspec.xml.i --> +<node name="ipv4-flowspec"> + <properties> + <help>IPv4 Flow Specification BGP neighbor parameters</help> + </properties> + <children> + #include <include/bgp/afi-ipv4-prefix-list.xml.i> + #include <include/bgp/afi-common-flowspec.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i new file mode 100644 index 0000000..a433f7c --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i @@ -0,0 +1,204 @@ +<!-- include start from bgp/neighbor-afi-ipv4-ipv6-common.xml.i --> +<leafNode name="addpath-tx-all"> + <properties> + <help>Use addpath to advertise all paths to a neighbor</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="addpath-tx-per-as"> + <properties> + <help>Use addpath to advertise the bestpath per each neighboring AS</help> + <valueless/> + </properties> +</leafNode> +<node name="conditionally-advertise"> + <properties> + <help>Use route-map to conditionally advertise routes</help> + </properties> + <children> + <leafNode name="advertise-map"> + <properties> + <help>Route-map to conditionally advertise routes</help> + <completionHelp> + <path>policy route-map</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Route map name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of route-map can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="exist-map"> + <properties> + <help>Advertise routes only if prefixes in exist-map are installed in BGP table</help> + <completionHelp> + <path>policy route-map</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Route map name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of route-map can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="non-exist-map"> + <properties> + <help>Advertise routes only if prefixes in non-exist-map are not installed in BGP table</help> + <completionHelp> + <path>policy route-map</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Route map name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of route-map can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> + </leafNode> + </children> +</node> +#include <include/bgp/afi-allowas-in.xml.i> +<leafNode name="as-override"> + <properties> + <help>Override ASN in outbound updates to configured neighbor local-as</help> + <valueless/> + </properties> +</leafNode> +#include <include/bgp/afi-attribute-unchanged.xml.i> +<node name="disable-send-community"> + <properties> + <help>Disable sending community attributes to this peer</help> + </properties> + <children> + <leafNode name="extended"> + <properties> + <help>Disable sending extended community attributes to this peer</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="standard"> + <properties> + <help>Disable sending standard community attributes to this peer</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<node name="distribute-list"> + <properties> + <help>Access-list to filter route updates to/from this peer-group</help> + </properties> + <children> + <leafNode name="export"> + <properties> + <help>Access-list to filter outgoing route updates to this peer-group</help> + <completionHelp> + <path>policy access-list</path> + </completionHelp> + <valueHelp> + <format>u32:1-65535</format> + <description>Access-list to filter outgoing route updates to this peer-group</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="import"> + <properties> + <help>Access-list to filter incoming route updates from this peer-group</help> + <completionHelp> + <path>policy access-list</path> + </completionHelp> + <valueHelp> + <format>u32:1-65535</format> + <description>Access-list to filter incoming route updates from this peer-group</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +#include <include/bgp/afi-filter-list.xml.i> +<leafNode name="maximum-prefix"> + <properties> + <help>Maximum number of prefixes to accept from this peer</help> + <valueHelp> + <format>u32:1-4294967295</format> + <description>Prefix limit</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> +</leafNode> +<leafNode name="maximum-prefix-out"> + <properties> + <help>Maximum number of prefixes to be sent to this peer</help> + <valueHelp> + <format>u32:1-4294967295</format> + <description>Prefix limit</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> +</leafNode> +#include <include/bgp/afi-nexthop-self.xml.i> +<node name="remove-private-as"> + <properties> + <help>Remove private AS numbers from AS path in outbound route updates</help> + </properties> + <children> + <leafNode name="all"> + <properties> + <help>Remove private AS numbers to all AS numbers in outbound route updates</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +#include <include/bgp/afi-route-map.xml.i> +#include <include/bgp/afi-route-reflector-client.xml.i> +#include <include/bgp/afi-route-server-client.xml.i> +#include <include/bgp/afi-soft-reconfiguration.xml.i> +<leafNode name="unsuppress-map"> + <properties> + <help>Route-map to selectively unsuppress suppressed routes</help> + <completionHelp> + <path>policy route-map</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Route map name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of route-map can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> +</leafNode> +<leafNode name="weight"> + <properties> + <help>Default weight for routes from this peer</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Default weight</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-labeled-unicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-labeled-unicast.xml.i new file mode 100644 index 0000000..0eae29f --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-labeled-unicast.xml.i @@ -0,0 +1,20 @@ +<!-- include start from bgp/neighbor-afi-ipv4-labeled-unicast.xml.i --> +<node name="ipv4-labeled-unicast"> + <properties> + <help>IPv4 Labeled Unicast BGP neighbor parameters</help> + </properties> + <children> + <node name="capability"> + <properties> + <help>Advertise capabilities to this neighbor (IPv4)</help> + </properties> + <children> + #include <include/bgp/afi-capability-orf.xml.i> + </children> + </node> + #include <include/bgp/afi-ipv4-prefix-list.xml.i> + #include <include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i> + #include <include/bgp/afi-default-originate.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-multicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-multicast.xml.i new file mode 100644 index 0000000..4bb6df7 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-multicast.xml.i @@ -0,0 +1,20 @@ +<!-- include start from bgp/neighbor-afi-ipv4-multicast.xml.i --> +<node name="ipv4-multicast"> + <properties> + <help>IPv4 Multicast BGP neighbor parameters</help> + </properties> + <children> + <node name="capability"> + <properties> + <help>Advertise capabilities to this neighbor (IPv4)</help> + </properties> + <children> + #include <include/bgp/afi-capability-orf.xml.i> + </children> + </node> + #include <include/bgp/afi-ipv4-prefix-list.xml.i> + #include <include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i> + #include <include/bgp/afi-default-originate.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-unicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-unicast.xml.i new file mode 100644 index 0000000..0094ce8 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-unicast.xml.i @@ -0,0 +1,20 @@ +<!-- include start from bgp/neighbor-afi-ipv4-unicast.xml.i --> +<node name="ipv4-unicast"> + <properties> + <help>IPv4 BGP neighbor parameters</help> + </properties> + <children> + <node name="capability"> + <properties> + <help>Advertise capabilities to this neighbor (IPv4)</help> + </properties> + <children> + #include <include/bgp/afi-capability-orf.xml.i> + </children> + </node> + #include <include/bgp/afi-ipv4-prefix-list.xml.i> + #include <include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i> + #include <include/bgp/afi-default-originate.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-vpn.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-vpn.xml.i new file mode 100644 index 0000000..220f22f --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-vpn.xml.i @@ -0,0 +1,11 @@ +<!-- include start from bgp/neighbor-afi-ipv4-vpn.xml.i --> +<node name="ipv4-vpn"> + <properties> + <help>IPv4 VPN BGP neighbor parameters</help> + </properties> + <children> + #include <include/bgp/afi-ipv4-prefix-list.xml.i> + #include <include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv6-flowspec.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv6-flowspec.xml.i new file mode 100644 index 0000000..bc61076 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-afi-ipv6-flowspec.xml.i @@ -0,0 +1,11 @@ +<!-- include start from bgp/neighbor-afi-ipv6-flowspec.xml.i --> +<node name="ipv6-flowspec"> + <properties> + <help>IPv6 Flow Specification BGP neighbor parameters</help> + </properties> + <children> + #include <include/bgp/afi-ipv6-prefix-list.xml.i> + #include <include/bgp/afi-common-flowspec.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv6-labeled-unicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv6-labeled-unicast.xml.i new file mode 100644 index 0000000..9951835 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-afi-ipv6-labeled-unicast.xml.i @@ -0,0 +1,21 @@ +<!-- include start from bgp/neighbor-afi-ipv6-labeled-unicast.xml.i --> +<node name="ipv6-labeled-unicast"> + <properties> + <help>IPv6 Labeled Unicast BGP neighbor parameters</help> + </properties> + <children> + <node name="capability"> + <properties> + <help>Advertise capabilities to this neighbor (IPv6)</help> + </properties> + <children> + #include <include/bgp/afi-capability-orf.xml.i> + </children> + </node> + #include <include/bgp/afi-ipv6-nexthop-local.xml.i> + #include <include/bgp/afi-ipv6-prefix-list.xml.i> + #include <include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i> + #include <include/bgp/afi-default-originate.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv6-multicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv6-multicast.xml.i new file mode 100644 index 0000000..bb713c3 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-afi-ipv6-multicast.xml.i @@ -0,0 +1,13 @@ +<!-- include start from bgp/neighbor-afi-ipv6-multicast.xml.i --> +<node name="ipv6-multicast"> + <properties> + <help>IPv6 Multicast BGP neighbor parameters</help> + </properties> + <children> + #include <include/bgp/afi-ipv6-nexthop-local.xml.i> + #include <include/bgp/afi-ipv6-prefix-list.xml.i> + #include <include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i> + #include <include/bgp/afi-default-originate.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv6-unicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv6-unicast.xml.i new file mode 100644 index 0000000..26a5e70 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-afi-ipv6-unicast.xml.i @@ -0,0 +1,21 @@ +<!-- include start from bgp/neighbor-afi-ipv6-unicast.xml.i --> +<node name="ipv6-unicast"> + <properties> + <help>IPv6 BGP neighbor parameters</help> + </properties> + <children> + <node name="capability"> + <properties> + <help>Advertise capabilities to this neighbor (IPv6)</help> + </properties> + <children> + #include <include/bgp/afi-capability-orf.xml.i> + </children> + </node> + #include <include/bgp/afi-ipv6-nexthop-local.xml.i> + #include <include/bgp/afi-ipv6-prefix-list.xml.i> + #include <include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i> + #include <include/bgp/afi-default-originate.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv6-vpn.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv6-vpn.xml.i new file mode 100644 index 0000000..5c68119 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-afi-ipv6-vpn.xml.i @@ -0,0 +1,12 @@ +<!-- include start from bgp/neighbor-afi-ipv6-vpn.xml.i --> +<node name="ipv6-vpn"> + <properties> + <help>IPv6 VPN BGP neighbor parameters</help> + </properties> + <children> + #include <include/bgp/afi-ipv6-nexthop-local.xml.i> + #include <include/bgp/afi-ipv6-prefix-list.xml.i> + #include <include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-afi-l2vpn-evpn.xml.i b/interface-definitions/include/bgp/neighbor-afi-l2vpn-evpn.xml.i new file mode 100644 index 0000000..c9f6600 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-afi-l2vpn-evpn.xml.i @@ -0,0 +1,16 @@ +<!-- include start from bgp/neighbor-afi-l2vpn-evpn.xml.i --> +<node name="l2vpn-evpn"> + <properties> + <help>L2VPN EVPN BGP settings</help> + </properties> + <children> + #include <include/bgp/afi-allowas-in.xml.i> + #include <include/bgp/afi-attribute-unchanged.xml.i> + #include <include/bgp/afi-nexthop-self.xml.i> + #include <include/bgp/afi-route-map.xml.i> + #include <include/bgp/afi-route-reflector-client.xml.i> + #include <include/bgp/afi-route-server-client.xml.i> + #include <include/bgp/afi-soft-reconfiguration.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-bfd.xml.i b/interface-definitions/include/bgp/neighbor-bfd.xml.i new file mode 100644 index 0000000..fac2a11 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-bfd.xml.i @@ -0,0 +1,16 @@ +<!-- include start from bgp/neighbor-bfd.xml.i --> +<node name="bfd"> + <properties> + <help>Enable Bidirectional Forwarding Detection (BFD) support</help> + </properties> + <children> + #include <include/bfd/profile.xml.i> + <leafNode name="check-control-plane-failure"> + <properties> + <help>Allow to write CBIT independence in BFD outgoing packets and read both C-BIT value of BFD and lookup BGP peer status</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-capability.xml.i b/interface-definitions/include/bgp/neighbor-capability.xml.i new file mode 100644 index 0000000..c5ed3c8 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-capability.xml.i @@ -0,0 +1,27 @@ +<!-- include start from bgp/neighbor-capability.xml.i --> +<node name="capability"> + <properties> + <help>Advertise capabilities to this peer-group</help> + </properties> + <children> + <leafNode name="dynamic"> + <properties> + <help>Advertise dynamic capability to this neighbor</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="extended-nexthop"> + <properties> + <help>Advertise extended-nexthop capability to this neighbor</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="software-version"> + <properties> + <help>Advertise Software Version capability to the peer</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-disable-capability-negotiation.xml.i b/interface-definitions/include/bgp/neighbor-disable-capability-negotiation.xml.i new file mode 100644 index 0000000..0c44e47 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-disable-capability-negotiation.xml.i @@ -0,0 +1,8 @@ +<!-- include start from bgp/neighbor-disable-capability-negotiation.xml.i --> +<leafNode name="disable-capability-negotiation"> + <properties> + <help>Disable capability negotiation with this neighbor</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-disable-connected-check.xml.i b/interface-definitions/include/bgp/neighbor-disable-connected-check.xml.i new file mode 100644 index 0000000..aef5a55 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-disable-connected-check.xml.i @@ -0,0 +1,8 @@ +<!-- include start from bgp/neighbor-disable-connected-check.xml.i --> +<leafNode name="disable-connected-check"> + <properties> + <help>Allow peerings between eBGP peer using loopback/dummy address</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-ebgp-multihop.xml.i b/interface-definitions/include/bgp/neighbor-ebgp-multihop.xml.i new file mode 100644 index 0000000..c053de7 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-ebgp-multihop.xml.i @@ -0,0 +1,14 @@ +<!-- include start from bgp/neighbor-ebgp-multihop.xml.i --> +<leafNode name="ebgp-multihop"> + <properties> + <help>Allow this EBGP neighbor to not be on a directly connected network</help> + <valueHelp> + <format>u32:1-255</format> + <description>Number of hops</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-graceful-restart.xml.i b/interface-definitions/include/bgp/neighbor-graceful-restart.xml.i new file mode 100644 index 0000000..4399d79 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-graceful-restart.xml.i @@ -0,0 +1,25 @@ +<!-- include start from bgp/neighbor-graceful-restart.xml.i --> +<leafNode name="graceful-restart"> + <properties> + <help>BGP graceful restart functionality</help> + <completionHelp> + <list>enable disable restart-helper</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable BGP graceful restart at peer level</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable BGP graceful restart at peer level</description> + </valueHelp> + <valueHelp> + <format>restart-helper</format> + <description>Enable BGP graceful restart helper only functionality</description> + </valueHelp> + <constraint> + <regex>(enable|disable|restart-helper)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-local-as.xml.i b/interface-definitions/include/bgp/neighbor-local-as.xml.i new file mode 100644 index 0000000..8868e30 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-local-as.xml.i @@ -0,0 +1,29 @@ +<!-- include start from bgp/neighbor-local-as.xml.i --> +<tagNode name="local-as"> + <properties> + <help>Specify alternate ASN for this BGP process</help> + <valueHelp> + <format>u32:1-4294967294</format> + <description>Autonomous System Number (ASN)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967294"/> + </constraint> + </properties> + <children> + <node name="no-prepend"> + <properties> + <help>Disable prepending local-as from/to updates for eBGP peers</help> + </properties> + <children> + <leafNode name="replace-as"> + <properties> + <help>Prepend only local-as from/to updates for eBGP peers</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-local-role.xml.i b/interface-definitions/include/bgp/neighbor-local-role.xml.i new file mode 100644 index 0000000..6ddb490 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-local-role.xml.i @@ -0,0 +1,42 @@ +<!-- include start from bgp/neigbhor-local-role.xml.i --> +<tagNode name="local-role"> + <properties> + <help>Local role for BGP neighbor (RFC9234)</help> + <completionHelp> + <list>customer peer provider rs-client rs-server</list> + </completionHelp> + <valueHelp> + <format>customer</format> + <description>Using Transit</description> + </valueHelp> + <valueHelp> + <format>peer</format> + <description>Public/Private Peering</description> + </valueHelp> + <valueHelp> + <format>provider</format> + <description>Providing Transit</description> + </valueHelp> + <valueHelp> + <format>rs-client</format> + <description>RS Client</description> + </valueHelp> + <valueHelp> + <format>rs-server</format> + <description>Route Server</description> + </valueHelp> + <constraint> + <regex>(provider|rs-server|rs-client|customer|peer)</regex> + </constraint> + <constraintErrorMessage>BGP local-role must be one of the following: customer, peer, provider, rs-client or rs-server</constraintErrorMessage> + </properties> + <children> + <leafNode name="strict"> + <properties> + <help>Neighbor must send this exact capability, otherwise a role missmatch notification will be sent</help> + <valueless/> + </properties> + </leafNode> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-override-capability.xml.i b/interface-definitions/include/bgp/neighbor-override-capability.xml.i new file mode 100644 index 0000000..1ef28b2 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-override-capability.xml.i @@ -0,0 +1,8 @@ +<!-- include start from bgp/neighbor-override-capability.xml.i --> +<leafNode name="override-capability"> + <properties> + <help>Ignore capability negotiation with specified neighbor</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-passive.xml.i b/interface-definitions/include/bgp/neighbor-passive.xml.i new file mode 100644 index 0000000..c7d867a --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-passive.xml.i @@ -0,0 +1,8 @@ +<!-- include start from bgp/neighbor-passive.xml.i --> +<leafNode name="passive"> + <properties> + <help>Do not initiate a session with this neighbor</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-password.xml.i b/interface-definitions/include/bgp/neighbor-password.xml.i new file mode 100644 index 0000000..3a7eaaa --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-password.xml.i @@ -0,0 +1,7 @@ +<!-- include start from bgp/neighbor-password.xml.i --> +<leafNode name="password"> + <properties> + <help>BGP MD5 password</help> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-path-attribute.xml.i b/interface-definitions/include/bgp/neighbor-path-attribute.xml.i new file mode 100644 index 0000000..399a6bc --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-path-attribute.xml.i @@ -0,0 +1,34 @@ +<!-- include start from bgp/neighbor-path-attribute.xml.i --> +<node name="path-attribute"> + <properties> + <help>Manipulate path attributes from incoming UPDATE messages</help> + </properties> + <children> + <leafNode name="discard"> + <properties> + <help>Drop specified attributes from incoming UPDATE messages</help> + <valueHelp> + <format>u32:1-255</format> + <description>Attribute number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="treat-as-withdraw"> + <properties> + <help>Treat-as-withdraw any incoming BGP UPDATE messages that contain the specified attribute</help> + <valueHelp> + <format>u32:1-255</format> + <description>Attribute number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-shutdown.xml.i b/interface-definitions/include/bgp/neighbor-shutdown.xml.i new file mode 100644 index 0000000..acc7bc5 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-shutdown.xml.i @@ -0,0 +1,8 @@ +<!-- include start from bgp/neighbor-shutdown.xml.i --> +<leafNode name="shutdown"> + <properties> + <help>Administratively shutdown this neighbor</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-ttl-security.xml.i b/interface-definitions/include/bgp/neighbor-ttl-security.xml.i new file mode 100644 index 0000000..6def1fe --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-ttl-security.xml.i @@ -0,0 +1,21 @@ +<!-- include start from bgp/neighbor-ttl-security.xml.i --> +<node name="ttl-security"> + <properties> + <help>Ttl security mechanism</help> + </properties> + <children> + <leafNode name="hops"> + <properties> + <help>Number of the maximum number of hops to the BGP peer</help> + <valueHelp> + <format>u32:1-254</format> + <description>Number of hops</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-254"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-update-source.xml.i b/interface-definitions/include/bgp/neighbor-update-source.xml.i new file mode 100644 index 0000000..92e8171 --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-update-source.xml.i @@ -0,0 +1,28 @@ +<!-- include start from bgp/neighbor-update-source.xml.i --> +<leafNode name="update-source"> + <!-- Need to check format interfaces --> + <properties> + <help>Source IP of routing updates</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address of route source</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of route source</description> + </valueHelp> + <valueHelp> + <format>txt</format> + <description>Interface as route source</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/peer-group.xml.i b/interface-definitions/include/bgp/peer-group.xml.i new file mode 100644 index 0000000..c80d4a3 --- /dev/null +++ b/interface-definitions/include/bgp/peer-group.xml.i @@ -0,0 +1,14 @@ +<!-- include start from bgp/peer-group.xml.i --> +<leafNode name="peer-group"> + <properties> + <help>Peer group for this peer</help> + <completionHelp> + <path>${COMP_WORDS[@]:1:${#COMP_WORDS[@]}-5} peer-group</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Peer-group name</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i new file mode 100644 index 0000000..0f05625 --- /dev/null +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -0,0 +1,1868 @@ +<!-- include start from bgp/protocol-common-config.xml.i --> +<node name="address-family"> + <properties> + <help>BGP address-family parameters</help> + </properties> + <children> + <node name="ipv4-unicast"> + <properties> + <help>IPv4 BGP settings</help> + </properties> + <children> + <tagNode name="aggregate-address"> + <properties> + <help>BGP aggregate network</help> + <valueHelp> + <format>ipv4net</format> + <description>BGP aggregate network</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + #include <include/bgp/afi-aggregate-address.xml.i> + </children> + </tagNode> + <node name="distance"> + <properties> + <help>Administrative distances for BGP routes</help> + </properties> + <children> + <leafNode name="external"> + <properties> + <help>eBGP routes administrative distance</help> + <valueHelp> + <format>u32:1-255</format> + <description>eBGP routes administrative distance</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="internal"> + <properties> + <help>iBGP routes administrative distance</help> + <valueHelp> + <format>u32:1-255</format> + <description>iBGP routes administrative distance</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="local"> + <properties> + <help>Locally originated BGP routes administrative distance</help> + <valueHelp> + <format>u32:1-255</format> + <description>Locally originated BGP routes administrative distance</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <tagNode name="prefix"> + <properties> + <help>Administrative distance for a specific BGP prefix</help> + <valueHelp> + <format>ipv4net</format> + <description>Administrative distance for a specific BGP prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="distance"> + <properties> + <help>Administrative distance for prefix</help> + <valueHelp> + <format>u32:1-255</format> + <description>Administrative distance for external BGP routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + #include <include/bgp/afi-export-import.xml.i> + #include <include/bgp/afi-label.xml.i> + #include <include/bgp/afi-maximum-paths.xml.i> + <tagNode name="network"> + <properties> + <help>BGP network</help> + <valueHelp> + <format>ipv4net</format> + <description>BGP network</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="backdoor"> + <properties> + <help>Network as a backdoor route</help> + <valueless/> + </properties> + </leafNode> + #include <include/route-map.xml.i> + </children> + </tagNode> + #include <include/bgp/afi-rd.xml.i> + #include <include/bgp/afi-route-map-vpn.xml.i> + #include <include/bgp/afi-route-target-vpn.xml.i> + #include <include/bgp/afi-nexthop-vpn-export.xml.i> + <node name="redistribute"> + <properties> + <help>Redistribute routes from other protocols into BGP</help> + </properties> + <children> + <node name="connected"> + <properties> + <help>Redistribute connected routes into BGP</help> + </properties> + <children> + #include <include/bgp/afi-redistribute-metric-route-map.xml.i> + </children> + </node> + <node name="isis"> + <properties> + <help>Redistribute IS-IS routes into BGP</help> + </properties> + <children> + #include <include/bgp/afi-redistribute-metric-route-map.xml.i> + </children> + </node> + <node name="kernel"> + <properties> + <help>Redistribute kernel routes into BGP</help> + </properties> + <children> + #include <include/bgp/afi-redistribute-metric-route-map.xml.i> + </children> + </node> + <node name="ospf"> + <properties> + <help>Redistribute OSPF routes into BGP</help> + </properties> + <children> + #include <include/bgp/afi-redistribute-metric-route-map.xml.i> + </children> + </node> + <node name="rip"> + <properties> + <help>Redistribute RIP routes into BGP</help> + </properties> + <children> + #include <include/bgp/afi-redistribute-metric-route-map.xml.i> + </children> + </node> + <node name="babel"> + <properties> + <help>Redistribute Babel routes into BGP</help> + </properties> + <children> + #include <include/bgp/afi-redistribute-metric-route-map.xml.i> + </children> + </node> + <node name="static"> + <properties> + <help>Redistribute static routes into BGP</help> + </properties> + <children> + #include <include/bgp/afi-redistribute-metric-route-map.xml.i> + </children> + </node> + <leafNode name="table"> + <properties> + <help>Redistribute non-main Kernel Routing Table</help> + </properties> + </leafNode> + </children> + </node> + #include <include/bgp/afi-sid.xml.i> + </children> + </node> + <node name="ipv4-multicast"> + <properties> + <help>Multicast IPv4 BGP settings</help> + </properties> + <children> + <tagNode name="aggregate-address"> + <properties> + <help>BGP aggregate network/prefix</help> + <valueHelp> + <format>ipv4net</format> + <description>BGP aggregate network/prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + #include <include/bgp/afi-aggregate-address.xml.i> + </children> + </tagNode> + <node name="distance"> + <properties> + <help>Administrative distances for BGP routes</help> + </properties> + <children> + <leafNode name="external"> + <properties> + <help>eBGP routes administrative distance</help> + <valueHelp> + <format>u32:1-255</format> + <description>eBGP routes administrative distance</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="internal"> + <properties> + <help>iBGP routes administrative distance</help> + <valueHelp> + <format>u32:1-255</format> + <description>iBGP routes administrative distance</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="local"> + <properties> + <help>Locally originated BGP routes administrative distance</help> + <valueHelp> + <format>u32:1-255</format> + <description>Locally originated BGP routes administrative distance</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <tagNode name="prefix"> + <properties> + <help>Administrative distance for a specific BGP prefix</help> + <valueHelp> + <format>ipv4net</format> + <description>Administrative distance for a specific BGP prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="distance"> + <properties> + <help>Administrative distance for prefix</help> + <valueHelp> + <format>u32:1-255</format> + <description>Administrative distance for external BGP routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + <tagNode name="network"> + <properties> + <help>Import BGP network/prefix into multicast IPv4 RIB</help> + <valueHelp> + <format>ipv4net</format> + <description>Multicast IPv4 BGP network/prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="backdoor"> + <properties> + <help>Use BGP network/prefix as a backdoor route</help> + <valueless/> + </properties> + </leafNode> + #include <include/route-map.xml.i> + </children> + </tagNode> + </children> + </node> + <node name="ipv4-labeled-unicast"> + <properties> + <help>Labeled Unicast IPv4 BGP settings</help> + </properties> + <children> + <tagNode name="aggregate-address"> + <properties> + <help>BGP aggregate network/prefix</help> + <valueHelp> + <format>ipv4net</format> + <description>BGP aggregate network/prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + #include <include/bgp/afi-aggregate-address.xml.i> + </children> + </tagNode> + <tagNode name="network"> + <properties> + <help>Import BGP network/prefix into labeled unicast IPv4 RIB</help> + <valueHelp> + <format>ipv4net</format> + <description>Labeled Unicast IPv4 BGP network/prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="backdoor"> + <properties> + <help>Use BGP network/prefix as a backdoor route</help> + <valueless/> + </properties> + </leafNode> + #include <include/route-map.xml.i> + </children> + </tagNode> + #include <include/bgp/afi-maximum-paths.xml.i> + </children> + </node> + <node name="ipv4-flowspec"> + <properties> + <help>Flowspec IPv4 BGP settings</help> + </properties> + <children> + <node name="local-install"> + <properties> + <help>Apply local policy routing to interface</help> + </properties> + <children> + #include <include/generic-interface-multi.xml.i> + </children> + </node> + </children> + </node> + <node name="ipv4-vpn"> + <properties> + <help>Unicast VPN IPv4 BGP settings</help> + </properties> + <children> + <tagNode name="network"> + <properties> + <help>Import BGP network/prefix into unicast VPN IPv4 RIB</help> + <valueHelp> + <format>ipv4net</format> + <description>Unicast VPN IPv4 BGP network/prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + #include <include/bgp/route-distinguisher.xml.i> + #include <include/bgp/afi-vpn-label.xml.i> + </children> + </tagNode> + </children> + </node> + <node name="ipv6-unicast"> + <properties> + <help>IPv6 BGP settings</help> + </properties> + <children> + <tagNode name="aggregate-address"> + <properties> + <help>BGP aggregate network</help> + <valueHelp> + <format>ipv6net</format> + <description>Aggregate network</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + #include <include/bgp/afi-aggregate-address.xml.i> + </children> + </tagNode> + <node name="distance"> + <properties> + <help>Administrative distances for BGP routes</help> + </properties> + <children> + <leafNode name="external"> + <properties> + <help>eBGP routes administrative distance</help> + <valueHelp> + <format>u32:1-255</format> + <description>eBGP routes administrative distance</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="internal"> + <properties> + <help>iBGP routes administrative distance</help> + <valueHelp> + <format>u32:1-255</format> + <description>iBGP routes administrative distance</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="local"> + <properties> + <help>Locally originated BGP routes administrative distance</help> + <valueHelp> + <format>u32:1-255</format> + <description>Locally originated BGP routes administrative distance</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <tagNode name="prefix"> + <properties> + <help>Administrative distance for a specific BGP prefix</help> + <valueHelp> + <format>ipv6net</format> + <description>Administrative distance for a specific BGP prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="distance"> + <properties> + <help>Administrative distance for prefix</help> + <valueHelp> + <format>u32:1-255</format> + <description>Administrative distance for external BGP routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + #include <include/bgp/afi-export-import.xml.i> + #include <include/bgp/afi-label.xml.i> + #include <include/bgp/afi-maximum-paths.xml.i> + <tagNode name="network"> + <properties> + <help>BGP network</help> + <valueHelp> + <format>ipv6net</format> + <description>Aggregate network</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + #include <include/bgp/afi-path-limit.xml.i> + #include <include/route-map.xml.i> + </children> + </tagNode> + #include <include/bgp/afi-rd.xml.i> + #include <include/bgp/afi-route-map-vpn.xml.i> + #include <include/bgp/afi-route-target-vpn.xml.i> + #include <include/bgp/afi-nexthop-vpn-export.xml.i> + <node name="redistribute"> + <properties> + <help>Redistribute routes from other protocols into BGP</help> + </properties> + <children> + <node name="connected"> + <properties> + <help>Redistribute connected routes into BGP</help> + </properties> + <children> + #include <include/bgp/afi-redistribute-metric-route-map.xml.i> + </children> + </node> + <node name="kernel"> + <properties> + <help>Redistribute kernel routes into BGP</help> + </properties> + <children> + #include <include/bgp/afi-redistribute-metric-route-map.xml.i> + </children> + </node> + <node name="ospfv3"> + <properties> + <help>Redistribute OSPFv3 routes into BGP</help> + </properties> + <children> + #include <include/bgp/afi-redistribute-metric-route-map.xml.i> + </children> + </node> + <node name="ripng"> + <properties> + <help>Redistribute RIPng routes into BGP</help> + </properties> + <children> + #include <include/bgp/afi-redistribute-metric-route-map.xml.i> + </children> + </node> + <node name="babel"> + <properties> + <help>Redistribute Babel routes into BGP</help> + </properties> + <children> + #include <include/bgp/afi-redistribute-metric-route-map.xml.i> + </children> + </node> + <node name="static"> + <properties> + <help>Redistribute static routes into BGP</help> + </properties> + <children> + #include <include/bgp/afi-redistribute-metric-route-map.xml.i> + </children> + </node> + <leafNode name="table"> + <properties> + <help>Redistribute non-main Kernel Routing Table</help> + </properties> + </leafNode> + </children> + </node> + #include <include/bgp/afi-sid.xml.i> + </children> + </node> + <node name="ipv6-multicast"> + <properties> + <help>Multicast IPv6 BGP settings</help> + </properties> + <children> + <tagNode name="aggregate-address"> + <properties> + <help>BGP aggregate network/prefix</help> + <valueHelp> + <format>ipv6net</format> + <description>BGP aggregate network/prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + #include <include/bgp/afi-aggregate-address.xml.i> + </children> + </tagNode> + <node name="distance"> + <properties> + <help>Administrative distances for BGP routes</help> + </properties> + <children> + <leafNode name="external"> + <properties> + <help>eBGP routes administrative distance</help> + <valueHelp> + <format>u32:1-255</format> + <description>eBGP routes administrative distance</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="internal"> + <properties> + <help>iBGP routes administrative distance</help> + <valueHelp> + <format>u32:1-255</format> + <description>iBGP routes administrative distance</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="local"> + <properties> + <help>Locally originated BGP routes administrative distance</help> + <valueHelp> + <format>u32:1-255</format> + <description>Locally originated BGP routes administrative distance</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <tagNode name="prefix"> + <properties> + <help>Administrative distance for a specific BGP prefix</help> + <valueHelp> + <format>ipv6net</format> + <description>Administrative distance for a specific BGP prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="distance"> + <properties> + <help>Administrative distance for prefix</help> + <valueHelp> + <format>u32:1-255</format> + <description>Administrative distance for external BGP routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + <tagNode name="network"> + <properties> + <help>Import BGP network/prefix into multicast IPv6 RIB</help> + <valueHelp> + <format>ipv6net</format> + <description>Multicast IPv6 BGP network/prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + #include <include/bgp/afi-path-limit.xml.i> + #include <include/route-map.xml.i> + </children> + </tagNode> + </children> + </node> + <node name="ipv6-labeled-unicast"> + <properties> + <help>Labeled Unicast IPv6 BGP settings</help> + </properties> + <children> + <tagNode name="aggregate-address"> + <properties> + <help>BGP aggregate network/prefix</help> + <valueHelp> + <format>ipv6net</format> + <description>BGP aggregate network/prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + #include <include/bgp/afi-aggregate-address.xml.i> + </children> + </tagNode> + <tagNode name="network"> + <properties> + <help>Import BGP network/prefix into labeled unicast IPv6 RIB</help> + <valueHelp> + <format>ipv6net</format> + <description>Labeled Unicast IPv6 BGP network/prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="backdoor"> + <properties> + <help>Use BGP network/prefix as a backdoor route</help> + <valueless/> + </properties> + </leafNode> + #include <include/route-map.xml.i> + </children> + </tagNode> + </children> + </node> + <node name="ipv6-flowspec"> + <properties> + <help>Flowspec IPv6 BGP settings</help> + </properties> + <children> + <node name="local-install"> + <properties> + <help>Apply local policy routing to interface</help> + </properties> + <children> + <leafNode name="interface"> + <properties> + <help>Interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <multi/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <node name="ipv6-vpn"> + <properties> + <help>Unicast VPN IPv6 BGP settings</help> + </properties> + <children> + <tagNode name="network"> + <properties> + <help>Import BGP network/prefix into unicast VPN IPv6 RIB</help> + <valueHelp> + <format>ipv6net</format> + <description>Unicast VPN IPv6 BGP network/prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + #include <include/bgp/route-distinguisher.xml.i> + #include <include/bgp/afi-vpn-label.xml.i> + </children> + </tagNode> + </children> + </node> + <node name="l2vpn-evpn"> + <properties> + <help>L2VPN EVPN BGP settings</help> + </properties> + <children> + <node name="advertise"> + <properties> + <help>Advertise prefix routes</help> + </properties> + <children> + <node name="ipv4"> + <properties> + <help>IPv4 address family</help> + </properties> + <children> + #include <include/bgp/afi-l2vpn-advertise.xml.i> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 address family</help> + </properties> + <children> + #include <include/bgp/afi-l2vpn-advertise.xml.i> + </children> + </node> + </children> + </node> + <leafNode name="advertise-all-vni"> + <properties> + <help>Advertise All local VNIs</help> + <valueless/> + </properties> + </leafNode> + #include <include/bgp/afi-l2vpn-common.xml.i> + <leafNode name="advertise-pip"> + <properties> + <help>EVPN system primary IP</help> + <valueHelp> + <format>ipv4</format> + <description>IP address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="rt-auto-derive"> + <properties> + <help>Auto derivation of Route Target (RFC8365)</help> + <valueless/> + </properties> + </leafNode> + <node name="default-originate"> + <properties> + <help>Originate a default route</help> + </properties> + <children> + <leafNode name="ipv4"> + <properties> + <help>IPv4 address family</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ipv6"> + <properties> + <help>IPv6 address family</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="disable-ead-evi-rx"> + <properties> + <help>Activate PE on EAD-ES even if EAD-EVI is not received</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="disable-ead-evi-tx"> + <properties> + <help>Do not advertise EAD-EVI for local ESs</help> + <valueless/> + </properties> + </leafNode> + <node name="ead-es-frag"> + <properties> + <help>EAD ES fragment config</help> + </properties> + <children> + <leafNode name="evi-limit"> + <properties> + <help>EVIs per-fragment</help> + <valueHelp> + <format>u32:1-1000</format> + <description>limit</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-1000"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="ead-es-route-target"> + <properties> + <help>EAD ES Route Target</help> + </properties> + <children> + <leafNode name="export"> + <properties> + <help>Route Target export</help> + <valueHelp> + <format>txt</format> + <description>Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN)</description> + </valueHelp> + <constraint> + <validator name="bgp-rd-rt" argument="--route-target-multi"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + <node name="flooding"> + <properties> + <help>Specify handling for BUM packets</help> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <leafNode name="head-end-replication"> + <properties> + <help>Flood BUM packets using head-end replication</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="mac-vrf"> + <properties> + <help>EVPN MAC-VRF</help> + </properties> + <children> + <leafNode name="soo"> + <properties> + <help>Site-of-Origin extended community</help> + <valueHelp> + <format>ASN:NN</format> + <description>based on autonomous system number in format <0-65535:0-4294967295></description> + </valueHelp> + <valueHelp> + <format>IP:NN</format> + <description>Based on a router-id IP address in format <IP:0-65535></description> + </valueHelp> + <constraint> + <validator name="bgp-extended-community"/> + </constraint> + <constraintErrorMessage>Should be in form: ASN:NN or IPADDR:NN where ASN is autonomous system number</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + <tagNode name="vni"> + <properties> + <help>VXLAN Network Identifier</help> + <valueHelp> + <format>u32:1-16777215</format> + <description>VNI number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-16777215"/> + </constraint> + </properties> + <children> + #include <include/bgp/afi-l2vpn-common.xml.i> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<node name="bmp"> + <properties> + <help>BGP Monitoring Protocol (BMP)</help> + </properties> + <children> + <leafNode name="mirror-buffer-limit"> + <properties> + <help>Maximum memory used for buffered mirroring messages (in bytes)</help> + <valueHelp> + <format>u32:0-4294967294</format> + <description>Limit in bytes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967294"/> + </constraint> + </properties> + </leafNode> + <tagNode name="target"> + <properties> + <help>BMP target</help> + </properties> + <children> + #include <include/address-ipv4-ipv6-single.xml.i> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>5000</defaultValue> + </leafNode> + <leafNode name="min-retry"> + <properties> + <help>Minimum connection retry interval (in milliseconds)</help> + <valueHelp> + <format>u32:100-86400000</format> + <description>Minimum connection retry interval</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 100-86400000"/> + </constraint> + </properties> + <defaultValue>1000</defaultValue> + </leafNode> + <leafNode name="max-retry"> + <properties> + <help>Maximum connection retry interval</help> + <valueHelp> + <format>u32:100-4294967295</format> + <description>Maximum connection retry interval</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 100-86400000"/> + </constraint> + </properties> + <defaultValue>2000</defaultValue> + </leafNode> + <leafNode name="mirror"> + <properties> + <help>Send BMP route mirroring messages</help> + <valueless/> + </properties> + </leafNode> + <node name="monitor"> + <properties> + <help>Send BMP route monitoring messages</help> + </properties> + <children> + <node name="ipv4-unicast"> + <properties> + <help>Address family IPv4 unicast</help> + </properties> + <children> + #include <include/bgp/bmp-monitor-afi-policy.xml.i> + </children> + </node> + <node name="ipv6-unicast"> + <properties> + <help>Address family IPv6 unicast</help> + </properties> + <children> + #include <include/bgp/bmp-monitor-afi-policy.xml.i> + </children> + </node> + </children> + </node> + </children> + </tagNode> + </children> +</node> +<tagNode name="interface"> + <properties> + <help>Configure interface related parameters, e.g. MPLS</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + <node name="mpls"> + <properties> + <help>MPLS options</help> + </properties> + <children> + <leafNode name="forwarding"> + <properties> + <help>Enable MPLS forwarding for eBGP directly connected peers</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> +</tagNode> +<node name="listen"> + <properties> + <help>Listen for and accept BGP dynamic neighbors from range</help> + </properties> + <children> + <leafNode name="limit"> + <properties> + <help>Maximum number of dynamic neighbors that can be created</help> + <valueHelp> + <format>u32:1-5000</format> + <description>BGP neighbor limit</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-5000"/> + </constraint> + </properties> + </leafNode> + <tagNode name="range"> + <properties> + <help>BGP dynamic neighbors listen range</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 dynamic neighbors listen range</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 dynamic neighbors listen range</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + #include <include/bgp/peer-group.xml.i> + </children> + </tagNode> + </children> +</node> +<leafNode name="system-as"> + <properties> + <help>Autonomous System Number (ASN)</help> + <valueHelp> + <format>u32:1-4294967294</format> + <description>Autonomous System Number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967294"/> + </constraint> + </properties> +</leafNode> +<tagNode name="neighbor"> + <properties> + <help>BGP neighbor</help> + <valueHelp> + <format>ipv4</format> + <description>BGP neighbor IP address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>BGP neighbor IPv6 address</description> + </valueHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + <node name="address-family"> + <properties> + <help>Address-family parameters</help> + </properties> + <children> + #include <include/bgp/neighbor-afi-ipv4-unicast.xml.i> + #include <include/bgp/neighbor-afi-ipv6-unicast.xml.i> + #include <include/bgp/neighbor-afi-ipv4-labeled-unicast.xml.i> + #include <include/bgp/neighbor-afi-ipv6-labeled-unicast.xml.i> + #include <include/bgp/neighbor-afi-ipv4-vpn.xml.i> + #include <include/bgp/neighbor-afi-ipv6-vpn.xml.i> + #include <include/bgp/neighbor-afi-ipv4-flowspec.xml.i> + #include <include/bgp/neighbor-afi-ipv6-flowspec.xml.i> + #include <include/bgp/neighbor-afi-ipv4-multicast.xml.i> + #include <include/bgp/neighbor-afi-ipv6-multicast.xml.i> + #include <include/bgp/neighbor-afi-l2vpn-evpn.xml.i> + </children> + </node> + <leafNode name="advertisement-interval"> + <properties> + <help>Minimum interval for sending routing updates</help> + <valueHelp> + <format>u32:0-600</format> + <description>Advertisement interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-600"/> + </constraint> + </properties> + </leafNode> + #include <include/generic-description.xml.i> + #include <include/bgp/neighbor-bfd.xml.i> + #include <include/bgp/neighbor-capability.xml.i> + #include <include/bgp/neighbor-disable-capability-negotiation.xml.i> + #include <include/bgp/neighbor-disable-connected-check.xml.i> + #include <include/bgp/neighbor-ebgp-multihop.xml.i> + #include <include/bgp/neighbor-graceful-restart.xml.i> + <node name="interface"> + <properties> + <help>Interface parameters</help> + </properties> + <children> + #include <include/bgp/peer-group.xml.i> + #include <include/bgp/remote-as.xml.i> + #include <include/source-interface.xml.i> + <node name="v6only"> + <properties> + <help>Enable BGP with v6 link-local only</help> + </properties> + <children> + #include <include/bgp/peer-group.xml.i> + #include <include/bgp/remote-as.xml.i> + </children> + </node> + </children> + </node> + #include <include/bgp/neighbor-local-as.xml.i> + #include <include/bgp/neighbor-local-role.xml.i> + #include <include/bgp/neighbor-override-capability.xml.i> + #include <include/bgp/neighbor-path-attribute.xml.i> + #include <include/bgp/neighbor-passive.xml.i> + #include <include/bgp/neighbor-password.xml.i> + #include <include/bgp/peer-group.xml.i> + #include <include/bgp/remote-as.xml.i> + #include <include/bgp/neighbor-shutdown.xml.i> + <leafNode name="solo"> + <properties> + <help>Do not send back prefixes learned from the neighbor</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="enforce-first-as"> + <properties> + <help>Ensure the first AS in the AS path matches the peer AS</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="strict-capability-match"> + <properties> + <help>Enable strict capability negotiation</help> + <valueless/> + </properties> + </leafNode> + <node name="timers"> + <properties> + <help>Neighbor timers</help> + </properties> + <children> + <leafNode name="connect"> + <properties> + <help>BGP connect timer for this neighbor</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Connect timer in seconds</description> + </valueHelp> + <valueHelp> + <format>0</format> + <description>Disable connect timer</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + </leafNode> + #include <include/bgp/timers-holdtime.xml.i> + #include <include/bgp/timers-keepalive.xml.i> + </children> + </node> + #include <include/bgp/neighbor-ttl-security.xml.i> + #include <include/bgp/neighbor-update-source.xml.i> + #include <include/port-number.xml.i> + </children> +</tagNode> +<node name="parameters"> + <properties> + <help>BGP parameters</help> + </properties> + <children> + <leafNode name="allow-martian-nexthop"> + <properties> + <help>Allow Martian nexthops to be received in the NLRI from a peer</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="disable-ebgp-connected-route-check"> + <properties> + <help>Disable checking if nexthop is connected on eBGP session</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="always-compare-med"> + <properties> + <help>Always compare MEDs from different neighbors</help> + <valueless/> + </properties> + </leafNode> + <node name="bestpath"> + <properties> + <help>Default bestpath selection mechanism</help> + </properties> + <children> + <node name="as-path"> + <properties> + <help>AS-path attribute comparison parameters</help> + </properties> + <children> + <leafNode name="confed"> + <properties> + <help>Compare AS-path lengths including confederation sets and sequences</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ignore"> + <properties> + <help>Ignore AS-path length in selecting a route</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="multipath-relax"> + <properties> + <help>Allow load sharing across routes that have different AS paths (but same length)</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="bandwidth"> + <properties> + <help>Link Bandwidth attribute</help> + <completionHelp> + <list>default-weight-for-missing ignore skip-missing</list> + </completionHelp> + <valueHelp> + <format>default-weight-for-missing</format> + <description>Assign low default weight (1) to paths not having link bandwidth</description> + </valueHelp> + <valueHelp> + <format>ignore</format> + <description>Ignore link bandwidth (do regular ECMP, not weighted)</description> + </valueHelp> + <valueHelp> + <format>skip-missing</format> + <description>Ignore paths without link bandwidth for ECMP (if other paths have it)</description> + </valueHelp> + <constraint> + <regex>(default-weight-for-missing|ignore|skip-missing)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="compare-routerid"> + <properties> + <help>Compare the router-id for identical EBGP paths</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="med"> + <properties> + <help>MED attribute comparison parameters</help> + <completionHelp> + <list>confed missing-as-worst</list> + </completionHelp> + <valueHelp> + <format>confed</format> + <description>Compare MEDs among confederation paths</description> + </valueHelp> + <valueHelp> + <format>missing-as-worst</format> + <description>Treat missing route as a MED as the least preferred one</description> + </valueHelp> + <constraint> + <regex>(confed|missing-as-worst)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + <node name="peer-type"> + <properties> + <help>Peer type</help> + </properties> + <children> + <leafNode name="multipath-relax"> + <properties> + <help>Allow load sharing across routes learned from different peer types</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="cluster-id"> + <properties> + <help>Route-reflector cluster-id</help> + <valueHelp> + <format>ipv4</format> + <description>Route-reflector cluster-id</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <node name="confederation"> + <properties> + <help>AS confederation parameters</help> + </properties> + <children> + <leafNode name="identifier"> + <properties> + <help>Confederation AS identifier</help> + <valueHelp> + <format>u32:1-4294967294</format> + <description>Confederation AS id</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967294"/> + </constraint> + </properties> + </leafNode> + <leafNode name="peers"> + <properties> + <help>Peer ASs in the BGP confederation</help> + <valueHelp> + <format>u32:1-4294967294</format> + <description>Peer AS number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967294"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + <node name="conditional-advertisement"> + <properties> + <help>Conditional advertisement settings</help> + </properties> + <children> + <leafNode name="timer"> + <properties> + <help>Set period to rescan BGP table to check if condition is met</help> + <valueHelp> + <format>u32:5-240</format> + <description>Period to rerun the conditional advertisement scanner process</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 5-240"/> + </constraint> + </properties> + <defaultValue>60</defaultValue> + </leafNode> + </children> + </node> + <node name="dampening"> + <properties> + <help>Enable route-flap dampening</help> + </properties> + <children> + <leafNode name="half-life"> + <properties> + <help>Half-life time for dampening</help> + <valueHelp> + <format>u32:1-45</format> + <description>Half-life penalty in minutes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-45"/> + </constraint> + </properties> + </leafNode> + <leafNode name="max-suppress-time"> + <properties> + <help>Maximum duration to suppress a stable route</help> + <valueHelp> + <format>u32:1-255</format> + <description>Maximum suppress duration in minutes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="re-use"> + <properties> + <help>Threshold to start reusing a route</help> + <valueHelp> + <format>u32:1-20000</format> + <description>Re-use penalty points</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-20000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="start-suppress-time"> + <properties> + <help>When to start suppressing a route</help> + <valueHelp> + <format>u32:1-20000</format> + <description>Start-suppress penalty points</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-20000"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="default"> + <properties> + <help>BGP defaults</help> + </properties> + <children> + <leafNode name="local-pref"> + <properties> + <help>Default local preference</help> + <valueHelp> + <format>u32</format> + <description>Local preference</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <leafNode name="deterministic-med"> + <properties> + <help>Compare MEDs between different peers in the same AS</help> + <valueless/> + </properties> + </leafNode> + <node name="distance"> + <properties> + <help>Administratives distances for BGP routes</help> + </properties> + <children> + <node name="global"> + <properties> + <help>Global administratives distances for BGP routes</help> + </properties> + <children> + <leafNode name="external"> + <properties> + <help>Administrative distance for external BGP routes</help> + <valueHelp> + <format>u32:1-255</format> + <description>Administrative distance for external BGP routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="internal"> + <properties> + <help>Administrative distance for internal BGP routes</help> + <valueHelp> + <format>u32:1-255</format> + <description>Administrative distance for internal BGP routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="local"> + <properties> + <help>Administrative distance for local BGP routes</help> + <valueHelp> + <format>u32:1-255</format> + <description>Administrative distance for internal BGP routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <tagNode name="prefix"> + <properties> + <help>Administrative distance for a specific BGP prefix</help> + <valueHelp> + <format>ipv4net</format> + <description>Administrative distance for a specific BGP prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="distance"> + <properties> + <help>Administrative distance for prefix</help> + <valueHelp> + <format>u32:1-255</format> + <description>Administrative distance for external BGP routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + <leafNode name="ebgp-requires-policy"> + <properties> + <help>Require in and out policy for eBGP peers (RFC8212)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="fast-convergence"> + <properties> + <help>Teardown sessions immediately whenever peer becomes unreachable</help> + <valueless/> + </properties> + </leafNode> + <node name="graceful-restart"> + <properties> + <help>Graceful restart capability parameters</help> + </properties> + <children> + <leafNode name="stalepath-time"> + <properties> + <help>Maximum time to hold onto restarting neighbors stale paths</help> + <valueHelp> + <format>u32:1-3600</format> + <description>Hold time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-3600"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <leafNode name="graceful-shutdown"> + <properties> + <help>Graceful shutdown</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="no-hard-administrative-reset"> + <properties> + <help>Do not send hard reset CEASE Notification for 'Administrative Reset'</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="labeled-unicast"> + <properties> + <help>BGP Labeled-unicast options</help> + <completionHelp> + <list>explicit-null ipv4-explicit-null ipv6-explicit-null</list> + </completionHelp> + <valueHelp> + <format>explicit-null</format> + <description>Use explicit-null label values for all local prefixes</description> + </valueHelp> + <valueHelp> + <format>ipv4-explicit-null</format> + <description>Use IPv4 explicit-null label value for IPv4 local prefixes</description> + </valueHelp> + <valueHelp> + <format>ipv6-explicit-null</format> + <description>Use IPv6 explicit-null label value for IPv4 local prefixes</description> + </valueHelp> + <constraint> + <regex>(explicit-null|ipv4-explicit-null|ipv6-explicit-null)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="log-neighbor-changes"> + <properties> + <help>Log neighbor up/down changes and reset reason</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="minimum-holdtime"> + <properties> + <help>BGP minimum holdtime</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Minimum holdtime in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="network-import-check"> + <properties> + <help>Enable IGP route check for network statements</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="route-reflector-allow-outbound-policy"> + <properties> + <help>Route reflector client allow policy outbound</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="no-client-to-client-reflection"> + <properties> + <help>Disable client to client route reflection</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="no-fast-external-failover"> + <properties> + <help>Disable immediate session reset on peer link down event</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="no-suppress-duplicates"> + <properties> + <help>Disable suppress duplicate updates if the route actually not changed</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="reject-as-sets"> + <properties> + <help>Reject routes with AS_SET or AS_CONFED_SET flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="shutdown"> + <properties> + <help>Administrative shutdown of the BGP instance</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="suppress-fib-pending"> + <properties> + <help>Advertise only routes that are programmed in kernel to peers</help> + <valueless/> + </properties> + </leafNode> + #include <include/router-id.xml.i> + <node name="tcp-keepalive"> + <properties> + <help>TCP keepalive parameters</help> + </properties> + <children> + <leafNode name="idle"> + <properties> + <help>TCP keepalive idle time</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Idle time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="interval"> + <properties> + <help>TCP keepalive interval</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="probes"> + <properties> + <help>TCP keepalive maximum probes</help> + <valueHelp> + <format>u32:1-30</format> + <description>Maximum probes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-30"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> +</node> +<tagNode name="peer-group"> + <properties> + <help>Name of peer-group</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + </properties> + <children> + <node name="address-family"> + <properties> + <help>Address-family parameters</help> + </properties> + <children> + #include <include/bgp/neighbor-afi-ipv4-unicast.xml.i> + #include <include/bgp/neighbor-afi-ipv4-labeled-unicast.xml.i> + #include <include/bgp/neighbor-afi-ipv4-vpn.xml.i> + #include <include/bgp/neighbor-afi-ipv6-unicast.xml.i> + #include <include/bgp/neighbor-afi-ipv6-labeled-unicast.xml.i> + #include <include/bgp/neighbor-afi-ipv6-vpn.xml.i> + #include <include/bgp/neighbor-afi-l2vpn-evpn.xml.i> + </children> + </node> + #include <include/generic-description.xml.i> + #include <include/bgp/neighbor-bfd.xml.i> + #include <include/bgp/neighbor-capability.xml.i> + #include <include/bgp/neighbor-disable-capability-negotiation.xml.i> + #include <include/bgp/neighbor-disable-connected-check.xml.i> + #include <include/bgp/neighbor-ebgp-multihop.xml.i> + #include <include/bgp/neighbor-graceful-restart.xml.i> + #include <include/bgp/neighbor-graceful-restart.xml.i> + #include <include/bgp/neighbor-local-as.xml.i> + #include <include/bgp/neighbor-local-role.xml.i> + #include <include/bgp/neighbor-override-capability.xml.i> + #include <include/bgp/neighbor-path-attribute.xml.i> + #include <include/bgp/neighbor-passive.xml.i> + #include <include/bgp/neighbor-password.xml.i> + #include <include/bgp/neighbor-shutdown.xml.i> + #include <include/bgp/neighbor-ttl-security.xml.i> + #include <include/bgp/neighbor-update-source.xml.i> + #include <include/bgp/remote-as.xml.i> + #include <include/port-number.xml.i> + </children> +</tagNode> +<node name="srv6"> + <properties> + <help>Segment-Routing SRv6 configuration</help> + </properties> + <children> + <leafNode name="locator"> + <properties> + <help>Specify SRv6 locator</help> + <valueHelp> + <format>txt</format> + <description>SRv6 locator name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + </properties> + </leafNode> + </children> +</node> +<node name="sid"> + <properties> + <help>SID value for VRF</help> + </properties> + <children> + <node name="vpn"> + <properties> + <help>Between current VRF and VPN</help> + </properties> + <children> + <node name="per-vrf"> + <properties> + <help>SID per-VRF (both IPv4 and IPv6 address families)</help> + </properties> + <children> + <leafNode name="export"> + <properties> + <help>For routes leaked from current VRF to VPN</help> + <completionHelp> + <list>auto</list> + </completionHelp> + <valueHelp> + <format>u32:1-1048575</format> + <description>SID allocation index</description> + </valueHelp> + <valueHelp> + <format>auto</format> + <description>Automatically assign a label</description> + </valueHelp> + <constraint> + <regex>auto</regex> + <validator name="numeric" argument="--range 1-1048575"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> +</node> +<node name="timers"> + <properties> + <help>BGP protocol timers</help> + </properties> + <children> + #include <include/bgp/timers-holdtime.xml.i> + #include <include/bgp/timers-keepalive.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/remote-as.xml.i b/interface-definitions/include/bgp/remote-as.xml.i new file mode 100644 index 0000000..79d3b95 --- /dev/null +++ b/interface-definitions/include/bgp/remote-as.xml.i @@ -0,0 +1,27 @@ +<!-- include start from bgp/remote-as.xml.i --> +<leafNode name="remote-as"> + <properties> + <help>Neighbor BGP AS number</help> + <completionHelp> + <list>external internal</list> + </completionHelp> + <valueHelp> + <format>u32:1-4294967294</format> + <description>Neighbor AS number</description> + </valueHelp> + <valueHelp> + <format>external</format> + <description>Any AS different from the local AS</description> + </valueHelp> + <valueHelp> + <format>internal</format> + <description>Neighbor AS number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967294"/> + <regex>(external|internal)</regex> + </constraint> + <constraintErrorMessage>Invalid AS number</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/route-distinguisher.xml.i b/interface-definitions/include/bgp/route-distinguisher.xml.i new file mode 100644 index 0000000..8bc5b45 --- /dev/null +++ b/interface-definitions/include/bgp/route-distinguisher.xml.i @@ -0,0 +1,14 @@ +<!-- include start from bgp/route-distinguisher.xml.i --> +<leafNode name="rd"> + <properties> + <help>Route Distinguisher</help> + <valueHelp> + <format>ASN:NN_OR_IP-ADDRESS:NN</format> + <description>Route Distinguisher, (x.x.x.x:yyy|xxxx:yyyy)</description> + </valueHelp> + <constraint> + <validator name="bgp-rd-rt" argument="--route-distinguisher"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/timers-holdtime.xml.i b/interface-definitions/include/bgp/timers-holdtime.xml.i new file mode 100644 index 0000000..31e97f6 --- /dev/null +++ b/interface-definitions/include/bgp/timers-holdtime.xml.i @@ -0,0 +1,18 @@ +<!-- include start from bgp/timers-holdtime.xml.i --> +<leafNode name="holdtime"> + <properties> + <help>Hold timer</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Hold timer in seconds</description> + </valueHelp> + <valueHelp> + <format>0</format> + <description>Disable hold timer</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/timers-keepalive.xml.i b/interface-definitions/include/bgp/timers-keepalive.xml.i new file mode 100644 index 0000000..b23f96e --- /dev/null +++ b/interface-definitions/include/bgp/timers-keepalive.xml.i @@ -0,0 +1,14 @@ +<!-- include start from bgp/timers-keepalive.xml.i --> +<leafNode name="keepalive"> + <properties> + <help>BGP keepalive interval for this neighbor</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Keepalive interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/certificate-ca.xml.i b/interface-definitions/include/certificate-ca.xml.i new file mode 100644 index 0000000..3cde2a4 --- /dev/null +++ b/interface-definitions/include/certificate-ca.xml.i @@ -0,0 +1,14 @@ +<!-- include start from certificate-ca.xml.i --> +<leafNode name="ca-cert-file"> + <properties> + <help>Certificate Authority in x509 PEM format</help> + <valueHelp> + <format>filename</format> + <description>File in /config/auth directory</description> + </valueHelp> + <constraint> + <validator name="file-path" argument="--strict --parent-dir /config/auth"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/certificate-key.xml.i b/interface-definitions/include/certificate-key.xml.i new file mode 100644 index 0000000..2c4d81f --- /dev/null +++ b/interface-definitions/include/certificate-key.xml.i @@ -0,0 +1,14 @@ +<!-- include start from certificate-key.xml.i --> +<leafNode name="key-file"> + <properties> + <help>Certificate private key in x509 PEM format</help> + <valueHelp> + <format>filename</format> + <description>File in /config/auth directory</description> + </valueHelp> + <constraint> + <validator name="file-path" argument="--strict --parent-dir /config/auth"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/certificate.xml.i b/interface-definitions/include/certificate.xml.i new file mode 100644 index 0000000..6a5b293 --- /dev/null +++ b/interface-definitions/include/certificate.xml.i @@ -0,0 +1,14 @@ +<!-- include start from certificate.xml.i --> +<leafNode name="cert-file"> + <properties> + <help>Certificate public key in x509 PEM format</help> + <valueHelp> + <format>filename</format> + <description>File in /config/auth directory</description> + </valueHelp> + <constraint> + <validator name="file-path" argument="--strict --parent-dir /config/auth"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/conntrack/log-protocols.xml.i b/interface-definitions/include/conntrack/log-protocols.xml.i new file mode 100644 index 0000000..0192507 --- /dev/null +++ b/interface-definitions/include/conntrack/log-protocols.xml.i @@ -0,0 +1,26 @@ +<!-- include start from conntrack/log-protocols.xml.i --> +<leafNode name="icmp"> + <properties> + <help>Log connection tracking events for ICMP</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="other"> + <properties> + <help>Log connection tracking events for all protocols other than TCP, UDP and ICMP</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="tcp"> + <properties> + <help>Log connection tracking events for TCP</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="udp"> + <properties> + <help>Log connection tracking events for UDP</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/conntrack/timeout-custom-protocols.xml.i b/interface-definitions/include/conntrack/timeout-custom-protocols.xml.i new file mode 100644 index 0000000..e6bff7e --- /dev/null +++ b/interface-definitions/include/conntrack/timeout-custom-protocols.xml.i @@ -0,0 +1,136 @@ +<!-- include start from conntrack/timeout-custom-protocols.xml.i --> +<node name="tcp"> + <properties> + <help>TCP connection timeout options</help> + </properties> + <children> + <leafNode name="close-wait"> + <properties> + <help>TCP CLOSE-WAIT timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP CLOSE-WAIT timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + <leafNode name="close"> + <properties> + <help>TCP CLOSE timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP CLOSE timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + <leafNode name="established"> + <properties> + <help>TCP ESTABLISHED timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP ESTABLISHED timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + <leafNode name="fin-wait"> + <properties> + <help>TCP FIN-WAIT timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP FIN-WAIT timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + <leafNode name="last-ack"> + <properties> + <help>TCP LAST-ACK timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP LAST-ACK timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + <leafNode name="syn-recv"> + <properties> + <help>TCP SYN-RECEIVED timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP SYN-RECEIVED timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + <leafNode name="syn-sent"> + <properties> + <help>TCP SYN-SENT timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP SYN-SENT timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + <leafNode name="time-wait"> + <properties> + <help>TCP TIME-WAIT timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP TIME-WAIT timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<node name="udp"> + <properties> + <help>UDP timeout options</help> + </properties> + <children> + <leafNode name="replied"> + <properties> + <help>Timeout for UDP connection seen in both directions</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>Timeout for UDP connection seen in both directions</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + <leafNode name="unreplied"> + <properties> + <help>Timeout for unreplied UDP</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>Timeout for unreplied UDP</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i b/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i new file mode 100644 index 0000000..34c94e5 --- /dev/null +++ b/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i @@ -0,0 +1,3 @@ +<!-- include start from constraint/alpha-numeric-hyphen-underscore-dot.xml.i --> +<regex>[-_a-zA-Z0-9][\w\-\.\+]*</regex> +<!-- include end --> diff --git a/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i b/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i new file mode 100644 index 0000000..399f2e1 --- /dev/null +++ b/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i @@ -0,0 +1,3 @@ +<!-- include start from constraint/alpha-numeric-hyphen-underscore.xml.i --> +<regex>[-_a-zA-Z0-9]+</regex> +<!-- include end --> diff --git a/interface-definitions/include/constraint/container-network.xml.i b/interface-definitions/include/constraint/container-network.xml.i new file mode 100644 index 0000000..6f0f06d --- /dev/null +++ b/interface-definitions/include/constraint/container-network.xml.i @@ -0,0 +1,6 @@ +<!-- include start from constraint/container-network.xml.i --> +<constraint> + <regex>[-_a-zA-Z0-9]{1,11}</regex> +</constraint> +<constraintErrorMessage>Network name cannot be longer than 11 characters</constraintErrorMessage> +<!-- include end --> diff --git a/interface-definitions/include/constraint/dhcp-client-string-option.xml.i b/interface-definitions/include/constraint/dhcp-client-string-option.xml.i new file mode 100644 index 0000000..0e3fb8a --- /dev/null +++ b/interface-definitions/include/constraint/dhcp-client-string-option.xml.i @@ -0,0 +1,4 @@ +<!-- include start from constraint/dhcp-client-string-option.xml.i --> +<regex>[-_a-zA-Z0-9.\s]+</regex> +<regex>([a-fA-F0-9][a-fA-F0-9]:){2,}[a-fA-F0-9][a-fA-F0-9]</regex> +<!-- include end --> diff --git a/interface-definitions/include/constraint/email.xml.i b/interface-definitions/include/constraint/email.xml.i new file mode 100644 index 0000000..b19a88d --- /dev/null +++ b/interface-definitions/include/constraint/email.xml.i @@ -0,0 +1,3 @@ +<!-- include start from constraint/email.xml.i --> +<regex>[^\s@]+@([^\s@.,]+\.)+[^\s@.,]{2,}</regex> +<!-- include end --> diff --git a/interface-definitions/include/constraint/host-name.xml.i b/interface-definitions/include/constraint/host-name.xml.i new file mode 100644 index 0000000..5943772 --- /dev/null +++ b/interface-definitions/include/constraint/host-name.xml.i @@ -0,0 +1,3 @@ +<!-- include start from constraint/host-name.xml.i --> +<regex>[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]</regex> +<!-- include end --> diff --git a/interface-definitions/include/constraint/interface-name-with-wildcard.xml.i b/interface-definitions/include/constraint/interface-name-with-wildcard.xml.i new file mode 100644 index 0000000..adff530 --- /dev/null +++ b/interface-definitions/include/constraint/interface-name-with-wildcard.xml.i @@ -0,0 +1,4 @@ +<!-- include start from constraint/interface-name-with-wildcard.xml.i --> +<regex>(bond|br|dum|en|ersp|eth|gnv|ifb|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)([0-9]?)(\*?)(.+)?|lo</regex> +<validator name="file-path --lookup-path /sys/class/net --directory"/> +<!-- include end --> diff --git a/interface-definitions/include/constraint/interface-name.xml.i b/interface-definitions/include/constraint/interface-name.xml.i new file mode 100644 index 0000000..3e7c4e6 --- /dev/null +++ b/interface-definitions/include/constraint/interface-name.xml.i @@ -0,0 +1,4 @@ +<!-- include start from constraint/interface-name.xml.i --> +<regex>(bond|br|dum|en|ersp|eth|gnv|ifb|ipoe|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|sstpc|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)[0-9]+(.\d+)?|lo</regex> +<validator name="file-path --lookup-path /sys/class/net --directory"/> +<!-- include end --> diff --git a/interface-definitions/include/constraint/login-username.xml.i b/interface-definitions/include/constraint/login-username.xml.i new file mode 100644 index 0000000..09a68b7 --- /dev/null +++ b/interface-definitions/include/constraint/login-username.xml.i @@ -0,0 +1,3 @@ +<!-- include start from constraint/login-username.xml.i --> +<regex>[-_a-zA-Z0-9.]{1,100}</regex> +<!-- include end --> diff --git a/interface-definitions/include/constraint/vrf.xml.i b/interface-definitions/include/constraint/vrf.xml.i new file mode 100644 index 0000000..a1922bb --- /dev/null +++ b/interface-definitions/include/constraint/vrf.xml.i @@ -0,0 +1,6 @@ +<!-- include start from constraint/vrf.xml.i --> +<constraint> + <validator name="vrf-name"/> +</constraint> +<constraintErrorMessage>VRF instance name must be 15 characters or less and can not\nbe named as regular network interfaces.\nA name must starts from a letter.\n</constraintErrorMessage> +<!-- include end --> diff --git a/interface-definitions/include/dhcp-interface-multi.xml.i b/interface-definitions/include/dhcp-interface-multi.xml.i new file mode 100644 index 0000000..0db11cf --- /dev/null +++ b/interface-definitions/include/dhcp-interface-multi.xml.i @@ -0,0 +1,18 @@ +<!-- include start from dhcp-interface-multi.xml.i --> +<leafNode name="dhcp-interface"> + <properties> + <help>DHCP interface supplying next-hop IP address</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>DHCP interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/dhcp-interface.xml.i b/interface-definitions/include/dhcp-interface.xml.i new file mode 100644 index 0000000..b5c94cb --- /dev/null +++ b/interface-definitions/include/dhcp-interface.xml.i @@ -0,0 +1,15 @@ + <leafNode name="dhcp-interface"> + <properties> + <help>DHCP interface supplying next-hop IP address</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>DHCP interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + </leafNode> diff --git a/interface-definitions/include/dhcp/captive-portal.xml.i b/interface-definitions/include/dhcp/captive-portal.xml.i new file mode 100644 index 0000000..643f055 --- /dev/null +++ b/interface-definitions/include/dhcp/captive-portal.xml.i @@ -0,0 +1,11 @@ +<!-- include start from dhcp/captive-portal.xml.i --> +<leafNode name="captive-portal"> + <properties> + <help>Captive portal API endpoint</help> + <valueHelp> + <format>txt</format> + <description>Captive portal API endpoint</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/dhcp/domain-name.xml.i b/interface-definitions/include/dhcp/domain-name.xml.i new file mode 100644 index 0000000..410e27d --- /dev/null +++ b/interface-definitions/include/dhcp/domain-name.xml.i @@ -0,0 +1,11 @@ +<!-- include start from dhcp/domain-name.xml.i --> +<leafNode name="domain-name"> + <properties> + <help>Client Domain Name</help> + <constraint> + <validator name="fqdn"/> + </constraint> + <constraintErrorMessage>Invalid domain name (RFC 1123 section 2).\nMay only contain letters, numbers and .-_</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/dhcp/domain-search.xml.i b/interface-definitions/include/dhcp/domain-search.xml.i new file mode 100644 index 0000000..bcc8fcd --- /dev/null +++ b/interface-definitions/include/dhcp/domain-search.xml.i @@ -0,0 +1,12 @@ +<!-- include start from dhcp/domain-search.xml.i --> +<leafNode name="domain-search"> + <properties> + <help>Client Domain Name search list</help> + <constraint> + <validator name="fqdn"/> + </constraint> + <constraintErrorMessage>Invalid domain name (RFC 1123 section 2).\nMay only contain letters, numbers, period, and underscore.</constraintErrorMessage> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/dhcp/ntp-server.xml.i b/interface-definitions/include/dhcp/ntp-server.xml.i new file mode 100644 index 0000000..4d7235a --- /dev/null +++ b/interface-definitions/include/dhcp/ntp-server.xml.i @@ -0,0 +1,15 @@ +<!-- include start from dhcp/ntp-server.xml.i --> +<leafNode name="ntp-server"> + <properties> + <help>IP address of NTP server</help> + <valueHelp> + <format>ipv4</format> + <description>NTP server IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/dhcp/option-v4.xml.i b/interface-definitions/include/dhcp/option-v4.xml.i new file mode 100644 index 0000000..bd6fc60 --- /dev/null +++ b/interface-definitions/include/dhcp/option-v4.xml.i @@ -0,0 +1,257 @@ +<!-- include start from dhcp/option-v4.xml.i --> +<node name="option"> + <properties> + <help>DHCP option</help> + </properties> + <children> + #include <include/dhcp/captive-portal.xml.i> + #include <include/dhcp/domain-name.xml.i> + #include <include/dhcp/domain-search.xml.i> + #include <include/dhcp/ntp-server.xml.i> + #include <include/name-server-ipv4.xml.i> + <leafNode name="bootfile-name"> + <properties> + <help>Bootstrap file name</help> + <constraint> + <regex>[[:ascii:]]{1,253}</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="bootfile-server"> + <properties> + <help>Server from which the initial boot file is to be loaded</help> + <valueHelp> + <format>ipv4</format> + <description>Bootfile server IPv4 address</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>Bootfile server FQDN</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="fqdn"/> + </constraint> + </properties> + </leafNode> + <leafNode name="bootfile-size"> + <properties> + <help>Bootstrap file size</help> + <valueHelp> + <format>u32:1-16</format> + <description>Bootstrap file size in 512 byte blocks</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-16"/> + </constraint> + </properties> + </leafNode> + <leafNode name="client-prefix-length"> + <properties> + <help>Specifies the clients subnet mask as per RFC 950. If unset, subnet declaration is used.</help> + <valueHelp> + <format>u32:0-32</format> + <description>DHCP client prefix length must be 0 to 32</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-32"/> + </constraint> + <constraintErrorMessage>DHCP client prefix length must be 0 to 32</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="default-router"> + <properties> + <help>IP address of default router</help> + <valueHelp> + <format>ipv4</format> + <description>Default router IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="ip-forwarding"> + <properties> + <help>Enable IP forwarding on client</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ipv6-only-preferred"> + <properties> + <help>Disable IPv4 on IPv6 only hosts (RFC 8925)</help> + <valueHelp> + <format>u32</format> + <description>Seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + <constraintErrorMessage>Seconds must be between 0 and 4294967295 (49 days)</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="pop-server"> + <properties> + <help>IP address of POP3 server</help> + <valueHelp> + <format>ipv4</format> + <description>POP3 server IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="server-identifier"> + <properties> + <help>Address for DHCP server identifier</help> + <valueHelp> + <format>ipv4</format> + <description>DHCP server identifier IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="smtp-server"> + <properties> + <help>IP address of SMTP server</help> + <valueHelp> + <format>ipv4</format> + <description>SMTP server IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <tagNode name="static-route"> + <properties> + <help>Classless static route destination subnet</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="next-hop"> + <properties> + <help>IP address of router to be used to reach the destination subnet</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address of router</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode > + <leafNode name="tftp-server-name"> + <properties> + <help>TFTP server name</help> + <valueHelp> + <format>ipv4</format> + <description>TFTP server IPv4 address</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>TFTP server FQDN</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="fqdn"/> + </constraint> + </properties> + </leafNode> + <leafNode name="time-offset"> + <properties> + <help>Client subnet offset in seconds from Coordinated Universal Time (UTC)</help> + <valueHelp> + <format>[-]N</format> + <description>Time offset (number, may be negative)</description> + </valueHelp> + <constraint> + <regex>-?[0-9]+</regex> + </constraint> + <constraintErrorMessage>Invalid time offset value</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="time-server"> + <properties> + <help>IP address of time server</help> + <valueHelp> + <format>ipv4</format> + <description>Time server IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="time-zone"> + <properties> + <help>Time zone to send to clients. Uses RFC4833 options 100 and 101</help> + <completionHelp> + <script>timedatectl list-timezones</script> + </completionHelp> + <constraint> + <validator name="timezone" argument="--validate"/> + </constraint> + </properties> + </leafNode> + <node name="vendor-option"> + <properties> + <help>Vendor Specific Options</help> + </properties> + <children> + <node name="ubiquiti"> + <properties> + <help>Ubiquiti specific parameters</help> + </properties> + <children> + <leafNode name="unifi-controller"> + <properties> + <help>Address of UniFi controller</help> + <valueHelp> + <format>ipv4</format> + <description>IP address of UniFi controller</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="wins-server"> + <properties> + <help>IP address for Windows Internet Name Service (WINS) server</help> + <valueHelp> + <format>ipv4</format> + <description>WINS server IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="wpad-url"> + <properties> + <help>Web Proxy Autodiscovery (WPAD) URL</help> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/dhcp/option-v6.xml.i b/interface-definitions/include/dhcp/option-v6.xml.i new file mode 100644 index 0000000..e1897f5 --- /dev/null +++ b/interface-definitions/include/dhcp/option-v6.xml.i @@ -0,0 +1,122 @@ +<!-- include start from dhcp/option-v6.xml.i --> +<node name="option"> + <properties> + <help>DHCPv6 option</help> + </properties> + <children> + #include <include/dhcp/captive-portal.xml.i> + #include <include/dhcp/domain-search.xml.i> + #include <include/name-server-ipv6.xml.i> + <leafNode name="nis-domain"> + <properties> + <help>NIS domain name for client to use</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Invalid NIS domain name</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="nis-server"> + <properties> + <help>IPv6 address of a NIS Server</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of NIS server</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="nisplus-domain"> + <properties> + <help>NIS+ domain name for client to use</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Invalid NIS+ domain name. May only contain letters, numbers and .-_</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="nisplus-server"> + <properties> + <help>IPv6 address of a NIS+ Server</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of NIS+ server</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="sip-server"> + <properties> + <help>IPv6 address of SIP server</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of SIP server</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>FQDN of SIP server</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + <validator name="fqdn"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="sntp-server"> + <properties> + <help>IPv6 address of an SNTP server for client to use</help> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="info-refresh-time"> + <properties> + <help>Time (in seconds) that stateless clients should wait between refreshing the information they were given</help> + <valueHelp> + <format>u32:1-4294967295</format> + <description>DHCPv6 information refresh time</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> + </leafNode> + <node name="vendor-option"> + <properties> + <help>Vendor Specific Options</help> + </properties> + <children> + <node name="cisco"> + <properties> + <help>Cisco specific parameters</help> + </properties> + <children> + <leafNode name="tftp-server"> + <properties> + <help>TFTP server name</help> + <valueHelp> + <format>ipv6</format> + <description>TFTP server IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/dns/time-to-live.xml.i b/interface-definitions/include/dns/time-to-live.xml.i new file mode 100644 index 0000000..000eea1 --- /dev/null +++ b/interface-definitions/include/dns/time-to-live.xml.i @@ -0,0 +1,14 @@ +<!-- include start from dns/time-to-live.xml.i --> +<leafNode name="ttl"> + <properties> + <help>Time-to-live (TTL)</help> + <valueHelp> + <format>u32:0-2147483647</format> + <description>TTL in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2147483647"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/eigrp/protocol-common-config.xml.i b/interface-definitions/include/eigrp/protocol-common-config.xml.i new file mode 100644 index 0000000..a8290f7 --- /dev/null +++ b/interface-definitions/include/eigrp/protocol-common-config.xml.i @@ -0,0 +1,125 @@ +<!-- include start from eigrp/protocol-common-config.xml.i --> +<leafNode name="system-as"> + <properties> + <help>Autonomous System Number (ASN)</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Autonomous System Number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> +</leafNode> +<leafNode name="maximum-paths"> + <properties> + <help>Forward packets over multiple paths</help> + <valueHelp> + <format>u32:1-32</format> + <description>Number of paths</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-32"/> + </constraint> + </properties> +</leafNode> +<node name="metric"> + <properties> + <help>Modify metrics and parameters for advertisement</help> + </properties> + <children> + <leafNode name="weights"> + <properties> + <help>Modify metric coefficients</help> + <valueHelp> + <format>u32:0-255</format> + <description>K1</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<leafNode name="network"> + <properties> + <help>Enable routing on an IP network</help> + <valueHelp> + <format>ipv4net</format> + <description>EIGRP network prefix</description> + </valueHelp> + <constraint> + <validator name="ip-prefix"/> + </constraint> + <multi/> + </properties> +</leafNode> +<leafNode name="passive-interface"> + <properties> + <help>Suppress routing updates on an interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <multi/> + </properties> +</leafNode> +<leafNode name="redistribute"> + <properties> + <help>Redistribute information from another routing protocol</help> + <valueHelp> + <format>bgp</format> + <description>Border Gateway Protocol (BGP)</description> + </valueHelp> + <valueHelp> + <format>connected</format> + <description>Connected routes</description> + </valueHelp> + <valueHelp> + <format>nhrp</format> + <description>Next Hop Resolution Protocol (NHRP)</description> + </valueHelp> + <valueHelp> + <format>ospf</format> + <description>Open Shortest Path First (OSPFv2)</description> + </valueHelp> + <valueHelp> + <format>rip</format> + <description>Routing Information Protocol (RIP)</description> + </valueHelp> + <valueHelp> + <format>babel</format> + <description>Babel routing protocol (Babel)</description> + </valueHelp> + <valueHelp> + <format>static</format> + <description>Statically configured routes</description> + </valueHelp> + <valueHelp> + <format>vnc</format> + <description>Virtual Network Control (VNC)</description> + </valueHelp> + <completionHelp> + <list>bgp connected nhrp ospf rip static vnc</list> + </completionHelp> + <constraint> + <regex>(bgp|connected|nhrp|ospf|rip|babel|static|vnc)</regex> + </constraint> + <multi/> + </properties> +</leafNode> +#include <include/router-id.xml.i> +<!-- FRR error: active time not implemented yet --> +<leafNode name="variance"> + <properties> + <help>Control load balancing variance</help> + <valueHelp> + <format>u32:1-128</format> + <description>Metric variance multiplier</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-128"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/action-accept-drop-reject.xml.i b/interface-definitions/include/firewall/action-accept-drop-reject.xml.i new file mode 100644 index 0000000..7fd5231 --- /dev/null +++ b/interface-definitions/include/firewall/action-accept-drop-reject.xml.i @@ -0,0 +1,25 @@ +<!-- include start from firewall/action-accept-drop-reject.xml.i --> +<leafNode name="action"> + <properties> + <help>Action for packets</help> + <completionHelp> + <list>accept drop reject</list> + </completionHelp> + <valueHelp> + <format>accept</format> + <description>Action to accept</description> + </valueHelp> + <valueHelp> + <format>drop</format> + <description>Action to drop</description> + </valueHelp> + <valueHelp> + <format>reject</format> + <description>Action to reject</description> + </valueHelp> + <constraint> + <regex>(accept|drop|reject)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/action-and-notrack.xml.i b/interface-definitions/include/firewall/action-and-notrack.xml.i new file mode 100644 index 0000000..de11f7d --- /dev/null +++ b/interface-definitions/include/firewall/action-and-notrack.xml.i @@ -0,0 +1,45 @@ +<!-- include start from firewall/action-and-notrack.xml.i --> +<leafNode name="action"> + <properties> + <help>Rule action</help> + <completionHelp> + <list>accept continue jump notrack reject return drop queue</list> + </completionHelp> + <valueHelp> + <format>accept</format> + <description>Accept matching entries</description> + </valueHelp> + <valueHelp> + <format>continue</format> + <description>Continue parsing next rule</description> + </valueHelp> + <valueHelp> + <format>jump</format> + <description>Jump to another chain</description> + </valueHelp> + <valueHelp> + <format>reject</format> + <description>Reject matching entries</description> + </valueHelp> + <valueHelp> + <format>return</format> + <description>Return from the current chain and continue at the next rule of the last chain</description> + </valueHelp> + <valueHelp> + <format>drop</format> + <description>Drop matching entries</description> + </valueHelp> + <valueHelp> + <format>queue</format> + <description>Enqueue packet to userspace</description> + </valueHelp> + <valueHelp> + <format>notrack</format> + <description>Ignore connection tracking</description> + </valueHelp> + <constraint> + <regex>(accept|continue|jump|notrack|reject|return|drop|queue)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/action-forward.xml.i b/interface-definitions/include/firewall/action-forward.xml.i new file mode 100644 index 0000000..4e59f3c --- /dev/null +++ b/interface-definitions/include/firewall/action-forward.xml.i @@ -0,0 +1,49 @@ +<!-- include start from firewall/action-forward.xml.i --> +<leafNode name="action"> + <properties> + <help>Rule action</help> + <completionHelp> + <list>accept continue jump reject return drop queue offload synproxy</list> + </completionHelp> + <valueHelp> + <format>accept</format> + <description>Accept matching entries</description> + </valueHelp> + <valueHelp> + <format>continue</format> + <description>Continue parsing next rule</description> + </valueHelp> + <valueHelp> + <format>jump</format> + <description>Jump to another chain</description> + </valueHelp> + <valueHelp> + <format>reject</format> + <description>Reject matching entries</description> + </valueHelp> + <valueHelp> + <format>return</format> + <description>Return from the current chain and continue at the next rule of the last chain</description> + </valueHelp> + <valueHelp> + <format>drop</format> + <description>Drop matching entries</description> + </valueHelp> + <valueHelp> + <format>queue</format> + <description>Enqueue packet to userspace</description> + </valueHelp> + <valueHelp> + <format>offload</format> + <description>Offload packet via flowtable</description> + </valueHelp> + <valueHelp> + <format>synproxy</format> + <description>Synproxy connections</description> + </valueHelp> + <constraint> + <regex>(accept|continue|jump|reject|return|drop|queue|offload|synproxy)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/action-l2.xml.i b/interface-definitions/include/firewall/action-l2.xml.i new file mode 100644 index 0000000..84af576 --- /dev/null +++ b/interface-definitions/include/firewall/action-l2.xml.i @@ -0,0 +1,37 @@ +<!-- include start from firewall/action.xml.i --> +<leafNode name="action"> + <properties> + <help>Rule action</help> + <completionHelp> + <list>accept continue jump return drop queue</list> + </completionHelp> + <valueHelp> + <format>accept</format> + <description>Accept matching entries</description> + </valueHelp> + <valueHelp> + <format>continue</format> + <description>Continue parsing next rule</description> + </valueHelp> + <valueHelp> + <format>jump</format> + <description>Jump to another chain</description> + </valueHelp> + <valueHelp> + <format>return</format> + <description>Return from the current chain and continue at the next rule of the last chain</description> + </valueHelp> + <valueHelp> + <format>drop</format> + <description>Drop matching entries</description> + </valueHelp> + <valueHelp> + <format>queue</format> + <description>Enqueue packet to userspace</description> + </valueHelp> + <constraint> + <regex>(accept|continue|jump|return|drop|queue)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/action.xml.i b/interface-definitions/include/firewall/action.xml.i new file mode 100644 index 0000000..e1f0c6c --- /dev/null +++ b/interface-definitions/include/firewall/action.xml.i @@ -0,0 +1,49 @@ +<!-- include start from firewall/action.xml.i --> +<leafNode name="action"> + <properties> + <help>Rule action</help> + <completionHelp> + <list>accept continue jump reject return drop queue offload synproxy</list> + </completionHelp> + <valueHelp> + <format>accept</format> + <description>Accept matching entries</description> + </valueHelp> + <valueHelp> + <format>continue</format> + <description>Continue parsing next rule</description> + </valueHelp> + <valueHelp> + <format>jump</format> + <description>Jump to another chain</description> + </valueHelp> + <valueHelp> + <format>reject</format> + <description>Reject matching entries</description> + </valueHelp> + <valueHelp> + <format>return</format> + <description>Return from the current chain and continue at the next rule of the last chain</description> + </valueHelp> + <valueHelp> + <format>drop</format> + <description>Drop matching entries</description> + </valueHelp> + <valueHelp> + <format>queue</format> + <description>Enqueue packet to userspace</description> + </valueHelp> + <valueHelp> + <format>offload</format> + <description>Offload packet via flowtable</description> + </valueHelp> + <valueHelp> + <format>synproxy</format> + <description>Synproxy connections</description> + </valueHelp> + <constraint> + <regex>(accept|continue|jump|reject|return|drop|queue|offload|synproxy)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/add-addr-to-group-ipv4.xml.i b/interface-definitions/include/firewall/add-addr-to-group-ipv4.xml.i new file mode 100644 index 0000000..a47cadd --- /dev/null +++ b/interface-definitions/include/firewall/add-addr-to-group-ipv4.xml.i @@ -0,0 +1,25 @@ +<!-- include start from firewall/add-addr-to-group-ipv4.xml.i --> +<node name="add-address-to-group"> + <properties> + <help>Add ip address to dynamic address-group</help> + </properties> + <children> + <node name="source-address"> + <properties> + <help>Add source ip addresses to dynamic address-group</help> + </properties> + <children> + #include <include/firewall/add-dynamic-address-groups.xml.i> + </children> + </node> + <node name="destination-address"> + <properties> + <help>Add destination ip addresses to dynamic address-group</help> + </properties> + <children> + #include <include/firewall/add-dynamic-address-groups.xml.i> + </children> + </node> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/add-addr-to-group-ipv6.xml.i b/interface-definitions/include/firewall/add-addr-to-group-ipv6.xml.i new file mode 100644 index 0000000..2cb0774 --- /dev/null +++ b/interface-definitions/include/firewall/add-addr-to-group-ipv6.xml.i @@ -0,0 +1,25 @@ +<!-- include start from firewall/add-addr-to-group-ipv6.xml.i --> +<node name="add-address-to-group"> + <properties> + <help>Add ipv6 address to dynamic ipv6-address-group</help> + </properties> + <children> + <node name="source-address"> + <properties> + <help>Add source ipv6 addresses to dynamic ipv6-address-group</help> + </properties> + <children> + #include <include/firewall/add-dynamic-ipv6-address-groups.xml.i> + </children> + </node> + <node name="destination-address"> + <properties> + <help>Add destination ipv6 addresses to dynamic ipv6-address-group</help> + </properties> + <children> + #include <include/firewall/add-dynamic-ipv6-address-groups.xml.i> + </children> + </node> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/add-dynamic-address-groups.xml.i b/interface-definitions/include/firewall/add-dynamic-address-groups.xml.i new file mode 100644 index 0000000..769761c --- /dev/null +++ b/interface-definitions/include/firewall/add-dynamic-address-groups.xml.i @@ -0,0 +1,34 @@ +<!-- include start from firewall/add-dynamic-address-groups.xml.i --> +<leafNode name="address-group"> + <properties> + <help>Dynamic address-group</help> + <completionHelp> + <path>firewall group dynamic-group address-group</path> + </completionHelp> + </properties> +</leafNode> +<leafNode name="timeout"> + <properties> + <help>Set timeout</help> + <valueHelp> + <format><number>s</format> + <description>Timeout value in seconds</description> + </valueHelp> + <valueHelp> + <format><number>m</format> + <description>Timeout value in minutes</description> + </valueHelp> + <valueHelp> + <format><number>h</format> + <description>Timeout value in hours</description> + </valueHelp> + <valueHelp> + <format><number>d</format> + <description>Timeout value in days</description> + </valueHelp> + <constraint> + <regex>\d+(s|m|h|d)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/add-dynamic-ipv6-address-groups.xml.i b/interface-definitions/include/firewall/add-dynamic-ipv6-address-groups.xml.i new file mode 100644 index 0000000..7bd91c5 --- /dev/null +++ b/interface-definitions/include/firewall/add-dynamic-ipv6-address-groups.xml.i @@ -0,0 +1,34 @@ +<!-- include start from firewall/add-dynamic-ipv6-address-groups.xml.i --> +<leafNode name="address-group"> + <properties> + <help>Dynamic ipv6-address-group</help> + <completionHelp> + <path>firewall group dynamic-group ipv6-address-group</path> + </completionHelp> + </properties> +</leafNode> +<leafNode name="timeout"> + <properties> + <help>Set timeout</help> + <valueHelp> + <format><number>s</format> + <description>Timeout value in seconds</description> + </valueHelp> + <valueHelp> + <format><number>m</format> + <description>Timeout value in minutes</description> + </valueHelp> + <valueHelp> + <format><number>h</format> + <description>Timeout value in hours</description> + </valueHelp> + <valueHelp> + <format><number>d</format> + <description>Timeout value in days</description> + </valueHelp> + <constraint> + <regex>\d+(s|m|h|d)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/address-inet.xml.i b/interface-definitions/include/firewall/address-inet.xml.i new file mode 100644 index 0000000..02ed8f6 --- /dev/null +++ b/interface-definitions/include/firewall/address-inet.xml.i @@ -0,0 +1,63 @@ +<!-- include start from firewall/address-inet.xml.i --> +<leafNode name="address"> + <properties> + <help>IP address, subnet, or range</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <valueHelp> + <format>ipv4range</format> + <description>IPv4 address range to match</description> + </valueHelp> + <valueHelp> + <format>!ipv4</format> + <description>Match everything except the specified address</description> + </valueHelp> + <valueHelp> + <format>!ipv4net</format> + <description>Match everything except the specified prefix</description> + </valueHelp> + <valueHelp> + <format>!ipv4range</format> + <description>Match everything except the specified range</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>Subnet to match</description> + </valueHelp> + <valueHelp> + <format>ipv6range</format> + <description>IP range to match</description> + </valueHelp> + <valueHelp> + <format>!ipv6</format> + <description>Match everything except the specified address</description> + </valueHelp> + <valueHelp> + <format>!ipv6net</format> + <description>Match everything except the specified prefix</description> + </valueHelp> + <valueHelp> + <format>!ipv6range</format> + <description>Match everything except the specified range</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv4-prefix"/> + <validator name="ipv4-range"/> + <validator name="ipv4-address-exclude"/> + <validator name="ipv4-prefix-exclude"/> + <validator name="ipv4-range-exclude"/> + <validator name="ipv6"/> + <validator name="ipv6-exclude"/> + <validator name="ipv6-range"/> + <validator name="ipv6-range-exclude"/> + </constraint> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/address-ipv6.xml.i b/interface-definitions/include/firewall/address-ipv6.xml.i new file mode 100644 index 0000000..fa60c0c --- /dev/null +++ b/interface-definitions/include/firewall/address-ipv6.xml.i @@ -0,0 +1,37 @@ +<!-- include start from firewall/address-ipv6.xml.i --> +<leafNode name="address"> + <properties> + <help>IP address, subnet, or range</help> + <valueHelp> + <format>ipv6</format> + <description>IP address to match</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>Subnet to match</description> + </valueHelp> + <valueHelp> + <format>ipv6range</format> + <description>IP range to match</description> + </valueHelp> + <valueHelp> + <format>!ipv6</format> + <description>Match everything except the specified address</description> + </valueHelp> + <valueHelp> + <format>!ipv6net</format> + <description>Match everything except the specified prefix</description> + </valueHelp> + <valueHelp> + <format>!ipv6range</format> + <description>Match everything except the specified range</description> + </valueHelp> + <constraint> + <validator name="ipv6"/> + <validator name="ipv6-exclude"/> + <validator name="ipv6-range"/> + <validator name="ipv6-range-exclude"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/address-mask-inet.xml.i b/interface-definitions/include/firewall/address-mask-inet.xml.i new file mode 100644 index 0000000..e2a5927 --- /dev/null +++ b/interface-definitions/include/firewall/address-mask-inet.xml.i @@ -0,0 +1,19 @@ +<!-- include start from firewall/address-mask-inet.xml.i --> +<leafNode name="address-mask"> + <properties> + <help>IP mask</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 mask to apply</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IP mask to apply</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv6"/> + </constraint> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/address-mask-ipv6.xml.i b/interface-definitions/include/firewall/address-mask-ipv6.xml.i new file mode 100644 index 0000000..8c04832 --- /dev/null +++ b/interface-definitions/include/firewall/address-mask-ipv6.xml.i @@ -0,0 +1,14 @@ +<!-- include start from firewall/address-mask-ipv6.xml.i --> +<leafNode name="address-mask"> + <properties> + <help>IP mask</help> + <valueHelp> + <format>ipv6</format> + <description>IP mask to apply</description> + </valueHelp> + <constraint> + <validator name="ipv6"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/address-mask.xml.i b/interface-definitions/include/firewall/address-mask.xml.i new file mode 100644 index 0000000..7f6f17d --- /dev/null +++ b/interface-definitions/include/firewall/address-mask.xml.i @@ -0,0 +1,14 @@ +<!-- include start from firewall/address-mask.xml.i --> +<leafNode name="address-mask"> + <properties> + <help>IP mask</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 mask to apply</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/address.xml.i b/interface-definitions/include/firewall/address.xml.i new file mode 100644 index 0000000..2e1bde5 --- /dev/null +++ b/interface-definitions/include/firewall/address.xml.i @@ -0,0 +1,39 @@ +<!-- include start from firewall/address.xml.i --> +<leafNode name="address"> + <properties> + <help>IP address, subnet, or range</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <valueHelp> + <format>ipv4range</format> + <description>IPv4 address range to match</description> + </valueHelp> + <valueHelp> + <format>!ipv4</format> + <description>Match everything except the specified address</description> + </valueHelp> + <valueHelp> + <format>!ipv4net</format> + <description>Match everything except the specified prefix</description> + </valueHelp> + <valueHelp> + <format>!ipv4range</format> + <description>Match everything except the specified range</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv4-prefix"/> + <validator name="ipv4-range"/> + <validator name="ipv4-address-exclude"/> + <validator name="ipv4-prefix-exclude"/> + <validator name="ipv4-range-exclude"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/bridge-custom-name.xml.i b/interface-definitions/include/firewall/bridge-custom-name.xml.i new file mode 100644 index 0000000..9a2a829 --- /dev/null +++ b/interface-definitions/include/firewall/bridge-custom-name.xml.i @@ -0,0 +1,45 @@ +<!-- include start from firewall/bridge-custom-name.xml.i --> +<tagNode name="name"> + <properties> + <help>Bridge custom firewall</help> + <constraint> + <regex>[a-zA-Z0-9][\w\-\.]*</regex> + </constraint> + </properties> + <children> + #include <include/firewall/default-action.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <leafNode name="default-jump-target"> + <properties> + <help>Set jump target. Action jump must be defined in default-action to use this setting</help> + <completionHelp> + <path>firewall bridge name</path> + </completionHelp> + </properties> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Bridge Firewall forward filter rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-bridge.xml.i> + #include <include/firewall/action-l2.xml.i> + #include <include/firewall/connection-mark.xml.i> + #include <include/firewall/connection-status.xml.i> + #include <include/firewall/state.xml.i> + #include <include/firewall/inbound-interface.xml.i> + #include <include/firewall/outbound-interface.xml.i> + </children> + </tagNode> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/bridge-hook-forward.xml.i b/interface-definitions/include/firewall/bridge-hook-forward.xml.i new file mode 100644 index 0000000..fcc9819 --- /dev/null +++ b/interface-definitions/include/firewall/bridge-hook-forward.xml.i @@ -0,0 +1,41 @@ +<!-- include start from firewall/bridge-hook-forward.xml.i --> +<node name="forward"> + <properties> + <help>Bridge forward firewall</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Bridge firewall forward filter</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Bridge Firewall forward filter rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-bridge.xml.i> + #include <include/firewall/action-l2.xml.i> + #include <include/firewall/connection-mark.xml.i> + #include <include/firewall/connection-status.xml.i> + #include <include/firewall/state.xml.i> + #include <include/firewall/inbound-interface.xml.i> + #include <include/firewall/outbound-interface.xml.i> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/bridge-hook-input.xml.i b/interface-definitions/include/firewall/bridge-hook-input.xml.i new file mode 100644 index 0000000..f6a11f8 --- /dev/null +++ b/interface-definitions/include/firewall/bridge-hook-input.xml.i @@ -0,0 +1,40 @@ +<!-- include start from firewall/bridge-hook-input.xml.i --> +<node name="input"> + <properties> + <help>Bridge input firewall</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Bridge firewall input filter</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Bridge Firewall input filter rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-bridge.xml.i> + #include <include/firewall/action-l2.xml.i> + #include <include/firewall/connection-mark.xml.i> + #include <include/firewall/connection-status.xml.i> + #include <include/firewall/state.xml.i> + #include <include/firewall/inbound-interface.xml.i> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/bridge-hook-output.xml.i b/interface-definitions/include/firewall/bridge-hook-output.xml.i new file mode 100644 index 0000000..38b8b08 --- /dev/null +++ b/interface-definitions/include/firewall/bridge-hook-output.xml.i @@ -0,0 +1,40 @@ +<!-- include start from firewall/bridge-hook-output.xml.i --> +<node name="output"> + <properties> + <help>Bridge output firewall</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Bridge firewall output filter</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Bridge Firewall output filter rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-bridge.xml.i> + #include <include/firewall/action-l2.xml.i> + #include <include/firewall/connection-mark.xml.i> + #include <include/firewall/connection-status.xml.i> + #include <include/firewall/state.xml.i> + #include <include/firewall/outbound-interface.xml.i> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/bridge-hook-prerouting.xml.i b/interface-definitions/include/firewall/bridge-hook-prerouting.xml.i new file mode 100644 index 0000000..ea56764 --- /dev/null +++ b/interface-definitions/include/firewall/bridge-hook-prerouting.xml.i @@ -0,0 +1,37 @@ +<!-- include start from firewall/bridge-hook-prerouting.xml.i --> +<node name="prerouting"> + <properties> + <help>Bridge prerouting firewall</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Bridge firewall prerouting filter</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Bridge firewall prerouting filter rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-bridge.xml.i> + #include <include/firewall/action-and-notrack.xml.i> + #include <include/firewall/inbound-interface.xml.i> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/common-rule-bridge.xml.i b/interface-definitions/include/firewall/common-rule-bridge.xml.i new file mode 100644 index 0000000..80088bb --- /dev/null +++ b/interface-definitions/include/firewall/common-rule-bridge.xml.i @@ -0,0 +1,55 @@ +<!-- include start from firewall/common-rule-bridge.xml.i --> +#include <include/generic-description.xml.i> +#include <include/generic-disable-node.xml.i> +#include <include/firewall/dscp.xml.i> +#include <include/firewall/firewall-mark.xml.i> +#include <include/firewall/fragment.xml.i> +#include <include/firewall/hop-limit.xml.i> +#include <include/firewall/icmp.xml.i> +#include <include/firewall/icmpv6.xml.i> +#include <include/firewall/limit.xml.i> +#include <include/firewall/log.xml.i> +#include <include/firewall/log-options.xml.i> +#include <include/firewall/match-ether-type.xml.i> +#include <include/firewall/match-ipsec.xml.i> +#include <include/firewall/match-vlan.xml.i> +#include <include/firewall/nft-queue.xml.i> +#include <include/firewall/packet-options.xml.i> +#include <include/firewall/protocol.xml.i> +#include <include/firewall/tcp-flags.xml.i> +#include <include/firewall/tcp-mss.xml.i> +#include <include/firewall/time.xml.i> +#include <include/firewall/ttl.xml.i> +<node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/mac-address.xml.i> + #include <include/firewall/address-inet.xml.i> + #include <include/firewall/address-mask-inet.xml.i> + #include <include/firewall/port.xml.i> + #include <include/firewall/source-destination-group-inet.xml.i> + </children> +</node> +<leafNode name="jump-target"> + <properties> + <help>Set jump target. Action jump must be defined to use this setting</help> + <completionHelp> + <path>firewall bridge name</path> + </completionHelp> + </properties> +</leafNode> +<node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/mac-address.xml.i> + #include <include/firewall/address-inet.xml.i> + #include <include/firewall/address-mask-inet.xml.i> + #include <include/firewall/port.xml.i> + #include <include/firewall/source-destination-group-inet.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/common-rule-inet.xml.i b/interface-definitions/include/firewall/common-rule-inet.xml.i new file mode 100644 index 0000000..e44938b --- /dev/null +++ b/interface-definitions/include/firewall/common-rule-inet.xml.i @@ -0,0 +1,24 @@ +<!-- include start from firewall/common-rule-inet.xml.i --> +#include <include/firewall/action.xml.i> +#include <include/firewall/conntrack-helper.xml.i> +#include <include/firewall/connection-mark.xml.i> +#include <include/firewall/connection-status.xml.i> +#include <include/generic-description.xml.i> +#include <include/generic-disable-node.xml.i> +#include <include/firewall/dscp.xml.i> +#include <include/firewall/fragment.xml.i> +#include <include/firewall/limit.xml.i> +#include <include/firewall/log.xml.i> +#include <include/firewall/log-options.xml.i> +#include <include/firewall/firewall-mark.xml.i> +#include <include/firewall/packet-options.xml.i> +#include <include/firewall/protocol.xml.i> +#include <include/firewall/nft-queue.xml.i> +#include <include/firewall/recent.xml.i> +#include <include/firewall/state.xml.i> +#include <include/firewall/synproxy.xml.i> +#include <include/firewall/tcp-flags.xml.i> +#include <include/firewall/tcp-mss.xml.i> +#include <include/firewall/gre.xml.i> +#include <include/firewall/time.xml.i> +<!-- include end --> diff --git a/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i b/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i new file mode 100644 index 0000000..e8da1a0 --- /dev/null +++ b/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i @@ -0,0 +1,47 @@ +<!-- include start from firewall/common-rule-ipv4-raw.xml.i --> +#include <include/firewall/add-addr-to-group-ipv4.xml.i> +#include <include/firewall/action-and-notrack.xml.i> +#include <include/generic-description.xml.i> +#include <include/firewall/dscp.xml.i> +#include <include/firewall/fragment.xml.i> +#include <include/generic-disable-node.xml.i> +#include <include/firewall/icmp.xml.i> +#include <include/firewall/limit.xml.i> +#include <include/firewall/log.xml.i> +#include <include/firewall/log-options.xml.i> +#include <include/firewall/protocol.xml.i> +#include <include/firewall/nft-queue.xml.i> +#include <include/firewall/recent.xml.i> +#include <include/firewall/tcp-flags.xml.i> +#include <include/firewall/tcp-mss.xml.i> +#include <include/firewall/time.xml.i> +#include <include/firewall/ttl.xml.i> +<node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/address.xml.i> + #include <include/firewall/address-mask.xml.i> + #include <include/firewall/fqdn.xml.i> + #include <include/firewall/geoip.xml.i> + #include <include/firewall/mac-address.xml.i> + #include <include/firewall/port.xml.i> + #include <include/firewall/source-destination-group.xml.i> + </children> +</node> +<node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/address.xml.i> + #include <include/firewall/address-mask.xml.i> + #include <include/firewall/fqdn.xml.i> + #include <include/firewall/geoip.xml.i> + #include <include/firewall/mac-address.xml.i> + #include <include/firewall/port.xml.i> + #include <include/firewall/source-destination-group.xml.i> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/common-rule-ipv4.xml.i b/interface-definitions/include/firewall/common-rule-ipv4.xml.i new file mode 100644 index 0000000..803b94b --- /dev/null +++ b/interface-definitions/include/firewall/common-rule-ipv4.xml.i @@ -0,0 +1,44 @@ +<!-- include start from firewall/common-rule-ipv4.xml.i --> +#include <include/firewall/add-addr-to-group-ipv4.xml.i> +#include <include/firewall/common-rule-inet.xml.i> +#include <include/firewall/icmp.xml.i> +#include <include/firewall/ttl.xml.i> +<node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/address.xml.i> + #include <include/firewall/address-mask.xml.i> + #include <include/firewall/fqdn.xml.i> + #include <include/firewall/geoip.xml.i> + #include <include/firewall/mac-address.xml.i> + #include <include/firewall/port.xml.i> + #include <include/firewall/source-destination-group.xml.i> + #include <include/firewall/source-destination-dynamic-group.xml.i> + </children> +</node> +<leafNode name="jump-target"> + <properties> + <help>Set jump target. Action jump must be defined to use this setting</help> + <completionHelp> + <path>firewall ipv4 name</path> + </completionHelp> + </properties> +</leafNode> +<node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/address.xml.i> + #include <include/firewall/address-mask.xml.i> + #include <include/firewall/fqdn.xml.i> + #include <include/firewall/geoip.xml.i> + #include <include/firewall/mac-address.xml.i> + #include <include/firewall/port.xml.i> + #include <include/firewall/source-destination-group.xml.i> + #include <include/firewall/source-destination-dynamic-group.xml.i> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/common-rule-ipv6-raw.xml.i b/interface-definitions/include/firewall/common-rule-ipv6-raw.xml.i new file mode 100644 index 0000000..3f7c5a0 --- /dev/null +++ b/interface-definitions/include/firewall/common-rule-ipv6-raw.xml.i @@ -0,0 +1,49 @@ +<!-- include start from firewall/common-rule-ipv6-raw.xml.i --> +#include <include/firewall/add-addr-to-group-ipv6.xml.i> +#include <include/firewall/action-and-notrack.xml.i> +#include <include/generic-description.xml.i> +#include <include/firewall/dscp.xml.i> +#include <include/firewall/fragment.xml.i> +#include <include/generic-disable-node.xml.i> +#include <include/firewall/icmpv6.xml.i> +#include <include/firewall/limit.xml.i> +#include <include/firewall/log.xml.i> +#include <include/firewall/log-options.xml.i> +#include <include/firewall/protocol.xml.i> +#include <include/firewall/nft-queue.xml.i> +#include <include/firewall/recent.xml.i> +#include <include/firewall/tcp-flags.xml.i> +#include <include/firewall/tcp-mss.xml.i> +#include <include/firewall/time.xml.i> +#include <include/firewall/hop-limit.xml.i> +<node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/firewall/address-mask-ipv6.xml.i> + #include <include/firewall/fqdn.xml.i> + #include <include/firewall/geoip.xml.i> + #include <include/firewall/mac-address.xml.i> + #include <include/firewall/port.xml.i> + #include <include/firewall/source-destination-group-ipv6.xml.i> + #include <include/firewall/source-destination-dynamic-group-ipv6.xml.i> + </children> +</node> +<node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/firewall/address-mask-ipv6.xml.i> + #include <include/firewall/fqdn.xml.i> + #include <include/firewall/geoip.xml.i> + #include <include/firewall/mac-address.xml.i> + #include <include/firewall/port.xml.i> + #include <include/firewall/source-destination-group-ipv6.xml.i> + #include <include/firewall/source-destination-dynamic-group-ipv6.xml.i> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/common-rule-ipv6.xml.i b/interface-definitions/include/firewall/common-rule-ipv6.xml.i new file mode 100644 index 0000000..bb176fe --- /dev/null +++ b/interface-definitions/include/firewall/common-rule-ipv6.xml.i @@ -0,0 +1,44 @@ +<!-- include start from firewall/common-rule-ipv6.xml.i --> +#include <include/firewall/add-addr-to-group-ipv6.xml.i> +#include <include/firewall/common-rule-inet.xml.i> +#include <include/firewall/hop-limit.xml.i> +#include <include/firewall/icmpv6.xml.i> +<node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/firewall/address-mask-ipv6.xml.i> + #include <include/firewall/fqdn.xml.i> + #include <include/firewall/geoip.xml.i> + #include <include/firewall/mac-address.xml.i> + #include <include/firewall/port.xml.i> + #include <include/firewall/source-destination-group-ipv6.xml.i> + #include <include/firewall/source-destination-dynamic-group-ipv6.xml.i> + </children> +</node> +<leafNode name="jump-target"> + <properties> + <help>Set jump target. Action jump must be defined to use this setting</help> + <completionHelp> + <path>firewall ipv6 name</path> + </completionHelp> + </properties> +</leafNode> +<node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/firewall/address-mask-ipv6.xml.i> + #include <include/firewall/fqdn.xml.i> + #include <include/firewall/geoip.xml.i> + #include <include/firewall/mac-address.xml.i> + #include <include/firewall/port.xml.i> + #include <include/firewall/source-destination-group-ipv6.xml.i> + #include <include/firewall/source-destination-dynamic-group-ipv6.xml.i> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/connection-mark.xml.i b/interface-definitions/include/firewall/connection-mark.xml.i new file mode 100644 index 0000000..69f7fe6 --- /dev/null +++ b/interface-definitions/include/firewall/connection-mark.xml.i @@ -0,0 +1,15 @@ +<!-- include start from firewall/connection-mark.xml.i --> +<leafNode name="connection-mark"> + <properties> + <help>Connection mark</help> + <valueHelp> + <format>u32:0-2147483647</format> + <description>Connection-mark to match</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2147483647"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/connection-status.xml.i b/interface-definitions/include/firewall/connection-status.xml.i new file mode 100644 index 0000000..5236c2f --- /dev/null +++ b/interface-definitions/include/firewall/connection-status.xml.i @@ -0,0 +1,28 @@ +<!-- include start from firewall/connection-status.xml.i --> +<node name="connection-status"> + <properties> + <help>Connection status</help> + </properties> + <children> + <leafNode name="nat"> + <properties> + <help>NAT connection status</help> + <completionHelp> + <list>destination source</list> + </completionHelp> + <valueHelp> + <format>destination</format> + <description>Match connections that are subject to destination NAT</description> + </valueHelp> + <valueHelp> + <format>source</format> + <description>Match connections that are subject to source NAT</description> + </valueHelp> + <constraint> + <regex>(destination|source)</regex> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/conntrack-helper.xml.i b/interface-definitions/include/firewall/conntrack-helper.xml.i new file mode 100644 index 0000000..3ca1a03 --- /dev/null +++ b/interface-definitions/include/firewall/conntrack-helper.xml.i @@ -0,0 +1,46 @@ +<!-- include start from firewall/conntrack-helper.xml.i --> +<leafNode name="conntrack-helper"> + <properties> + <help>Match related traffic from conntrack helpers</help> + <completionHelp> + <list>ftp h323 pptp nfs sip tftp sqlnet</list> + </completionHelp> + <valueHelp> + <format>ftp</format> + <description>Related traffic from FTP helper</description> + </valueHelp> + <valueHelp> + <format>h323</format> + <description>Related traffic from H.323 helper</description> + </valueHelp> + <valueHelp> + <format>pptp</format> + <description>Related traffic from PPTP helper</description> + </valueHelp> + <valueHelp> + <format>nfs</format> + <description>Related traffic from NFS helper</description> + </valueHelp> + <valueHelp> + <format>rtsp</format> + <description>Related traffic from RTSP helper</description> + </valueHelp> + <valueHelp> + <format>sip</format> + <description>Related traffic from SIP helper</description> + </valueHelp> + <valueHelp> + <format>tftp</format> + <description>Related traffic from TFTP helper</description> + </valueHelp> + <valueHelp> + <format>sqlnet</format> + <description>Related traffic from SQLNet helper</description> + </valueHelp> + <constraint> + <regex>(ftp|h323|pptp|nfs|rtsp|sip|tftp|sqlnet)</regex> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/default-action-base-chains.xml.i b/interface-definitions/include/firewall/default-action-base-chains.xml.i new file mode 100644 index 0000000..aa62abf --- /dev/null +++ b/interface-definitions/include/firewall/default-action-base-chains.xml.i @@ -0,0 +1,22 @@ +<!-- include start from firewall/default-action-base-chains.xml.i --> +<leafNode name="default-action"> + <properties> + <help>Default-action for rule-set</help> + <completionHelp> + <list>drop accept</list> + </completionHelp> + <valueHelp> + <format>drop</format> + <description>Drop if no prior rules are hit</description> + </valueHelp> + <valueHelp> + <format>accept</format> + <description>Accept if no prior rules are hit</description> + </valueHelp> + <constraint> + <regex>(drop|accept)</regex> + </constraint> + </properties> + <defaultValue>accept</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/default-action-bridge.xml.i b/interface-definitions/include/firewall/default-action-bridge.xml.i new file mode 100644 index 0000000..858c7ae --- /dev/null +++ b/interface-definitions/include/firewall/default-action-bridge.xml.i @@ -0,0 +1,34 @@ +<!-- include start from firewall/default-action.xml.i --> +<leafNode name="default-action"> + <properties> + <help>Default-action for rule-set</help> + <completionHelp> + <list>drop jump return accept continue</list> + </completionHelp> + <valueHelp> + <format>drop</format> + <description>Drop if no prior rules are hit</description> + </valueHelp> + <valueHelp> + <format>jump</format> + <description>Jump to another chain if no prior rules are hit</description> + </valueHelp> + <valueHelp> + <format>return</format> + <description>Return from the current chain and continue at the next rule of the last chain</description> + </valueHelp> + <valueHelp> + <format>accept</format> + <description>Accept if no prior rules are hit</description> + </valueHelp> + <valueHelp> + <format>continue</format> + <description>Continue parsing next rule</description> + </valueHelp> + <constraint> + <regex>(drop|jump|return|accept|continue)</regex> + </constraint> + </properties> + <defaultValue>drop</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/default-action.xml.i b/interface-definitions/include/firewall/default-action.xml.i new file mode 100644 index 0000000..53a1614 --- /dev/null +++ b/interface-definitions/include/firewall/default-action.xml.i @@ -0,0 +1,38 @@ +<!-- include start from firewall/default-action.xml.i --> +<leafNode name="default-action"> + <properties> + <help>Default-action for rule-set</help> + <completionHelp> + <list>drop jump reject return accept continue</list> + </completionHelp> + <valueHelp> + <format>drop</format> + <description>Drop if no prior rules are hit</description> + </valueHelp> + <valueHelp> + <format>jump</format> + <description>Jump to another chain if no prior rules are hit</description> + </valueHelp> + <valueHelp> + <format>reject</format> + <description>Drop and notify source if no prior rules are hit</description> + </valueHelp> + <valueHelp> + <format>return</format> + <description>Return from the current chain and continue at the next rule of the last chain</description> + </valueHelp> + <valueHelp> + <format>accept</format> + <description>Accept if no prior rules are hit</description> + </valueHelp> + <valueHelp> + <format>continue</format> + <description>Continue parsing next rule</description> + </valueHelp> + <constraint> + <regex>(drop|jump|reject|return|accept|continue)</regex> + </constraint> + </properties> + <defaultValue>drop</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/default-log.xml.i b/interface-definitions/include/firewall/default-log.xml.i new file mode 100644 index 0000000..dceacdb --- /dev/null +++ b/interface-definitions/include/firewall/default-log.xml.i @@ -0,0 +1,8 @@ +<!-- include start from firewall/default-log.xml.i --> +<leafNode name="default-log"> + <properties> + <help>Log packets hitting default-action</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/dscp.xml.i b/interface-definitions/include/firewall/dscp.xml.i new file mode 100644 index 0000000..dd4da48 --- /dev/null +++ b/interface-definitions/include/firewall/dscp.xml.i @@ -0,0 +1,36 @@ +<!-- include start from firewall/dscp.xml.i --> +<leafNode name="dscp"> + <properties> + <help>DSCP value</help> + <valueHelp> + <format>u32:0-63</format> + <description>DSCP value to match</description> + </valueHelp> + <valueHelp> + <format><start-end></format> + <description>DSCP range to match</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 0-63"/> + </constraint> + <multi/> + </properties> +</leafNode> +<leafNode name="dscp-exclude"> + <properties> + <help>DSCP value not to match</help> + <valueHelp> + <format>u32:0-63</format> + <description>DSCP value not to match</description> + </valueHelp> + <valueHelp> + <format><start-end></format> + <description>DSCP range not to match</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 0-63"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/eq.xml.i b/interface-definitions/include/firewall/eq.xml.i new file mode 100644 index 0000000..e1b4f37 --- /dev/null +++ b/interface-definitions/include/firewall/eq.xml.i @@ -0,0 +1,14 @@ +<!-- include start from firewall/eq.xml.i --> +<leafNode name="eq"> + <properties> + <help>Match on equal value</help> + <valueHelp> + <format>u32:0-255</format> + <description>Equal to value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/firewall-hashing-parameters.xml.i b/interface-definitions/include/firewall/firewall-hashing-parameters.xml.i new file mode 100644 index 0000000..7f34de3 --- /dev/null +++ b/interface-definitions/include/firewall/firewall-hashing-parameters.xml.i @@ -0,0 +1,35 @@ +<!-- include start from firewall/firewall-hashing-parameters.xml.i --> +<leafNode name="hash"> + <properties> + <help>Define the parameters of the packet header to apply the hashing</help> + <completionHelp> + <list>source-address destination-address source-port destination-port random</list> + </completionHelp> + <valueHelp> + <format>source-address</format> + <description>Use source IP address for hashing</description> + </valueHelp> + <valueHelp> + <format>destination-address</format> + <description>Use destination IP address for hashing</description> + </valueHelp> + <valueHelp> + <format>source-port</format> + <description>Use source port for hashing</description> + </valueHelp> + <valueHelp> + <format>destination-port</format> + <description>Use destination port for hashing</description> + </valueHelp> + <valueHelp> + <format>random</format> + <description>Do not use information from ip header. Use random value.</description> + </valueHelp> + <constraint> + <regex>(source-address|destination-address|source-port|destination-port|random)</regex> + </constraint> + <multi/> + </properties> + <defaultValue>random</defaultValue> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/firewall-mark.xml.i b/interface-definitions/include/firewall/firewall-mark.xml.i new file mode 100644 index 0000000..36a939b --- /dev/null +++ b/interface-definitions/include/firewall/firewall-mark.xml.i @@ -0,0 +1,26 @@ +<!-- include start from firewall/firewall-mark.xml.i --> +<leafNode name="mark"> + <properties> + <help>Firewall mark</help> + <valueHelp> + <format>u32:0-2147483647</format> + <description>Firewall mark to match</description> + </valueHelp> + <valueHelp> + <format>!u32:0-2147483647</format> + <description>Inverted Firewall mark to match</description> + </valueHelp> + <valueHelp> + <format><start-end></format> + <description>Firewall mark range to match</description> + </valueHelp> + <valueHelp> + <format>!<start-end></format> + <description>Firewall mark inverted range to match</description> + </valueHelp> + <constraint> + <validator name="numeric-exclude" argument="--allow-range --range 0-2147483647"/> + </constraint> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/fqdn.xml.i b/interface-definitions/include/firewall/fqdn.xml.i new file mode 100644 index 0000000..9eb3925 --- /dev/null +++ b/interface-definitions/include/firewall/fqdn.xml.i @@ -0,0 +1,14 @@ +<!-- include start from firewall/fqdn.xml.i --> +<leafNode name="fqdn"> + <properties> + <help>Fully qualified domain name</help> + <valueHelp> + <format><fqdn></format> + <description>Fully qualified domain name</description> + </valueHelp> + <constraint> + <validator name="fqdn"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/fragment.xml.i b/interface-definitions/include/firewall/fragment.xml.i new file mode 100644 index 0000000..1f4c110 --- /dev/null +++ b/interface-definitions/include/firewall/fragment.xml.i @@ -0,0 +1,21 @@ +<!-- include start from firewall/fragment.xml.i --> +<node name="fragment"> + <properties> + <help>IP fragment match</help> + </properties> + <children> + <leafNode name="match-frag"> + <properties> + <help>Second and further fragments of fragmented packets</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="match-non-frag"> + <properties> + <help>Head fragments or unfragmented packets</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/fwmark.xml.i b/interface-definitions/include/firewall/fwmark.xml.i new file mode 100644 index 0000000..4607ef5 --- /dev/null +++ b/interface-definitions/include/firewall/fwmark.xml.i @@ -0,0 +1,14 @@ +<!-- include start from firewall/fwmark.xml.i --> +<leafNode name="fwmark"> + <properties> + <help>Match fwmark value</help> + <valueHelp> + <format>u32:1-2147483647</format> + <description>Match firewall mark value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2147483647"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/geoip.xml.i b/interface-definitions/include/firewall/geoip.xml.i new file mode 100644 index 0000000..9fb37a5 --- /dev/null +++ b/interface-definitions/include/firewall/geoip.xml.i @@ -0,0 +1,28 @@ +<!-- include start from firewall/geoip.xml.i --> +<node name="geoip"> + <properties> + <help>GeoIP options - Data provided by DB-IP.com</help> + </properties> + <children> + <leafNode name="country-code"> + <properties> + <help>GeoIP country code</help> + <valueHelp> + <format><country></format> + <description>Country code (2 characters)</description> + </valueHelp> + <constraint> + <regex>^(ad|ae|af|ag|ai|al|am|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bl|bm|bn|bo|bq|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mf|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|ss|st|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tr|tt|tv|tw|tz|ua|ug|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)$</regex> + </constraint> + <multi /> + </properties> + </leafNode> + <leafNode name="inverse-match"> + <properties> + <help>Inverse match of country-codes</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i new file mode 100644 index 0000000..05fdd75 --- /dev/null +++ b/interface-definitions/include/firewall/global-options.xml.i @@ -0,0 +1,366 @@ +<!-- include start from firewall/global-options.xml.i --> +<node name="global-options"> + <properties> + <help>Global Options</help> + </properties> + <children> + <leafNode name="all-ping"> + <properties> + <help>Policy for handling of all IPv4 ICMP echo requests</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable processing of all IPv4 ICMP echo requests</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable processing of all IPv4 ICMP echo requests</description> + </valueHelp> + <constraint> + <regex>(enable|disable)</regex> + </constraint> + </properties> + <defaultValue>enable</defaultValue> + </leafNode> + <leafNode name="broadcast-ping"> + <properties> + <help>Policy for handling broadcast IPv4 ICMP echo and timestamp requests</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable processing of broadcast IPv4 ICMP echo/timestamp requests</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable processing of broadcast IPv4 ICMP echo/timestamp requests</description> + </valueHelp> + <constraint> + <regex>(enable|disable)</regex> + </constraint> + </properties> + <defaultValue>disable</defaultValue> + </leafNode> + <node name="apply-to-bridged-traffic"> + <properties> + <help>Apply configured firewall rules to traffic switched by bridges</help> + </properties> + <children> + <leafNode name="invalid-connections"> + <properties> + <help>Accept ARP and DHCP despite they are marked as invalid connection</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ipv4"> + <properties> + <help>Apply configured IPv4 firewall rules</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ipv6"> + <properties> + <help>Apply configured IPv6 firewall rules</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="directed-broadcast"> + <properties> + <help>Policy for handling IPv4 directed broadcast forwarding on all interfaces</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable IPv4 directed broadcast forwarding on all interfaces</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable IPv4 directed broadcast forwarding on all interfaces</description> + </valueHelp> + <constraint> + <regex>(enable|disable)</regex> + </constraint> + </properties> + <defaultValue>enable</defaultValue> + </leafNode> + <leafNode name="ip-src-route"> + <properties> + <help>Policy for handling IPv4 packets with source route option</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable processing of IPv4 packets with source route option</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable processing of IPv4 packets with source route option</description> + </valueHelp> + <constraint> + <regex>(enable|disable)</regex> + </constraint> + </properties> + <defaultValue>disable</defaultValue> + </leafNode> + <leafNode name="log-martians"> + <properties> + <help>Policy for logging IPv4 packets with invalid addresses</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable logging of IPv4 packets with invalid addresses</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable logging of Ipv4 packets with invalid addresses</description> + </valueHelp> + <constraint> + <regex>(enable|disable)</regex> + </constraint> + </properties> + <defaultValue>enable</defaultValue> + </leafNode> + <leafNode name="receive-redirects"> + <properties> + <help>Policy for handling received IPv4 ICMP redirect messages</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable processing of received IPv4 ICMP redirect messages</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable processing of received IPv4 ICMP redirect messages</description> + </valueHelp> + <constraint> + <regex>(enable|disable)</regex> + </constraint> + </properties> + <defaultValue>disable</defaultValue> + </leafNode> + <leafNode name="resolver-cache"> + <properties> + <help>Retains last successful value if domain resolution fails</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="resolver-interval"> + <properties> + <help>Domain resolver update interval</help> + <valueHelp> + <format>u32:10-3600</format> + <description>Interval (seconds)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 10-3600"/> + </constraint> + </properties> + <defaultValue>300</defaultValue> + </leafNode> + <leafNode name="send-redirects"> + <properties> + <help>Policy for sending IPv4 ICMP redirect messages</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable sending IPv4 ICMP redirect messages</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable sending IPv4 ICMP redirect messages</description> + </valueHelp> + <constraint> + <regex>(enable|disable)</regex> + </constraint> + </properties> + <defaultValue>enable</defaultValue> + </leafNode> + <leafNode name="source-validation"> + <properties> + <help>Policy for IPv4 source validation by reversed path, as specified in RFC3704</help> + <completionHelp> + <list>strict loose disable</list> + </completionHelp> + <valueHelp> + <format>strict</format> + <description>Enable IPv4 Strict Reverse Path Forwarding as defined in RFC3704</description> + </valueHelp> + <valueHelp> + <format>loose</format> + <description>Enable IPv4 Loose Reverse Path Forwarding as defined in RFC3704</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>No IPv4 source validation</description> + </valueHelp> + <constraint> + <regex>(strict|loose|disable)</regex> + </constraint> + </properties> + <defaultValue>disable</defaultValue> + </leafNode> + <node name="state-policy"> + <properties> + <help>Global firewall state-policy</help> + </properties> + <children> + <node name="established"> + <properties> + <help>Global firewall policy for packets part of an established connection</help> + </properties> + <children> + #include <include/firewall/action-accept-drop-reject.xml.i> + #include <include/firewall/log.xml.i> + #include <include/firewall/rule-log-level.xml.i> + </children> + </node> + <node name="invalid"> + <properties> + <help>Global firewall policy for packets part of an invalid connection</help> + </properties> + <children> + #include <include/firewall/action-accept-drop-reject.xml.i> + #include <include/firewall/log.xml.i> + #include <include/firewall/rule-log-level.xml.i> + </children> + </node> + <node name="related"> + <properties> + <help>Global firewall policy for packets part of a related connection</help> + </properties> + <children> + #include <include/firewall/action-accept-drop-reject.xml.i> + #include <include/firewall/log.xml.i> + #include <include/firewall/rule-log-level.xml.i> + </children> + </node> + </children> + </node> + <leafNode name="syn-cookies"> + <properties> + <help>Policy for using TCP SYN cookies with IPv4</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable use of TCP SYN cookies with IPv4</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable use of TCP SYN cookies with IPv4</description> + </valueHelp> + <constraint> + <regex>(enable|disable)</regex> + </constraint> + </properties> + <defaultValue>enable</defaultValue> + </leafNode> + <node name="timeout"> + <properties> + <help>Connection timeout options</help> + </properties> + <children> + #include <include/firewall/timeout-common-protocols.xml.i> + </children> + </node> + <leafNode name="twa-hazards-protection"> + <properties> + <help>RFC1337 TCP TIME-WAIT assasination hazards protection</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable RFC1337 TIME-WAIT hazards protection</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable RFC1337 TIME-WAIT hazards protection</description> + </valueHelp> + <constraint> + <regex>(enable|disable)</regex> + </constraint> + </properties> + <defaultValue>disable</defaultValue> + </leafNode> + <leafNode name="ipv6-receive-redirects"> + <properties> + <help>Policy for handling received ICMPv6 redirect messages</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable processing of received ICMPv6 redirect messages</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable processing of received ICMPv6 redirect messages</description> + </valueHelp> + <constraint> + <regex>(enable|disable)</regex> + </constraint> + </properties> + <defaultValue>disable</defaultValue> + </leafNode> + <leafNode name="ipv6-source-validation"> + <properties> + <help>Policy for IPv6 source validation by reversed path, as specified in RFC3704</help> + <completionHelp> + <list>strict loose disable</list> + </completionHelp> + <valueHelp> + <format>strict</format> + <description>Enable IPv6 Strict Reverse Path Forwarding as defined in RFC3704</description> + </valueHelp> + <valueHelp> + <format>loose</format> + <description>Enable IPv6 Loose Reverse Path Forwarding as defined in RFC3704</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>No IPv6 source validation</description> + </valueHelp> + <constraint> + <regex>(strict|loose|disable)</regex> + </constraint> + </properties> + <defaultValue>disable</defaultValue> + </leafNode> + <leafNode name="ipv6-src-route"> + <properties> + <help>Policy for handling IPv6 packets with routing extension header</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable processing of IPv6 packets with routing header type 2</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable processing of IPv6 packets with routing header</description> + </valueHelp> + <constraint> + <regex>(enable|disable)</regex> + </constraint> + </properties> + <defaultValue>disable</defaultValue> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/gre.xml.i b/interface-definitions/include/firewall/gre.xml.i new file mode 100644 index 0000000..e7b9fd5 --- /dev/null +++ b/interface-definitions/include/firewall/gre.xml.i @@ -0,0 +1,116 @@ +<!-- include start from firewall/gre.xml.i --> +<node name="gre"> + <properties> + <help>GRE fields to match</help> + </properties> + <children> + <node name="flags"> + <properties> + <help>GRE flag bits to match</help> + </properties> + <children> + <node name="key"> + <properties> + <help>Header includes optional key field</help> + </properties> + <children> + <leafNode name="unset"> + <properties> + <help>Header does not include optional key field</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="checksum"> + <properties> + <help>Header includes optional checksum</help> + </properties> + <children> + <leafNode name="unset"> + <properties> + <help>Header does not include optional checksum</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="sequence"> + <properties> + <help>Header includes a sequence number field</help> + </properties> + <children> + <leafNode name="unset"> + <properties> + <help>Header does not include a sequence number field</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="inner-proto"> + <properties> + <help>EtherType of encapsulated packet</help> + <completionHelp> + <list>ip ip6 arp 802.1q 802.1ad</list> + </completionHelp> + <valueHelp> + <format>u32:0-65535</format> + <description>Ethernet protocol number</description> + </valueHelp> + <valueHelp> + <format>u32:0x0-0xffff</format> + <description>Ethernet protocol number (hex)</description> + </valueHelp> + <valueHelp> + <format>ip</format> + <description>IPv4</description> + </valueHelp> + <valueHelp> + <format>ip6</format> + <description>IPv6</description> + </valueHelp> + <valueHelp> + <format>arp</format> + <description>Address Resolution Protocol</description> + </valueHelp> + <valueHelp> + <format>802.1q</format> + <description>VLAN-tagged frames (IEEE 802.1q)</description> + </valueHelp> + <valueHelp> + <format>802.1ad</format> + <description>Provider Bridging (IEEE 802.1ad, Q-in-Q)</description> + </valueHelp> + <valueHelp> + <format>gretap</format> + <description>Transparent Ethernet Bridging (L2 Ethernet over GRE, gretap)</description> + </valueHelp> + <constraint> + <regex>(ip|ip6|arp|802.1q|802.1ad|gretap|0x[0-9a-fA-F]{1,4})</regex> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + </leafNode> + #include <include/interface/parameters-key.xml.i> + <leafNode name="version"> + <properties> + <help>GRE Version</help> + <valueHelp> + <format>gre</format> + <description>Standard GRE</description> + </valueHelp> + <valueHelp> + <format>pptp</format> + <description>Point to Point Tunnelling Protocol</description> + </valueHelp> + <constraint> + <regex>(gre|pptp)</regex> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/gt.xml.i b/interface-definitions/include/firewall/gt.xml.i new file mode 100644 index 0000000..c879171 --- /dev/null +++ b/interface-definitions/include/firewall/gt.xml.i @@ -0,0 +1,14 @@ +<!-- include start from firewall/gt.xml.i --> +<leafNode name="gt"> + <properties> + <help>Match on greater then value</help> + <valueHelp> + <format>u32:0-255</format> + <description>Greater then value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/hop-limit.xml.i b/interface-definitions/include/firewall/hop-limit.xml.i new file mode 100644 index 0000000..d375dc9 --- /dev/null +++ b/interface-definitions/include/firewall/hop-limit.xml.i @@ -0,0 +1,12 @@ +<!-- include start from firewall/hop-limit.xml.i --> +<node name="hop-limit"> + <properties> + <help>Hop limit</help> + </properties> + <children> + #include <include/firewall/eq.xml.i> + #include <include/firewall/gt.xml.i> + #include <include/firewall/lt.xml.i> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/icmp-type-name.xml.i b/interface-definitions/include/firewall/icmp-type-name.xml.i new file mode 100644 index 0000000..d4197cf --- /dev/null +++ b/interface-definitions/include/firewall/icmp-type-name.xml.i @@ -0,0 +1,73 @@ +<!-- include start from firewall/icmp-type-name.xml.i --> +<leafNode name="type-name"> + <properties> + <help>ICMP type-name</help> + <completionHelp> + <list>echo-reply destination-unreachable source-quench redirect echo-request router-advertisement router-solicitation time-exceeded parameter-problem timestamp-request timestamp-reply info-request info-reply address-mask-request address-mask-reply</list> + </completionHelp> + <valueHelp> + <format>echo-reply</format> + <description>ICMP type 0: echo-reply</description> + </valueHelp> + <valueHelp> + <format>destination-unreachable</format> + <description>ICMP type 3: destination-unreachable</description> + </valueHelp> + <valueHelp> + <format>source-quench</format> + <description>ICMP type 4: source-quench</description> + </valueHelp> + <valueHelp> + <format>redirect</format> + <description>ICMP type 5: redirect</description> + </valueHelp> + <valueHelp> + <format>echo-request</format> + <description>ICMP type 8: echo-request</description> + </valueHelp> + <valueHelp> + <format>router-advertisement</format> + <description>ICMP type 9: router-advertisement</description> + </valueHelp> + <valueHelp> + <format>router-solicitation</format> + <description>ICMP type 10: router-solicitation</description> + </valueHelp> + <valueHelp> + <format>time-exceeded</format> + <description>ICMP type 11: time-exceeded</description> + </valueHelp> + <valueHelp> + <format>parameter-problem</format> + <description>ICMP type 12: parameter-problem</description> + </valueHelp> + <valueHelp> + <format>timestamp-request</format> + <description>ICMP type 13: timestamp-request</description> + </valueHelp> + <valueHelp> + <format>timestamp-reply</format> + <description>ICMP type 14: timestamp-reply</description> + </valueHelp> + <valueHelp> + <format>info-request</format> + <description>ICMP type 15: info-request</description> + </valueHelp> + <valueHelp> + <format>info-reply</format> + <description>ICMP type 16: info-reply</description> + </valueHelp> + <valueHelp> + <format>address-mask-request</format> + <description>ICMP type 17: address-mask-request</description> + </valueHelp> + <valueHelp> + <format>address-mask-reply</format> + <description>ICMP type 18: address-mask-reply</description> + </valueHelp> + <constraint> + <regex>(echo-reply|destination-unreachable|source-quench|redirect|echo-request|router-advertisement|router-solicitation|time-exceeded|parameter-problem|timestamp-request|timestamp-reply|info-request|info-reply|address-mask-request|address-mask-reply)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/icmp.xml.i b/interface-definitions/include/firewall/icmp.xml.i new file mode 100644 index 0000000..deb50a4 --- /dev/null +++ b/interface-definitions/include/firewall/icmp.xml.i @@ -0,0 +1,34 @@ +<!-- include start from firewall/icmp.xml.i --> +<node name="icmp"> + <properties> + <help>ICMP type and code information</help> + </properties> + <children> + <leafNode name="code"> + <properties> + <help>ICMP code</help> + <valueHelp> + <format>u32:0-255</format> + <description>ICMP code (0-255)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="type"> + <properties> + <help>ICMP type</help> + <valueHelp> + <format>u32:0-255</format> + <description>ICMP type (0-255)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> + </leafNode> + #include <include/firewall/icmp-type-name.xml.i> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/icmpv6-type-name.xml.i b/interface-definitions/include/firewall/icmpv6-type-name.xml.i new file mode 100644 index 0000000..e17a20e --- /dev/null +++ b/interface-definitions/include/firewall/icmpv6-type-name.xml.i @@ -0,0 +1,85 @@ +<!-- include start from firewall/icmpv6-type-name.xml.i --> +<leafNode name="type-name"> + <properties> + <help>ICMPv6 type-name</help> + <completionHelp> + <list>destination-unreachable packet-too-big time-exceeded echo-request echo-reply mld-listener-query mld-listener-report mld-listener-reduction nd-router-solicit nd-router-advert nd-neighbor-solicit nd-neighbor-advert nd-redirect parameter-problem router-renumbering ind-neighbor-solicit ind-neighbor-advert mld2-listener-report</list> + </completionHelp> + <valueHelp> + <format>destination-unreachable</format> + <description>ICMPv6 type 1: destination-unreachable</description> + </valueHelp> + <valueHelp> + <format>packet-too-big</format> + <description>ICMPv6 type 2: packet-too-big</description> + </valueHelp> + <valueHelp> + <format>time-exceeded</format> + <description>ICMPv6 type 3: time-exceeded</description> + </valueHelp> + <valueHelp> + <format>echo-request</format> + <description>ICMPv6 type 128: echo-request</description> + </valueHelp> + <valueHelp> + <format>echo-reply</format> + <description>ICMPv6 type 129: echo-reply</description> + </valueHelp> + <valueHelp> + <format>mld-listener-query</format> + <description>ICMPv6 type 130: mld-listener-query</description> + </valueHelp> + <valueHelp> + <format>mld-listener-report</format> + <description>ICMPv6 type 131: mld-listener-report</description> + </valueHelp> + <valueHelp> + <format>mld-listener-reduction</format> + <description>ICMPv6 type 132: mld-listener-reduction</description> + </valueHelp> + <valueHelp> + <format>nd-router-solicit</format> + <description>ICMPv6 type 133: nd-router-solicit</description> + </valueHelp> + <valueHelp> + <format>nd-router-advert</format> + <description>ICMPv6 type 134: nd-router-advert</description> + </valueHelp> + <valueHelp> + <format>nd-neighbor-solicit</format> + <description>ICMPv6 type 135: nd-neighbor-solicit</description> + </valueHelp> + <valueHelp> + <format>nd-neighbor-advert</format> + <description>ICMPv6 type 136: nd-neighbor-advert</description> + </valueHelp> + <valueHelp> + <format>nd-redirect</format> + <description>ICMPv6 type 137: nd-redirect</description> + </valueHelp> + <valueHelp> + <format>parameter-problem</format> + <description>ICMPv6 type 4: parameter-problem</description> + </valueHelp> + <valueHelp> + <format>router-renumbering</format> + <description>ICMPv6 type 138: router-renumbering</description> + </valueHelp> + <valueHelp> + <format>ind-neighbor-solicit</format> + <description>ICMPv6 type 141: ind-neighbor-solicit</description> + </valueHelp> + <valueHelp> + <format>ind-neighbor-advert</format> + <description>ICMPv6 type 142: ind-neighbor-advert</description> + </valueHelp> + <valueHelp> + <format>mld2-listener-report</format> + <description>ICMPv6 type 143: mld2-listener-report</description> + </valueHelp> + <constraint> + <regex>(destination-unreachable|packet-too-big|time-exceeded|echo-request|echo-reply|mld-listener-query|mld-listener-report|mld-listener-reduction|nd-router-solicit|nd-router-advert|nd-neighbor-solicit|nd-neighbor-advert|nd-redirect|parameter-problem|router-renumbering|ind-neighbor-solicit|ind-neighbor-advert|mld2-listener-report)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/icmpv6.xml.i b/interface-definitions/include/firewall/icmpv6.xml.i new file mode 100644 index 0000000..c011862 --- /dev/null +++ b/interface-definitions/include/firewall/icmpv6.xml.i @@ -0,0 +1,34 @@ +<!-- include start from firewall/icmpv6.xml.i --> +<node name="icmpv6"> + <properties> + <help>ICMPv6 type and code information</help> + </properties> + <children> + <leafNode name="code"> + <properties> + <help>ICMPv6 code</help> + <valueHelp> + <format>u32:0-255</format> + <description>ICMPv6 code (0-255)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="type"> + <properties> + <help>ICMPv6 type</help> + <valueHelp> + <format>u32:0-255</format> + <description>ICMPv6 type (0-255)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> + </leafNode> + #include <include/firewall/icmpv6-type-name.xml.i> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/inbound-interface-no-group.xml.i b/interface-definitions/include/firewall/inbound-interface-no-group.xml.i new file mode 100644 index 0000000..bcd4c95 --- /dev/null +++ b/interface-definitions/include/firewall/inbound-interface-no-group.xml.i @@ -0,0 +1,34 @@ +<!-- include start from firewall/inbound-interface-no-group.xml.i --> +<node name="inbound-interface"> + <properties> + <help>Match inbound-interface</help> + </properties> + <children> + <leafNode name="name"> + <properties> + <help>Match interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + <path>vrf name</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <valueHelp> + <format>txt*</format> + <description>Interface name with wildcard</description> + </valueHelp> + <valueHelp> + <format>!txt</format> + <description>Inverted interface name to match</description> + </valueHelp> + <constraint> + <regex>(\!?)(bond|br|dum|en|ersp|eth|gnv|ifb|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)([0-9]?)(\*?)(.+)?|(\!?)lo</regex> + <validator name="vrf-name"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/inbound-interface.xml.i b/interface-definitions/include/firewall/inbound-interface.xml.i new file mode 100644 index 0000000..13df71d --- /dev/null +++ b/interface-definitions/include/firewall/inbound-interface.xml.i @@ -0,0 +1,10 @@ +<!-- include start from firewall/inbound-interface.xml.i --> +<node name="inbound-interface"> + <properties> + <help>Match inbound-interface</help> + </properties> + <children> + #include <include/firewall/match-interface.xml.i> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/ipv4-custom-name.xml.i b/interface-definitions/include/firewall/ipv4-custom-name.xml.i new file mode 100644 index 0000000..8046b2d --- /dev/null +++ b/interface-definitions/include/firewall/ipv4-custom-name.xml.i @@ -0,0 +1,43 @@ +<!-- include start from firewall/ipv4-custom-name.xml.i --> +<tagNode name="name"> + <properties> + <help>IPv4 custom firewall</help> + <constraint> + <regex>[a-zA-Z0-9][\w\-\.]*</regex> + </constraint> + </properties> + <children> + #include <include/firewall/default-action.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <leafNode name="default-jump-target"> + <properties> + <help>Set jump target. Action jump must be defined in default-action to use this setting</help> + <completionHelp> + <path>firewall ipv4 name</path> + </completionHelp> + </properties> + </leafNode> + <tagNode name="rule"> + <properties> + <help>IPv4 Firewall custom rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-ipv4.xml.i> + #include <include/firewall/inbound-interface.xml.i> + #include <include/firewall/match-ipsec.xml.i> + #include <include/firewall/offload-target.xml.i> + #include <include/firewall/outbound-interface.xml.i> + </children> + </tagNode> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/ipv4-hook-forward.xml.i b/interface-definitions/include/firewall/ipv4-hook-forward.xml.i new file mode 100644 index 0000000..b0e240a --- /dev/null +++ b/interface-definitions/include/firewall/ipv4-hook-forward.xml.i @@ -0,0 +1,40 @@ +<!-- include start from firewall/ipv4-hook-forward.xml.i --> +<node name="forward"> + <properties> + <help>IPv4 forward firewall</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>IPv4 firewall forward filter</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>IPv4 Firewall forward filter rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/action-forward.xml.i> + #include <include/firewall/common-rule-ipv4.xml.i> + #include <include/firewall/inbound-interface.xml.i> + #include <include/firewall/match-ipsec.xml.i> + #include <include/firewall/offload-target.xml.i> + #include <include/firewall/outbound-interface.xml.i> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/ipv4-hook-input.xml.i b/interface-definitions/include/firewall/ipv4-hook-input.xml.i new file mode 100644 index 0000000..491d1a9 --- /dev/null +++ b/interface-definitions/include/firewall/ipv4-hook-input.xml.i @@ -0,0 +1,37 @@ +<!-- include start from firewall/ipv4-hook-input.xml.i --> +<node name="input"> + <properties> + <help>IPv4 input firewall</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>IPv4 firewall input filter</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>IPv4 Firewall input filter rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-ipv4.xml.i> + #include <include/firewall/inbound-interface.xml.i> + #include <include/firewall/match-ipsec-in.xml.i> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/ipv4-hook-output.xml.i b/interface-definitions/include/firewall/ipv4-hook-output.xml.i new file mode 100644 index 0000000..ee91575 --- /dev/null +++ b/interface-definitions/include/firewall/ipv4-hook-output.xml.i @@ -0,0 +1,65 @@ +<!-- include start from firewall/ipv4-hook-output.xml.i --> +<node name="output"> + <properties> + <help>IPv4 output firewall</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>IPv4 firewall output filter</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>IPv4 Firewall output filter rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-ipv4.xml.i> + #include <include/firewall/match-ipsec-out.xml.i> + #include <include/firewall/outbound-interface.xml.i> + </children> + </tagNode> + </children> + </node> + <node name="raw"> + <properties> + <help>IPv4 firewall output raw</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>IPv4 Firewall output raw rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-ipv4-raw.xml.i> + #include <include/firewall/match-ipsec-out.xml.i> + #include <include/firewall/outbound-interface.xml.i> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/ipv4-hook-prerouting.xml.i b/interface-definitions/include/firewall/ipv4-hook-prerouting.xml.i new file mode 100644 index 0000000..b431303 --- /dev/null +++ b/interface-definitions/include/firewall/ipv4-hook-prerouting.xml.i @@ -0,0 +1,52 @@ +<!-- include start from firewall/ipv4-hook-prerouting.xml.i --> +<node name="prerouting"> + <properties> + <help>IPv4 prerouting firewall</help> + </properties> + <children> + <node name="raw"> + <properties> + <help>IPv4 firewall prerouting raw</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/generic-description.xml.i> + <leafNode name="default-jump-target"> + <properties> + <help>Set jump target. Action jump must be defined in default-action to use this setting</help> + <completionHelp> + <path>firewall ipv4 name</path> + </completionHelp> + </properties> + </leafNode> + <tagNode name="rule"> + <properties> + <help>IPv4 Firewall prerouting raw rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-ipv4-raw.xml.i> + #include <include/firewall/match-ipsec-in.xml.i> + #include <include/firewall/inbound-interface.xml.i> + <leafNode name="jump-target"> + <properties> + <help>Set jump target. Action jump must be defined to use this setting</help> + <completionHelp> + <path>firewall ipv4 name</path> + </completionHelp> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/ipv6-custom-name.xml.i b/interface-definitions/include/firewall/ipv6-custom-name.xml.i new file mode 100644 index 0000000..fb8740c --- /dev/null +++ b/interface-definitions/include/firewall/ipv6-custom-name.xml.i @@ -0,0 +1,43 @@ +<!-- include start from firewall/ipv6-custom-name.xml.i --> +<tagNode name="name"> + <properties> + <help>IPv6 custom firewall</help> + <constraint> + <regex>[a-zA-Z0-9][\w\-\.]*</regex> + </constraint> + </properties> + <children> + #include <include/firewall/default-action.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <leafNode name="default-jump-target"> + <properties> + <help>Set jump target. Action jump must be defined in default-action to use this setting</help> + <completionHelp> + <path>firewall ipv6 name</path> + </completionHelp> + </properties> + </leafNode> + <tagNode name="rule"> + <properties> + <help>IPv6 Firewall custom rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-ipv6.xml.i> + #include <include/firewall/inbound-interface.xml.i> + #include <include/firewall/match-ipsec.xml.i> + #include <include/firewall/offload-target.xml.i> + #include <include/firewall/outbound-interface.xml.i> + </children> + </tagNode> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/ipv6-hook-forward.xml.i b/interface-definitions/include/firewall/ipv6-hook-forward.xml.i new file mode 100644 index 0000000..7efc261 --- /dev/null +++ b/interface-definitions/include/firewall/ipv6-hook-forward.xml.i @@ -0,0 +1,40 @@ +<!-- include start from firewall/ipv6-hook-forward.xml.i --> +<node name="forward"> + <properties> + <help>IPv6 forward firewall</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>IPv6 firewall forward filter</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>IPv6 Firewall forward filter rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/action-forward.xml.i> + #include <include/firewall/common-rule-ipv6.xml.i> + #include <include/firewall/inbound-interface.xml.i> + #include <include/firewall/match-ipsec.xml.i> + #include <include/firewall/offload-target.xml.i> + #include <include/firewall/outbound-interface.xml.i> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/ipv6-hook-input.xml.i b/interface-definitions/include/firewall/ipv6-hook-input.xml.i new file mode 100644 index 0000000..154b102 --- /dev/null +++ b/interface-definitions/include/firewall/ipv6-hook-input.xml.i @@ -0,0 +1,37 @@ +<!-- include start from firewall/ipv6-hook-input.xml.i --> +<node name="input"> + <properties> + <help>IPv6 input firewall</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>IPv6 firewall input filter</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>IPv6 Firewall input filter rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-ipv6.xml.i> + #include <include/firewall/inbound-interface.xml.i> + #include <include/firewall/match-ipsec-in.xml.i> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/ipv6-hook-output.xml.i b/interface-definitions/include/firewall/ipv6-hook-output.xml.i new file mode 100644 index 0000000..d3c4c1e --- /dev/null +++ b/interface-definitions/include/firewall/ipv6-hook-output.xml.i @@ -0,0 +1,65 @@ +<!-- include start from firewall/ipv6-hook-output.xml.i --> +<node name="output"> + <properties> + <help>IPv6 output firewall</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>IPv6 firewall output filter</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>IPv6 Firewall output filter rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-ipv6.xml.i> + #include <include/firewall/match-ipsec-out.xml.i> + #include <include/firewall/outbound-interface.xml.i> + </children> + </tagNode> + </children> + </node> + <node name="raw"> + <properties> + <help>IPv6 firewall output raw</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>IPv6 Firewall output raw rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-ipv6-raw.xml.i> + #include <include/firewall/match-ipsec-out.xml.i> + #include <include/firewall/outbound-interface.xml.i> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/ipv6-hook-prerouting.xml.i b/interface-definitions/include/firewall/ipv6-hook-prerouting.xml.i new file mode 100644 index 0000000..21f8de6 --- /dev/null +++ b/interface-definitions/include/firewall/ipv6-hook-prerouting.xml.i @@ -0,0 +1,52 @@ +<!-- include start from firewall/ipv6-hook-prerouting.xml.i --> +<node name="prerouting"> + <properties> + <help>IPv6 prerouting firewall</help> + </properties> + <children> + <node name="raw"> + <properties> + <help>IPv6 firewall prerouting raw</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/generic-description.xml.i> + <leafNode name="default-jump-target"> + <properties> + <help>Set jump target. Action jump must be defined in default-action to use this setting</help> + <completionHelp> + <path>firewall ipv6 name</path> + </completionHelp> + </properties> + </leafNode> + <tagNode name="rule"> + <properties> + <help>IPv6 Firewall prerouting raw rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-ipv6-raw.xml.i> + #include <include/firewall/match-ipsec-in.xml.i> + #include <include/firewall/inbound-interface.xml.i> + <leafNode name="jump-target"> + <properties> + <help>Set jump target. Action jump must be defined to use this setting</help> + <completionHelp> + <path>firewall ipv6 name</path> + </completionHelp> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/limit.xml.i b/interface-definitions/include/firewall/limit.xml.i new file mode 100644 index 0000000..21068de --- /dev/null +++ b/interface-definitions/include/firewall/limit.xml.i @@ -0,0 +1,33 @@ +<!-- include start from firewall/limit.xml.i --> +<node name="limit"> + <properties> + <help>Rate limit using a token bucket filter</help> + </properties> + <children> + <leafNode name="burst"> + <properties> + <help>Maximum number of packets to allow in excess of rate</help> + <valueHelp> + <format>u32:0-4294967295</format> + <description>Maximum number of packets to allow in excess of rate</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="rate"> + <properties> + <help>Maximum average matching rate</help> + <valueHelp> + <format>txt</format> + <description>integer/unit (Example: 5/minute)</description> + </valueHelp> + <constraint> + <regex>\d+/(second|minute|hour|day)</regex> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/log-options.xml.i b/interface-definitions/include/firewall/log-options.xml.i new file mode 100644 index 0000000..e8b0cde --- /dev/null +++ b/interface-definitions/include/firewall/log-options.xml.i @@ -0,0 +1,89 @@ +<!-- include start from firewall/rule-log-options.xml.i --> +<node name="log-options"> + <properties> + <help>Log options</help> + </properties> + <children> + <leafNode name="group"> + <properties> + <help>Set log group</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Log group to send messages to</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="snapshot-length"> + <properties> + <help>Length of packet payload to include in netlink message</help> + <valueHelp> + <format>u32:0-9000</format> + <description>Length of packet payload to include in netlink message</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-9000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="queue-threshold"> + <properties> + <help>Number of packets to queue inside the kernel before sending them to userspace</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Number of packets to queue inside the kernel before sending them to userspace</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="level"> + <properties> + <help>Set log-level</help> + <completionHelp> + <list>emerg alert crit err warn notice info debug</list> + </completionHelp> + <valueHelp> + <format>emerg</format> + <description>Emerg log level</description> + </valueHelp> + <valueHelp> + <format>alert</format> + <description>Alert log level</description> + </valueHelp> + <valueHelp> + <format>crit</format> + <description>Critical log level</description> + </valueHelp> + <valueHelp> + <format>err</format> + <description>Error log level</description> + </valueHelp> + <valueHelp> + <format>warn</format> + <description>Warning log level</description> + </valueHelp> + <valueHelp> + <format>notice</format> + <description>Notice log level</description> + </valueHelp> + <valueHelp> + <format>info</format> + <description>Info log level</description> + </valueHelp> + <valueHelp> + <format>debug</format> + <description>Debug log level</description> + </valueHelp> + <constraint> + <regex>(emerg|alert|crit|err|warn|notice|info|debug)</regex> + </constraint> + <constraintErrorMessage>level must be alert, crit, debug, emerg, err, info, notice or warn</constraintErrorMessage> + </properties> + </leafNode> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/log.xml.i b/interface-definitions/include/firewall/log.xml.i new file mode 100644 index 0000000..21548f3 --- /dev/null +++ b/interface-definitions/include/firewall/log.xml.i @@ -0,0 +1,8 @@ +<!-- include start from firewall/log.xml.i --> +<leafNode name="log"> + <properties> + <help>Log packets hitting this rule</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/lt.xml.i b/interface-definitions/include/firewall/lt.xml.i new file mode 100644 index 0000000..77894d3 --- /dev/null +++ b/interface-definitions/include/firewall/lt.xml.i @@ -0,0 +1,14 @@ +<!-- include start from firewall/lt.xml.i --> +<leafNode name="lt"> + <properties> + <help>Match on less then value</help> + <valueHelp> + <format>u32:0-255</format> + <description>Less then value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/mac-address.xml.i b/interface-definitions/include/firewall/mac-address.xml.i new file mode 100644 index 0000000..db3e1e3 --- /dev/null +++ b/interface-definitions/include/firewall/mac-address.xml.i @@ -0,0 +1,19 @@ +<!-- include start from firewall/mac-address.xml.i --> +<leafNode name="mac-address"> + <properties> + <help>MAC address</help> + <valueHelp> + <format>macaddr</format> + <description>MAC address to match</description> + </valueHelp> + <valueHelp> + <format>!macaddr</format> + <description>Match everything except the specified MAC address</description> + </valueHelp> + <constraint> + <validator name="mac-address"/> + <validator name="mac-address-exclude"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/mac-group.xml.i b/interface-definitions/include/firewall/mac-group.xml.i new file mode 100644 index 0000000..dbce3fc --- /dev/null +++ b/interface-definitions/include/firewall/mac-group.xml.i @@ -0,0 +1,10 @@ +<!-- include start from firewall/mac-group.xml.i --> +<leafNode name="mac-group"> + <properties> + <help>Group of MAC addresses</help> + <completionHelp> + <path>firewall group mac-group</path> + </completionHelp> + </properties> +</leafNode> +<!-- include start from firewall/mac-group.xml.i -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/match-ether-type.xml.i b/interface-definitions/include/firewall/match-ether-type.xml.i new file mode 100644 index 0000000..abfa903 --- /dev/null +++ b/interface-definitions/include/firewall/match-ether-type.xml.i @@ -0,0 +1,30 @@ +<!-- include start from firewall/match-ether-type.xml.i --> +<leafNode name="ethernet-type"> + <properties> + <help>Ethernet type</help> + <completionHelp> + <list>802.1q 802.1ad arp ipv4 ipv6</list> + </completionHelp> + <valueHelp> + <format>802.1q</format> + <description>Customer VLAN tag type</description> + </valueHelp> + <valueHelp> + <format>802.1ad</format> + <description>Service VLAN tag type</description> + </valueHelp> + <valueHelp> + <format>arp</format> + <description>Adress Resolution Protocol</description> + </valueHelp> + <valueHelp> + <format>_ipv4</format> + <description>Internet Protocol version 4</description> + </valueHelp> + <valueHelp> + <format>_ipv6</format> + <description>Internet Protocol version 6</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/match-interface.xml.i b/interface-definitions/include/firewall/match-interface.xml.i new file mode 100644 index 0000000..f25686e --- /dev/null +++ b/interface-definitions/include/firewall/match-interface.xml.i @@ -0,0 +1,43 @@ +<!-- include start from firewall/match-interface.xml.i --> +<leafNode name="name"> + <properties> + <help>Match interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + <path>vrf name</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <valueHelp> + <format>txt*</format> + <description>Interface name with wildcard</description> + </valueHelp> + <valueHelp> + <format>!txt</format> + <description>Inverted interface name to match</description> + </valueHelp> + <constraint> + <regex>(\!?)(bond|br|dum|en|ersp|eth|gnv|ifb|ipoe|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)([0-9]?)(\*?)(.+)?|(\!?)lo</regex> + <validator name="vrf-name"/> + </constraint> + </properties> +</leafNode> +<leafNode name="group"> + <properties> + <help>Match interface-group</help> + <completionHelp> + <path>firewall group interface-group</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface-group name to match</description> + </valueHelp> + <valueHelp> + <format>!txt</format> + <description>Inverted interface-group name to match</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/match-ipsec-in.xml.i b/interface-definitions/include/firewall/match-ipsec-in.xml.i new file mode 100644 index 0000000..62ed646 --- /dev/null +++ b/interface-definitions/include/firewall/match-ipsec-in.xml.i @@ -0,0 +1,21 @@ +<!-- include start from firewall/match-ipsec-in.xml.i --> +<node name="ipsec"> + <properties> + <help>Inbound IPsec packets</help> + </properties> + <children> + <leafNode name="match-ipsec-in"> + <properties> + <help>Inbound traffic that was IPsec encapsulated</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="match-none-in"> + <properties> + <help>Inbound traffic that was not IPsec encapsulated</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/match-ipsec-out.xml.i b/interface-definitions/include/firewall/match-ipsec-out.xml.i new file mode 100644 index 0000000..880fdd4 --- /dev/null +++ b/interface-definitions/include/firewall/match-ipsec-out.xml.i @@ -0,0 +1,21 @@ +<!-- include start from firewall/match-ipsec-out.xml.i --> +<node name="ipsec"> + <properties> + <help>Outbound IPsec packets</help> + </properties> + <children> + <leafNode name="match-ipsec-out"> + <properties> + <help>Outbound traffic to be IPsec encapsulated</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="match-none-out"> + <properties> + <help>Outbound traffic that will not be IPsec encapsulated</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/match-ipsec.xml.i b/interface-definitions/include/firewall/match-ipsec.xml.i new file mode 100644 index 0000000..d8d31ef --- /dev/null +++ b/interface-definitions/include/firewall/match-ipsec.xml.i @@ -0,0 +1,33 @@ +<!-- include start from firewall/match-ipsec.xml.i --> +<node name="ipsec"> + <properties> + <help>IPsec encapsulated packets</help> + </properties> + <children> + <leafNode name="match-ipsec-in"> + <properties> + <help>Inbound traffic that was IPsec encapsulated</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="match-none-in"> + <properties> + <help>Inbound traffic that was not IPsec encapsulated</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="match-ipsec-out"> + <properties> + <help>Outbound traffic to be IPsec encapsulated</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="match-none-out"> + <properties> + <help>Outbound traffic that will not be IPsec encapsulated</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/match-vlan.xml.i b/interface-definitions/include/firewall/match-vlan.xml.i new file mode 100644 index 0000000..d58e843 --- /dev/null +++ b/interface-definitions/include/firewall/match-vlan.xml.i @@ -0,0 +1,42 @@ +<!-- include start from firewall/match-vlan.xml.i --> +<node name="vlan"> + <properties> + <help>VLAN parameters</help> + </properties> + <children> + <leafNode name="id"> + <properties> + <help>Vlan id</help> + <valueHelp> + <format>u32:0-4096</format> + <description>Vlan id</description> + </valueHelp> + <valueHelp> + <format><start-end></format> + <description>Vlan id range to match</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 0-4095"/> + </constraint> + </properties> + </leafNode> + <leafNode name="priority"> + <properties> + <help>Vlan priority(pcp)</help> + <valueHelp> + <format>u32:0-7</format> + <description>Vlan priority</description> + </valueHelp> + <valueHelp> + <format><start-end></format> + <description>Vlan priority range to match</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 0-7"/> + </constraint> + </properties> + </leafNode> + #include <include/firewall/match-ether-type.xml.i> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/name.xml.i b/interface-definitions/include/firewall/name.xml.i new file mode 100644 index 0000000..231b9b1 --- /dev/null +++ b/interface-definitions/include/firewall/name.xml.i @@ -0,0 +1,18 @@ +<!-- include start from firewall/name.xml.i --> +<leafNode name="name"> + <properties> + <help>Local IPv4 firewall ruleset name for interface</help> + <completionHelp> + <path>firewall name</path> + </completionHelp> + </properties> +</leafNode> +<leafNode name="ipv6-name"> + <properties> + <help>Local IPv6 firewall ruleset name for interface</help> + <completionHelp> + <path>firewall ipv6-name</path> + </completionHelp> + </properties> +</leafNode> +<!-- include end from firewall/name.xml.i -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/nat-balance.xml.i b/interface-definitions/include/firewall/nat-balance.xml.i new file mode 100644 index 0000000..01793f0 --- /dev/null +++ b/interface-definitions/include/firewall/nat-balance.xml.i @@ -0,0 +1,28 @@ +<!-- include start from firewall/nat-balance.xml.i --> +<tagNode name="backend"> + <properties> + <help>Translated IP address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <children> + <leafNode name="weight"> + <properties> + <help>Set probability for this output value</help> + <valueHelp> + <format>u32:1-100</format> + <description>Set probability for this output value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 1-100"/> + </constraint> + </properties> + </leafNode> + </children> +</tagNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/nft-queue.xml.i b/interface-definitions/include/firewall/nft-queue.xml.i new file mode 100644 index 0000000..8799eac --- /dev/null +++ b/interface-definitions/include/firewall/nft-queue.xml.i @@ -0,0 +1,34 @@ +<!-- include start from firewall/nft-queue.xml.i --> +<leafNode name="queue"> + <properties> + <help>Queue target to use. Action queue must be defined to use this setting</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Queue target</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 0-65535"/> + </constraint> + </properties> +</leafNode> +<leafNode name="queue-options"> + <properties> + <help>Options used for queue target. Action queue must be defined to use this setting</help> + <completionHelp> + <list>bypass fanout</list> + </completionHelp> + <valueHelp> + <format>bypass</format> + <description>Let packets go through if userspace application cannot back off</description> + </valueHelp> + <valueHelp> + <format>fanout</format> + <description>Distribute packets between several queues</description> + </valueHelp> + <constraint> + <regex>(bypass|fanout)</regex> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/offload-target.xml.i b/interface-definitions/include/firewall/offload-target.xml.i new file mode 100644 index 0000000..940ed80 --- /dev/null +++ b/interface-definitions/include/firewall/offload-target.xml.i @@ -0,0 +1,10 @@ +<!-- include start from firewall/offload-target.xml.i --> +<leafNode name="offload-target"> + <properties> + <help>Set flowtable offload target. Action offload must be defined to use this setting</help> + <completionHelp> + <path>firewall flowtable</path> + </completionHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/outbound-interface-no-group.xml.i b/interface-definitions/include/firewall/outbound-interface-no-group.xml.i new file mode 100644 index 0000000..e3bace4 --- /dev/null +++ b/interface-definitions/include/firewall/outbound-interface-no-group.xml.i @@ -0,0 +1,34 @@ +<!-- include start from firewall/outbound-interface-no-group.xml.i --> +<node name="outbound-interface"> + <properties> + <help>Match outbound-interface</help> + </properties> + <children> + <leafNode name="name"> + <properties> + <help>Match interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + <path>vrf name</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <valueHelp> + <format>txt*</format> + <description>Interface name with wildcard</description> + </valueHelp> + <valueHelp> + <format>!txt</format> + <description>Inverted interface name to match</description> + </valueHelp> + <constraint> + <regex>(\!?)(bond|br|dum|en|ersp|eth|gnv|ifb|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)([0-9]?)(\*?)(.+)?|(\!?)lo</regex> + <validator name="vrf-name"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/outbound-interface.xml.i b/interface-definitions/include/firewall/outbound-interface.xml.i new file mode 100644 index 0000000..8654dfd --- /dev/null +++ b/interface-definitions/include/firewall/outbound-interface.xml.i @@ -0,0 +1,10 @@ +<!-- include start from firewall/outbound-interface.xml.i --> +<node name="outbound-interface"> + <properties> + <help>Match outbound-interface</help> + </properties> + <children> + #include <include/firewall/match-interface.xml.i> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/packet-options.xml.i b/interface-definitions/include/firewall/packet-options.xml.i new file mode 100644 index 0000000..cd94e69 --- /dev/null +++ b/interface-definitions/include/firewall/packet-options.xml.i @@ -0,0 +1,63 @@ +<!-- include start from firewall/packet-options.xml.i --> +<leafNode name="packet-length"> + <properties> + <help>Payload size in bytes, including header and data to match</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Packet length to match</description> + </valueHelp> + <valueHelp> + <format><start-end></format> + <description>Packet length range to match</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 1-65535"/> + </constraint> + <multi/> + </properties> +</leafNode> +<leafNode name="packet-length-exclude"> + <properties> + <help>Payload size in bytes, including header and data not to match</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Packet length not to match</description> + </valueHelp> + <valueHelp> + <format><start-end></format> + <description>Packet length range not to match</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 1-65535"/> + </constraint> + <multi/> + </properties> +</leafNode> +<leafNode name="packet-type"> + <properties> + <help>Packet type</help> + <completionHelp> + <list>broadcast host multicast other</list> + </completionHelp> + <valueHelp> + <format>broadcast</format> + <description>Match broadcast packet type</description> + </valueHelp> + <valueHelp> + <format>host</format> + <description>Match host packet type, addressed to local host</description> + </valueHelp> + <valueHelp> + <format>multicast</format> + <description>Match multicast packet type</description> + </valueHelp> + <valueHelp> + <format>other</format> + <description>Match packet addressed to another host</description> + </valueHelp> + <constraint> + <regex>(broadcast|host|multicast|other)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/port.xml.i b/interface-definitions/include/firewall/port.xml.i new file mode 100644 index 0000000..3bacaff --- /dev/null +++ b/interface-definitions/include/firewall/port.xml.i @@ -0,0 +1,26 @@ +<!-- include start from firewall/port.xml.i --> +<leafNode name="port"> + <properties> + <help>Port</help> + <valueHelp> + <format>txt</format> + <description>Named port (any name in /etc/services, e.g., http)</description> + </valueHelp> + <valueHelp> + <format>u32:1-65535</format> + <description>Numbered port</description> + </valueHelp> + <valueHelp> + <format><start-end></format> + <description>Numbered port range (e.g. 1001-1005)</description> + </valueHelp> + <valueHelp> + <format> </format> + <description>\n\n Multiple destination ports can be specified as a comma-separated list.\n For example: 'telnet,http,123,1001-1005'</description> + </valueHelp> + <constraint> + <validator name="port-multi"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/protocol.xml.i b/interface-definitions/include/firewall/protocol.xml.i new file mode 100644 index 0000000..e391cae --- /dev/null +++ b/interface-definitions/include/firewall/protocol.xml.i @@ -0,0 +1,34 @@ +<!-- include start from firewall/protocol.xml.i --> +<leafNode name="protocol"> + <properties> + <help>Protocol to match (protocol name, number, or "all")</help> + <completionHelp> + <script>${vyos_completion_dir}/list_protocols.sh</script> + <list>all tcp_udp</list> + </completionHelp> + <valueHelp> + <format>all</format> + <description>All IP protocols</description> + </valueHelp> + <valueHelp> + <format>tcp_udp</format> + <description>Both TCP and UDP</description> + </valueHelp> + <valueHelp> + <format>u32:0-255</format> + <description>IP protocol number</description> + </valueHelp> + <valueHelp> + <format><protocol></format> + <description>IP protocol name</description> + </valueHelp> + <valueHelp> + <format>!<protocol></format> + <description>IP protocol name</description> + </valueHelp> + <constraint> + <validator name="ip-protocol"/> + </constraint> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/recent.xml.i b/interface-definitions/include/firewall/recent.xml.i new file mode 100644 index 0000000..38f40b9 --- /dev/null +++ b/interface-definitions/include/firewall/recent.xml.i @@ -0,0 +1,44 @@ +<!-- include start from firewall/recent.xml.i --> +<node name="recent"> + <properties> + <help>Parameters for matching recently seen sources</help> + </properties> + <children> + <leafNode name="count"> + <properties> + <help>Source addresses seen more than N times</help> + <valueHelp> + <format>u32:1-255</format> + <description>Source addresses seen more than N times</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="time"> + <properties> + <help>Source addresses seen in the last second/minute/hour</help> + <completionHelp> + <list>second minute hour</list> + </completionHelp> + <valueHelp> + <format>second</format> + <description>Source addresses seen COUNT times in the last second</description> + </valueHelp> + <valueHelp> + <format>minute</format> + <description>Source addresses seen COUNT times in the last minute</description> + </valueHelp> + <valueHelp> + <format>hour</format> + <description>Source addresses seen COUNT times in the last hour</description> + </valueHelp> + <constraint> + <regex>(second|minute|hour)</regex> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/rule-log-level.xml.i b/interface-definitions/include/firewall/rule-log-level.xml.i new file mode 100644 index 0000000..3ac4738 --- /dev/null +++ b/interface-definitions/include/firewall/rule-log-level.xml.i @@ -0,0 +1,45 @@ +<!-- include start from firewall/rule-log-level.xml.i --> +<leafNode name="log-level"> + <properties> + <help>Set log-level. Log must be enable.</help> + <completionHelp> + <list>emerg alert crit err warn notice info debug</list> + </completionHelp> + <valueHelp> + <format>emerg</format> + <description>Emerg log level</description> + </valueHelp> + <valueHelp> + <format>alert</format> + <description>Alert log level</description> + </valueHelp> + <valueHelp> + <format>crit</format> + <description>Critical log level</description> + </valueHelp> + <valueHelp> + <format>err</format> + <description>Error log level</description> + </valueHelp> + <valueHelp> + <format>warn</format> + <description>Warning log level</description> + </valueHelp> + <valueHelp> + <format>notice</format> + <description>Notice log level</description> + </valueHelp> + <valueHelp> + <format>info</format> + <description>Info log level</description> + </valueHelp> + <valueHelp> + <format>debug</format> + <description>Debug log level</description> + </valueHelp> + <constraint> + <regex>(emerg|alert|crit|err|warn|notice|info|debug)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/set-packet-modifications.xml.i b/interface-definitions/include/firewall/set-packet-modifications.xml.i new file mode 100644 index 0000000..ee019b6 --- /dev/null +++ b/interface-definitions/include/firewall/set-packet-modifications.xml.i @@ -0,0 +1,96 @@ +<!-- include start from firewall/set-packet-modifications.xml.i --> +<node name="set"> + <properties> + <help>Packet modifications</help> + </properties> + <children> + <leafNode name="connection-mark"> + <properties> + <help>Set connection mark</help> + <valueHelp> + <format>u32:0-2147483647</format> + <description>Connection mark</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2147483647"/> + </constraint> + </properties> + </leafNode> + <leafNode name="dscp"> + <properties> + <help>Set DSCP (Packet Differentiated Services Codepoint) bits</help> + <valueHelp> + <format>u32:0-63</format> + <description>DSCP number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-63"/> + </constraint> + </properties> + </leafNode> + <leafNode name="mark"> + <properties> + <help>Set packet mark</help> + <valueHelp> + <format>u32:1-2147483647</format> + <description>Packet mark</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2147483647"/> + </constraint> + </properties> + </leafNode> + <leafNode name="table"> + <properties> + <help>Set the routing table for matched packets</help> + <valueHelp> + <format>u32:1-200</format> + <description>Table number</description> + </valueHelp> + <valueHelp> + <format>main</format> + <description>Main table</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-200"/> + <regex>(main)</regex> + </constraint> + <completionHelp> + <list>main</list> + <path>protocols static table</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="vrf"> + <properties> + <help>VRF to forward packet with</help> + <valueHelp> + <format>txt</format> + <description>VRF instance name</description> + </valueHelp> + <valueHelp> + <format>default</format> + <description>Forward into default global VRF</description> + </valueHelp> + <completionHelp> + <list>default</list> + <path>vrf name</path> + </completionHelp> + #include <include/constraint/vrf.xml.i> + </properties> + </leafNode> + <leafNode name="tcp-mss"> + <properties> + <help>Set TCP Maximum Segment Size</help> + <valueHelp> + <format>u32:500-1460</format> + <description>Explicitly set TCP MSS value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 500-1460"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/source-destination-dynamic-group-ipv6.xml.i b/interface-definitions/include/firewall/source-destination-dynamic-group-ipv6.xml.i new file mode 100644 index 0000000..845f8fe --- /dev/null +++ b/interface-definitions/include/firewall/source-destination-dynamic-group-ipv6.xml.i @@ -0,0 +1,17 @@ +<!-- include start from firewall/source-destination-dynamic-group-ipv6.xml.i --> +<node name="group"> + <properties> + <help>Group</help> + </properties> + <children> + <leafNode name="dynamic-address-group"> + <properties> + <help>Group of dynamic ipv6 addresses</help> + <completionHelp> + <path>firewall group dynamic-group ipv6-address-group</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/source-destination-dynamic-group.xml.i b/interface-definitions/include/firewall/source-destination-dynamic-group.xml.i new file mode 100644 index 0000000..29ab98c --- /dev/null +++ b/interface-definitions/include/firewall/source-destination-dynamic-group.xml.i @@ -0,0 +1,17 @@ +<!-- include start from firewall/source-destination-dynamic-group.xml.i --> +<node name="group"> + <properties> + <help>Group</help> + </properties> + <children> + <leafNode name="dynamic-address-group"> + <properties> + <help>Group of dynamic addresses</help> + <completionHelp> + <path>firewall group dynamic-group address-group</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/source-destination-group-inet.xml.i b/interface-definitions/include/firewall/source-destination-group-inet.xml.i new file mode 100644 index 0000000..1740516 --- /dev/null +++ b/interface-definitions/include/firewall/source-destination-group-inet.xml.i @@ -0,0 +1,50 @@ +<!-- include start from firewall/source-destination-group-inet.xml.i --> +<node name="group"> + <properties> + <help>Group</help> + </properties> + <children> + <leafNode name="ipv4-address-group"> + <properties> + <help>Group of IPv4 addresses</help> + <completionHelp> + <path>firewall group address-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="ipv6-address-group"> + <properties> + <help>Group of IPv6 addresses</help> + <completionHelp> + <path>firewall group ipv6-address-group</path> + </completionHelp> + </properties> + </leafNode> + #include <include/firewall/mac-group.xml.i> + <leafNode name="ipv4-network-group"> + <properties> + <help>Group of IPv4 networks</help> + <completionHelp> + <path>firewall group network-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="ipv6-network-group"> + <properties> + <help>Group of IPv6 networks</help> + <completionHelp> + <path>firewall group ipv6-network-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="port-group"> + <properties> + <help>Group of ports</help> + <completionHelp> + <path>firewall group port-group</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/source-destination-group-ipv4.xml.i b/interface-definitions/include/firewall/source-destination-group-ipv4.xml.i new file mode 100644 index 0000000..8c34fb9 --- /dev/null +++ b/interface-definitions/include/firewall/source-destination-group-ipv4.xml.i @@ -0,0 +1,41 @@ +<!-- include start from firewall/source-destination-group-ipv4.xml.i --> +<node name="group"> + <properties> + <help>Group</help> + </properties> + <children> + <leafNode name="address-group"> + <properties> + <help>Group of addresses</help> + <completionHelp> + <path>firewall group address-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="domain-group"> + <properties> + <help>Group of domains</help> + <completionHelp> + <path>firewall group domain-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="network-group"> + <properties> + <help>Group of networks</help> + <completionHelp> + <path>firewall group network-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="port-group"> + <properties> + <help>Group of ports</help> + <completionHelp> + <path>firewall group port-group</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i b/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i new file mode 100644 index 0000000..2a42d23 --- /dev/null +++ b/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i @@ -0,0 +1,42 @@ +<!-- include start from firewall/source-destination-group-ipv6.xml.i --> +<node name="group"> + <properties> + <help>Group</help> + </properties> + <children> + <leafNode name="address-group"> + <properties> + <help>Group of addresses</help> + <completionHelp> + <path>firewall group ipv6-address-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="domain-group"> + <properties> + <help>Group of domains</help> + <completionHelp> + <path>firewall group domain-group</path> + </completionHelp> + </properties> + </leafNode> + #include <include/firewall/mac-group.xml.i> + <leafNode name="network-group"> + <properties> + <help>Group of networks</help> + <completionHelp> + <path>firewall group ipv6-network-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="port-group"> + <properties> + <help>Group of ports</help> + <completionHelp> + <path>firewall group port-group</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/source-destination-group.xml.i b/interface-definitions/include/firewall/source-destination-group.xml.i new file mode 100644 index 0000000..6ebee35 --- /dev/null +++ b/interface-definitions/include/firewall/source-destination-group.xml.i @@ -0,0 +1,42 @@ +<!-- include start from firewall/source-destination-group.xml.i --> +<node name="group"> + <properties> + <help>Group</help> + </properties> + <children> + <leafNode name="address-group"> + <properties> + <help>Group of addresses</help> + <completionHelp> + <path>firewall group address-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="domain-group"> + <properties> + <help>Group of domains</help> + <completionHelp> + <path>firewall group domain-group</path> + </completionHelp> + </properties> + </leafNode> + #include <include/firewall/mac-group.xml.i> + <leafNode name="network-group"> + <properties> + <help>Group of networks</help> + <completionHelp> + <path>firewall group network-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="port-group"> + <properties> + <help>Group of ports</help> + <completionHelp> + <path>firewall group port-group</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/state.xml.i b/interface-definitions/include/firewall/state.xml.i new file mode 100644 index 0000000..dee9722 --- /dev/null +++ b/interface-definitions/include/firewall/state.xml.i @@ -0,0 +1,30 @@ +<!-- include start from firewall/state.xml.i --> +<leafNode name="state"> + <properties> + <help>Session state</help> + <completionHelp> + <list>established invalid new related</list> + </completionHelp> + <valueHelp> + <format>established</format> + <description>Established state</description> + </valueHelp> + <valueHelp> + <format>invalid</format> + <description>Invalid state</description> + </valueHelp> + <valueHelp> + <format>new</format> + <description>New state</description> + </valueHelp> + <valueHelp> + <format>related</format> + <description>Related state</description> + </valueHelp> + <constraint> + <regex>(established|invalid|new|related)</regex> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/synproxy.xml.i b/interface-definitions/include/firewall/synproxy.xml.i new file mode 100644 index 0000000..a65126e --- /dev/null +++ b/interface-definitions/include/firewall/synproxy.xml.i @@ -0,0 +1,40 @@ +<!-- include start from firewall/synproxy.xml.i --> +<node name="synproxy"> + <properties> + <help>Synproxy options</help> + </properties> + <children> + <node name="tcp"> + <properties> + <help>TCP synproxy options</help> + </properties> + <children> + <leafNode name="mss"> + <properties> + <help>TCP Maximum segment size</help> + <valueHelp> + <format>u32:501-65535</format> + <description>Maximum segment size for synproxy connections</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 501-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="window-scale"> + <properties> + <help>TCP window scale for synproxy connections</help> + <valueHelp> + <format>u32:1-14</format> + <description>TCP window scale</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-14"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/tcp-flags.xml.i b/interface-definitions/include/firewall/tcp-flags.xml.i new file mode 100644 index 0000000..36546c2 --- /dev/null +++ b/interface-definitions/include/firewall/tcp-flags.xml.i @@ -0,0 +1,119 @@ +<!-- include start from firewall/tcp-flags.xml.i --> +<node name="tcp"> + <properties> + <help>TCP options to match</help> + </properties> + <children> + <node name="flags"> + <properties> + <help>TCP flags to match</help> + </properties> + <children> + <leafNode name="syn"> + <properties> + <help>Synchronise flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ack"> + <properties> + <help>Acknowledge flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="fin"> + <properties> + <help>Finish flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="rst"> + <properties> + <help>Reset flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="urg"> + <properties> + <help>Urgent flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="psh"> + <properties> + <help>Push flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ecn"> + <properties> + <help>Explicit Congestion Notification flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="cwr"> + <properties> + <help>Congestion Window Reduced flag</help> + <valueless/> + </properties> + </leafNode> + <node name="not"> + <properties> + <help>Match flags not set</help> + </properties> + <children> + <leafNode name="syn"> + <properties> + <help>Synchronise flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ack"> + <properties> + <help>Acknowledge flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="fin"> + <properties> + <help>Finish flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="rst"> + <properties> + <help>Reset flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="urg"> + <properties> + <help>Urgent flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="psh"> + <properties> + <help>Push flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ecn"> + <properties> + <help>Explicit Congestion Notification flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="cwr"> + <properties> + <help>Congestion Window Reduced flag</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/tcp-mss.xml.i b/interface-definitions/include/firewall/tcp-mss.xml.i new file mode 100644 index 0000000..dc49b42 --- /dev/null +++ b/interface-definitions/include/firewall/tcp-mss.xml.i @@ -0,0 +1,25 @@ +<!-- include start from firewall/tcp-mss.xml.i --> +<node name="tcp"> + <properties> + <help>TCP options to match</help> + </properties> + <children> + <leafNode name="mss"> + <properties> + <help>Maximum segment size (MSS)</help> + <valueHelp> + <format>u32:1-16384</format> + <description>Maximum segment size</description> + </valueHelp> + <valueHelp> + <format><min>-<max></format> + <description>TCP MSS range (use '-' as delimiter)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 1-16384"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/time.xml.i b/interface-definitions/include/firewall/time.xml.i new file mode 100644 index 0000000..7bd7374 --- /dev/null +++ b/interface-definitions/include/firewall/time.xml.i @@ -0,0 +1,70 @@ +<!-- include start from firewall/time.xml.i --> +<node name="time"> + <properties> + <help>Time to match rule</help> + </properties> + <children> + <leafNode name="startdate"> + <properties> + <help>Date to start matching rule</help> + <valueHelp> + <format>txt</format> + <description>Enter date using following notation - YYYY-MM-DD</description> + </valueHelp> + <constraint> + <regex>(\d{4}\-\d{2}\-\d{2})</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="starttime"> + <properties> + <help>Time of day to start matching rule</help> + <valueHelp> + <format>txt</format> + <description>Enter time using using 24 hour notation - hh:mm:ss</description> + </valueHelp> + <constraint> + <regex>([0-2][0-9](\:[0-5][0-9]){1,2})</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="stopdate"> + <properties> + <help>Date to stop matching rule</help> + <valueHelp> + <format>txt</format> + <description>Enter date using following notation - YYYY-MM-DD</description> + </valueHelp> + <constraint> + <regex>(\d{4}\-\d{2}\-\d{2})</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="stoptime"> + <properties> + <help>Time of day to stop matching rule</help> + <valueHelp> + <format>txt</format> + <description>Enter time using using 24 hour notation - hh:mm:ss</description> + </valueHelp> + <constraint> + <regex>([0-2][0-9](\:[0-5][0-9]){1,2})</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="weekdays"> + <properties> + <help>Comma separated weekdays to match rule on</help> + <valueHelp> + <format>txt</format> + <description>Name of day (Monday, Tuesday, Wednesday, Thursdays, Friday, Saturday, Sunday)</description> + </valueHelp> + <valueHelp> + <format>u32:0-6</format> + <description>Day number (0 = Sunday ... 6 = Saturday)</description> + </valueHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/timeout-common-protocols.xml.i b/interface-definitions/include/firewall/timeout-common-protocols.xml.i new file mode 100644 index 0000000..037d7d2 --- /dev/null +++ b/interface-definitions/include/firewall/timeout-common-protocols.xml.i @@ -0,0 +1,171 @@ +<!-- include start from firewall/timeout-common-protocols.xml.i --> +<leafNode name="icmp"> + <properties> + <help>ICMP timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>ICMP timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + <defaultValue>30</defaultValue> +</leafNode> +<leafNode name="other"> + <properties> + <help>Generic connection timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>Generic connection timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + <defaultValue>600</defaultValue> +</leafNode> +<node name="tcp"> + <properties> + <help>TCP connection timeout options</help> + </properties> + <children> + <leafNode name="close-wait"> + <properties> + <help>TCP CLOSE-WAIT timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP CLOSE-WAIT timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + <defaultValue>60</defaultValue> + </leafNode> + <leafNode name="close"> + <properties> + <help>TCP CLOSE timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP CLOSE timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + <defaultValue>10</defaultValue> + </leafNode> + <leafNode name="established"> + <properties> + <help>TCP ESTABLISHED timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP ESTABLISHED timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + <defaultValue>432000</defaultValue> + </leafNode> + <leafNode name="fin-wait"> + <properties> + <help>TCP FIN-WAIT timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP FIN-WAIT timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + <defaultValue>120</defaultValue> + </leafNode> + <leafNode name="last-ack"> + <properties> + <help>TCP LAST-ACK timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP LAST-ACK timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + <defaultValue>30</defaultValue> + </leafNode> + <leafNode name="syn-recv"> + <properties> + <help>TCP SYN-RECEIVED timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP SYN-RECEIVED timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + <defaultValue>60</defaultValue> + </leafNode> + <leafNode name="syn-sent"> + <properties> + <help>TCP SYN-SENT timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP SYN-SENT timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + <defaultValue>120</defaultValue> + </leafNode> + <leafNode name="time-wait"> + <properties> + <help>TCP TIME-WAIT timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP TIME-WAIT timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + <defaultValue>120</defaultValue> + </leafNode> + </children> +</node> +<node name="udp"> + <properties> + <help>UDP timeout options</help> + </properties> + <children> + <leafNode name="other"> + <properties> + <help>UDP generic timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>UDP generic timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + <defaultValue>30</defaultValue> + </leafNode> + <leafNode name="stream"> + <properties> + <help>UDP stream timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>UDP stream timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + <defaultValue>180</defaultValue> + </leafNode> + </children> +</node> diff --git a/interface-definitions/include/firewall/ttl.xml.i b/interface-definitions/include/firewall/ttl.xml.i new file mode 100644 index 0000000..9c782a9 --- /dev/null +++ b/interface-definitions/include/firewall/ttl.xml.i @@ -0,0 +1,12 @@ +<!-- include start from firewall/ttl.xml.i --> +<node name="ttl"> + <properties> + <help>Time to live limit</help> + </properties> + <children> + #include <include/firewall/eq.xml.i> + #include <include/firewall/gt.xml.i> + #include <include/firewall/lt.xml.i> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/generic-description.xml.i b/interface-definitions/include/generic-description.xml.i new file mode 100644 index 0000000..7e091ea --- /dev/null +++ b/interface-definitions/include/generic-description.xml.i @@ -0,0 +1,15 @@ +<!-- include start from generic-description.xml.i --> +<leafNode name="description"> + <properties> + <help>Description</help> + <valueHelp> + <format>txt</format> + <description>Description</description> + </valueHelp> + <constraint> + <regex>.{0,255}</regex> + </constraint> + <constraintErrorMessage>Description too long (limit 255 characters)</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/generic-disable-node.xml.i b/interface-definitions/include/generic-disable-node.xml.i new file mode 100644 index 0000000..97a328e --- /dev/null +++ b/interface-definitions/include/generic-disable-node.xml.i @@ -0,0 +1,8 @@ +<!-- include start from generic-disable-node.xml.i --> +<leafNode name="disable"> + <properties> + <help>Disable instance</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/generic-interface-broadcast.xml.i b/interface-definitions/include/generic-interface-broadcast.xml.i new file mode 100644 index 0000000..e37e750 --- /dev/null +++ b/interface-definitions/include/generic-interface-broadcast.xml.i @@ -0,0 +1,17 @@ +<!-- include start from generic-interface-broadcast.xml.i --> +<leafNode name="interface"> + <properties> + <help>Interface to use</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --broadcast</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/generic-interface-multi-broadcast.xml.i b/interface-definitions/include/generic-interface-multi-broadcast.xml.i new file mode 100644 index 0000000..ed13cf2 --- /dev/null +++ b/interface-definitions/include/generic-interface-multi-broadcast.xml.i @@ -0,0 +1,18 @@ +<!-- include start from generic-interface-multi-broadcast.xml.i --> +<leafNode name="interface"> + <properties> + <help>Interface to use</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --broadcast</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/generic-interface-multi-wildcard.xml.i b/interface-definitions/include/generic-interface-multi-wildcard.xml.i new file mode 100644 index 0000000..6c846a7 --- /dev/null +++ b/interface-definitions/include/generic-interface-multi-wildcard.xml.i @@ -0,0 +1,18 @@ +<!-- include start from generic-interface-multi-wildcard.xml.i --> +<leafNode name="interface"> + <properties> + <help>Interface to use</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name, wildcard (*) supported</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name-with-wildcard.xml.i> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/generic-interface-multi.xml.i b/interface-definitions/include/generic-interface-multi.xml.i new file mode 100644 index 0000000..cfc77af --- /dev/null +++ b/interface-definitions/include/generic-interface-multi.xml.i @@ -0,0 +1,18 @@ +<!-- include start from generic-interface-multi.xml.i --> +<leafNode name="interface"> + <properties> + <help>Interface to use</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/generic-interface.xml.i b/interface-definitions/include/generic-interface.xml.i new file mode 100644 index 0000000..65f5bfb --- /dev/null +++ b/interface-definitions/include/generic-interface.xml.i @@ -0,0 +1,17 @@ +<!-- include start from generic-interface.xml.i --> +<leafNode name="interface"> + <properties> + <help>Interface to use</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/generic-password.xml.i b/interface-definitions/include/generic-password.xml.i new file mode 100644 index 0000000..76d5f12 --- /dev/null +++ b/interface-definitions/include/generic-password.xml.i @@ -0,0 +1,15 @@ +<!-- include start from generic-password.xml.i --> +<leafNode name="password"> + <properties> + <help>Password used for authentication</help> + <valueHelp> + <format>txt</format> + <description>Password</description> + </valueHelp> + <constraint> + <regex>[[:ascii:]]{1,128}</regex> + </constraint> + <constraintErrorMessage>Password is limited to ASCII characters only, with a total length of 128</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/generic-username.xml.i b/interface-definitions/include/generic-username.xml.i new file mode 100644 index 0000000..678f30d --- /dev/null +++ b/interface-definitions/include/generic-username.xml.i @@ -0,0 +1,15 @@ +<!-- include start from generic-username.xml.i --> +<leafNode name="username"> + <properties> + <help>Username used for authentication</help> + <valueHelp> + <format>txt</format> + <description>Username</description> + </valueHelp> + <constraint> + <regex>[[:ascii:]]{1,128}</regex> + </constraint> + <constraintErrorMessage>Username is limited to ASCII characters only, with a total length of 128</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/haproxy/http-response-headers.xml.i b/interface-definitions/include/haproxy/http-response-headers.xml.i new file mode 100644 index 0000000..9e7ddfd --- /dev/null +++ b/interface-definitions/include/haproxy/http-response-headers.xml.i @@ -0,0 +1,29 @@ +<!-- include start from haproxy/http-response-headers.xml.i --> +<tagNode name="http-response-headers"> + <properties> + <help>Headers to include in HTTP response</help> + <valueHelp> + <format>txt</format> + <description>HTTP header name</description> + </valueHelp> + <constraint> + <regex>[-a-zA-Z]+</regex> + </constraint> + <constraintErrorMessage>Header names must only include alphabetical characters and hyphens</constraintErrorMessage> + </properties> + <children> + <leafNode name="value"> + <properties> + <help>HTTP header value</help> + <valueHelp> + <format>txt</format> + <description>HTTP header value</description> + </valueHelp> + <constraint> + <regex>[[:ascii:]]{1,256}</regex> + </constraint> + </properties> + </leafNode> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/haproxy/logging.xml.i b/interface-definitions/include/haproxy/logging.xml.i new file mode 100644 index 0000000..e0af54f --- /dev/null +++ b/interface-definitions/include/haproxy/logging.xml.i @@ -0,0 +1,10 @@ +<!-- include start from haproxy/logging.xml.i --> +<node name="logging"> + <properties> + <help>Logging parameters</help> + </properties> + <children> + #include <include/syslog-facility.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/haproxy/mode.xml.i b/interface-definitions/include/haproxy/mode.xml.i new file mode 100644 index 0000000..d013e02 --- /dev/null +++ b/interface-definitions/include/haproxy/mode.xml.i @@ -0,0 +1,23 @@ +<!-- include start from haproxy/mode.xml.i --> +<leafNode name="mode"> + <properties> + <help>Proxy mode</help> + <completionHelp> + <list>http tcp</list> + </completionHelp> + <constraintErrorMessage>invalid value</constraintErrorMessage> + <valueHelp> + <format>http</format> + <description>HTTP proxy mode</description> + </valueHelp> + <valueHelp> + <format>tcp</format> + <description>TCP proxy mode</description> + </valueHelp> + <constraint> + <regex>(http|tcp)</regex> + </constraint> + </properties> + <defaultValue>http</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/haproxy/rule-backend.xml.i b/interface-definitions/include/haproxy/rule-backend.xml.i new file mode 100644 index 0000000..b2be4fd --- /dev/null +++ b/interface-definitions/include/haproxy/rule-backend.xml.i @@ -0,0 +1,131 @@ +<!-- include start from haproxy/rule.xml.i --> +<tagNode name="rule"> + <properties> + <help>Proxy rule number</help> + <valueHelp> + <format>u32:1-10000</format> + <description>Number for this proxy rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-10000"/> + </constraint> + <constraintErrorMessage>Proxy rule number must be between 1 and 10000</constraintErrorMessage> + </properties> + <children> + <leafNode name="domain-name"> + <properties> + <help>Domain name to match</help> + <valueHelp> + <format>txt</format> + <description>Domain address to match</description> + </valueHelp> + <constraint> + <validator name="fqdn"/> + </constraint> + <multi/> + </properties> + </leafNode> + <node name="set"> + <properties> + <help>Proxy modifications</help> + </properties> + <children> + <leafNode name="redirect-location"> + <properties> + <help>Set URL location</help> + <valueHelp> + <format>url</format> + <description>Set URL location</description> + </valueHelp> + <constraint> + <regex>^\/[\w\-.\/]+$</regex> + </constraint> + <constraintErrorMessage>Incorrect URL format</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="server"> + <properties> + <help>Server name</help> + <constraint> + <regex>[-_a-zA-Z0-9]+</regex> + </constraint> + <constraintErrorMessage>Server name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + <leafNode name="ssl"> + <properties> + <help>SSL match options</help> + <completionHelp> + <list>req-ssl-sni ssl-fc-sni</list> + </completionHelp> + <valueHelp> + <format>req-ssl-sni</format> + <description>SSL Server Name Indication (SNI) request match</description> + </valueHelp> + <valueHelp> + <format>ssl-fc-sni</format> + <description>SSL frontend connection Server Name Indication match</description> + </valueHelp> + <valueHelp> + <format>ssl-fc-sni-end</format> + <description>SSL frontend match end of connection Server Name Indication</description> + </valueHelp> + <constraint> + <regex>(req-ssl-sni|ssl-fc-sni|ssl-fc-sni-end)</regex> + </constraint> + </properties> + </leafNode> + <node name="url-path"> + <properties> + <help>URL path match</help> + </properties> + <children> + <leafNode name="begin"> + <properties> + <help>Begin URL match</help> + <valueHelp> + <format>url</format> + <description>Begin URL</description> + </valueHelp> + <constraint> + <regex>^\/[\w\-.\/]+$</regex> + </constraint> + <constraintErrorMessage>Incorrect URL format</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + <leafNode name="end"> + <properties> + <help>End URL match</help> + <valueHelp> + <format>url</format> + <description>End URL</description> + </valueHelp> + <constraint> + <regex>^\/[\w\-.\/]+$</regex> + </constraint> + <constraintErrorMessage>Incorrect URL format</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + <leafNode name="exact"> + <properties> + <help>Exactly URL match</help> + <valueHelp> + <format>url</format> + <description>Exactly URL</description> + </valueHelp> + <constraint> + <regex>^\/[\w\-.\/]*$</regex> + </constraint> + <constraintErrorMessage>Incorrect URL format</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + </children> + </node> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/haproxy/rule-frontend.xml.i b/interface-definitions/include/haproxy/rule-frontend.xml.i new file mode 100644 index 0000000..001ae2d --- /dev/null +++ b/interface-definitions/include/haproxy/rule-frontend.xml.i @@ -0,0 +1,131 @@ +<!-- include start from haproxy/rule.xml.i --> +<tagNode name="rule"> + <properties> + <help>Proxy rule number</help> + <valueHelp> + <format>u32:1-10000</format> + <description>Number for this proxy rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-10000"/> + </constraint> + <constraintErrorMessage>Proxy rule number must be between 1 and 10000</constraintErrorMessage> + </properties> + <children> + <leafNode name="domain-name"> + <properties> + <help>Domain name to match</help> + <valueHelp> + <format>txt</format> + <description>Domain address to match</description> + </valueHelp> + <constraint> + <validator name="fqdn"/> + </constraint> + <multi/> + </properties> + </leafNode> + <node name="set"> + <properties> + <help>Proxy modifications</help> + </properties> + <children> + <leafNode name="redirect-location"> + <properties> + <help>Set URL location</help> + <valueHelp> + <format>url</format> + <description>Set URL location</description> + </valueHelp> + <constraint> + <regex>^\/[\w\-.\/]+$</regex> + </constraint> + <constraintErrorMessage>Incorrect URL format</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="backend"> + <properties> + <help>Backend name</help> + <constraint> + <regex>[-_a-zA-Z0-9]+</regex> + </constraint> + <constraintErrorMessage>Server name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + <leafNode name="ssl"> + <properties> + <help>SSL match options</help> + <completionHelp> + <list>req-ssl-sni ssl-fc-sni</list> + </completionHelp> + <valueHelp> + <format>req-ssl-sni</format> + <description>SSL Server Name Indication (SNI) request match</description> + </valueHelp> + <valueHelp> + <format>ssl-fc-sni</format> + <description>SSL frontend connection Server Name Indication match</description> + </valueHelp> + <valueHelp> + <format>ssl-fc-sni-end</format> + <description>SSL frontend match end of connection Server Name Indication</description> + </valueHelp> + <constraint> + <regex>(req-ssl-sni|ssl-fc-sni|ssl-fc-sni-end)</regex> + </constraint> + </properties> + </leafNode> + <node name="url-path"> + <properties> + <help>URL path match</help> + </properties> + <children> + <leafNode name="begin"> + <properties> + <help>Begin URL match</help> + <valueHelp> + <format>url</format> + <description>Begin URL</description> + </valueHelp> + <constraint> + <regex>^\/[\w\-.\/]+$</regex> + </constraint> + <constraintErrorMessage>Incorrect URL format</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + <leafNode name="end"> + <properties> + <help>End URL match</help> + <valueHelp> + <format>url</format> + <description>End URL</description> + </valueHelp> + <constraint> + <regex>^\/[\w\-.\/]+$</regex> + </constraint> + <constraintErrorMessage>Incorrect URL format</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + <leafNode name="exact"> + <properties> + <help>Exactly URL match</help> + <valueHelp> + <format>url</format> + <description>Exactly URL</description> + </valueHelp> + <constraint> + <regex>^\/[\w\-.\/]+$</regex> + </constraint> + <constraintErrorMessage>Incorrect URL format</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + </children> + </node> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/haproxy/tcp-request.xml.i b/interface-definitions/include/haproxy/tcp-request.xml.i new file mode 100644 index 0000000..3d60bd8 --- /dev/null +++ b/interface-definitions/include/haproxy/tcp-request.xml.i @@ -0,0 +1,22 @@ +<!-- include start from haproxy/tcp-request.xml.i --> +<node name="tcp-request"> + <properties> + <help>TCP request directive</help> + </properties> + <children> + <leafNode name="inspect-delay"> + <properties> + <help>Set the maximum allowed time to wait for data during content inspection</help> + <valueHelp> + <format>u32:1-65535</format> + <description>The timeout value specified in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + <constraintErrorMessage>The timeout value must be in range 1 to 65535 milliseconds</constraintErrorMessage> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/haproxy/timeout.xml.i b/interface-definitions/include/haproxy/timeout.xml.i new file mode 100644 index 0000000..79e7303 --- /dev/null +++ b/interface-definitions/include/haproxy/timeout.xml.i @@ -0,0 +1,45 @@ +<!-- include start from haproxy/timeout.xml.i --> +<node name="timeout"> + <properties> + <help>Timeout options</help> + </properties> + <children> + <leafNode name="check"> + <properties> + <help>Timeout in seconds for established connections</help> + <valueHelp> + <format>u32:1-3600</format> + <description>Check timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-3600"/> + </constraint> + </properties> + </leafNode> + <leafNode name="connect"> + <properties> + <help>Set the maximum time to wait for a connection attempt to a server to succeed</help> + <valueHelp> + <format>u32:1-3600</format> + <description>Connect timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-3600"/> + </constraint> + </properties> + </leafNode> + <leafNode name="server"> + <properties> + <help>Set the maximum inactivity time on the server side</help> + <valueHelp> + <format>u32:1-3600</format> + <description>Server timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-3600"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/ids/threshold.xml.i b/interface-definitions/include/ids/threshold.xml.i new file mode 100644 index 0000000..e21e3a0 --- /dev/null +++ b/interface-definitions/include/ids/threshold.xml.i @@ -0,0 +1,38 @@ +<!-- include start from ids/threshold.xml.i --> +<leafNode name="fps"> + <properties> + <help>Flows per second</help> + <valueHelp> + <format>u32:0-4294967294</format> + <description>Flows per second</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967294"/> + </constraint> + </properties> +</leafNode> +<leafNode name="mbps"> + <properties> + <help>Megabits per second</help> + <valueHelp> + <format>u32:0-4294967294</format> + <description>Megabits per second</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967294"/> + </constraint> + </properties> +</leafNode> +<leafNode name="pps"> + <properties> + <help>Packets per second</help> + <valueHelp> + <format>u32:0-4294967294</format> + <description>Packets per second</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967294"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/inbound-interface.xml.i b/interface-definitions/include/inbound-interface.xml.i new file mode 100644 index 0000000..422f9de --- /dev/null +++ b/interface-definitions/include/inbound-interface.xml.i @@ -0,0 +1,11 @@ +<!-- include start from inbound-interface.xml.i --> +<leafNode name="inbound-interface"> + <properties> + <help>Inbound interface of NAT traffic</help> + <completionHelp> + <list>any</list> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i b/interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i new file mode 100644 index 0000000..5057ed9 --- /dev/null +++ b/interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i @@ -0,0 +1,31 @@ +<!-- include start from interface/address-ipv4-ipv6-dhcp.xml.i --> +<leafNode name="address"> + <properties> + <help>IP address</help> + <completionHelp> + <list>dhcp dhcpv6</list> + </completionHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 address and prefix length</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <valueHelp> + <format>dhcp</format> + <description>Dynamic Host Configuration Protocol</description> + </valueHelp> + <valueHelp> + <format>dhcpv6</format> + <description>Dynamic Host Configuration Protocol for IPv6</description> + </valueHelp> + <constraint> + <validator name="ip-host"/> + <regex>(dhcp|dhcpv6)</regex> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/address-ipv4-ipv6.xml.i b/interface-definitions/include/interface/address-ipv4-ipv6.xml.i new file mode 100644 index 0000000..d689da5 --- /dev/null +++ b/interface-definitions/include/interface/address-ipv4-ipv6.xml.i @@ -0,0 +1,19 @@ +<!-- include start from interface/address-ipv4-ipv6.xml.i --> +<leafNode name="address"> + <properties> + <help>IP address</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 address and prefix length</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ip-host"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/adjust-mss.xml.i b/interface-definitions/include/interface/adjust-mss.xml.i new file mode 100644 index 0000000..2b184a0 --- /dev/null +++ b/interface-definitions/include/interface/adjust-mss.xml.i @@ -0,0 +1,23 @@ +<!-- include start from interface/adjust-mss.xml.i --> +<!-- https://datatracker.ietf.org/doc/html/rfc6691 --> +<leafNode name="adjust-mss"> + <properties> + <help>Adjust TCP MSS value</help> + <completionHelp> + <list>clamp-mss-to-pmtu</list> + </completionHelp> + <valueHelp> + <format>clamp-mss-to-pmtu</format> + <description>Automatically sets the MSS to the proper value</description> + </valueHelp> + <valueHelp> + <format>u32:536-65535</format> + <description>TCP Maximum segment size in bytes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 536-65535"/> + <regex>(clamp-mss-to-pmtu)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/arp-cache-timeout.xml.i b/interface-definitions/include/interface/arp-cache-timeout.xml.i new file mode 100644 index 0000000..06d7ffe --- /dev/null +++ b/interface-definitions/include/interface/arp-cache-timeout.xml.i @@ -0,0 +1,16 @@ +<!-- include start from interface/arp-cache-timeout.xml.i --> +<leafNode name="arp-cache-timeout"> + <properties> + <help>ARP cache entry timeout in seconds</help> + <valueHelp> + <format>u32:1-86400</format> + <description>ARP cache entry timout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-86400"/> + </constraint> + <constraintErrorMessage>ARP cache entry timeout must be between 1 and 86400 seconds</constraintErrorMessage> + </properties> + <defaultValue>30</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/authentication.xml.i b/interface-definitions/include/interface/authentication.xml.i new file mode 100644 index 0000000..0bd7922 --- /dev/null +++ b/interface-definitions/include/interface/authentication.xml.i @@ -0,0 +1,11 @@ +<!-- include start from interface/authentication.xml.i --> +<node name="authentication"> + <properties> + <help>Authentication settings</help> + </properties> + <children> + #include <include/generic-username.xml.i> + #include <include/generic-password.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/interface/base-reachable-time.xml.i b/interface-definitions/include/interface/base-reachable-time.xml.i new file mode 100644 index 0000000..fb0d701 --- /dev/null +++ b/interface-definitions/include/interface/base-reachable-time.xml.i @@ -0,0 +1,16 @@ +<!-- include start from interface/base-reachable-time.xml.i --> +<leafNode name="base-reachable-time"> + <properties> + <help>Base reachable time in seconds</help> + <valueHelp> + <format>u32:1-86400</format> + <description>Base reachable time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-86400"/> + </constraint> + <constraintErrorMessage>Base reachable time must be between 1 and 86400 seconds</constraintErrorMessage> + </properties> + <defaultValue>30</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/default-route-distance.xml.i b/interface-definitions/include/interface/default-route-distance.xml.i new file mode 100644 index 0000000..6eda52c --- /dev/null +++ b/interface-definitions/include/interface/default-route-distance.xml.i @@ -0,0 +1,15 @@ +<!-- include start from interface/default-route-distance.xml.i --> +<leafNode name="default-route-distance"> + <properties> + <help>Distance for installed default route</help> + <valueHelp> + <format>u32:1-255</format> + <description>Distance for the default route from DHCP server</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <defaultValue>210</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/dhcp-options.xml.i b/interface-definitions/include/interface/dhcp-options.xml.i new file mode 100644 index 0000000..733512a --- /dev/null +++ b/interface-definitions/include/interface/dhcp-options.xml.i @@ -0,0 +1,80 @@ +<!-- include start from interface/dhcp-options.xml.i --> +<node name="dhcp-options"> + <properties> + <help>DHCP client settings/options</help> + </properties> + <children> + <leafNode name="client-id"> + <properties> + <help>Identifier used by client to identify itself to the DHCP server</help> + <valueHelp> + <format>txt</format> + <description>DHCP option string</description> + </valueHelp> + <constraint> + #include <include/constraint/dhcp-client-string-option.xml.i> + </constraint> + </properties> + </leafNode> + <leafNode name="host-name"> + <properties> + <help>Override system host-name sent to DHCP server</help> + <constraint> + #include <include/constraint/host-name.xml.i> + </constraint> + <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="mtu"> + <properties> + <help>Use MTU value from DHCP server - ignore interface setting</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="vendor-class-id"> + <properties> + <help>Identify the vendor client type to the DHCP server</help> + <valueHelp> + <format>txt</format> + <description>DHCP option string</description> + </valueHelp> + <constraint> + #include <include/constraint/dhcp-client-string-option.xml.i> + </constraint> + </properties> + </leafNode> + <leafNode name="user-class"> + <properties> + <help>Identify to the DHCP server, user configurable option</help> + <valueHelp> + <format>txt</format> + <description>DHCP option string</description> + </valueHelp> + <constraint> + #include <include/constraint/dhcp-client-string-option.xml.i> + </constraint> + </properties> + </leafNode> + #include <include/interface/no-default-route.xml.i> + #include <include/interface/default-route-distance.xml.i> + <leafNode name="reject"> + <properties> + <help>IP addresses or subnets from which to reject DHCP leases</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv4-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/interface/dhcpv6-options.xml.i b/interface-definitions/include/interface/dhcpv6-options.xml.i new file mode 100644 index 0000000..68d1b17 --- /dev/null +++ b/interface-definitions/include/interface/dhcpv6-options.xml.i @@ -0,0 +1,95 @@ +<!-- include start from interface/dhcpv6-options.xml.i --> +<node name="dhcpv6-options"> + <properties> + <help>DHCPv6 client settings/options</help> + </properties> + <children> + #include <include/interface/duid.xml.i> + <leafNode name="parameters-only"> + <properties> + <help>Acquire only config parameters, no address</help> + <valueless/> + </properties> + </leafNode> + <tagNode name="pd"> + <properties> + <help>DHCPv6 prefix delegation interface statement</help> + <valueHelp> + <format>instance number</format> + <description>Prefix delegation instance (>= 0)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--non-negative"/> + </constraint> + </properties> + <children> + <leafNode name="length"> + <properties> + <help>Request IPv6 prefix length from peer</help> + <valueHelp> + <format>u32:32-64</format> + <description>Length of delegated prefix</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 32-64"/> + </constraint> + </properties> + <defaultValue>64</defaultValue> + </leafNode> + <tagNode name="interface"> + <properties> + <help>Delegate IPv6 prefix from provider to this interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --broadcast</script> + </completionHelp> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>Local interface address assigned to interface (default: EUI-64)</help> + <valueHelp> + <format>>0</format> + <description>Used to form IPv6 interface address</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--non-negative"/> + </constraint> + </properties> + </leafNode> + <leafNode name="sla-id"> + <properties> + <help>Interface site-Level aggregator (SLA)</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Decimal integer which fits in the length of SLA IDs</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <leafNode name="rapid-commit"> + <properties> + <help>Wait for immediate reply instead of advertisements</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="temporary"> + <properties> + <help>IPv6 temporary address</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="no-release"> + <properties> + <help>Do not send a release message on client exit</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/interface/dial-on-demand.xml.i b/interface-definitions/include/interface/dial-on-demand.xml.i new file mode 100644 index 0000000..30e8c7e --- /dev/null +++ b/interface-definitions/include/interface/dial-on-demand.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/dial-on-demand.xml.i --> +<leafNode name="connect-on-demand"> + <properties> + <help>Establishment connection automatically when traffic is sent</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/disable-arp-filter.xml.i b/interface-definitions/include/interface/disable-arp-filter.xml.i new file mode 100644 index 0000000..a69455d --- /dev/null +++ b/interface-definitions/include/interface/disable-arp-filter.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/disable-arp-filter.xml.i --> +<leafNode name="disable-arp-filter"> + <properties> + <help>Disable ARP filter on this interface</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/disable-forwarding.xml.i b/interface-definitions/include/interface/disable-forwarding.xml.i new file mode 100644 index 0000000..45382ec --- /dev/null +++ b/interface-definitions/include/interface/disable-forwarding.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/disable-forwarding.xml.i --> +<leafNode name="disable-forwarding"> + <properties> + <help>Disable IP forwarding on this interface</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/disable-link-detect.xml.i b/interface-definitions/include/interface/disable-link-detect.xml.i new file mode 100644 index 0000000..b101ec2 --- /dev/null +++ b/interface-definitions/include/interface/disable-link-detect.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/disable-link-detect.xml.i --> +<leafNode name="disable-link-detect"> + <properties> + <help>Ignore link state changes</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/disable.xml.i b/interface-definitions/include/interface/disable.xml.i new file mode 100644 index 0000000..b76bd3f --- /dev/null +++ b/interface-definitions/include/interface/disable.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/disable.xml.i --> +<leafNode name="disable"> + <properties> + <help>Administratively disable interface</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/duid.xml.i b/interface-definitions/include/interface/duid.xml.i new file mode 100644 index 0000000..8d80869 --- /dev/null +++ b/interface-definitions/include/interface/duid.xml.i @@ -0,0 +1,15 @@ +<!-- include start from interface/duid.xml.i --> +<leafNode name="duid"> + <properties> + <help>DHCP unique identifier (DUID) to be sent by client</help> + <valueHelp> + <format>duid</format> + <description>DHCP unique identifier</description> + </valueHelp> + <constraint> + <regex>([0-9A-Fa-f]{2}:){0,127}([0-9A-Fa-f]{2})</regex> + </constraint> + <constraintErrorMessage>Invalid DUID, must be in the format h[[:h]...]</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/eapol.xml.i b/interface-definitions/include/interface/eapol.xml.i new file mode 100644 index 0000000..a3206f2 --- /dev/null +++ b/interface-definitions/include/interface/eapol.xml.i @@ -0,0 +1,11 @@ +<!-- include start from interface/eapol.xml.i --> +<node name="eapol"> + <properties> + <help>Extensible Authentication Protocol over Local Area Network</help> + </properties> + <children> + #include <include/pki/ca-certificate-multi.xml.i> + #include <include/pki/certificate-key.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/interface/enable-arp-accept.xml.i b/interface-definitions/include/interface/enable-arp-accept.xml.i new file mode 100644 index 0000000..90f6bc3 --- /dev/null +++ b/interface-definitions/include/interface/enable-arp-accept.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/enable-arp-accept.xml.i --> +<leafNode name="enable-arp-accept"> + <properties> + <help>Enable ARP accept on this interface</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/enable-arp-announce.xml.i b/interface-definitions/include/interface/enable-arp-announce.xml.i new file mode 100644 index 0000000..cf02fce --- /dev/null +++ b/interface-definitions/include/interface/enable-arp-announce.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/enable-arp-announce.xml.i --> +<leafNode name="enable-arp-announce"> + <properties> + <help>Enable ARP announce on this interface</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/enable-arp-ignore.xml.i b/interface-definitions/include/interface/enable-arp-ignore.xml.i new file mode 100644 index 0000000..5bb444f --- /dev/null +++ b/interface-definitions/include/interface/enable-arp-ignore.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/enable-arp-ignore.xml.i --> +<leafNode name="enable-arp-ignore"> + <properties> + <help>Enable ARP ignore on this interface</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/enable-directed-broadcast.xml.i b/interface-definitions/include/interface/enable-directed-broadcast.xml.i new file mode 100644 index 0000000..a873958 --- /dev/null +++ b/interface-definitions/include/interface/enable-directed-broadcast.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/enable-directed-broadcast.xml.i --> +<leafNode name="enable-directed-broadcast"> + <properties> + <help>Enable directed broadcast forwarding on this interface</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/enable-proxy-arp.xml.i b/interface-definitions/include/interface/enable-proxy-arp.xml.i new file mode 100644 index 0000000..27e497f --- /dev/null +++ b/interface-definitions/include/interface/enable-proxy-arp.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/enable-proxy-arp.xml.i --> +<leafNode name="enable-proxy-arp"> + <properties> + <help>Enable proxy-arp on this interface</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/evpn-mh-uplink.xml.i b/interface-definitions/include/interface/evpn-mh-uplink.xml.i new file mode 100644 index 0000000..5f7fe1b --- /dev/null +++ b/interface-definitions/include/interface/evpn-mh-uplink.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/evpn-mh-uplink.xml.i --> +<leafNode name="uplink"> + <properties> + <help>Uplink to the VXLAN core</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/hw-id.xml.i b/interface-definitions/include/interface/hw-id.xml.i new file mode 100644 index 0000000..a3a1eec --- /dev/null +++ b/interface-definitions/include/interface/hw-id.xml.i @@ -0,0 +1,14 @@ +<!-- include start from interface/hw-id.xml.i --> +<leafNode name="hw-id"> + <properties> + <help>Associate Ethernet Interface with given Media Access Control (MAC) address</help> + <valueHelp> + <format>macaddr</format> + <description>Hardware (MAC) address</description> + </valueHelp> + <constraint> + <validator name="mac-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/inbound-interface.xml.i b/interface-definitions/include/interface/inbound-interface.xml.i new file mode 100644 index 0000000..96ade33 --- /dev/null +++ b/interface-definitions/include/interface/inbound-interface.xml.i @@ -0,0 +1,10 @@ +<!-- include start from interface/inbound-interface.xml.i --> +<leafNode name="inbound-interface"> + <properties> + <help>Inbound Interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/ipv4-options.xml.i b/interface-definitions/include/interface/ipv4-options.xml.i new file mode 100644 index 0000000..eda77e8 --- /dev/null +++ b/interface-definitions/include/interface/ipv4-options.xml.i @@ -0,0 +1,20 @@ +<!-- include start from interface/ipv4-options.xml.i --> +<node name="ip"> + <properties> + <help>IPv4 routing parameters</help> + </properties> + <children> + #include <include/interface/adjust-mss.xml.i> + #include <include/interface/arp-cache-timeout.xml.i> + #include <include/interface/disable-arp-filter.xml.i> + #include <include/interface/disable-forwarding.xml.i> + #include <include/interface/enable-directed-broadcast.xml.i> + #include <include/interface/enable-arp-accept.xml.i> + #include <include/interface/enable-arp-announce.xml.i> + #include <include/interface/enable-arp-ignore.xml.i> + #include <include/interface/enable-proxy-arp.xml.i> + #include <include/interface/proxy-arp-pvlan.xml.i> + #include <include/interface/source-validation.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/interface/ipv6-accept-dad.xml.i b/interface-definitions/include/interface/ipv6-accept-dad.xml.i new file mode 100644 index 0000000..7554b27 --- /dev/null +++ b/interface-definitions/include/interface/ipv6-accept-dad.xml.i @@ -0,0 +1,20 @@ +<!-- include start from interface/ipv6-accept-dad.xml.i --> +<leafNode name="accept-dad"> + <properties> + <help>Accept Duplicate Address Detection</help> + <valueHelp> + <format>0</format> + <description>Disable DAD</description> + </valueHelp> + <valueHelp> + <format>1</format> + <description>Enable DAD</description> + </valueHelp> + <valueHelp> + <format>2</format> + <description>Enable DAD - disable IPv6 if MAC-based duplicate link-local address found</description> + </valueHelp> + </properties> + <defaultValue>1</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/ipv6-address-autoconf.xml.i b/interface-definitions/include/interface/ipv6-address-autoconf.xml.i new file mode 100644 index 0000000..cd1483b --- /dev/null +++ b/interface-definitions/include/interface/ipv6-address-autoconf.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/ipv6-address-autoconf.xml.i --> +<leafNode name="autoconf"> + <properties> + <help>Enable acquisition of IPv6 address using stateless autoconfig (SLAAC)</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/ipv6-address-eui64.xml.i b/interface-definitions/include/interface/ipv6-address-eui64.xml.i new file mode 100644 index 0000000..fe1f43d --- /dev/null +++ b/interface-definitions/include/interface/ipv6-address-eui64.xml.i @@ -0,0 +1,16 @@ +<!-- include start from interface/ipv6-address-eui64.xml.i --> +<leafNode name="eui64"> + <properties> + <help>Prefix for IPv6 address with MAC-based EUI-64</help> + <valueHelp> + <format><h:h:h:h:h:h:h:h/64></format> + <description>IPv6 /64 network</description> + </valueHelp> + <constraint> + <validator name="ipv6-eui64-prefix"/> + </constraint> + <constraintErrorMessage>EUI64 prefix length must be 64</constraintErrorMessage> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/ipv6-address-no-default-link-local.xml.i b/interface-definitions/include/interface/ipv6-address-no-default-link-local.xml.i new file mode 100644 index 0000000..012490e --- /dev/null +++ b/interface-definitions/include/interface/ipv6-address-no-default-link-local.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/ipv6-address-no-default-link-local.xml.i --> +<leafNode name="no-default-link-local"> + <properties> + <help>Remove the default link-local address from the interface</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/ipv6-address.xml.i b/interface-definitions/include/interface/ipv6-address.xml.i new file mode 100644 index 0000000..e1bdf02 --- /dev/null +++ b/interface-definitions/include/interface/ipv6-address.xml.i @@ -0,0 +1,12 @@ +<!-- include start from interface/ipv6-address.xml.i --> +<node name="address"> + <properties> + <help>IPv6 address configuration modes</help> + </properties> + <children> + #include <include/interface/ipv6-address-autoconf.xml.i> + #include <include/interface/ipv6-address-eui64.xml.i> + #include <include/interface/ipv6-address-no-default-link-local.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/interface/ipv6-dup-addr-detect-transmits.xml.i b/interface-definitions/include/interface/ipv6-dup-addr-detect-transmits.xml.i new file mode 100644 index 0000000..3b9294d --- /dev/null +++ b/interface-definitions/include/interface/ipv6-dup-addr-detect-transmits.xml.i @@ -0,0 +1,19 @@ +<!-- include start from interface/ipv6-dup-addr-detect-transmits.xml.i --> +<leafNode name="dup-addr-detect-transmits"> + <properties> + <help>Number of NS messages to send while performing DAD</help> + <valueHelp> + <format>u32:0</format> + <description>Disable Duplicate Address Dectection (DAD)</description> + </valueHelp> + <valueHelp> + <format>u32:1-n</format> + <description>Number of NS messages to send while performing DAD</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--non-negative"/> + </constraint> + </properties> + <defaultValue>1</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/ipv6-options.xml.i b/interface-definitions/include/interface/ipv6-options.xml.i new file mode 100644 index 0000000..ec6ec64 --- /dev/null +++ b/interface-definitions/include/interface/ipv6-options.xml.i @@ -0,0 +1,16 @@ +<!-- include start from interface/ipv6-options.xml.i --> +<node name="ipv6"> + <properties> + <help>IPv6 routing parameters</help> + </properties> + <children> + #include <include/interface/adjust-mss.xml.i> + #include <include/interface/base-reachable-time.xml.i> + #include <include/interface/disable-forwarding.xml.i> + #include <include/interface/ipv6-accept-dad.xml.i> + #include <include/interface/ipv6-address.xml.i> + #include <include/interface/ipv6-dup-addr-detect-transmits.xml.i> + #include <include/interface/source-validation.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/interface/mac-multi.xml.i b/interface-definitions/include/interface/mac-multi.xml.i new file mode 100644 index 0000000..458372e --- /dev/null +++ b/interface-definitions/include/interface/mac-multi.xml.i @@ -0,0 +1,15 @@ +<!-- include start from interface/mac-multi.xml.i --> +<leafNode name="mac"> + <properties> + <help>Media Access Control (MAC) address</help> + <valueHelp> + <format>macaddr</format> + <description>Hardware (MAC) address</description> + </valueHelp> + <constraint> + <validator name="mac-address"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/mac.xml.i b/interface-definitions/include/interface/mac.xml.i new file mode 100644 index 0000000..705330d --- /dev/null +++ b/interface-definitions/include/interface/mac.xml.i @@ -0,0 +1,14 @@ +<!-- include start from interface/mac.xml.i --> +<leafNode name="mac"> + <properties> + <help>Media Access Control (MAC) address</help> + <valueHelp> + <format>macaddr</format> + <description>Hardware (MAC) address</description> + </valueHelp> + <constraint> + <validator name="mac-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/macsec-key.xml.i b/interface-definitions/include/interface/macsec-key.xml.i new file mode 100644 index 0000000..5a857a6 --- /dev/null +++ b/interface-definitions/include/interface/macsec-key.xml.i @@ -0,0 +1,15 @@ +<!-- include start from interface/macsec-key.xml.i --> +<leafNode name="key"> + <properties> + <help>MACsec static key</help> + <valueHelp> + <format>txt</format> + <description>16-byte (128-bit) hex-string (32 hex-digits) for gcm-aes-128 or 32-byte (256-bit) hex-string (64 hex-digits) for gcm-aes-256</description> + </valueHelp> + <constraint> + <regex>[A-Fa-f0-9]{32}</regex> + <regex>[A-Fa-f0-9]{64}</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/mirror.xml.i b/interface-definitions/include/interface/mirror.xml.i new file mode 100644 index 0000000..903c627 --- /dev/null +++ b/interface-definitions/include/interface/mirror.xml.i @@ -0,0 +1,33 @@ +<!-- include start from interface/mirror.xml.i --> +<node name="mirror"> + <properties> + <help>Mirror ingress/egress packets</help> + </properties> + <children> + <leafNode name="ingress"> + <properties> + <help>Mirror ingress traffic to destination interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Destination interface name</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="egress"> + <properties> + <help>Mirror egress traffic to destination interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Destination interface name</description> + </valueHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/interface/mtu-1200-16000.xml.i b/interface-definitions/include/interface/mtu-1200-16000.xml.i new file mode 100644 index 0000000..fab053f --- /dev/null +++ b/interface-definitions/include/interface/mtu-1200-16000.xml.i @@ -0,0 +1,16 @@ +<!-- include start from interface/mtu-1200-16000.xml.i --> +<leafNode name="mtu"> + <properties> + <help>Maximum Transmission Unit (MTU)</help> + <valueHelp> + <format>u32:1200-16000</format> + <description>Maximum Transmission Unit in byte</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1200-16000"/> + </constraint> + <constraintErrorMessage>MTU must be between 1200 and 16000</constraintErrorMessage> + </properties> + <defaultValue>1500</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/mtu-1450-16000.xml.i b/interface-definitions/include/interface/mtu-1450-16000.xml.i new file mode 100644 index 0000000..1e71eab --- /dev/null +++ b/interface-definitions/include/interface/mtu-1450-16000.xml.i @@ -0,0 +1,16 @@ +<!-- include start from interface/mtu-1450-16000.xml.i --> +<leafNode name="mtu"> + <properties> + <help>Maximum Transmission Unit (MTU)</help> + <valueHelp> + <format>u32:1450-16000</format> + <description>Maximum Transmission Unit in byte</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1450-16000"/> + </constraint> + <constraintErrorMessage>MTU must be between 1450 and 16000</constraintErrorMessage> + </properties> + <defaultValue>1500</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/mtu-64-8024.xml.i b/interface-definitions/include/interface/mtu-64-8024.xml.i new file mode 100644 index 0000000..30c77f7 --- /dev/null +++ b/interface-definitions/include/interface/mtu-64-8024.xml.i @@ -0,0 +1,16 @@ +<!-- include start from interface/mtu-68-8024.xml.i --> +<leafNode name="mtu"> + <properties> + <help>Maximum Transmission Unit (MTU)</help> + <valueHelp> + <format>u32:64-8024</format> + <description>Maximum Transmission Unit in byte</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 64-8024"/> + </constraint> + <constraintErrorMessage>MTU must be between 64 and 8024</constraintErrorMessage> + </properties> + <defaultValue>1500</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/mtu-68-1500.xml.i b/interface-definitions/include/interface/mtu-68-1500.xml.i new file mode 100644 index 0000000..693e0be --- /dev/null +++ b/interface-definitions/include/interface/mtu-68-1500.xml.i @@ -0,0 +1,16 @@ +<!-- include start from interface/mtu-68-1500.xml.i --> +<leafNode name="mtu"> + <properties> + <help>Maximum Transmission Unit (MTU)</help> + <valueHelp> + <format>u32:68-1500</format> + <description>Maximum Transmission Unit in byte</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 68-1500"/> + </constraint> + <constraintErrorMessage>MTU must be between 68 and 1500</constraintErrorMessage> + </properties> + <defaultValue>1500</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/mtu-68-16000.xml.i b/interface-definitions/include/interface/mtu-68-16000.xml.i new file mode 100644 index 0000000..df1b7b7 --- /dev/null +++ b/interface-definitions/include/interface/mtu-68-16000.xml.i @@ -0,0 +1,15 @@ +<!-- include start from interface/mtu-68-16000.xml.i --> +<leafNode name="mtu"> + <properties> + <help>Maximum Transmission Unit (MTU)</help> + <valueHelp> + <format>u32:68-16000</format> + <description>Maximum Transmission Unit in byte</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 68-16000"/> + </constraint> + <constraintErrorMessage>MTU must be between 68 and 16000</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/netns.xml.i b/interface-definitions/include/interface/netns.xml.i new file mode 100644 index 0000000..fd6da8f --- /dev/null +++ b/interface-definitions/include/interface/netns.xml.i @@ -0,0 +1,14 @@ +<!-- include start from interface/netns.xml.i --> +<leafNode name="netns"> + <properties> + <help>Network namespace name</help> + <valueHelp> + <format>txt</format> + <description>Network namespace name</description> + </valueHelp> + <completionHelp> + <path>netns name</path> + </completionHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/no-default-route.xml.i b/interface-definitions/include/interface/no-default-route.xml.i new file mode 100644 index 0000000..307fcff --- /dev/null +++ b/interface-definitions/include/interface/no-default-route.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/dhcp-options.xml.i --> +<leafNode name="no-default-route"> + <properties> + <help>Do not install default route to system</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/no-peer-dns.xml.i b/interface-definitions/include/interface/no-peer-dns.xml.i new file mode 100644 index 0000000..d663f04 --- /dev/null +++ b/interface-definitions/include/interface/no-peer-dns.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/no-peer-dns.xml.i --> +<leafNode name="no-peer-dns"> + <properties> + <help>Do not use DNS servers provided by the peer</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/parameters-df.xml.i b/interface-definitions/include/interface/parameters-df.xml.i new file mode 100644 index 0000000..82436b5 --- /dev/null +++ b/interface-definitions/include/interface/parameters-df.xml.i @@ -0,0 +1,26 @@ +<!-- include start from interface/parameters-df.xml.i --> +<leafNode name="df"> + <properties> + <help>Usage of the DF (don't Fragment) bit in outgoing packets</help> + <completionHelp> + <list>set unset inherit</list> + </completionHelp> + <valueHelp> + <format>set</format> + <description>Always set DF (don't fragment) bit</description> + </valueHelp> + <valueHelp> + <format>unset</format> + <description>Always unset DF (don't fragment) bit</description> + </valueHelp> + <valueHelp> + <format>inherit</format> + <description>Copy from the original IP header</description> + </valueHelp> + <constraint> + <regex>(set|unset|inherit)</regex> + </constraint> + </properties> + <defaultValue>unset</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/parameters-flowlabel.xml.i b/interface-definitions/include/interface/parameters-flowlabel.xml.i new file mode 100644 index 0000000..b2e8821 --- /dev/null +++ b/interface-definitions/include/interface/parameters-flowlabel.xml.i @@ -0,0 +1,22 @@ +<!-- include start from interface/parameters-flowlabel.xml.i --> +<leafNode name="flowlabel"> + <properties> + <help>Specifies the flow label to use in outgoing packets</help> + <completionHelp> + <list>inherit</list> + </completionHelp> + <valueHelp> + <format>inherit</format> + <description>Copy field from original header</description> + </valueHelp> + <valueHelp> + <format>0x0-0x0fffff</format> + <description>Tunnel key, or hex value</description> + </valueHelp> + <constraint> + <regex>((0x){0,1}(0?[0-9A-Fa-f]{1,5})|inherit)</regex> + </constraint> + <constraintErrorMessage>Must be 'inherit' or a number</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/parameters-innerproto.xml.i b/interface-definitions/include/interface/parameters-innerproto.xml.i new file mode 100644 index 0000000..9cafebd --- /dev/null +++ b/interface-definitions/include/interface/parameters-innerproto.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/parameters-innerproto.xml.i --> +<leafNode name="innerproto"> + <properties> + <help>Use IPv4 as inner protocol instead of Ethernet</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/parameters-key.xml.i b/interface-definitions/include/interface/parameters-key.xml.i new file mode 100644 index 0000000..25a6c03 --- /dev/null +++ b/interface-definitions/include/interface/parameters-key.xml.i @@ -0,0 +1,15 @@ +<!-- include start from interface/parameters-key.xml.i --> +<leafNode name="key"> + <properties> + <help>Tunnel key (only GRE tunnels)</help> + <valueHelp> + <format>u32</format> + <description>Tunnel key</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + <constraintErrorMessage>key must be between 0-4294967295</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/parameters-tos.xml.i b/interface-definitions/include/interface/parameters-tos.xml.i new file mode 100644 index 0000000..1b342a4 --- /dev/null +++ b/interface-definitions/include/interface/parameters-tos.xml.i @@ -0,0 +1,16 @@ +<!-- include start from interface/tunnel-parameters-tos.xml.i --> +<leafNode name="tos"> + <properties> + <help>Specifies TOS value to use in outgoing packets</help> + <valueHelp> + <format>u32:0-99</format> + <description>Type of Service (TOS)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-99"/> + </constraint> + <constraintErrorMessage>TOS must be between 0 and 99</constraintErrorMessage> + </properties> + <defaultValue>inherit</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/parameters-ttl.xml.i b/interface-definitions/include/interface/parameters-ttl.xml.i new file mode 100644 index 0000000..ade33b4 --- /dev/null +++ b/interface-definitions/include/interface/parameters-ttl.xml.i @@ -0,0 +1,20 @@ +<!-- include start from interface/parameters-ttl.xml.i --> +<leafNode name="ttl"> + <properties> + <help>Specifies TTL value to use in outgoing packets</help> + <valueHelp> + <format>u32:0</format> + <description>Inherit - copy value from original IP header</description> + </valueHelp> + <valueHelp> + <format>u32:1-255</format> + <description>Time to Live</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + <constraintErrorMessage>TTL must be between 0 and 255</constraintErrorMessage> + </properties> + <defaultValue>0</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/per-client-thread.xml.i b/interface-definitions/include/interface/per-client-thread.xml.i new file mode 100644 index 0000000..2fd19b5 --- /dev/null +++ b/interface-definitions/include/interface/per-client-thread.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/per-client-thread.xml.i --> +<leafNode name="per-client-thread"> + <properties> + <help>Process traffic from each client in a dedicated thread</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/proxy-arp-pvlan.xml.i b/interface-definitions/include/interface/proxy-arp-pvlan.xml.i new file mode 100644 index 0000000..c00b2fe --- /dev/null +++ b/interface-definitions/include/interface/proxy-arp-pvlan.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/proxy-arp-pvlan.xml.i --> +<leafNode name="proxy-arp-pvlan"> + <properties> + <help>Enable private VLAN proxy ARP on this interface</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/redirect.xml.i b/interface-definitions/include/interface/redirect.xml.i new file mode 100644 index 0000000..9b41cd8 --- /dev/null +++ b/interface-definitions/include/interface/redirect.xml.i @@ -0,0 +1,17 @@ +<!-- include start from interface/redirect.xml.i --> +<leafNode name="redirect"> + <properties> + <help>Redirect incoming packet to destination</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Destination interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/source-validation.xml.i b/interface-definitions/include/interface/source-validation.xml.i new file mode 100644 index 0000000..fc9a7d3 --- /dev/null +++ b/interface-definitions/include/interface/source-validation.xml.i @@ -0,0 +1,25 @@ +<!-- include start from interface/source-validation.xml.i --> +<leafNode name="source-validation"> + <properties> + <help>Source validation by reversed path (RFC3704)</help> + <completionHelp> + <list>strict loose disable</list> + </completionHelp> + <valueHelp> + <format>strict</format> + <description>Enable Strict Reverse Path Forwarding as defined in RFC3704</description> + </valueHelp> + <valueHelp> + <format>loose</format> + <description>Enable Loose Reverse Path Forwarding as defined in RFC3704</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>No source validation</description> + </valueHelp> + <constraint> + <regex>(strict|loose|disable)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/tunnel-remote-multi.xml.i b/interface-definitions/include/interface/tunnel-remote-multi.xml.i new file mode 100644 index 0000000..f672087 --- /dev/null +++ b/interface-definitions/include/interface/tunnel-remote-multi.xml.i @@ -0,0 +1,19 @@ +<!-- include start from interface/tunnel-remote-multi.xml.i --> +<leafNode name="remote"> + <properties> + <help>Tunnel remote address</help> + <valueHelp> + <format>ipv4</format> + <description>Tunnel remote IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Tunnel remote IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/tunnel-remote.xml.i b/interface-definitions/include/interface/tunnel-remote.xml.i new file mode 100644 index 0000000..2a8891b --- /dev/null +++ b/interface-definitions/include/interface/tunnel-remote.xml.i @@ -0,0 +1,18 @@ +<!-- include start from interface/tunnel-remote.xml.i --> +<leafNode name="remote"> + <properties> + <help>Tunnel remote address</help> + <valueHelp> + <format>ipv4</format> + <description>Tunnel remote IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Tunnel remote IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i new file mode 100644 index 0000000..02e7ab0 --- /dev/null +++ b/interface-definitions/include/interface/vif-s.xml.i @@ -0,0 +1,55 @@ +<!-- include start from interface/vif-s.xml.i --> +<tagNode name="vif-s"> + <properties> + <help>QinQ TAG-S Virtual Local Area Network (VLAN) ID</help> + <valueHelp> + <format>u32:0-4094</format> + <description>QinQ Virtual Local Area Network (VLAN) ID</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4094"/> + </constraint> + <constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> + #include <include/interface/dhcp-options.xml.i> + #include <include/interface/dhcpv6-options.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/vlan-protocol.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/mirror.xml.i> + #include <include/interface/mtu-68-16000.xml.i> + <tagNode name="vif-c"> + <properties> + <help>QinQ TAG-C Virtual Local Area Network (VLAN) ID</help> + <constraint> + <validator name="numeric" argument="--range 0-4094"/> + </constraint> + <constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> + #include <include/interface/dhcp-options.xml.i> + #include <include/interface/dhcpv6-options.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/mirror.xml.i> + #include <include/interface/mtu-68-16000.xml.i> + #include <include/interface/redirect.xml.i> + #include <include/interface/vrf.xml.i> + </children> + </tagNode> + #include <include/interface/redirect.xml.i> + #include <include/interface/vrf.xml.i> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/vif.xml.i b/interface-definitions/include/interface/vif.xml.i new file mode 100644 index 0000000..ec3921b --- /dev/null +++ b/interface-definitions/include/interface/vif.xml.i @@ -0,0 +1,56 @@ +<!-- include start from interface/vif.xml.i --> +<tagNode name="vif"> + <properties> + <help>Virtual Local Area Network (VLAN) ID</help> + <valueHelp> + <format>u32:0-4094</format> + <description>Virtual Local Area Network (VLAN) ID</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4094"/> + </constraint> + <constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> + #include <include/interface/dhcp-options.xml.i> + #include <include/interface/dhcpv6-options.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/disable.xml.i> + <leafNode name="egress-qos"> + <properties> + <help>VLAN egress QoS</help> + <valueHelp> + <format>txt</format> + <description>Format for qos mapping, e.g.: '0:1 1:6 7:6'</description> + </valueHelp> + <constraint> + <regex>[:0-7 ]+</regex> + </constraint> + <constraintErrorMessage>QoS mapping should be in the format of '0:7 2:3' with numbers 0-9</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="ingress-qos"> + <properties> + <help>VLAN ingress QoS</help> + <valueHelp> + <format>txt</format> + <description>Format for qos mapping, e.g.: '0:1 1:6 7:6'</description> + </valueHelp> + <constraint> + <regex>[:0-7 ]+</regex> + </constraint> + <constraintErrorMessage>QoS mapping should be in the format of '0:7 2:3' with numbers 0-9</constraintErrorMessage> + </properties> + </leafNode> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/mirror.xml.i> + #include <include/interface/mtu-68-16000.xml.i> + #include <include/interface/redirect.xml.i> + #include <include/interface/vrf.xml.i> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/vlan-protocol.xml.i b/interface-definitions/include/interface/vlan-protocol.xml.i new file mode 100644 index 0000000..2fe8d65 --- /dev/null +++ b/interface-definitions/include/interface/vlan-protocol.xml.i @@ -0,0 +1,23 @@ +<!-- include start from interface/vif.xml.i --> +<leafNode name="protocol"> + <properties> + <help>Protocol used for service VLAN (default: 802.1ad)</help> + <completionHelp> + <list>802.1ad 802.1q</list> + </completionHelp> + <valueHelp> + <format>802.1ad</format> + <description>Provider Bridging (IEEE 802.1ad, Q-inQ), ethertype 0x88a8</description> + </valueHelp> + <valueHelp> + <format>802.1q</format> + <description>VLAN-tagged frame (IEEE 802.1q), ethertype 0x8100</description> + </valueHelp> + <constraint> + <regex>(802.1q|802.1ad)</regex> + </constraint> + <constraintErrorMessage>Ethertype must be 802.1ad or 802.1q</constraintErrorMessage> + </properties> + <defaultValue>802.1ad</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/vrf.xml.i b/interface-definitions/include/interface/vrf.xml.i new file mode 100644 index 0000000..ef0058f --- /dev/null +++ b/interface-definitions/include/interface/vrf.xml.i @@ -0,0 +1,15 @@ +<!-- include start from interface/vrf.xml.i --> +<leafNode name="vrf"> + <properties> + <help>VRF instance name</help> + <valueHelp> + <format>txt</format> + <description>VRF instance name</description> + </valueHelp> + <completionHelp> + <path>vrf name</path> + </completionHelp> + #include <include/constraint/vrf.xml.i> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ip-protocol.xml.i b/interface-definitions/include/ip-protocol.xml.i new file mode 100644 index 0000000..ce93450 --- /dev/null +++ b/interface-definitions/include/ip-protocol.xml.i @@ -0,0 +1,17 @@ +<!-- include start from ip-protocol.xml.i --> +<leafNode name="protocol"> + <properties> + <help>Protocol</help> + <valueHelp> + <format>txt</format> + <description>Protocol name</description> + </valueHelp> + <completionHelp> + <script>${vyos_completion_dir}/list_protocols.sh</script> + </completionHelp> + <constraint> + <validator name="ip-protocol"/> + </constraint> + </properties> +</leafNode> +<!-- include end from ip-protocol.xml.i --> diff --git a/interface-definitions/include/ipsec/authentication-id.xml.i b/interface-definitions/include/ipsec/authentication-id.xml.i new file mode 100644 index 0000000..4e0b848 --- /dev/null +++ b/interface-definitions/include/ipsec/authentication-id.xml.i @@ -0,0 +1,11 @@ +<!-- include start from ipsec/authentication-id.xml.i --> +<leafNode name="local-id"> + <properties> + <help>Local ID for peer authentication</help> + <valueHelp> + <format>txt</format> + <description>Local ID used for peer authentication</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ipsec/authentication-pre-shared-secret.xml.i b/interface-definitions/include/ipsec/authentication-pre-shared-secret.xml.i new file mode 100644 index 0000000..af26693 --- /dev/null +++ b/interface-definitions/include/ipsec/authentication-pre-shared-secret.xml.i @@ -0,0 +1,11 @@ +<!-- include start from ipsec/authentication-pre-shared-secret.xml.i --> +<leafNode name="pre-shared-secret"> + <properties> + <help>Pre-shared secret key</help> + <valueHelp> + <format>txt</format> + <description>Pre-shared secret key</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ipsec/authentication-rsa.xml.i b/interface-definitions/include/ipsec/authentication-rsa.xml.i new file mode 100644 index 0000000..0a364e8 --- /dev/null +++ b/interface-definitions/include/ipsec/authentication-rsa.xml.i @@ -0,0 +1,30 @@ +<!-- include start from ipsec/authentication-rsa.xml.i --> +<node name="rsa"> + <properties> + <help>RSA keys</help> + </properties> + <children> + <leafNode name="local-key"> + <properties> + <help>Name of PKI key-pair with local private key</help> + <completionHelp> + <path>pki key-pair</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="passphrase"> + <properties> + <help>Local private key passphrase</help> + </properties> + </leafNode> + <leafNode name="remote-key"> + <properties> + <help>Name of PKI key-pair with remote public key</help> + <completionHelp> + <path>pki key-pair</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/ipsec/authentication-x509.xml.i b/interface-definitions/include/ipsec/authentication-x509.xml.i new file mode 100644 index 0000000..1d04c94 --- /dev/null +++ b/interface-definitions/include/ipsec/authentication-x509.xml.i @@ -0,0 +1,11 @@ +<!-- include start from ipsec/authentication-x509.xml.i --> +<node name="x509"> + <properties> + <help>X.509 certificate</help> + </properties> + <children> + #include <include/pki/certificate-key.xml.i> + #include <include/pki/ca-certificate-multi.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/ipsec/bind.xml.i b/interface-definitions/include/ipsec/bind.xml.i new file mode 100644 index 0000000..edc46d4 --- /dev/null +++ b/interface-definitions/include/ipsec/bind.xml.i @@ -0,0 +1,10 @@ +<!-- include start from ipsec/bind.xml.i --> +<leafNode name="bind"> + <properties> + <help>VTI tunnel interface associated with this configuration</help> + <completionHelp> + <path>interfaces vti</path> + </completionHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ipsec/esp-group.xml.i b/interface-definitions/include/ipsec/esp-group.xml.i new file mode 100644 index 0000000..5e5d819 --- /dev/null +++ b/interface-definitions/include/ipsec/esp-group.xml.i @@ -0,0 +1,10 @@ +<!-- include start from ipsec/esp-group.xml.i --> +<leafNode name="esp-group"> + <properties> + <help>Encapsulating Security Payloads (ESP) group name</help> + <completionHelp> + <path>vpn ipsec esp-group</path> + </completionHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ipsec/ike-group.xml.i b/interface-definitions/include/ipsec/ike-group.xml.i new file mode 100644 index 0000000..f7649ed --- /dev/null +++ b/interface-definitions/include/ipsec/ike-group.xml.i @@ -0,0 +1,10 @@ +<!-- include start from ipsec/ike-group.xml.i --> +<leafNode name="ike-group"> + <properties> + <help>Internet Key Exchange (IKE) group name</help> + <completionHelp> + <path>vpn ipsec ike-group</path> + </completionHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ipsec/local-address.xml.i b/interface-definitions/include/ipsec/local-address.xml.i new file mode 100644 index 0000000..71f5149 --- /dev/null +++ b/interface-definitions/include/ipsec/local-address.xml.i @@ -0,0 +1,27 @@ +<!-- include start from ipsec/local-address.xml.i --> +<leafNode name="local-address"> + <properties> + <help>IPv4 or IPv6 address of a local interface to use for VPN</help> + <completionHelp> + <list>any</list> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address of a local interface for VPN</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of a local interface for VPN</description> + </valueHelp> + <valueHelp> + <format>any</format> + <description>Allow any IPv4 address present on the system to be used for VPN</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + <regex>(any)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ipsec/local-traffic-selector.xml.i b/interface-definitions/include/ipsec/local-traffic-selector.xml.i new file mode 100644 index 0000000..9ae67f5 --- /dev/null +++ b/interface-definitions/include/ipsec/local-traffic-selector.xml.i @@ -0,0 +1,28 @@ +<!-- include start from ipsec/local-traffic-selector.xml.i --> +<node name="local"> + <properties> + <help>Local parameters for interesting traffic</help> + </properties> + <children> + #include <include/port-number.xml.i> + <leafNode name="prefix"> + <properties> + <help>Local IPv4 or IPv6 prefix</help> + <valueHelp> + <format>ipv4net</format> + <description>Local IPv4 prefix</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>Local IPv6 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv6-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/ipsec/remote-address.xml.i b/interface-definitions/include/ipsec/remote-address.xml.i new file mode 100644 index 0000000..91decba --- /dev/null +++ b/interface-definitions/include/ipsec/remote-address.xml.i @@ -0,0 +1,29 @@ +<!-- include start from ipsec/remote-address.xml.i --> +<leafNode name="remote-address"> + <properties> + <help>IPv4 or IPv6 address of the remote peer</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address of the remote peer</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of the remote peer</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>Fully qualified domain name of the remote peer</description> + </valueHelp> + <valueHelp> + <format>any</format> + <description>Allow any IP address of the remote peer</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + <validator name="fqdn"/> + <regex>(any)</regex> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ipsec/replay-window.xml.i b/interface-definitions/include/ipsec/replay-window.xml.i new file mode 100644 index 0000000..f35ed55 --- /dev/null +++ b/interface-definitions/include/ipsec/replay-window.xml.i @@ -0,0 +1,19 @@ +<!-- include start from ipsec/replay-window.xml.i --> +<leafNode name="replay-window"> + <properties> + <help>IPsec replay window to configure for this CHILD_SA</help> + <valueHelp> + <format>u32:0</format> + <description>Disable IPsec replay protection</description> + </valueHelp> + <valueHelp> + <format>u32:1-2040</format> + <description>Replay window size in packets</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2040"/> + </constraint> + </properties> + <defaultValue>32</defaultValue> + </leafNode> + <!-- include end --> diff --git a/interface-definitions/include/ipv4-address-prefix-range.xml.i b/interface-definitions/include/ipv4-address-prefix-range.xml.i new file mode 100644 index 0000000..aadc6aa --- /dev/null +++ b/interface-definitions/include/ipv4-address-prefix-range.xml.i @@ -0,0 +1,39 @@ +<!-- include start from ipv4-address-prefix-range.xml.i --> +<leafNode name="address"> + <properties> + <help>IP address, subnet, or range</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <valueHelp> + <format>ipv4range</format> + <description>IPv4 address range to match</description> + </valueHelp> + <valueHelp> + <format>!ipv4</format> + <description>Match everything except the specified address</description> + </valueHelp> + <valueHelp> + <format>!ipv4net</format> + <description>Match everything except the specified prefix</description> + </valueHelp> + <valueHelp> + <format>!ipv4range</format> + <description>Match everything except the specified range</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv4-prefix"/> + <validator name="ipv4-range"/> + <validator name="ipv4-address-exclude"/> + <validator name="ipv4-prefix-exclude"/> + <validator name="ipv4-range-exclude"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ipv4-address-prefix.xml.i b/interface-definitions/include/ipv4-address-prefix.xml.i new file mode 100644 index 0000000..f5be6f1 --- /dev/null +++ b/interface-definitions/include/ipv4-address-prefix.xml.i @@ -0,0 +1,19 @@ +<!-- include start from ipv4-address-prefix.xml.i --> +<leafNode name="address"> + <properties> + <help>IP address, prefix</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv4-prefix"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/isis/default-information-level.xml.i b/interface-definitions/include/isis/default-information-level.xml.i new file mode 100644 index 0000000..5ade72a --- /dev/null +++ b/interface-definitions/include/isis/default-information-level.xml.i @@ -0,0 +1,32 @@ +<!-- include start from isis/default-information-level.xml.i --> +<node name="level-1"> + <properties> + <help>Distribute default route into level-1</help> + </properties> + <children> + <leafNode name="always"> + <properties> + <help>Always advertise default route</help> + <valueless/> + </properties> + </leafNode> + #include <include/isis/metric.xml.i> + #include <include/route-map.xml.i> + </children> +</node> +<node name="level-2"> + <properties> + <help>Distribute default route into level-2</help> + </properties> + <children> + <leafNode name="always"> + <properties> + <help>Always advertise default route</help> + <valueless/> + </properties> + </leafNode> + #include <include/isis/metric.xml.i> + #include <include/route-map.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/isis/ldp-sync-holddown.xml.i b/interface-definitions/include/isis/ldp-sync-holddown.xml.i new file mode 100644 index 0000000..15ac26f --- /dev/null +++ b/interface-definitions/include/isis/ldp-sync-holddown.xml.i @@ -0,0 +1,14 @@ +<!-- include start from isis/ldp-sync-holddown.xml.i --> +<leafNode name="holddown"> + <properties> + <help>Hold down timer for LDP-IGP cost restoration</help> + <valueHelp> + <format>u32:0-10000</format> + <description>Time to wait in seconds for LDP-IGP synchronization to occur before restoring interface cost</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-10000"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/isis/ldp-sync-interface.xml.i b/interface-definitions/include/isis/ldp-sync-interface.xml.i new file mode 100644 index 0000000..222a352 --- /dev/null +++ b/interface-definitions/include/isis/ldp-sync-interface.xml.i @@ -0,0 +1,11 @@ +<!-- include start from isis/ldp-igp-sync.xml.i --> +<node name="ldp-sync"> + <properties> + <help>LDP-IGP synchronization configuration for interface</help> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/isis/ldp-sync-holddown.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/isis/ldp-sync-protocol.xml.i b/interface-definitions/include/isis/ldp-sync-protocol.xml.i new file mode 100644 index 0000000..b2e696a --- /dev/null +++ b/interface-definitions/include/isis/ldp-sync-protocol.xml.i @@ -0,0 +1,10 @@ +<!-- include start from isis/ldp-igp-sync.xml.i --> +<node name="ldp-sync"> + <properties> + <help>Protocol wide LDP-IGP synchronization configuration</help> + </properties> + <children> + #include <include/isis/ldp-sync-holddown.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/isis/level-1-2-leaf.xml.i b/interface-definitions/include/isis/level-1-2-leaf.xml.i new file mode 100644 index 0000000..3703da1 --- /dev/null +++ b/interface-definitions/include/isis/level-1-2-leaf.xml.i @@ -0,0 +1,13 @@ +<!-- include start from isis/level-1-2-leaf.xml.i --> +<leafNode name="level-1"> + <properties> + <help>Match on IS-IS level-1 routes</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="level-2"> + <properties> + <help>Match on IS-IS level-2 routes</help> + <valueless/> + </properties> +</leafNode>
\ No newline at end of file diff --git a/interface-definitions/include/isis/lfa-local.xml.i b/interface-definitions/include/isis/lfa-local.xml.i new file mode 100644 index 0000000..c5bf6a3 --- /dev/null +++ b/interface-definitions/include/isis/lfa-local.xml.i @@ -0,0 +1,128 @@ +<!-- include start from isis/lfa-local.xml.i --> +<node name="local"> + <properties> + <help>Local loop free alternate options</help> + </properties> + <children> + <node name="load-sharing"> + <properties> + <help>Load share prefixes across multiple backups</help> + </properties> + <children> + <node name="disable"> + <properties> + <help>Disable load sharing</help> + </properties> + <children> + #include <include/isis/level-1-2-leaf.xml.i> + </children> + </node> + </children> + </node> + <node name="priority-limit"> + <properties> + <help>Limit backup computation up to the prefix priority</help> + </properties> + <children> + <node name="medium"> + <properties> + <help>Compute for critical, high, and medium priority prefixes</help> + </properties> + <children> + #include <include/isis/level-1-2-leaf.xml.i> + </children> + </node> + <node name="high"> + <properties> + <help>Compute for critical, and high priority prefixes</help> + </properties> + <children> + #include <include/isis/level-1-2-leaf.xml.i> + </children> + </node> + <node name="critical"> + <properties> + <help>Compute for critical priority prefixes only</help> + </properties> + <children> + #include <include/isis/level-1-2-leaf.xml.i> + </children> + </node> + </children> + </node> + <node name="tiebreaker"> + <properties> + <help>Configure tiebreaker for multiple backups</help> + </properties> + <children> + <node name="downstream"> + <properties> + <help>Prefer backup path via downstream node</help> + </properties> + <children> + <tagNode name="index"> + <properties> + <help>Set preference order among tiebreakers</help> + <valueHelp> + <format>u32:1-255</format> + <description>The index integer value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <children> + #include <include/isis/level-1-2-leaf.xml.i> + </children> + </tagNode> + </children> + </node> + <node name="lowest-backup-metric"> + <properties> + <help>Prefer backup path with lowest total metric</help> + </properties> + <children> + <tagNode name="index"> + <properties> + <help>Set preference order among tiebreakers</help> + <valueHelp> + <format>u32:1-255</format> + <description>The index integer value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <children> + #include <include/isis/level-1-2-leaf.xml.i> + </children> + </tagNode> + </children> + </node> + <node name="node-protecting"> + <properties> + <help>Prefer node protecting backup path</help> + </properties> + <children> + <tagNode name="index"> + <properties> + <help>Set preference order among tiebreakers</help> + <valueHelp> + <format>u32:1-255</format> + <description>The index integer value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <children> + #include <include/isis/level-1-2-leaf.xml.i> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/isis/lfa-protocol.xml.i b/interface-definitions/include/isis/lfa-protocol.xml.i new file mode 100644 index 0000000..cfb1a6d --- /dev/null +++ b/interface-definitions/include/isis/lfa-protocol.xml.i @@ -0,0 +1,11 @@ +<!-- include start from isis/lfa-protocol.xml.i --> +<node name="lfa"> + <properties> + <help>Loop free alternate functionality</help> + </properties> + <children> + #include <include/isis/lfa-remote.xml.i> + #include <include/isis/lfa-local.xml.i> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/isis/lfa-remote.xml.i b/interface-definitions/include/isis/lfa-remote.xml.i new file mode 100644 index 0000000..8434e35 --- /dev/null +++ b/interface-definitions/include/isis/lfa-remote.xml.i @@ -0,0 +1,28 @@ +<!-- include start from isis/lfa-remote.xml.i --> +<node name="remote"> + <properties> + <help>Remote loop free alternate options</help> + </properties> + <children> + <tagNode name="prefix-list"> + <properties> + <help>Filter PQ node router ID based on prefix list</help> + <completionHelp> + <path>policy prefix-list</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of IPv4/IPv6 prefix-list</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Name of prefix-list can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> + <children> + #include <include/isis/level-1-2-leaf.xml.i> + </children> + </tagNode> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/isis/metric.xml.i b/interface-definitions/include/isis/metric.xml.i new file mode 100644 index 0000000..30e2cdc --- /dev/null +++ b/interface-definitions/include/isis/metric.xml.i @@ -0,0 +1,14 @@ +<!-- include start from isis/metric.xml.i --> +<leafNode name="metric"> + <properties> + <help>Set default metric for circuit</help> + <valueHelp> + <format>u32:0-16777215</format> + <description>Default metric value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16777215"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/isis/passive.xml.i b/interface-definitions/include/isis/passive.xml.i new file mode 100644 index 0000000..6d05f8c --- /dev/null +++ b/interface-definitions/include/isis/passive.xml.i @@ -0,0 +1,8 @@ +<!-- include start from isis/passive.xml.i --> +<leafNode name="passive"> + <properties> + <help>Configure passive mode for interface</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/isis/password.xml.i b/interface-definitions/include/isis/password.xml.i new file mode 100644 index 0000000..27c3b0f --- /dev/null +++ b/interface-definitions/include/isis/password.xml.i @@ -0,0 +1,20 @@ +<!-- include start from isis/password.xml.i --> +<leafNode name="plaintext-password"> + <properties> + <help>Plain-text authentication type</help> + <valueHelp> + <format>txt</format> + <description>Circuit password</description> + </valueHelp> + </properties> +</leafNode> +<leafNode name="md5"> + <properties> + <help>MD5 authentication type</help> + <valueHelp> + <format>txt</format> + <description>Level-wide password</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/isis/protocol-common-config.xml.i b/interface-definitions/include/isis/protocol-common-config.xml.i new file mode 100644 index 0000000..35ce80b --- /dev/null +++ b/interface-definitions/include/isis/protocol-common-config.xml.i @@ -0,0 +1,729 @@ +<!-- include start from isis/protocol-common-config.xml.i --> +<leafNode name="advertise-high-metrics"> + <properties> + <help>Advertise high metric value on all interfaces</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="advertise-passive-only"> + <properties> + <help>Advertise prefixes of passive interfaces only</help> + <valueless/> + </properties> +</leafNode> +<node name="area-password"> + <properties> + <help>Configure the authentication password for an area</help> + </properties> + <children> + #include <include/isis/password.xml.i> + </children> +</node> +<node name="default-information"> + <properties> + <help>Control distribution of default information</help> + </properties> + <children> + <node name="originate"> + <properties> + <help>Distribute a default route</help> + </properties> + <children> + <node name="ipv4"> + <properties> + <help>Distribute default route for IPv4</help> + </properties> + <children> + #include <include/isis/default-information-level.xml.i> + </children> + </node> + <node name="ipv6"> + <properties> + <help>Distribute default route for IPv6</help> + </properties> + <children> + #include <include/isis/default-information-level.xml.i> + </children> + </node> + </children> + </node> + </children> +</node> +<node name="domain-password"> + <properties> + <help>Set the authentication password for a routing domain</help> + </properties> + <children> + #include <include/isis/password.xml.i> + </children> +</node> +<leafNode name="dynamic-hostname"> + <properties> + <help>Dynamic hostname for IS-IS</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="level"> + <properties> + <help>IS-IS level number</help> + <completionHelp> + <list>level-1 level-1-2 level-2</list> + </completionHelp> + <valueHelp> + <format>level-1</format> + <description>Act as a station router</description> + </valueHelp> + <valueHelp> + <format>level-1-2</format> + <description>Act as both a station and an area router</description> + </valueHelp> + <valueHelp> + <format>level-2</format> + <description>Act as an area router</description> + </valueHelp> + <constraint> + <regex>(level-1|level-1-2|level-2)</regex> + </constraint> + </properties> +</leafNode> +#include <include/log-adjacency-changes.xml.i> +<leafNode name="lsp-gen-interval"> + <properties> + <help>Minimum interval between regenerating same LSP</help> + <valueHelp> + <format>u32:1-120</format> + <description>Minimum interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-120"/> + </constraint> + </properties> +</leafNode> +<leafNode name="lsp-mtu"> + <properties> + <help>Configure the maximum size of generated LSPs</help> + <valueHelp> + <format>u32:128-4352</format> + <description>Maximum size of generated LSPs</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 128-4352"/> + </constraint> + </properties> + <defaultValue>1497</defaultValue> +</leafNode> +<leafNode name="lsp-refresh-interval"> + <properties> + <help>LSP refresh interval</help> + <valueHelp> + <format>u32:1-65235</format> + <description>LSP refresh interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65235"/> + </constraint> + </properties> +</leafNode> +<leafNode name="max-lsp-lifetime"> + <properties> + <help>Maximum LSP lifetime</help> + <valueHelp> + <format>u32:350-65535</format> + <description>LSP lifetime in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> +</leafNode> +<leafNode name="metric-style"> + <properties> + <help>Use old-style (ISO 10589) or new-style packet formats</help> + <completionHelp> + <list>narrow transition wide</list> + </completionHelp> + <valueHelp> + <format>narrow</format> + <description>Use old style of TLVs with narrow metric</description> + </valueHelp> + <valueHelp> + <format>transition</format> + <description>Send and accept both styles of TLVs during transition</description> + </valueHelp> + <valueHelp> + <format>wide</format> + <description>Use new style of TLVs to carry wider metric</description> + </valueHelp> + <constraint> + <regex>(narrow|transition|wide)</regex> + </constraint> + </properties> +</leafNode> +#include <include/isis/ldp-sync-protocol.xml.i> +<leafNode name="topology"> + <properties> + <help>Configure IS-IS topologies</help> + <completionHelp> + <list>ipv4-multicast ipv4-mgmt ipv6-unicast ipv6-multicast ipv6-mgmt ipv6-dstsrc</list> + </completionHelp> + <valueHelp> + <format>ipv4-multicast</format> + <description>Use IPv4 multicast topology</description> + </valueHelp> + <valueHelp> + <format>ipv4-mgmt</format> + <description>Use IPv4 management topology</description> + </valueHelp> + <valueHelp> + <format>ipv6-unicast</format> + <description>Use IPv6 unicast topology</description> + </valueHelp> + <valueHelp> + <format>ipv6-multicast</format> + <description>Use IPv6 multicast topology</description> + </valueHelp> + <valueHelp> + <format>ipv6-mgmt</format> + <description>Use IPv6 management topology</description> + </valueHelp> + <valueHelp> + <format>ipv6-dstsrc</format> + <description>Use IPv6 dst-src topology</description> + </valueHelp> + <constraint> + <regex>(ipv4-multicast|ipv4-mgmt|ipv6-unicast|ipv6-multicast|ipv6-mgmt|ipv6-dstsrc)</regex> + </constraint> + </properties> +</leafNode> +<node name="fast-reroute"> + <properties> + <help>IS-IS fast reroute configuration</help> + </properties> + <children> + #include <include/isis/lfa-protocol.xml.i> + </children> +</node> +#include <include/net.xml.i> +<leafNode name="purge-originator"> + <properties> + <help>Use the RFC 6232 purge-originator</help> + <valueless/> + </properties> +</leafNode> +<node name="traffic-engineering"> + <properties> + <help>IS-IS traffic engineering extensions</help> + </properties> + <children> + <leafNode name="enable"> + <properties> + <help>Enable MPLS traffic engineering extensions</help> + <valueless/> + </properties> + </leafNode> +<!-- + <node name="inter-as"> + <properties> + <help>MPLS traffic engineering inter-AS support</help> + </properties> + <children> + <leafNode name="level-1"> + <properties> + <help>Area native mode self originate inter-AS LSP with L1 only flooding scope</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="level-1-2"> + <properties> + <help>Area native mode self originate inter-AS LSP with L1 and L2 flooding scope</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="level-2"> + <properties> + <help>Area native mode self originate inter-AS LSP with L2 only flooding scope</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="inter-as"> + <properties> + <help>MPLS traffic engineering inter-AS support</help> + <valueless/> + </properties> + </leafNode> +--> + <leafNode name="address"> + <properties> + <help>MPLS traffic engineering router ID</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<node name="segment-routing"> + <properties> + <help>Segment-Routing (SPRING) settings</help> + </properties> + <children> + <node name="global-block"> + <properties> + <help>Segment Routing Global Block label range</help> + </properties> + <children> + #include <include/segment-routing-label-value.xml.i> + </children> + </node> + <node name="local-block"> + <properties> + <help>Segment Routing Local Block label range</help> + </properties> + <children> + #include <include/segment-routing-label-value.xml.i> + </children> + </node> + <leafNode name="maximum-label-depth"> + <properties> + <help>Maximum MPLS labels allowed for this router</help> + <valueHelp> + <format>u32:1-16</format> + <description>MPLS label depth</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-16"/> + </constraint> + </properties> + </leafNode> + <tagNode name="prefix"> + <properties> + <help>Static IPv4/IPv6 prefix segment/label mapping</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix segment</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix segment</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + <node name="absolute"> + <properties> + <help>Specify the absolute value of prefix segment/label ID</help> + </properties> + <children> + <leafNode name="value"> + <properties> + <help>Specify the absolute value of prefix segment/label ID</help> + <valueHelp> + <format>u32:16-1048575</format> + <description>The absolute segment/label ID value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 16-1048575"/> + </constraint> + </properties> + </leafNode> + <leafNode name="explicit-null"> + <properties> + <help>Request upstream neighbor to replace segment/label with explicit null label</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="no-php-flag"> + <properties> + <help>Do not request penultimate hop popping for segment/label</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="index"> + <properties> + <help>Specify the index value of prefix segment/label ID</help> + </properties> + <children> + <leafNode name="value"> + <properties> + <help>Specify the index value of prefix segment/label ID</help> + <valueHelp> + <format>u32:0-65535</format> + <description>The index segment/label ID value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="explicit-null"> + <properties> + <help>Request upstream neighbor to replace segment/label with explicit null label</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="no-php-flag"> + <properties> + <help>Do not request penultimate hop popping for segment/label</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> +</node> +<node name="redistribute"> + <properties> + <help>Redistribute information from another routing protocol</help> + </properties> + <children> + <node name="ipv4"> + <properties> + <help>Redistribute IPv4 routes</help> + </properties> + <children> + <node name="bgp"> + <properties> + <help>Border Gateway Protocol (BGP)</help> + </properties> + <children> + #include <include/isis/redistribute-level-1-2.xml.i> + </children> + </node> + <node name="connected"> + <properties> + <help>Redistribute connected routes into IS-IS</help> + </properties> + <children> + #include <include/isis/redistribute-level-1-2.xml.i> + </children> + </node> + <node name="kernel"> + <properties> + <help>Redistribute kernel routes into IS-IS</help> + </properties> + <children> + #include <include/isis/redistribute-level-1-2.xml.i> + </children> + </node> + <node name="ospf"> + <properties> + <help>Redistribute OSPF routes into IS-IS</help> + </properties> + <children> + #include <include/isis/redistribute-level-1-2.xml.i> + </children> + </node> + <node name="rip"> + <properties> + <help>Redistribute RIP routes into IS-IS</help> + </properties> + <children> + #include <include/isis/redistribute-level-1-2.xml.i> + </children> + </node> + <node name="babel"> + <properties> + <help>Redistribute Babel routes into IS-IS</help> + </properties> + <children> + #include <include/isis/redistribute-level-1-2.xml.i> + </children> + </node> + <node name="static"> + <properties> + <help>Redistribute static routes into IS-IS</help> + </properties> + <children> + #include <include/isis/redistribute-level-1-2.xml.i> + </children> + </node> + </children> + </node> + <node name="ipv6"> + <properties> + <help>Redistribute IPv6 routes</help> + </properties> + <children> + <node name="bgp"> + <properties> + <help>Redistribute BGP routes into IS-IS</help> + </properties> + <children> + #include <include/isis/redistribute-level-1-2.xml.i> + </children> + </node> + <node name="connected"> + <properties> + <help>Redistribute connected routes into IS-IS</help> + </properties> + <children> + #include <include/isis/redistribute-level-1-2.xml.i> + </children> + </node> + <node name="kernel"> + <properties> + <help>Redistribute kernel routes into IS-IS</help> + </properties> + <children> + #include <include/isis/redistribute-level-1-2.xml.i> + </children> + </node> + <node name="ospf6"> + <properties> + <help>Redistribute OSPFv3 routes into IS-IS</help> + </properties> + <children> + #include <include/isis/redistribute-level-1-2.xml.i> + </children> + </node> + <node name="ripng"> + <properties> + <help>Redistribute RIPng routes into IS-IS</help> + </properties> + <children> + #include <include/isis/redistribute-level-1-2.xml.i> + </children> + </node> + <node name="babel"> + <properties> + <help>Redistribute Babel routes into IS-IS</help> + </properties> + <children> + #include <include/isis/redistribute-level-1-2.xml.i> + </children> + </node> + <node name="static"> + <properties> + <help>Redistribute static routes into IS-IS</help> + </properties> + <children> + #include <include/isis/redistribute-level-1-2.xml.i> + </children> + </node> + </children> + </node> + </children> +</node> +<leafNode name="set-attached-bit"> + <properties> + <help>Set attached bit to identify as L1/L2 router for inter-area traffic</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="set-overload-bit"> + <properties> + <help>Set overload bit to avoid any transit traffic</help> + <valueless/> + </properties> +</leafNode> +<node name="spf-delay-ietf"> + <properties> + <help>IETF SPF delay algorithm</help> + </properties> + <children> + <leafNode name="init-delay"> + <properties> + <help>Delay used while in QUIET state</help> + <valueHelp> + <format>u32:0-60000</format> + <description>Delay used while in QUIET state (in ms)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-60000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="short-delay"> + <properties> + <help>Delay used while in SHORT_WAIT state</help> + <valueHelp> + <format>u32:0-60000</format> + <description>Delay used while in SHORT_WAIT state (in ms)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-60000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="long-delay"> + <properties> + <help>Delay used while in LONG_WAIT</help> + <valueHelp> + <format>u32:0-60000</format> + <description>Delay used while in LONG_WAIT state in ms</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-60000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="holddown"> + <properties> + <help>Time with no received IGP events before considering IGP stable</help> + <valueHelp> + <format>u32:0-60000</format> + <description>Time with no received IGP events before considering IGP stable in ms</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-60000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="time-to-learn"> + <properties> + <help>Maximum duration needed to learn all the events related to a single failure</help> + <valueHelp> + <format>u32:0-60000</format> + <description>Maximum duration needed to learn all the events related to a single failure in ms</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-60000"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<leafNode name="spf-interval"> + <properties> + <help>Minimum interval between SPF calculations</help> + <valueHelp> + <format>u32:1-120</format> + <description>Interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-120"/> + </constraint> + </properties> +</leafNode> +<tagNode name="interface"> + <properties> + <help>Interface params</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + #include <include/bfd/bfd.xml.i> + <leafNode name="circuit-type"> + <properties> + <help>Configure circuit type for interface</help> + <completionHelp> + <list>level-1 level-1-2 level-2-only</list> + </completionHelp> + <valueHelp> + <format>level-1</format> + <description>Level-1 only adjacencies are formed</description> + </valueHelp> + <valueHelp> + <format>level-1-2</format> + <description>Level-1-2 adjacencies are formed</description> + </valueHelp> + <valueHelp> + <format>level-2-only</format> + <description>Level-2 only adjacencies are formed</description> + </valueHelp> + <constraint> + <regex>(level-1|level-1-2|level-2-only)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="hello-padding"> + <properties> + <help>Add padding to IS-IS hello packets</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="hello-interval"> + <properties> + <help>Set Hello interval</help> + <valueHelp> + <format>u32:1-600</format> + <description>Set Hello interval</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-600"/> + </constraint> + </properties> + </leafNode> + <leafNode name="hello-multiplier"> + <properties> + <help>Set Hello interval</help> + <valueHelp> + <format>u32:2-100</format> + <description>Set multiplier for Hello holding time</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 2-100"/> + </constraint> + </properties> + </leafNode> + #include <include/isis/metric.xml.i> + #include <include/isis/ldp-sync-interface.xml.i> + <node name="network"> + <properties> + <help>Set network type</help> + </properties> + <children> + <leafNode name="point-to-point"> + <properties> + <help>point-to-point network type</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + #include <include/isis/passive.xml.i> + <node name="password"> + <properties> + <help>Configure the authentication password for a circuit</help> + </properties> + <children> + #include <include/isis/password.xml.i> + </children> + </node> + <leafNode name="priority"> + <properties> + <help>Set priority for Designated Router election</help> + <valueHelp> + <format>u32:0-127</format> + <description>Priority value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-127"/> + </constraint> + </properties> + </leafNode> + <leafNode name="psnp-interval"> + <properties> + <help>Set PSNP interval</help> + <valueHelp> + <format>u32:0-127</format> + <description>PSNP interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-127"/> + </constraint> + </properties> + </leafNode> + <leafNode name="no-three-way-handshake"> + <properties> + <help>Disable three-way handshake</help> + <valueless/> + </properties> + </leafNode> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/isis/redistribute-level-1-2.xml.i b/interface-definitions/include/isis/redistribute-level-1-2.xml.i new file mode 100644 index 0000000..abb8527 --- /dev/null +++ b/interface-definitions/include/isis/redistribute-level-1-2.xml.i @@ -0,0 +1,20 @@ +<!-- include start from isis/redistribute-level-1-2.xml.i --> +<node name="level-1"> + <properties> + <help>Redistribute into level-1</help> + </properties> + <children> + #include <include/isis/metric.xml.i> + #include <include/route-map.xml.i> + </children> +</node> +<node name="level-2"> + <properties> + <help>Redistribute into level-2</help> + </properties> + <children> + #include <include/isis/metric.xml.i> + #include <include/route-map.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/listen-address-ipv4-single.xml.i b/interface-definitions/include/listen-address-ipv4-single.xml.i new file mode 100644 index 0000000..81e9479 --- /dev/null +++ b/interface-definitions/include/listen-address-ipv4-single.xml.i @@ -0,0 +1,17 @@ +<!-- include start from listen-address-ipv4-single.xml.i --> +<leafNode name="listen-address"> + <properties> + <help>Local IPv4 addresses to listen on</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to listen for incoming connections</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/listen-address-ipv4.xml.i b/interface-definitions/include/listen-address-ipv4.xml.i new file mode 100644 index 0000000..9cca297 --- /dev/null +++ b/interface-definitions/include/listen-address-ipv4.xml.i @@ -0,0 +1,18 @@ +<!-- include start from listen-address-ipv4.xml.i --> +<leafNode name="listen-address"> + <properties> + <help>Local IPv4 addresses to listen on</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to listen for incoming connections</description> + </valueHelp> + <multi/> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/listen-address-single.xml.i b/interface-definitions/include/listen-address-single.xml.i new file mode 100644 index 0000000..6cc5aef --- /dev/null +++ b/interface-definitions/include/listen-address-single.xml.i @@ -0,0 +1,22 @@ +<!-- include start from listen-address-single.xml.i --> +<leafNode name="listen-address"> + <properties> + <help>Local IP addresses to listen on</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to listen for incoming connections</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address to listen for incoming connections</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + <validator name="ipv6-link-local"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/listen-address-vrf.xml.i b/interface-definitions/include/listen-address-vrf.xml.i new file mode 100644 index 0000000..23ecc24 --- /dev/null +++ b/interface-definitions/include/listen-address-vrf.xml.i @@ -0,0 +1,24 @@ +<!-- include start from listen-address-vrf.xml.i --> +<tagNode name="listen-address"> + <properties> + <help>Local IP addresses to listen on</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to listen for incoming connections</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address to listen for incoming connections</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + #include <include/interface/vrf.xml.i> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/listen-address.xml.i b/interface-definitions/include/listen-address.xml.i new file mode 100644 index 0000000..2454f43 --- /dev/null +++ b/interface-definitions/include/listen-address.xml.i @@ -0,0 +1,23 @@ +<!-- include start from listen-address.xml.i --> +<leafNode name="listen-address"> + <properties> + <help>Local IP addresses to listen on</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to listen for incoming connections</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address to listen for incoming connections</description> + </valueHelp> + <multi/> + <constraint> + <validator name="ip-address"/> + <validator name="ipv6-link-local"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/listen-interface-multi-broadcast.xml.i b/interface-definitions/include/listen-interface-multi-broadcast.xml.i new file mode 100644 index 0000000..00bd45e --- /dev/null +++ b/interface-definitions/include/listen-interface-multi-broadcast.xml.i @@ -0,0 +1,18 @@ +<!-- include start from listen-interface-multi-broadcast.xml.i --> +<leafNode name="listen-interface"> + <properties> + <help>Interface to listen on</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --broadcast</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/log-adjacency-changes.xml.i b/interface-definitions/include/log-adjacency-changes.xml.i new file mode 100644 index 0000000..a0628b8 --- /dev/null +++ b/interface-definitions/include/log-adjacency-changes.xml.i @@ -0,0 +1,8 @@ +<!-- include start from log-adjacency-changes.xml.i --> +<leafNode name="log-adjacency-changes"> + <properties> + <help>Log changes in adjacency state</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/name-server-ipv4-ipv6-port.xml.i b/interface-definitions/include/name-server-ipv4-ipv6-port.xml.i new file mode 100644 index 0000000..b326a65 --- /dev/null +++ b/interface-definitions/include/name-server-ipv4-ipv6-port.xml.i @@ -0,0 +1,24 @@ +<!-- include start from name-server-ipv4-ipv6-port.xml.i --> +<tagNode name="name-server"> + <properties> + <help>Domain Name Servers (DNS) addresses to forward queries to</help> + <valueHelp> + <format>ipv4</format> + <description>Domain Name Server (DNS) IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Domain Name Server (DNS) IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>53</defaultValue> + </leafNode> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/name-server-ipv4-ipv6.xml.i b/interface-definitions/include/name-server-ipv4-ipv6.xml.i new file mode 100644 index 0000000..cf483e5 --- /dev/null +++ b/interface-definitions/include/name-server-ipv4-ipv6.xml.i @@ -0,0 +1,19 @@ +<!-- include start from name-server-ipv4-ipv6.xml.i --> +<leafNode name="name-server"> + <properties> + <help>Domain Name Servers (DNS) addresses</help> + <valueHelp> + <format>ipv4</format> + <description>Domain Name Server (DNS) IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Domain Name Server (DNS) IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/name-server-ipv4.xml.i b/interface-definitions/include/name-server-ipv4.xml.i new file mode 100644 index 0000000..0cf884e --- /dev/null +++ b/interface-definitions/include/name-server-ipv4.xml.i @@ -0,0 +1,15 @@ +<!-- include start from name-server-ipv4.xml.i --> +<leafNode name="name-server"> + <properties> + <help>Domain Name Servers (DNS) addresses</help> + <valueHelp> + <format>ipv4</format> + <description>Domain Name Server (DNS) IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/name-server-ipv6.xml.i b/interface-definitions/include/name-server-ipv6.xml.i new file mode 100644 index 0000000..d4517c4 --- /dev/null +++ b/interface-definitions/include/name-server-ipv6.xml.i @@ -0,0 +1,15 @@ +<!-- include start from name-server-ipv6.xml.i --> +<leafNode name="name-server"> + <properties> + <help>Domain Name Servers (DNS) addresses</help> + <valueHelp> + <format>ipv6</format> + <description>Domain Name Server (DNS) IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/nat-address.xml.i b/interface-definitions/include/nat-address.xml.i new file mode 100644 index 0000000..a6460ac --- /dev/null +++ b/interface-definitions/include/nat-address.xml.i @@ -0,0 +1,39 @@ +<!-- include start from nat-address.xml.i --> +<leafNode name="address"> + <properties> + <help>IP address, subnet, or range</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <valueHelp> + <format>ipv4range</format> + <description>IPv4 address range to match</description> + </valueHelp> + <valueHelp> + <format>!ipv4</format> + <description>Match everything except the specified address</description> + </valueHelp> + <valueHelp> + <format>!ipv4net</format> + <description>Match everything except the specified prefix</description> + </valueHelp> + <valueHelp> + <format>!ipv4range</format> + <description>Match everything except the specified range</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv4-prefix"/> + <validator name="ipv4-range"/> + <validator name="ipv4-address-exclude"/> + <validator name="ipv4-prefix-exclude"/> + <validator name="ipv4-range-exclude"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/nat-exclude.xml.i b/interface-definitions/include/nat-exclude.xml.i new file mode 100644 index 0000000..4d53cf8 --- /dev/null +++ b/interface-definitions/include/nat-exclude.xml.i @@ -0,0 +1,8 @@ +<!-- include start from nat-exclude.xml.i --> +<leafNode name="exclude"> + <properties> + <help>Exclude packets matching this rule from NAT</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/nat-interface.xml.i b/interface-definitions/include/nat-interface.xml.i new file mode 100644 index 0000000..ef1ffc1 --- /dev/null +++ b/interface-definitions/include/nat-interface.xml.i @@ -0,0 +1,11 @@ +<!-- include start from nat-interface.xml.i --> +<leafNode name="outbound-interface"> + <properties> + <help>Outbound interface of NAT traffic</help> + <completionHelp> + <list>any</list> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/nat-port.xml.i b/interface-definitions/include/nat-port.xml.i new file mode 100644 index 0000000..5f762cf --- /dev/null +++ b/interface-definitions/include/nat-port.xml.i @@ -0,0 +1,26 @@ +<!-- include start from nat-port.xml.i --> +<leafNode name="port"> + <properties> + <help>Port number</help> + <valueHelp> + <format>txt</format> + <description>Named port (any name in /etc/services, e.g., http)</description> + </valueHelp> + <valueHelp> + <format>u32:1-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <valueHelp> + <format>start-end</format> + <description>Numbered port range (e.g. 1001-1005)</description> + </valueHelp> + <valueHelp> + <format/> + <description>\n\nMultiple destination ports can be specified as a comma-separated list.\nThe whole list can also be negated using '!'.\nFor example: '!22,telnet,http,123,1001-1005'</description> + </valueHelp> + <constraint> + <validator name="port-multi"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/nat-rule.xml.i b/interface-definitions/include/nat-rule.xml.i new file mode 100644 index 0000000..deb1352 --- /dev/null +++ b/interface-definitions/include/nat-rule.xml.i @@ -0,0 +1,325 @@ +<!-- include start from nat-rule.xml.i --> +<tagNode name="rule"> + <properties> + <help>Rule number for NAT</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number of NAT rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>NAT rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <node name="destination"> + <properties> + <help>NAT destination parameters</help> + </properties> + <children> + #include <include/nat-address.xml.i> + #include <include/nat-port.xml.i> + #include <include/firewall/source-destination-group.xml.i> + </children> + </node> + #include <include/generic-disable-node.xml.i> + #include <include/nat-exclude.xml.i> + <node name="load-balance"> + <properties> + <help>Apply NAT load balance</help> + </properties> + <children> + #include <include/firewall/firewall-hashing-parameters.xml.i> + #include <include/firewall/nat-balance.xml.i> + </children> + </node> + #include <include/firewall/log.xml.i> + <leafNode name="packet-type"> + <properties> + <help>Packet type</help> + <completionHelp> + <list>broadcast host multicast other</list> + </completionHelp> + <valueHelp> + <format>broadcast</format> + <description>Match broadcast packet type</description> + </valueHelp> + <valueHelp> + <format>host</format> + <description>Match host packet type, addressed to local host</description> + </valueHelp> + <valueHelp> + <format>multicast</format> + <description>Match multicast packet type</description> + </valueHelp> + <valueHelp> + <format>other</format> + <description>Match packet addressed to another host</description> + </valueHelp> + <constraint> + <regex>(broadcast|host|multicast|other)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="protocol"> + <properties> + <help>Protocol to NAT</help> + <completionHelp> + <list>all ip hopopt icmp igmp ggp ipencap st tcp egp igp pup udp tcp_udp hmp xns-idp rdp iso-tp4 dccp xtp ddp idpr-cmtp ipv6 ipv6-route ipv6-frag idrp rsvp gre esp ah skip ipv6-icmp ipv6-nonxt ipv6-opts rspf vmtp eigrp ospf ax.25 ipip etherip encap 99 pim ipcomp vrrp l2tp isis sctp fc mobility-header udplite mpls-in-ip manet hip shim6 wesp rohc</list> + </completionHelp> + <valueHelp> + <format>all</format> + <description>All IP protocols</description> + </valueHelp> + <valueHelp> + <format>ip</format> + <description>Internet Protocol, pseudo protocol number</description> + </valueHelp> + <valueHelp> + <format>hopopt</format> + <description>IPv6 Hop-by-Hop Option [RFC1883]</description> + </valueHelp> + <valueHelp> + <format>icmp</format> + <description>internet control message protocol</description> + </valueHelp> + <valueHelp> + <format>igmp</format> + <description>Internet Group Management</description> + </valueHelp> + <valueHelp> + <format>ggp</format> + <description>gateway-gateway protocol</description> + </valueHelp> + <valueHelp> + <format>ipencap</format> + <description>IP encapsulated in IP (officially IP)</description> + </valueHelp> + <valueHelp> + <format>st</format> + <description>ST datagram mode</description> + </valueHelp> + <valueHelp> + <format>tcp</format> + <description>transmission control protocol</description> + </valueHelp> + <valueHelp> + <format>egp</format> + <description>exterior gateway protocol</description> + </valueHelp> + <valueHelp> + <format>igp</format> + <description>any private interior gateway (Cisco)</description> + </valueHelp> + <valueHelp> + <format>pup</format> + <description>PARC universal packet protocol</description> + </valueHelp> + <valueHelp> + <format>udp</format> + <description>user datagram protocol</description> + </valueHelp> + <valueHelp> + <format>tcp_udp</format> + <description>Both TCP and UDP</description> + </valueHelp> + <valueHelp> + <format>hmp</format> + <description>host monitoring protocol</description> + </valueHelp> + <valueHelp> + <format>xns-idp</format> + <description>Xerox NS IDP</description> + </valueHelp> + <valueHelp> + <format>rdp</format> + <description>"reliable datagram" protocol</description> + </valueHelp> + <valueHelp> + <format>iso-tp4</format> + <description>ISO Transport Protocol class 4 [RFC905]</description> + </valueHelp> + <valueHelp> + <format>dccp</format> + <description>Datagram Congestion Control Prot. [RFC4340]</description> + </valueHelp> + <valueHelp> + <format>xtp</format> + <description>Xpress Transfer Protocol</description> + </valueHelp> + <valueHelp> + <format>ddp</format> + <description>Datagram Delivery Protocol</description> + </valueHelp> + <valueHelp> + <format>idpr-cmtp</format> + <description>IDPR Control Message Transport</description> + </valueHelp> + <valueHelp> + <format>Ipv6</format> + <description>Internet Protocol, version 6</description> + </valueHelp> + <valueHelp> + <format>ipv6-route</format> + <description>Routing Header for IPv6</description> + </valueHelp> + <valueHelp> + <format>ipv6-frag</format> + <description>Fragment Header for IPv6</description> + </valueHelp> + <valueHelp> + <format>idrp</format> + <description>Inter-Domain Routing Protocol</description> + </valueHelp> + <valueHelp> + <format>rsvp</format> + <description>Reservation Protocol</description> + </valueHelp> + <valueHelp> + <format>gre</format> + <description>General Routing Encapsulation</description> + </valueHelp> + <valueHelp> + <format>esp</format> + <description>Encap Security Payload [RFC2406]</description> + </valueHelp> + <valueHelp> + <format>ah</format> + <description>Authentication Header [RFC2402]</description> + </valueHelp> + <valueHelp> + <format>skip</format> + <description>SKIP</description> + </valueHelp> + <valueHelp> + <format>ipv6-icmp</format> + <description>ICMP for IPv6</description> + </valueHelp> + <valueHelp> + <format>ipv6-nonxt</format> + <description>No Next Header for IPv6</description> + </valueHelp> + <valueHelp> + <format>ipv6-opts</format> + <description>Destination Options for IPv6</description> + </valueHelp> + <valueHelp> + <format>rspf</format> + <description>Radio Shortest Path First (officially CPHB)</description> + </valueHelp> + <valueHelp> + <format>vmtp</format> + <description>Versatile Message Transport</description> + </valueHelp> + <valueHelp> + <format>eigrp</format> + <description>Enhanced Interior Routing Protocol (Cisco)</description> + </valueHelp> + <valueHelp> + <format>ospf</format> + <description>Open Shortest Path First IGP</description> + </valueHelp> + <valueHelp> + <format>ax.25</format> + <description>AX.25 frames</description> + </valueHelp> + <valueHelp> + <format>ipip</format> + <description>IP-within-IP Encapsulation Protocol</description> + </valueHelp> + <valueHelp> + <format>etherip</format> + <description>Ethernet-within-IP Encapsulation [RFC3378]</description> + </valueHelp> + <valueHelp> + <format>encap</format> + <description>Yet Another IP encapsulation [RFC1241]</description> + </valueHelp> + <valueHelp> + <format>99</format> + <description>Any private encryption scheme</description> + </valueHelp> + <valueHelp> + <format>pim</format> + <description>Protocol Independent Multicast</description> + </valueHelp> + <valueHelp> + <format>ipcomp</format> + <description>IP Payload Compression Protocol</description> + </valueHelp> + <valueHelp> + <format>vrrp</format> + <description>Virtual Router Redundancy Protocol [RFC5798]</description> + </valueHelp> + <valueHelp> + <format>l2tp</format> + <description>Layer Two Tunneling Protocol [RFC2661]</description> + </valueHelp> + <valueHelp> + <format>isis</format> + <description>IS-IS over IPv4</description> + </valueHelp> + <valueHelp> + <format>sctp</format> + <description>Stream Control Transmission Protocol</description> + </valueHelp> + <valueHelp> + <format>fc</format> + <description>Fibre Channel</description> + </valueHelp> + <valueHelp> + <format>mobility-header</format> + <description>Mobility Support for IPv6 [RFC3775]</description> + </valueHelp> + <valueHelp> + <format>udplite</format> + <description>UDP-Lite [RFC3828]</description> + </valueHelp> + <valueHelp> + <format>mpls-in-ip</format> + <description>MPLS-in-IP [RFC4023]</description> + </valueHelp> + <valueHelp> + <format>manet</format> + <description>MANET Protocols [RFC5498]</description> + </valueHelp> + <valueHelp> + <format>hip</format> + <description>Host Identity Protocol</description> + </valueHelp> + <valueHelp> + <format>shim6</format> + <description>Shim6 Protocol</description> + </valueHelp> + <valueHelp> + <format>wesp</format> + <description>Wrapped Encapsulating Security Payload</description> + </valueHelp> + <valueHelp> + <format>rohc</format> + <description>Robust Header Compression</description> + </valueHelp> + <valueHelp> + <format>u32:0-255</format> + <description>IP protocol number</description> + </valueHelp> + <constraint> + <validator name="ip-protocol"/> + </constraint> + </properties> + <defaultValue>all</defaultValue> + </leafNode> + <node name="source"> + <properties> + <help>NAT source parameters</help> + </properties> + <children> + #include <include/nat-address.xml.i> + #include <include/nat-port.xml.i> + #include <include/firewall/source-destination-group.xml.i> + </children> + </node> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/nat-translation-options.xml.i b/interface-definitions/include/nat-translation-options.xml.i new file mode 100644 index 0000000..c890059 --- /dev/null +++ b/interface-definitions/include/nat-translation-options.xml.i @@ -0,0 +1,49 @@ +<!-- include start from nat-translation-options.xml.i --> +<node name="options"> + <properties> + <help>Translation options</help> + </properties> + <children> + <leafNode name="address-mapping"> + <properties> + <help>Address mapping options</help> + <completionHelp> + <list>persistent random</list> + </completionHelp> + <valueHelp> + <format>persistent</format> + <description>Gives a client the same source or destination-address for each connection</description> + </valueHelp> + <valueHelp> + <format>random</format> + <description>Random source or destination address allocation for each connection</description> + </valueHelp> + <constraint> + <regex>(persistent|random)</regex> + </constraint> + </properties> + <defaultValue>random</defaultValue> + </leafNode> + <leafNode name="port-mapping"> + <properties> + <help>Port mapping options</help> + <completionHelp> + <list>random none</list> + </completionHelp> + <valueHelp> + <format>random</format> + <description>Randomize source port mapping</description> + </valueHelp> + <valueHelp> + <format>none</format> + <description>Do not apply port randomization</description> + </valueHelp> + <constraint> + <regex>(random|none)</regex> + </constraint> + </properties> + <defaultValue>none</defaultValue> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/nat-translation-port.xml.i b/interface-definitions/include/nat-translation-port.xml.i new file mode 100644 index 0000000..6f17df3 --- /dev/null +++ b/interface-definitions/include/nat-translation-port.xml.i @@ -0,0 +1,18 @@ +<!-- include start from nat-translation-port.xml.i --> +<leafNode name="port"> + <properties> + <help>Port number</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <valueHelp> + <format>range</format> + <description>Numbered port range (e.g., 1001-1005)</description> + </valueHelp> + <constraint> + <validator name="port-range"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/nat/protocol.xml.i b/interface-definitions/include/nat/protocol.xml.i new file mode 100644 index 0000000..54e7ff0 --- /dev/null +++ b/interface-definitions/include/nat/protocol.xml.i @@ -0,0 +1,34 @@ +<!-- include start from nat/protocol.xml.i --> +<leafNode name="protocol"> + <properties> + <help>Protocol to match (protocol name, number, or "all")</help> + <completionHelp> + <script>${vyos_completion_dir}/list_protocols.sh</script> + <list>all tcp_udp</list> + </completionHelp> + <valueHelp> + <format>all</format> + <description>All IP protocols</description> + </valueHelp> + <valueHelp> + <format>tcp_udp</format> + <description>Both TCP and UDP</description> + </valueHelp> + <valueHelp> + <format>u32:0-255</format> + <description>IP protocol number</description> + </valueHelp> + <valueHelp> + <format><protocol></format> + <description>IP protocol name</description> + </valueHelp> + <valueHelp> + <format>!<protocol></format> + <description>IP protocol name</description> + </valueHelp> + <constraint> + <validator name="ip-protocol"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/nat64/protocol.xml.i b/interface-definitions/include/nat64/protocol.xml.i new file mode 100644 index 0000000..a640873 --- /dev/null +++ b/interface-definitions/include/nat64/protocol.xml.i @@ -0,0 +1,27 @@ +<!-- include start from nat64/protocol.xml.i --> +<node name="protocol"> + <properties> + <help>Apply translation address to a specfic protocol</help> + </properties> + <children> + <leafNode name="tcp"> + <properties> + <help>Transmission Control Protocol</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="udp"> + <properties> + <help>User Datagram Protocol</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="icmp"> + <properties> + <help>Internet Control Message Protocol</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/net.xml.i b/interface-definitions/include/net.xml.i new file mode 100644 index 0000000..10b54ee --- /dev/null +++ b/interface-definitions/include/net.xml.i @@ -0,0 +1,14 @@ +<!-- include start from net.xml.i --> +<leafNode name="net"> + <properties> + <help>A Network Entity Title for the process (ISO only)</help> + <valueHelp> + <format>XX.XXXX. ... .XXX.XX</format> + <description>Network entity title (NET)</description> + </valueHelp> + <constraint> + <regex>[a-fA-F0-9]{2}(\.[a-fA-F0-9]{4}){3,9}\.[a-fA-F0-9]{2}</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/openfabric/password.xml.i b/interface-definitions/include/openfabric/password.xml.i new file mode 100644 index 0000000..fa34a4d --- /dev/null +++ b/interface-definitions/include/openfabric/password.xml.i @@ -0,0 +1,20 @@ +<!-- include start from openfabric/password.xml.i --> +<leafNode name="plaintext-password"> + <properties> + <help>Use plain text password</help> + <valueHelp> + <format>txt</format> + <description>Authentication password</description> + </valueHelp> + </properties> +</leafNode> +<leafNode name="md5"> + <properties> + <help>Use MD5 hash authentication</help> + <valueHelp> + <format>txt</format> + <description>Authentication password</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ospf/authentication.xml.i b/interface-definitions/include/ospf/authentication.xml.i new file mode 100644 index 0000000..8e8cad0 --- /dev/null +++ b/interface-definitions/include/ospf/authentication.xml.i @@ -0,0 +1,56 @@ +<!-- include start from ospf/authentication.xml.i --> +<node name="authentication"> + <properties> + <help>Authentication</help> + </properties> + <children> + <node name="md5"> + <properties> + <help>MD5 key id</help> + </properties> + <children> + <tagNode name="key-id"> + <properties> + <help>MD5 key id</help> + <valueHelp> + <format>u32:1-255</format> + <description>MD5 key id</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <children> + <leafNode name="md5-key"> + <properties> + <help>MD5 authentication type</help> + <valueHelp> + <format>txt</format> + <description>MD5 Key (16 characters or less)</description> + </valueHelp> + <constraint> + <regex>[^[:space:]]{1,16}</regex> + </constraint> + <constraintErrorMessage>Password must be 16 characters or less</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + <leafNode name="plaintext-password"> + <properties> + <help>Plain text password</help> + <valueHelp> + <format>txt</format> + <description>Plain text password (8 characters or less)</description> + </valueHelp> + <constraint> + <regex>[^[:space:]]{1,8}</regex> + </constraint> + <constraintErrorMessage>Password must be 8 characters or less</constraintErrorMessage> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/ospf/auto-cost.xml.i b/interface-definitions/include/ospf/auto-cost.xml.i new file mode 100644 index 0000000..da6483a --- /dev/null +++ b/interface-definitions/include/ospf/auto-cost.xml.i @@ -0,0 +1,22 @@ +<!-- include start from ospf/auto-cost.xml.i --> +<node name="auto-cost"> + <properties> + <help>Calculate interface cost according to bandwidth</help> + </properties> + <children> + <leafNode name="reference-bandwidth"> + <properties> + <help>Reference bandwidth method to assign cost</help> + <valueHelp> + <format>u32:1-4294967</format> + <description>Reference bandwidth cost in Mbits/sec</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967"/> + </constraint> + </properties> + <defaultValue>100</defaultValue> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/ospf/default-information.xml.i b/interface-definitions/include/ospf/default-information.xml.i new file mode 100644 index 0000000..50cda54 --- /dev/null +++ b/interface-definitions/include/ospf/default-information.xml.i @@ -0,0 +1,25 @@ +<!-- include start from ospf/intervals.xml.i --> +<node name="default-information"> + <properties> + <help>Default route advertisment settings</help> + </properties> + <children> + <node name="originate"> + <properties> + <help>Distribute a default route</help> + </properties> + <children> + <leafNode name="always"> + <properties> + <help>Always advertise a default route</help> + <valueless/> + </properties> + </leafNode> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/ospf/distance-global.xml.i b/interface-definitions/include/ospf/distance-global.xml.i new file mode 100644 index 0000000..31809cb --- /dev/null +++ b/interface-definitions/include/ospf/distance-global.xml.i @@ -0,0 +1,14 @@ +<!-- include start from ospf/distance-global.xml.i --> +<leafNode name="global"> + <properties> + <help>Administrative distance</help> + <valueHelp> + <format>u32:1-255</format> + <description>Administrative distance</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ospf/distance-per-protocol.xml.i b/interface-definitions/include/ospf/distance-per-protocol.xml.i new file mode 100644 index 0000000..da3f16c --- /dev/null +++ b/interface-definitions/include/ospf/distance-per-protocol.xml.i @@ -0,0 +1,38 @@ +<!-- include start from ospf/distance-per-protocol.xml.i --> +<leafNode name="external"> + <properties> + <help>Distance for external routes</help> + <valueHelp> + <format>u32:1-255</format> + <description>Distance for external routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> +</leafNode> +<leafNode name="inter-area"> + <properties> + <help>Distance for inter-area routes</help> + <valueHelp> + <format>u32:1-255</format> + <description>Distance for inter-area routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> +</leafNode> +<leafNode name="intra-area"> + <properties> + <help>Distance for intra-area routes</help> + <valueHelp> + <format>u32:1-255</format> + <description>Distance for intra-area routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ospf/graceful-restart.xml.i b/interface-definitions/include/ospf/graceful-restart.xml.i new file mode 100644 index 0000000..37d9a7f --- /dev/null +++ b/interface-definitions/include/ospf/graceful-restart.xml.i @@ -0,0 +1,67 @@ +<!-- include start from ospf/graceful-restart.xml.i --> +<node name="graceful-restart"> + <properties> + <help>Graceful Restart</help> + </properties> + <children> + <leafNode name="grace-period"> + <properties> + <help>Maximum length of the grace period</help> + <valueHelp> + <format>u32:1-1800</format> + <description>Maximum length of the grace period in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 5-1800"/> + </constraint> + </properties> + <defaultValue>120</defaultValue> + </leafNode> + <node name="helper"> + <properties> + <help>OSPF graceful-restart helpers</help> + </properties> + <children> + <node name="enable"> + <properties> + <help>Enable helper support</help> + </properties> + <children> + <leafNode name="router-id"> + <properties> + <help>Advertising Router-ID</help> + <valueHelp> + <format>ipv4</format> + <description>Router-ID in IP address format</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="planned-only"> + <properties> + <help>Supported only planned restart</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="supported-grace-time"> + <properties> + <help>Supported grace timer</help> + <valueHelp> + <format>u32:10-1800</format> + <description>Grace interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 10-1800"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/ospf/interface-common.xml.i b/interface-definitions/include/ospf/interface-common.xml.i new file mode 100644 index 0000000..9c8b94f --- /dev/null +++ b/interface-definitions/include/ospf/interface-common.xml.i @@ -0,0 +1,34 @@ +<!-- include start from ospf/interface-common.xml.i --> +#include <include/bfd/bfd.xml.i> +<leafNode name="cost"> + <properties> + <help>Interface cost</help> + <valueHelp> + <format>u32:1-65535</format> + <description>OSPF interface cost</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> +</leafNode> +<leafNode name="mtu-ignore"> + <properties> + <help>Disable Maximum Transmission Unit (MTU) mismatch detection</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="priority"> + <properties> + <help>Router priority</help> + <valueHelp> + <format>u32:0-255</format> + <description>OSPF router priority cost</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> + <defaultValue>1</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ospf/intervals.xml.i b/interface-definitions/include/ospf/intervals.xml.i new file mode 100644 index 0000000..9f6e5df --- /dev/null +++ b/interface-definitions/include/ospf/intervals.xml.i @@ -0,0 +1,54 @@ +<!-- include start from ospf/intervals.xml.i --> +<leafNode name="dead-interval"> + <properties> + <help>Interval after which a neighbor is declared dead</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Neighbor dead interval (seconds)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>40</defaultValue> +</leafNode> +<leafNode name="hello-interval"> + <properties> + <help>Interval between hello packets</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Hello interval (seconds)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>10</defaultValue> +</leafNode> +<leafNode name="retransmit-interval"> + <properties> + <help>Interval between retransmitting lost link state advertisements</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Retransmit interval (seconds)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>5</defaultValue> +</leafNode> +<leafNode name="transmit-delay"> + <properties> + <help>Link state transmit delay</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Link state transmit delay (seconds)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>1</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ospf/log-adjacency-changes.xml.i b/interface-definitions/include/ospf/log-adjacency-changes.xml.i new file mode 100644 index 0000000..24c6cbe --- /dev/null +++ b/interface-definitions/include/ospf/log-adjacency-changes.xml.i @@ -0,0 +1,15 @@ +<!-- include start from ospf/metric-type.xml.i --> +<node name="log-adjacency-changes"> + <properties> + <help>Log adjacency state changes</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Log all state changes</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/ospf/metric-type.xml.i b/interface-definitions/include/ospf/metric-type.xml.i new file mode 100644 index 0000000..de55c76 --- /dev/null +++ b/interface-definitions/include/ospf/metric-type.xml.i @@ -0,0 +1,15 @@ +<!-- include start from ospf/metric-type.xml.i --> +<leafNode name="metric-type"> + <properties> + <help>OSPF metric type for default routes</help> + <valueHelp> + <format>u32:1-2</format> + <description>Set OSPF External Type 1/2 metrics</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2"/> + </constraint> + </properties> + <defaultValue>2</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ospf/metric.xml.i b/interface-definitions/include/ospf/metric.xml.i new file mode 100644 index 0000000..64b455b --- /dev/null +++ b/interface-definitions/include/ospf/metric.xml.i @@ -0,0 +1,14 @@ +<!-- include start from ospf/metric.xml.i --> +<leafNode name="metric"> + <properties> + <help>OSPF default metric</help> + <valueHelp> + <format>u32:0-16777214</format> + <description>Default metric</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16777214"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i new file mode 100644 index 0000000..c4778e1 --- /dev/null +++ b/interface-definitions/include/ospf/protocol-common-config.xml.i @@ -0,0 +1,959 @@ +<!-- include start from ospf/protocol-common-config.xml.i --> +<node name="aggregation"> + <properties> + <help>External route aggregation</help> + </properties> + <children> + <leafNode name="timer"> + <properties> + <help>Delay timer</help> + <valueHelp> + <format>u32:5-1800</format> + <description>Timer interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 5-1800"/> + </constraint> + </properties> + <defaultValue>5</defaultValue> + </leafNode> + </children> +</node> +<tagNode name="access-list"> + <properties> + <help>Access list to filter networks in routing updates</help> + <completionHelp> + <path>policy access-list</path> + </completionHelp> + <valueHelp> + <format>u32</format> + <description>Access-list number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + <children> + <leafNode name="export"> + <properties> + <help>Filter for outgoing routing update</help> + <completionHelp> + <list>bgp connected kernel rip static</list> + </completionHelp> + <valueHelp> + <format>bgp</format> + <description>Filter BGP routes</description> + </valueHelp> + <valueHelp> + <format>connected</format> + <description>Filter connected routes</description> + </valueHelp> + <valueHelp> + <format>isis</format> + <description>Filter IS-IS routes</description> + </valueHelp> + <valueHelp> + <format>kernel</format> + <description>Filter Kernel routes</description> + </valueHelp> + <valueHelp> + <format>rip</format> + <description>Filter RIP routes</description> + </valueHelp> + <valueHelp> + <format>static</format> + <description>Filter static routes</description> + </valueHelp> + <constraint> + <regex>(bgp|connected|isis|kernel|rip|static)</regex> + </constraint> + <constraintErrorMessage>Must be bgp, connected, kernel, rip, or static</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + </children> +</tagNode> +<tagNode name="area"> + <properties> + <help>OSPF area settings</help> + <valueHelp> + <format>u32</format> + <description>OSPF area number in decimal notation</description> + </valueHelp> + <valueHelp> + <format>ipv4</format> + <description>OSPF area number in dotted decimal notation</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + <node name="area-type"> + <properties> + <help>Area type</help> + </properties> + <children> + <leafNode name="normal"> + <properties> + <help>Normal OSPF area</help> + <valueless/> + </properties> + </leafNode> + <node name="nssa"> + <properties> + <help>Not-So-Stubby OSPF area</help> + </properties> + <children> + <leafNode name="default-cost"> + <properties> + <help>Summary-default cost of an NSSA area</help> + <valueHelp> + <format>u32:0-16777215</format> + <description>Summary default cost</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16777215"/> + </constraint> + </properties> + </leafNode> + <leafNode name="no-summary"> + <properties> + <help>Do not inject inter-area routes into stub</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="translate"> + <properties> + <help>Configure NSSA-ABR</help> + <completionHelp> + <list>always candidate never</list> + </completionHelp> + <valueHelp> + <format>always</format> + <description>Always translate LSA types</description> + </valueHelp> + <valueHelp> + <format>candidate</format> + <description>Translate for election</description> + </valueHelp> + <valueHelp> + <format>never</format> + <description>Never translate LSA types</description> + </valueHelp> + <constraint> + <regex>(always|candidate|never)</regex> + </constraint> + </properties> + <defaultValue>candidate</defaultValue> + </leafNode> + </children> + </node> + <node name="stub"> + <properties> + <help>Stub OSPF area</help> + </properties> + <children> + <leafNode name="default-cost"> + <properties> + <help>Summary-default cost</help> + <valueHelp> + <format>u32:0-16777215</format> + <description>Summary default cost</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16777215"/> + </constraint> + </properties> + </leafNode> + <leafNode name="no-summary"> + <properties> + <help>Do not inject inter-area routes into the stub</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="authentication"> + <properties> + <help>OSPF area authentication type</help> + <completionHelp> + <list>plaintext-password md5</list> + </completionHelp> + <valueHelp> + <format>plaintext-password</format> + <description>Use plain-text authentication</description> + </valueHelp> + <valueHelp> + <format>md5</format> + <description>Use MD5 authentication</description> + </valueHelp> + <constraint> + <regex>(plaintext-password|md5)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="network"> + <properties> + <help>OSPF network</help> + <valueHelp> + <format>ipv4net</format> + <description>OSPF network</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + <tagNode name="range"> + <properties> + <help>Summarize routes matching a prefix (border routers only)</help> + <valueHelp> + <format>ipv4net</format> + <description>Area range prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="cost"> + <properties> + <help>Metric for this range</help> + <valueHelp> + <format>u32:0-16777215</format> + <description>Metric for this range</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16777215"/> + </constraint> + </properties> + </leafNode> + <leafNode name="not-advertise"> + <properties> + <help>Do not advertise this range</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="substitute"> + <properties> + <help>Advertise area range as another prefix</help> + <valueHelp> + <format>ipv4net</format> + <description>Advertise area range as another prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="shortcut"> + <properties> + <help>Area shortcut mode</help> + <completionHelp> + <list>default disable enable</list> + </completionHelp> + <valueHelp> + <format>default</format> + <description>Set default</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable shortcutting mode</description> + </valueHelp> + <valueHelp> + <format>enable</format> + <description>Enable shortcutting mode</description> + </valueHelp> + <constraint> + <regex>(default|disable|enable)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="export-list"> + <properties> + <help>Set the filter for networks announced to other areas</help> + <completionHelp> + <path>policy access-list</path> + </completionHelp> + <valueHelp> + <format>u32</format> + <description>Access-list number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="import-list"> + <properties> + <help>Set the filter for networks from other areas announced</help> + <completionHelp> + <path>policy access-list</path> + </completionHelp> + <valueHelp> + <format>u32</format> + <description>Access-list number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + <tagNode name="virtual-link"> + <properties> + <help>Virtual link</help> + <valueHelp> + <format>ipv4</format> + <description>OSPF area in dotted decimal notation</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + #include <include/ospf/authentication.xml.i> + #include <include/ospf/intervals.xml.i> + </children> + </tagNode> + </children> +</tagNode> +#include <include/ospf/auto-cost.xml.i> +<node name="capability"> + <properties> + <help>Enable specific OSPF features</help> + </properties> + <children> + <leafNode name="opaque"> + <properties> + <help>Opaque LSA</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +#include <include/ospf/default-information.xml.i> +<leafNode name="default-metric"> + <properties> + <help>Metric of redistributed routes</help> + <valueHelp> + <format>u32:0-16777214</format> + <description>Metric of redistributed routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16777214"/> + </constraint> + </properties> +</leafNode> +#include <include/ospf/graceful-restart.xml.i> +<node name="graceful-restart"> + <children> + <node name="helper"> + <children> + <leafNode name="no-strict-lsa-checking"> + <properties> + <help>Disable strict LSA check</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> +</node> +<leafNode name="maximum-paths"> + <properties> + <help>Maximum multiple paths (ECMP)</help> + <valueHelp> + <format>u32:1-64</format> + <description>Maximum multiple paths (ECMP)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-64"/> + </constraint> + </properties> +</leafNode> +#include <include/isis/ldp-sync-protocol.xml.i> +<node name="distance"> + <properties> + <help>Administrative distance</help> + </properties> + <children> + #include <include/ospf/distance-global.xml.i> + <node name="ospf"> + <properties> + <help>OSPF administrative distance</help> + </properties> + <children> + #include <include/ospf/distance-per-protocol.xml.i> + </children> + </node> + </children> +</node> +<tagNode name="interface"> + <properties> + <help>Interface configuration</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + <leafNode name="area"> + <properties> + <help>Enable OSPF on this interface</help> + <completionHelp> + <path>protocols ospf area</path> + </completionHelp> + <valueHelp> + <format>u32</format> + <description>OSPF area ID as decimal notation</description> + </valueHelp> + <valueHelp> + <format>ipv4</format> + <description>OSPF area ID in IP address notation</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> + #include <include/ospf/authentication.xml.i> + #include <include/ospf/intervals.xml.i> + #include <include/ospf/interface-common.xml.i> + #include <include/isis/ldp-sync-interface.xml.i> + <leafNode name="bandwidth"> + <properties> + <help>Interface bandwidth (Mbit/s)</help> + <valueHelp> + <format>u32:1-100000</format> + <description>Bandwidth in Megabit/sec (for calculating OSPF cost)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-100000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="hello-multiplier"> + <properties> + <help>Hello multiplier factor</help> + <valueHelp> + <format>u32:1-10</format> + <description>Number of Hellos to send each second</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-10"/> + </constraint> + </properties> + </leafNode> + <leafNode name="network"> + <properties> + <help>Network type</help> + <completionHelp> + <list>broadcast non-broadcast point-to-multipoint point-to-point</list> + </completionHelp> + <valueHelp> + <format>broadcast</format> + <description>Broadcast network type</description> + </valueHelp> + <valueHelp> + <format>non-broadcast</format> + <description>Non-broadcast network type</description> + </valueHelp> + <valueHelp> + <format>point-to-multipoint</format> + <description>Point-to-multipoint network type</description> + </valueHelp> + <valueHelp> + <format>point-to-point</format> + <description>Point-to-point network type</description> + </valueHelp> + <constraint> + <regex>(broadcast|non-broadcast|point-to-multipoint|point-to-point)</regex> + </constraint> + <constraintErrorMessage>Must be broadcast, non-broadcast, point-to-multipoint or point-to-point</constraintErrorMessage> + </properties> + </leafNode> + <node name="passive"> + <properties> + <help>Suppress routing updates on an interface</help> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + </children> + </node> + </children> +</tagNode> +#include <include/ospf/log-adjacency-changes.xml.i> +<node name="max-metric"> + <properties> + <help>OSPF maximum and infinite-distance metric</help> + </properties> + <children> + <node name="router-lsa"> + <properties> + <help>Advertise own Router-LSA with infinite distance (stub router)</help> + </properties> + <children> + <leafNode name="administrative"> + <properties> + <help>Administratively apply, for an indefinite period</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="on-shutdown"> + <properties> + <help>Advertise stub-router prior to full shutdown of OSPF</help> + <valueHelp> + <format>u32:5-100</format> + <description>Time (seconds) to advertise self as stub-router</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 5-100"/> + </constraint> + </properties> + </leafNode> + <leafNode name="on-startup"> + <properties> + <help>Automatically advertise stub Router-LSA on startup of OSPF</help> + <valueHelp> + <format>u32:5-86400</format> + <description>Time (seconds) to advertise self as stub-router</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 5-86400"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> +</node> +<node name="mpls-te"> + <properties> + <help>MultiProtocol Label Switching-Traffic Engineering (MPLS-TE) parameters</help> + </properties> + <children> + <leafNode name="enable"> + <properties> + <help>Enable MPLS-TE functionality</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="router-address"> + <properties> + <help>Stable IP address of the advertising router</help> + <valueHelp> + <format>ipv4</format> + <description>Stable IP address of the advertising router</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <defaultValue>0.0.0.0</defaultValue> + </leafNode> + </children> +</node> +<tagNode name="neighbor"> + <properties> + <help>Specify neighbor router</help> + <valueHelp> + <format>ipv4</format> + <description>Neighbor IP address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <children> + <leafNode name="poll-interval"> + <properties> + <help>Dead neighbor polling interval</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Seconds between dead neighbor polling interval</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>60</defaultValue> + </leafNode> + <leafNode name="priority"> + <properties> + <help>Neighbor priority in seconds</help> + <valueHelp> + <format>u32:0-255</format> + <description>Neighbor priority</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + </children> +</tagNode> +<node name="parameters"> + <properties> + <help>OSPF specific parameters</help> + </properties> + <children> + <leafNode name="abr-type"> + <properties> + <help>OSPF ABR type</help> + <completionHelp> + <list>cisco ibm shortcut standard</list> + </completionHelp> + <valueHelp> + <format>cisco</format> + <description>Cisco ABR type</description> + </valueHelp> + <valueHelp> + <format>ibm</format> + <description>IBM ABR type</description> + </valueHelp> + <valueHelp> + <format>shortcut</format> + <description>Shortcut ABR type</description> + </valueHelp> + <valueHelp> + <format>standard</format> + <description>Standard ABR type</description> + </valueHelp> + <constraint> + <regex>(cisco|ibm|shortcut|standard)</regex> + </constraint> + </properties> + <defaultValue>cisco</defaultValue> + </leafNode> + <leafNode name="opaque-lsa"> + <properties> + <help>Enable the Opaque-LSA capability (rfc2370)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="rfc1583-compatibility"> + <properties> + <help>Enable RFC1583 criteria for handling AS external routes</help> + <valueless/> + </properties> + </leafNode> + #include <include/router-id.xml.i> + </children> +</node> +<leafNode name="passive-interface"> + <properties> + <help>Suppress routing updates on an interface</help> + <completionHelp> + <list>default</list> + </completionHelp> + <valueHelp> + <format>default</format> + <description>Default to suppress routing updates on all interfaces</description> + </valueHelp> + <constraint> + <regex>(default)</regex> + </constraint> + </properties> +</leafNode> +<node name="segment-routing"> + <properties> + <help>Segment-Routing (SPRING) settings</help> + </properties> + <children> + <node name="global-block"> + <properties> + <help>Segment Routing Global Block label range</help> + </properties> + <children> + #include <include/segment-routing-label-value.xml.i> + </children> + </node> + <node name="local-block"> + <properties> + <help>Segment Routing Local Block label range</help> + </properties> + <children> + #include <include/segment-routing-label-value.xml.i> + </children> + </node> + <leafNode name="maximum-label-depth"> + <properties> + <help>Maximum MPLS labels allowed for this router</help> + <valueHelp> + <format>u32:1-16</format> + <description>MPLS label depth</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-16"/> + </constraint> + </properties> + </leafNode> + <tagNode name="prefix"> + <properties> + <help>Static IPv4 prefix segment/label mapping</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix segment</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + <node name="index"> + <properties> + <help>Specify the index value of prefix segment/label ID</help> + </properties> + <children> + <leafNode name="value"> + <properties> + <help>Specify the index value of prefix segment/label ID</help> + <valueHelp> + <format>u32:0-65535</format> + <description>The index segment/label ID value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="explicit-null"> + <properties> + <help>Request upstream neighbor to replace segment/label with explicit null label</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="no-php-flag"> + <properties> + <help>Do not request penultimate hop popping for segment/label</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> +</node> +<node name="redistribute"> + <properties> + <help>Redistribute information from another routing protocol</help> + </properties> + <children> + <node name="bgp"> + <properties> + <help>Redistribute BGP routes</help> + </properties> + <children> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </node> + <node name="connected"> + <properties> + <help>Redistribute connected routes</help> + </properties> + <children> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </node> + <node name="isis"> + <properties> + <help>Redistribute IS-IS routes</help> + </properties> + <children> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </node> + <node name="kernel"> + <properties> + <help>Redistribute Kernel routes</help> + </properties> + <children> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </node> + <node name="rip"> + <properties> + <help>Redistribute RIP routes</help> + </properties> + <children> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </node> + <node name="babel"> + <properties> + <help>Redistribute Babel routes</help> + </properties> + <children> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </node> + <node name="static"> + <properties> + <help>Redistribute statically configured routes</help> + </properties> + <children> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </node> + <tagNode name="table"> + <properties> + <help>Redistribute non-main Kernel Routing Table</help> + <completionHelp> + <path>protocols static table</path> + </completionHelp> + <valueHelp> + <format>u32:1-200</format> + <description>Policy route table number</description> + </valueHelp> + </properties> + <children> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </tagNode> + </children> +</node> +<node name="refresh"> + <properties> + <help>Adjust refresh parameters</help> + </properties> + <children> + <leafNode name="timers"> + <properties> + <help>Refresh timer</help> + <valueHelp> + <format>u32:10-1800</format> + <description>Timer value in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 10-1800"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<tagNode name="summary-address"> + <properties> + <help>External summary address</help> + <valueHelp> + <format>ipv4net</format> + <description>OSPF area number in dotted decimal notation</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="no-advertise"> + <properties> + <help>Don not advertise summary route</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="tag"> + <properties> + <help>Router tag</help> + <valueHelp> + <format>u32:1-4294967295</format> + <description>Router tag value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> + </leafNode> + </children> +</tagNode> +<node name="timers"> + <properties> + <help>Adjust routing timers</help> + </properties> + <children> + <node name="throttle"> + <properties> + <help>Throttling adaptive timers</help> + </properties> + <children> + <node name="spf"> + <properties> + <help>OSPF SPF timers</help> + </properties> + <children> + <leafNode name="delay"> + <properties> + <help>Delay from the first change received to SPF calculation</help> + <valueHelp> + <format>u32:0-600000</format> + <description>Delay in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-600000"/> + </constraint> + </properties> + <defaultValue>200</defaultValue> + </leafNode> + <leafNode name="initial-holdtime"> + <properties> + <help>Initial hold time between consecutive SPF calculations</help> + <valueHelp> + <format>u32:0-600000</format> + <description>Initial hold time in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-600000"/> + </constraint> + </properties> + <defaultValue>1000</defaultValue> + </leafNode> + <leafNode name="max-holdtime"> + <properties> + <help>Maximum hold time</help> + <valueHelp> + <format>u32:0-600000</format> + <description>Max hold time in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-600000"/> + </constraint> + </properties> + <defaultValue>10000</defaultValue> + </leafNode> + </children> + </node> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/ospfv3/no-summary.xml.i b/interface-definitions/include/ospfv3/no-summary.xml.i new file mode 100644 index 0000000..a6afda3 --- /dev/null +++ b/interface-definitions/include/ospfv3/no-summary.xml.i @@ -0,0 +1,8 @@ +<!-- include start from ospfv3/no-summary.xml.i --> +<leafNode name="no-summary"> + <properties> + <help>Do not inject inter-area routes into the stub</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ospfv3/protocol-common-config.xml.i b/interface-definitions/include/ospfv3/protocol-common-config.xml.i new file mode 100644 index 0000000..72fb86d --- /dev/null +++ b/interface-definitions/include/ospfv3/protocol-common-config.xml.i @@ -0,0 +1,296 @@ +<!-- include start from ospfv3/protocol-common-config.xml.i --> +<tagNode name="area"> + <properties> + <help>OSPFv3 Area</help> + <valueHelp> + <format>u32</format> + <description>Area ID as a decimal value</description> + </valueHelp> + <valueHelp> + <format>ipv4</format> + <description>Area ID in IP address forma</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + <node name="area-type"> + <properties> + <help>OSPFv3 Area type</help> + </properties> + <children> + <node name="nssa"> + <properties> + <help>NSSA OSPFv3 area</help> + </properties> + <children> + <leafNode name="default-information-originate"> + <properties> + <help>Originate Type 7 default into NSSA area</help> + <valueless/> + </properties> + </leafNode> + #include <include/ospfv3/no-summary.xml.i> + </children> + </node> + <node name="stub"> + <properties> + <help>Stub OSPFv3 area</help> + </properties> + <children> + #include <include/ospfv3/no-summary.xml.i> + </children> + </node> + </children> + </node> + <leafNode name="export-list"> + <properties> + <help>Name of export-list</help> + <completionHelp> + <path>policy access-list6</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="import-list"> + <properties> + <help>Name of import-list</help> + <completionHelp> + <path>policy access-list6</path> + </completionHelp> + </properties> + </leafNode> + <tagNode name="range"> + <properties> + <help>Specify IPv6 prefix (border routers only)</help> + <valueHelp> + <format>ipv6net</format> + <description>Specify IPv6 prefix (border routers only)</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="advertise"> + <properties> + <help>Advertise this range</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="not-advertise"> + <properties> + <help>Do not advertise this range</help> + <valueless/> + </properties> + </leafNode> + </children> + </tagNode> + </children> +</tagNode> +#include <include/ospf/auto-cost.xml.i> +#include <include/ospf/default-information.xml.i> +<node name="distance"> + <properties> + <help>Administrative distance</help> + </properties> + <children> + #include <include/ospf/distance-global.xml.i> + <node name="ospfv3"> + <properties> + <help>OSPFv3 administrative distance</help> + </properties> + <children> + #include <include/ospf/distance-per-protocol.xml.i> + </children> + </node> + </children> +</node> +#include <include/ospf/graceful-restart.xml.i> +<node name="graceful-restart"> + <children> + <node name="helper"> + <children> + <leafNode name="lsa-check-disable"> + <properties> + <help>Disable strict LSA check</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> +</node> +<tagNode name="interface"> + <properties> + <help>Enable routing on an IPv6 interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface used for routing information exchange</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + <leafNode name="area"> + <properties> + <help>Enable OSPF on this interface</help> + <completionHelp> + <path>protocols ospfv3 area</path> + </completionHelp> + <valueHelp> + <format>u32</format> + <description>OSPF area ID as decimal notation</description> + </valueHelp> + <valueHelp> + <format>ipv4</format> + <description>OSPF area ID in IP address notation</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> + #include <include/ospf/intervals.xml.i> + #include <include/ospf/interface-common.xml.i> + <leafNode name="ifmtu"> + <properties> + <help>Interface MTU</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Interface MTU</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="instance-id"> + <properties> + <help>Instance ID</help> + <valueHelp> + <format>u32:0-255</format> + <description>Instance Id</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="network"> + <properties> + <help>Network type</help> + <completionHelp> + <list>broadcast point-to-point</list> + </completionHelp> + <valueHelp> + <format>broadcast</format> + <description>Broadcast network type</description> + </valueHelp> + <valueHelp> + <format>point-to-point</format> + <description>Point-to-point network type</description> + </valueHelp> + <constraint> + <regex>(broadcast|point-to-point)</regex> + </constraint> + <constraintErrorMessage>Must be broadcast or point-to-point</constraintErrorMessage> + </properties> + </leafNode> + #include <include/isis/passive.xml.i> + </children> +</tagNode> +#include <include/ospf/log-adjacency-changes.xml.i> +<node name="parameters"> + <properties> + <help>OSPFv3 specific parameters</help> + </properties> + <children> + #include <include/router-id.xml.i> + </children> +</node> +<node name="redistribute"> + <properties> + <help>Redistribute information from another routing protocol</help> + </properties> + <children> + <node name="babel"> + <properties> + <help>Redistribute Babel routes</help> + </properties> + <children> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </node> + <node name="bgp"> + <properties> + <help>Redistribute BGP routes</help> + </properties> + <children> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </node> + <node name="connected"> + <properties> + <help>Redistribute connected routes</help> + </properties> + <children> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </node> + <node name="isis"> + <properties> + <help>Redistribute IS-IS routes</help> + </properties> + <children> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </node> + <node name="kernel"> + <properties> + <help>Redistribute kernel routes</help> + </properties> + <children> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </node> + <node name="ripng"> + <properties> + <help>Redistribute RIPNG routes</help> + </properties> + <children> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </node> + <node name="static"> + <properties> + <help>Redistribute static routes</help> + </properties> + <children> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/pim/bsm.xml.i b/interface-definitions/include/pim/bsm.xml.i new file mode 100644 index 0000000..cc2cf14 --- /dev/null +++ b/interface-definitions/include/pim/bsm.xml.i @@ -0,0 +1,14 @@ +<!-- include start from pim/bsm.xml.i --> +<leafNode name="no-bsm"> + <properties> + <help>Do not process bootstrap messages</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="no-unicast-bsm"> + <properties> + <help>Do not process unicast bootstrap messages</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pim/dr-priority.xml.i b/interface-definitions/include/pim/dr-priority.xml.i new file mode 100644 index 0000000..e4b3067 --- /dev/null +++ b/interface-definitions/include/pim/dr-priority.xml.i @@ -0,0 +1,14 @@ +<!-- include start from pim/dr-priority.xml.i --> +<leafNode name="dr-priority"> + <properties> + <help>Designated router election priority</help> + <valueHelp> + <format>u32:1-4294967295</format> + <description>DR Priority</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pim/hello.xml.i b/interface-definitions/include/pim/hello.xml.i new file mode 100644 index 0000000..0c7601b --- /dev/null +++ b/interface-definitions/include/pim/hello.xml.i @@ -0,0 +1,14 @@ +<!-- include start from pim/hello.xml.i --> +<leafNode name="hello"> + <properties> + <help>Hello Interval</help> + <valueHelp> + <format>u32:1-180</format> + <description>Hello Interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-180"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pim/join-prune-interval.xml.i b/interface-definitions/include/pim/join-prune-interval.xml.i new file mode 100644 index 0000000..882787d --- /dev/null +++ b/interface-definitions/include/pim/join-prune-interval.xml.i @@ -0,0 +1,15 @@ +<!-- include start from pim/join-prune-interval.xml.i --> +<leafNode name="join-prune-interval"> + <properties> + <help>Join prune send interval</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>60</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pim/keep-alive-timer.xml.i b/interface-definitions/include/pim/keep-alive-timer.xml.i new file mode 100644 index 0000000..0dd27d6 --- /dev/null +++ b/interface-definitions/include/pim/keep-alive-timer.xml.i @@ -0,0 +1,14 @@ +<!-- include start from pim/keep-alive-timer.xml.i --> +<leafNode name="keep-alive-timer"> + <properties> + <help>Keep alive Timer</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Keep alive Timer in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pim/packets.xml.i b/interface-definitions/include/pim/packets.xml.i new file mode 100644 index 0000000..1dc00c9 --- /dev/null +++ b/interface-definitions/include/pim/packets.xml.i @@ -0,0 +1,15 @@ +<!-- include start from pim/packets.xml.i --> +<leafNode name="packets"> + <properties> + <help>Packets to process at once</help> + <valueHelp> + <format>u32:1-255</format> + <description>Number of packets</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <defaultValue>3</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pim/passive.xml.i b/interface-definitions/include/pim/passive.xml.i new file mode 100644 index 0000000..e4e9ca0 --- /dev/null +++ b/interface-definitions/include/pim/passive.xml.i @@ -0,0 +1,8 @@ +<!-- include start from pim/passive.xml.i --> +<leafNode name="passive"> + <properties> + <help>Disable sending and receiving PIM control packets on the interface</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pim/register-suppress-time.xml.i b/interface-definitions/include/pim/register-suppress-time.xml.i new file mode 100644 index 0000000..919945b --- /dev/null +++ b/interface-definitions/include/pim/register-suppress-time.xml.i @@ -0,0 +1,14 @@ +<!-- include start from pim/register-suppress-time.xml.i --> +<leafNode name="register-suppress-time"> + <properties> + <help>Register suppress timer</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Timer in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pki/ca-certificate-multi.xml.i b/interface-definitions/include/pki/ca-certificate-multi.xml.i new file mode 100644 index 0000000..646131b --- /dev/null +++ b/interface-definitions/include/pki/ca-certificate-multi.xml.i @@ -0,0 +1,15 @@ +<!-- include start from pki/ca-certificate-multi.xml.i --> +<leafNode name="ca-certificate"> + <properties> + <help>Certificate Authority chain in PKI configuration</help> + <completionHelp> + <path>pki ca</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of CA in PKI configuration</description> + </valueHelp> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pki/ca-certificate.xml.i b/interface-definitions/include/pki/ca-certificate.xml.i new file mode 100644 index 0000000..b32bb67 --- /dev/null +++ b/interface-definitions/include/pki/ca-certificate.xml.i @@ -0,0 +1,14 @@ +<!-- include start from pki/ca-certificate.xml.i --> +<leafNode name="ca-certificate"> + <properties> + <help>Certificate Authority in PKI configuration</help> + <completionHelp> + <path>pki ca</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of CA in PKI configuration</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pki/certificate-key.xml.i b/interface-definitions/include/pki/certificate-key.xml.i new file mode 100644 index 0000000..7f26d25 --- /dev/null +++ b/interface-definitions/include/pki/certificate-key.xml.i @@ -0,0 +1,12 @@ +<!-- include start from pki/certificate-key.xml.i --> +#include <include/pki/certificate.xml.i> +<leafNode name="passphrase"> + <properties> + <help>Private key passphrase</help> + <valueHelp> + <format>txt</format> + <description>Passphrase to decrypt the private key</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pki/certificate-multi.xml.i b/interface-definitions/include/pki/certificate-multi.xml.i new file mode 100644 index 0000000..c49c5d9 --- /dev/null +++ b/interface-definitions/include/pki/certificate-multi.xml.i @@ -0,0 +1,15 @@ +<!-- include start from pki/certificate-multi.xml.i --> +<leafNode name="certificate"> + <properties> + <help>Certificate in PKI configuration</help> + <completionHelp> + <path>pki certificate</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of certificate in PKI configuration</description> + </valueHelp> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pki/certificate.xml.i b/interface-definitions/include/pki/certificate.xml.i new file mode 100644 index 0000000..1ba70e0 --- /dev/null +++ b/interface-definitions/include/pki/certificate.xml.i @@ -0,0 +1,14 @@ +<!-- include start from pki/certificate.xml.i --> +<leafNode name="certificate"> + <properties> + <help>Certificate in PKI configuration</help> + <completionHelp> + <path>pki certificate</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of certificate in PKI configuration</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pki/cli-certificate-base64.xml.i b/interface-definitions/include/pki/cli-certificate-base64.xml.i new file mode 100644 index 0000000..a3eff79 --- /dev/null +++ b/interface-definitions/include/pki/cli-certificate-base64.xml.i @@ -0,0 +1,11 @@ +<!-- include start from pki/cli-certificate-base64.xml.i --> +<leafNode name="certificate"> + <properties> + <help>Certificate in PEM format</help> + <constraint> + <validator name="base64"/> + </constraint> + <constraintErrorMessage>Certificate is not base64-encoded</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pki/cli-private-key-base64.xml.i b/interface-definitions/include/pki/cli-private-key-base64.xml.i new file mode 100644 index 0000000..f57e9b1 --- /dev/null +++ b/interface-definitions/include/pki/cli-private-key-base64.xml.i @@ -0,0 +1,11 @@ +<!-- include start from pki/cli-private-key-base64.xml.i --> +<leafNode name="key"> + <properties> + <help>Private key in PEM format</help> + <constraint> + <validator name="base64"/> + </constraint> + <constraintErrorMessage>Private key is not base64-encoded</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pki/cli-public-key-base64.xml.i b/interface-definitions/include/pki/cli-public-key-base64.xml.i new file mode 100644 index 0000000..f7cffae --- /dev/null +++ b/interface-definitions/include/pki/cli-public-key-base64.xml.i @@ -0,0 +1,11 @@ +<!-- include start from pki/cli-public-key-base64.xml.i --> +<leafNode name="key"> + <properties> + <help>Public key in PEM format</help> + <constraint> + <validator name="base64"/> + </constraint> + <constraintErrorMessage>Public key is not base64-encoded</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pki/cli-revoke.xml.i b/interface-definitions/include/pki/cli-revoke.xml.i new file mode 100644 index 0000000..61cd978 --- /dev/null +++ b/interface-definitions/include/pki/cli-revoke.xml.i @@ -0,0 +1,8 @@ +<!-- include start from pki/cli-revoke.xml.i --> +<leafNode name="revoke"> + <properties> + <help>Include certificate in parent CRL</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pki/dh-params.xml.i b/interface-definitions/include/pki/dh-params.xml.i new file mode 100644 index 0000000..a422df8 --- /dev/null +++ b/interface-definitions/include/pki/dh-params.xml.i @@ -0,0 +1,10 @@ +<!-- include start from pki/certificate-multi.xml.i --> +<leafNode name="dh-params"> + <properties> + <help>Diffie Hellman parameters (server only)</help> + <completionHelp> + <path>pki dh</path> + </completionHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pki/openssh-key.xml.i b/interface-definitions/include/pki/openssh-key.xml.i new file mode 100644 index 0000000..8f005d0 --- /dev/null +++ b/interface-definitions/include/pki/openssh-key.xml.i @@ -0,0 +1,14 @@ +<!-- include start from pki/openssh-key.xml.i --> +<leafNode name="key"> + <properties> + <help>OpenSSH key in PKI configuration</help> + <completionHelp> + <path>pki openssh</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of OpenSSH key in PKI configuration</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pki/password-protected.xml.i b/interface-definitions/include/pki/password-protected.xml.i new file mode 100644 index 0000000..b72e4ec --- /dev/null +++ b/interface-definitions/include/pki/password-protected.xml.i @@ -0,0 +1,8 @@ +<!-- include start from pki/password-protected.xml.i --> +<leafNode name="password-protected"> + <properties> + <help>Private key portion is password protected</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pki/private-key.xml.i b/interface-definitions/include/pki/private-key.xml.i new file mode 100644 index 0000000..ae4e910 --- /dev/null +++ b/interface-definitions/include/pki/private-key.xml.i @@ -0,0 +1,30 @@ +<!-- include start from pki/private-key.xml.i --> +<node name="private"> + <properties> + <help>Private key</help> + </properties> + <children> + <leafNode name="key"> + <properties> + <help>Private key in PKI configuration</help> + <completionHelp> + <path>pki key-pair</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of private key in PKI configuration</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="passphrase"> + <properties> + <help>Private key passphrase</help> + <valueHelp> + <format>txt</format> + <description>Passphrase to decrypt the private key</description> + </valueHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/pki/public-key.xml.i b/interface-definitions/include/pki/public-key.xml.i new file mode 100644 index 0000000..3067bff --- /dev/null +++ b/interface-definitions/include/pki/public-key.xml.i @@ -0,0 +1,14 @@ +<!-- include start from pki/public-key.xml.i --> +<leafNode name="public-key"> + <properties> + <help>Public key in PKI configuration</help> + <completionHelp> + <path>pki key-pair</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of public key in PKI configuration</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/policy/action.xml.i b/interface-definitions/include/policy/action.xml.i new file mode 100644 index 0000000..5aa8655 --- /dev/null +++ b/interface-definitions/include/policy/action.xml.i @@ -0,0 +1,21 @@ +<!-- include start from policy/action.xml.i --> +<leafNode name="action"> + <properties> + <help>Action to take on entries matching this rule</help> + <completionHelp> + <list>permit deny</list> + </completionHelp> + <valueHelp> + <format>permit</format> + <description>Permit matching entries</description> + </valueHelp> + <valueHelp> + <format>deny</format> + <description>Deny matching entries</description> + </valueHelp> + <constraint> + <regex>(permit|deny)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/policy/community-clear.xml.i b/interface-definitions/include/policy/community-clear.xml.i new file mode 100644 index 0000000..0fd57cd --- /dev/null +++ b/interface-definitions/include/policy/community-clear.xml.i @@ -0,0 +1,8 @@ +<!-- include start from policy/community-clear.xml.i --> +<leafNode name="none"> + <properties> + <help>Completely remove communities attribute from a prefix</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/policy/community-value-list.xml.i b/interface-definitions/include/policy/community-value-list.xml.i new file mode 100644 index 0000000..8c665c5 --- /dev/null +++ b/interface-definitions/include/policy/community-value-list.xml.i @@ -0,0 +1,90 @@ +<!-- include start from policy/community-value-list.xml.i --> +<completionHelp> + <list> + local-as + no-advertise + no-export + internet + graceful-shutdown + accept-own + route-filter-translated-v4 + route-filter-v4 + route-filter-translated-v6 + route-filter-v6 + llgr-stale + no-llgr + accept-own-nexthop + blackhole + no-peer + </list> +</completionHelp> +<valueHelp> + <format><AS:VAL></format> + <description>Community number in <0-65535:0-65535> format</description> +</valueHelp> +<valueHelp> + <format>local-as</format> + <description>Well-known communities value NO_EXPORT_SUBCONFED 0xFFFFFF03</description> +</valueHelp> +<valueHelp> + <format>no-advertise</format> + <description>Well-known communities value NO_ADVERTISE 0xFFFFFF02</description> +</valueHelp> +<valueHelp> + <format>no-export</format> + <description>Well-known communities value NO_EXPORT 0xFFFFFF01</description> +</valueHelp> +<valueHelp> + <format>internet</format> + <description>Well-known communities value 0</description> +</valueHelp> +<valueHelp> + <format>graceful-shutdown</format> + <description>Well-known communities value GRACEFUL_SHUTDOWN 0xFFFF0000</description> +</valueHelp> +<valueHelp> + <format>accept-own</format> + <description>Well-known communities value ACCEPT_OWN 0xFFFF0001</description> +</valueHelp> +<valueHelp> + <format>route-filter-translated-v4</format> + <description>Well-known communities value ROUTE_FILTER_TRANSLATED_v4 0xFFFF0002</description> +</valueHelp> +<valueHelp> + <format>route-filter-v4</format> + <description>Well-known communities value ROUTE_FILTER_v4 0xFFFF0003</description> +</valueHelp> +<valueHelp> + <format>route-filter-translated-v6</format> + <description>Well-known communities value ROUTE_FILTER_TRANSLATED_v6 0xFFFF0004</description> +</valueHelp> +<valueHelp> + <format>route-filter-v6</format> + <description>Well-known communities value ROUTE_FILTER_v6 0xFFFF0005</description> +</valueHelp> +<valueHelp> + <format>llgr-stale</format> + <description>Well-known communities value LLGR_STALE 0xFFFF0006</description> +</valueHelp> +<valueHelp> + <format>no-llgr</format> + <description>Well-known communities value NO_LLGR 0xFFFF0007</description> +</valueHelp> +<valueHelp> + <format>accept-own-nexthop</format> + <description>Well-known communities value accept-own-nexthop 0xFFFF0008</description> +</valueHelp> +<valueHelp> + <format>blackhole</format> + <description>Well-known communities value BLACKHOLE 0xFFFF029A</description> +</valueHelp> +<valueHelp> + <format>no-peer</format> + <description>Well-known communities value NOPEER 0xFFFFFF04</description> +</valueHelp> +<multi/> +<constraint> + <regex>local-as|no-advertise|no-export|internet|graceful-shutdown|accept-own|route-filter-translated-v4|route-filter-v4|route-filter-translated-v6|route-filter-v6|llgr-stale|no-llgr|accept-own-nexthop|blackhole|no-peer</regex> + <validator name="bgp-regular-community"/> +</constraint> + <!-- include end --> diff --git a/interface-definitions/include/policy/extended-community-value-list.xml.i b/interface-definitions/include/policy/extended-community-value-list.xml.i new file mode 100644 index 0000000..33a279b --- /dev/null +++ b/interface-definitions/include/policy/extended-community-value-list.xml.i @@ -0,0 +1,15 @@ +<!-- include start from policy/community-value-list.xml.i --> +<valueHelp> + <format>ASN:NN</format> + <description>based on autonomous system number in format <0-65535:0-4294967295></description> +</valueHelp> +<valueHelp> + <format>IP:NN</format> + <description>Based on a router-id IP address in format <IP:0-65535></description> +</valueHelp> +<constraint> + <validator name="bgp-extended-community"/> +</constraint> +<constraintErrorMessage>Should be in form: ASN:NN or IPADDR:NN where ASN is autonomous system number</constraintErrorMessage> +<multi/> +<!-- include end --> diff --git a/interface-definitions/include/policy/host.xml.i b/interface-definitions/include/policy/host.xml.i new file mode 100644 index 0000000..ac017c6 --- /dev/null +++ b/interface-definitions/include/policy/host.xml.i @@ -0,0 +1,14 @@ +<!-- include start from policy/host.xml.i --> +<leafNode name="host"> + <properties> + <help>Single host IP address to match</help> + <valueHelp> + <format>ipv4</format> + <description>Host address to match</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/policy/inverse-mask.xml.i b/interface-definitions/include/policy/inverse-mask.xml.i new file mode 100644 index 0000000..cec69a8 --- /dev/null +++ b/interface-definitions/include/policy/inverse-mask.xml.i @@ -0,0 +1,14 @@ +<!-- include start from policy/inverse-mask.xml.i --> +<leafNode name="inverse-mask"> + <properties> + <help>Network/netmask to match (requires network be defined)</help> + <valueHelp> + <format>ipv4</format> + <description>Inverse-mask to match</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/policy/large-community-value-list.xml.i b/interface-definitions/include/policy/large-community-value-list.xml.i new file mode 100644 index 0000000..33b1f13 --- /dev/null +++ b/interface-definitions/include/policy/large-community-value-list.xml.i @@ -0,0 +1,10 @@ +<!-- include start from policy/community-value-list.xml.i --> +<valueHelp> + <description>Community in format <0-4294967295:0-4294967295:0-4294967295></description> + <format><GA:LDP1:LDP2></format> +</valueHelp> +<multi/> +<constraint> + <validator name="bgp-large-community"/> +</constraint> + <!-- include end --> diff --git a/interface-definitions/include/policy/local-route_rule_ipv4_address.xml.i b/interface-definitions/include/policy/local-route_rule_ipv4_address.xml.i new file mode 100644 index 0000000..ffe73ee --- /dev/null +++ b/interface-definitions/include/policy/local-route_rule_ipv4_address.xml.i @@ -0,0 +1,20 @@ +<!-- include start from policy/local-route_rule_ipv4_address.xml.i --> +<leafNode name="address"> + <properties> + <help>IPv4 address or prefix</help> + <valueHelp> + <format>ipv4</format> + <description>Address to match against</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>Prefix to match against</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ip-prefix"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/policy/local-route_rule_ipv6_address.xml.i b/interface-definitions/include/policy/local-route_rule_ipv6_address.xml.i new file mode 100644 index 0000000..d8fb6c0 --- /dev/null +++ b/interface-definitions/include/policy/local-route_rule_ipv6_address.xml.i @@ -0,0 +1,20 @@ +<!-- include start from policy/local-route_rule_ipv6_address.xml.i --> +<leafNode name="address"> + <properties> + <help>IPv6 address or prefix</help> + <valueHelp> + <format>ipv6</format> + <description>Address to match against</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>Prefix to match against</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + <validator name="ipv6-prefix"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/policy/local-route_rule_protocol.xml.i b/interface-definitions/include/policy/local-route_rule_protocol.xml.i new file mode 100644 index 0000000..57582eb --- /dev/null +++ b/interface-definitions/include/policy/local-route_rule_protocol.xml.i @@ -0,0 +1,21 @@ +<!-- include start from policy/local-route_rule_protocol.xml.i --> +<leafNode name="protocol"> + <properties> + <help>Protocol to match (protocol name or number)</help> + <completionHelp> + <script>${vyos_completion_dir}/list_protocols.sh</script> + </completionHelp> + <valueHelp> + <format>u32:0-255</format> + <description>IP protocol number</description> + </valueHelp> + <valueHelp> + <format><protocol></format> + <description>IP protocol name</description> + </valueHelp> + <constraint> + <validator name="ip-protocol"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/policy/network.xml.i b/interface-definitions/include/policy/network.xml.i new file mode 100644 index 0000000..f2aea6b --- /dev/null +++ b/interface-definitions/include/policy/network.xml.i @@ -0,0 +1,14 @@ +<!-- include start from policy/network.xml.i --> +<leafNode name="network"> + <properties> + <help>Network/netmask to match (requires inverse-mask be defined)</help> + <valueHelp> + <format>ipv4net</format> + <description>Inverse-mask to match</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/policy/prefix-list.xml.i b/interface-definitions/include/policy/prefix-list.xml.i new file mode 100644 index 0000000..5d7980e --- /dev/null +++ b/interface-definitions/include/policy/prefix-list.xml.i @@ -0,0 +1,14 @@ +<!-- include start from policy/prefix-list.xml.i --> +<leafNode name="prefix-list"> + <properties> + <help>Prefix-list to use</help> + <valueHelp> + <format>txt</format> + <description>Prefix-list to apply (IPv4)</description> + </valueHelp> + <completionHelp> + <path>policy prefix-list</path> + </completionHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/policy/prefix-list6.xml.i b/interface-definitions/include/policy/prefix-list6.xml.i new file mode 100644 index 0000000..101702f --- /dev/null +++ b/interface-definitions/include/policy/prefix-list6.xml.i @@ -0,0 +1,14 @@ +<!-- include start from policy/prefix-list6.xml.i --> +<leafNode name="prefix-list6"> + <properties> + <help>Prefix-list to use</help> + <valueHelp> + <format>txt</format> + <description>Prefix-list to apply (IPv6)</description> + </valueHelp> + <completionHelp> + <path>policy prefix-list6</path> + </completionHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/policy/route-common.xml.i b/interface-definitions/include/policy/route-common.xml.i new file mode 100644 index 0000000..19ffc05 --- /dev/null +++ b/interface-definitions/include/policy/route-common.xml.i @@ -0,0 +1,116 @@ +<!-- include start from policy/route-common.xml.i --> +#include <include/policy/route-rule-action.xml.i> +#include <include/generic-description.xml.i> +#include <include/firewall/firewall-mark.xml.i> +#include <include/generic-disable-node.xml.i> +#include <include/firewall/fragment.xml.i> +#include <include/firewall/match-ipsec.xml.i> +#include <include/firewall/limit.xml.i> +#include <include/firewall/log.xml.i> +<leafNode name="protocol"> + <properties> + <help>Protocol to match (protocol name, number, or "all")</help> + <completionHelp> + <script>cat /etc/protocols | sed -e '/^#.*/d' | awk '{ print $1 }'</script> + </completionHelp> + <valueHelp> + <format>all</format> + <description>All IP protocols</description> + </valueHelp> + <valueHelp> + <format>tcp_udp</format> + <description>Both TCP and UDP</description> + </valueHelp> + <valueHelp> + <format>0-255</format> + <description>IP protocol number</description> + </valueHelp> + <valueHelp> + <format>!<protocol></format> + <description>IP protocol number</description> + </valueHelp> + <constraint> + <validator name="ip-protocol"/> + </constraint> + </properties> + <defaultValue>all</defaultValue> +</leafNode> +<node name="recent"> + <properties> + <help>Parameters for matching recently seen sources</help> + </properties> + <children> + <leafNode name="count"> + <properties> + <help>Source addresses seen more than N times</help> + <valueHelp> + <format>u32:1-255</format> + <description>Source addresses seen more than N times</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="time"> + <properties> + <help>Source addresses seen in the last N seconds</help> + <valueHelp> + <format>u32:0-4294967295</format> + <description>Source addresses seen in the last N seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +#include <include/firewall/set-packet-modifications.xml.i> +#include <include/firewall/state.xml.i> +#include <include/firewall/tcp-flags.xml.i> +#include <include/firewall/tcp-mss.xml.i> +<node name="time"> + <properties> + <help>Time to match rule</help> + </properties> + <children> + <leafNode name="monthdays"> + <properties> + <help>Monthdays to match rule on</help> + </properties> + </leafNode> + <leafNode name="startdate"> + <properties> + <help>Date to start matching rule</help> + </properties> + </leafNode> + <leafNode name="starttime"> + <properties> + <help>Time of day to start matching rule</help> + </properties> + </leafNode> + <leafNode name="stopdate"> + <properties> + <help>Date to stop matching rule</help> + </properties> + </leafNode> + <leafNode name="stoptime"> + <properties> + <help>Time of day to stop matching rule</help> + </properties> + </leafNode> + <leafNode name="utc"> + <properties> + <help>Interpret times for startdate, stopdate, starttime and stoptime to be UTC</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="weekdays"> + <properties> + <help>Weekdays to match rule on</help> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/policy/route-ipv4.xml.i b/interface-definitions/include/policy/route-ipv4.xml.i new file mode 100644 index 0000000..c12abca --- /dev/null +++ b/interface-definitions/include/policy/route-ipv4.xml.i @@ -0,0 +1,14 @@ +<!-- include start from policy/route-ipv4.xml.i --> +<node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/address.xml.i> + #include <include/firewall/source-destination-group.xml.i> + #include <include/firewall/mac-address.xml.i> + #include <include/firewall/port.xml.i> + </children> +</node> +#include <include/firewall/icmp.xml.i> +<!-- include end --> diff --git a/interface-definitions/include/policy/route-ipv6.xml.i b/interface-definitions/include/policy/route-ipv6.xml.i new file mode 100644 index 0000000..d636a65 --- /dev/null +++ b/interface-definitions/include/policy/route-ipv6.xml.i @@ -0,0 +1,196 @@ +<!-- include start from policy/route-ipv6.xml.i --> +<node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/firewall/source-destination-group.xml.i> + #include <include/firewall/mac-address.xml.i> + #include <include/firewall/port.xml.i> + </children> +</node> +<node name="icmpv6"> + <properties> + <help>ICMPv6 type and code information</help> + </properties> + <children> + <leafNode name="type"> + <properties> + <help>ICMP type-name</help> + <completionHelp> + <list>any echo-reply pong destination-unreachable network-unreachable host-unreachable protocol-unreachable port-unreachable fragmentation-needed source-route-failed network-unknown host-unknown network-prohibited host-prohibited TOS-network-unreachable TOS-host-unreachable communication-prohibited host-precedence-violation precedence-cutoff source-quench redirect network-redirect host-redirect TOS-network-redirect TOS host-redirect echo-request ping router-advertisement router-solicitation time-exceeded ttl-exceeded ttl-zero-during-transit ttl-zero-during-reassembly parameter-problem ip-header-bad required-option-missing timestamp-request timestamp-reply address-mask-request address-mask-reply packet-too-big</list> + </completionHelp> + <valueHelp> + <format>any</format> + <description>Any ICMP type/code</description> + </valueHelp> + <valueHelp> + <format>echo-reply</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>pong</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>destination-unreachable</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>network-unreachable</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>host-unreachable</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>protocol-unreachable</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>port-unreachable</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>fragmentation-needed</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>source-route-failed</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>network-unknown</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>host-unknown</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>network-prohibited</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>host-prohibited</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>TOS-network-unreachable</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>TOS-host-unreachable</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>communication-prohibited</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>host-precedence-violation</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>precedence-cutoff</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>source-quench</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>redirect</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>network-redirect</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>host-redirect</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>TOS-network-redirect</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>TOS host-redirect</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>echo-request</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>ping</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>router-advertisement</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>router-solicitation</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>time-exceeded</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>ttl-exceeded</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>ttl-zero-during-transit</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>ttl-zero-during-reassembly</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>parameter-problem</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>ip-header-bad</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>required-option-missing</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>timestamp-request</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>timestamp-reply</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>address-mask-request</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>address-mask-reply</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>packet-too-big</format> + <description>ICMP type/code name</description> + </valueHelp> + <constraint> + <regex>(any|echo-reply|pong|destination-unreachable|network-unreachable|host-unreachable|protocol-unreachable|port-unreachable|fragmentation-needed|source-route-failed|network-unknown|host-unknown|network-prohibited|host-prohibited|TOS-network-unreachable|TOS-host-unreachable|communication-prohibited|host-precedence-violation|precedence-cutoff|source-quench|redirect|network-redirect|host-redirect|TOS-network-redirect|TOS host-redirect|echo-request|ping|router-advertisement|router-solicitation|time-exceeded|ttl-exceeded|ttl-zero-during-transit|ttl-zero-during-reassembly|parameter-problem|ip-header-bad|required-option-missing|timestamp-request|timestamp-reply|address-mask-request|address-mask-reply|packet-too-big)</regex> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/policy/route-rule-action.xml.i b/interface-definitions/include/policy/route-rule-action.xml.i new file mode 100644 index 0000000..c2698db --- /dev/null +++ b/interface-definitions/include/policy/route-rule-action.xml.i @@ -0,0 +1,29 @@ +<!-- include start from policy/route-rule-action.xml.i --> +<leafNode name="action"> + <properties> + <help>Rule action</help> + <completionHelp> + <list>accept reject return drop</list> + </completionHelp> + <valueHelp> + <format>accept</format> + <description>Accept matching entries</description> + </valueHelp> + <valueHelp> + <format>reject</format> + <description>Reject matching entries</description> + </valueHelp> + <valueHelp> + <format>return</format> + <description>Return from the current chain and continue at the next rule of the last chain</description> + </valueHelp> + <valueHelp> + <format>drop</format> + <description>Drop matching entries</description> + </valueHelp> + <constraint> + <regex>(accept|reject|return|drop)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/policy/tag.xml.i b/interface-definitions/include/policy/tag.xml.i new file mode 100644 index 0000000..ec25b93 --- /dev/null +++ b/interface-definitions/include/policy/tag.xml.i @@ -0,0 +1,14 @@ +<!-- include start from policy/tag.xml.i --> +<leafNode name="tag"> + <properties> + <help>Route tag value</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Route tag</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/port-number-start-zero.xml.i b/interface-definitions/include/port-number-start-zero.xml.i new file mode 100644 index 0000000..04a1442 --- /dev/null +++ b/interface-definitions/include/port-number-start-zero.xml.i @@ -0,0 +1,15 @@ +<!-- include start from port-number-start-zero.xml.i --> +<leafNode name="port"> + <properties> + <help>Port number used by connection</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + <constraintErrorMessage>Port number must be in range 0 to 65535</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/port-number.xml.i b/interface-definitions/include/port-number.xml.i new file mode 100644 index 0000000..6820df0 --- /dev/null +++ b/interface-definitions/include/port-number.xml.i @@ -0,0 +1,15 @@ +<!-- include start from port-number.xml.i --> +<leafNode name="port"> + <properties> + <help>Port number used by connection</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + <constraintErrorMessage>Port number must be in range 1 to 65535</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/port-port-range.xml.i b/interface-definitions/include/port-port-range.xml.i new file mode 100644 index 0000000..ce550f5 --- /dev/null +++ b/interface-definitions/include/port-port-range.xml.i @@ -0,0 +1,26 @@ +<!-- include start from port-port-range.xml.i --> +<leafNode name="port"> + <properties> + <help>Port number</help> + <valueHelp> + <format>txt</format> + <description>Named port (any name in /etc/services, e.g., http)</description> + </valueHelp> + <valueHelp> + <format>u32:1-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <valueHelp> + <format>start-end</format> + <description>Numbered port range (e.g. 1001-1005)</description> + </valueHelp> + <valueHelp> + <format/> + <description>\n\nMultiple destination ports can be specified as a comma-separated list.\nThe whole list can also be negated using '!'.\nFor example: '!22,telnet,http,123,1001-1005'</description> + </valueHelp> + <constraint> + <validator name="port-multi"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/pppoe-access-concentrator.xml.i b/interface-definitions/include/pppoe-access-concentrator.xml.i new file mode 100644 index 0000000..8a75dae --- /dev/null +++ b/interface-definitions/include/pppoe-access-concentrator.xml.i @@ -0,0 +1,11 @@ +<!-- include start from pppoe-access-concentrator.xml.i --> +<leafNode name="access-concentrator"> + <properties> + <help>Access concentrator name</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Access-concentrator name can only contain alpha-numeric letters, hyphen and underscores(max. 100 characters)</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/protocol-tcp-udp.xml.i b/interface-definitions/include/protocol-tcp-udp.xml.i new file mode 100644 index 0000000..c186c02 --- /dev/null +++ b/interface-definitions/include/protocol-tcp-udp.xml.i @@ -0,0 +1,22 @@ +<!-- include start from snmp/protocol.xml.i --> +<leafNode name="protocol"> + <properties> + <help>Protocol to be used (TCP/UDP)</help> + <completionHelp> + <list>udp tcp</list> + </completionHelp> + <valueHelp> + <format>udp</format> + <description>Listen protocol UDP</description> + </valueHelp> + <valueHelp> + <format>tcp</format> + <description>Listen protocol TCP</description> + </valueHelp> + <constraint> + <regex>(udp|tcp)</regex> + </constraint> + </properties> + <defaultValue>udp</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/bandwidth-auto.xml.i b/interface-definitions/include/qos/bandwidth-auto.xml.i new file mode 100644 index 0000000..fa16a6c --- /dev/null +++ b/interface-definitions/include/qos/bandwidth-auto.xml.i @@ -0,0 +1,47 @@ +<!-- include start from qos/bandwidth-auto.xml.i --> +<leafNode name="bandwidth"> + <properties> + <help>Available bandwidth for this policy</help> + <completionHelp> + <list>auto</list> + </completionHelp> + <valueHelp> + <format>auto</format> + <description>Bandwidth matches interface speed</description> + </valueHelp> + <valueHelp> + <format><number></format> + <description>Bits per second</description> + </valueHelp> + <valueHelp> + <format><number>bit</format> + <description>Bits per second</description> + </valueHelp> + <valueHelp> + <format><number>kbit</format> + <description>Kilobits per second</description> + </valueHelp> + <valueHelp> + <format><number>mbit</format> + <description>Megabits per second</description> + </valueHelp> + <valueHelp> + <format><number>gbit</format> + <description>Gigabits per second</description> + </valueHelp> + <valueHelp> + <format><number>tbit</format> + <description>Terabits per second</description> + </valueHelp> + <valueHelp> + <format><number>%%</format> + <description>Percentage of interface link speed</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--positive"/> + <regex>(auto|\d+(bit|kbit|mbit|gbit|tbit)?|(100|\d(\d)?)%)</regex> + </constraint> + </properties> + <defaultValue>auto</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/bandwidth.xml.i b/interface-definitions/include/qos/bandwidth.xml.i new file mode 100644 index 0000000..0e29b64 --- /dev/null +++ b/interface-definitions/include/qos/bandwidth.xml.i @@ -0,0 +1,39 @@ +<!-- include start from qos/bandwidth.xml.i --> +<leafNode name="bandwidth"> + <properties> + <help>Available bandwidth for this policy</help> + <valueHelp> + <format><number></format> + <description>Bits per second</description> + </valueHelp> + <valueHelp> + <format><number>bit</format> + <description>Bits per second</description> + </valueHelp> + <valueHelp> + <format><number>kbit</format> + <description>Kilobits per second</description> + </valueHelp> + <valueHelp> + <format><number>mbit</format> + <description>Megabits per second</description> + </valueHelp> + <valueHelp> + <format><number>gbit</format> + <description>Gigabits per second</description> + </valueHelp> + <valueHelp> + <format><number>tbit</format> + <description>Terabits per second</description> + </valueHelp> + <valueHelp> + <format><number>%%</format> + <description>Percentage of interface link speed</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--positive"/> + <regex>(\d+(bit|kbit|mbit|gbit|tbit)?|(100|\d(\d)?)%)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/burst.xml.i b/interface-definitions/include/qos/burst.xml.i new file mode 100644 index 0000000..7616180 --- /dev/null +++ b/interface-definitions/include/qos/burst.xml.i @@ -0,0 +1,16 @@ +<!-- include start from qos/burst.xml.i --> +<leafNode name="burst"> + <properties> + <help>Burst size for this class</help> + <valueHelp> + <format><number></format> + <description>Bytes</description> + </valueHelp> + <valueHelp> + <format><number><suffix></format> + <description>Bytes with scaling suffix (kb, mb, gb)</description> + </valueHelp> + </properties> + <defaultValue>15k</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/class-match-group.xml.i b/interface-definitions/include/qos/class-match-group.xml.i new file mode 100644 index 0000000..40e3b72 --- /dev/null +++ b/interface-definitions/include/qos/class-match-group.xml.i @@ -0,0 +1,15 @@ +<!-- include start from qos/class-match-group.xml.i --> +<leafNode name="match-group"> + <properties> + <help>Filter group for QoS policy</help> + <valueHelp> + <format>txt</format> + <description>Match group name</description> + </valueHelp> + <completionHelp> + <script>${vyos_completion_dir}/qos/list_traffic_match_group.py</script> + </completionHelp> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/class-match-ipv4-address.xml.i b/interface-definitions/include/qos/class-match-ipv4-address.xml.i new file mode 100644 index 0000000..8e84c98 --- /dev/null +++ b/interface-definitions/include/qos/class-match-ipv4-address.xml.i @@ -0,0 +1,19 @@ +<!-- include start from qos/class-match-ipv4-address.xml.i --> +<leafNode name="address"> + <properties> + <help>IPv4 destination address for this match</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv4-prefix"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/class-match-ipv4.xml.i b/interface-definitions/include/qos/class-match-ipv4.xml.i new file mode 100644 index 0000000..dc44d32 --- /dev/null +++ b/interface-definitions/include/qos/class-match-ipv4.xml.i @@ -0,0 +1,31 @@ +<!-- include start from qos/class-match-ipv4.xml.i --> +<node name="ip"> + <properties> + <help>Match IP protocol header</help> + </properties> + <children> + <node name="destination"> + <properties> + <help>Match on destination port or address</help> + </properties> + <children> + #include <include/qos/class-match-ipv4-address.xml.i> + #include <include/port-number.xml.i> + </children> + </node> + #include <include/qos/match-dscp.xml.i> + #include <include/qos/max-length.xml.i> + #include <include/ip-protocol.xml.i> + <node name="source"> + <properties> + <help>Match on source port or address</help> + </properties> + <children> + #include <include/qos/class-match-ipv4-address.xml.i> + #include <include/port-number.xml.i> + </children> + </node> + #include <include/qos/tcp-flags.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/qos/class-match-ipv6-address.xml.i b/interface-definitions/include/qos/class-match-ipv6-address.xml.i new file mode 100644 index 0000000..fd73881 --- /dev/null +++ b/interface-definitions/include/qos/class-match-ipv6-address.xml.i @@ -0,0 +1,14 @@ +<!-- include start from qos/class-match-ipv6-address.xml.i --> +<leafNode name="address"> + <properties> + <help>IPv6 destination address for this match</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv6"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/class-match-ipv6.xml.i b/interface-definitions/include/qos/class-match-ipv6.xml.i new file mode 100644 index 0000000..ed7acef --- /dev/null +++ b/interface-definitions/include/qos/class-match-ipv6.xml.i @@ -0,0 +1,31 @@ +<!-- include start from qos/class-match-ipv6.xml.i --> +<node name="ipv6"> + <properties> + <help>Match IPv6 protocol header</help> + </properties> + <children> + <node name="destination"> + <properties> + <help>Match on destination port or address</help> + </properties> + <children> + #include <include/qos/class-match-ipv6-address.xml.i> + #include <include/port-number.xml.i> + </children> + </node> + #include <include/qos/match-dscp.xml.i> + #include <include/qos/max-length.xml.i> + #include <include/ip-protocol.xml.i> + <node name="source"> + <properties> + <help>Match on source port or address</help> + </properties> + <children> + #include <include/qos/class-match-ipv6-address.xml.i> + #include <include/port-number.xml.i> + </children> + </node> + #include <include/qos/tcp-flags.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/qos/class-match-mark.xml.i b/interface-definitions/include/qos/class-match-mark.xml.i new file mode 100644 index 0000000..a7481c6 --- /dev/null +++ b/interface-definitions/include/qos/class-match-mark.xml.i @@ -0,0 +1,14 @@ +<!-- include start from qos/class-match-mark.xml.i --> +<leafNode name="mark"> + <properties> + <help>Match on mark applied by firewall</help> + <valueHelp> + <format>u32</format> + <description>FW mark to match</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/class-match-vif.xml.i b/interface-definitions/include/qos/class-match-vif.xml.i new file mode 100644 index 0000000..ec58db6 --- /dev/null +++ b/interface-definitions/include/qos/class-match-vif.xml.i @@ -0,0 +1,15 @@ +<!-- include start from qos/class-match-vif.xml.i --> +<leafNode name="vif"> + <properties> + <help>Virtual Local Area Network (VLAN) ID for this match</help> + <valueHelp> + <format>u32:0-4095</format> + <description>Virtual Local Area Network (VLAN) tag </description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4095"/> + </constraint> + <constraintErrorMessage>VLAN ID must be between 0 and 4095</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/class-match.xml.i b/interface-definitions/include/qos/class-match.xml.i new file mode 100644 index 0000000..77d1933 --- /dev/null +++ b/interface-definitions/include/qos/class-match.xml.i @@ -0,0 +1,98 @@ +<!-- include start from qos/class-match.xml.i --> +<tagNode name="match"> + <properties> + <help>Class matching rule name</help> + <constraint> + <regex>[^-].*</regex> + </constraint> + <constraintErrorMessage>Match queue name cannot start with hyphen</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <node name="ether"> + <properties> + <help>Ethernet header match</help> + </properties> + <children> + <leafNode name="destination"> + <properties> + <help>Ethernet destination address for this match</help> + <valueHelp> + <format>macaddr</format> + <description>MAC address to match</description> + </valueHelp> + <constraint> + <validator name="mac-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="protocol"> + <properties> + <help>Ethernet protocol for this match</help> + <!-- this refers to /etc/protocols --> + <completionHelp> + <list>all 802.1Q 802_2 802_3 aarp aoe arp atalk dec ip ipv6 ipx lat localtalk rarp snap x25</list> + </completionHelp> + <valueHelp> + <format>u32:0-65535</format> + <description>Ethernet protocol number</description> + </valueHelp> + <valueHelp> + <format>txt</format> + <description>Ethernet protocol name</description> + </valueHelp> + <valueHelp> + <format>all</format> + <description>Any protocol</description> + </valueHelp> + <valueHelp> + <format>ip</format> + <description>Internet IP (IPv4)</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Internet IP (IPv6)</description> + </valueHelp> + <valueHelp> + <format>arp</format> + <description>Address Resolution Protocol</description> + </valueHelp> + <valueHelp> + <format>atalk</format> + <description>Appletalk</description> + </valueHelp> + <valueHelp> + <format>ipx</format> + <description>Novell Internet Packet Exchange</description> + </valueHelp> + <valueHelp> + <format>802.1Q</format> + <description>802.1Q VLAN tag</description> + </valueHelp> + <constraint> + <validator name="ip-protocol"/> + </constraint> + </properties> + </leafNode> + <leafNode name="source"> + <properties> + <help>Ethernet source address for this match</help> + <valueHelp> + <format>macaddr</format> + <description>MAC address to match</description> + </valueHelp> + <constraint> + <validator name="mac-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + #include <include/generic-interface.xml.i> + #include <include/qos/class-match-ipv4.xml.i> + #include <include/qos/class-match-ipv6.xml.i> + #include <include/qos/class-match-mark.xml.i> + #include <include/qos/class-match-vif.xml.i> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/class-police-exceed.xml.i b/interface-definitions/include/qos/class-police-exceed.xml.i new file mode 100644 index 0000000..ee2ce16 --- /dev/null +++ b/interface-definitions/include/qos/class-police-exceed.xml.i @@ -0,0 +1,66 @@ +<!-- include start from qos/police.xml.i --> +<leafNode name="exceed"> + <properties> + <help>Default action for packets exceeding the limiter</help> + <completionHelp> + <list>continue drop ok reclassify pipe</list> + </completionHelp> + <valueHelp> + <format>continue</format> + <description>Do not do anything, just continue with the next action in line</description> + </valueHelp> + <valueHelp> + <format>drop</format> + <description>Drop the packet immediately</description> + </valueHelp> + <valueHelp> + <format>ok</format> + <description>Accept the packet</description> + </valueHelp> + <valueHelp> + <format>reclassify</format> + <description>Treat the packet as non-matching to the filter this action is attached to and continue with the next filter in line (if any)</description> + </valueHelp> + <valueHelp> + <format>pipe</format> + <description>Pass the packet to the next action in line</description> + </valueHelp> + <constraint> + <regex>(continue|drop|ok|reclassify|pipe)</regex> + </constraint> + </properties> + <defaultValue>drop</defaultValue> +</leafNode> +<leafNode name="not-exceed"> + <properties> + <help>Default action for packets not exceeding the limiter</help> + <completionHelp> + <list>continue drop ok reclassify pipe</list> + </completionHelp> + <valueHelp> + <format>continue</format> + <description>Do not do anything, just continue with the next action in line</description> + </valueHelp> + <valueHelp> + <format>drop</format> + <description>Drop the packet immediately</description> + </valueHelp> + <valueHelp> + <format>ok</format> + <description>Accept the packet</description> + </valueHelp> + <valueHelp> + <format>reclassify</format> + <description>Treat the packet as non-matching to the filter this action is attached to and continue with the next filter in line (if any)</description> + </valueHelp> + <valueHelp> + <format>pipe</format> + <description>Pass the packet to the next action in line</description> + </valueHelp> + <constraint> + <regex>(continue|drop|ok|reclassify|pipe)</regex> + </constraint> + </properties> + <defaultValue>ok</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/class-priority.xml.i b/interface-definitions/include/qos/class-priority.xml.i new file mode 100644 index 0000000..3fd848c --- /dev/null +++ b/interface-definitions/include/qos/class-priority.xml.i @@ -0,0 +1,15 @@ +<!-- include start from qos/class-priority.xml.i --> +<leafNode name="priority"> + <properties> + <help>Priority for rule evaluation</help> + <valueHelp> + <format>u32:0-20</format> + <description>Priority for match rule evaluation</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-20"/> + </constraint> + <constraintErrorMessage>Priority must be between 0 and 20</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/codel-quantum.xml.i b/interface-definitions/include/qos/codel-quantum.xml.i new file mode 100644 index 0000000..bc24630 --- /dev/null +++ b/interface-definitions/include/qos/codel-quantum.xml.i @@ -0,0 +1,16 @@ +<!-- include start from qos/codel-quantum.xml.i --> +<leafNode name="codel-quantum"> + <properties> + <help>Deficit in the fair queuing algorithm</help> + <valueHelp> + <format>u32:0-1048576</format> + <description>Number of bytes used as 'deficit'</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-1048576"/> + </constraint> + <constraintErrorMessage>Interval must be in range 0 to 1048576</constraintErrorMessage> + </properties> + <defaultValue>1514</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/flows.xml.i b/interface-definitions/include/qos/flows.xml.i new file mode 100644 index 0000000..a7d7c64 --- /dev/null +++ b/interface-definitions/include/qos/flows.xml.i @@ -0,0 +1,16 @@ +<!-- include start from qos/flows.xml.i --> +<leafNode name="flows"> + <properties> + <help>Number of flows into which the incoming packets are classified</help> + <valueHelp> + <format>u32:1-65536</format> + <description>Number of flows</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65536"/> + </constraint> + <constraintErrorMessage>Interval must be in range 1 to 65536</constraintErrorMessage> + </properties> + <defaultValue>1024</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/hfsc-d.xml.i b/interface-definitions/include/qos/hfsc-d.xml.i new file mode 100644 index 0000000..2a51350 --- /dev/null +++ b/interface-definitions/include/qos/hfsc-d.xml.i @@ -0,0 +1,15 @@ +<!-- include start from qos/hfsc-d.xml.i --> +<leafNode name="d"> + <properties> + <help>Service curve delay</help> + <valueHelp> + <format><number></format> + <description>Time in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + <constraintErrorMessage>Priority must be between 0 and 65535</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/hfsc-m1.xml.i b/interface-definitions/include/qos/hfsc-m1.xml.i new file mode 100644 index 0000000..21b9c4f --- /dev/null +++ b/interface-definitions/include/qos/hfsc-m1.xml.i @@ -0,0 +1,32 @@ +<!-- include start from qos/hfsc-m1.xml.i --> +<leafNode name="m1"> + <properties> + <help>Linkshare m1 parameter for class traffic</help> + <valueHelp> + <format><number></format> + <description>Rate in kbit (kilobit per second)</description> + </valueHelp> + <valueHelp> + <format><number>%%</format> + <description>Percentage of overall rate</description> + </valueHelp> + <valueHelp> + <format><number>bit</format> + <description>bit(1), kbit(10^3), mbit(10^6), gbit, tbit</description> + </valueHelp> + <valueHelp> + <format><number>ibit</format> + <description>kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4)</description> + </valueHelp> + <valueHelp> + <format><number>ibps</format> + <description>kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec</description> + </valueHelp> + <valueHelp> + <format><number>bps</format> + <description>bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec</description> + </valueHelp> + </properties> + <defaultValue>0bit</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/hfsc-m2.xml.i b/interface-definitions/include/qos/hfsc-m2.xml.i new file mode 100644 index 0000000..24e8f5d --- /dev/null +++ b/interface-definitions/include/qos/hfsc-m2.xml.i @@ -0,0 +1,32 @@ +<!-- include start from qos/hfsc-m2.xml.i --> +<leafNode name="m2"> + <properties> + <help>Linkshare m2 parameter for class traffic</help> + <valueHelp> + <format><number></format> + <description>Rate in kbit (kilobit per second)</description> + </valueHelp> + <valueHelp> + <format><number>%%</format> + <description>Percentage of overall rate</description> + </valueHelp> + <valueHelp> + <format><number>bit</format> + <description>bit(1), kbit(10^3), mbit(10^6), gbit, tbit</description> + </valueHelp> + <valueHelp> + <format><number>ibit</format> + <description>kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4)</description> + </valueHelp> + <valueHelp> + <format><number>ibps</format> + <description>kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec</description> + </valueHelp> + <valueHelp> + <format><number>bps</format> + <description>bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec</description> + </valueHelp> + </properties> + <defaultValue>100%</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/interval.xml.i b/interface-definitions/include/qos/interval.xml.i new file mode 100644 index 0000000..41896ac --- /dev/null +++ b/interface-definitions/include/qos/interval.xml.i @@ -0,0 +1,16 @@ +<!-- include start from qos/interval.xml.i --> +<leafNode name="interval"> + <properties> + <help>Interval used to measure the delay</help> + <valueHelp> + <format>u32</format> + <description>Interval in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + <constraintErrorMessage>Interval must be in range 0 to 4294967295</constraintErrorMessage> + </properties> + <defaultValue>100</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/match-dscp.xml.i b/interface-definitions/include/qos/match-dscp.xml.i new file mode 100644 index 0000000..2d2fd0a --- /dev/null +++ b/interface-definitions/include/qos/match-dscp.xml.i @@ -0,0 +1,142 @@ +<!-- include start from qos/match-dscp.xml.i --> +<leafNode name="dscp"> + <properties> + <help>Match on Differentiated Services Codepoint (DSCP)</help> + <completionHelp> + <list>default reliability throughput lowdelay priority immediate flash flash-override critical internet network AF11 AF12 AF13 AF21 AF22 AF23 AF31 AF32 AF33 AF41 AF42 AF43 CS1 CS2 CS3 CS4 CS5 CS6 CS7 EF</list> + </completionHelp> + <valueHelp> + <format>u32:0-63</format> + <description>Differentiated Services Codepoint (DSCP) value </description> + </valueHelp> + <valueHelp> + <format>default</format> + <description>match DSCP (000000)</description> + </valueHelp> + <valueHelp> + <format>reliability</format> + <description>match DSCP (000001)</description> + </valueHelp> + <valueHelp> + <format>throughput</format> + <description>match DSCP (000010)</description> + </valueHelp> + <valueHelp> + <format>lowdelay</format> + <description>match DSCP (000100)</description> + </valueHelp> + <valueHelp> + <format>priority</format> + <description>match DSCP (001000)</description> + </valueHelp> + <valueHelp> + <format>immediate</format> + <description>match DSCP (010000)</description> + </valueHelp> + <valueHelp> + <format>flash</format> + <description>match DSCP (011000)</description> + </valueHelp> + <valueHelp> + <format>flash-override</format> + <description>match DSCP (100000)</description> + </valueHelp> + <valueHelp> + <format>critical</format> + <description>match DSCP (101000)</description> + </valueHelp> + <valueHelp> + <format>internet</format> + <description>match DSCP (110000)</description> + </valueHelp> + <valueHelp> + <format>network</format> + <description>match DSCP (111000)</description> + </valueHelp> + <valueHelp> + <format>AF11</format> + <description>High-throughput data</description> + </valueHelp> + <valueHelp> + <format>AF12</format> + <description>High-throughput data</description> + </valueHelp> + <valueHelp> + <format>AF13</format> + <description>High-throughput data</description> + </valueHelp> + <valueHelp> + <format>AF21</format> + <description>Low-latency data</description> + </valueHelp> + <valueHelp> + <format>AF22</format> + <description>Low-latency data</description> + </valueHelp> + <valueHelp> + <format>AF23</format> + <description>Low-latency data</description> + </valueHelp> + <valueHelp> + <format>AF31</format> + <description>Multimedia streaming</description> + </valueHelp> + <valueHelp> + <format>AF32</format> + <description>Multimedia streaming</description> + </valueHelp> + <valueHelp> + <format>AF33</format> + <description>Multimedia streaming</description> + </valueHelp> + <valueHelp> + <format>AF41</format> + <description>Multimedia conferencing</description> + </valueHelp> + <valueHelp> + <format>AF42</format> + <description>Multimedia conferencing</description> + </valueHelp> + <valueHelp> + <format>AF43</format> + <description>Multimedia conferencing</description> + </valueHelp> + <valueHelp> + <format>CS1</format> + <description>Low-priority data</description> + </valueHelp> + <valueHelp> + <format>CS2</format> + <description>OAM</description> + </valueHelp> + <valueHelp> + <format>CS3</format> + <description>Broadcast video</description> + </valueHelp> + <valueHelp> + <format>CS4</format> + <description>Real-time interactive</description> + </valueHelp> + <valueHelp> + <format>CS5</format> + <description>Signaling</description> + </valueHelp> + <valueHelp> + <format>CS6</format> + <description>Network control</description> + </valueHelp> + <valueHelp> + <format>CS7</format> + <description></description> + </valueHelp> + <valueHelp> + <format>EF</format> + <description>Expedited Forwarding</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-63"/> + <regex>(default|reliability|throughput|lowdelay|priority|immediate|flash|flash-override|critical|internet|network|AF11|AF12|AF13|AF21|AF22|AF23|AF31|AF32|AF33|AF41|AF42|AF43|CS1|CS2|CS3|CS4|CS5|CS6|CS7|EF)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/max-length.xml.i b/interface-definitions/include/qos/max-length.xml.i new file mode 100644 index 0000000..64cdd02 --- /dev/null +++ b/interface-definitions/include/qos/max-length.xml.i @@ -0,0 +1,15 @@ +<!-- include start from qos/max-length.xml.i --> +<leafNode name="max-length"> + <properties> + <help>Maximum packet length</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Maximum packet/payload length</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + <constraintErrorMessage>Maximum packet length is 65535</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/mtu.xml.i b/interface-definitions/include/qos/mtu.xml.i new file mode 100644 index 0000000..161d4c2 --- /dev/null +++ b/interface-definitions/include/qos/mtu.xml.i @@ -0,0 +1,14 @@ +<!-- include start from qos/mtu.xml.i --> +<leafNode name="mtu"> + <properties> + <help>MTU size for this class</help> + <valueHelp> + <format>u32:256-65535</format> + <description>Bytes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 256-65535"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/queue-average-packet.xml.i b/interface-definitions/include/qos/queue-average-packet.xml.i new file mode 100644 index 0000000..2f8bfe2 --- /dev/null +++ b/interface-definitions/include/qos/queue-average-packet.xml.i @@ -0,0 +1,16 @@ +<!-- include start from qos/queue-average-packet.xml.i --> +<leafNode name="average-packet"> + <properties> + <help>Average packet size (bytes)</help> + <valueHelp> + <format>u32:16-10240</format> + <description>Average packet size in bytes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 16-10240"/> + </constraint> + <constraintErrorMessage>Average packet size must be between 16 and 10240</constraintErrorMessage> + </properties> + <defaultValue>1024</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/queue-limit-1-4294967295.xml.i b/interface-definitions/include/qos/queue-limit-1-4294967295.xml.i new file mode 100644 index 0000000..2f2d446 --- /dev/null +++ b/interface-definitions/include/qos/queue-limit-1-4294967295.xml.i @@ -0,0 +1,15 @@ +<!-- include start from qos/queue-limit-1-4294967295.xml.i --> +<leafNode name="queue-limit"> + <properties> + <help>Maximum queue size</help> + <valueHelp> + <format>u32:1-4294967295</format> + <description>Queue size in packets</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + <constraintErrorMessage>Queue limit must be greater than zero</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/queue-limit-2-10999.xml.i b/interface-definitions/include/qos/queue-limit-2-10999.xml.i new file mode 100644 index 0000000..7a9c826 --- /dev/null +++ b/interface-definitions/include/qos/queue-limit-2-10999.xml.i @@ -0,0 +1,16 @@ +<!-- include start from qos/queue-limit.xml.i --> +<leafNode name="queue-limit"> + <properties> + <help>Upper limit of the queue</help> + <valueHelp> + <format>u32:2-10999</format> + <description>Queue size in packets</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 2-10999"/> + </constraint> + <constraintErrorMessage>Queue limit must greater than 1 and less than 11000</constraintErrorMessage> + </properties> + <defaultValue>10240</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/queue-mark-probability.xml.i b/interface-definitions/include/qos/queue-mark-probability.xml.i new file mode 100644 index 0000000..1a28628 --- /dev/null +++ b/interface-definitions/include/qos/queue-mark-probability.xml.i @@ -0,0 +1,16 @@ +<!-- include start from qos/queue-mark-probability.xml.i --> +<leafNode name="mark-probability"> + <properties> + <help>Mark probability for random detection</help> + <valueHelp> + <format>u32</format> + <description>Numeric value (1/N)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--positive"/> + </constraint> + <constraintErrorMessage>Mark probability must be greater than 0</constraintErrorMessage> + </properties> + <defaultValue>10</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/queue-maximum-threshold.xml.i b/interface-definitions/include/qos/queue-maximum-threshold.xml.i new file mode 100644 index 0000000..66d17cc --- /dev/null +++ b/interface-definitions/include/qos/queue-maximum-threshold.xml.i @@ -0,0 +1,16 @@ +<!-- include start from qos/queue-maximum-threshold.xml.i --> +<leafNode name="maximum-threshold"> + <properties> + <help>Maximum threshold for random detection</help> + <valueHelp> + <format>u32:0-4096</format> + <description>Maximum threshold in packets</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4096"/> + </constraint> + <constraintErrorMessage>Threshold must be between 0 and 4096</constraintErrorMessage> + </properties> + <defaultValue>18</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/queue-minimum-threshold.xml.i b/interface-definitions/include/qos/queue-minimum-threshold.xml.i new file mode 100644 index 0000000..81e12d6 --- /dev/null +++ b/interface-definitions/include/qos/queue-minimum-threshold.xml.i @@ -0,0 +1,15 @@ +<!-- include start from qos/queue-minimum-threshold.xml.i --> +<leafNode name="minimum-threshold"> + <properties> + <help>Minimum threshold for random detection</help> + <valueHelp> + <format>u32:0-4096</format> + <description>Minimum threshold in packets</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4096"/> + </constraint> + <constraintErrorMessage>Threshold must be between 0 and 4096</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/queue-type.xml.i b/interface-definitions/include/qos/queue-type.xml.i new file mode 100644 index 0000000..c7d4cde --- /dev/null +++ b/interface-definitions/include/qos/queue-type.xml.i @@ -0,0 +1,33 @@ +<!-- include start from qos/queue-type.xml.i --> +<leafNode name="queue-type"> + <properties> + <help>Queue type for default traffic</help> + <completionHelp> + <list>drop-tail fair-queue fq-codel priority random-detect</list> + </completionHelp> + <valueHelp> + <format>drop-tail</format> + <description>First-In-First-Out (FIFO)</description> + </valueHelp> + <valueHelp> + <format>fair-queue</format> + <description>Stochastic Fair Queue (SFQ)</description> + </valueHelp> + <valueHelp> + <format>fq-codel</format> + <description>Fair Queue Codel</description> + </valueHelp> + <valueHelp> + <format>priority</format> + <description>Priority queuing</description> + </valueHelp> + <valueHelp> + <format>random-detect</format> + <description>Random Early Detection (RED)</description> + </valueHelp> + <constraint> + <regex>(drop-tail|fair-queue|fq-codel|priority|random-detect)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/set-dscp.xml.i b/interface-definitions/include/qos/set-dscp.xml.i new file mode 100644 index 0000000..07f3378 --- /dev/null +++ b/interface-definitions/include/qos/set-dscp.xml.i @@ -0,0 +1,143 @@ +<!-- include start from qos/set-dscp.xml.i --> +<leafNode name="set-dscp"> + <properties> + <help>Change the Differentiated Services (DiffServ) field in the IP header</help> + <completionHelp> + <list>default reliability throughput lowdelay priority immediate flash flash-override critical internet network AF11 AF12 AF13 AF21 AF22 AF23 AF31 AF32 AF33 AF41 AF42 AF43 CS1 CS2 CS3 CS4 CS5 CS6 CS7 EF</list> + </completionHelp> + <valueHelp> + <format>u32:0-63</format> + <description>Priority order for bandwidth pool</description> + </valueHelp> + <valueHelp> + <format>default</format> + <description>match DSCP (000000)</description> + </valueHelp> + <valueHelp> + <format>reliability</format> + <description>match DSCP (000001)</description> + </valueHelp> + <valueHelp> + <format>throughput</format> + <description>match DSCP (000010)</description> + </valueHelp> + <valueHelp> + <format>lowdelay</format> + <description>match DSCP (000100)</description> + </valueHelp> + <valueHelp> + <format>priority</format> + <description>match DSCP (001000)</description> + </valueHelp> + <valueHelp> + <format>immediate</format> + <description>match DSCP (010000)</description> + </valueHelp> + <valueHelp> + <format>flash</format> + <description>match DSCP (011000)</description> + </valueHelp> + <valueHelp> + <format>flash-override</format> + <description>match DSCP (100000)</description> + </valueHelp> + <valueHelp> + <format>critical</format> + <description>match DSCP (101000)</description> + </valueHelp> + <valueHelp> + <format>internet</format> + <description>match DSCP (110000)</description> + </valueHelp> + <valueHelp> + <format>network</format> + <description>match DSCP (111000)</description> + </valueHelp> + <valueHelp> + <format>AF11</format> + <description>High-throughput data</description> + </valueHelp> + <valueHelp> + <format>AF12</format> + <description>High-throughput data</description> + </valueHelp> + <valueHelp> + <format>AF13</format> + <description>High-throughput data</description> + </valueHelp> + <valueHelp> + <format>AF21</format> + <description>Low-latency data</description> + </valueHelp> + <valueHelp> + <format>AF22</format> + <description>Low-latency data</description> + </valueHelp> + <valueHelp> + <format>AF23</format> + <description>Low-latency data</description> + </valueHelp> + <valueHelp> + <format>AF31</format> + <description>Multimedia streaming</description> + </valueHelp> + <valueHelp> + <format>AF32</format> + <description>Multimedia streaming</description> + </valueHelp> + <valueHelp> + <format>AF33</format> + <description>Multimedia streaming</description> + </valueHelp> + <valueHelp> + <format>AF41</format> + <description>Multimedia conferencing</description> + </valueHelp> + <valueHelp> + <format>AF42</format> + <description>Multimedia conferencing</description> + </valueHelp> + <valueHelp> + <format>AF43</format> + <description>Multimedia conferencing</description> + </valueHelp> + <valueHelp> + <format>CS1</format> + <description>Low-priority data</description> + </valueHelp> + <valueHelp> + <format>CS2</format> + <description>OAM</description> + </valueHelp> + <valueHelp> + <format>CS3</format> + <description>Broadcast video</description> + </valueHelp> + <valueHelp> + <format>CS4</format> + <description>Real-time interactive</description> + </valueHelp> + <valueHelp> + <format>CS5</format> + <description>Signaling</description> + </valueHelp> + <valueHelp> + <format>CS6</format> + <description>Network control</description> + </valueHelp> + <valueHelp> + <format>CS7</format> + <description></description> + </valueHelp> + <valueHelp> + <format>EF</format> + <description>Expedited Forwarding</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-63"/> + <regex>(default|reliability|throughput|lowdelay|priority|immediate|flash|flash-override|critical|internet|network|AF11|AF12|AF13|AF21|AF22|AF23|AF31|AF32|AF33|AF41|AF42|AF43|CS1|CS2|CS3|CS4|CS5|CS6|CS7|EF)</regex> + </constraint> + <constraintErrorMessage>Priority must be between 0 and 63</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/target.xml.i b/interface-definitions/include/qos/target.xml.i new file mode 100644 index 0000000..bf6342a --- /dev/null +++ b/interface-definitions/include/qos/target.xml.i @@ -0,0 +1,16 @@ +<!-- include start from qos/target.xml.i --> +<leafNode name="target"> + <properties> + <help>Acceptable minimum standing/persistent queue delay</help> + <valueHelp> + <format>u32</format> + <description>Queue delay in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + <constraintErrorMessage>Delay must be in range 0 to 4294967295</constraintErrorMessage> + </properties> + <defaultValue>5</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/tcp-flags.xml.i b/interface-definitions/include/qos/tcp-flags.xml.i new file mode 100644 index 0000000..81d70d1 --- /dev/null +++ b/interface-definitions/include/qos/tcp-flags.xml.i @@ -0,0 +1,21 @@ +<!-- include start from qos/tcp-flags.xml.i --> +<node name="tcp"> + <properties> + <help>TCP Flags matching</help> + </properties> + <children> + <leafNode name="ack"> + <properties> + <help>Match TCP ACK</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="syn"> + <properties> + <help>Match TCP SYN</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/radius-acct-server-ipv4.xml.i b/interface-definitions/include/radius-acct-server-ipv4.xml.i new file mode 100644 index 0000000..9365aa8 --- /dev/null +++ b/interface-definitions/include/radius-acct-server-ipv4.xml.i @@ -0,0 +1,26 @@ +<!-- include start from radius-acct-server-ipv4.xml.i --> +<node name="radius"> + <properties> + <help>RADIUS accounting for users OpenConnect VPN sessions OpenConnect authentication mode radius</help> + </properties> + <children> + <tagNode name="server"> + <properties> + <help>RADIUS server configuration</help> + <valueHelp> + <format>ipv4</format> + <description>RADIUS server IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/radius-server-key.xml.i> + #include <include/radius-server-acct-port.xml.i> + </children> + </tagNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/radius-auth-server-ipv4.xml.i b/interface-definitions/include/radius-auth-server-ipv4.xml.i new file mode 100644 index 0000000..dc6f4d8 --- /dev/null +++ b/interface-definitions/include/radius-auth-server-ipv4.xml.i @@ -0,0 +1,27 @@ +<!-- include start from radius-auth-server-ipv4.xml.i --> +<node name="radius"> + <properties> + <help>RADIUS based user authentication</help> + </properties> + <children> + #include <include/source-address-ipv4.xml.i> + <tagNode name="server"> + <properties> + <help>RADIUS server configuration</help> + <valueHelp> + <format>ipv4</format> + <description>RADIUS server IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/radius-server-key.xml.i> + #include <include/radius-server-auth-port.xml.i> + </children> + </tagNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/radius-nas-identifier.xml.i b/interface-definitions/include/radius-nas-identifier.xml.i new file mode 100644 index 0000000..8e6933c --- /dev/null +++ b/interface-definitions/include/radius-nas-identifier.xml.i @@ -0,0 +1,7 @@ +<!-- include start from radius-nas-identifier.xml.i --> +<leafNode name="nas-identifier"> + <properties> + <help>NAS-Identifier attribute sent to RADIUS</help> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/radius-nas-ip-address.xml.i b/interface-definitions/include/radius-nas-ip-address.xml.i new file mode 100644 index 0000000..8d0a3fd --- /dev/null +++ b/interface-definitions/include/radius-nas-ip-address.xml.i @@ -0,0 +1,14 @@ +<!-- include start from radius-nas-ip-address.xml.i --> +<leafNode name="nas-ip-address"> + <properties> + <help>NAS-IP-Address attribute sent to RADIUS</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <valueHelp> + <format>ipv4</format> + <description>NAS-IP-Address attribute</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/radius-priority.xml.i b/interface-definitions/include/radius-priority.xml.i new file mode 100644 index 0000000..f77f501 --- /dev/null +++ b/interface-definitions/include/radius-priority.xml.i @@ -0,0 +1,14 @@ +<!-- include start from radius-priority.xml.i --> +<leafNode name="priority"> + <properties> + <help>Server priority</help> + <valueHelp> + <format>u32:1-255</format> + <description>Server priority</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/radius-server-acct-port.xml.i b/interface-definitions/include/radius-server-acct-port.xml.i new file mode 100644 index 0000000..0b356fa --- /dev/null +++ b/interface-definitions/include/radius-server-acct-port.xml.i @@ -0,0 +1,15 @@ +<!-- include start from radius-server-acct-port.xml.i --> +<leafNode name="port"> + <properties> + <help>Accounting port</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>1813</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/radius-server-auth-port.xml.i b/interface-definitions/include/radius-server-auth-port.xml.i new file mode 100644 index 0000000..d9ea1d4 --- /dev/null +++ b/interface-definitions/include/radius-server-auth-port.xml.i @@ -0,0 +1,6 @@ +<!-- include start from radius-server-auth-port.xml.i --> +#include <include/port-number.xml.i> +<leafNode name="port"> + <defaultValue>1812</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/radius-server-ipv4-ipv6.xml.i b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i new file mode 100644 index 0000000..e454b90 --- /dev/null +++ b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i @@ -0,0 +1,51 @@ +<!-- include start from radius-server-ipv4-ipv6.xml.i --> +<node name="radius"> + <properties> + <help>RADIUS based user authentication</help> + </properties> + <children> + <tagNode name="server"> + <properties> + <help>RADIUS server configuration</help> + <valueHelp> + <format>ipv4</format> + <description>RADIUS server IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>RADIUS server IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/radius-server-key.xml.i> + #include <include/radius-server-auth-port.xml.i> + </children> + </tagNode> + #include <include/source-address-ipv4-ipv6-multi.xml.i> + <leafNode name="security-mode"> + <properties> + <help>Security mode for RADIUS authentication</help> + <completionHelp> + <list>mandatory optional</list> + </completionHelp> + <valueHelp> + <format>mandatory</format> + <description>Deny access immediately if RADIUS answers with Access-Reject</description> + </valueHelp> + <valueHelp> + <format>optional</format> + <description>Pass to the next authentication method if RADIUS answers with Access-Reject</description> + </valueHelp> + <constraint> + <regex>(mandatory|optional)</regex> + </constraint> + </properties> + <defaultValue>optional</defaultValue> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/radius-server-key.xml.i b/interface-definitions/include/radius-server-key.xml.i new file mode 100644 index 0000000..dd5cdb0 --- /dev/null +++ b/interface-definitions/include/radius-server-key.xml.i @@ -0,0 +1,15 @@ +<!-- include start from radius-server-key.xml.i --> +<leafNode name="key"> + <properties> + <help>Shared secret key</help> + <valueHelp> + <format>txt</format> + <description>Password string (key)</description> + </valueHelp> + <constraint> + <regex>[[:ascii:]]{1,128}</regex> + </constraint> + <constraintErrorMessage>Password must be less then 128 characters</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/radius-timeout.xml.i b/interface-definitions/include/radius-timeout.xml.i new file mode 100644 index 0000000..22bb6d3 --- /dev/null +++ b/interface-definitions/include/radius-timeout.xml.i @@ -0,0 +1,16 @@ +<!-- include start from radius-timeout.xml.i --> +<leafNode name="timeout"> + <properties> + <help>Session timeout</help> + <valueHelp> + <format>u32:1-240</format> + <description>Session timeout in seconds (default: 2)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-240"/> + </constraint> + <constraintErrorMessage>Timeout must be between 1 and 240 seconds</constraintErrorMessage> + </properties> + <defaultValue>2</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/rip/access-list.xml.i b/interface-definitions/include/rip/access-list.xml.i new file mode 100644 index 0000000..8799aa9 --- /dev/null +++ b/interface-definitions/include/rip/access-list.xml.i @@ -0,0 +1,39 @@ +<!-- include start from rip/access-list.xml.i --> +<node name="access-list"> + <properties> + <help>Access-list</help> + </properties> + <children> + <leafNode name="in"> + <properties> + <help>Access list to apply to input packets</help> + <valueHelp> + <format>u32</format> + <description>Access list to apply to input packets</description> + </valueHelp> + <completionHelp> + <path>policy access-list</path> + </completionHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="out"> + <properties> + <help>Access list to apply to output packets</help> + <valueHelp> + <format>u32</format> + <description>Access list to apply to output packets</description> + </valueHelp> + <completionHelp> + <path>policy access-list</path> + </completionHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/rip/access-list6.xml.i b/interface-definitions/include/rip/access-list6.xml.i new file mode 100644 index 0000000..7321352 --- /dev/null +++ b/interface-definitions/include/rip/access-list6.xml.i @@ -0,0 +1,39 @@ +<!-- include start from rip/access-list.xml.i --> +<node name="access-list"> + <properties> + <help>Access-list</help> + </properties> + <children> + <leafNode name="in"> + <properties> + <help>Access list to apply to input packets</help> + <valueHelp> + <format>u32</format> + <description>Access list to apply to input packets</description> + </valueHelp> + <completionHelp> + <path>policy access-list6</path> + </completionHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="out"> + <properties> + <help>Access list to apply to output packets</help> + <valueHelp> + <format>u32</format> + <description>Access list to apply to output packets</description> + </valueHelp> + <completionHelp> + <path>policy access-list6</path> + </completionHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/rip/default-information.xml.i b/interface-definitions/include/rip/default-information.xml.i new file mode 100644 index 0000000..957fb3a --- /dev/null +++ b/interface-definitions/include/rip/default-information.xml.i @@ -0,0 +1,15 @@ +<!-- include start from rip/default-information.xml.i --> +<node name="default-information"> + <properties> + <help>Control distribution of default route</help> + </properties> + <children> + <leafNode name="originate"> + <properties> + <help>Distribute a default route</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/rip/default-metric.xml.i b/interface-definitions/include/rip/default-metric.xml.i new file mode 100644 index 0000000..c0f1f9b --- /dev/null +++ b/interface-definitions/include/rip/default-metric.xml.i @@ -0,0 +1,14 @@ +<!-- include start from rip/default-metric.xml.i --> +<leafNode name="default-metric"> + <properties> + <help>Metric of redistributed routes</help> + <valueHelp> + <format>u32:1-16</format> + <description>Default metric</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-16"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/rip/interface.xml.i b/interface-definitions/include/rip/interface.xml.i new file mode 100644 index 0000000..7c64d07 --- /dev/null +++ b/interface-definitions/include/rip/interface.xml.i @@ -0,0 +1,33 @@ +<!-- include start from rip/interface.xml.i --> +<tagNode name="interface"> + <properties> + <help>Interface name</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + <node name="split-horizon"> + <properties> + <help>Split horizon parameters</help> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <leafNode name="poison-reverse"> + <properties> + <help>Disable split horizon on specified interface</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/rip/prefix-list.xml.i b/interface-definitions/include/rip/prefix-list.xml.i new file mode 100644 index 0000000..8e806aa --- /dev/null +++ b/interface-definitions/include/rip/prefix-list.xml.i @@ -0,0 +1,33 @@ +<!-- include start from rip/prefix-list.xml.i --> +<node name="prefix-list"> + <properties> + <help>Prefix-list</help> + </properties> + <children> + <leafNode name="in"> + <properties> + <help>Prefix-list to apply to input packets</help> + <valueHelp> + <format>txt</format> + <description>Prefix-list to apply to input packets</description> + </valueHelp> + <completionHelp> + <path>policy prefix-list</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="out"> + <properties> + <help>Prefix-list to apply to output packets</help> + <valueHelp> + <format>txt</format> + <description>Prefix-list to apply to output packets</description> + </valueHelp> + <completionHelp> + <path>policy prefix-list</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/rip/prefix-list6.xml.i b/interface-definitions/include/rip/prefix-list6.xml.i new file mode 100644 index 0000000..84b6846 --- /dev/null +++ b/interface-definitions/include/rip/prefix-list6.xml.i @@ -0,0 +1,33 @@ +<!-- include start from rip/prefix-list.xml.i --> +<node name="prefix-list"> + <properties> + <help>Prefix-list</help> + </properties> + <children> + <leafNode name="in"> + <properties> + <help>Prefix-list to apply to input packets</help> + <valueHelp> + <format>txt</format> + <description>Prefix-list to apply to input packets</description> + </valueHelp> + <completionHelp> + <path>policy prefix-list6</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="out"> + <properties> + <help>Prefix-list to apply to output packets</help> + <valueHelp> + <format>txt</format> + <description>Prefix-list to apply to output packets</description> + </valueHelp> + <completionHelp> + <path>policy prefix-list6</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/rip/redistribute.xml.i b/interface-definitions/include/rip/redistribute.xml.i new file mode 100644 index 0000000..34154a5 --- /dev/null +++ b/interface-definitions/include/rip/redistribute.xml.i @@ -0,0 +1,15 @@ +<!-- include start from rip/redistribute.xml.i --> +<leafNode name="metric"> + <properties> + <help>Metric for redistributed routes</help> + <valueHelp> + <format>u32:1-16</format> + <description>Redistribute route metric</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-16"/> + </constraint> + </properties> +</leafNode> +#include <include/route-map.xml.i> +<!-- include end --> diff --git a/interface-definitions/include/rip/timers.xml.i b/interface-definitions/include/rip/timers.xml.i new file mode 100644 index 0000000..771a670 --- /dev/null +++ b/interface-definitions/include/rip/timers.xml.i @@ -0,0 +1,48 @@ +<!-- include start from rip/timers.xml.i --> +<node name="timers"> + <properties> + <help>RIPng timer values</help> + </properties> + <children> + <leafNode name="garbage-collection"> + <properties> + <help>Garbage collection timer</help> + <valueHelp> + <format>u32:5-2147483647</format> + <description>Garbage colletion time</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 5-2147483647"/> + </constraint> + </properties> + <defaultValue>120</defaultValue> + </leafNode> + <leafNode name="timeout"> + <properties> + <help>Routing information timeout timer</help> + <valueHelp> + <format>u32:5-2147483647</format> + <description>Routing information timeout timer</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 5-2147483647"/> + </constraint> + </properties> + <defaultValue>180</defaultValue> + </leafNode> + <leafNode name="update"> + <properties> + <help>Routing table update timer</help> + <valueHelp> + <format>u32:5-2147483647</format> + <description>Routing table update timer in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 5-2147483647"/> + </constraint> + </properties> + <defaultValue>30</defaultValue> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/rip/version.xml.i b/interface-definitions/include/rip/version.xml.i new file mode 100644 index 0000000..61458b2 --- /dev/null +++ b/interface-definitions/include/rip/version.xml.i @@ -0,0 +1,18 @@ +<!-- include start from rip/version.xml.i --> +<leafNode name="version"> + <properties> + <help>Limit RIP protocol version</help> + <valueHelp> + <format>1</format> + <description>Allow RIPv1 only</description> + </valueHelp> + <valueHelp> + <format>2</format> + <description>Allow RIPv2 only</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/route-map.xml.i b/interface-definitions/include/route-map.xml.i new file mode 100644 index 0000000..e49c388 --- /dev/null +++ b/interface-definitions/include/route-map.xml.i @@ -0,0 +1,18 @@ +<!-- include start from route-map.xml.i --> +<leafNode name="route-map"> + <properties> + <help>Specify route-map name to use</help> + <completionHelp> + <path>policy route-map</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Route map name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of route-map can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/router-id.xml.i b/interface-definitions/include/router-id.xml.i new file mode 100644 index 0000000..272a8b6 --- /dev/null +++ b/interface-definitions/include/router-id.xml.i @@ -0,0 +1,14 @@ +<!-- include start from router-id.xml.i --> +<leafNode name="router-id"> + <properties> + <help>Override default router identifier</help> + <valueHelp> + <format>ipv4</format> + <description>Router-ID in IP address format</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/routing-passive-interface.xml.i b/interface-definitions/include/routing-passive-interface.xml.i new file mode 100644 index 0000000..8fa0d0f --- /dev/null +++ b/interface-definitions/include/routing-passive-interface.xml.i @@ -0,0 +1,24 @@ +<!-- include start from routing-passive-interface.xml.i --> +<leafNode name="passive-interface"> + <properties> + <help>Suppress routing updates on an interface</help> + <completionHelp> + <list>default</list> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface to be passive (i.e. suppress routing updates)</description> + </valueHelp> + <valueHelp> + <format>default</format> + <description>Default to suppress routing updates on all interfaces</description> + </valueHelp> + <constraint> + <regex>(default)</regex> + #include <include/constraint/interface-name.xml.i> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/segment-routing-label-value.xml.i b/interface-definitions/include/segment-routing-label-value.xml.i new file mode 100644 index 0000000..05e1edd --- /dev/null +++ b/interface-definitions/include/segment-routing-label-value.xml.i @@ -0,0 +1,26 @@ +<!-- include start from segment-routing-label-value.xml.i --> +<leafNode name="low-label-value"> + <properties> + <help>MPLS label lower bound</help> + <valueHelp> + <format>u32:16-1048575</format> + <description>Label value (recommended minimum value: 300)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 16-1048575"/> + </constraint> + </properties> +</leafNode> +<leafNode name="high-label-value"> + <properties> + <help>MPLS label upper bound</help> + <valueHelp> + <format>u32:16-1048575</format> + <description>Label value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 16-1048575"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/server-ipv4-fqdn.xml.i b/interface-definitions/include/server-ipv4-fqdn.xml.i new file mode 100644 index 0000000..7bab981 --- /dev/null +++ b/interface-definitions/include/server-ipv4-fqdn.xml.i @@ -0,0 +1,15 @@ +<!-- include start from server-ipv4-fqdn.xml.i --> +<leafNode name="server"> + <properties> + <help>Remote server to connect to</help> + <valueHelp> + <format>ipv4</format> + <description>Server IPv4 address</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>Server hostname/FQDN</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/snmp/access-mode.xml.i b/interface-definitions/include/snmp/access-mode.xml.i new file mode 100644 index 0000000..7469805 --- /dev/null +++ b/interface-definitions/include/snmp/access-mode.xml.i @@ -0,0 +1,23 @@ +<!-- include start from snmp/access-mode.xml.i --> +<leafNode name="mode"> + <properties> + <help>Define access permission</help> + <completionHelp> + <list>ro rw</list> + </completionHelp> + <valueHelp> + <format>ro</format> + <description>Read-Only</description> + </valueHelp> + <valueHelp> + <format>rw</format> + <description>read write</description> + </valueHelp> + <constraint> + <regex>(ro|rw)</regex> + </constraint> + <constraintErrorMessage>Authorization type must be either 'rw' or 'ro'</constraintErrorMessage> + </properties> + <defaultValue>ro</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/snmp/authentication-type.xml.i b/interface-definitions/include/snmp/authentication-type.xml.i new file mode 100644 index 0000000..047d8cf --- /dev/null +++ b/interface-definitions/include/snmp/authentication-type.xml.i @@ -0,0 +1,22 @@ +<!-- include start from snmp/authentication-type.xml.i --> +<leafNode name="type"> + <properties> + <help>Define used protocol</help> + <completionHelp> + <list>md5 sha</list> + </completionHelp> + <valueHelp> + <format>md5</format> + <description>Message Digest 5</description> + </valueHelp> + <valueHelp> + <format>sha</format> + <description>Secure Hash Algorithm</description> + </valueHelp> + <constraint> + <regex>(md5|sha)</regex> + </constraint> + </properties> + <defaultValue>md5</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/snmp/privacy-type.xml.i b/interface-definitions/include/snmp/privacy-type.xml.i new file mode 100644 index 0000000..d5fd1e8 --- /dev/null +++ b/interface-definitions/include/snmp/privacy-type.xml.i @@ -0,0 +1,22 @@ +<!-- include start from snmp/privacy-type.xml.i --> +<leafNode name="type"> + <properties> + <help>Defines the protocol for privacy</help> + <completionHelp> + <list>des aes</list> + </completionHelp> + <valueHelp> + <format>des</format> + <description>Data Encryption Standard</description> + </valueHelp> + <valueHelp> + <format>aes</format> + <description>Advanced Encryption Standard</description> + </valueHelp> + <constraint> + <regex>(des|aes)</regex> + </constraint> + </properties> + <defaultValue>des</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/source-address-ipv4-ipv6-multi.xml.i b/interface-definitions/include/source-address-ipv4-ipv6-multi.xml.i new file mode 100644 index 0000000..d56ca5b --- /dev/null +++ b/interface-definitions/include/source-address-ipv4-ipv6-multi.xml.i @@ -0,0 +1,22 @@ +<!-- include start from source-address-ipv4-ipv6-multi.xml.i --> +<leafNode name="source-address"> + <properties> + <help>Source IP address used to initiate connection</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 source address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 source address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/source-address-ipv4-ipv6.xml.i b/interface-definitions/include/source-address-ipv4-ipv6.xml.i new file mode 100644 index 0000000..af3f9bb --- /dev/null +++ b/interface-definitions/include/source-address-ipv4-ipv6.xml.i @@ -0,0 +1,21 @@ +<!-- include start from source-address-ipv4-ipv6.xml.i --> +<leafNode name="source-address"> + <properties> + <help>Source IP address used to initiate connection</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 source address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 source address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/source-address-ipv4-multi.xml.i b/interface-definitions/include/source-address-ipv4-multi.xml.i new file mode 100644 index 0000000..319a118 --- /dev/null +++ b/interface-definitions/include/source-address-ipv4-multi.xml.i @@ -0,0 +1,18 @@ +<!-- include start from source-address-ipv4-multi.xml.i --> +<leafNode name="source-address"> + <properties> + <help>IPv4 source address used to initiate connection</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 source address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/source-address-ipv4.xml.i b/interface-definitions/include/source-address-ipv4.xml.i new file mode 100644 index 0000000..0526781 --- /dev/null +++ b/interface-definitions/include/source-address-ipv4.xml.i @@ -0,0 +1,17 @@ +<!-- include start from source-address-ipv4.xml.i --> +<leafNode name="source-address"> + <properties> + <help>IPv4 source address used to initiate connection</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 source address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/source-interface-ethernet.xml.i b/interface-definitions/include/source-interface-ethernet.xml.i new file mode 100644 index 0000000..e06e47d --- /dev/null +++ b/interface-definitions/include/source-interface-ethernet.xml.i @@ -0,0 +1,14 @@ +<!-- include start from source-interface-ethernet.xml.i --> +<leafNode name="source-interface"> + <properties> + <help>Physical interface the traffic will go through</help> + <valueHelp> + <format>interface</format> + <description>Physical interface used for traffic forwarding</description> + </valueHelp> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type ethernet</script> + </completionHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/source-interface.xml.i b/interface-definitions/include/source-interface.xml.i new file mode 100644 index 0000000..40fdc6c --- /dev/null +++ b/interface-definitions/include/source-interface.xml.i @@ -0,0 +1,17 @@ +<!-- include start from source-interface.xml.i --> +<leafNode name="source-interface"> + <properties> + <help>Interface used to establish connection</help> + <valueHelp> + <format>interface</format> + <description>Interface name</description> + </valueHelp> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ssh-group.xml.i b/interface-definitions/include/ssh-group.xml.i new file mode 100644 index 0000000..9c8b869 --- /dev/null +++ b/interface-definitions/include/ssh-group.xml.i @@ -0,0 +1,12 @@ +<!-- include start from ssh-group.xml.i --> +<leafNode name="group"> + <properties> + <help>Allow members of a group to login</help> + <constraint> + <regex>[a-z_][a-z0-9_-]{1,31}[$]?</regex> + </constraint> + <constraintErrorMessage>illegal characters or more than 32 characters</constraintErrorMessage> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ssh-user.xml.i b/interface-definitions/include/ssh-user.xml.i new file mode 100644 index 0000000..6ac1f35 --- /dev/null +++ b/interface-definitions/include/ssh-user.xml.i @@ -0,0 +1,12 @@ +<!-- include start from ssh-user.xml.i --> +<leafNode name="user"> + <properties> + <help>Allow specific users to login</help> + <constraint> + <regex>[-_a-zA-Z0-9.]{1,100}</regex> + </constraint> + <constraintErrorMessage>Illegal characters or more than 100 characters</constraintErrorMessage> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/static/static-route-bfd.xml.i b/interface-definitions/include/static/static-route-bfd.xml.i new file mode 100644 index 0000000..d588b36 --- /dev/null +++ b/interface-definitions/include/static/static-route-bfd.xml.i @@ -0,0 +1,36 @@ +<!-- include start from static/static-route-bfd.xml.i --> +<node name="bfd"> + <properties> + <help>BFD monitoring</help> + </properties> + <children> + #include <include/bfd/profile.xml.i> + <node name="multi-hop"> + <properties> + <help>Use BFD multi hop session</help> + </properties> + <children> + <tagNode name="source"> + <properties> + <help>Use source for BFD session</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 source address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 source address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + #include <include/bfd/profile.xml.i> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/static/static-route-blackhole.xml.i b/interface-definitions/include/static/static-route-blackhole.xml.i new file mode 100644 index 0000000..487f775 --- /dev/null +++ b/interface-definitions/include/static/static-route-blackhole.xml.i @@ -0,0 +1,11 @@ +<!-- include start from static/static-route-blackhole.xml.i --> +<node name="blackhole"> + <properties> + <help>Silently discard pkts when matched</help> + </properties> + <children> + #include <include/static/static-route-distance.xml.i> + #include <include/static/static-route-tag.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/static/static-route-distance.xml.i b/interface-definitions/include/static/static-route-distance.xml.i new file mode 100644 index 0000000..a651b98 --- /dev/null +++ b/interface-definitions/include/static/static-route-distance.xml.i @@ -0,0 +1,14 @@ +<!-- include start from static/static-route-distance.xml.i --> +<leafNode name="distance"> + <properties> + <help>Distance for this route</help> + <valueHelp> + <format>u32:1-255</format> + <description>Distance for this route</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/static/static-route-interface.xml.i b/interface-definitions/include/static/static-route-interface.xml.i new file mode 100644 index 0000000..cb54368 --- /dev/null +++ b/interface-definitions/include/static/static-route-interface.xml.i @@ -0,0 +1,17 @@ +<!-- include start from static/static-route-interface.xml.i --> +<leafNode name="interface"> + <properties> + <help>Gateway interface name</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Gateway interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/static/static-route-reject.xml.i b/interface-definitions/include/static/static-route-reject.xml.i new file mode 100644 index 0000000..ef713ac --- /dev/null +++ b/interface-definitions/include/static/static-route-reject.xml.i @@ -0,0 +1,11 @@ +<!-- include start from static/static-route-blackhole.xml.i --> +<node name="reject"> + <properties> + <help>Emit an ICMP unreachable when matched</help> + </properties> + <children> + #include <include/static/static-route-distance.xml.i> + #include <include/static/static-route-tag.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/static/static-route-segments.xml.i b/interface-definitions/include/static/static-route-segments.xml.i new file mode 100644 index 0000000..2068b1a --- /dev/null +++ b/interface-definitions/include/static/static-route-segments.xml.i @@ -0,0 +1,14 @@ +<!-- include start from static/static-route-segments.xml.i --> +<leafNode name="segments"> + <properties> + <help>SRv6 segments</help> + <valueHelp> + <format>txt</format> + <description>Segs (SIDs)</description> + </valueHelp> + <constraint> + <validator name="ipv6-srv6-segments"/> + </constraint> + </properties> + </leafNode> + <!-- include end --> diff --git a/interface-definitions/include/static/static-route-tag.xml.i b/interface-definitions/include/static/static-route-tag.xml.i new file mode 100644 index 0000000..24bfa73 --- /dev/null +++ b/interface-definitions/include/static/static-route-tag.xml.i @@ -0,0 +1,14 @@ +<!-- include start from static/static-route-tag.xml.i --> +<leafNode name="tag"> + <properties> + <help>Tag value for this route</help> + <valueHelp> + <format>u32:1-4294967295</format> + <description>Tag value for this route</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/static/static-route-vrf.xml.i b/interface-definitions/include/static/static-route-vrf.xml.i new file mode 100644 index 0000000..e1968f0 --- /dev/null +++ b/interface-definitions/include/static/static-route-vrf.xml.i @@ -0,0 +1,19 @@ +<!-- include start from static/static-route-vrf.xml.i --> +<leafNode name="vrf"> + <properties> + <help>VRF to leak route</help> + <completionHelp> + <list>default</list> + <path>vrf name</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of VRF to leak to</description> + </valueHelp> + <constraint> + <regex>(default)</regex> + <validator name="vrf-name"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/static/static-route.xml.i b/interface-definitions/include/static/static-route.xml.i new file mode 100644 index 0000000..29921a7 --- /dev/null +++ b/interface-definitions/include/static/static-route.xml.i @@ -0,0 +1,60 @@ +<!-- include start from static/static-route.xml.i --> +<tagNode name="route"> + <properties> + <help>Static IPv4 route</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 static route</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + #include <include/static/static-route-blackhole.xml.i> + #include <include/static/static-route-reject.xml.i> + #include <include/dhcp-interface.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="interface"> + <properties> + <help>Next-hop IPv4 router interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Gateway interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/static/static-route-distance.xml.i> + #include <include/static/static-route-vrf.xml.i> + </children> + </tagNode> + <tagNode name="next-hop"> + <properties> + <help>Next-hop IPv4 router address</help> + <valueHelp> + <format>ipv4</format> + <description>Next-hop router address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/static/static-route-distance.xml.i> + #include <include/static/static-route-interface.xml.i> + #include <include/static/static-route-vrf.xml.i> + #include <include/static/static-route-bfd.xml.i> + </children> + </tagNode> + </children> +</tagNode> +<!-- include end --> + diff --git a/interface-definitions/include/static/static-route6.xml.i b/interface-definitions/include/static/static-route6.xml.i new file mode 100644 index 0000000..4468c80 --- /dev/null +++ b/interface-definitions/include/static/static-route6.xml.i @@ -0,0 +1,60 @@ +<!-- include start from static/static-route6.xml.i --> +<tagNode name="route6"> + <properties> + <help>Static IPv6 route</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 static route</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + #include <include/static/static-route-blackhole.xml.i> + #include <include/static/static-route-reject.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="interface"> + <properties> + <help>IPv6 gateway interface name</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Gateway interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/static/static-route-distance.xml.i> + #include <include/static/static-route-segments.xml.i> + #include <include/static/static-route-vrf.xml.i> + </children> + </tagNode> + <tagNode name="next-hop"> + <properties> + <help>IPv6 gateway address</help> + <valueHelp> + <format>ipv6</format> + <description>Next-hop IPv6 router</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/static/static-route-bfd.xml.i> + #include <include/static/static-route-distance.xml.i> + #include <include/static/static-route-interface.xml.i> + #include <include/static/static-route-segments.xml.i> + #include <include/static/static-route-vrf.xml.i> + </children> + </tagNode> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/stunnel/address.xml.i b/interface-definitions/include/stunnel/address.xml.i new file mode 100644 index 0000000..d2901d5 --- /dev/null +++ b/interface-definitions/include/stunnel/address.xml.i @@ -0,0 +1,20 @@ +<!-- include start from stunnel/address.xml.i --> +<leafNode name="address"> + <properties> + <help>Hostname or IP address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>hostname</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + <validator name="fqdn"/> + </constraint> + <constraintErrorMessage>Invalid FQDN or IP address</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/stunnel/connect.xml.i b/interface-definitions/include/stunnel/connect.xml.i new file mode 100644 index 0000000..cd6246a --- /dev/null +++ b/interface-definitions/include/stunnel/connect.xml.i @@ -0,0 +1,11 @@ +<!-- include start from stunnel/connect.xml.i --> +<node name="connect"> + <properties> + <help>Connect to a remote address</help> + </properties> + <children> + #include <include/stunnel/address.xml.i> + #include <include/port-number.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/stunnel/listen.xml.i b/interface-definitions/include/stunnel/listen.xml.i new file mode 100644 index 0000000..13d0986 --- /dev/null +++ b/interface-definitions/include/stunnel/listen.xml.i @@ -0,0 +1,11 @@ +<!-- include start from stunnel/listen.xml.i --> +<node name="listen"> + <properties> + <help>Accept connections on specified address</help> + </properties> + <children> + #include <include/stunnel/address.xml.i> + #include <include/port-number.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/stunnel/protocol-options.xml.i b/interface-definitions/include/stunnel/protocol-options.xml.i new file mode 100644 index 0000000..2f02028 --- /dev/null +++ b/interface-definitions/include/stunnel/protocol-options.xml.i @@ -0,0 +1,75 @@ +<!-- include start from stunel/protocol-options.xml.i --> +<node name="options"> + <properties> + <help>Advanced protocol options</help> + </properties> + <children> + <leafNode name="authentication"> + <properties> + <help>Authentication type for the protocol negotiations</help> + <completionHelp> + <list>basic ntlm plain login</list> + </completionHelp> + <valueHelp> + <format>basic</format> + <description>The default 'connect' authentication type</description> + </valueHelp> + <valueHelp> + <format>ntlm</format> + <description>Supported authentication types for the 'connect' protocol</description> + </valueHelp> + <valueHelp> + <format>plain</format> + <description>The default 'smtp' authentication type</description> + </valueHelp> + <valueHelp> + <format>login</format> + <description>Supported authentication types for the 'smtp' protocol</description> + </valueHelp> + <constraint> + <regex>(basic|ntlm|plain|login)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="domain"> + <properties> + <help>Domain for the 'connect' protocol.</help> + <valueHelp> + <format>domain</format> + <description>domain</description> + </valueHelp> + <constraint> + <validator name="fqdn"/> + </constraint> + </properties> + </leafNode> + <node name="host"> + <properties> + <help>Destination address for the 'connect' protocol</help> + </properties> + <children> + #include <include/stunnel/address.xml.i> + #include <include/port-number.xml.i> + </children> + </node> + <leafNode name="password"> + <properties> + <help>Password for the protocol negotiations</help> + <valueHelp> + <format>txt</format> + <description>Authentication password</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="username"> + <properties> + <help>Username for the protocol negotiations</help> + <valueHelp> + <format>txt</format> + <description>Authentication username</description> + </valueHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/stunnel/protocol-value-cifs.xml.i b/interface-definitions/include/stunnel/protocol-value-cifs.xml.i new file mode 100644 index 0000000..5b94847 --- /dev/null +++ b/interface-definitions/include/stunnel/protocol-value-cifs.xml.i @@ -0,0 +1,6 @@ +<!-- include start from stunnel/protocol-value-cifs.xml.i --> +<valueHelp> + <format>cifs</format> + <description>Proprietary (undocummented) extension of CIFS protocol</description> +</valueHelp> +<!-- include end --> diff --git a/interface-definitions/include/stunnel/protocol-value-connect.xml.i b/interface-definitions/include/stunnel/protocol-value-connect.xml.i new file mode 100644 index 0000000..3c30e71 --- /dev/null +++ b/interface-definitions/include/stunnel/protocol-value-connect.xml.i @@ -0,0 +1,6 @@ +<!-- include start from stunnel/protocol-value-connect.xml.i --> +<valueHelp> + <format>connect</format> + <description>Based on RFC 2817 - Upgrading to TLS Within HTTP/1.1, section 5.2 - Requesting a Tunnel with CONNECT</description> +</valueHelp> +<!-- include end --> diff --git a/interface-definitions/include/stunnel/protocol-value-imap.xml.i b/interface-definitions/include/stunnel/protocol-value-imap.xml.i new file mode 100644 index 0000000..033e547 --- /dev/null +++ b/interface-definitions/include/stunnel/protocol-value-imap.xml.i @@ -0,0 +1,6 @@ +<!-- include start from stunnel/protocol-value-imap.xml.i --> +<valueHelp> + <format>imap</format> + <description>Based on RFC 2595 - Using TLS with IMAP, POP3 and ACAP</description> +</valueHelp> +<!-- include end --> diff --git a/interface-definitions/include/stunnel/protocol-value-nntp.xml.i b/interface-definitions/include/stunnel/protocol-value-nntp.xml.i new file mode 100644 index 0000000..60a6c02 --- /dev/null +++ b/interface-definitions/include/stunnel/protocol-value-nntp.xml.i @@ -0,0 +1,6 @@ +<!-- include start from stunnel/protocol-value-nntp.xml.i --> +<valueHelp> + <format>nntp</format> + <description>Based on RFC 4642 - Using Transport Layer Security (TLS) with Network News Transfer Protocol (NNTP)</description> +</valueHelp> +<!-- include end --> diff --git a/interface-definitions/include/stunnel/protocol-value-pgsql.xml.i b/interface-definitions/include/stunnel/protocol-value-pgsql.xml.i new file mode 100644 index 0000000..fd3a166 --- /dev/null +++ b/interface-definitions/include/stunnel/protocol-value-pgsql.xml.i @@ -0,0 +1,6 @@ +<!-- include start from stunnel/protocol-value-pgsql.xml.i --> +<valueHelp> + <format>pgsql</format> + <description>Based on PostgreSQL frontend/backend protocol</description> +</valueHelp> +<!-- include end --> diff --git a/interface-definitions/include/stunnel/protocol-value-pop3.xml.i b/interface-definitions/include/stunnel/protocol-value-pop3.xml.i new file mode 100644 index 0000000..1c8af53 --- /dev/null +++ b/interface-definitions/include/stunnel/protocol-value-pop3.xml.i @@ -0,0 +1,6 @@ +<!-- include start from stunnel/protocol-value-pop3.xml.i --> +<valueHelp> + <format>pop3</format> + <description>Based on RFC 2449 - POP3 Extension Mechanism</description> +</valueHelp> +<!-- include end --> diff --git a/interface-definitions/include/stunnel/protocol-value-proxy.xml.i b/interface-definitions/include/stunnel/protocol-value-proxy.xml.i new file mode 100644 index 0000000..a4c20d1 --- /dev/null +++ b/interface-definitions/include/stunnel/protocol-value-proxy.xml.i @@ -0,0 +1,6 @@ +<!-- include start from stunnel/protocol-value-proxy.xml.i --> +<valueHelp> + <format>proxy</format> + <description>Passing of the original client IP address with HAProxy PROXY protocol version 1</description> +</valueHelp> +<!-- include end --> diff --git a/interface-definitions/include/stunnel/protocol-value-smtp.xml.i b/interface-definitions/include/stunnel/protocol-value-smtp.xml.i new file mode 100644 index 0000000..66ca204 --- /dev/null +++ b/interface-definitions/include/stunnel/protocol-value-smtp.xml.i @@ -0,0 +1,6 @@ +<!-- include start from stunnel/protocol-value-smtp.xml.i --> +<valueHelp> + <format>smtp</format> + <description>Based on RFC 2487 - SMTP Service Extension for Secure SMTP over TLS</description> +</valueHelp> +<!-- include end --> diff --git a/interface-definitions/include/stunnel/protocol-value-socks.xml.i b/interface-definitions/include/stunnel/protocol-value-socks.xml.i new file mode 100644 index 0000000..e110be5 --- /dev/null +++ b/interface-definitions/include/stunnel/protocol-value-socks.xml.i @@ -0,0 +1,6 @@ +<!-- include start from stunnel/protocol-value-socks.xml.i --> +<valueHelp> + <format>socks</format> + <description>SOCKS versions 4, 4a, and 5 are supported</description> +</valueHelp> +<!-- include end --> diff --git a/interface-definitions/include/stunnel/psk.xml.i b/interface-definitions/include/stunnel/psk.xml.i new file mode 100644 index 0000000..db11a93 --- /dev/null +++ b/interface-definitions/include/stunnel/psk.xml.i @@ -0,0 +1,30 @@ +<!-- include start from stunnel/psk.xml.i --> +<tagNode name="psk"> + <properties> + <help>Pre-shared key name</help> + </properties> + <children> + <leafNode name="id"> + <properties> + <help>ID for authentication</help> + <valueHelp> + <format>txt</format> + <description>ID used for authentication</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="secret"> + <properties> + <help>pre-shared secret key</help> + <valueHelp> + <format>txt</format> + <description>pre-shared secret key are required to be at least 16 bytes long, which implies at least 32 characters for hexadecimal key</description> + </valueHelp> + <constraint> + <validator name="psk-secret"/> + </constraint> + </properties> + </leafNode> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/stunnel/ssl.xml.i b/interface-definitions/include/stunnel/ssl.xml.i new file mode 100644 index 0000000..8aba299 --- /dev/null +++ b/interface-definitions/include/stunnel/ssl.xml.i @@ -0,0 +1,11 @@ +<!-- include start from stunnel/ssl.xml.i --> +<node name="ssl"> + <properties> + <help>SSL Certificate, SSL Key and CA</help> + </properties> + <children> + #include <include/pki/ca-certificate-multi.xml.i> + #include <include/pki/certificate.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/syslog-facility.xml.i b/interface-definitions/include/syslog-facility.xml.i new file mode 100644 index 0000000..e6138a1 --- /dev/null +++ b/interface-definitions/include/syslog-facility.xml.i @@ -0,0 +1,149 @@ +<!-- include start from syslog-facility.xml.i --> +<tagNode name="facility"> + <properties> + <help>Facility for logging</help> + <completionHelp> + <list>auth authpriv cron daemon kern lpr mail mark news syslog user uucp local0 local1 local2 local3 local4 local5 local6 local7 all</list> + </completionHelp> + <constraint> + <regex>(auth|authpriv|cron|daemon|kern|lpr|mail|mark|news|syslog|user|uucp|local0|local1|local2|local3|local4|local5|local6|local7|all)</regex> + </constraint> + <constraintErrorMessage>Invalid facility type</constraintErrorMessage> + <valueHelp> + <format>all</format> + <description>All facilities excluding "mark"</description> + </valueHelp> + <valueHelp> + <format>auth</format> + <description>Authentication and authorization</description> + </valueHelp> + <valueHelp> + <format>authpriv</format> + <description>Non-system authorization</description> + </valueHelp> + <valueHelp> + <format>cron</format> + <description>Cron daemon</description> + </valueHelp> + <valueHelp> + <format>daemon</format> + <description>System daemons</description> + </valueHelp> + <valueHelp> + <format>kern</format> + <description>Kernel</description> + </valueHelp> + <valueHelp> + <format>lpr</format> + <description>Line printer spooler</description> + </valueHelp> + <valueHelp> + <format>mail</format> + <description>Mail subsystem</description> + </valueHelp> + <valueHelp> + <format>mark</format> + <description>Timestamp</description> + </valueHelp> + <valueHelp> + <format>news</format> + <description>USENET subsystem</description> + </valueHelp> + <valueHelp> + <format>syslog</format> + <description>Authentication and authorization</description> + </valueHelp> + <valueHelp> + <format>user</format> + <description>Application processes</description> + </valueHelp> + <valueHelp> + <format>uucp</format> + <description>UUCP subsystem</description> + </valueHelp> + <valueHelp> + <format>local0</format> + <description>Local facility 0</description> + </valueHelp> + <valueHelp> + <format>local1</format> + <description>Local facility 1</description> + </valueHelp> + <valueHelp> + <format>local2</format> + <description>Local facility 2</description> + </valueHelp> + <valueHelp> + <format>local3</format> + <description>Local facility 3</description> + </valueHelp> + <valueHelp> + <format>local4</format> + <description>Local facility 4</description> + </valueHelp> + <valueHelp> + <format>local5</format> + <description>Local facility 5</description> + </valueHelp> + <valueHelp> + <format>local6</format> + <description>Local facility 6</description> + </valueHelp> + <valueHelp> + <format>local7</format> + <description>Local facility 7</description> + </valueHelp> + </properties> + <children> + <leafNode name="level"> + <properties> + <help>Logging level</help> + <completionHelp> + <list>emerg alert crit err warning notice info debug all</list> + </completionHelp> + <valueHelp> + <format>emerg</format> + <description>Emergency messages</description> + </valueHelp> + <valueHelp> + <format>alert</format> + <description>Urgent messages</description> + </valueHelp> + <valueHelp> + <format>crit</format> + <description>Critical messages</description> + </valueHelp> + <valueHelp> + <format>err</format> + <description>Error messages</description> + </valueHelp> + <valueHelp> + <format>warning</format> + <description>Warning messages</description> + </valueHelp> + <valueHelp> + <format>notice</format> + <description>Messages for further investigation</description> + </valueHelp> + <valueHelp> + <format>info</format> + <description>Informational messages</description> + </valueHelp> + <valueHelp> + <format>debug</format> + <description>Debug messages</description> + </valueHelp> + <valueHelp> + <format>all</format> + <description>Log everything</description> + </valueHelp> + <constraint> + <regex>(emerg|alert|crit|err|warning|notice|info|debug|all)</regex> + </constraint> + <constraintErrorMessage>Invalid loglevel</constraintErrorMessage> + </properties> + <defaultValue>err</defaultValue> + </leafNode> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/system-ip-nht.xml.i b/interface-definitions/include/system-ip-nht.xml.i new file mode 100644 index 0000000..4074043 --- /dev/null +++ b/interface-definitions/include/system-ip-nht.xml.i @@ -0,0 +1,15 @@ +<!-- include start from syslog-facility.xml.i --> +<node name="nht"> + <properties> + <help>Filter Next Hop tracking route resolution</help> + </properties> + <children> + <leafNode name="no-resolve-via-default"> + <properties> + <help>Do not resolve via default route</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/system-ip-protocol.xml.i b/interface-definitions/include/system-ip-protocol.xml.i new file mode 100644 index 0000000..c630eb3 --- /dev/null +++ b/interface-definitions/include/system-ip-protocol.xml.i @@ -0,0 +1,56 @@ +<!-- include start from system-ip-protocol.xml.i --> +<tagNode name="protocol"> + <properties> + <help>Filter routing info exchanged between routing protocol and zebra</help> + <completionHelp> + <list>any babel bgp connected eigrp isis kernel ospf rip static table</list> + </completionHelp> + <valueHelp> + <format>any</format> + <description>Any of the above protocols</description> + </valueHelp> + <valueHelp> + <format>babel</format> + <description>Babel routing protocol</description> + </valueHelp> + <valueHelp> + <format>bgp</format> + <description>Border Gateway Protocol</description> + </valueHelp> + <valueHelp> + <format>connected</format> + <description>Connected routes (directly attached subnet or host)</description> + </valueHelp> + <valueHelp> + <format>eigrp</format> + <description>Enhanced Interior Gateway Routing Protocol</description> + </valueHelp> + <valueHelp> + <format>isis</format> + <description>Intermediate System to Intermediate System</description> + </valueHelp> + <valueHelp> + <format>kernel</format> + <description>Kernel routes (not installed via the zebra RIB)</description> + </valueHelp> + <valueHelp> + <format>ospf</format> + <description>Open Shortest Path First (OSPFv2)</description> + </valueHelp> + <valueHelp> + <format>rip</format> + <description>Routing Information Protocol</description> + </valueHelp> + <valueHelp> + <format>static</format> + <description>Statically configured routes</description> + </valueHelp> + <constraint> + <regex>(any|babel|bgp|connected|eigrp|isis|kernel|ospf|rip|static|table)</regex> + </constraint> + </properties> + <children> + #include <include/route-map.xml.i> + </children> +</tagNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/system-ipv6-protocol.xml.i b/interface-definitions/include/system-ipv6-protocol.xml.i new file mode 100644 index 0000000..485776a --- /dev/null +++ b/interface-definitions/include/system-ipv6-protocol.xml.i @@ -0,0 +1,52 @@ +<!-- include start from system-ipv6-protocol.xml.i --> +<tagNode name="protocol"> + <properties> + <help>Filter routing info exchanged between routing protocol and zebra</help> + <completionHelp> + <list>any babel bgp connected isis kernel ospfv3 ripng static table</list> + </completionHelp> + <valueHelp> + <format>any</format> + <description>Any of the above protocols</description> + </valueHelp> + <valueHelp> + <format>babel</format> + <description>Babel routing protocol</description> + </valueHelp> + <valueHelp> + <format>bgp</format> + <description>Border Gateway Protocol</description> + </valueHelp> + <valueHelp> + <format>connected</format> + <description>Connected routes (directly attached subnet or host)</description> + </valueHelp> + <valueHelp> + <format>isis</format> + <description>Intermediate System to Intermediate System</description> + </valueHelp> + <valueHelp> + <format>kernel</format> + <description>Kernel routes (not installed via the zebra RIB)</description> + </valueHelp> + <valueHelp> + <format>ospfv3</format> + <description>Open Shortest Path First (OSPFv3)</description> + </valueHelp> + <valueHelp> + <format>ripng</format> + <description>Routing Information Protocol next-generation</description> + </valueHelp> + <valueHelp> + <format>static</format> + <description>Statically configured routes</description> + </valueHelp> + <constraint> + <regex>(any|babel|bgp|connected|isis|kernel|ospfv3|ripng|static|table)</regex> + </constraint> + </properties> + <children> + #include <include/route-map.xml.i> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/tls-version-min.xml.i b/interface-definitions/include/tls-version-min.xml.i new file mode 100644 index 0000000..b3dcbad --- /dev/null +++ b/interface-definitions/include/tls-version-min.xml.i @@ -0,0 +1,29 @@ +<!-- include start from tls-version-min.xml.i --> +<leafNode name="tls-version-min"> + <properties> + <help>Specify the minimum required TLS version</help> + <completionHelp> + <list>1.0 1.1 1.2 1.3</list> + </completionHelp> + <valueHelp> + <format>1.0</format> + <description>TLS v1.0</description> + </valueHelp> + <valueHelp> + <format>1.1</format> + <description>TLS v1.1</description> + </valueHelp> + <valueHelp> + <format>1.2</format> + <description>TLS v1.2</description> + </valueHelp> + <valueHelp> + <format>1.3</format> + <description>TLS v1.3</description> + </valueHelp> + <constraint> + <regex>(1.0|1.1|1.2|1.3)</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/url-http-https.xml.i b/interface-definitions/include/url-http-https.xml.i new file mode 100644 index 0000000..f763c2b --- /dev/null +++ b/interface-definitions/include/url-http-https.xml.i @@ -0,0 +1,15 @@ +<!-- include start from url-http-https.xml.i --> +<leafNode name="url"> + <properties> + <help>Remote URL</help> + <valueHelp> + <format>url</format> + <description>Remote HTTP(S) URL</description> + </valueHelp> + <constraint> + <validator name="url" argument="--scheme http --scheme https"/> + </constraint> + <constraintErrorMessage>Invalid HTTP(S) URL format</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/version/bgp-version.xml.i b/interface-definitions/include/version/bgp-version.xml.i new file mode 100644 index 0000000..6bed718 --- /dev/null +++ b/interface-definitions/include/version/bgp-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/bgp-version.xml.i --> +<syntaxVersion component='bgp' version='5'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/broadcast-relay-version.xml.i b/interface-definitions/include/version/broadcast-relay-version.xml.i new file mode 100644 index 0000000..98481f4 --- /dev/null +++ b/interface-definitions/include/version/broadcast-relay-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/broadcast-relay-version.xml.i --> +<syntaxVersion component='broadcast-relay' version='1'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/cluster-version.xml.i b/interface-definitions/include/version/cluster-version.xml.i new file mode 100644 index 0000000..402fe36 --- /dev/null +++ b/interface-definitions/include/version/cluster-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/cluster-version.xml.i --> +<syntaxVersion component='cluster' version='2'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/config-management-version.xml.i b/interface-definitions/include/version/config-management-version.xml.i new file mode 100644 index 0000000..695ba09 --- /dev/null +++ b/interface-definitions/include/version/config-management-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/config-management-version.xml.i --> +<syntaxVersion component='config-management' version='1'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/conntrack-sync-version.xml.i b/interface-definitions/include/version/conntrack-sync-version.xml.i new file mode 100644 index 0000000..f040c29 --- /dev/null +++ b/interface-definitions/include/version/conntrack-sync-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/conntrack-sync-version.xml.i --> +<syntaxVersion component='conntrack-sync' version='2'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/conntrack-version.xml.i b/interface-definitions/include/version/conntrack-version.xml.i new file mode 100644 index 0000000..6995ce1 --- /dev/null +++ b/interface-definitions/include/version/conntrack-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/conntrack-version.xml.i --> +<syntaxVersion component='conntrack' version='5'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/container-version.xml.i b/interface-definitions/include/version/container-version.xml.i new file mode 100644 index 0000000..ed6e942 --- /dev/null +++ b/interface-definitions/include/version/container-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/container-version.xml.i --> +<syntaxVersion component='container' version='2'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/dhcp-relay-version.xml.i b/interface-definitions/include/version/dhcp-relay-version.xml.i new file mode 100644 index 0000000..75f5d54 --- /dev/null +++ b/interface-definitions/include/version/dhcp-relay-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/dhcp-relay-version.xml.i --> +<syntaxVersion component='dhcp-relay' version='2'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/dhcp-server-version.xml.i b/interface-definitions/include/version/dhcp-server-version.xml.i new file mode 100644 index 0000000..71f3d4a --- /dev/null +++ b/interface-definitions/include/version/dhcp-server-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/dhcp-server-version.xml.i --> +<syntaxVersion component='dhcp-server' version='11'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/dhcpv6-server-version.xml.i b/interface-definitions/include/version/dhcpv6-server-version.xml.i new file mode 100644 index 0000000..8b72a9c --- /dev/null +++ b/interface-definitions/include/version/dhcpv6-server-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/dhcpv6-server-version.xml.i --> +<syntaxVersion component='dhcpv6-server' version='6'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/dns-dynamic-version.xml.i b/interface-definitions/include/version/dns-dynamic-version.xml.i new file mode 100644 index 0000000..346385c --- /dev/null +++ b/interface-definitions/include/version/dns-dynamic-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/dns-dynamic-version.xml.i --> +<syntaxVersion component='dns-dynamic' version='4'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/dns-forwarding-version.xml.i b/interface-definitions/include/version/dns-forwarding-version.xml.i new file mode 100644 index 0000000..86121ae --- /dev/null +++ b/interface-definitions/include/version/dns-forwarding-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/dns-forwarding-version.xml.i --> +<syntaxVersion component='dns-forwarding' version='4'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/firewall-version.xml.i b/interface-definitions/include/version/firewall-version.xml.i new file mode 100644 index 0000000..a15cf0e --- /dev/null +++ b/interface-definitions/include/version/firewall-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/firewall-version.xml.i --> +<syntaxVersion component='firewall' version='17'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/flow-accounting-version.xml.i b/interface-definitions/include/version/flow-accounting-version.xml.i new file mode 100644 index 0000000..5b01fe4 --- /dev/null +++ b/interface-definitions/include/version/flow-accounting-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/flow-accounting-version.xml.i --> +<syntaxVersion component='flow-accounting' version='1'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/https-version.xml.i b/interface-definitions/include/version/https-version.xml.i new file mode 100644 index 0000000..525314d --- /dev/null +++ b/interface-definitions/include/version/https-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/https-version.xml.i --> +<syntaxVersion component='https' version='6'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/ids-version.xml.i b/interface-definitions/include/version/ids-version.xml.i new file mode 100644 index 0000000..9133be0 --- /dev/null +++ b/interface-definitions/include/version/ids-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/ids-version.xml.i --> +<syntaxVersion component='ids' version='1'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/interfaces-version.xml.i b/interface-definitions/include/version/interfaces-version.xml.i new file mode 100644 index 0000000..2915b31 --- /dev/null +++ b/interface-definitions/include/version/interfaces-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/interfaces-version.xml.i --> +<syntaxVersion component='interfaces' version='33'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/ipoe-server-version.xml.i b/interface-definitions/include/version/ipoe-server-version.xml.i new file mode 100644 index 0000000..b7718fc --- /dev/null +++ b/interface-definitions/include/version/ipoe-server-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/ipoe-server-version.xml.i --> +<syntaxVersion component='ipoe-server' version='4'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/ipsec-version.xml.i b/interface-definitions/include/version/ipsec-version.xml.i new file mode 100644 index 0000000..a4d556c --- /dev/null +++ b/interface-definitions/include/version/ipsec-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/ipsec-version.xml.i --> +<syntaxVersion component='ipsec' version='13'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/isis-version.xml.i b/interface-definitions/include/version/isis-version.xml.i new file mode 100644 index 0000000..f50329b --- /dev/null +++ b/interface-definitions/include/version/isis-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/isis-version.xml.i --> +<syntaxVersion component='isis' version='3'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/l2tp-version.xml.i b/interface-definitions/include/version/l2tp-version.xml.i new file mode 100644 index 0000000..5397407 --- /dev/null +++ b/interface-definitions/include/version/l2tp-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/l2tp-version.xml.i --> +<syntaxVersion component='l2tp' version='9'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/lldp-version.xml.i b/interface-definitions/include/version/lldp-version.xml.i new file mode 100644 index 0000000..b41d804 --- /dev/null +++ b/interface-definitions/include/version/lldp-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/lldp-version.xml.i --> +<syntaxVersion component='lldp' version='2'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/mdns-version.xml.i b/interface-definitions/include/version/mdns-version.xml.i new file mode 100644 index 0000000..b200a68 --- /dev/null +++ b/interface-definitions/include/version/mdns-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/mdns-version.xml.i --> +<syntaxVersion component='mdns' version='1'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/monitoring-version.xml.i b/interface-definitions/include/version/monitoring-version.xml.i new file mode 100644 index 0000000..6a275a5 --- /dev/null +++ b/interface-definitions/include/version/monitoring-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/monitoring-version.xml.i --> +<syntaxVersion component='monitoring' version='1'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/nat-version.xml.i b/interface-definitions/include/version/nat-version.xml.i new file mode 100644 index 0000000..173e91e --- /dev/null +++ b/interface-definitions/include/version/nat-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/nat-version.xml.i --> +<syntaxVersion component='nat' version='8'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/nat66-version.xml.i b/interface-definitions/include/version/nat66-version.xml.i new file mode 100644 index 0000000..43a54c9 --- /dev/null +++ b/interface-definitions/include/version/nat66-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/nat66-version.xml.i --> +<syntaxVersion component='nat66' version='3'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/ntp-version.xml.i b/interface-definitions/include/version/ntp-version.xml.i new file mode 100644 index 0000000..155c824 --- /dev/null +++ b/interface-definitions/include/version/ntp-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/ntp-version.xml.i --> +<syntaxVersion component='ntp' version='3'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/openconnect-version.xml.i b/interface-definitions/include/version/openconnect-version.xml.i new file mode 100644 index 0000000..15097ee --- /dev/null +++ b/interface-definitions/include/version/openconnect-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/openconnect-version.xml.i --> +<syntaxVersion component='openconnect' version='3'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/openvpn-version.xml.i b/interface-definitions/include/version/openvpn-version.xml.i new file mode 100644 index 0000000..67ef219 --- /dev/null +++ b/interface-definitions/include/version/openvpn-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/openvpn-version.xml.i --> +<syntaxVersion component='openvpn' version='4'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/ospf-version.xml.i b/interface-definitions/include/version/ospf-version.xml.i new file mode 100644 index 0000000..df10883 --- /dev/null +++ b/interface-definitions/include/version/ospf-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/ospf-version.xml.i --> +<syntaxVersion component='ospf' version='2'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/pim-version.xml.i b/interface-definitions/include/version/pim-version.xml.i new file mode 100644 index 0000000..24cc38c --- /dev/null +++ b/interface-definitions/include/version/pim-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/pim-version.xml.i --> +<syntaxVersion component='pim' version='1'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/policy-version.xml.i b/interface-definitions/include/version/policy-version.xml.i new file mode 100644 index 0000000..db727fe --- /dev/null +++ b/interface-definitions/include/version/policy-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/policy-version.xml.i --> +<syntaxVersion component='policy' version='8'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/pppoe-server-version.xml.i b/interface-definitions/include/version/pppoe-server-version.xml.i new file mode 100644 index 0000000..2e020fa --- /dev/null +++ b/interface-definitions/include/version/pppoe-server-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/pppoe-server-version.xml.i --> +<syntaxVersion component='pppoe-server' version='11'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/pptp-version.xml.i b/interface-definitions/include/version/pptp-version.xml.i new file mode 100644 index 0000000..a877d77 --- /dev/null +++ b/interface-definitions/include/version/pptp-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/pptp-version.xml.i --> +<syntaxVersion component='pptp' version='5'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/qos-version.xml.i b/interface-definitions/include/version/qos-version.xml.i new file mode 100644 index 0000000..c67e61e --- /dev/null +++ b/interface-definitions/include/version/qos-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/qos-version.xml.i --> +<syntaxVersion component='qos' version='2'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/quagga-version.xml.i b/interface-definitions/include/version/quagga-version.xml.i new file mode 100644 index 0000000..23d884c --- /dev/null +++ b/interface-definitions/include/version/quagga-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/quagga-version.xml.i --> +<syntaxVersion component='quagga' version='11'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/reverseproxy-version.xml.i b/interface-definitions/include/version/reverseproxy-version.xml.i new file mode 100644 index 0000000..907ea1e --- /dev/null +++ b/interface-definitions/include/version/reverseproxy-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/reverseproxy-version.xml.i --> +<syntaxVersion component='reverse-proxy' version='1'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/rip-version.xml.i b/interface-definitions/include/version/rip-version.xml.i new file mode 100644 index 0000000..30ace48 --- /dev/null +++ b/interface-definitions/include/version/rip-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/rip-version.xml.i --> +<syntaxVersion component='rip' version='1'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/rpki-version.xml.i b/interface-definitions/include/version/rpki-version.xml.i new file mode 100644 index 0000000..45ff4fb --- /dev/null +++ b/interface-definitions/include/version/rpki-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/rpki-version.xml.i --> +<syntaxVersion component='rpki' version='2'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/salt-version.xml.i b/interface-definitions/include/version/salt-version.xml.i new file mode 100644 index 0000000..fe46840 --- /dev/null +++ b/interface-definitions/include/version/salt-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/salt-version.xml.i --> +<syntaxVersion component='salt' version='1'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/snmp-version.xml.i b/interface-definitions/include/version/snmp-version.xml.i new file mode 100644 index 0000000..fa58672 --- /dev/null +++ b/interface-definitions/include/version/snmp-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/snmp-version.xml.i --> +<syntaxVersion component='snmp' version='3'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/ssh-version.xml.i b/interface-definitions/include/version/ssh-version.xml.i new file mode 100644 index 0000000..0f25caf --- /dev/null +++ b/interface-definitions/include/version/ssh-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/ssh-version.xml.i --> +<syntaxVersion component='ssh' version='2'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/sstp-version.xml.i b/interface-definitions/include/version/sstp-version.xml.i new file mode 100644 index 0000000..5e30950 --- /dev/null +++ b/interface-definitions/include/version/sstp-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/sstp-version.xml.i --> +<syntaxVersion component='sstp' version='6'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/system-version.xml.i b/interface-definitions/include/version/system-version.xml.i new file mode 100644 index 0000000..fcb24ab --- /dev/null +++ b/interface-definitions/include/version/system-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/system-version.xml.i --> +<syntaxVersion component='system' version='27'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/vrf-version.xml.i b/interface-definitions/include/version/vrf-version.xml.i new file mode 100644 index 0000000..9d7ff35 --- /dev/null +++ b/interface-definitions/include/version/vrf-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/vrf-version.xml.i --> +<syntaxVersion component='vrf' version='3'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/vrrp-version.xml.i b/interface-definitions/include/version/vrrp-version.xml.i new file mode 100644 index 0000000..1514b19 --- /dev/null +++ b/interface-definitions/include/version/vrrp-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/vrrp-version.xml.i --> +<syntaxVersion component='vrrp' version='4'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/vyos-accel-ppp-version.xml.i b/interface-definitions/include/version/vyos-accel-ppp-version.xml.i new file mode 100644 index 0000000..e5a4e16 --- /dev/null +++ b/interface-definitions/include/version/vyos-accel-ppp-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/vyos-accel-ppp-version.xml.i --> +<syntaxVersion component='vyos-accel-ppp' version='2'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/wanloadbalance-version.xml.i b/interface-definitions/include/version/wanloadbalance-version.xml.i new file mode 100644 index 0000000..59f8729 --- /dev/null +++ b/interface-definitions/include/version/wanloadbalance-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/wanloadbalance-version.xml.i --> +<syntaxVersion component='wanloadbalance' version='3'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/version/webproxy-version.xml.i b/interface-definitions/include/version/webproxy-version.xml.i new file mode 100644 index 0000000..42dbf3f --- /dev/null +++ b/interface-definitions/include/version/webproxy-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/webproxy-version.xml.i --> +<syntaxVersion component='webproxy' version='2'></syntaxVersion> +<!-- include end --> diff --git a/interface-definitions/include/vni.xml.i b/interface-definitions/include/vni.xml.i new file mode 100644 index 0000000..36176ca --- /dev/null +++ b/interface-definitions/include/vni.xml.i @@ -0,0 +1,14 @@ +<!-- include start from vni.xml.i --> +<leafNode name="vni"> + <properties> + <help>Virtual Network Identifier</help> + <valueHelp> + <format>u32:0-16777214</format> + <description>VXLAN virtual network identifier</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16777214"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/vpn-ipsec-encryption.xml.i b/interface-definitions/include/vpn-ipsec-encryption.xml.i new file mode 100644 index 0000000..629e6a0 --- /dev/null +++ b/interface-definitions/include/vpn-ipsec-encryption.xml.i @@ -0,0 +1,234 @@ +<!-- include start from vpn-ipsec-encryption.xml.i --> +<leafNode name="encryption"> + <properties> + <help>Encryption algorithm</help> + <completionHelp> + <list>null aes128 aes192 aes256 aes128ctr aes192ctr aes256ctr aes128ccm64 aes192ccm64 aes256ccm64 aes128ccm96 aes192ccm96 aes256ccm96 aes128ccm128 aes192ccm128 aes256ccm128 aes128gcm64 aes192gcm64 aes256gcm64 aes128gcm96 aes192gcm96 aes256gcm96 aes128gcm128 aes192gcm128 aes256gcm128 aes128gmac aes192gmac aes256gmac 3des blowfish128 blowfish192 blowfish256 camellia128 camellia192 camellia256 camellia128ctr camellia192ctr camellia256ctr camellia128ccm64 camellia192ccm64 camellia256ccm64 camellia128ccm96 camellia192ccm96 camellia256ccm96 camellia128ccm128 camellia192ccm128 camellia256ccm128 serpent128 serpent192 serpent256 twofish128 twofish192 twofish256 cast128 chacha20poly1305</list> + </completionHelp> + <valueHelp> + <format>null</format> + <description>Null encryption</description> + </valueHelp> + <valueHelp> + <format>aes128</format> + <description>128 bit AES-CBC</description> + </valueHelp> + <valueHelp> + <format>aes192</format> + <description>192 bit AES-CBC</description> + </valueHelp> + <valueHelp> + <format>aes256</format> + <description>256 bit AES-CBC</description> + </valueHelp> + <valueHelp> + <format>aes128ctr</format> + <description>128 bit AES-COUNTER</description> + </valueHelp> + <valueHelp> + <format>aes192ctr</format> + <description>192 bit AES-COUNTER</description> + </valueHelp> + <valueHelp> + <format>aes256ctr</format> + <description>256 bit AES-COUNTER</description> + </valueHelp> + <valueHelp> + <format>aes128ccm64</format> + <description>128 bit AES-CCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes192ccm64</format> + <description>192 bit AES-CCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes256ccm64</format> + <description>256 bit AES-CCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes128ccm96</format> + <description>128 bit AES-CCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes192ccm96</format> + <description>192 bit AES-CCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes256ccm96</format> + <description>256 bit AES-CCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes128ccm128</format> + <description>128 bit AES-CCM with 128 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes192ccm128</format> + <description>192 bit AES-CCM with 128 bit IC</description> + </valueHelp> + <valueHelp> + <format>aes256ccm128</format> + <description>256 bit AES-CCM with 128 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes128gcm64</format> + <description>128 bit AES-GCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes192gcm64</format> + <description>192 bit AES-GCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes256gcm64</format> + <description>256 bit AES-GCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes128gcm96</format> + <description>128 bit AES-GCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes192gcm96</format> + <description>192 bit AES-GCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes256gcm96</format> + <description>256 bit AES-GCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes128gcm128</format> + <description>128 bit AES-GCM with 128 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes192gcm128</format> + <description>192 bit AES-GCM with 128 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes256gcm128</format> + <description>256 bit AES-GCM with 128 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes128gmac</format> + <description>Null encryption with 128 bit AES-GMAC</description> + </valueHelp> + <valueHelp> + <format>aes192gmac</format> + <description>Null encryption with 192 bit AES-GMAC</description> + </valueHelp> + <valueHelp> + <format>aes256gmac</format> + <description>Null encryption with 256 bit AES-GMAC</description> + </valueHelp> + <valueHelp> + <format>3des</format> + <description>168 bit 3DES-EDE-CBC</description> + </valueHelp> + <valueHelp> + <format>blowfish128</format> + <description>128 bit Blowfish-CBC</description> + </valueHelp> + <valueHelp> + <format>blowfish192</format> + <description>192 bit Blowfish-CBC</description> + </valueHelp> + <valueHelp> + <format>blowfish256</format> + <description>256 bit Blowfish-CBC</description> + </valueHelp> + <valueHelp> + <format>camellia128</format> + <description>128 bit Camellia-CBC</description> + </valueHelp> + <valueHelp> + <format>camellia192</format> + <description>192 bit Camellia-CBC</description> + </valueHelp> + <valueHelp> + <format>camellia256</format> + <description>256 bit Camellia-CBC</description> + </valueHelp> + <valueHelp> + <format>camellia128ctr</format> + <description>128 bit Camellia-COUNTER</description> + </valueHelp> + <valueHelp> + <format>camellia192ctr</format> + <description>192 bit Camellia-COUNTER</description> + </valueHelp> + <valueHelp> + <format>camellia256ctr</format> + <description>256 bit Camellia-COUNTER</description> + </valueHelp> + <valueHelp> + <format>camellia128ccm64</format> + <description>128 bit Camellia-CCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>camellia192ccm64</format> + <description>192 bit Camellia-CCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>camellia256ccm64</format> + <description>256 bit Camellia-CCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>camellia128ccm96</format> + <description>128 bit Camellia-CCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>camellia192ccm96</format> + <description>192 bit Camellia-CCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>camellia256ccm96</format> + <description>256 bit Camellia-CCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>camellia128ccm128</format> + <description>128 bit Camellia-CCM with 128 bit ICV</description> + </valueHelp> + <valueHelp> + <format>camellia192ccm128</format> + <description>192 bit Camellia-CCM with 128 bit ICV</description> + </valueHelp> + <valueHelp> + <format>camellia256ccm128</format> + <description>256 bit Camellia-CCM with 128 bit ICV</description> + </valueHelp> + <valueHelp> + <format>serpent128</format> + <description>128 bit Serpent-CBC</description> + </valueHelp> + <valueHelp> + <format>serpent192</format> + <description>192 bit Serpent-CBC</description> + </valueHelp> + <valueHelp> + <format>serpent256</format> + <description>256 bit Serpent-CBC</description> + </valueHelp> + <valueHelp> + <format>twofish128</format> + <description>128 bit Twofish-CBC</description> + </valueHelp> + <valueHelp> + <format>twofish192</format> + <description>192 bit Twofish-CBC</description> + </valueHelp> + <valueHelp> + <format>twofish256</format> + <description>256 bit Twofish-CBC</description> + </valueHelp> + <valueHelp> + <format>cast128</format> + <description>128 bit CAST-CBC</description> + </valueHelp> + <valueHelp> + <format>chacha20poly1305</format> + <description>256 bit ChaCha20/Poly1305 with 128 bit ICV</description> + </valueHelp> + <constraint> + <regex>(null|aes128|aes192|aes256|aes128ctr|aes192ctr|aes256ctr|aes128ccm64|aes192ccm64|aes256ccm64|aes128ccm96|aes192ccm96|aes256ccm96|aes128ccm128|aes192ccm128|aes256ccm128|aes128gcm64|aes192gcm64|aes256gcm64|aes128gcm96|aes192gcm96|aes256gcm96|aes128gcm128|aes192gcm128|aes256gcm128|aes128gmac|aes192gmac|aes256gmac|3des|blowfish128|blowfish192|blowfish256|camellia128|camellia192|camellia256|camellia128ctr|camellia192ctr|camellia256ctr|camellia128ccm64|camellia192ccm64|camellia256ccm64|camellia128ccm96|camellia192ccm96|camellia256ccm96|camellia128ccm128|camellia192ccm128|camellia256ccm128|serpent128|serpent192|serpent256|twofish128|twofish192|twofish256|cast128|chacha20poly1305)</regex> + </constraint> + </properties> + <defaultValue>aes128</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/vpn-ipsec-hash.xml.i b/interface-definitions/include/vpn-ipsec-hash.xml.i new file mode 100644 index 0000000..73d19c2 --- /dev/null +++ b/interface-definitions/include/vpn-ipsec-hash.xml.i @@ -0,0 +1,66 @@ +<!-- include start from vpn-ipsec-hash.xml.i --> +<leafNode name="hash"> + <properties> + <help>Hash algorithm</help> + <completionHelp> + <list>md5 md5_128 sha1 sha1_160 sha256 sha256_96 sha384 sha512 aesxcbc aescmac aes128gmac aes192gmac aes256gmac</list> + </completionHelp> + <valueHelp> + <format>md5</format> + <description>MD5 HMAC</description> + </valueHelp> + <valueHelp> + <format>md5_128</format> + <description>MD5_128 HMAC</description> + </valueHelp> + <valueHelp> + <format>sha1</format> + <description>SHA1 HMAC</description> + </valueHelp> + <valueHelp> + <format>sha1_160</format> + <description>SHA1_160 HMAC</description> + </valueHelp> + <valueHelp> + <format>sha256</format> + <description>SHA2_256_128 HMAC</description> + </valueHelp> + <valueHelp> + <format>sha256_96</format> + <description>SHA2_256_96 HMAC</description> + </valueHelp> + <valueHelp> + <format>sha384</format> + <description>SHA2_384_192 HMAC</description> + </valueHelp> + <valueHelp> + <format>sha512</format> + <description>SHA2_512_256 HMAC</description> + </valueHelp> + <valueHelp> + <format>aesxcbc</format> + <description>AES XCBC</description> + </valueHelp> + <valueHelp> + <format>aescmac</format> + <description>AES CMAC</description> + </valueHelp> + <valueHelp> + <format>aes128gmac</format> + <description>128-bit AES-GMAC</description> + </valueHelp> + <valueHelp> + <format>aes192gmac</format> + <description>192-bit AES-GMAC</description> + </valueHelp> + <valueHelp> + <format>aes256gmac</format> + <description>256-bit AES-GMAC</description> + </valueHelp> + <constraint> + <regex>(md5|md5_128|sha1|sha1_160|sha256|sha256_96|sha384|sha512|aesxcbc|aescmac|aes128gmac|aes192gmac|aes256gmac)</regex> + </constraint> + </properties> + <defaultValue>sha1</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/vrf-multi.xml.i b/interface-definitions/include/vrf-multi.xml.i new file mode 100644 index 0000000..0b22894 --- /dev/null +++ b/interface-definitions/include/vrf-multi.xml.i @@ -0,0 +1,22 @@ +<!-- include start from interface/vrf.xml.i --> +<leafNode name="vrf"> + <properties> + <help>VRF instance name</help> + <completionHelp> + <path>vrf name</path> + <list>default</list> + </completionHelp> + <valueHelp> + <format>default</format> + <description>Explicitly start in default VRF</description> + </valueHelp> + <valueHelp> + <format>txt</format> + <description>VRF instance name</description> + </valueHelp> + #include <include/constraint/vrf.xml.i> + <multi/> + </properties> + <defaultValue>default</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/vrrp-transition-script.xml.i b/interface-definitions/include/vrrp-transition-script.xml.i new file mode 100644 index 0000000..cf57c3c --- /dev/null +++ b/interface-definitions/include/vrrp-transition-script.xml.i @@ -0,0 +1,41 @@ +<!-- include start from vrrp-transition-script.xml.i --> +<node name="transition-script"> + <properties> + <help>VRRP transition scripts</help> + </properties> + <children> + <leafNode name="master"> + <properties> + <help>Script to run on VRRP state transition to master</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + <leafNode name="backup"> + <properties> + <help>Script to run on VRRP state transition to backup</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + <leafNode name="fault"> + <properties> + <help>Script to run on VRRP state transition to fault</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + <leafNode name="stop"> + <properties> + <help>Script to run on VRRP state transition to stop</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/vrrp/garp.xml.i b/interface-definitions/include/vrrp/garp.xml.i new file mode 100644 index 0000000..b56b490 --- /dev/null +++ b/interface-definitions/include/vrrp/garp.xml.i @@ -0,0 +1,78 @@ +<!-- include start from vrrp/garp.xml.i --> +<node name="garp"> + <properties> + <help>Gratuitous ARP parameters</help> + </properties> + <children> + <leafNode name="interval"> + <properties> + <help>Interval between Gratuitous ARP</help> + <valueHelp> + <format><0.000-1000></format> + <description>Interval in seconds, resolution microseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0.000-1000 --float"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="master-delay"> + <properties> + <help>Delay for second set of gratuitous ARPs after transition to master</help> + <valueHelp> + <format>u32:1-1000</format> + <description>Delay in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-1000"/> + </constraint> + </properties> + <defaultValue>5</defaultValue> + </leafNode> + <leafNode name="master-refresh"> + <properties> + <help>Minimum time interval for refreshing gratuitous ARPs while beeing master</help> + <valueHelp> + <format>u32:0</format> + <description>No refresh</description> + </valueHelp> + <valueHelp> + <format>u32:1-255</format> + <description>Interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <defaultValue>5</defaultValue> + </leafNode> + <leafNode name="master-refresh-repeat"> + <properties> + <help>Number of gratuitous ARP messages to send at a time while beeing master</help> + <valueHelp> + <format>u32:1-255</format> + <description>Number of gratuitous ARP messages</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <defaultValue>1</defaultValue> + </leafNode> + <leafNode name="master-repeat"> + <properties> + <help>Number of gratuitous ARP messages to send at a time after transition to master</help> + <valueHelp> + <format>u32:1-255</format> + <description>Number of gratuitous ARP messages</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <defaultValue>5</defaultValue> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/webproxy-url-filtering.xml.i b/interface-definitions/include/webproxy-url-filtering.xml.i new file mode 100644 index 0000000..7763cb3 --- /dev/null +++ b/interface-definitions/include/webproxy-url-filtering.xml.i @@ -0,0 +1,119 @@ +<!-- include start from webproxy-url-filtering.xml.i --> +<leafNode name="allow-category"> + <properties> + <help>Category to allow</help> + <completionHelp> + <script>${vyos_completion_dir}/list_webproxy_category.sh</script> + </completionHelp> + <multi/> + </properties> +</leafNode> +<leafNode name="allow-ipaddr-url"> + <properties> + <help>Allow IP address URLs</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="block-category"> + <properties> + <help>Category to block</help> + <completionHelp> + <script>${vyos_completion_dir}/list_webproxy_category.sh</script> + </completionHelp> + <multi/> + </properties> +</leafNode> +<leafNode name="default-action"> + <properties> + <help>Default action (default: allow)</help> + <completionHelp> + <list>allow block</list> + </completionHelp> + <valueHelp> + <format>allow</format> + <description>Default filter action is allow)</description> + </valueHelp> + <valueHelp> + <format>block</format> + <description>Default filter action is block</description> + </valueHelp> + <constraint> + <regex>(allow|block)</regex> + </constraint> + </properties> +</leafNode> +<leafNode name="enable-safe-search"> + <properties> + <help>Enable safe-mode search on popular search engines</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="local-block-keyword"> + <properties> + <help>Local keyword to block</help> + <valueHelp> + <format>keyword</format> + <description>Keyword (or regex) to block</description> + </valueHelp> + <multi/> + </properties> +</leafNode> +<leafNode name="local-block-url"> + <properties> + <help>Local URL to block</help> + <valueHelp> + <format>url</format> + <description>Local URL to block (without "http://")</description> + </valueHelp> + <multi/> + </properties> +</leafNode> +<leafNode name="local-block"> + <properties> + <help>Local site to block</help> + <valueHelp> + <format>ipv4</format> + <description>IP address of site to block</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="fqdn"/> + </constraint> + <multi/> + </properties> +</leafNode> +<leafNode name="local-ok-url"> + <properties> + <help>Local URL to allow</help> + <valueHelp> + <format>url</format> + <description>Local URL to allow (without "http://")</description> + </valueHelp> + <multi/> + </properties> +</leafNode> +<leafNode name="local-ok"> + <properties> + <help>Local site to allow</help> + <valueHelp> + <format>ipv4</format> + <description>IP address of site to allow</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="fqdn"/> + </constraint> + <multi/> + </properties> +</leafNode> +<leafNode name="log"> + <properties> + <help>Log block category</help> + <completionHelp> + <script>${vyos_completion_dir}/list_webproxy_category.sh</script> + <list>all</list> + </completionHelp> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/interfaces_bonding.xml.in b/interface-definitions/interfaces_bonding.xml.in new file mode 100644 index 0000000..b17cad4 --- /dev/null +++ b/interface-definitions/interfaces_bonding.xml.in @@ -0,0 +1,297 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="bonding" owner="${vyos_conf_scripts_dir}/interfaces_bonding.py"> + <properties> + <help>Bonding Interface/Link Aggregation</help> + <priority>320</priority> + <constraint> + <regex>bond[0-9]+</regex> + </constraint> + <constraintErrorMessage>Bonding interface must be named bondN</constraintErrorMessage> + <valueHelp> + <format>bondN</format> + <description>Bonding interface name</description> + </valueHelp> + </properties> + <children> + #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> + <node name="arp-monitor"> + <properties> + <help>ARP link monitoring parameters</help> + </properties> + <children> + <leafNode name="interval"> + <properties> + <help>ARP link monitoring interval</help> + <valueHelp> + <format>u32</format> + <description>Specifies the ARP link monitoring frequency in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="target"> + <properties> + <help>IP address used for ARP monitoring</help> + <valueHelp> + <format>ipv4</format> + <description>Specify IPv4 address of ARP requests when interval is enabled</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + #include <include/generic-description.xml.i> + #include <include/interface/dhcp-options.xml.i> + #include <include/interface/dhcpv6-options.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/vrf.xml.i> + #include <include/interface/mirror.xml.i> + #include <include/interface/eapol.xml.i> + <node name="evpn"> + <properties> + <help>EVPN Multihoming</help> + </properties> + <children> + <leafNode name="es-df-pref"> + <properties> + <help>Preference value used for designated forwarder (DF) election</help> + <valueHelp> + <format>u32:1-65535</format> + <description>DF Preference value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="es-id"> + <properties> + <help>Ethernet segment identifier</help> + <valueHelp> + <format>u32:1-16777215</format> + <description>Local discriminator</description> + </valueHelp> + <valueHelp> + <format>txt</format> + <description>10-byte ID - 00:11:22:33:44:55:AA:BB:CC:DD</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + <regex>([0-9A-Fa-f][0-9A-Fa-f]:){9}[0-9A-Fa-f][0-9A-Fa-f]</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="es-sys-mac"> + <properties> + <help>Ethernet segment system MAC</help> + <valueHelp> + <format>macaddr</format> + <description>MAC address</description> + </valueHelp> + <constraint> + <validator name="mac-address"/> + </constraint> + </properties> + </leafNode> + #include <include/interface/evpn-mh-uplink.xml.i> + </children> + </node> + <leafNode name="hash-policy"> + <properties> + <help>Bonding transmit hash policy</help> + <completionHelp> + <list>layer2 layer2+3 layer3+4 encap2+3 encap3+4</list> + </completionHelp> + <valueHelp> + <format>layer2</format> + <description>use MAC addresses to generate the hash</description> + </valueHelp> + <valueHelp> + <format>layer2+3</format> + <description>combine MAC address and IP address to make hash</description> + </valueHelp> + <valueHelp> + <format>layer3+4</format> + <description>combine IP address and port to make hash</description> + </valueHelp> + <valueHelp> + <format>encap2+3</format> + <description>combine encapsulated MAC address and IP address to make hash</description> + </valueHelp> + <valueHelp> + <format>encap3+4</format> + <description>combine encapsulated IP address and port to make hash</description> + </valueHelp> + <constraint> + <regex>(layer2\+3|layer3\+4|layer2|encap2\+3|encap3\+4)</regex> + </constraint> + <constraintErrorMessage>hash-policy must be layer2 layer2+3 layer3+4 encap2+3 or encap3+4</constraintErrorMessage> + </properties> + <defaultValue>layer2</defaultValue> + </leafNode> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mac.xml.i> + <leafNode name="mii-mon-interval"> + <properties> + <help>Specifies the MII link monitoring frequency in milliseconds</help> + <valueHelp> + <format>u32:0</format> + <description>Disable MII link monitoring</description> + </valueHelp> + <valueHelp> + <format>u32:50-1000</format> + <description>MII link monitoring frequency in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-0 --range 50-1000"/> + </constraint> + </properties> + <defaultValue>100</defaultValue> + </leafNode> + <leafNode name="min-links"> + <properties> + <help>Minimum number of member interfaces required up before enabling bond</help> + <valueHelp> + <format>u32:0-16</format> + <description>Minimum number of member interfaces required up before enabling bond</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="system-mac"> + <properties> + <help>System MAC address for 802.3ad</help> + <valueHelp> + <format>macaddr</format> + <description>MAC address</description> + </valueHelp> + <constraint> + <validator name="mac-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="lacp-rate"> + <properties> + <help>Rate in which we will ask our link partner to transmit LACPDU packets</help> + <completionHelp> + <list>slow fast</list> + </completionHelp> + <valueHelp> + <format>slow</format> + <description>Request partner to transmit LACPDUs every 30 seconds</description> + </valueHelp> + <valueHelp> + <format>fast</format> + <description>Request partner to transmit LACPDUs every 1 second</description> + </valueHelp> + <constraint> + <regex>(slow|fast)</regex> + </constraint> + </properties> + <defaultValue>slow</defaultValue> + </leafNode> + <leafNode name="mode"> + <properties> + <help>Bonding mode</help> + <completionHelp> + <list>802.3ad active-backup broadcast round-robin transmit-load-balance adaptive-load-balance xor-hash</list> + </completionHelp> + <valueHelp> + <format>802.3ad</format> + <description>IEEE 802.3ad Dynamic link aggregation</description> + </valueHelp> + <valueHelp> + <format>active-backup</format> + <description>Fault tolerant: only one slave in the bond is active</description> + </valueHelp> + <valueHelp> + <format>broadcast</format> + <description>Fault tolerant: transmits everything on all slave interfaces</description> + </valueHelp> + <valueHelp> + <format>round-robin</format> + <description>Load balance: transmit packets in sequential order</description> + </valueHelp> + <valueHelp> + <format>transmit-load-balance</format> + <description>Load balance: adapts based on transmit load and speed</description> + </valueHelp> + <valueHelp> + <format>adaptive-load-balance</format> + <description>Load balance: adapts based on transmit and receive plus ARP</description> + </valueHelp> + <valueHelp> + <format>xor-hash</format> + <description>Distribute based on MAC address</description> + </valueHelp> + <constraint> + <regex>(802.3ad|active-backup|broadcast|round-robin|transmit-load-balance|adaptive-load-balance|xor-hash)</regex> + </constraint> + <constraintErrorMessage>mode must be 802.3ad, active-backup, broadcast, round-robin, transmit-load-balance, adaptive-load-balance, or xor</constraintErrorMessage> + </properties> + <defaultValue>802.3ad</defaultValue> + </leafNode> + <node name="member"> + <properties> + <help>Bridge member interfaces</help> + </properties> + <children> + <leafNode name="interface"> + <properties> + <help>Member interface name</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --bondable</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + #include <include/interface/mtu-68-16000.xml.i> + <leafNode name="mtu"> + <defaultValue>1500</defaultValue> + </leafNode> + <leafNode name="primary"> + <properties> + <help>Primary device interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --bondable</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + </leafNode> + #include <include/interface/redirect.xml.i> + #include <include/interface/vif-s.xml.i> + #include <include/interface/vif.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_bridge.xml.in b/interface-definitions/interfaces_bridge.xml.in new file mode 100644 index 0000000..29dd61d --- /dev/null +++ b/interface-definitions/interfaces_bridge.xml.in @@ -0,0 +1,233 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="bridge" owner="${vyos_conf_scripts_dir}/interfaces_bridge.py"> + <properties> + <help>Bridge Interface</help> + <priority>310</priority> + <constraint> + <regex>br[0-9]+</regex> + </constraint> + <constraintErrorMessage>Bridge interface must be named brN</constraintErrorMessage> + <valueHelp> + <format>brN</format> + <description>Bridge interface name</description> + </valueHelp> + </properties> + <children> + #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> + <leafNode name="aging"> + <properties> + <help>MAC address aging interval</help> + <valueHelp> + <format>u32:0</format> + <description>Disable MAC address learning (always flood)</description> + </valueHelp> + <valueHelp> + <format>u32:10-1000000</format> + <description>MAC address aging time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-0 --range 10-1000000"/> + </constraint> + </properties> + <defaultValue>300</defaultValue> + </leafNode> + #include <include/generic-description.xml.i> + #include <include/interface/dhcp-options.xml.i> + #include <include/interface/dhcpv6-options.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/vrf.xml.i> + #include <include/interface/mtu-68-16000.xml.i> + <leafNode name="mtu"> + <defaultValue>1500</defaultValue> + </leafNode> + <leafNode name="forwarding-delay"> + <properties> + <help>Forwarding delay</help> + <valueHelp> + <format>u32:0-200</format> + <description>Spanning Tree Protocol forwarding delay in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-200"/> + </constraint> + <constraintErrorMessage>Forwarding delay must be between 0 and 200 seconds</constraintErrorMessage> + </properties> + <defaultValue>14</defaultValue> + </leafNode> + <leafNode name="hello-time"> + <properties> + <help>Hello packet advertisement interval</help> + <valueHelp> + <format>u32:1-10</format> + <description>Spanning Tree Protocol hello advertisement interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-10"/> + </constraint> + <constraintErrorMessage>Bridge Hello interval must be between 1 and 10 seconds</constraintErrorMessage> + </properties> + <defaultValue>2</defaultValue> + </leafNode> + <node name="igmp"> + <properties> + <help>Internet Group Management Protocol (IGMP) and Multicast Listener Discovery (MLD) settings</help> + </properties> + <children> + <leafNode name="querier"> + <properties> + <help>Enable IGMP/MLD querier</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="snooping"> + <properties> + <help>Enable IGMP/MLD snooping</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/mirror.xml.i> + <leafNode name="enable-vlan"> + <properties> + <help>Enable VLAN aware bridge</help> + <valueless/> + </properties> + </leafNode> + #include <include/interface/vlan-protocol.xml.i> + <leafNode name="protocol"> + <defaultValue>802.1q</defaultValue> + </leafNode> + <leafNode name="max-age"> + <properties> + <help>Interval at which neighbor bridges are removed</help> + <valueHelp> + <format>u32:1-40</format> + <description>Bridge maximum aging time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-40"/> + </constraint> + <constraintErrorMessage>Bridge max aging value must be between 1 and 40 seconds</constraintErrorMessage> + </properties> + <defaultValue>20</defaultValue> + </leafNode> + <node name="member"> + <properties> + <help>Bridge member interfaces</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Member interface name</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --bridgeable</script> + </completionHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + <leafNode name="native-vlan"> + <properties> + <help>Specify VLAN id which should natively be present on the link</help> + <valueHelp> + <format>u32:1-4094</format> + <description>Virtual Local Area Network (VLAN) ID</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4094"/> + </constraint> + <constraintErrorMessage>VLAN ID must be between 1 and 4094</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="allowed-vlan"> + <properties> + <help>Specify VLAN id which is allowed in this trunk interface</help> + <valueHelp> + <format><id></format> + <description>VLAN id allowed to pass this interface</description> + </valueHelp> + <valueHelp> + <format><idN>-<idM></format> + <description>VLAN id range allowed on this interface (use '-' as delimiter)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 1-4094"/> + </constraint> + <constraintErrorMessage>not a valid VLAN ID value or range</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + <leafNode name="cost"> + <properties> + <help>Bridge port cost</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Path cost value for Spanning Tree Protocol</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + <constraintErrorMessage>Path cost value must be between 1 and 65535</constraintErrorMessage> + </properties> + <defaultValue>100</defaultValue> + </leafNode> + <leafNode name="priority"> + <properties> + <help>Bridge port priority</help> + <valueHelp> + <format>u32:0-63</format> + <description>Bridge port priority</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-63"/> + </constraint> + <constraintErrorMessage>Port priority value must be between 0 and 63</constraintErrorMessage> + </properties> + <defaultValue>32</defaultValue> + </leafNode> + <leafNode name="isolated"> + <properties> + <help>Port is isolated (also known as Private-VLAN)</help> + <valueless/> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + <leafNode name="priority"> + <properties> + <help>Priority for this bridge</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Bridge priority</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + <constraintErrorMessage>Bridge priority must be between 0 and 65535 (multiples of 4096)</constraintErrorMessage> + </properties> + <defaultValue>32768</defaultValue> + </leafNode> + <leafNode name="stp"> + <properties> + <help>Enable spanning tree protocol</help> + <valueless/> + </properties> + </leafNode> + #include <include/interface/redirect.xml.i> + #include <include/interface/vif.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_dummy.xml.in b/interface-definitions/interfaces_dummy.xml.in new file mode 100644 index 0000000..36b4e41 --- /dev/null +++ b/interface-definitions/interfaces_dummy.xml.in @@ -0,0 +1,60 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="dummy" owner="${vyos_conf_scripts_dir}/interfaces_dummy.py"> + <properties> + <help>Dummy Interface</help> + <priority>300</priority> + <constraint> + <regex>dum[0-9]+</regex> + </constraint> + <constraintErrorMessage>Dummy interface must be named dumN</constraintErrorMessage> + <valueHelp> + <format>dumN</format> + <description>Dummy interface name</description> + </valueHelp> + </properties> + <children> + #include <include/interface/address-ipv4-ipv6.xml.i> + #include <include/generic-description.xml.i> + #include <include/interface/disable.xml.i> + <node name="ip"> + <properties> + <help>IPv4 routing parameters</help> + </properties> + <children> + #include <include/interface/source-validation.xml.i> + #include <include/interface/disable-forwarding.xml.i> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 routing parameters</help> + </properties> + <children> + #include <include/interface/disable-forwarding.xml.i> + <node name="address"> + <properties> + <help>IPv6 address configuration modes</help> + </properties> + <children> + #include <include/interface/ipv6-address-eui64.xml.i> + #include <include/interface/ipv6-address-no-default-link-local.xml.i> + </children> + </node> + </children> + </node> + #include <include/interface/mtu-68-16000.xml.i> + <leafNode name="mtu"> + <defaultValue>1500</defaultValue> + </leafNode> + #include <include/interface/mirror.xml.i> + #include <include/interface/netns.xml.i> + #include <include/interface/redirect.xml.i> + #include <include/interface/vrf.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_ethernet.xml.in b/interface-definitions/interfaces_ethernet.xml.in new file mode 100644 index 0000000..89f990d --- /dev/null +++ b/interface-definitions/interfaces_ethernet.xml.in @@ -0,0 +1,225 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <properties> + <help>Network interfaces</help> + </properties> + <children> + <tagNode name="ethernet" owner="${vyos_conf_scripts_dir}/interfaces_ethernet.py"> + <properties> + <help>Ethernet Interface</help> + <priority>318</priority> + <valueHelp> + <format>ethN</format> + <description>Ethernet interface name</description> + </valueHelp> + <constraint> + <regex>((eth|lan)[0-9]+|(eno|ens|enp|enx).+)</regex> + </constraint> + <constraintErrorMessage>Invalid Ethernet interface name</constraintErrorMessage> + </properties> + <children> + #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> + #include <include/generic-description.xml.i> + #include <include/interface/dhcp-options.xml.i> + #include <include/interface/dhcpv6-options.xml.i> + <leafNode name="disable-flow-control"> + <properties> + <help>Disable Ethernet flow control (pause frames)</help> + <valueless/> + </properties> + </leafNode> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/disable.xml.i> + <leafNode name="duplex"> + <properties> + <help>Duplex mode</help> + <completionHelp> + <list>auto half full</list> + </completionHelp> + <valueHelp> + <format>auto</format> + <description>Auto negotiation</description> + </valueHelp> + <valueHelp> + <format>half</format> + <description>Half duplex</description> + </valueHelp> + <valueHelp> + <format>full</format> + <description>Full duplex</description> + </valueHelp> + <constraint> + <regex>(auto|half|full)</regex> + </constraint> + <constraintErrorMessage>duplex must be auto, half or full</constraintErrorMessage> + </properties> + <defaultValue>auto</defaultValue> + </leafNode> + #include <include/interface/eapol.xml.i> + <node name="evpn"> + <properties> + <help>EVPN Multihoming</help> + </properties> + <children> + #include <include/interface/evpn-mh-uplink.xml.i> + </children> + </node> + #include <include/interface/hw-id.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/mtu-68-16000.xml.i> + #include <include/interface/mirror.xml.i> + <node name="offload"> + <properties> + <help>Configurable offload options</help> + </properties> + <children> + <leafNode name="gro"> + <properties> + <help>Enable Generic Receive Offload</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="gso"> + <properties> + <help>Enable Generic Segmentation Offload</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="hw-tc-offload"> + <properties> + <help>Enable Hardware Flow Offload</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="lro"> + <properties> + <help>Enable Large Receive Offload</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="rps"> + <properties> + <help>Enable Receive Packet Steering</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="rfs"> + <properties> + <help>Enable Receive Flow Steering</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="sg"> + <properties> + <help>Enable Scatter-Gather</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="tso"> + <properties> + <help>Enable TCP Segmentation Offloading</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="speed"> + <properties> + <help>Link speed</help> + <completionHelp> + <list>auto 10 100 1000 2500 5000 10000 25000 40000 50000 100000</list> + </completionHelp> + <valueHelp> + <format>auto</format> + <description>Auto negotiation</description> + </valueHelp> + <valueHelp> + <format>10</format> + <description>10 Mbit/sec</description> + </valueHelp> + <valueHelp> + <format>100</format> + <description>100 Mbit/sec</description> + </valueHelp> + <valueHelp> + <format>1000</format> + <description>1 Gbit/sec</description> + </valueHelp> + <valueHelp> + <format>2500</format> + <description>2.5 Gbit/sec</description> + </valueHelp> + <valueHelp> + <format>5000</format> + <description>5 Gbit/sec</description> + </valueHelp> + <valueHelp> + <format>10000</format> + <description>10 Gbit/sec</description> + </valueHelp> + <valueHelp> + <format>25000</format> + <description>25 Gbit/sec</description> + </valueHelp> + <valueHelp> + <format>40000</format> + <description>40 Gbit/sec</description> + </valueHelp> + <valueHelp> + <format>50000</format> + <description>50 Gbit/sec</description> + </valueHelp> + <valueHelp> + <format>100000</format> + <description>100 Gbit/sec</description> + </valueHelp> + <constraint> + <regex>(auto|10|100|1000|2500|5000|10000|25000|40000|50000|100000)</regex> + </constraint> + <constraintErrorMessage>Speed must be auto, 10, 100, 1000, 2500, 5000, 10000, 25000, 40000, 50000 or 100000</constraintErrorMessage> + </properties> + <defaultValue>auto</defaultValue> + </leafNode> + <node name="ring-buffer"> + <properties> + <help>Shared buffer between the device driver and NIC</help> + </properties> + <children> + <leafNode name="rx"> + <properties> + <help>RX ring buffer</help> + <valueHelp> + <format>u32:80-16384</format> + <description>ring buffer size</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 80-16384"/> + </constraint> + </properties> + </leafNode> + <leafNode name="tx"> + <properties> + <help>TX ring buffer</help> + <valueHelp> + <format>u32:80-16384</format> + <description>ring buffer size</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 80-16384"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + #include <include/interface/redirect.xml.i> + #include <include/interface/vif-s.xml.i> + #include <include/interface/vif.xml.i> + #include <include/interface/vrf.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_geneve.xml.in b/interface-definitions/interfaces_geneve.xml.in new file mode 100644 index 0000000..990c5bd --- /dev/null +++ b/interface-definitions/interfaces_geneve.xml.in @@ -0,0 +1,61 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="geneve" owner="${vyos_conf_scripts_dir}/interfaces_geneve.py"> + <properties> + <help>Generic Network Virtualization Encapsulation (GENEVE) Interface</help> + <priority>460</priority> + <constraint> + <regex>gnv[0-9]+</regex> + </constraint> + <constraintErrorMessage>GENEVE interface must be named gnvN</constraintErrorMessage> + <valueHelp> + <format>gnvN</format> + <description>GENEVE interface name</description> + </valueHelp> + </properties> + <children> + #include <include/interface/address-ipv4-ipv6.xml.i> + #include <include/generic-description.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/mtu-1200-16000.xml.i> + <node name="parameters"> + <properties> + <help>GENEVE tunnel parameters</help> + </properties> + <children> + <node name="ip"> + <properties> + <help>IPv4 specific tunnel parameters</help> + </properties> + <children> + #include <include/interface/parameters-df.xml.i> + #include <include/interface/parameters-tos.xml.i> + #include <include/interface/parameters-ttl.xml.i> + #include <include/interface/parameters-innerproto.xml.i> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 specific tunnel parameters</help> + </properties> + <children> + #include <include/interface/parameters-flowlabel.xml.i> + </children> + </node> + </children> + </node> + #include <include/interface/mirror.xml.i> + #include <include/interface/redirect.xml.i> + #include <include/interface/tunnel-remote.xml.i> + #include <include/interface/vrf.xml.i> + #include <include/vni.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_input.xml.in b/interface-definitions/interfaces_input.xml.in new file mode 100644 index 0000000..771c47e --- /dev/null +++ b/interface-definitions/interfaces_input.xml.in @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="input" owner="${vyos_conf_scripts_dir}/interfaces_input.py"> + <properties> + <help>Input Functional Block (IFB) interface name</help> + <!-- before real devices that redirect --> + <priority>310</priority> + <constraint> + <regex>ifb[0-9]+</regex> + </constraint> + <constraintErrorMessage>Input interface must be named ifbN</constraintErrorMessage> + <valueHelp> + <format>ifbN</format> + <description>Input interface name</description> + </valueHelp> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/redirect.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_l2tpv3.xml.in b/interface-definitions/interfaces_l2tpv3.xml.in new file mode 100644 index 0000000..5f816c9 --- /dev/null +++ b/interface-definitions/interfaces_l2tpv3.xml.in @@ -0,0 +1,131 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="l2tpv3" owner="${vyos_conf_scripts_dir}/interfaces_l2tpv3.py"> + <properties> + <help>Layer 2 Tunnel Protocol Version 3 (L2TPv3) Interface</help> + <priority>485</priority> + <constraint> + <regex>l2tpeth[0-9]+</regex> + </constraint> + <constraintErrorMessage>L2TPv3 interface must be named l2tpethN</constraintErrorMessage> + <valueHelp> + <format>l2tpethN</format> + <description>L2TPv3 interface name</description> + </valueHelp> + </properties> + <children> + #include <include/interface/address-ipv4-ipv6.xml.i> + #include <include/generic-description.xml.i> + <leafNode name="destination-port"> + <properties> + <help>UDP destination port for L2TPv3 tunnel</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>5000</defaultValue> + </leafNode> + #include <include/interface/disable.xml.i> + <leafNode name="encapsulation"> + <properties> + <help>Encapsulation type</help> + <completionHelp> + <list>udp ip</list> + </completionHelp> + <valueHelp> + <format>udp</format> + <description>UDP encapsulation</description> + </valueHelp> + <valueHelp> + <format>ip</format> + <description>IP encapsulation</description> + </valueHelp> + <constraint> + <regex>(udp|ip)</regex> + </constraint> + <constraintErrorMessage>Encapsulation must be UDP or IP</constraintErrorMessage> + </properties> + <defaultValue>udp</defaultValue> + </leafNode> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/source-address-ipv4-ipv6.xml.i> + #include <include/interface/mirror.xml.i> + #include <include/interface/mtu-68-16000.xml.i> + <leafNode name="mtu"> + <defaultValue>1488</defaultValue> + </leafNode> + <leafNode name="peer-session-id"> + <properties> + <help>Peer session identifier</help> + <valueHelp> + <format>u32:1-429496729</format> + <description>L2TPv3 peer session identifier</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-429496729"/> + </constraint> + </properties> + </leafNode> + <leafNode name="peer-tunnel-id"> + <properties> + <help>Peer tunnel identifier</help> + <valueHelp> + <format>u32:1-429496729</format> + <description>L2TPv3 peer tunnel identifier</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-429496729"/> + </constraint> + </properties> + </leafNode> + #include <include/interface/tunnel-remote.xml.i> + <leafNode name="session-id"> + <properties> + <help>Session identifier</help> + <valueHelp> + <format>u32:1-429496729</format> + <description>L2TPv3 session identifier</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-429496729"/> + </constraint> + </properties> + </leafNode> + <leafNode name="source-port"> + <properties> + <help>UDP source port for L2TPv3 tunnel</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>5000</defaultValue> + </leafNode> + <leafNode name="tunnel-id"> + <properties> + <help>Local tunnel identifier</help> + <valueHelp> + <format>u32:1-429496729</format> + <description>L2TPv3 local tunnel identifier</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-429496729"/> + </constraint> + </properties> + </leafNode> + #include <include/interface/vrf.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_loopback.xml.in b/interface-definitions/interfaces_loopback.xml.in new file mode 100644 index 0000000..09b4a00 --- /dev/null +++ b/interface-definitions/interfaces_loopback.xml.in @@ -0,0 +1,35 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="loopback" owner="${vyos_conf_scripts_dir}/interfaces_loopback.py"> + <properties> + <help>Loopback Interface</help> + <priority>300</priority> + <constraint> + <regex>lo</regex> + </constraint> + <constraintErrorMessage>Loopback interface must be named lo</constraintErrorMessage> + <valueHelp> + <format>lo</format> + <description>Loopback interface</description> + </valueHelp> + </properties> + <children> + #include <include/interface/address-ipv4-ipv6.xml.i> + #include <include/generic-description.xml.i> + <node name="ip"> + <properties> + <help>IPv4 routing parameters</help> + </properties> + <children> + #include <include/interface/source-validation.xml.i> + </children> + </node> + #include <include/interface/mirror.xml.i> + #include <include/interface/redirect.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_macsec.xml.in b/interface-definitions/interfaces_macsec.xml.in new file mode 100644 index 0000000..d825f82 --- /dev/null +++ b/interface-definitions/interfaces_macsec.xml.in @@ -0,0 +1,153 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="macsec" owner="${vyos_conf_scripts_dir}/interfaces_macsec.py"> + <properties> + <help>MACsec Interface (802.1ae)</help> + <priority>461</priority> + <constraint> + <regex>macsec[0-9]+</regex> + </constraint> + <constraintErrorMessage>MACsec interface must be named macsecN</constraintErrorMessage> + <valueHelp> + <format>macsecN</format> + <description>MACsec interface name</description> + </valueHelp> + </properties> + <children> + #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> + #include <include/interface/dhcp-options.xml.i> + #include <include/interface/dhcpv6-options.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mirror.xml.i> + <node name="security"> + <properties> + <help>Security/Encryption Settings</help> + </properties> + <children> + <leafNode name="cipher"> + <properties> + <help>Cipher suite used</help> + <completionHelp> + <list>gcm-aes-128 gcm-aes-256</list> + </completionHelp> + <valueHelp> + <format>gcm-aes-128</format> + <description>Galois/Counter Mode of AES cipher with 128-bit key</description> + </valueHelp> + <valueHelp> + <format>gcm-aes-256</format> + <description>Galois/Counter Mode of AES cipher with 256-bit key</description> + </valueHelp> + <constraint> + <regex>(gcm-aes-128|gcm-aes-256)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="encrypt"> + <properties> + <help>Enable optional MACsec encryption</help> + <valueless/> + </properties> + </leafNode> + <node name="static"> + <properties> + <help>Use static keys for MACsec [static Secure Authentication Key (SAK) mode]</help> + </properties> + <children> + #include <include/interface/macsec-key.xml.i> + <tagNode name="peer"> + <properties> + <help>MACsec peer name</help> + <constraint> + <regex>[^ ]{1,100}</regex> + </constraint> + <constraintErrorMessage>MACsec peer name exceeds limit of 100 characters</constraintErrorMessage> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/macsec-key.xml.i> + </children> + </tagNode> + </children> + </node> + <node name="mka"> + <properties> + <help>MACsec Key Agreement protocol (MKA)</help> + </properties> + <children> + <leafNode name="cak"> + <properties> + <help>Secure Connectivity Association Key</help> + <valueHelp> + <format>txt</format> + <description>16-byte (128-bit) hex-string (32 hex-digits) for gcm-aes-128 or 32-byte (256-bit) hex-string (64 hex-digits) for gcm-aes-256</description> + </valueHelp> + <constraint> + <regex>[A-Fa-f0-9]{32}</regex> + <regex>[A-Fa-f0-9]{64}</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="ckn"> + <properties> + <help>Secure Connectivity Association Key Name</help> + <valueHelp> + <format>txt</format> + <description>1..32-bytes (8..256 bit) hex-string (2..64 hex-digits)</description> + </valueHelp> + <constraint> + <regex>[A-Fa-f0-9]{2,64}</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="priority"> + <properties> + <help>Priority of MACsec Key Agreement protocol (MKA) actor</help> + <valueHelp> + <format>u32:0-255</format> + <description>MACsec Key Agreement protocol (MKA) priority</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255" /> + </constraint> + </properties> + <defaultValue>255</defaultValue> + </leafNode> + </children> + </node> + <leafNode name="replay-window"> + <properties> + <help>IEEE 802.1X/MACsec replay protection window</help> + <valueHelp> + <format>u32:0</format> + <description>No replay window, strict check</description> + </valueHelp> + <valueHelp> + <format>u32:1-4294967295</format> + <description>Number of packets that could be misordered</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295" /> + </constraint> + </properties> + </leafNode> + </children> + </node> + #include <include/generic-description.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/mtu-68-16000.xml.i> + <leafNode name="mtu"> + <defaultValue>1460</defaultValue> + </leafNode> + #include <include/source-interface-ethernet.xml.i> + #include <include/interface/redirect.xml.i> + #include <include/interface/vrf.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_openvpn.xml.in b/interface-definitions/interfaces_openvpn.xml.in new file mode 100644 index 0000000..3c84410 --- /dev/null +++ b/interface-definitions/interfaces_openvpn.xml.in @@ -0,0 +1,860 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="openvpn" owner="${vyos_conf_scripts_dir}/interfaces_openvpn.py"> + <properties> + <help>OpenVPN Tunnel Interface</help> + <priority>460</priority> + <constraint> + <regex>vtun[0-9]+</regex> + </constraint> + <constraintErrorMessage>OpenVPN tunnel interface must be named vtunN</constraintErrorMessage> + <valueHelp> + <format>vtunN</format> + <description>OpenVPN interface name</description> + </valueHelp> + </properties> + <children> + #include <include/interface/authentication.xml.i> + #include <include/generic-description.xml.i> + <leafNode name="device-type"> + <properties> + <help>OpenVPN interface device-type</help> + <completionHelp> + <list>tun tap</list> + </completionHelp> + <valueHelp> + <format>tun</format> + <description>TUN device, required for OSI layer 3</description> + </valueHelp> + <valueHelp> + <format>tap</format> + <description>TAP device, required for OSI layer 2</description> + </valueHelp> + <constraint> + <regex>(tun|tap)</regex> + </constraint> + </properties> + <defaultValue>tun</defaultValue> + </leafNode> + #include <include/interface/disable.xml.i> + <node name="encryption"> + <properties> + <help>Data Encryption settings</help> + </properties> + <children> + <leafNode name="cipher"> + <properties> + <help>Standard Data Encryption Algorithm</help> + <completionHelp> + <list>none 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm</list> + </completionHelp> + <valueHelp> + <format>none</format> + <description>Disable encryption</description> + </valueHelp> + <valueHelp> + <format>3des</format> + <description>DES algorithm with triple encryption</description> + </valueHelp> + <valueHelp> + <format>aes128</format> + <description>AES algorithm with 128-bit key CBC</description> + </valueHelp> + <valueHelp> + <format>aes128gcm</format> + <description>AES algorithm with 128-bit key GCM</description> + </valueHelp> + <valueHelp> + <format>aes192</format> + <description>AES algorithm with 192-bit key CBC</description> + </valueHelp> + <valueHelp> + <format>aes192gcm</format> + <description>AES algorithm with 192-bit key GCM</description> + </valueHelp> + <valueHelp> + <format>aes256</format> + <description>AES algorithm with 256-bit key CBC</description> + </valueHelp> + <valueHelp> + <format>aes256gcm</format> + <description>AES algorithm with 256-bit key GCM</description> + </valueHelp> + <constraint> + <regex>(none|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="data-ciphers"> + <properties> + <help>Cipher negotiation list for use in server or client mode</help> + <completionHelp> + <list>none 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm</list> + </completionHelp> + <valueHelp> + <format>none</format> + <description>Disable encryption</description> + </valueHelp> + <valueHelp> + <format>3des</format> + <description>DES algorithm with triple encryption</description> + </valueHelp> + <valueHelp> + <format>aes128</format> + <description>AES algorithm with 128-bit key CBC</description> + </valueHelp> + <valueHelp> + <format>aes128gcm</format> + <description>AES algorithm with 128-bit key GCM</description> + </valueHelp> + <valueHelp> + <format>aes192</format> + <description>AES algorithm with 192-bit key CBC</description> + </valueHelp> + <valueHelp> + <format>aes192gcm</format> + <description>AES algorithm with 192-bit key GCM</description> + </valueHelp> + <valueHelp> + <format>aes256</format> + <description>AES algorithm with 256-bit key CBC</description> + </valueHelp> + <valueHelp> + <format>aes256gcm</format> + <description>AES algorithm with 256-bit key GCM</description> + </valueHelp> + <constraint> + <regex>(none|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mirror.xml.i> + <leafNode name="hash"> + <properties> + <help>Hashing Algorithm</help> + <completionHelp> + <list>md5 sha1 sha256 sha384 sha512</list> + </completionHelp> + <valueHelp> + <format>md5</format> + <description>MD5 algorithm</description> + </valueHelp> + <valueHelp> + <format>sha1</format> + <description>SHA-1 algorithm</description> + </valueHelp> + <valueHelp> + <format>sha256</format> + <description>SHA-256 algorithm</description> + </valueHelp> + <valueHelp> + <format>sha384</format> + <description>SHA-384 algorithm</description> + </valueHelp> + <valueHelp> + <format>sha512</format> + <description>SHA-512 algorithm</description> + </valueHelp> + <constraint> + <regex>(md5|sha1|sha256|sha384|sha512)</regex> + </constraint> + </properties> + </leafNode> + <node name="keep-alive"> + <properties> + <help>Keepalive helper options</help> + </properties> + <children> + <leafNode name="failure-count"> + <properties> + <help>Maximum number of keepalive packet failures</help> + <valueHelp> + <format>u32:0-1000</format> + <description>Maximum number of keepalive packet failures</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-1000"/> + </constraint> + </properties> + <defaultValue>60</defaultValue> + </leafNode> + <leafNode name="interval"> + <properties> + <help>Keepalive packet interval in seconds</help> + <valueHelp> + <format>u32:0-600</format> + <description>Keepalive packet interval (seconds)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-600"/> + </constraint> + </properties> + <defaultValue>10</defaultValue> + </leafNode> + </children> + </node> + <tagNode name="local-address"> + <properties> + <help>Local IP address of tunnel (IPv4 or IPv6)</help> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + <leafNode name="subnet-mask"> + <properties> + <help>Subnet-mask for local IP address of tunnel (IPv4 only)</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="local-host"> + <properties> + <help>Local IP address to accept connections (all if not set)</help> + <valueHelp> + <format>ipv4</format> + <description>Local IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Local IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="local-port"> + <properties> + <help>Local port number to accept connections</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="mode"> + <properties> + <help>OpenVPN mode of operation</help> + <completionHelp> + <list>site-to-site client server</list> + </completionHelp> + <valueHelp> + <format>site-to-site</format> + <description>Site-to-site mode</description> + </valueHelp> + <valueHelp> + <format>client</format> + <description>Client in client-server mode</description> + </valueHelp> + <valueHelp> + <format>server</format> + <description>Server in client-server mode</description> + </valueHelp> + <constraint> + <regex>(site-to-site|client|server)</regex> + </constraint> + </properties> + </leafNode> + <node name="offload"> + <properties> + <help>Configurable offload options</help> + </properties> + <children> + <leafNode name="dco"> + <properties> + <help>Enable data channel offload on this interface</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="openvpn-option"> + <properties> + <help>Additional OpenVPN options. You must use the syntax of openvpn.conf in this text-field. Using this without proper knowledge may result in a crashed OpenVPN server. Check system log to look for errors.</help> + <multi/> + </properties> + </leafNode> + <leafNode name="persistent-tunnel"> + <properties> + <help>Do not close and reopen interface (TUN/TAP device) on client restarts</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="protocol"> + <properties> + <help>OpenVPN communication protocol</help> + <completionHelp> + <list>udp tcp-passive tcp-active</list> + </completionHelp> + <valueHelp> + <format>udp</format> + <description>UDP</description> + </valueHelp> + <valueHelp> + <format>tcp-passive</format> + <description>TCP and accepts connections passively</description> + </valueHelp> + <valueHelp> + <format>tcp-active</format> + <description>TCP and initiates connections actively</description> + </valueHelp> + <constraint> + <regex>(udp|tcp-passive|tcp-active)</regex> + </constraint> + </properties> + <defaultValue>udp</defaultValue> + </leafNode> + <leafNode name="ip-version"> + <properties> + <help>Force OpenVPN to use a specific IP protocol version</help> + <completionHelp> + <list>auto ipv4 ipv6 dual-stack</list> + </completionHelp> + <valueHelp> + <format>auto</format> + <description>Select one IP protocol to use based on local or remote host</description> + </valueHelp> + <valueHelp> + <format>_ipv4</format> + <description>Accept connections on or initate connections to IPv4 addresses only</description> + </valueHelp> + <valueHelp> + <format>_ipv6</format> + <description>Accept connections on or initate connections to IPv6 addresses only</description> + </valueHelp> + <valueHelp> + <format>dual-stack</format> + <description>Accept connections on both protocols simultaneously (only supported in server mode)</description> + </valueHelp> + <constraint> + <regex>(auto|ipv4|ipv6|dual-stack)</regex> + </constraint> + </properties> + <defaultValue>auto</defaultValue> + </leafNode> + <leafNode name="remote-address"> + <properties> + <help>IP address of remote end of tunnel</help> + <valueHelp> + <format>ipv4</format> + <description>Remote end IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Remote end IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="remote-host"> + <properties> + <help>Remote host to connect to (dynamic if not set)</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address of remote host</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of remote host</description> + </valueHelp> + <valueHelp> + <format>txt</format> + <description>Hostname of remote host</description> + </valueHelp> + <multi/> + </properties> + </leafNode> + <leafNode name="remote-port"> + <properties> + <help>Remote port number to connect to</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <node name="replace-default-route"> + <properties> + <help>OpenVPN tunnel to be used as the default route</help> + </properties> + <children> + <leafNode name="local"> + <properties> + <help>Tunnel endpoints are on the same subnet</help> + </properties> + </leafNode> + </children> + </node> + <node name="server"> + <properties> + <help>Server-mode options</help> + </properties> + <children> + <tagNode name="client"> + <properties> + <help>Client-specific settings</help> + <valueHelp> + <format>name</format> + <description>Client common-name in the certificate</description> + </valueHelp> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <leafNode name="ip"> + <properties> + <help>IP address of the client</help> + <valueHelp> + <format>ipv4</format> + <description>Client IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Client IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="push-route"> + <properties> + <help>Route to be pushed to the client</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 network and prefix length</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 network and prefix length</description> + </valueHelp> + <constraint> + <validator name="ip-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="subnet"> + <properties> + <help>Subnet belonging to the client (iroute)</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 network and prefix length belonging to the client</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 network and prefix length belonging to the client</description> + </valueHelp> + <constraint> + <validator name="ip-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + <node name="bridge"> + <properties> + <help>Used with TAP device (layer 2)</help> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <leafNode name="start"> + <properties> + <help>First IP address in the pool</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="stop"> + <properties> + <help>Last IP address in the pool</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="subnet-mask"> + <properties> + <help>Subnet mask pushed to dynamic clients.</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <valueHelp> + <format>ipv4</format> + <description>IPv4 subnet mask</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="gateway"> + <properties> + <help>Gateway IP address</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + </properties> + </leafNode> + </children> + </node> + <node name="client-ip-pool"> + <properties> + <help>Pool of client IPv4 addresses</help> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <leafNode name="start"> + <properties> + <help>First IP address in the pool</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="stop"> + <properties> + <help>Last IP address in the pool</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="subnet-mask"> + <properties> + <help>Subnet mask pushed to dynamic clients. If not set the server subnet mask will be used. Only used with topology subnet or device type tap. Not used with bridged interfaces.</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <valueHelp> + <format>ipv4</format> + <description>IPv4 subnet mask</description> + </valueHelp> + </properties> + </leafNode> + </children> + </node> + <node name="client-ipv6-pool"> + <properties> + <help>Pool of client IPv6 addresses</help> + </properties> + <children> + <leafNode name="base"> + <properties> + <help>Client IPv6 pool base address with optional prefix length</help> + <valueHelp> + <format>ipv6net</format> + <description>Client IPv6 pool base address with optional prefix length (defaults: base = server subnet + 0x1000, prefix length = server prefix length)</description> + </valueHelp> + <constraint> + <validator name="ipv6"/> + </constraint> + </properties> + </leafNode> + #include <include/generic-disable-node.xml.i> + </children> + </node> + <leafNode name="domain-name"> + <properties> + <help>DNS suffix to be pushed to all clients</help> + <valueHelp> + <format>txt</format> + <description>Domain Name Server suffix</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="max-connections"> + <properties> + <help>Number of maximum client connections</help> + <valueHelp> + <format>u32:1-4096</format> + <description>Number of concurrent clients</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4096"/> + </constraint> + </properties> + </leafNode> + #include <include/name-server-ipv4-ipv6.xml.i> + <tagNode name="push-route"> + <properties> + <help>Route to be pushed to all clients</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 network and prefix length</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 network and prefix length</description> + </valueHelp> + <constraint> + <validator name="ip-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="metric"> + <properties> + <help>Set metric for this route</help> + <valueHelp> + <format>u32:0-4294967295</format> + <description>Metric for this route</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + </children> + </tagNode> + <leafNode name="reject-unconfigured-clients"> + <properties> + <help>Reject connections from clients that are not explicitly configured</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="subnet"> + <properties> + <help>Server-mode subnet (from which client IPs are allocated)</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 network and prefix length</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 network and prefix length</description> + </valueHelp> + <constraint> + <validator name="ip-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="topology"> + <properties> + <help>Topology for clients</help> + <completionHelp> + <list>subnet point-to-point net30</list> + </completionHelp> + <valueHelp> + <format>subnet</format> + <description>Subnet topology (recommended)</description> + </valueHelp> + <valueHelp> + <format>point-to-point</format> + <description>Point-to-point topology</description> + </valueHelp> + <valueHelp> + <format>net30</format> + <description>net30 topology (deprecated)</description> + </valueHelp> + <constraint> + <regex>(subnet|point-to-point|net30)</regex> + </constraint> + </properties> + <defaultValue>subnet</defaultValue> + </leafNode> + <node name="mfa"> + <properties> + <help>multi-factor authentication</help> + </properties> + <children> + <node name="totp"> + <properties> + <help>Time-based one-time passwords</help> + </properties> + <children> + <leafNode name="slop"> + <properties> + <help>Maximum allowed clock slop in seconds</help> + <valueHelp> + <format>1-65535</format> + <description>Seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>180</defaultValue> + </leafNode> + <leafNode name="drift"> + <properties> + <help>Time drift in seconds</help> + <valueHelp> + <format>1-65535</format> + <description>Seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="step"> + <properties> + <help>Step value for totp in seconds</help> + <valueHelp> + <format>1-65535</format> + <description>Seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>30</defaultValue> + </leafNode> + <leafNode name="digits"> + <properties> + <help>Number of digits to use for totp hash</help> + <valueHelp> + <format>1-65535</format> + <description>Digits</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>6</defaultValue> + </leafNode> + <leafNode name="challenge"> + <properties> + <help>Expect password as result of a challenge response protocol</help> + <completionHelp> + <list>disable enable</list> + </completionHelp> + <valueHelp> + <format>disable</format> + <description>Disable challenge-response</description> + </valueHelp> + <valueHelp> + <format>enable</format> + <description>Enable chalenge-response</description> + </valueHelp> + <constraint> + <regex>(disable|enable)</regex> + </constraint> + </properties> + <defaultValue>enable</defaultValue> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> + <leafNode name="shared-secret-key"> + <properties> + <help>Secret key shared with remote end of tunnel</help> + <completionHelp> + <path>pki openvpn shared-secret</path> + </completionHelp> + </properties> + </leafNode> + <node name="tls"> + <properties> + <help>Transport Layer Security (TLS) options</help> + </properties> + <children> + <leafNode name="auth-key"> + <properties> + <help>TLS shared secret key for tls-auth</help> + <completionHelp> + <path>pki openvpn shared-secret</path> + </completionHelp> + </properties> + </leafNode> + #include <include/pki/certificate.xml.i> + #include <include/pki/ca-certificate-multi.xml.i> + #include <include/pki/dh-params.xml.i> + <leafNode name="crypt-key"> + <properties> + <help>Static key to use to authenticate control channel</help> + <completionHelp> + <path>pki openvpn shared-secret</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="peer-fingerprint"> + <properties> + <multi/> + <help>Peer certificate SHA256 fingerprint</help> + <constraint> + <regex>[0-9a-fA-F]{2}:([0-9a-fA-F]{2}:){30}[0-9a-fA-F]{2}</regex> + </constraint> + <constraintErrorMessage>Peer certificate fingerprint must be a colon-separated SHA256 hex digest</constraintErrorMessage> + </properties> + </leafNode> + #include <include/tls-version-min.xml.i> + <leafNode name="role"> + <properties> + <help>TLS negotiation role</help> + <completionHelp> + <list>active passive</list> + </completionHelp> + <valueHelp> + <format>active</format> + <description>Initiate TLS negotiation actively</description> + </valueHelp> + <valueHelp> + <format>passive</format> + <description>Wait for incoming TLS connection</description> + </valueHelp> + <constraint> + <regex>(active|passive)</regex> + </constraint> + </properties> + </leafNode> + </children> + </node> + <leafNode name="use-lzo-compression"> + <properties> + <help>Use fast LZO compression on this TUN/TAP interface</help> + <valueless/> + </properties> + </leafNode> + #include <include/interface/redirect.xml.i> + #include <include/interface/vrf.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_pppoe.xml.in b/interface-definitions/interfaces_pppoe.xml.in new file mode 100644 index 0000000..56660bc --- /dev/null +++ b/interface-definitions/interfaces_pppoe.xml.in @@ -0,0 +1,153 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="pppoe" owner="${vyos_conf_scripts_dir}/interfaces_pppoe.py"> + <properties> + <help>Point-to-Point Protocol over Ethernet (PPPoE) Interface</help> + <priority>322</priority> + <constraint> + <regex>pppoe[0-9]+</regex> + </constraint> + <constraintErrorMessage>PPPoE interface must be named pppoeN</constraintErrorMessage> + <valueHelp> + <format>pppoeN</format> + <description>PPPoE dialer interface name</description> + </valueHelp> + </properties> + <children> + #include <include/pppoe-access-concentrator.xml.i> + #include <include/interface/authentication.xml.i> + #include <include/interface/dial-on-demand.xml.i> + #include <include/interface/no-default-route.xml.i> + #include <include/interface/default-route-distance.xml.i> + #include <include/interface/dhcpv6-options.xml.i> + #include <include/generic-description.xml.i> + #include <include/interface/disable.xml.i> + <leafNode name="idle-timeout"> + <properties> + <help>Delay before disconnecting idle session (in seconds)</help> + <valueHelp> + <format>u32:0-86400</format> + <description>Idle timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-86400"/> + </constraint> + <constraintErrorMessage>Timeout must be in range 0 to 86400</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="host-uniq"> + <properties> + <help>PPPoE RFC2516 host-uniq tag</help> + <valueHelp> + <format>txt</format> + <description>Host-uniq tag as byte string in HEX</description> + </valueHelp> + <constraint> + <regex>([a-fA-F0-9][a-fA-F0-9]){1,18}</regex> + </constraint> + <constraintErrorMessage>Host-uniq must be specified as hex-adecimal byte-string (even number of HEX characters)</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="holdoff"> + <properties> + <help>Delay before re-dial to the access concentrator when PPP session terminated by peer (in seconds)</help> + <valueHelp> + <format>u32:0-86400</format> + <description>Holdoff time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-86400"/> + </constraint> + <constraintErrorMessage>Holdoff must be in range 0 to 86400</constraintErrorMessage> + </properties> + <defaultValue>30</defaultValue> + </leafNode> + <node name="ip"> + <properties> + <help>IPv4 routing parameters</help> + </properties> + <children> + #include <include/interface/adjust-mss.xml.i> + #include <include/interface/disable-forwarding.xml.i> + #include <include/interface/source-validation.xml.i> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 routing parameters</help> + </properties> + <children> + <node name="address"> + <properties> + <help>IPv6 address configuration modes</help> + </properties> + <children> + #include <include/interface/ipv6-address-autoconf.xml.i> + </children> + </node> + #include <include/interface/adjust-mss.xml.i> + #include <include/interface/disable-forwarding.xml.i> + </children> + </node> + #include <include/source-interface.xml.i> + <leafNode name="local-address"> + <properties> + <help>IPv4 address of local end of the PPPoE link</help> + <valueHelp> + <format>ipv4</format> + <description>Address of local end of the PPPoE link</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + #include <include/interface/mirror.xml.i> + #include <include/interface/mtu-68-1500.xml.i> + <leafNode name="mtu"> + <defaultValue>1492</defaultValue> + </leafNode> + <leafNode name="mru"> + <properties> + <help>Maximum Receive Unit (MRU) (default: MTU value)</help> + <valueHelp> + <format>u32:128-16384</format> + <description>Maximum Receive Unit in byte</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 128-16384"/> + </constraint> + <constraintErrorMessage>MRU must be between 128 and 16384</constraintErrorMessage> + </properties> + </leafNode> + #include <include/interface/no-peer-dns.xml.i> + <leafNode name="remote-address"> + <properties> + <help>IPv4 address of remote end of the PPPoE link</help> + <valueHelp> + <format>ipv4</format> + <description>Address of remote end of the PPPoE link</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="service-name"> + <properties> + <help>Service name, only connect to access concentrators advertising this</help> + <constraint> + <regex>[a-zA-Z0-9]+</regex> + </constraint> + <constraintErrorMessage>Service name must be alphanumeric only</constraintErrorMessage> + </properties> + </leafNode> + #include <include/interface/redirect.xml.i> + #include <include/interface/vrf.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_pseudo-ethernet.xml.in b/interface-definitions/interfaces_pseudo-ethernet.xml.in new file mode 100644 index 0000000..031af35 --- /dev/null +++ b/interface-definitions/interfaces_pseudo-ethernet.xml.in @@ -0,0 +1,68 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="pseudo-ethernet" owner="${vyos_conf_scripts_dir}/interfaces_pseudo-ethernet.py"> + <properties> + <help>Pseudo Ethernet Interface (Macvlan)</help> + <priority>321</priority> + <constraint> + <regex>peth[0-9]+</regex> + </constraint> + <constraintErrorMessage>Pseudo Ethernet interface must be named pethN</constraintErrorMessage> + <valueHelp> + <format>pethN</format> + <description>Pseudo Ethernet interface name</description> + </valueHelp> + </properties> + <children> + #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> + #include <include/generic-description.xml.i> + #include <include/interface/dhcp-options.xml.i> + #include <include/interface/dhcpv6-options.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/vrf.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/source-interface-ethernet.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/mirror.xml.i> + <leafNode name="mode"> + <properties> + <help>Receive mode (default: private)</help> + <completionHelp> + <list>private vepa bridge passthru</list> + </completionHelp> + <valueHelp> + <format>private</format> + <description>No communication with other pseudo-devices</description> + </valueHelp> + <valueHelp> + <format>vepa</format> + <description>Virtual Ethernet Port Aggregator reflective relay</description> + </valueHelp> + <valueHelp> + <format>bridge</format> + <description>Simple bridge between pseudo-devices</description> + </valueHelp> + <valueHelp> + <format>passthru</format> + <description>Promicious mode passthrough of underlying device</description> + </valueHelp> + <constraint> + <regex>(private|vepa|bridge|passthru)</regex> + </constraint> + <constraintErrorMessage>mode must be private, vepa, bridge or passthru</constraintErrorMessage> + </properties> + <defaultValue>private</defaultValue> + </leafNode> + #include <include/interface/mtu-68-16000.xml.i> + #include <include/interface/redirect.xml.i> + #include <include/interface/vif-s.xml.i> + #include <include/interface/vif.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_sstpc.xml.in b/interface-definitions/interfaces_sstpc.xml.in new file mode 100644 index 0000000..b7c4944 --- /dev/null +++ b/interface-definitions/interfaces_sstpc.xml.in @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="sstpc" owner="${vyos_conf_scripts_dir}/interfaces_sstpc.py"> + <properties> + <help>Secure Socket Tunneling Protocol (SSTP) client Interface</help> + <priority>460</priority> + <constraint> + <regex>sstpc[0-9]+</regex> + </constraint> + <constraintErrorMessage>Secure Socket Tunneling Protocol interface must be named sstpcN</constraintErrorMessage> + <valueHelp> + <format>sstpcN</format> + <description>Secure Socket Tunneling Protocol interface name</description> + </valueHelp> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/authentication.xml.i> + #include <include/interface/no-default-route.xml.i> + #include <include/interface/default-route-distance.xml.i> + #include <include/interface/no-peer-dns.xml.i> + #include <include/interface/mtu-68-1500.xml.i> + <leafNode name="mtu"> + <defaultValue>1452</defaultValue> + </leafNode> + #include <include/server-ipv4-fqdn.xml.i> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>443</defaultValue> + </leafNode> + <node name="ssl"> + <properties> + <help>Secure Sockets Layer (SSL) configuration</help> + </properties> + <children> + #include <include/pki/ca-certificate.xml.i> + </children> + </node> + #include <include/interface/vrf.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_tunnel.xml.in b/interface-definitions/interfaces_tunnel.xml.in new file mode 100644 index 0000000..fe1dad3 --- /dev/null +++ b/interface-definitions/interfaces_tunnel.xml.in @@ -0,0 +1,281 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="tunnel" owner="${vyos_conf_scripts_dir}/interfaces_tunnel.py"> + <properties> + <help>Tunnel interface</help> + <priority>380</priority> + <constraint> + <regex>tun[0-9]+</regex> + </constraint> + <constraintErrorMessage>tunnel interface must be named tunN</constraintErrorMessage> + <valueHelp> + <format>tunN</format> + <description>Tunnel interface name</description> + </valueHelp> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/interface/address-ipv4-ipv6.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/mtu-68-16000.xml.i> + <leafNode name="mtu"> + <defaultValue>1476</defaultValue> + </leafNode> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/source-address-ipv4-ipv6.xml.i> + #include <include/interface/tunnel-remote.xml.i> + #include <include/source-interface.xml.i> + <leafNode name="6rd-prefix"> + <properties> + <help>6rd network prefix</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + <leafNode name="6rd-relay-prefix"> + <properties> + <help>6rd relay prefix</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix of interface for 6rd</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + </leafNode> + <leafNode name="encapsulation"> + <properties> + <help>Encapsulation of this tunnel interface</help> + <completionHelp> + <list>erspan gre gretap ip6erspan ip6gre ip6gretap ip6ip6 ipip ipip6 sit</list> + </completionHelp> + <valueHelp> + <format>erspan</format> + <description>Encapsulated Remote Switched Port Analyzer</description> + </valueHelp> + <valueHelp> + <format>gre</format> + <description>Generic Routing Encapsulation (network layer)</description> + </valueHelp> + <valueHelp> + <format>gretap</format> + <description>Generic Routing Encapsulation (datalink layer)</description> + </valueHelp> + <valueHelp> + <format>ip6erspan</format> + <description>Encapsulated Remote Switched Port Analyzer over IPv6</description> + </valueHelp> + <valueHelp> + <format>ip6gre</format> + <description>GRE over IPv6 (network layer)</description> + </valueHelp> + <valueHelp> + <format>ip6gretap</format> + <description>GRE over IPv6 (datalink layer)</description> + </valueHelp> + <valueHelp> + <format>ip6ip6</format> + <description>IPv6 in IPv6 encapsulation</description> + </valueHelp> + <valueHelp> + <format>ipip</format> + <description>IPv4 in IPv4 encapsulation</description> + </valueHelp> + <valueHelp> + <format>ipip6</format> + <description>IPv4 in IP6 encapsulation</description> + </valueHelp> + <valueHelp> + <format>sit</format> + <description>Simple Internet Transition (IPv6 in IPv4)</description> + </valueHelp> + <constraint> + <regex>(erspan|gre|gretap|ip6erspan|ip6gre|ip6gretap|ip6ip6|ipip|ipip6|sit)</regex> + </constraint> + <constraintErrorMessage>Invalid encapsulation, must be one of: erspan, gre, gretap, ip6erspan, ip6gre, ip6gretap, ipip, sit, ipip6 or ip6ip6</constraintErrorMessage> + </properties> + </leafNode> + #include <include/interface/mirror.xml.i> + <leafNode name="enable-multicast"> + <properties> + <help>Enable multicast operation over tunnel</help> + <valueless/> + </properties> + </leafNode> + <node name="parameters"> + <properties> + <help>Tunnel parameters</help> + </properties> + <children> + <node name="erspan"> + <properties> + <help>ERSPAN tunnel parameters</help> + </properties> + <children> + <leafNode name="direction"> + <properties> + <help>Mirrored traffic direction</help> + <completionHelp> + <list>ingress egress</list> + </completionHelp> + <valueHelp> + <format>ingress</format> + <description>Mirror ingress traffic</description> + </valueHelp> + <valueHelp> + <format>egress</format> + <description>Mirror egress traffic</description> + </valueHelp> + <constraint> + <regex>(ingress|egress)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="hw-id"> + <properties> + <help>Unique identifier of an ERSPAN engine within a system</help> + <valueHelp> + <format>u32:0-1048575</format> + <description>Unique identifier of an ERSPAN engine</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-1048575"/> + </constraint> + </properties> + </leafNode> + <leafNode name="index"> + <properties> + <help>ERSPAN version 1 index field</help> + <valueHelp> + <format>u32:0-63</format> + <description>Platform-depedent field for specifying port number and direction</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-63"/> + </constraint> + </properties> + </leafNode> + <leafNode name="version"> + <properties> + <help>Protocol version</help> + <completionHelp> + <list>1 2</list> + </completionHelp> + <valueHelp> + <format>1</format> + <description>ERSPAN Type II</description> + </valueHelp> + <valueHelp> + <format>2</format> + <description>ERSPAN Type III</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2"/> + </constraint> + </properties> + <defaultValue>1</defaultValue> + </leafNode> + </children> + </node> + <node name="ip"> + <properties> + <help>IPv4-specific tunnel parameters</help> + </properties> + <children> + <leafNode name="no-pmtu-discovery"> + <properties> + <help>Disable path MTU discovery</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ignore-df"> + <properties> + <help>Ignore the DF (don't fragment) bit</help> + <valueless/> + </properties> + </leafNode> + #include <include/interface/parameters-key.xml.i> + #include <include/interface/parameters-tos.xml.i> + #include <include/interface/parameters-ttl.xml.i> + <leafNode name="ttl"> + <defaultValue>64</defaultValue> + </leafNode> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6-specific tunnel parameters</help> + </properties> + <children> + <leafNode name="encaplimit"> + <properties> + <help>Set fixed encapsulation limit</help> + <completionHelp> + <list>none</list> + </completionHelp> + <valueHelp> + <format>u32:0-255</format> + <description>Encapsulation limit</description> + </valueHelp> + <valueHelp> + <format>none</format> + <description>Disable encapsulation limit</description> + </valueHelp> + <constraint> + <regex>(none)</regex> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + <constraintErrorMessage>Tunnel encaplimit must be 0-255 or none</constraintErrorMessage> + </properties> + <defaultValue>4</defaultValue> + </leafNode> + #include <include/interface/parameters-flowlabel.xml.i> + <leafNode name="hoplimit"> + <properties> + <help>Hoplimit</help> + <valueHelp> + <format>u32:0-255</format> + <description>Hop limit</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + <constraintErrorMessage>hop limit must be between 0-255</constraintErrorMessage> + </properties> + <defaultValue>64</defaultValue> + </leafNode> + <leafNode name="tclass"> + <properties> + <help>Traffic class (Tclass)</help> + <valueHelp> + <format>0x0-0x0fffff</format> + <description>Traffic class, 'inherit' or hex value</description> + </valueHelp> + <constraint> + <regex>(0x){0,1}(0?[0-9A-Fa-f]{1,2})</regex> + </constraint> + <constraintErrorMessage>Must be 'inherit' or a number</constraintErrorMessage> + </properties> + <defaultValue>inherit</defaultValue> + </leafNode> + </children> + </node> + </children> + </node> + #include <include/interface/vrf.xml.i> + #include <include/interface/redirect.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_virtual-ethernet.xml.in b/interface-definitions/interfaces_virtual-ethernet.xml.in new file mode 100644 index 0000000..c4610fe --- /dev/null +++ b/interface-definitions/interfaces_virtual-ethernet.xml.in @@ -0,0 +1,48 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="virtual-ethernet" owner="${vyos_conf_scripts_dir}/interfaces_virtual-ethernet.py"> + <properties> + <help>Virtual Ethernet (veth) Interface</help> + <priority>300</priority> + <constraint> + <regex>veth[0-9]+</regex> + </constraint> + <constraintErrorMessage>Virtual Ethernet interface must be named vethN</constraintErrorMessage> + <valueHelp> + <format>vethN</format> + <description>Virtual Ethernet interface name</description> + </valueHelp> + </properties> + <children> + #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> + #include <include/generic-description.xml.i> + #include <include/interface/dhcp-options.xml.i> + #include <include/interface/dhcpv6-options.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/netns.xml.i> + #include <include/interface/vif-s.xml.i> + #include <include/interface/vif.xml.i> + #include <include/interface/vrf.xml.i> + <leafNode name="peer-name"> + <properties> + <help>Virtual ethernet peer interface name</help> + <completionHelp> + <path>interfaces virtual-ethernet</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of peer interface</description> + </valueHelp> + <constraint> + <regex>veth[0-9]+</regex> + </constraint> + <constraintErrorMessage>Virutal Ethernet interface must be named vethN</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_vti.xml.in b/interface-definitions/interfaces_vti.xml.in new file mode 100644 index 0000000..39fb313 --- /dev/null +++ b/interface-definitions/interfaces_vti.xml.in @@ -0,0 +1,35 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="vti" owner="${vyos_conf_scripts_dir}/interfaces_vti.py"> + <properties> + <help>Virtual Tunnel Interface (XFRM)</help> + <priority>381</priority> + <constraint> + <regex>vti[0-9]+</regex> + </constraint> + <constraintErrorMessage>VTI interface must be named vtiN</constraintErrorMessage> + <valueHelp> + <format>vtiN</format> + <description>VTI interface name</description> + </valueHelp> + </properties> + <children> + #include <include/interface/address-ipv4-ipv6.xml.i> + #include <include/generic-description.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mtu-68-16000.xml.i> + <leafNode name="mtu"> + <defaultValue>1500</defaultValue> + </leafNode> + #include <include/interface/mirror.xml.i> + #include <include/interface/redirect.xml.i> + #include <include/interface/vrf.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_vxlan.xml.in b/interface-definitions/interfaces_vxlan.xml.in new file mode 100644 index 0000000..937acb1 --- /dev/null +++ b/interface-definitions/interfaces_vxlan.xml.in @@ -0,0 +1,153 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="vxlan" owner="${vyos_conf_scripts_dir}/interfaces_vxlan.py"> + <properties> + <help>Virtual Extensible LAN (VXLAN) Interface</help> + <priority>460</priority> + <constraint> + <regex>vxlan[0-9]+</regex> + </constraint> + <constraintErrorMessage>VXLAN interface must be named vxlanN</constraintErrorMessage> + <valueHelp> + <format>vxlanN</format> + <description>VXLAN interface name</description> + </valueHelp> + </properties> + <children> + #include <include/interface/address-ipv4-ipv6.xml.i> + #include <include/generic-description.xml.i> + #include <include/interface/disable.xml.i> + <leafNode name="gpe"> + <properties> + <help>Enable Generic Protocol extension (VXLAN-GPE)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="group"> + <properties> + <help>Multicast group address for VXLAN interface</help> + <valueHelp> + <format>ipv4</format> + <description>Multicast IPv4 group address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Multicast IPv6 group address</description> + </valueHelp> + <constraint> + <validator name="ipv4-multicast"/> + <validator name="ipv6-multicast"/> + </constraint> + <constraintErrorMessage>Multicast IPv4/IPv6 address required</constraintErrorMessage> + </properties> + </leafNode> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/mtu-1200-16000.xml.i> + #include <include/interface/mirror.xml.i> + <node name="parameters"> + <properties> + <help>VXLAN tunnel parameters</help> + </properties> + <children> + <node name="ip"> + <properties> + <help>IPv4 specific tunnel parameters</help> + </properties> + <children> + #include <include/interface/parameters-df.xml.i> + #include <include/interface/parameters-tos.xml.i> + #include <include/interface/parameters-ttl.xml.i> + <leafNode name="ttl"> + <defaultValue>16</defaultValue> + </leafNode> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 specific tunnel parameters</help> + </properties> + <children> + #include <include/interface/parameters-flowlabel.xml.i> + </children> + </node> + <leafNode name="external"> + <properties> + <help>Use external control plane</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="nolearning"> + <properties> + <help>Do not add unknown addresses into forwarding database</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="neighbor-suppress"> + <properties> + <help>Enable neighbor discovery (ARP and ND) suppression</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="vni-filter"> + <properties> + <help>Enable VNI filter support</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>4789</defaultValue> + </leafNode> + #include <include/source-address-ipv4-ipv6.xml.i> + #include <include/source-interface.xml.i> + #include <include/interface/tunnel-remote-multi.xml.i> + #include <include/interface/redirect.xml.i> + #include <include/interface/vrf.xml.i> + #include <include/vni.xml.i> + <tagNode name="vlan-to-vni"> + <properties> + <help>Configuring VLAN-to-VNI mappings for EVPN-VXLAN</help> + <valueHelp> + <format>u32:0-4094</format> + <description>Virtual Local Area Network (VLAN) ID</description> + </valueHelp> + <valueHelp> + <format><start-end></format> + <description>VLAN IDs range (use '-' as delimiter)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 0-4094"/> + </constraint> + <constraintErrorMessage>Not a valid VLAN ID or range, VLAN ID must be between 0 and 4094</constraintErrorMessage> + </properties> + <children> + <leafNode name="vni"> + <properties> + <help>Virtual Network Identifier</help> + <valueHelp> + <format>u32:0-16777214</format> + <description>VXLAN virtual network identifier</description> + </valueHelp> + <valueHelp> + <format><start-end></format> + <description>VXLAN virtual network IDs range (use '-' as delimiter)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 0-16777214"/> + </constraint> + <constraintErrorMessage>Not a valid VXLAN virtual network ID or range</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_wireguard.xml.in b/interface-definitions/interfaces_wireguard.xml.in new file mode 100644 index 0000000..ce49de0 --- /dev/null +++ b/interface-definitions/interfaces_wireguard.xml.in @@ -0,0 +1,129 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="wireguard" owner="${vyos_conf_scripts_dir}/interfaces_wireguard.py"> + <properties> + <help>WireGuard Interface</help> + <priority>379</priority> + <constraint> + <regex>wg[0-9]+</regex> + </constraint> + <constraintErrorMessage>WireGuard interface must be named wgN</constraintErrorMessage> + <valueHelp> + <format>wgN</format> + <description>WireGuard interface name</description> + </valueHelp> + </properties> + <children> + #include <include/interface/address-ipv4-ipv6.xml.i> + #include <include/generic-description.xml.i> + #include <include/interface/disable.xml.i> + #include <include/port-number.xml.i> + #include <include/interface/mtu-68-16000.xml.i> + <leafNode name="mtu"> + <defaultValue>1420</defaultValue> + </leafNode> + #include <include/interface/mirror.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + <leafNode name="fwmark"> + <properties> + <help>A 32-bit fwmark value set on all outgoing packets</help> + <valueHelp> + <format>number</format> + <description>value which marks the packet for QoS/shaper</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="private-key"> + <properties> + <help>Base64 encoded private key</help> + <constraint> + <validator name="base64"/> + </constraint> + <constraintErrorMessage>Key is not base64-encoded</constraintErrorMessage> + </properties> + </leafNode> + <tagNode name="peer"> + <properties> + <help>peer alias</help> + <constraint> + <regex>[^ ]{1,100}</regex> + </constraint> + <constraintErrorMessage>peer alias too long (limit 100 characters)</constraintErrorMessage> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/generic-description.xml.i> + <leafNode name="public-key"> + <properties> + <help>base64 encoded public key</help> + <constraint> + <validator name="base64"/> + </constraint> + <constraintErrorMessage>Key is not base64-encoded</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="preshared-key"> + <properties> + <help>base64 encoded preshared key</help> + <constraint> + <validator name="base64"/> + </constraint> + <constraintErrorMessage>Key is not base64-encoded</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="allowed-ips"> + <properties> + <help>IP addresses allowed to traverse the peer</help> + <constraint> + <validator name="ip-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="address"> + <properties> + <help>IP address of tunnel endpoint</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address of remote tunnel endpoint</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of remote tunnel endpoint</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + <validator name="ipv6-link-local"/> + </constraint> + </properties> + </leafNode> + #include <include/port-number.xml.i> + <leafNode name="persistent-keepalive"> + <properties> + <help>Interval to send keepalive messages</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + #include <include/interface/redirect.xml.i> + #include <include/interface/per-client-thread.xml.i> + #include <include/interface/vrf.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_wireless.xml.in b/interface-definitions/interfaces_wireless.xml.in new file mode 100644 index 0000000..4749535 --- /dev/null +++ b/interface-definitions/interfaces_wireless.xml.in @@ -0,0 +1,1026 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="wireless" owner="${vyos_conf_scripts_dir}/interfaces_wireless.py"> + <properties> + <help>Wireless (WiFi/WLAN) Network Interface</help> + <priority>318</priority> + <completionHelp> + <script>cd /sys/class/net; if compgen -G "wlan*" > /dev/null; then ls -d wlan*; fi</script> + </completionHelp> + <constraint> + <regex>wlan[0-9]+</regex> + </constraint> + <constraintErrorMessage>Wireless interface must be named wlanN</constraintErrorMessage> + <valueHelp> + <format>wlanN</format> + <description>Wireless (WiFi/WLAN) interface name</description> + </valueHelp> + </properties> + <children> + #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> + <node name="capabilities"> + <properties> + <help>HT and VHT capabilities for your card</help> + </properties> + <children> + <node name="ht"> + <properties> + <help>High Throughput (HT) settings</help> + </properties> + <children> + <leafNode name="40mhz-incapable"> + <properties> + <help>40MHz intolerance, use 20MHz only!</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="auto-powersave"> + <properties> + <help>Enable WMM-PS unscheduled automatic power save delivery [U-APSD]</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="channel-set-width"> + <properties> + <help>Supported channel set width</help> + <completionHelp> + <list>ht20 ht40+ ht40-</list> + </completionHelp> + <valueHelp> + <format>ht20</format> + <description>Supported channel set width both 20 MHz only</description> + </valueHelp> + <valueHelp> + <format>ht40+</format> + <description>Supported channel set width both 20 MHz and 40 MHz with secondary channel above primary channel</description> + </valueHelp> + <valueHelp> + <format>ht40-</format> + <description>Supported channel set width both 20 MHz and 40 MHz with secondary channel below primary channel</description> + </valueHelp> + <constraint> + <regex>(ht20|ht40\+|ht40-)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="delayed-block-ack"> + <properties> + <help>Enable HT-delayed block ack</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="dsss-cck-40"> + <properties> + <help>Enable DSSS_CCK-40</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="greenfield"> + <properties> + <help>Enable HT-greenfield</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ldpc"> + <properties> + <help>Enable LDPC coding capability</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="lsig-protection"> + <properties> + <help>Enable L-SIG TXOP protection capability</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="max-amsdu"> + <properties> + <help>Set maximum A-MSDU length</help> + <completionHelp> + <list>3839 7935</list> + </completionHelp> + <valueHelp> + <format>3839</format> + <description>Set maximum A-MSDU length to 3839 octets</description> + </valueHelp> + <valueHelp> + <format>7935</format> + <description>Set maximum A-MSDU length to 7935 octets</description> + </valueHelp> + <constraint> + <regex>(3839|7935)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="short-gi"> + <properties> + <help>Short GI capabilities</help> + <completionHelp> + <list>20 40</list> + </completionHelp> + <valueHelp> + <format>20</format> + <description>Short GI for 20 MHz</description> + </valueHelp> + <valueHelp> + <format>40</format> + <description>Short GI for 40 MHz</description> + </valueHelp> + <constraint> + <regex>(20|40)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="smps"> + <properties> + <help>Spatial Multiplexing Power Save (SMPS) settings</help> + <completionHelp> + <list>static dynamic</list> + </completionHelp> + <valueHelp> + <format>static</format> + <description>STATIC Spatial Multiplexing (SM) Power Save</description> + </valueHelp> + <valueHelp> + <format>dynamic</format> + <description>DYNAMIC Spatial Multiplexing (SM) Power Save</description> + </valueHelp> + <constraint> + <regex>(static|dynamic)</regex> + </constraint> + </properties> + </leafNode> + <node name="stbc"> + <properties> + <help>Support for sending and receiving PPDU using STBC (Space Time Block Coding)</help> + </properties> + <children> + <leafNode name="rx"> + <properties> + <help>Enable receiving PPDU using STBC (Space Time Block Coding)</help> + <valueHelp> + <format>[1-3]+</format> + <description>Number of spacial streams that can use RX STBC</description> + </valueHelp> + <constraint> + <regex>[1-3]+</regex> + </constraint> + <constraintErrorMessage>Invalid capability item</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="tx"> + <properties> + <help>Enable sending PPDU using STBC (Space Time Block Coding)</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="require-ht"> + <properties> + <help>Require stations to support HT PHY</help> + <valueless/> + </properties> + </leafNode> + <node name="vht"> + <properties> + <help>Very High Throughput (VHT) settings</help> + </properties> + <children> + <leafNode name="antenna-count"> + <properties> + <help>Number of antennas on this card</help> + <valueHelp> + <format>u32:1-8</format> + <description>Number of antennas for this card</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-8"/> + </constraint> + </properties> + </leafNode> + <leafNode name="antenna-pattern-fixed"> + <properties> + <help>Set if antenna pattern does not change during the lifetime of an association</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="beamform"> + <properties> + <help>VHT beamforming capabilities</help> + <completionHelp> + <list>single-user-beamformer single-user-beamformee multi-user-beamformer multi-user-beamformee</list> + </completionHelp> + <valueHelp> + <format>single-user-beamformer</format> + <description>Support for operation as single user beamformer</description> + </valueHelp> + <valueHelp> + <format>single-user-beamformee</format> + <description>Support for operation as single user beamformee</description> + </valueHelp> + <valueHelp> + <format>multi-user-beamformer</format> + <description>Support for operation as multi user beamformer</description> + </valueHelp> + <valueHelp> + <format>multi-user-beamformee</format> + <description>Support for operation as multi user beamformee</description> + </valueHelp> + <constraint> + <regex>(single-user-beamformer|single-user-beamformee|multi-user-beamformer|multi-user-beamformee)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + <node name="center-channel-freq"> + <properties> + <help>VHT operating channel center frequency</help> + </properties> + <children> + <leafNode name="freq-1"> + <properties> + <help>VHT operating channel center frequency - center freq 1 (for use with 80, 80+80 and 160 modes)</help> + <valueHelp> + <format>u32:34-177</format> + <description>5Ghz (802.11 a/h/j/n/ac) center channel index (use 42 for primary 80MHz channel 36)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 34-177"/> + </constraint> + <constraintErrorMessage>Channel center value must be between 34 and 177</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="freq-2"> + <properties> + <help>VHT operating channel center frequency - center freq 2 (for use with the 80+80 mode)</help> + <valueHelp> + <format>u32:34-177</format> + <description>5Ghz (802.11 ac) center channel index (use 58 for secondary 80MHz channel 52)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 34-177"/> + </constraint> + <constraintErrorMessage>Channel center value must be between 34 and 177</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + <leafNode name="channel-set-width"> + <properties> + <help>VHT operating Channel width</help> + <completionHelp> + <list>0 1 2 3</list> + </completionHelp> + <valueHelp> + <format>0</format> + <description>20 or 40 MHz channel width</description> + </valueHelp> + <valueHelp> + <format>1</format> + <description>80 MHz channel width</description> + </valueHelp> + <valueHelp> + <format>2</format> + <description>160 MHz channel width</description> + </valueHelp> + <valueHelp> + <format>3</format> + <description>80+80 MHz channel width</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-3"/> + </constraint> + </properties> + </leafNode> + <leafNode name="ldpc"> + <properties> + <help>Enable LDPC (Low Density Parity Check) coding capability</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="link-adaptation"> + <properties> + <help>VHT link adaptation capabilities</help> + <completionHelp> + <list>unsolicited both</list> + </completionHelp> + <valueHelp> + <format>unsolicited</format> + <description>Station provides only unsolicited VHT MFB</description> + </valueHelp> + <valueHelp> + <format>both</format> + <description>Station can provide VHT MFB in response to VHT MRQ and unsolicited VHT MFB</description> + </valueHelp> + <constraint> + <regex>(unsolicited|both)</regex> + </constraint> + <constraintErrorMessage>Invalid capability item</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="max-mpdu-exp"> + <properties> + <help>Set the maximum length of A-MPDU pre-EOF padding that the station can receive</help> + <valueHelp> + <format>u32:0-7</format> + <description>Maximum length of A-MPDU pre-EOF padding = 2 pow(13 + x) -1 octets</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-7"/> + </constraint> + </properties> + </leafNode> + <leafNode name="max-mpdu"> + <properties> + <help>Increase Maximum MPDU length to 7991 or 11454 octets (otherwise: 3895 octets)</help> + <completionHelp> + <list>7991 11454</list> + </completionHelp> + <valueHelp> + <format>7991</format> + <description>ncrease Maximum MPDU length to 7991 octets</description> + </valueHelp> + <valueHelp> + <format>11454</format> + <description>ncrease Maximum MPDU length to 11454 octets</description> + </valueHelp> + <constraint> + <regex>(7991|11454)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="short-gi"> + <properties> + <help>Short GI capabilities</help> + <completionHelp> + <list>80 160</list> + </completionHelp> + <valueHelp> + <format>80</format> + <description>Short GI for 80 MHz</description> + </valueHelp> + <valueHelp> + <format>160</format> + <description>Short GI for 160 MHz</description> + </valueHelp> + <constraint> + <regex>(80|160)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + <node name="stbc"> + <properties> + <help>Support for sending and receiving PPDU using STBC (Space Time Block Coding)</help> + </properties> + <children> + <leafNode name="rx"> + <properties> + <help>Enable receiving PPDU using STBC (Space Time Block Coding)</help> + <valueHelp> + <format>[1-4]+</format> + <description>Number of spacial streams that can use RX STBC</description> + </valueHelp> + <constraint> + <regex>[1-4]+</regex> + </constraint> + <constraintErrorMessage>Invalid capability item</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="tx"> + <properties> + <help>Enable sending PPDU using STBC (Space Time Block Coding)</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="tx-powersave"> + <properties> + <help>Enable VHT TXOP Power Save Mode</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="vht-cf"> + <properties> + <help>Station supports receiving VHT variant HT Control field</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="require-vht"> + <properties> + <help>Require stations to support VHT PHY</help> + <valueless/> + </properties> + </leafNode> + <node name="he"> + <properties> + <help>High Efficiency (HE) settings</help> + </properties> + <children> + <leafNode name="channel-set-width"> + <properties> + <help>HE operating channel width</help> + <completionHelp> + <!-- + op_modes drawn from: + https://w1.fi/cgit/hostap/tree/src/common/ieee802_11_common.c?id=195cc3d919503fb0d699d9a56a58a72602b25f51#n1525 + 802.11ax (WiFi-6e - HE) can use up to 160MHz bandwidth channels + --> + <list>81 83 84 131 132 133 134 135</list> + </completionHelp> + <valueHelp> + <format>81</format> + <description>2.4GHz, 20 MHz channel width</description> + </valueHelp> + <valueHelp> + <format>83</format> + <description>2.4GHz, 40 MHz channel width, secondary 20MHz channel above primary channel</description> + </valueHelp> + <valueHelp> + <format>84</format> + <description>2.4GHz, 40 MHz channel width, secondary 20MHz channel below primary channel</description> + </valueHelp> + <valueHelp> + <format>131</format> + <description>6GHz, 20 MHz channel width</description> + </valueHelp> + <valueHelp> + <format>132</format> + <description>6GHz, 40 MHz channel width</description> + </valueHelp> + <valueHelp> + <format>133</format> + <description>6GHz, 80 MHz channel width</description> + </valueHelp> + <valueHelp> + <format>134</format> + <description>6GHz, 160 MHz channel width</description> + </valueHelp> + <valueHelp> + <format>135</format> + <description>6GHz, 80+80 MHz channel width</description> + </valueHelp> + <constraint> + <regex>(81|83|84|131|132|133|134|135)</regex> + </constraint> + </properties> + </leafNode> + <node name="center-channel-freq"> + <properties> + <help>HE operating channel center frequency</help> + </properties> + <children> + <leafNode name="freq-1"> + <properties> + <help>HE operating channel center frequency - center freq 1 (for use with 80, 80+80 and 160 modes)</help> + <valueHelp> + <format>u32:1-233</format> + <description>6Ghz (802.11 ax) center channel index (use 3 (at 40MHz), 7 (at 80MHz) or 15 (at 160MHz) for primary channel 1)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-233"/> + </constraint> + <constraintErrorMessage>Channel center value must be between 1 and 233</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="freq-2"> + <properties> + <help>HE operating channel center frequency - center freq 2 (for use with the 80+80 mode)</help> + <valueHelp> + <format>u32:1-233</format> + <description>6Ghz (802.11 ax) center channel index (use 23 (at 80MHz) for secondary channel 17)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-233"/> + </constraint> + <constraintErrorMessage>Channel center value must be between 1 and 233</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + <leafNode name="antenna-pattern-fixed"> + <properties> + <help>Tell the AP that antenna positions are fixed and will not change during the lifetime of an association</help> + <valueless/> + </properties> + </leafNode> + <node name="beamform"> + <properties> + <help>HE beamforming capabilities</help> + </properties> + <children> + <leafNode name="single-user-beamformer"> + <properties> + <help>Support for operation as single user beamformer</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="single-user-beamformee"> + <properties> + <help>Support for operation as single user beamformee</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="multi-user-beamformer"> + <properties> + <help>Support for operation as multi user beamformer</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="bss-color"> + <properties> + <help>BSS coloring helps to prevent channel jamming when multiple APs use the same channels</help> + <constraint> + <validator name="numeric" argument="--range 1-63"/> + </constraint> + </properties> + </leafNode> + <leafNode name="coding-scheme"> + <properties> + <help>Spacial Stream and Modulation Coding Scheme settings</help> + <valueHelp> + <format>u32:0</format> + <description>HE-MCS 0-7</description> + </valueHelp> + <valueHelp> + <format>u32:1</format> + <description>HE-MCS 0-9</description> + </valueHelp> + <valueHelp> + <format>u32:2</format> + <description>HE-MCS 0-11</description> + </valueHelp> + <valueHelp> + <format>u32:3</format> + <description>HE-MCS is not supported</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-3"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <leafNode name="require-he"> + <properties> + <help>Require stations to support HE PHY</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="channel"> + <properties> + <help>Wireless radio channel</help> + <valueHelp> + <format>0</format> + <description>Automatic Channel Selection (ACS)</description> + </valueHelp> + <valueHelp> + <format>u32:1-14</format> + <description>2.4Ghz (802.11 b/g/n/ax) Channel</description> + </valueHelp> + <valueHelp> + <format>u32:34-177</format> + <description>5Ghz (802.11 a/h/j/n/ac) Channel</description> + </valueHelp> + <valueHelp> + <format>u32:1-233</format> + <description>6Ghz (802.11 ax) Channel</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-0 --range 1-14 --range 34-177 --range 1-233"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + #include <include/generic-description.xml.i> + #include <include/interface/dhcp-options.xml.i> + #include <include/interface/dhcpv6-options.xml.i> + <leafNode name="disable-broadcast-ssid"> + <properties> + <help>Disable broadcast of SSID from access-point</help> + <valueless/> + </properties> + </leafNode> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/vrf.xml.i> + <leafNode name="expunge-failing-stations"> + <properties> + <help>Disassociate stations based on excessive transmission failures</help> + <valueless/> + </properties> + </leafNode> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/hw-id.xml.i> + <leafNode name="isolate-stations"> + <properties> + <help>Isolate stations on the AP so they cannot see each other</help> + <valueless/> + </properties> + </leafNode> + #include <include/interface/mac.xml.i> + <leafNode name="max-stations"> + <properties> + <help>Maximum number of wireless radio stations. Excess stations will be rejected upon authentication request.</help> + <valueHelp> + <format>u32:1-2007</format> + <description>Number of allowed stations</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2007"/> + </constraint> + <constraintErrorMessage>Number of stations must be between 1 and 2007</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="stationary-ap"> + <properties> + <help>Stationary AP config indicates that the AP doesn't move.</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="mgmt-frame-protection"> + <properties> + <help>Management Frame Protection (MFP) according to IEEE 802.11w</help> + <completionHelp> + <list>disabled optional required</list> + </completionHelp> + <valueHelp> + <format>disabled</format> + <description>no MFP</description> + </valueHelp> + <valueHelp> + <format>optional</format> + <description>MFP optional</description> + </valueHelp> + <valueHelp> + <format>required</format> + <description>MFP enforced (mandatory for WPA3)</description> + </valueHelp> + <constraint> + <regex>(disabled|optional|required)</regex> + </constraint> + </properties> + <defaultValue>disabled</defaultValue> + </leafNode> + <leafNode name="enable-bf-protection"> + <properties> + <help>Beacon Protection: management frame protection for Beacon frames, requires Management Frame Protection (MFP)</help> + <valueless/> + </properties> + <defaultValue>disabled</defaultValue> + </leafNode> + <leafNode name="mode"> + <properties> + <help>Wireless radio mode</help> + <completionHelp> + <list>a b g n ac ax</list> + </completionHelp> + <valueHelp> + <format>a</format> + <description>802.11a - 54 Mbits/sec</description> + </valueHelp> + <valueHelp> + <format>b</format> + <description>802.11b - 11 Mbits/sec</description> + </valueHelp> + <valueHelp> + <format>g</format> + <description>802.11g - 54 Mbits/sec</description> + </valueHelp> + <valueHelp> + <format>n</format> + <description>802.11n - 600 Mbits/sec</description> + </valueHelp> + <valueHelp> + <format>ac</format> + <description>802.11ac - 1300 Mbits/sec</description> + </valueHelp> + <valueHelp> + <format>ax</format> + <description>802.11ax (6GHz only for now)</description> + </valueHelp> + <constraint> + <regex>(a|b|g|n|ac|ax)</regex> + </constraint> + </properties> + <defaultValue>g</defaultValue> + </leafNode> + <!-- background_radar_detection not yet supported by VyOS's hostapd + <leafNode name="background-radar-detection"> + <properties> + <help>Enabling background radar detection feature allows CAC to be run on dedicated radio RF chains while the radio(s) are otherwise running normal AP activities on other channels.</help> + <valueless/> + </properties> + </leafNode> + --> + #include <include/interface/mirror.xml.i> + <leafNode name="physical-device"> + <properties> + <help>Wireless physical device</help> + <completionHelp> + <script>${vyos_completion_dir}/list_wireless_phys.sh</script> + </completionHelp> + <constraint> + <validator name="wireless-phy"/> + </constraint> + </properties> + <defaultValue>phy0</defaultValue> + </leafNode> + <leafNode name="reduce-transmit-power"> + <properties> + <help>Transmission power reduction in dBm</help> + <valueHelp> + <format>u32:0-255</format> + <description>TX power reduction in dBm</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + <constraintErrorMessage>dBm value must be between 0 and 255</constraintErrorMessage> + </properties> + </leafNode> + <node name="security"> + <properties> + <help>Wireless security settings</help> + </properties> + <children> + <node name="station-address"> + <properties> + <help>Station MAC address based authentication</help> + </properties> + <children> + <leafNode name="mode"> + <properties> + <help>Select security operation mode</help> + <completionHelp> + <list>accept deny</list> + </completionHelp> + <valueHelp> + <format>accept</format> + <description>Accept all clients unless found in deny list</description> + </valueHelp> + <valueHelp> + <format>deny</format> + <description>Deny all clients unless found in accept list</description> + </valueHelp> + <constraint> + <regex>(accept|deny)</regex> + </constraint> + </properties> + <defaultValue>accept</defaultValue> + </leafNode> + <node name="accept"> + <properties> + <help>Accept station MAC address</help> + </properties> + <children> + #include <include/interface/mac-multi.xml.i> + </children> + </node> + <node name="deny"> + <properties> + <help>Deny station MAC address</help> + </properties> + <children> + #include <include/interface/mac-multi.xml.i> + </children> + </node> + </children> + </node> + <node name="wep"> + <properties> + <help>Wired Equivalent Privacy (WEP) parameters</help> + </properties> + <children> + <leafNode name="key"> + <properties> + <help>WEP encryption key</help> + <valueHelp> + <format>txt</format> + <description>Wired Equivalent Privacy key</description> + </valueHelp> + <constraint> + <regex>([a-fA-F0-9]{10}|[a-fA-F0-9]{26}|[a-fA-F0-9]{32})</regex> + </constraint> + <constraintErrorMessage>Invalid WEP key</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + </children> + </node> + <node name="wpa"> + <properties> + <help>Wifi Protected Access (WPA) parameters</help> + </properties> + <children> + <leafNode name="cipher"> + <properties> + <help>Cipher suite for WPA unicast packets</help> + <completionHelp> + <list>GCMP-256 GCMP CCMP-256 CCMP TKIP</list> + </completionHelp> + <valueHelp> + <format>GCMP-256</format> + <description>AES in Galois/counter mode with 256-bit key</description> + </valueHelp> + <valueHelp> + <format>GCMP</format> + <description>AES in Galois/counter mode with 128-bit key</description> + </valueHelp> + <valueHelp> + <format>CCMP-256</format> + <description>AES in Counter mode with CBC-MAC with 256-bit key</description> + </valueHelp> + <valueHelp> + <format>CCMP</format> + <description>AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] (supported on all WPA2 APs)</description> + </valueHelp> + <valueHelp> + <format>TKIP</format> + <description>Temporal Key Integrity Protocol [IEEE 802.11i/D7.0]</description> + </valueHelp> + <constraint> + <regex>(GCMP-256|GCMP|CCMP-256|CCMP|TKIP)</regex> + </constraint> + <constraintErrorMessage>Invalid cipher selection</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + <leafNode name="group-cipher"> + <properties> + <help>Cipher suite for WPA multicast and broadcast packets</help> + <completionHelp> + <list>GCMP-256 GCMP CCMP-256 CCMP TKIP</list> + </completionHelp> + <valueHelp> + <format>GCMP-256</format> + <description>AES in Galois/counter mode with 256-bit key</description> + </valueHelp> + <valueHelp> + <format>GCMP</format> + <description>AES in Galois/counter mode with 128-bit key</description> + </valueHelp> + <valueHelp> + <format>CCMP-256</format> + <description>AES in Counter mode with CBC-MAC with 256-bit key</description> + </valueHelp> + <valueHelp> + <format>CCMP</format> + <description>AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] (supported on all WPA2 APs)</description> + </valueHelp> + <valueHelp> + <format>TKIP</format> + <description>Temporal Key Integrity Protocol [IEEE 802.11i/D7.0]</description> + </valueHelp> + <constraint> + <regex>(GCMP-256|GCMP|CCMP-256|CCMP|TKIP)</regex> + </constraint> + <constraintErrorMessage>Invalid group cipher selection</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="group-mgmt-cipher"> + <properties> + <help>Group management cipher suite. All the stations connecting to the BSS will also need to support the selected cipher</help> + <completionHelp> + <list>AES-128-CMAC BIP-CMAC-256 BIP-GMAC-128 BIP-GMAC-256</list> + </completionHelp> + <constraint> + <regex>(AES-128-CMAC|BIP-CMAC-256|BIP-GMAC-128|BIP-GMAC-256)</regex> + </constraint> + <constraintErrorMessage>Invalid group management cipher selection</constraintErrorMessage> + </properties> + <defaultValue>AES-128-CMAC</defaultValue> + </leafNode> + <leafNode name="mode"> + <properties> + <help>WPA mode</help> + <completionHelp> + <list>wpa wpa2 wpa+wpa2 wpa3</list> + </completionHelp> + <valueHelp> + <format>wpa</format> + <description>WPA (IEEE 802.11i/D3.0)</description> + </valueHelp> + <valueHelp> + <format>wpa2</format> + <description>WPA2 (full IEEE 802.11i/RSN)</description> + </valueHelp> + <valueHelp> + <format>wpa+wpa2</format> + <description>Allow both WPA and WPA2</description> + </valueHelp> + <valueHelp> + <format>wpa3</format> + <description>WPA3 (required for 802.11ax, you must also set mgmt-frame-protection as required)</description> + </valueHelp> + <constraint> + <regex>(wpa|wpa2|wpa\+wpa2|wpa3)</regex> + </constraint> + <constraintErrorMessage>Unknown WPA mode</constraintErrorMessage> + </properties> + <defaultValue>wpa+wpa2</defaultValue> + </leafNode> + #include <include/generic-username.xml.i> + <leafNode name="passphrase"> + <properties> + <help>WPA passphrase. If you are using special characters in the WPA passphrase then single quotes are required.</help> + <valueHelp> + <format>txt</format> + <description>Passphrase of at least 8 but not more than 63 printable characters for WPA-Personal and any passphrase for WPA-Enterprise</description> + </valueHelp> + <constraint> + <regex>[[:ascii:]]{1,256}</regex> + </constraint> + <constraintErrorMessage>Invalid WPA pass phrase, must be 8 to 63 printable characters!</constraintErrorMessage> + </properties> + </leafNode> + #include <include/radius-auth-server-ipv4.xml.i> + <node name="radius"> + <children> + <tagNode name="server"> + <children> + <leafNode name="accounting"> + <properties> + <help>Enable RADIUS server to receive accounting info</help> + <valueless/> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + <leafNode name="ssid"> + <properties> + <help>Wireless access-point service set identifier (SSID)</help> + <constraint> + <regex>.{1,32}</regex> + </constraint> + <constraintErrorMessage>Invalid SSID</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="bssid"> + <properties> + <help>Basic Service Set Identifier (BSSID) - currently station mode only</help> + <valueHelp> + <format>macaddr</format> + <description>BSSID (MAC) address</description> + </valueHelp> + <constraint> + <validator name="mac-address"/> + </constraint> + <constraintErrorMessage>Invalid BSSID</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="type"> + <properties> + <help>Wireless device type for this interface</help> + <completionHelp> + <list>access-point station monitor</list> + </completionHelp> + <valueHelp> + <format>access-point</format> + <description>Access-point forwards packets between other nodes</description> + </valueHelp> + <valueHelp> + <format>station</format> + <description>Connects to another access point</description> + </valueHelp> + <valueHelp> + <format>monitor</format> + <description>Passively monitor all packets on the frequency/channel</description> + </valueHelp> + <constraint> + <regex>(access-point|station|monitor)</regex> + </constraint> + <constraintErrorMessage>Type must be access-point, station or monitor</constraintErrorMessage> + </properties> + <defaultValue>monitor</defaultValue> + </leafNode> + #include <include/interface/per-client-thread.xml.i> + #include <include/interface/redirect.xml.i> + #include <include/interface/vif.xml.i> + #include <include/interface/vif-s.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces_wwan.xml.in b/interface-definitions/interfaces_wwan.xml.in new file mode 100644 index 0000000..1580c3b --- /dev/null +++ b/interface-definitions/interfaces_wwan.xml.in @@ -0,0 +1,48 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="wwan" owner="${vyos_conf_scripts_dir}/interfaces_wwan.py"> + <properties> + <help>Wireless Modem (WWAN) Interface</help> + <priority>350</priority> + <completionHelp> + <script>cd /sys/class/net; if compgen -G "wwan*" > /dev/null; then ls -d wwan*; fi</script> + </completionHelp> + <constraint> + <regex>wwan[0-9]+</regex> + </constraint> + <constraintErrorMessage>Wireless Modem interface must be named wwanN</constraintErrorMessage> + <valueHelp> + <format>wwanN</format> + <description>Wireless Wide Area Network interface name</description> + </valueHelp> + </properties> + <children> + #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> + <leafNode name="apn"> + <properties> + <help>Access Point Name (APN)</help> + </properties> + </leafNode> + #include <include/interface/dhcp-options.xml.i> + #include <include/interface/dhcpv6-options.xml.i> + #include <include/interface/authentication.xml.i> + #include <include/generic-description.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/mirror.xml.i> + #include <include/interface/mtu-68-1500.xml.i> + <leafNode name="mtu"> + <defaultValue>1430</defaultValue> + </leafNode> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/dial-on-demand.xml.i> + #include <include/interface/redirect.xml.i> + #include <include/interface/vrf.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/load-balancing_reverse-proxy.xml.in b/interface-definitions/load-balancing_reverse-proxy.xml.in new file mode 100644 index 0000000..1827462 --- /dev/null +++ b/interface-definitions/load-balancing_reverse-proxy.xml.in @@ -0,0 +1,344 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="load-balancing"> + <children> + <node name="reverse-proxy" owner="${vyos_conf_scripts_dir}/load-balancing_reverse-proxy.py"> + <properties> + <help>Configure reverse-proxy</help> + <priority>900</priority> + </properties> + <children> + <tagNode name="service"> + <properties> + <help>Frontend service name</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Server name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage> + </properties> + <children> + <leafNode name="backend"> + <properties> + <help>Backend member</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Backend name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage> + <valueHelp> + <format>txt</format> + <description>Name of reverse-proxy backend system</description> + </valueHelp> + <completionHelp> + <path>load-balancing reverse-proxy backend</path> + </completionHelp> + <multi/> + </properties> + </leafNode> + #include <include/generic-description.xml.i> + #include <include/listen-address.xml.i> + #include <include/haproxy/logging.xml.i> + #include <include/haproxy/mode.xml.i> + #include <include/port-number.xml.i> + #include <include/haproxy/rule-frontend.xml.i> + #include <include/haproxy/tcp-request.xml.i> + #include <include/haproxy/http-response-headers.xml.i> + <leafNode name="redirect-http-to-https"> + <properties> + <help>Redirect HTTP to HTTPS</help> + <valueless/> + </properties> + </leafNode> + <node name="ssl"> + <properties> + <help>SSL Certificate, SSL Key and CA</help> + </properties> + <children> + #include <include/pki/certificate-multi.xml.i> + </children> + </node> + </children> + </tagNode> + <tagNode name="backend"> + <properties> + <help>Backend server name</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Backend name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage> + </properties> + <children> + <leafNode name="balance"> + <properties> + <help>Load-balancing algorithm</help> + <completionHelp> + <list>source-address round-robin least-connection</list> + </completionHelp> + <valueHelp> + <format>source-address</format> + <description>Based on hash of source IP address</description> + </valueHelp> + <valueHelp> + <format>round-robin</format> + <description>Round robin</description> + </valueHelp> + <valueHelp> + <format>least-connection</format> + <description>Least connection</description> + </valueHelp> + <constraint> + <regex>(source-address|round-robin|least-connection)</regex> + </constraint> + </properties> + <defaultValue>round-robin</defaultValue> + </leafNode> + #include <include/generic-description.xml.i> + #include <include/haproxy/logging.xml.i> + #include <include/haproxy/mode.xml.i> + #include <include/haproxy/http-response-headers.xml.i> + <node name="http-check"> + <properties> + <help>HTTP check configuration</help> + </properties> + <children> + <leafNode name="method"> + <properties> + <help>HTTP method used for health check</help> + <completionHelp> + <list>options head get post put</list> + </completionHelp> + <valueHelp> + <format>options|head|get|post|put</format> + <description>HTTP method used for health checking</description> + </valueHelp> + <constraint> + <regex>(options|head|get|post|put)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="uri"> + <properties> + <help>URI used for HTTP health check (Example: '/' or '/health')</help> + <constraint> + <regex>^\/([^?#\s]*)(\?[^#\s]*)?$</regex> + </constraint> + </properties> + </leafNode> + <node name="expect"> + <properties> + <help>Expected response for the health check to pass</help> + </properties> + <children> + <leafNode name="status"> + <properties> + <help>Expected response status code for the health check to pass</help> + <valueHelp> + <format>u32:200-399</format> + <description>Expected response code</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 200-399"/> + </constraint> + <constraintErrorMessage>Status code must be in range 200-399</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="string"> + <properties> + <help>Expected to be in response body for the health check to pass</help> + <valueHelp> + <format>txt</format> + <description>A string expected to be in the response</description> + </valueHelp> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="health-check"> + <properties> + <help>Non HTTP health check options</help> + <completionHelp> + <list>ldap mysql pgsql redis smtp</list> + </completionHelp> + <valueHelp> + <format>ldap</format> + <description>LDAP protocol check</description> + </valueHelp> + <valueHelp> + <format>mysql</format> + <description>MySQL protocol check</description> + </valueHelp> + <valueHelp> + <format>pgsql</format> + <description>PostgreSQL protocol check</description> + </valueHelp> + <valueHelp> + <format>redis</format> + <description>Redis protocol check</description> + </valueHelp> + <valueHelp> + <format>smtp</format> + <description>SMTP protocol check</description> + </valueHelp> + <constraint> + <regex>(ldap|mysql|redis|pgsql|smtp)</regex> + </constraint> + </properties> + </leafNode> + #include <include/haproxy/rule-backend.xml.i> + <tagNode name="server"> + <properties> + <help>Backend server name</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>Backend server address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 unicast peer address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 unicast peer address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="backup"> + <properties> + <help>Use backup server if other servers are not available</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="check"> + <properties> + <help>Active health check backend server</help> + <valueless/> + </properties> + </leafNode> + #include <include/port-number.xml.i> + <leafNode name="send-proxy"> + <properties> + <help>Send a Proxy Protocol version 1 header (text format)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="send-proxy-v2"> + <properties> + <help>Send a Proxy Protocol version 2 header (binary format)</help> + <valueless/> + </properties> + </leafNode> + </children> + </tagNode> + <node name="ssl"> + <properties> + <help>SSL Certificate, SSL Key and CA</help> + </properties> + <children> + #include <include/pki/ca-certificate.xml.i> + <leafNode name="no-verify"> + <properties> + <help>Do not attempt to verify SSL certificates for backend servers</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + #include <include/haproxy/timeout.xml.i> + </children> + </tagNode> + <node name="global-parameters"> + <properties> + <help>Global perfomance parameters and limits</help> + </properties> + <children> + #include <include/haproxy/logging.xml.i> + <leafNode name="max-connections"> + <properties> + <help>Maximum allowed connections</help> + <valueHelp> + <format>u32:1-2000000</format> + <description>Maximum allowed connections</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2000000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="ssl-bind-ciphers"> + <properties> + <help>Cipher algorithms ("cipher suite") used during SSL/TLS handshake for all frontend servers</help> + <completionHelp> + <list>ecdhe-ecdsa-aes128-gcm-sha256 ecdhe-rsa-aes128-gcm-sha256 ecdhe-ecdsa-aes256-gcm-sha384 ecdhe-rsa-aes256-gcm-sha384 ecdhe-ecdsa-chacha20-poly1305 ecdhe-rsa-chacha20-poly1305 dhe-rsa-aes128-gcm-sha256 dhe-rsa-aes256-gcm-sha384</list> + </completionHelp> + <valueHelp> + <format>ecdhe-ecdsa-aes128-gcm-sha256</format> + <description>ecdhe-ecdsa-aes128-gcm-sha256</description> + </valueHelp> + <valueHelp> + <format>ecdhe-rsa-aes128-gcm-sha256</format> + <description>ecdhe-rsa-aes128-gcm-sha256</description> + </valueHelp> + <valueHelp> + <format>ecdhe-ecdsa-aes256-gcm-sha384</format> + <description>ecdhe-ecdsa-aes256-gcm-sha384</description> + </valueHelp> + <valueHelp> + <format>ecdhe-rsa-aes256-gcm-sha384</format> + <description>ecdhe-rsa-aes256-gcm-sha384</description> + </valueHelp> + <valueHelp> + <format>ecdhe-ecdsa-chacha20-poly1305</format> + <description>ecdhe-ecdsa-chacha20-poly1305</description> + </valueHelp> + <valueHelp> + <format>ecdhe-rsa-chacha20-poly1305</format> + <description>ecdhe-rsa-chacha20-poly1305</description> + </valueHelp> + <valueHelp> + <format>dhe-rsa-aes128-gcm-sha256</format> + <description>dhe-rsa-aes128-gcm-sha256</description> + </valueHelp> + <valueHelp> + <format>dhe-rsa-aes256-gcm-sha384</format> + <description>dhe-rsa-aes256-gcm-sha384</description> + </valueHelp> + <constraint> + <regex>(ecdhe-ecdsa-aes128-gcm-sha256|ecdhe-rsa-aes128-gcm-sha256|ecdhe-ecdsa-aes256-gcm-sha384|ecdhe-rsa-aes256-gcm-sha384|ecdhe-ecdsa-chacha20-poly1305|ecdhe-rsa-chacha20-poly1305|dhe-rsa-aes128-gcm-sha256|dhe-rsa-aes256-gcm-sha384)</regex> + </constraint> + <multi/> + </properties> + <defaultValue>ecdhe-ecdsa-aes128-gcm-sha256 ecdhe-rsa-aes128-gcm-sha256 ecdhe-ecdsa-aes256-gcm-sha384 ecdhe-rsa-aes256-gcm-sha384 ecdhe-ecdsa-chacha20-poly1305 ecdhe-rsa-chacha20-poly1305 dhe-rsa-aes128-gcm-sha256 dhe-rsa-aes256-gcm-sha384</defaultValue> + </leafNode> + <leafNode name="tls-version-min"> + <properties> + <help>Specify the minimum required TLS version</help> + <completionHelp> + <list>1.2 1.3</list> + </completionHelp> + <valueHelp> + <format>1.2</format> + <description>TLS v1.2</description> + </valueHelp> + <valueHelp> + <format>1.3</format> + <description>TLS v1.3</description> + </valueHelp> + <constraint> + <regex>(1.2|1.3)</regex> + </constraint> + </properties> + <defaultValue>1.3</defaultValue> + </leafNode> + </children> + </node> + #include <include/interface/vrf.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/load-balancing_wan.xml.in b/interface-definitions/load-balancing_wan.xml.in new file mode 100644 index 0000000..310aa03 --- /dev/null +++ b/interface-definitions/load-balancing_wan.xml.in @@ -0,0 +1,399 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="load-balancing"> + <properties> + <help>Configure load-balancing</help> + </properties> + <children> + <node name="wan" owner="${vyos_conf_scripts_dir}/load-balancing_wan.py"> + <properties> + <help>Configure Wide Area Network (WAN) load-balancing</help> + <priority>900</priority> + </properties> + <children> + <leafNode name="disable-source-nat"> + <properties> + <help>Disable source NAT rules from being configured for WAN load balancing</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="enable-local-traffic"> + <properties> + <help>Enable WAN load balancing for locally sourced traffic</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="flush-connections"> + <properties> + <help>Flush connection tracking tables on connection state change</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="hook"> + <properties> + <help>Script to be executed on interface status change</help> + <valueHelp> + <format>txt</format> + <description>Script in /config/scripts</description> + </valueHelp> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + <tagNode name="interface-health"> + <properties> + <help>Interface name</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + <leafNode name="failure-count"> + <properties> + <help>Failure count</help> + <valueHelp> + <format>u32:1-10</format> + <description>Failure count</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-10"/> + </constraint> + </properties> + <defaultValue>1</defaultValue> + </leafNode> + <leafNode name="nexthop"> + <properties> + <help>Outbound interface nexthop address. Can be 'DHCP or IPv4 address' [REQUIRED]</help> + <completionHelp> + <list>dhcp</list> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>Nexthop IP address</description> + </valueHelp> + <valueHelp> + <format>dhcp</format> + <description>Set the nexthop via DHCP</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <regex>(dhcp)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="success-count"> + <properties> + <help>Success count</help> + <valueHelp> + <format>u32:1-10</format> + <description>Success count</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-10"/> + </constraint> + </properties> + <defaultValue>1</defaultValue> + </leafNode> + <tagNode name="test"> + <properties> + <help>Rule number</help> + <valueHelp> + <format>u32:0-4294967295</format> + <description>Rule number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + <children> + <leafNode name="resp-time"> + <properties> + <help>Ping response time (seconds)</help> + <valueHelp> + <format>u32:1-30</format> + <description>Response time (seconds)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-30"/> + </constraint> + </properties> + <defaultValue>5</defaultValue> + </leafNode> + <leafNode name="target"> + <properties> + <help>Health target address</help> + <valueHelp> + <format>ipv4</format> + <description>Health target address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="test-script"> + <properties> + <help>Path to user-defined script</help> + <valueHelp> + <format>txt</format> + <description>Script in /config/scripts</description> + </valueHelp> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + <leafNode name="ttl-limit"> + <properties> + <help>TTL limit (hop count)</help> + <valueHelp> + <format>u32:1-254</format> + <description>Number of hops</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-254"/> + </constraint> + </properties> + <defaultValue>1</defaultValue> + </leafNode> + <leafNode name="type"> + <properties> + <help>WLB test type</help> + <completionHelp> + <list>ping ttl user-defined</list> + </completionHelp> + <valueHelp> + <format>ping</format> + <description>Test with ICMP echo response</description> + </valueHelp> + <valueHelp> + <format>ttl</format> + <description>Test with UDP TTL expired response</description> + </valueHelp> + <valueHelp> + <format>user-defined</format> + <description>User-defined test script</description> + </valueHelp> + <constraint> + <regex>(ping|ttl|user-defined)</regex> + </constraint> + </properties> + <defaultValue>ping</defaultValue> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="rule"> + <properties> + <help>Rule number (1-9999)</help> + <valueHelp> + <format>u32:1-9999</format> + <description>Rule number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-9999"/> + </constraint> + </properties> + <children> + #include <include/generic-description.xml.i> + <node name="destination"> + <properties> + <help>Destination</help> + </properties> + <children> + #include <include/ipv4-address-prefix-range.xml.i> + #include <include/port-port-range.xml.i> + </children> + </node> + <leafNode name="exclude"> + <properties> + <help>Exclude packets matching this rule from WAN load balance</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="failover"> + <properties> + <help>Enable failover for packets matching this rule from WAN load balance</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="inbound-interface"> + <properties> + <help>Inbound interface name (e.g., "eth0") [REQUIRED]</help> + <completionHelp> + <list>any</list> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + </leafNode> + <tagNode name="interface"> + <properties> + <help>Interface name [REQUIRED]</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + <leafNode name="weight"> + <properties> + <help>Load-balance weight</help> + <valueHelp> + <format>u32:1-255</format> + <description>Interface weight</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + <constraintErrorMessage>Weight must be between 1 and 255</constraintErrorMessage> + </properties> + <defaultValue>1</defaultValue> + </leafNode> + </children> + </tagNode> + <node name="limit"> + <properties> + <help>Enable packet limit for this rule</help> + </properties> + <children> + <leafNode name="burst"> + <properties> + <help>Burst limit for matching packets</help> + <valueHelp> + <format>u32:0-4294967295</format> + <description>Burst limit for matching packets</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + <defaultValue>5</defaultValue> + </leafNode> + <leafNode name="period"> + <properties> + <help>Time window for rate calculation</help> + <completionHelp> + <list>hour minute second</list> + </completionHelp> + <valueHelp> + <format>hour</format> + <description>hour</description> + </valueHelp> + <valueHelp> + <format>minute</format> + <description>minute</description> + </valueHelp> + <valueHelp> + <format>second</format> + <description>second</description> + </valueHelp> + <constraint> + <regex>(hour|minute|second)</regex> + </constraint> + </properties> + <defaultValue>second</defaultValue> + </leafNode> + <leafNode name="rate"> + <properties> + <help>Number of packets used for rate limit</help> + <valueHelp> + <format>u32:0-4294967295</format> + <description>Number of packets used for rate limit</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + <defaultValue>5</defaultValue> + </leafNode> + <leafNode name="threshold"> + <properties> + <help>Threshold behavior for limit</help> + <completionHelp> + <list>above below</list> + </completionHelp> + <valueHelp> + <format>above</format> + <description>Above limit</description> + </valueHelp> + <valueHelp> + <format>below</format> + <description>Below limit</description> + </valueHelp> + <constraint> + <regex>(above|below)</regex> + </constraint> + </properties> + <defaultValue>below</defaultValue> + </leafNode> + </children> + </node> + <leafNode name="per-packet-balancing"> + <properties> + <help>Option to match traffic per-packet instead of the default, per-flow</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="protocol"> + <properties> + <help>Protocol to match (protocol name, number, or "all")</help> + <completionHelp> + <script>${vyos_completion_dir}/list_protocols.sh</script> + <list>all tcp_udp</list> + </completionHelp> + <valueHelp> + <format>all</format> + <description>All IP protocols</description> + </valueHelp> + <valueHelp> + <format>tcp_udp</format> + <description>Both TCP and UDP</description> + </valueHelp> + <valueHelp> + <format>u32:0-255</format> + <description>IP protocol number</description> + </valueHelp> + <valueHelp> + <format><protocol></format> + <description>IP protocol name</description> + </valueHelp> + <valueHelp> + <format>!<protocol></format> + <description>IP protocol name</description> + </valueHelp> + <constraint> + <validator name="ip-protocol"/> + </constraint> + </properties> + <defaultValue>all</defaultValue> + </leafNode> + <node name="source"> + <properties> + <help>Source information</help> + </properties> + <children> + #include <include/ipv4-address-prefix-range.xml.i> + #include <include/port-port-range.xml.i> + </children> + </node> + </children> + </tagNode> + <node name="sticky-connections"> + <properties> + <help>Configure sticky connections</help> + </properties> + <children> + <leafNode name="inbound"> + <properties> + <help>Enable sticky incoming WAN connections</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/nat.xml.in b/interface-definitions/nat.xml.in new file mode 100644 index 0000000..73a7481 --- /dev/null +++ b/interface-definitions/nat.xml.in @@ -0,0 +1,159 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="nat" owner="${vyos_conf_scripts_dir}/nat.py"> + <properties> + <help>Network Address Translation (NAT) parameters</help> + <priority>220</priority> + </properties> + <children> + <node name="destination"> + <properties> + <help>Destination NAT settings</help> + </properties> + <children> + #include <include/nat-rule.xml.i> + <tagNode name="rule"> + <children> + #include <include/firewall/inbound-interface.xml.i> + <node name="translation"> + <properties> + <help>Inside NAT IP (destination NAT only)</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IP address, subnet, or range</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <valueHelp> + <format>ipv4range</format> + <description>IPv4 address range to match</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv4-address"/> + <validator name="ipv4-range"/> + </constraint> + </properties> + </leafNode> + #include <include/nat-translation-port.xml.i> + #include <include/nat-translation-options.xml.i> + <node name="redirect"> + <properties> + <help>Redirect to local host</help> + </properties> + <children> + #include <include/nat-translation-port.xml.i> + </children> + </node> + </children> + </node> + </children> + </tagNode> + </children> + </node> + <node name="source"> + <properties> + <help>Source NAT settings</help> + </properties> + <children> + #include <include/nat-rule.xml.i> + <tagNode name="rule"> + <properties> + <help>Rule number for NAT</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number of NAT rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>NAT rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/outbound-interface.xml.i> + <node name="translation"> + <properties> + <help>Outside NAT IP (source NAT only)</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IP address, subnet, or range</help> + <completionHelp> + <list>masquerade</list> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <valueHelp> + <format>ipv4range</format> + <description>IPv4 address range to match</description> + </valueHelp> + <valueHelp> + <format>masquerade</format> + <description>NAT to the primary address of outbound-interface</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv4-address"/> + <validator name="ipv4-range"/> + <regex>(masquerade)</regex> + </constraint> + </properties> + </leafNode> + #include <include/nat-translation-port.xml.i> + #include <include/nat-translation-options.xml.i> + </children> + </node> + </children> + </tagNode> + </children> + </node> + <node name="static"> + <properties> + <help>Static NAT (one-to-one)</help> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>Rule number for NAT</help> + </properties> + <children> + #include <include/generic-description.xml.i> + <node name="destination"> + <properties> + <help>NAT destination parameters</help> + </properties> + <children> + #include <include/ipv4-address-prefix.xml.i> + </children> + </node> + #include <include/inbound-interface.xml.i> + #include <include/firewall/log.xml.i> + <node name="translation"> + <properties> + <help>Translation address or prefix</help> + </properties> + <children> + #include <include/ipv4-address-prefix.xml.i> + </children> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/nat64.xml.in b/interface-definitions/nat64.xml.in new file mode 100644 index 0000000..4b3c157 --- /dev/null +++ b/interface-definitions/nat64.xml.in @@ -0,0 +1,116 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="nat64" owner="${vyos_conf_scripts_dir}/nat64.py"> + <properties> + <help>Network Address Translation (NAT64) parameters</help> + <priority>501</priority> + </properties> + <children> + <node name="source"> + <properties> + <help>IPv6 source to IPv4 destination address translation</help> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>Source NAT64 rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>NAT64 rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/generic-disable-node.xml.i> + <node name="match"> + <properties> + <help>Match</help> + </properties> + <children> + <leafNode name="mark"> + <properties> + <help>Match fwmark value</help> + <valueHelp> + <format>u32:1-2147483647</format> + <description>Fwmark value to match against</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2147483647"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="source"> + <properties> + <help>IPv6 source prefix options</help> + </properties> + <children> + <leafNode name="prefix"> + <properties> + <help>IPv6 prefix to be translated</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="translation"> + <properties> + <help>Translated IPv4 address options</help> + </properties> + <children> + <tagNode name="pool"> + <properties> + <help>Translation IPv4 pool number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>NAT64 pool number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/generic-disable-node.xml.i> + #include <include/nat-translation-port.xml.i> + #include <include/nat64/protocol.xml.i> + <leafNode name="address"> + <properties> + <help>IPv4 address or prefix to translate to</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/nat66.xml.in b/interface-definitions/nat66.xml.in new file mode 100644 index 0000000..c59725c --- /dev/null +++ b/interface-definitions/nat66.xml.in @@ -0,0 +1,251 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="nat66" owner="${vyos_conf_scripts_dir}/nat66.py"> + <properties> + <help>Network Prefix Translation (NAT66/NPTv6) parameters</help> + <priority>500</priority> + </properties> + <children> + <node name="source"> + <properties> + <help>Prefix mapping of IPv6 source address translation</help> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>Source NAT66 rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>NAT66 rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/generic-disable-node.xml.i> + #include <include/nat-exclude.xml.i> + #include <include/firewall/log.xml.i> + #include <include/firewall/outbound-interface-no-group.xml.i> + #include <include/nat/protocol.xml.i> + <node name="destination"> + <properties> + <help>IPv6 destination prefix options</help> + </properties> + <children> + <leafNode name="prefix"> + <properties> + <help>IPv6 prefix to be translated</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix</description> + </valueHelp> + <valueHelp> + <format>!ipv6net</format> + <description>Match everything except the specified IPv6 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + <validator name="ipv6-prefix-exclude"/> + </constraint> + </properties> + </leafNode> + #include <include/nat-port.xml.i> + </children> + </node> + <node name="source"> + <properties> + <help>IPv6 source prefix options</help> + </properties> + <children> + <leafNode name="prefix"> + <properties> + <help>IPv6 prefix to be translated</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix</description> + </valueHelp> + <valueHelp> + <format>!ipv6net</format> + <description>Match everything except the specified IPv6 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + <validator name="ipv6-prefix-exclude"/> + </constraint> + </properties> + </leafNode> + #include <include/nat-port.xml.i> + </children> + </node> + <node name="translation"> + <properties> + <help>Translated IPv6 address options</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IPv6 address to translate to</help> + <completionHelp> + <list>masquerade</list> + </completionHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix</description> + </valueHelp> + <valueHelp> + <format>masquerade</format> + <description>NAT to the primary address of outbound-interface</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + <validator name="ipv6-prefix"/> + <regex>(masquerade)</regex> + </constraint> + </properties> + </leafNode> + #include <include/nat-translation-port.xml.i> + </children> + </node> + </children> + </tagNode> + </children> + </node> + <node name="destination"> + <properties> + <help>Prefix mapping for IPv6 destination address translation</help> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>Destination NAT66 rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>NAT66 rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/generic-disable-node.xml.i> + #include <include/nat-exclude.xml.i> + <leafNode name="log"> + <properties> + <help>NAT66 rule logging</help> + <valueless/> + </properties> + </leafNode> + #include <include/firewall/inbound-interface-no-group.xml.i> + #include <include/nat/protocol.xml.i> + <node name="destination"> + <properties> + <help>IPv6 destination prefix options</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IPv6 address or prefix to be translated</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix</description> + </valueHelp> + <valueHelp> + <format>!ipv6</format> + <description>Match everything except the specified IPv6 address</description> + </valueHelp> + <valueHelp> + <format>!ipv6net</format> + <description>Match everything except the specified IPv6 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + <validator name="ipv6-prefix"/> + <validator name="ipv6-address-exclude"/> + <validator name="ipv6-prefix-exclude"/> + </constraint> + </properties> + </leafNode> + #include <include/nat-port.xml.i> + #include <include/firewall/source-destination-group-ipv6.xml.i> + </children> + </node> + <node name="source"> + <properties> + <help>IPv6 source prefix options</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IPv6 address or prefix to be translated</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix</description> + </valueHelp> + <valueHelp> + <format>!ipv6</format> + <description>Match everything except the specified IPv6 address</description> + </valueHelp> + <valueHelp> + <format>!ipv6net</format> + <description>Match everything except the specified IPv6 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + <validator name="ipv6-prefix"/> + <validator name="ipv6-address-exclude"/> + <validator name="ipv6-prefix-exclude"/> + </constraint> + </properties> + </leafNode> + #include <include/nat-port.xml.i> + </children> + </node> + <node name="translation"> + <properties> + <help>Translated IPv6 address options</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IPv6 address or prefix to translate to</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + #include <include/nat-translation-port.xml.i> + </children> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/nat_cgnat.xml.in b/interface-definitions/nat_cgnat.xml.in new file mode 100644 index 0000000..71f4d67 --- /dev/null +++ b/interface-definitions/nat_cgnat.xml.in @@ -0,0 +1,204 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="nat"> + <children> + <node name="cgnat" owner="${vyos_conf_scripts_dir}/nat_cgnat.py"> + <properties> + <help>Carrier-grade NAT (CGNAT) parameters</help> + <priority>221</priority> + </properties> + <children> + <leafNode name="log-allocation"> + <properties> + <help>Log IP address and port allocation</help> + <valueless/> + </properties> + </leafNode> + <node name="pool"> + <properties> + <help>External and internal pool parameters</help> + </properties> + <children> + <tagNode name="external"> + <properties> + <help>External pool name</help> + <valueHelp> + <format>txt</format> + <description>External pool name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of pool can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> + <children> + <leafNode name="external-port-range"> + <properties> + <help>Port range</help> + <valueHelp> + <format>range</format> + <description>Numbered port range (e.g., 1001-1005)</description> + </valueHelp> + <constraint> + <validator name="port-range"/> + </constraint> + </properties> + <defaultValue>1024-65535</defaultValue> + </leafNode> + <node name="per-user-limit"> + <properties> + <help>Per user limits for the pool</help> + </properties> + <children> + <leafNode name="port"> + <properties> + <help>Ports per user</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>2000</defaultValue> + </leafNode> + </children> + </node> + <tagNode name="range"> + <properties> + <help>Range of IP addresses</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix</description> + </valueHelp> + <valueHelp> + <format>ipv4range</format> + <description>IPv4 address range</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv4-host"/> + <validator name="ipv4-range"/> + </constraint> + </properties> + <children> + <leafNode name="seq"> + <properties> + <help>Sequence</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Sequence number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Sequence number must be between 1 and 999999</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="internal"> + <properties> + <help>Internal pool name</help> + <valueHelp> + <format>txt</format> + <description>Internal pool name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of pool can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> + <children> + <leafNode name="range"> + <properties> + <help>Range of IP addresses</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix</description> + </valueHelp> + <valueHelp> + <format>ipv4range</format> + <description>IPv4 address range</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv4-host"/> + <validator name="ipv4-range"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + <tagNode name="rule"> + <properties> + <help>Rule</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this CGNAT rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + <node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + <leafNode name="pool"> + <properties> + <help>Source internal pool</help> + <completionHelp> + <path>nat cgnat pool internal</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Source internal pool name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of pool can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + <node name="translation"> + <properties> + <help>Translation parameters</help> + </properties> + <children> + <leafNode name="pool"> + <properties> + <help>Translation external pool</help> + <completionHelp> + <path>nat cgnat pool external</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Translation external pool name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of pool can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/netns.xml.in b/interface-definitions/netns.xml.in new file mode 100644 index 0000000..d5026bf --- /dev/null +++ b/interface-definitions/netns.xml.in @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="netns" owner="${vyos_conf_scripts_dir}/netns.py"> + <properties> + <help>Network namespace</help> + <priority>10</priority> + </properties> + <children> + <tagNode name="name"> + <properties> + <help>Network namespace name</help> + <constraint> + <regex>[a-zA-Z0-9-_]{1,100}</regex> + </constraint> + <constraintErrorMessage>Netns name must be alphanumeric and can contain hyphens and underscores.</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/pki.xml.in b/interface-definitions/pki.xml.in new file mode 100644 index 0000000..b922771 --- /dev/null +++ b/interface-definitions/pki.xml.in @@ -0,0 +1,287 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="pki" owner="${vyos_conf_scripts_dir}/pki.py"> + <properties> + <help>Public key infrastructure (PKI)</help> + <priority>300</priority> + </properties> + <children> + <tagNode name="ca"> + <properties> + <help>Certificate Authority</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + </properties> + <children> + #include <include/pki/cli-certificate-base64.xml.i> + #include <include/generic-description.xml.i> + <node name="private"> + <properties> + <help>CA private key in PEM format</help> + </properties> + <children> + #include <include/pki/cli-private-key-base64.xml.i> + #include <include/pki/password-protected.xml.i> + </children> + </node> + <leafNode name="crl"> + <properties> + <help>Certificate revocation list in PEM format</help> + <constraint> + <validator name="base64"/> + </constraint> + <constraintErrorMessage>CRL is not base64-encoded</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + #include <include/pki/cli-revoke.xml.i> + </children> + </tagNode> + <tagNode name="certificate"> + <properties> + <help>Certificate</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + </properties> + <children> + #include <include/pki/cli-certificate-base64.xml.i> + <node name="acme"> + <properties> + <help>Automatic Certificate Management Environment (ACME) request</help> + </properties> + <children> + #include <include/url-http-https.xml.i> + <leafNode name="url"> + <defaultValue>https://acme-v02.api.letsencrypt.org/directory</defaultValue> + </leafNode> + <leafNode name="domain-name"> + <properties> + <help>Domain Name</help> + <constraint> + <validator name="fqdn"/> + </constraint> + <constraintErrorMessage>Invalid domain name (RFC 1123 section 2).\nMay only contain letters, numbers and .-_</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + <leafNode name="email"> + <properties> + <help>Email address to associate with certificate</help> + <constraint> + #include <include/constraint/email.xml.i> + </constraint> + </properties> + </leafNode> + #include <include/listen-address-ipv4-single.xml.i> + <leafNode name="rsa-key-size"> + <properties> + <help>Size of the RSA key</help> + <completionHelp> + <list>2048 3072 4096</list> + </completionHelp> + <valueHelp> + <format>2048</format> + <description>RSA key length 2048 bit</description> + </valueHelp> + <valueHelp> + <format>3072</format> + <description>RSA key length 3072 bit</description> + </valueHelp> + <valueHelp> + <format>4096</format> + <description>RSA key length 4096 bit</description> + </valueHelp> + <constraint> + <regex>(2048|3072|4096)</regex> + </constraint> + </properties> + <defaultValue>2048</defaultValue> + </leafNode> + </children> + </node> + #include <include/generic-description.xml.i> + <node name="private"> + <properties> + <help>Certificate private key</help> + </properties> + <children> + #include <include/pki/cli-private-key-base64.xml.i> + #include <include/pki/password-protected.xml.i> + </children> + </node> + #include <include/pki/cli-revoke.xml.i> + </children> + </tagNode> + <tagNode name="dh"> + <properties> + <help>Diffie-Hellman parameters</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + </properties> + <children> + <leafNode name="parameters"> + <properties> + <help>DH parameters in PEM format</help> + <constraint> + <validator name="base64"/> + </constraint> + <constraintErrorMessage>DH parameters are not base64-encoded</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="key-pair"> + <properties> + <help>Public and private keys</help> + </properties> + <children> + <node name="public"> + <properties> + <help>Public key</help> + </properties> + <children> + #include <include/pki/cli-public-key-base64.xml.i> + </children> + </node> + <node name="private"> + <properties> + <help>Private key</help> + </properties> + <children> + #include <include/pki/cli-private-key-base64.xml.i> + #include <include/pki/password-protected.xml.i> + </children> + </node> + </children> + </tagNode> + <tagNode name="openssh"> + <properties> + <help>OpenSSH public and private keys</help> + </properties> + <children> + <node name="public"> + <properties> + <help>Public key</help> + </properties> + <children> + #include <include/pki/cli-public-key-base64.xml.i> + <leafNode name="type"> + <properties> + <help>SSH public key type</help> + <completionHelp> + <list>ssh-rsa</list> + </completionHelp> + <valueHelp> + <format>ssh-rsa</format> + <description>Key pair based on RSA algorithm</description> + </valueHelp> + <constraint> + <regex>(ssh-rsa)</regex> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="private"> + <properties> + <help>Private key</help> + </properties> + <children> + #include <include/pki/cli-private-key-base64.xml.i> + #include <include/pki/password-protected.xml.i> + </children> + </node> + </children> + </tagNode> + <tagNode name="openssh"> + <properties> + <help>OpenSSH public and private keys</help> + </properties> + <children> + <node name="public"> + <properties> + <help>Public key</help> + </properties> + <children> + #include <include/pki/cli-public-key-base64.xml.i> + </children> + </node> + <node name="private"> + <properties> + <help>Private key</help> + </properties> + <children> + #include <include/pki/cli-private-key-base64.xml.i> + #include <include/pki/password-protected.xml.i> + </children> + </node> + </children> + </tagNode> + <node name="openvpn"> + <properties> + <help>OpenVPN keys</help> + </properties> + <children> + <tagNode name="shared-secret"> + <properties> + <help>OpenVPN shared secret key</help> + </properties> + <children> + <leafNode name="key"> + <properties> + <help>OpenVPN shared secret key data</help> + </properties> + </leafNode> + <leafNode name="version"> + <properties> + <help>OpenVPN shared secret key version</help> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + <node name="x509"> + <properties> + <help>X509 Settings</help> + </properties> + <children> + <node name="default"> + <properties> + <help>X509 Default Values</help> + </properties> + <children> + <leafNode name="country"> + <properties> + <help>Default country</help> + </properties> + <defaultValue>GB</defaultValue> + </leafNode> + <leafNode name="state"> + <properties> + <help>Default state</help> + </properties> + <defaultValue>Some-State</defaultValue> + </leafNode> + <leafNode name="locality"> + <properties> + <help>Default locality</help> + </properties> + <defaultValue>Some-City</defaultValue> + </leafNode> + <leafNode name="organization"> + <properties> + <help>Default organization</help> + </properties> + <defaultValue>VyOS</defaultValue> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in new file mode 100644 index 0000000..eb907cb --- /dev/null +++ b/interface-definitions/policy.xml.in @@ -0,0 +1,1578 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="policy" owner="${vyos_conf_scripts_dir}/policy.py"> + <properties> + <priority>200</priority> + <help>Routing policy</help> + </properties> + <children> + <tagNode name="access-list"> + <properties> + <help>IP access-list filter</help> + <valueHelp> + <format>u32:1-99</format> + <description>IP standard access list</description> + </valueHelp> + <valueHelp> + <format>u32:100-199</format> + <description>IP extended access list</description> + </valueHelp> + <valueHelp> + <format>u32:1300-1999</format> + <description>IP standard access list (expanded range)</description> + </valueHelp> + <valueHelp> + <format>u32:2000-2699</format> + <description>IP extended access list (expanded range)</description> + </valueHelp> + </properties> + <children> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Rule for this access-list</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Access-list rule number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <children> + #include <include/policy/action.xml.i> + #include <include/generic-description.xml.i> + <node name="destination"> + <properties> + <help>Destination network or address</help> + </properties> + <children> + <leafNode name="any"> + <properties> + <help>Any IP address to match</help> + <valueless/> + </properties> + </leafNode> + #include <include/policy/host.xml.i> + #include <include/policy/inverse-mask.xml.i> + #include <include/policy/network.xml.i> + </children> + </node> + <node name="source"> + <properties> + <help>Source network or address to match</help> + </properties> + <children> + <leafNode name="any"> + <properties> + <help>Any IP address to match</help> + <valueless/> + </properties> + </leafNode> + #include <include/policy/host.xml.i> + #include <include/policy/inverse-mask.xml.i> + #include <include/policy/network.xml.i> + </children> + </node> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="access-list6"> + <properties> + <help>IPv6 access-list filter</help> + <valueHelp> + <format>txt</format> + <description>Name of IPv6 access-list</description> + </valueHelp> + </properties> + <children> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Rule for this access-list6</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Access-list6 rule number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <children> + #include <include/policy/action.xml.i> + #include <include/generic-description.xml.i> + <node name="source"> + <properties> + <help>Source IPv6 network to match</help> + </properties> + <children> + <leafNode name="any"> + <properties> + <help>Any IP address to match</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="exact-match"> + <properties> + <help>Exact match of the network prefixes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="network"> + <properties> + <help>Network/netmask to match</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="as-path-list"> + <properties> + <help>Add a BGP autonomous system path filter</help> + <valueHelp> + <format>txt</format> + <description>AS path list name</description> + </valueHelp> + </properties> + <children> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Rule for this as-path-list</help> + <valueHelp> + <format>u32:1-65535</format> + <description>AS path list rule number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <children> + #include <include/policy/action.xml.i> + #include <include/generic-description.xml.i> + <leafNode name="regex"> + <properties> + <help>Regular expression to match against an AS path</help> + <valueHelp> + <format>txt</format> + <description>AS path regular expression (ex: "64501 64502")</description> + </valueHelp> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="community-list"> + <properties> + <help>Add a BGP community list entry</help> + <valueHelp> + <format>txt</format> + <description>BGP community-list name</description> + </valueHelp> + </properties> + <children> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Rule for this BGP community list</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Community-list rule number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <children> + #include <include/policy/action.xml.i> + #include <include/generic-description.xml.i> + <leafNode name="regex"> + <properties> + <help>Regular expression to match against a community-list</help> + <completionHelp> + <list>local-AS no-advertise no-export internet additive</list> + </completionHelp> + <valueHelp> + <format><aa:nn></format> + <description>Community number in AA:NN format</description> + </valueHelp> + <valueHelp> + <format>local-AS</format> + <description>Well-known communities value NO_EXPORT_SUBCONFED 0xFFFFFF03</description> + </valueHelp> + <valueHelp> + <format>no-advertise</format> + <description>Well-known communities value NO_ADVERTISE 0xFFFFFF02</description> + </valueHelp> + <valueHelp> + <format>no-export</format> + <description>Well-known communities value NO_EXPORT 0xFFFFFF01</description> + </valueHelp> + <valueHelp> + <format>internet</format> + <description>Well-known communities value 0</description> + </valueHelp> + <valueHelp> + <format>additive</format> + <description>New value is appended to the existing value</description> + </valueHelp> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="extcommunity-list"> + <properties> + <help>Add a BGP extended community list entry</help> + <valueHelp> + <format>txt</format> + <description>BGP extended community-list name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Should be an alphanumeric name</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Rule for this BGP extended community list</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Extended community-list rule number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <children> + #include <include/policy/action.xml.i> + #include <include/generic-description.xml.i> + <leafNode name="regex"> + <properties> + <help>Regular expression to match against an extended community list</help> + <valueHelp> + <format><aa:nn:nn></format> + <description>Extended community list regular expression</description> + </valueHelp> + <valueHelp> + <format><rt aa:nn:nn></format> + <description>Route Target regular expression</description> + </valueHelp> + <valueHelp> + <format><soo aa:nn:nn></format> + <description>Site of Origin regular expression</description> + </valueHelp> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="large-community-list"> + <properties> + <help>Add a BGP large community list entry</help> + <valueHelp> + <format>txt</format> + <description>BGP large-community-list name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Should be an alphanumeric name</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Rule for this BGP extended community list</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Large community-list rule number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <children> + #include <include/policy/action.xml.i> + #include <include/generic-description.xml.i> + <leafNode name="regex"> + <properties> + <help>Regular expression to match against a large community list</help> + <valueHelp> + <format>ASN:NN:NN</format> + <description>BGP large-community-list filter</description> + </valueHelp> + <valueHelp> + <format>IP:NN:NN</format> + <description>BGP large-community-list filter (IPv4 address format)</description> + </valueHelp> + <constraint> + <validator name="bgp-large-community-list"/> + </constraint> + <constraintErrorMessage>Malformed large-community-list</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="prefix-list"> + <properties> + <help>IP prefix-list filter</help> + <valueHelp> + <format>txt</format> + <description>Name of IPv4 prefix-list</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Name of prefix-list can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Rule for this prefix-list</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Prefix-list rule number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <children> + #include <include/policy/action.xml.i> + #include <include/generic-description.xml.i> + <leafNode name="ge"> + <properties> + <help>Prefix length to match a netmask greater than or equal to it</help> + <valueHelp> + <format>u32:0-32</format> + <description>Netmask greater than length</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-32"/> + </constraint> + </properties> + </leafNode> + <leafNode name="le"> + <properties> + <help>Prefix length to match a netmask less than or equal to it</help> + <valueHelp> + <format>u32:0-32</format> + <description>Netmask less than length</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-32"/> + </constraint> + </properties> + </leafNode> + <leafNode name="prefix"> + <properties> + <help>Prefix to match</help> + <valueHelp> + <format>ipv4net</format> + <description>Prefix to match against</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="prefix-list6"> + <properties> + <help>IPv6 prefix-list filter</help> + <valueHelp> + <format>txt</format> + <description>Name of IPv6 prefix-list</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Name of prefix-list6 can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Rule for this prefix-list6</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Prefix-list rule number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <children> + #include <include/policy/action.xml.i> + #include <include/generic-description.xml.i> + <leafNode name="ge"> + <properties> + <help>Prefix length to match a netmask greater than or equal to it</help> + <valueHelp> + <format>u32:0-128</format> + <description>Netmask greater than length</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-128"/> + </constraint> + </properties> + </leafNode> + <leafNode name="le"> + <properties> + <help>Prefix length to match a netmask less than or equal to it</help> + <valueHelp> + <format>u32:0-128</format> + <description>Netmask less than length</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-128"/> + </constraint> + </properties> + </leafNode> + <leafNode name="prefix"> + <properties> + <help>Prefix to match</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="route-map"> + <properties> + <help>IP route-map</help> + <valueHelp> + <format>txt</format> + <description>Route map name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of route-map can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Rule for this route-map</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Route-map rule number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <children> + #include <include/policy/action.xml.i> + <leafNode name="call"> + <properties> + <help>Call another route-map on match</help> + <valueHelp> + <format>txt</format> + <description>Route map name</description> + </valueHelp> + <completionHelp> + <path>policy route-map</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="continue"> + <properties> + <help>Jump to a different rule in this route-map on a match</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Rule number</description> + </valueHelp> + </properties> + </leafNode> + #include <include/generic-description.xml.i> + <node name="match"> + <properties> + <help>Route parameters to match</help> + </properties> + <children> + <leafNode name="as-path"> + <properties> + <help>BGP as-path-list to match</help> + <completionHelp> + <path>policy as-path-list</path> + </completionHelp> + </properties> + </leafNode> + <node name="community"> + <properties> + <help>BGP community-list to match</help> + </properties> + <children> + <leafNode name="community-list"> + <properties> + <help>BGP community-list to match</help> + <completionHelp> + <path>policy community-list</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="exact-match"> + <properties> + <help>Community-list to exactly match</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="evpn"> + <properties> + <help>Ethernet Virtual Private Network</help> + </properties> + <children> + <leafNode name="default-route"> + <properties> + <help>Default EVPN type-5 route</help> + <valueless/> + </properties> + </leafNode> + #include <include/bgp/route-distinguisher.xml.i> + <leafNode name="route-type"> + <properties> + <help>Match route-type</help> + <completionHelp> + <list>macip multicast prefix</list> + </completionHelp> + <valueHelp> + <format>macip</format> + <description>mac-ip route</description> + </valueHelp> + <valueHelp> + <format>multicast</format> + <description>IMET route</description> + </valueHelp> + <valueHelp> + <format>prefix</format> + <description>Prefix route</description> + </valueHelp> + <constraint> + <regex>(macip|multicast|prefix)</regex> + </constraint> + </properties> + </leafNode> + #include <include/vni.xml.i> + </children> + </node> + <leafNode name="extcommunity"> + <properties> + <help>BGP extended community to match</help> + <completionHelp> + <path>policy extcommunity-list</path> + </completionHelp> + </properties> + </leafNode> + #include <include/generic-interface.xml.i> + <node name="ip"> + <properties> + <help>IP prefix parameters to match</help> + </properties> + <children> + <node name="address"> + <properties> + <help>IP address of route to match</help> + </properties> + <children> + <leafNode name="access-list"> + <properties> + <help>IP access-list to match</help> + <valueHelp> + <format>u32:1-99</format> + <description>IP standard access list</description> + </valueHelp> + <valueHelp> + <format>u32:100-199</format> + <description>IP extended access list</description> + </valueHelp> + <valueHelp> + <format>u32:1300-1999</format> + <description>IP standard access list (expanded range)</description> + </valueHelp> + <valueHelp> + <format>u32:2000-2699</format> + <description>IP extended access list (expanded range)</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="prefix-list"> + <properties> + <help>IP prefix-list to match</help> + <completionHelp> + <path>policy prefix-list</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="prefix-len"> + <properties> + <help>IP prefix-length to match (can be used for kernel routes only)</help> + <valueHelp> + <format>u32:0-32</format> + <description>Prefix length</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-32"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <!-- T3304 but it overwrite node nexthop + <leafNode name="nexthop"> + <properties> + <help>IP next-hop of route to match</help> + <valueHelp> + <format>ipv4</format> + <description>Next-hop IPv4 router address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> --> + <node name="nexthop"> + <properties> + <help>IP next-hop of route to match</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IP address to match</help> + <valueHelp> + <format>ipv4</format> + <description>Nexthop IP address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="access-list"> + <properties> + <help>IP access-list to match</help> + <valueHelp> + <format>u32:1-99</format> + <description>IP standard access list</description> + </valueHelp> + <valueHelp> + <format>u32:100-199</format> + <description>IP extended access list</description> + </valueHelp> + <valueHelp> + <format>u32:1300-1999</format> + <description>IP standard access list (expanded range)</description> + </valueHelp> + <valueHelp> + <format>u32:2000-2699</format> + <description>IP extended access list (expanded range)</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="prefix-len"> + <properties> + <help>IP prefix-length to match</help> + <valueHelp> + <format>u32:0-32</format> + <description>Prefix length</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-32"/> + </constraint> + </properties> + </leafNode> + <leafNode name="prefix-list"> + <properties> + <help>IP prefix-list to match</help> + <completionHelp> + <path>policy prefix-list</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="type"> + <properties> + <help>Match type</help> + <completionHelp> + <list>blackhole</list> + </completionHelp> + <valueHelp> + <format>blackhole</format> + <description>Blackhole</description> + </valueHelp> + <constraint> + <regex>(blackhole)</regex> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="route-source"> + <properties> + <help>Match advertising source address of route</help> + </properties> + <children> + <leafNode name="access-list"> + <properties> + <help>IP access-list to match</help> + <valueHelp> + <format>u32:1-99</format> + <description>IP standard access list</description> + </valueHelp> + <valueHelp> + <format>u32:100-199</format> + <description>IP extended access list</description> + </valueHelp> + <valueHelp> + <format>u32:1300-1999</format> + <description>IP standard access list (expanded range)</description> + </valueHelp> + <valueHelp> + <format>u32:2000-2699</format> + <description>IP extended access list (expanded range)</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="prefix-list"> + <properties> + <help>IP prefix-list to match</help> + <completionHelp> + <path>policy prefix-list</path> + </completionHelp> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 prefix parameters to match</help> + </properties> + <children> + <node name="address"> + <properties> + <help>IPv6 address of route to match</help> + </properties> + <children> + <leafNode name="access-list"> + <properties> + <help>IPv6 access-list to match</help> + <valueHelp> + <format>txt</format> + <description>IPV6 access list name</description> + </valueHelp> + <completionHelp> + <path>policy access-list6</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="prefix-list"> + <properties> + <help>IPv6 prefix-list to match</help> + <completionHelp> + <path>policy prefix-list6</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="prefix-len"> + <properties> + <help>IPv6 prefix-length to match (can be used for kernel routes only)</help> + <valueHelp> + <format>u32:0-128</format> + <description>Prefix length</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-128"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <!-- T3976 but it overwrite node nexthop + <leafNode name="nexthop"> + <properties> + <help>IPv6 next-hop of route to match</help> + <valueHelp> + <format>ipv6</format> + <description>Nexthop IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> --> + <node name="nexthop"> + <properties> + <help>IPv6 next-hop of route to match</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IPv6 address of next-hop</help> + <valueHelp> + <format>ipv6</format> + <description>Nexthop IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="access-list"> + <properties> + <help>IPv6 access-list to match</help> + <valueHelp> + <format>txt</format> + <description>IPV6 access list name</description> + </valueHelp> + <completionHelp> + <path>policy access-list6</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="prefix-list"> + <properties> + <help>IPv6 prefix-list to match</help> + <completionHelp> + <path>policy prefix-list6</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="type"> + <properties> + <help>Match type</help> + <completionHelp> + <list>blackhole</list> + </completionHelp> + <valueHelp> + <format>blackhole</format> + <description>Blackhole</description> + </valueHelp> + <constraint> + <regex>(blackhole)</regex> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <node name="large-community"> + <properties> + <help>Match BGP large communities</help> + </properties> + <children> + <leafNode name="large-community-list"> + <properties> + <help>BGP large-community-list to match</help> + <completionHelp> + <path>policy large-community-list</path> + </completionHelp> + </properties> + </leafNode> + </children> + </node> + <leafNode name="local-preference"> + <properties> + <help>Local Preference</help> + <valueHelp> + <format>u32:0-4294967295</format> + <description>Local Preference</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="metric"> + <properties> + <help>Metric of route to match</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Route metric</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="origin"> + <properties> + <help>BGP origin code to match</help> + <completionHelp> + <list>egp igp incomplete</list> + </completionHelp> + <valueHelp> + <format>egp</format> + <description>Exterior gateway protocol origin</description> + </valueHelp> + <valueHelp> + <format>igp</format> + <description>Interior gateway protocol origin</description> + </valueHelp> + <valueHelp> + <format>incomplete</format> + <description>Incomplete origin</description> + </valueHelp> + <constraint> + <regex>(egp|igp|incomplete)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="peer"> + <properties> + <help>Peer address to match</help> + <valueHelp> + <format>ipv4</format> + <description>Peer IP address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Peer IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="protocol"> + <properties> + <help>Match protocol via which the route was learnt</help> + <completionHelp> + <list>babel bgp connected isis kernel ospf ospfv3 rip ripng static table vnc</list> + </completionHelp> + <valueHelp> + <format>babel</format> + <description>Babel routing protocol (Babel)</description> + </valueHelp> + <valueHelp> + <format>bgp</format> + <description>Border Gateway Protocol (BGP)</description> + </valueHelp> + <valueHelp> + <format>connected</format> + <description>Connected routes (directly attached subnet or host)</description> + </valueHelp> + <valueHelp> + <format>isis</format> + <description>Intermediate System to Intermediate System (IS-IS)</description> + </valueHelp> + <valueHelp> + <format>kernel</format> + <description>Kernel routes</description> + </valueHelp> + <valueHelp> + <format>ospf</format> + <description>Open Shortest Path First (OSPFv2)</description> + </valueHelp> + <valueHelp> + <format>ospfv3</format> + <description>Open Shortest Path First (IPv6) (OSPFv3)</description> + </valueHelp> + <valueHelp> + <format>rip</format> + <description>Routing Information Protocol (RIP)</description> + </valueHelp> + <valueHelp> + <format>ripng</format> + <description>Routing Information Protocol next-generation (IPv6) (RIPng)</description> + </valueHelp> + <valueHelp> + <format>static</format> + <description>Statically configured routes</description> + </valueHelp> + <valueHelp> + <format>table</format> + <description>Non-main Kernel Routing Table</description> + </valueHelp> + <valueHelp> + <format>vnc</format> + <description>Virtual Network Control (VNC)</description> + </valueHelp> + <constraint> + <regex>(babel|bgp|connected|isis|kernel|ospf|ospfv3|rip|ripng|static|table|vnc)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="rpki"> + <properties> + <help>Match RPKI validation result</help> + <completionHelp> + <list>invalid notfound valid</list> + </completionHelp> + <valueHelp> + <format>invalid</format> + <description>Match invalid entries</description> + </valueHelp> + <valueHelp> + <format>notfound</format> + <description>Match notfound entries</description> + </valueHelp> + <valueHelp> + <format>valid</format> + <description>Match valid entries</description> + </valueHelp> + <constraint> + <regex>(invalid|notfound|valid)</regex> + </constraint> + </properties> + </leafNode> + #include <include/policy/tag.xml.i> + </children> + </node> + <node name="on-match"> + <properties> + <help>Exit policy on matches</help> + </properties> + <children> + <leafNode name="goto"> + <properties> + <help>Rule number to goto on match</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Rule number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="next"> + <properties> + <help>Next sequence number to goto on match</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="set"> + <properties> + <help>Route parameters</help> + </properties> + <children> + <node name="aggregator"> + <properties> + <help>BGP aggregator attribute</help> + </properties> + <children> + <leafNode name="as"> + <properties> + <help>AS number of an aggregation</help> + <valueHelp> + <format>u32:1-4294967295</format> + <description>Rule number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="ip"> + <properties> + <help>IP address of an aggregation</help> + <valueHelp> + <format>ipv4</format> + <description>IP address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="as-path"> + <properties> + <help>Transform BGP AS_PATH attribute</help> + </properties> + <children> + <leafNode name="exclude"> + <properties> + <help>Remove/exclude from the as-path attribute</help> + <completionHelp> + <list>all</list> + </completionHelp> + <valueHelp> + <format>u32:1-4294967295</format> + <description>AS number</description> + </valueHelp> + <valueHelp> + <format>all</format> + <description>Exclude all AS numbers from the as-path</description> + </valueHelp> + <constraint> + <validator name="as-number-list"/> + <regex>(all)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="prepend"> + <properties> + <help>Prepend to the as-path</help> + <valueHelp> + <format>u32:1-4294967295</format> + <description>AS number</description> + </valueHelp> + <constraint> + <validator name="as-number-list"/> + </constraint> + </properties> + </leafNode> + <leafNode name="prepend-last-as"> + <properties> + <help>Use the last AS-number in the as-path</help> + <valueHelp> + <format>u32:1-10</format> + <description>Number of times to insert</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-10"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <leafNode name="atomic-aggregate"> + <properties> + <help>BGP atomic aggregate attribute</help> + <valueless/> + </properties> + </leafNode> + <node name="community"> + <properties> + <help>BGP community attribute</help> + </properties> + <children> + <leafNode name="add"> + <properties> + <help>Add communities to a prefix</help> + #include <include/policy/community-value-list.xml.i> + </properties> + </leafNode> + <leafNode name="replace"> + <properties> + <help>Set communities for a prefix</help> + #include <include/policy/community-value-list.xml.i> + </properties> + </leafNode> + #include <include/policy/community-clear.xml.i> + <leafNode name="delete"> + <properties> + <help>Remove communities defined in a list from a prefix</help> + <completionHelp> + <path>policy community-list</path> + </completionHelp> + <valueHelp> + <description>Community-list</description> + <format>txt</format> + </valueHelp> + </properties> + </leafNode> + </children> + </node> + <node name="large-community"> + <properties> + <help>BGP large community attribute</help> + </properties> + <children> + <leafNode name="add"> + <properties> + <help>Add large communities to a prefix ;</help> + #include <include/policy/large-community-value-list.xml.i> + </properties> + </leafNode> + <leafNode name="replace"> + <properties> + <help>Set large communities for a prefix</help> + #include <include/policy/large-community-value-list.xml.i> + </properties> + </leafNode> + #include <include/policy/community-clear.xml.i> + <leafNode name="delete"> + <properties> + <help>Remove communities defined in a list from a prefix</help> + <completionHelp> + <path>policy large-community-list</path> + </completionHelp> + <valueHelp> + <description>Community-list</description> + <format>txt</format> + </valueHelp> + </properties> + </leafNode> + </children> + </node> + <node name="extcommunity"> + <properties> + <help>BGP extended community attribute</help> + </properties> + <children> + <leafNode name="bandwidth"> + <properties> + <help>Bandwidth value in Mbps</help> + <completionHelp> + <list>cumulative num-multipaths</list> + </completionHelp> + <valueHelp> + <format>u32:1-25600</format> + <description>Bandwidth value in Mbps</description> + </valueHelp> + <valueHelp> + <format>cumulative</format> + <description>Cumulative bandwidth of all multipaths (outbound-only)</description> + </valueHelp> + <valueHelp> + <format>num-multipaths</format> + <description>Internally computed bandwidth based on number of multipaths (outbound-only)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-25600"/> + <regex>(cumulative|num-multipaths)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="bandwidth-non-transitive"> + <properties> + <help>The link bandwidth extended community is encoded as non-transitive</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="rt"> + <properties> + <help>Set route target value</help> + #include <include/policy/extended-community-value-list.xml.i> + </properties> + </leafNode> + <leafNode name="soo"> + <properties> + <help>Set Site of Origin value</help> + #include <include/policy/extended-community-value-list.xml.i> + </properties> + </leafNode> + #include <include/policy/community-clear.xml.i> + </children> + </node> + <leafNode name="distance"> + <properties> + <help>Locally significant administrative distance</help> + <valueHelp> + <format>u32:0-255</format> + <description>Distance value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> + </leafNode> + <node name="evpn"> + <properties> + <help>Ethernet Virtual Private Network</help> + </properties> + <children> + <node name="gateway"> + <properties> + <help>Set gateway IP for prefix advertisement route</help> + </properties> + <children> + <leafNode name="ipv4"> + <properties> + <help>Set gateway IPv4 address</help> + <valueHelp> + <format>ipv4</format> + <description>Gateway IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="ipv6"> + <properties> + <help>Set gateway IPv6 address</help> + <valueHelp> + <format>ipv6</format> + <description>Gateway IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="ip-next-hop"> + <properties> + <help>Nexthop IP address</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script> + <list>unchanged peer-address</list> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IP address</description> + </valueHelp> + <valueHelp> + <format>unchanged</format> + <description>Set the BGP nexthop address as unchanged</description> + </valueHelp> + <valueHelp> + <format>peer-address</format> + <description>Set the BGP nexthop address to the address of the peer</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <regex>(unchanged|peer-address)</regex> + </constraint> + </properties> + </leafNode> + <node name="ipv6-next-hop"> + <properties> + <help>Nexthop IPv6 address</help> + </properties> + <children> + <leafNode name="global"> + <properties> + <help>Nexthop IPv6 global address</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --ipv6</script> + </completionHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="local"> + <properties> + <help>Nexthop IPv6 local address</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --ipv6</script> + </completionHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="peer-address"> + <properties> + <help>Use peer address (for BGP only)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="prefer-global"> + <properties> + <help>Prefer global address as the nexthop</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="l3vpn-nexthop"> + <properties> + <help>Next hop Information</help> + </properties> + <children> + <node name="encapsulation"> + <properties> + <help>Encapsulation options (for BGP only)</help> + </properties> + <children> + <leafNode name="gre"> + <properties> + <help>Accept L3VPN traffic over GRE encapsulation</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="local-preference"> + <properties> + <help>BGP local preference attribute</help> + <valueHelp> + <format>u32:0-4294967295</format> + <description>Local preference value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="metric"> + <properties> + <help>Destination routing protocol metric</help> + <valueHelp> + <format><+/-metric></format> + <description>Add or subtract metric</description> + </valueHelp> + <valueHelp> + <format>u32:0-4294967295</format> + <description>Metric value</description> + </valueHelp> + <valueHelp> + <format><+/-rtt></format> + <description>Add or subtract round trip time</description> + </valueHelp> + <valueHelp> + <format><rtt></format> + <description>Round trip time</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--relative --"/> + <validator name="numeric" argument="--range 0-4294967295"/> + <regex>^[+|-]?rtt$</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="metric-type"> + <properties> + <help>Open Shortest Path First (OSPF) external metric-type</help> + <completionHelp> + <list>type-1 type-2</list> + </completionHelp> + <valueHelp> + <format>type-1</format> + <description>OSPF external type 1 metric</description> + </valueHelp> + <valueHelp> + <format>type-2</format> + <description>OSPF external type 2 metric</description> + </valueHelp> + <constraint> + <regex>(type-1|type-2)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="origin"> + <properties> + <help>Border Gateway Protocl (BGP) origin code</help> + <completionHelp> + <list>igp egp incomplete</list> + </completionHelp> + <valueHelp> + <format>igp</format> + <description>Interior gateway protocol origin</description> + </valueHelp> + <valueHelp> + <format>egp</format> + <description>Exterior gateway protocol origin</description> + </valueHelp> + <valueHelp> + <format>incomplete</format> + <description>Incomplete origin</description> + </valueHelp> + <constraint> + <regex>(igp|egp|incomplete)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="originator-id"> + <properties> + <help>BGP originator ID attribute</help> + <valueHelp> + <format>ipv4</format> + <description>Orignator IP address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="src"> + <properties> + <help>Source address for route</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="table"> + <properties> + <help>Set prefixes to table</help> + <valueHelp> + <format>u32:1-4294967295</format> + <description>Table value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> + </leafNode> + #include <include/policy/tag.xml.i> + <leafNode name="weight"> + <properties> + <help>BGP weight attribute</help> + <valueHelp> + <format>u32:0-4294967295</format> + <description>BGP weight</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/policy_local-route.xml.in b/interface-definitions/policy_local-route.xml.in new file mode 100644 index 0000000..7a01915 --- /dev/null +++ b/interface-definitions/policy_local-route.xml.in @@ -0,0 +1,156 @@ +<?xml version="1.0"?> +<!-- Policy local-route --> +<interfaceDefinition> + <node name="policy"> + <children> + <node name="local-route" owner="${vyos_conf_scripts_dir}/policy_local-route.py"> + <properties> + <help>IPv4 policy route of local traffic</help> + <priority>500</priority> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>Policy local-route rule set number</help> + <valueHelp> + <!-- table main with prio 32766 --> + <format>u32:1-32765</format> + <description>Local-route rule number (1-32765)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-32765"/> + </constraint> + </properties> + <children> + <node name="set"> + <properties> + <help>Packet modifications</help> + </properties> + <children> + <leafNode name="table"> + <properties> + <help>Routing table to forward packet with</help> + <valueHelp> + <format>u32:1-200</format> + <description>Table number</description> + </valueHelp> + <completionHelp> + <list>main</list> + </completionHelp> + </properties> + </leafNode> + </children> + </node> + <leafNode name="fwmark"> + <properties> + <help>Match fwmark value</help> + <valueHelp> + <format>u32:1-2147483647</format> + <description>Address to match against</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2147483647"/> + </constraint> + </properties> + </leafNode> + #include <include/policy/local-route_rule_protocol.xml.i> + <node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/policy/local-route_rule_ipv4_address.xml.i> + #include <include/port-number.xml.i> + </children> + </node> + <node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/policy/local-route_rule_ipv4_address.xml.i> + #include <include/port-number.xml.i> + </children> + </node> + #include <include/interface/inbound-interface.xml.i> + </children> + </tagNode> + </children> + </node> + <node name="local-route6" owner="${vyos_conf_scripts_dir}/policy_local-route.py"> + <properties> + <help>IPv6 policy route of local traffic</help> + <priority>500</priority> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>IPv6 policy local-route rule set number</help> + <valueHelp> + <!-- table main with prio 32766 --> + <format>u32:1-32765</format> + <description>Local-route rule number (1-32765)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-32765"/> + </constraint> + </properties> + <children> + <node name="set"> + <properties> + <help>Packet modifications</help> + </properties> + <children> + <leafNode name="table"> + <properties> + <help>Routing table to forward packet with</help> + <valueHelp> + <format>u32:1-200</format> + <description>Table number</description> + </valueHelp> + <completionHelp> + <list>main</list> + </completionHelp> + </properties> + </leafNode> + </children> + </node> + <leafNode name="fwmark"> + <properties> + <help>Match fwmark value</help> + <valueHelp> + <format>u32:1-2147483647</format> + <description>Address to match against</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2147483647"/> + </constraint> + </properties> + </leafNode> + #include <include/policy/local-route_rule_protocol.xml.i> + <node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/policy/local-route_rule_ipv6_address.xml.i> + #include <include/port-number.xml.i> + </children> + </node> + <node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/policy/local-route_rule_ipv6_address.xml.i> + #include <include/port-number.xml.i> + </children> + </node> + #include <include/interface/inbound-interface.xml.i> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/policy_route.xml.in b/interface-definitions/policy_route.xml.in new file mode 100644 index 0000000..9cc2254 --- /dev/null +++ b/interface-definitions/policy_route.xml.in @@ -0,0 +1,117 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="policy"> + <children> + <tagNode name="route6" owner="${vyos_conf_scripts_dir}/policy_route.py"> + <properties> + <help>Policy route rule set name for IPv6</help> + <constraint> + <regex>[a-zA-Z0-9][\w\-\.]*</regex> + </constraint> + <priority>201</priority> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-interface-multi-wildcard.xml.i> + <tagNode name="rule"> + <properties> + <help>Policy rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number of policy rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Policy rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + <node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/firewall/source-destination-group-ipv6.xml.i> + #include <include/firewall/port.xml.i> + </children> + </node> + <node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/firewall/source-destination-group-ipv6.xml.i> + #include <include/firewall/port.xml.i> + </children> + </node> + #include <include/policy/route-common.xml.i> + #include <include/policy/route-ipv6.xml.i> + #include <include/firewall/dscp.xml.i> + #include <include/firewall/packet-options.xml.i> + #include <include/firewall/hop-limit.xml.i> + #include <include/firewall/connection-mark.xml.i> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="route" owner="${vyos_conf_scripts_dir}/policy_route.py"> + <properties> + <help>Policy route rule set name for IPv4</help> + <constraint> + <regex>[a-zA-Z0-9][\w\-\.]*</regex> + </constraint> + <priority>201</priority> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-interface-multi-wildcard.xml.i> + <tagNode name="rule"> + <properties> + <help>Policy rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number of policy rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Policy rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + <node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/address.xml.i> + #include <include/firewall/source-destination-group.xml.i> + #include <include/firewall/port.xml.i> + </children> + </node> + <node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/address.xml.i> + #include <include/firewall/source-destination-group.xml.i> + #include <include/firewall/port.xml.i> + </children> + </node> + #include <include/policy/route-common.xml.i> + #include <include/policy/route-ipv4.xml.i> + #include <include/firewall/dscp.xml.i> + #include <include/firewall/packet-options.xml.i> + #include <include/firewall/ttl.xml.i> + #include <include/firewall/connection-mark.xml.i> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_babel.xml.in b/interface-definitions/protocols_babel.xml.in new file mode 100644 index 0000000..49fffe2 --- /dev/null +++ b/interface-definitions/protocols_babel.xml.in @@ -0,0 +1,254 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="babel" owner="${vyos_conf_scripts_dir}/protocols_babel.py"> + <properties> + <help>Babel Routing Protocol</help> + <priority>650</priority> + </properties> + <children> + <node name="parameters"> + <properties> + <help>Babel-specific parameters</help> + </properties> + <children> + <leafNode name="diversity"> + <properties> + <help>Enable diversity-aware routing</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="diversity-factor"> + <properties> + <help>Multiplicative factor used for diversity routing</help> + <valueHelp> + <format>u32:1-256</format> + <description>Multiplicative factor, in units of 1/256</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-256"/> + </constraint> + </properties> + <defaultValue>256</defaultValue> + </leafNode> + <leafNode name="resend-delay"> + <properties> + <help>Time before resending a message</help> + <valueHelp> + <format>u32:20-655340</format> + <description>Milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 20-655340"/> + </constraint> + </properties> + <defaultValue>2000</defaultValue> + </leafNode> + <leafNode name="smoothing-half-life"> + <properties> + <help>Smoothing half-life</help> + <valueHelp> + <format>u32:0-65534</format> + <description>Seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65534"/> + </constraint> + </properties> + <defaultValue>4</defaultValue> + </leafNode> + </children> + </node> + #include <include/babel/interface.xml.i> + <node name="redistribute"> + <properties> + <help>Redistribute information from another routing protocol</help> + </properties> + <children> + <node name="ipv4"> + <properties> + <help>Redistribute IPv4 routes</help> + </properties> + <children> + <leafNode name="bgp"> + <properties> + <help>Redistribute BGP routes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="connected"> + <properties> + <help>Redistribute connected routes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="eigrp"> + <properties> + <help>Redistribute EIGRP routes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="isis"> + <properties> + <help>Redistribute IS-IS routes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="kernel"> + <properties> + <help>Redistribute kernel routes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="nhrp"> + <properties> + <help>Redistribute NHRP routes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ospf"> + <properties> + <help>Redistribute OSPF routes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="rip"> + <properties> + <help>Redistribute RIP routes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="static"> + <properties> + <help>Redistribute static routes</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="ipv6"> + <properties> + <help>Redistribute IPv6 routes</help> + </properties> + <children> + <leafNode name="bgp"> + <properties> + <help>Redistribute BGP routes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="connected"> + <properties> + <help>Redistribute connected routes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="isis"> + <properties> + <help>Redistribute IS-IS routes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="kernel"> + <properties> + <help>Redistribute kernel routes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="nhrp"> + <properties> + <help>Redistribute NHRP routes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ospfv3"> + <properties> + <help>Redistribute OSPFv3 routes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ripng"> + <properties> + <help>Redistribute RIPng routes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="static"> + <properties> + <help>Redistribute static routes</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <node name="distribute-list"> + <properties> + <help>Filter networks in routing updates</help> + </properties> + <children> + <node name="ipv4"> + <properties> + <help>Filter IPv4 routes</help> + </properties> + <children> + #include <include/rip/access-list.xml.i> + <tagNode name="interface"> + <properties> + <help>Apply filtering to an interface</help> + <valueHelp> + <format>txt</format> + <description>Apply filtering to an interface</description> + </valueHelp> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + #include <include/rip/access-list.xml.i> + #include <include/rip/prefix-list.xml.i> + </children> + </tagNode> + #include <include/rip/prefix-list.xml.i> + </children> + </node> + <node name="ipv6"> + <properties> + <help>Filter IPv6 routes</help> + </properties> + <children> + #include <include/rip/access-list6.xml.i> + <tagNode name="interface"> + <properties> + <help>Apply filtering to an interface</help> + <valueHelp> + <format>txt</format> + <description>Apply filtering to an interface</description> + </valueHelp> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + #include <include/rip/access-list6.xml.i> + #include <include/rip/prefix-list6.xml.i> + </children> + </tagNode> + #include <include/rip/prefix-list6.xml.i> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_bfd.xml.in b/interface-definitions/protocols_bfd.xml.in new file mode 100644 index 0000000..9048cf5 --- /dev/null +++ b/interface-definitions/protocols_bfd.xml.in @@ -0,0 +1,85 @@ +<?xml version="1.0"?> +<!-- Bidirectional Forwarding Detection (BFD) configuration --> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="bfd" owner="${vyos_conf_scripts_dir}/protocols_bfd.py"> + <properties> + <help>Bidirectional Forwarding Detection (BFD)</help> + <priority>820</priority> + </properties> + <children> + <tagNode name="peer"> + <properties> + <help>Configures BFD peer to listen and talk to</help> + <valueHelp> + <format>ipv4</format> + <description>BFD peer IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>BFD peer IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + #include <include/bfd/profile.xml.i> + <node name="source"> + <properties> + <help>Bind listener to specified interface/address, mandatory for IPv6</help> + </properties> + <children> + #include <include/generic-interface.xml.i> + <leafNode name="address"> + <properties> + <help>Local address to bind our peer listener to</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>Local IPv4 address used to connect to the peer</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Local IPv6 address used to connect to the peer</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + #include <include/bfd/common.xml.i> + <leafNode name="multihop"> + <properties> + <help>Allow this BFD peer to not be directly connected</help> + <valueless/> + </properties> + </leafNode> + #include <include/interface/vrf.xml.i> + </children> + </tagNode> + <tagNode name="profile"> + <properties> + <help>Configure BFD profile used by individual peer</help> + <valueHelp> + <format>txt</format> + <description>Name of BFD profile</description> + </valueHelp> + <constraint> + <regex>[-_a-zA-Z0-9]{1,32}</regex> + </constraint> + </properties> + <children> + #include <include/bfd/common.xml.i> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_bgp.xml.in b/interface-definitions/protocols_bgp.xml.in new file mode 100644 index 0000000..e1a8229 --- /dev/null +++ b/interface-definitions/protocols_bgp.xml.in @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="bgp" owner="${vyos_conf_scripts_dir}/protocols_bgp.py"> + <properties> + <help>Border Gateway Protocol (BGP)</help> + <priority>820</priority> + </properties> + <children> + #include <include/bgp/protocol-common-config.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_eigrp.xml.in b/interface-definitions/protocols_eigrp.xml.in new file mode 100644 index 0000000..88a881a --- /dev/null +++ b/interface-definitions/protocols_eigrp.xml.in @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!-- Enhanced Interior Gateway Routing Protocol (EIGRP) configuration --> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="eigrp" owner="${vyos_conf_scripts_dir}/protocols_eigrp.py"> + <properties> + <help>Enhanced Interior Gateway Routing Protocol (EIGRP)</help> + <priority>820</priority> + </properties> + <children> + #include <include/eigrp/protocol-common-config.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_failover.xml.in b/interface-definitions/protocols_failover.xml.in new file mode 100644 index 0000000..f709759 --- /dev/null +++ b/interface-definitions/protocols_failover.xml.in @@ -0,0 +1,141 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="failover" owner="${vyos_conf_scripts_dir}/protocols_failover.py"> + <properties> + <help>Failover Routing</help> + <priority>490</priority> + </properties> + <children> + <tagNode name="route"> + <properties> + <help>Failover IPv4 route</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 failover route</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + <tagNode name="next-hop"> + <properties> + <help>Next-hop IPv4 router address</help> + <valueHelp> + <format>ipv4</format> + <description>Next-hop router address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <children> + <node name="check"> + <properties> + <help>Check target options</help> + </properties> + <children> + <leafNode name="policy"> + <properties> + <help>Policy for check targets</help> + <completionHelp> + <list>any-available all-available</list> + </completionHelp> + <valueHelp> + <format>all-available</format> + <description>All targets must be alive</description> + </valueHelp> + <valueHelp> + <format>any-available</format> + <description>Any target must be alive</description> + </valueHelp> + <constraint> + <regex>(all-available|any-available)</regex> + </constraint> + </properties> + <defaultValue>any-available</defaultValue> + </leafNode> + #include <include/port-number.xml.i> + <leafNode name="target"> + <properties> + <help>Check target address</help> + <valueHelp> + <format>ipv4</format> + <description>Address to check</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="timeout"> + <properties> + <help>Timeout between checks</help> + <valueHelp> + <format>u32:1-300</format> + <description>Timeout in seconds between checks</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <defaultValue>10</defaultValue> + </leafNode> + <leafNode name="type"> + <properties> + <help>Check type</help> + <completionHelp> + <list>arp icmp tcp</list> + </completionHelp> + <valueHelp> + <format>arp</format> + <description>Check target by ARP</description> + </valueHelp> + <valueHelp> + <format>icmp</format> + <description>Check target by ICMP</description> + </valueHelp> + <valueHelp> + <format>tcp</format> + <description>Check target by TCP</description> + </valueHelp> + <constraint> + <regex>(arp|icmp|tcp)</regex> + </constraint> + </properties> + <defaultValue>icmp</defaultValue> + </leafNode> + </children> + </node> + #include <include/static/static-route-interface.xml.i> + <leafNode name="metric"> + <properties> + <help>Route metric for this gateway</help> + <valueHelp> + <format>u32:1-255</format> + <description>Route metric</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <defaultValue>1</defaultValue> + </leafNode> + <leafNode name="onlink"> + <properties> + <help>The next hop is directly connected to the interface, even if it does not match interface prefix</help> + <valueless/> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_igmp-proxy.xml.in b/interface-definitions/protocols_igmp-proxy.xml.in new file mode 100644 index 0000000..5cde484 --- /dev/null +++ b/interface-definitions/protocols_igmp-proxy.xml.in @@ -0,0 +1,97 @@ +<?xml version="1.0"?> +<!-- IGMP Proxy configuration --> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="igmp-proxy" owner="${vyos_conf_scripts_dir}/protocols_igmp-proxy.py"> + <properties> + <help>Internet Group Management Protocol (IGMP) proxy parameters</help> + <priority>740</priority> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <leafNode name="disable-quickleave"> + <properties> + <help>Option to disable "quickleave"</help> + <valueless/> + </properties> + </leafNode> + <tagNode name="interface"> + <properties> + <help>Interface for IGMP proxy</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + <leafNode name="alt-subnet"> + <properties> + <help>Unicast source networks allowed for multicast traffic to be proxyed</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 network</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="role"> + <properties> + <help>IGMP interface role</help> + <completionHelp> + <list>upstream downstream disabled</list> + </completionHelp> + <valueHelp> + <format>upstream</format> + <description>Upstream interface (only 1 allowed)</description> + </valueHelp> + <valueHelp> + <format>downstream</format> + <description>Downstream interface(s)</description> + </valueHelp> + <valueHelp> + <format>disabled</format> + <description>Disabled interface</description> + </valueHelp> + <constraint> + <regex>(upstream|downstream|disabled)</regex> + </constraint> + </properties> + <defaultValue>downstream</defaultValue> + </leafNode> + <leafNode name="threshold"> + <properties> + <help>TTL threshold</help> + <valueHelp> + <format>u32:1-255</format> + <description>TTL threshold for the interfaces</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + <constraintErrorMessage>Threshold must be between 1 and 255</constraintErrorMessage> + </properties> + <defaultValue>1</defaultValue> + </leafNode> + <leafNode name="whitelist"> + <properties> + <help>Group to whitelist</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 network</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_isis.xml.in b/interface-definitions/protocols_isis.xml.in new file mode 100644 index 0000000..e0bc47b --- /dev/null +++ b/interface-definitions/protocols_isis.xml.in @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="isis" owner="${vyos_conf_scripts_dir}/protocols_isis.py"> + <properties> + <help>Intermediate System to Intermediate System (IS-IS)</help> + <priority>610</priority> + </properties> + <children> + #include <include/isis/protocol-common-config.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_mpls.xml.in b/interface-definitions/protocols_mpls.xml.in new file mode 100644 index 0000000..831601f --- /dev/null +++ b/interface-definitions/protocols_mpls.xml.in @@ -0,0 +1,560 @@ +<?xml version="1.0"?> +<!-- Multiprotocol Label Switching (MPLS) configuration --> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="mpls" owner="${vyos_conf_scripts_dir}/protocols_mpls.py"> + <properties> + <help>Multiprotocol Label Switching (MPLS)</help> + <priority>490</priority> + </properties> + <children> + <node name="ldp"> + <properties> + <help>Label Distribution Protocol (LDP)</help> + </properties> + <children> + #include <include/router-id.xml.i> + <node name="allocation"> + <properties> + <help>Forwarding equivalence class allocation from local routes</help> + </properties> + <children> + <node name="ipv4"> + <properties> + <help>IPv4 routes</help> + </properties> + <children> + <leafNode name="access-list"> + <properties> + <help>Access-list number</help> + <valueHelp> + <format>u32:1-2699</format> + <description>Access list number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2699"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 routes</help> + </properties> + <children> + <leafNode name="access-list6"> + <properties> + <help>Access-list6 number</help> + <valueHelp> + <format>u32:1-2699</format> + <description>Access list number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2699"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <tagNode name="neighbor"> + <properties> + <help>LDP neighbor parameters</help> + <valueHelp> + <format>ipv4</format> + <description>Neighbor IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <children> + <leafNode name="password"> + <properties> + <help>Neighbor password</help> + </properties> + </leafNode> + <leafNode name="ttl-security"> + <properties> + <help>Neighbor TTL security</help> + <completionHelp> + <list>disable</list> + </completionHelp> + <valueHelp> + <format>u32:1-254</format> + <description>TTL</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable neighbor TTL security</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="session-holdtime"> + <properties> + <help>Session IPv4 hold time</help> + <valueHelp> + <format>u32:15-65535</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 15-65535"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + <node name="discovery"> + <properties> + <help>Discovery parameters</help> + <valueHelp> + <format>ipv4</format> + <description>Discovery parameters</description> + </valueHelp> + </properties> + <children> + <leafNode name="hello-ipv4-holdtime"> + <properties> + <help>Hello IPv4 hold time</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="hello-ipv4-interval"> + <properties> + <help>Hello IPv4 interval</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="hello-ipv6-holdtime"> + <properties> + <help>Hello IPv6 hold time</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="hello-ipv6-interval"> + <properties> + <help>Hello IPv6 interval</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="session-ipv4-holdtime"> + <properties> + <help>Session IPv4 hold time</help> + <valueHelp> + <format>u32:15-65535</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 15-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="session-ipv6-holdtime"> + <properties> + <help>Session IPv6 hold time</help> + <valueHelp> + <format>u32:15-65535</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 15-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="transport-ipv4-address"> + <properties> + <help>Transport IPv4 address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 bind as transport</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="transport-ipv6-address"> + <properties> + <help>Transport IPv6 address</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 bind as transport</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="targeted-neighbor"> + <properties> + <help>Targeted LDP neighbor/session parameters</help> + </properties> + <children> + <node name="ipv4"> + <properties> + <help>Targeted IPv4 neighbor/session parameters</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>Neighbor/session address</help> + <valueHelp> + <format>ipv4</format> + <description>Neighbor/session address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="enable"> + <properties> + <help>Accept and respond to targeted hellos</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="hello-interval"> + <properties> + <help>Hello interval</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="hello-holdtime"> + <properties> + <help>Hello hold time</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="ipv6"> + <properties> + <help>Targeted IPv6 neighbor/session parameters</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>Neighbor/session address</help> + <valueHelp> + <format>ipv6</format> + <description>Neighbor/session address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="enable"> + <properties> + <help>Accept and respond to targeted hellos</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="hello-interval"> + <properties> + <help>Hello interval</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="hello-holdtime"> + <properties> + <help>Hello hold time</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <node name="parameters"> + <properties> + <help>Label Distribution Protocol miscellaneous parameters</help> + </properties> + <children> + <leafNode name="cisco-interop-tlv"> + <properties> + <help>Enable Cisco non-compliant format capability TLV</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="transport-prefer-ipv4"> + <properties> + <help>Prefer IPv4 for TCP peer transport connection</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ordered-control"> + <properties> + <help>Enable LDP ordered label distribution control mode</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="export"> + <properties> + <help>Export parameters</help> + </properties> + <children> + <node name="ipv4"> + <properties> + <help>IPv4 parameters</help> + </properties> + <children> + <leafNode name="explicit-null"> + <properties> + <help>Explicit-Null Label</help> + <valueless/> + </properties> + </leafNode> + <node name="export-filter"> + <properties> + <help>Forwarding equivalence class export filter</help> + </properties> + <children> + <leafNode name="filter-access-list"> + <properties> + <help>Access-list number to apply FEC filtering</help> + <valueHelp> + <format>u32:1-2699</format> + <description>Access list number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2699"/> + </constraint> + </properties> + </leafNode> + <leafNode name="neighbor-access-list"> + <properties> + <help>Access-list number for IPv4 neighbor selection to apply filtering</help> + <valueHelp> + <format>u32:1-2699</format> + <description>Access list number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2699"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 parameters</help> + </properties> + <children> + <leafNode name="explicit-null"> + <properties> + <help>Explicit-Null Label</help> + <valueless/> + </properties> + </leafNode> + <node name="export-filter"> + <properties> + <help>Forwarding equivalence class export filter</help> + </properties> + <children> + <leafNode name="filter-access-list6"> + <properties> + <help>Access-list6 number to apply FEC filtering</help> + <valueHelp> + <format>u32:1-2699</format> + <description>Access list number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2699"/> + </constraint> + </properties> + </leafNode> + <leafNode name="neighbor-access-list6"> + <properties> + <help>Access-list6 number for IPv6 neighbor selection to apply filtering</help> + <valueHelp> + <format>u32:1-2699</format> + <description>Access list number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2699"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="import"> + <properties> + <help>Import parameters</help> + </properties> + <children> + <node name="ipv4"> + <properties> + <help>IPv4 parameters</help> + </properties> + <children> + <node name="import-filter"> + <properties> + <help>Forwarding equivalence class import filter</help> + </properties> + <children> + <leafNode name="filter-access-list"> + <properties> + <help>Access-list number to apply FEC filtering</help> + <valueHelp> + <format>u32:1-2699</format> + <description>Access list number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2699"/> + </constraint> + </properties> + </leafNode> + <leafNode name="neighbor-access-list"> + <properties> + <help>Access-list number for IPv4 neighbor selection to apply filtering</help> + <valueHelp> + <format>u32:1-2699</format> + <description>Access list number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2699"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 parameters</help> + </properties> + <children> + <node name="import-filter"> + <properties> + <help>Forwarding equivalence class import filter</help> + </properties> + <children> + <leafNode name="filter-access-list6"> + <properties> + <help>Access-list6 number to apply FEC filtering</help> + <valueHelp> + <format>u32:1-2699</format> + <description>Access list number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2699"/> + </constraint> + </properties> + </leafNode> + <leafNode name="neighbor-access-list6"> + <properties> + <help>Access-list6 number for IPv6 neighbor selection to apply filtering</help> + <valueHelp> + <format>u32:1-2699</format> + <description>Access list number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2699"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> + #include <include/generic-interface-multi.xml.i> + </children> + </node> + <node name="parameters"> + <properties> + <help>Multiprotocol Label Switching miscellaneous parameters</help> + </properties> + <children> + <leafNode name="no-propagate-ttl"> + <properties> + <help>Disable copy of IP TTL to MPLS TTL</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="maximum-ttl"> + <properties> + <help>Maximum TTL for MPLS packets</help> + <valueHelp> + <format>u32:1-255</format> + <description>Maximum hops allowed</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + #include <include/generic-interface-multi.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_nhrp.xml.in b/interface-definitions/protocols_nhrp.xml.in new file mode 100644 index 0000000..d7663c0 --- /dev/null +++ b/interface-definitions/protocols_nhrp.xml.in @@ -0,0 +1,138 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="nhrp" owner="${vyos_conf_scripts_dir}/protocols_nhrp.py"> + <properties> + <help>Next Hop Resolution Protocol (NHRP) parameters</help> + <priority>680</priority> + </properties> + <children> + <tagNode name="tunnel"> + <properties> + <help>Tunnel for NHRP</help> + <constraint> + <regex>tun[0-9]+</regex> + </constraint> + <valueHelp> + <format>tunN</format> + <description>NHRP tunnel name</description> + </valueHelp> + </properties> + <children> + <leafNode name="cisco-authentication"> + <properties> + <help>Pass phrase for cisco authentication</help> + <valueHelp> + <format>txt</format> + <description>Pass phrase for cisco authentication</description> + </valueHelp> + <constraint> + <regex>[^[:space:]]{1,8}</regex> + </constraint> + <constraintErrorMessage>Password should contain up to eight non-whitespace characters</constraintErrorMessage> + </properties> + </leafNode> + <tagNode name="dynamic-map"> + <properties> + <help>Set an HUB tunnel address</help> + <valueHelp> + <format>ipv4net</format> + <description>Set the IP address and prefix length</description> + </valueHelp> + </properties> + <children> + <leafNode name="nbma-domain-name"> + <properties> + <help>Set HUB fqdn (nbma-address - fqdn)</help> + <valueHelp> + <format><fqdn></format> + <description>Set the external HUB fqdn</description> + </valueHelp> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="holding-time"> + <properties> + <help>Holding time in seconds</help> + </properties> + </leafNode> + <tagNode name="map"> + <properties> + <help>Set an HUB tunnel address</help> + </properties> + <children> + <leafNode name="cisco"> + <properties> + <help>If the statically mapped peer is running Cisco IOS, specify this</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="nbma-address"> + <properties> + <help>Set HUB address (nbma-address - external hub address or fqdn)</help> + </properties> + </leafNode> + <leafNode name="register"> + <properties> + <help>Specifies that Registration Request should be sent to this peer on startup</help> + <valueless/> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="multicast"> + <properties> + <help>Set multicast for NHRP</help> + <completionHelp> + <list>dynamic nhs</list> + </completionHelp> + <constraint> + <regex>(dynamic|nhs)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="non-caching"> + <properties> + <help>This can be used to reduce memory consumption on big NBMA subnets</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="redirect"> + <properties> + <help>Enable sending of Cisco style NHRP Traffic Indication packets</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="shortcut-destination"> + <properties> + <help>This instructs opennhrp to reply with authorative answers on NHRP Resolution Requests destined to addresses in this interface</help> + <valueless/> + </properties> + </leafNode> + <tagNode name="shortcut-target"> + <properties> + <help>Defines an off-NBMA network prefix for which the GRE interface will act as a gateway</help> + </properties> + <children> + <leafNode name="holding-time"> + <properties> + <help>Holding time in seconds</help> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="shortcut"> + <properties> + <help>Enable creation of shortcut routes. A received NHRP Traffic Indication will trigger the resolution and establishment of a shortcut route</help> + <valueless/> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_openfabric.xml.in b/interface-definitions/protocols_openfabric.xml.in new file mode 100644 index 0000000..8120036 --- /dev/null +++ b/interface-definitions/protocols_openfabric.xml.in @@ -0,0 +1,218 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="openfabric" owner="${vyos_conf_scripts_dir}/protocols_openfabric.py"> + <properties> + <help>OpenFabric protocol</help> + <priority>680</priority> + </properties> + <children> + #include <include/net.xml.i> + <tagNode name="domain"> + <properties> + <help>OpenFabric process name</help> + <valueHelp> + <format>txt</format> + <description>Domain name</description> + </valueHelp> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Interface params</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + <node name="address-family"> + <properties> + <help>Openfabric address family</help> + </properties> + <children> + <leafNode name="ipv4"> + <properties> + <help>IPv4 OpenFabric</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ipv6"> + <properties> + <help>IPv6 OpenFabric</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="csnp-interval"> + <properties> + <help>Complete Sequence Number Packets (CSNP) interval</help> + <valueHelp> + <format>u32:1-600</format> + <description>CSNP interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-600"/> + </constraint> + </properties> + </leafNode> + <leafNode name="hello-interval"> + <properties> + <help>Hello interval</help> + <valueHelp> + <format>u32:1-600</format> + <description>Hello interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-600"/> + </constraint> + </properties> + </leafNode> + <leafNode name="hello-multiplier"> + <properties> + <help>Multiplier for Hello holding time</help> + <valueHelp> + <format>u32:2-100</format> + <description>Multiplier for Hello holding time</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 2-100"/> + </constraint> + </properties> + </leafNode> + <leafNode name="metric"> + <properties> + <help>Interface metric value</help> + <valueHelp> + <format>u32:0-16777215</format> + <description>Interface metric value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16777215"/> + </constraint> + </properties> + </leafNode> + <leafNode name="passive"> + <properties> + <help>Do not initiate adjacencies to the interface</help> + <valueless/> + </properties> + </leafNode> + <node name="password"> + <properties> + <help>Authentication password for the interface</help> + </properties> + <children> + #include <include/openfabric/password.xml.i> + </children> + </node> + <leafNode name="psnp-interval"> + <properties> + <help>Partial Sequence Number Packets (PSNP) interval</help> + <valueHelp> + <format>u32:0-120</format> + <description>PSNP interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-120"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + <node name="domain-password"> + <properties> + <help>Authentication password for a routing domain</help> + </properties> + <children> + #include <include/openfabric/password.xml.i> + </children> + </node> + #include <include/log-adjacency-changes.xml.i> + <leafNode name="set-overload-bit"> + <properties> + <help>Overload bit to avoid any transit traffic</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="purge-originator"> + <properties> + <help>RFC 6232 purge originator identification</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="fabric-tier"> + <properties> + <help>Static tier number to advertise as location in the fabric</help> + <valueHelp> + <format>u32:0-14</format> + <description>Static tier number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-14"/> + </constraint> + </properties> + </leafNode> + <leafNode name="lsp-gen-interval"> + <properties> + <help>Minimum interval between regenerating same link-state packet (LSP)</help> + <valueHelp> + <format>u32:1-120</format> + <description>Minimum interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-120"/> + </constraint> + </properties> + </leafNode> + <leafNode name="lsp-refresh-interval"> + <properties> + <help>Link-state packet (LSP) refresh interval</help> + <valueHelp> + <format>u32:1-65235</format> + <description>LSP refresh interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65235"/> + </constraint> + </properties> + </leafNode> + <leafNode name="max-lsp-lifetime"> + <properties> + <help>Maximum link-state packet lifetime</help> + <valueHelp> + <format>u32:360-65535</format> + <description>Maximum LSP lifetime in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 360-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="spf-interval"> + <properties> + <help>Minimum interval between SPF calculations</help> + <valueHelp> + <format>u32:1-120</format> + <description>Interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-120"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_ospf.xml.in b/interface-definitions/protocols_ospf.xml.in new file mode 100644 index 0000000..b3c063d --- /dev/null +++ b/interface-definitions/protocols_ospf.xml.in @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="ospf" owner="${vyos_conf_scripts_dir}/protocols_ospf.py"> + <properties> + <help>Open Shortest Path First (OSPF)</help> + <priority>620</priority> + </properties> + <children> + #include <include/ospf/protocol-common-config.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_ospfv3.xml.in b/interface-definitions/protocols_ospfv3.xml.in new file mode 100644 index 0000000..2b98ffa --- /dev/null +++ b/interface-definitions/protocols_ospfv3.xml.in @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="ospfv3" owner="${vyos_conf_scripts_dir}/protocols_ospfv3.py"> + <properties> + <help>Open Shortest Path First (OSPF) for IPv6</help> + <priority>620</priority> + </properties> + <children> + #include <include/ospfv3/protocol-common-config.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_pim.xml.in b/interface-definitions/protocols_pim.xml.in new file mode 100644 index 0000000..4a20c0d --- /dev/null +++ b/interface-definitions/protocols_pim.xml.in @@ -0,0 +1,210 @@ +<?xml version="1.0"?> +<!-- Protocol Independent Multicast (PIM) configuration --> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="pim" owner="${vyos_conf_scripts_dir}/protocols_pim.py"> + <properties> + <help>Protocol Independent Multicast (PIM) and IGMP</help> + <priority>400</priority> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>PIM interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + #include <include/bfd/bfd.xml.i> + #include <include/pim/bsm.xml.i> + #include <include/pim/dr-priority.xml.i> + #include <include/pim/hello.xml.i> + #include <include/pim/passive.xml.i> + #include <include/source-address-ipv4.xml.i> + <node name="igmp"> + <properties> + <help>Internet Group Management Protocol (IGMP) options</help> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <tagNode name="join"> + <properties> + <help>IGMP join multicast group</help> + <valueHelp> + <format>ipv4</format> + <description>Multicast group address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <children> + #include <include/source-address-ipv4-multi.xml.i> + </children> + </tagNode> + <leafNode name="query-interval"> + <properties> + <help>IGMP host query interval</help> + <valueHelp> + <format>u32:1-1800</format> + <description>Query interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-1800"/> + </constraint> + </properties> + </leafNode> + <leafNode name="query-max-response-time"> + <properties> + <help>IGMP max query response time</help> + <valueHelp> + <format>u32:10-250</format> + <description>Query response value in deci-seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 10-250"/> + </constraint> + </properties> + </leafNode> + <leafNode name="version"> + <properties> + <help>Interface IGMP version</help> + <completionHelp> + <list>2 3</list> + </completionHelp> + <valueHelp> + <format>2</format> + <description>IGMP version 2</description> + </valueHelp> + <valueHelp> + <format>3</format> + <description>IGMP version 3</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 2-3"/> + </constraint> + </properties> + <defaultValue>3</defaultValue> + </leafNode> + </children> + </node> + </children> + </tagNode> + <node name="ecmp"> + <properties> + <help>Enable PIM ECMP</help> + </properties> + <children> + <leafNode name="rebalance"> + <properties> + <help>Enable PIM ECMP Rebalance</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="igmp"> + <properties> + <help>Internet Group Management Protocol (IGMP) options</help> + </properties> + <children> + <leafNode name="watermark-warning"> + <properties> + <help>Configure group limit for watermark warning</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Group count to generate watermark warning</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + #include <include/pim/join-prune-interval.xml.i> + #include <include/pim/keep-alive-timer.xml.i> + #include <include/pim/packets.xml.i> + #include <include/pim/register-suppress-time.xml.i> + <node name="register-accept-list"> + <properties> + <help>Only accept registers from a specific source prefix list</help> + </properties> + <children> + #include <include/policy/prefix-list.xml.i> + </children> + </node> + <node name="rp"> + <properties> + <help>Rendezvous Point</help> + </properties> + <children> + <tagNode name="address"> + <properties> + <help>Rendezvous Point address</help> + <valueHelp> + <format>ipv4</format> + <description>Rendezvous Point address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <children> + <leafNode name="group"> + <properties> + <help>Group Address range</help> + <valueHelp> + <format>ipv4net</format> + <description>Group Address range RFC 3171</description> + </valueHelp> + <constraint> + <validator name="ip-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + #include <include/pim/keep-alive-timer.xml.i> + </children> + </node> + <leafNode name="no-v6-secondary"> + <properties> + <help>Disable IPv6 secondary address in hello packets</help> + <valueless/> + </properties> + </leafNode> + <node name="spt-switchover"> + <properties> + <help>Shortest-path tree (SPT) switchover</help> + </properties> + <children> + <node name="infinity-and-beyond"> + <properties> + <help>Never switch to SPT Tree</help> + </properties> + <children> + #include <include/policy/prefix-list.xml.i> + </children> + </node> + </children> + </node> + <node name="ssm"> + <properties> + <help>Source-Specific Multicast</help> + </properties> + <children> + #include <include/policy/prefix-list.xml.i> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_pim6.xml.in b/interface-definitions/protocols_pim6.xml.in new file mode 100644 index 0000000..8bd3f3f --- /dev/null +++ b/interface-definitions/protocols_pim6.xml.in @@ -0,0 +1,179 @@ +<?xml version="1.0"?> +<!-- Protocol Independent Multicast for IPv6 (PIMv6) configuration --> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="pim6" owner="${vyos_conf_scripts_dir}/protocols_pim6.py"> + <properties> + <help>Protocol Independent Multicast for IPv6 (PIMv6) and MLD</help> + <priority>400</priority> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>PIMv6 interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + #include <include/pim/bsm.xml.i> + #include <include/pim/dr-priority.xml.i> + #include <include/pim/hello.xml.i> + #include <include/pim/passive.xml.i> + <node name="mld"> + <properties> + <help>Multicast Listener Discovery (MLD)</help> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <tagNode name="join"> + <properties> + <help>MLD join multicast group</help> + <valueHelp> + <format>ipv6</format> + <description>Multicast group address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + <children> + <leafNode name="source"> + <properties> + <help>Source address</help> + <valueHelp> + <format>ipv6</format> + <description>Source address</description> + </valueHelp> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --ipv6</script> + </completionHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="last-member-query-count"> + <properties> + <help>Last member query count</help> + <valueHelp> + <format>u32:1-255</format> + <description>Count</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="last-member-query-interval"> + <properties> + <help>Last member query interval</help> + <valueHelp> + <format>u32:100-6553500</format> + <description>Last member query interval in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 100-6553500"/> + </constraint> + </properties> + </leafNode> + <leafNode name="interval"> + <properties> + <help>Query interval</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Query interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="max-response-time"> + <properties> + <help>Max query response time</help> + <valueHelp> + <format>u32:100-6553500</format> + <description>Query response value in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 100-6553500"/> + </constraint> + </properties> + </leafNode> + <leafNode name="version"> + <properties> + <help>MLD version</help> + <completionHelp> + <list>1 2</list> + </completionHelp> + <valueHelp> + <format>1</format> + <description>MLD version 1</description> + </valueHelp> + <valueHelp> + <format>2</format> + <description>MLD version 2</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2"/> + </constraint> + </properties> + <defaultValue>2</defaultValue> + </leafNode> + </children> + </node> + </children> + </tagNode> + #include <include/pim/join-prune-interval.xml.i> + #include <include/pim/keep-alive-timer.xml.i> + #include <include/pim/packets.xml.i> + #include <include/pim/register-suppress-time.xml.i> + <node name="rp"> + <properties> + <help>Rendezvous Point</help> + </properties> + <children> + <tagNode name="address"> + <properties> + <help>Rendezvous Point address</help> + <valueHelp> + <format>ipv6</format> + <description>Rendezvous Point address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + <children> + <leafNode name="group"> + <properties> + <help>Group Address range</help> + <valueHelp> + <format>ipv6net</format> + <description>Group Address range</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + #include <include/policy/prefix-list6.xml.i> + </children> + </tagNode> + #include <include/pim/keep-alive-timer.xml.i> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_rip.xml.in b/interface-definitions/protocols_rip.xml.in new file mode 100644 index 0000000..0edd8f2 --- /dev/null +++ b/interface-definitions/protocols_rip.xml.in @@ -0,0 +1,258 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="rip" owner="${vyos_conf_scripts_dir}/protocols_rip.py"> + <properties> + <help>Routing Information Protocol (RIP) parameters</help> + <priority>650</priority> + </properties> + <children> + <leafNode name="default-distance"> + <properties> + <help>Administrative distance</help> + <valueHelp> + <format>u32:1-255</format> + <description>Administrative distance</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + #include <include/rip/default-information.xml.i> + #include <include/rip/default-metric.xml.i> + <node name="distribute-list"> + <properties> + <help>Filter networks in routing updates</help> + </properties> + <children> + #include <include/rip/access-list.xml.i> + <tagNode name="interface"> + <properties> + <help>Apply filtering to an interface</help> + <valueHelp> + <format>txt</format> + <description>Apply filtering to an interface</description> + </valueHelp> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + #include <include/rip/access-list.xml.i> + #include <include/rip/prefix-list.xml.i> + </children> + </tagNode> + #include <include/rip/prefix-list.xml.i> + </children> + </node> + #include <include/rip/interface.xml.i> + <tagNode name="interface"> + <children> + <node name="authentication"> + <properties> + <help>Authentication</help> + </properties> + <children> + <tagNode name="md5"> + <properties> + <help>MD5 key id</help> + <valueHelp> + <format>u32:1-255</format> + <description>OSPF key id</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <children> + <leafNode name="password"> + <properties> + <help>Authentication password</help> + <valueHelp> + <format>txt</format> + <description>MD5 Key (16 characters or less)</description> + </valueHelp> + <constraint> + <regex>[^[:space:]]{1,16}</regex> + </constraint> + <constraintErrorMessage>Password must be 16 characters or less</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="plaintext-password"> + <properties> + <help>Plain text password</help> + <valueHelp> + <format>txt</format> + <description>Plain text password (16 characters or less)</description> + </valueHelp> + <constraint> + <regex>[^[:space:]]{1,16}</regex> + </constraint> + <constraintErrorMessage>Password must be 16 characters or less</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + <node name="receive"> + <properties> + <help>Advertisement reception</help> + </properties> + <children> + #include <include/rip/version.xml.i> + </children> + </node> + <node name="send"> + <properties> + <help>Advertisement transmission</help> + </properties> + <children> + #include <include/rip/version.xml.i> + </children> + </node> + </children> + </tagNode> + <leafNode name="neighbor"> + <properties> + <help>Neighbor router</help> + <valueHelp> + <format>ipv4</format> + <description>Neighbor router</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="network"> + <properties> + <help>RIP network</help> + <valueHelp> + <format>ipv4net</format> + <description>RIP network</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + <tagNode name="network-distance"> + <properties> + <help>Source network</help> + <valueHelp> + <format>ipv4net</format> + <description>Source network</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="access-list"> + <properties> + <help>Access list</help> + <valueHelp> + <format>txt</format> + <description>Access list</description> + </valueHelp> + <completionHelp> + <path>policy access-list</path> + </completionHelp> + </properties> + </leafNode> + #include <include/static/static-route-distance.xml.i> + </children> + </tagNode> + #include <include/routing-passive-interface.xml.i> + <node name="redistribute"> + <properties> + <help>Redistribute information from another routing protocol</help> + </properties> + <children> + <node name="bgp"> + <properties> + <help>Redistribute BGP routes</help> + </properties> + <children> + #include <include/rip/redistribute.xml.i> + </children> + </node> + <node name="connected"> + <properties> + <help>Redistribute connected routes</help> + </properties> + <children> + #include <include/rip/redistribute.xml.i> + </children> + </node> + <node name="isis"> + <properties> + <help>Redistribute IS-IS routes</help> + </properties> + <children> + #include <include/rip/redistribute.xml.i> + </children> + </node> + <node name="kernel"> + <properties> + <help>Redistribute kernel routes</help> + </properties> + <children> + #include <include/rip/redistribute.xml.i> + </children> + </node> + <node name="ospf"> + <properties> + <help>Redistribute OSPF routes</help> + </properties> + <children> + #include <include/rip/redistribute.xml.i> + </children> + </node> + <node name="static"> + <properties> + <help>Redistribute static routes</help> + </properties> + <children> + #include <include/rip/redistribute.xml.i> + </children> + </node> + <node name="babel"> + <properties> + <help>Redistribute Babel routes</help> + </properties> + <children> + #include <include/rip/redistribute.xml.i> + </children> + </node> + </children> + </node> + <leafNode name="route"> + <properties> + <help>RIP static route</help> + <valueHelp> + <format>ipv4net</format> + <description>RIP static route</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + #include <include/rip/timers.xml.i> + #include <include/route-map.xml.i> + #include <include/rip/version.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_ripng.xml.in b/interface-definitions/protocols_ripng.xml.in new file mode 100644 index 0000000..9d4d874 --- /dev/null +++ b/interface-definitions/protocols_ripng.xml.in @@ -0,0 +1,155 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="ripng" owner="${vyos_conf_scripts_dir}/protocols_ripng.py"> + <properties> + <help>Routing Information Protocol (RIPng) parameters</help> + <priority>660</priority> + </properties> + <children> + <leafNode name="aggregate-address"> + <properties> + <help>Aggregate RIPng route announcement</help> + <valueHelp> + <format>ipv6net</format> + <description>Aggregate RIPng route announcement</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + #include <include/rip/default-information.xml.i> + #include <include/rip/default-metric.xml.i> + <node name="distribute-list"> + <properties> + <help>Filter networks in routing updates</help> + </properties> + <children> + #include <include/rip/access-list6.xml.i> + <tagNode name="interface"> + <properties> + <help>Apply filtering to an interface</help> + <valueHelp> + <format>txt</format> + <description>Apply filtering to an interface</description> + </valueHelp> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + #include <include/rip/access-list6.xml.i> + #include <include/rip/prefix-list6.xml.i> + </children> + </tagNode> + #include <include/rip/prefix-list6.xml.i> + </children> + </node> + #include <include/rip/interface.xml.i> + <leafNode name="network"> + <properties> + <help>RIPng network</help> + <valueHelp> + <format>ipv6net</format> + <description>RIPng network</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="passive-interface"> + <properties> + <help>Passive interface</help> + <valueHelp> + <format>txt</format> + <description>Suppress routing updates on interface</description> + </valueHelp> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <multi/> + </properties> + </leafNode> + <node name="redistribute"> + <properties> + <help>Redistribute information from another routing protocol</help> + </properties> + <children> + <node name="bgp"> + <properties> + <help>Redistribute BGP routes</help> + </properties> + <children> + #include <include/rip/redistribute.xml.i> + </children> + </node> + <node name="connected"> + <properties> + <help>Redistribute connected routes</help> + </properties> + <children> + #include <include/rip/redistribute.xml.i> + </children> + </node> + <node name="kernel"> + <properties> + <help>Redistribute kernel routes</help> + </properties> + <children> + #include <include/rip/redistribute.xml.i> + </children> + </node> + <node name="ospfv3"> + <properties> + <help>Redistribute OSPFv3 routes</help> + </properties> + <children> + #include <include/rip/redistribute.xml.i> + </children> + </node> + <node name="static"> + <properties> + <help>Redistribute static routes</help> + </properties> + <children> + #include <include/rip/redistribute.xml.i> + </children> + </node> + <node name="babel"> + <properties> + <help>Redistribute Babel routes</help> + </properties> + <children> + #include <include/rip/redistribute.xml.i> + </children> + </node> + </children> + </node> + <leafNode name="route"> + <properties> + <help>RIPng static route</help> + <valueHelp> + <format>ipv6net</format> + <description>RIPng static route</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + #include <include/route-map.xml.i> + #include <include/rip/timers.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_rpki.xml.in b/interface-definitions/protocols_rpki.xml.in new file mode 100644 index 0000000..54d69ea --- /dev/null +++ b/interface-definitions/protocols_rpki.xml.in @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="utf-8"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="rpki" owner="${vyos_conf_scripts_dir}/protocols_rpki.py"> + <properties> + <help>Resource Public Key Infrastructure (RPKI)</help> + <priority>819</priority> + </properties> + <children> + <tagNode name="cache"> + <properties> + <help>RPKI cache server address</help> + <valueHelp> + <format>ipv4</format> + <description>IP address of RPKI server</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of RPKI server</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>Fully qualified domain name of RPKI server</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + <validator name="fqdn"/> + </constraint> + </properties> + <children> + #include <include/port-number.xml.i> + <leafNode name="preference"> + <properties> + <help>Preference of the cache server</help> + <valueHelp> + <format>u32:1-255</format> + <description>Preference of the cache server</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <node name="ssh"> + <properties> + <help>RPKI SSH connection settings</help> + </properties> + <children> + #include <include/pki/openssh-key.xml.i> + #include <include/generic-username.xml.i> + </children> + </node> + </children> + </tagNode> + <leafNode name="expire-interval"> + <properties> + <help>Interval to wait before expiring the cache</help> + <valueHelp> + <format>u32:600-172800</format> + <description>Interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 600-172800"/> + </constraint> + </properties> + <defaultValue>7200</defaultValue> + </leafNode> + <leafNode name="polling-period"> + <properties> + <help>Cache polling interval</help> + <valueHelp> + <format>u32:1-86400</format> + <description>Interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-86400"/> + </constraint> + </properties> + <defaultValue>300</defaultValue> + </leafNode> + <leafNode name="retry-interval"> + <properties> + <help>Retry interval to connect to the cache server</help> + <valueHelp> + <format>u32:1-7200</format> + <description>Interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-7200"/> + </constraint> + </properties> + <defaultValue>600</defaultValue> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_segment-routing.xml.in b/interface-definitions/protocols_segment-routing.xml.in new file mode 100644 index 0000000..c299f62 --- /dev/null +++ b/interface-definitions/protocols_segment-routing.xml.in @@ -0,0 +1,137 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="segment-routing" owner="${vyos_conf_scripts_dir}/protocols_segment-routing.py"> + <properties> + <help>Segment Routing</help> + <priority>900</priority> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Interface specific Segment Routing options</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + <node name="srv6"> + <properties> + <help>Accept SR-enabled IPv6 packets on this interface</help> + </properties> + <children> + <leafNode name="hmac"> + <properties> + <help>Define HMAC policy for ingress SR-enabled packets on this interface</help> + <completionHelp> + <list>accept drop ignore</list> + </completionHelp> + <valueHelp> + <format>accept</format> + <description>Accept packets without HMAC, validate packets with HMAC</description> + </valueHelp> + <valueHelp> + <format>drop</format> + <description>Drop packets without HMAC, validate packets with HMAC</description> + </valueHelp> + <valueHelp> + <format>ignore</format> + <description>Ignore HMAC field.</description> + </valueHelp> + <constraint> + <regex>(accept|drop|ignore)</regex> + </constraint> + </properties> + <defaultValue>accept</defaultValue> + </leafNode> + </children> + </node> + </children> + </tagNode> + <node name="srv6"> + <properties> + <help>Segment-Routing SRv6 configuration</help> + </properties> + <children> + <tagNode name="locator"> + <properties> + <help>Segment Routing SRv6 locator</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + </properties> + <children> + <leafNode name="behavior-usid"> + <properties> + <help>Set SRv6 behavior uSID</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="prefix"> + <properties> + <help>SRv6 locator prefix</help> + <valueHelp> + <format>ipv6net</format> + <description>SRv6 locator prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + <leafNode name="block-len"> + <properties> + <help>Configure SRv6 locator block length in bits</help> + <valueHelp> + <format>u32:16-64</format> + <description>Specify SRv6 locator block length in bits</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 16-64"/> + </constraint> + </properties> + <defaultValue>40</defaultValue> + </leafNode> + <leafNode name="func-bits"> + <properties> + <help>Configure SRv6 locator function length in bits</help> + <valueHelp> + <format>u32:0-64</format> + <description>Specify SRv6 locator function length in bits</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-64"/> + </constraint> + </properties> + <defaultValue>16</defaultValue> + </leafNode> + <leafNode name="node-len"> + <properties> + <help>Configure SRv6 locator node length in bits</help> + <valueHelp> + <format>u32:16-64</format> + <description>Configure SRv6 locator node length in bits</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 16-64"/> + </constraint> + </properties> + <defaultValue>24</defaultValue> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_static.xml.in b/interface-definitions/protocols_static.xml.in new file mode 100644 index 0000000..ca4ca2d --- /dev/null +++ b/interface-definitions/protocols_static.xml.in @@ -0,0 +1,44 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="protocols"> + <properties> + <help>Routing protocols</help> + </properties> + <children> + <node name="static" owner="${vyos_conf_scripts_dir}/protocols_static.py"> + <properties> + <help>Static Routing</help> + <priority>480</priority> + </properties> + <children> + #include <include/route-map.xml.i> + #include <include/static/static-route.xml.i> + #include <include/static/static-route6.xml.i> + <tagNode name="table"> + <properties> + <help>Policy route table number</help> + <valueHelp> + <format>u32:1-200</format> + <description>Policy route table number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-200"/> + </constraint> + </properties> + <children> + <!-- + iproute2 only considers the first "word" until whitespace in the name field + but does not complain about special characters. + We put an artificial limit here to make table descriptions potentially valid node names + to avoid quoting and simplify future syntax changes if we decide to make any. + --> + #include <include/generic-description.xml.i> + #include <include/static/static-route.xml.i> + #include <include/static/static-route6.xml.i> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_static_arp.xml.in b/interface-definitions/protocols_static_arp.xml.in new file mode 100644 index 0000000..0c5d6e4 --- /dev/null +++ b/interface-definitions/protocols_static_arp.xml.in @@ -0,0 +1,52 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="static"> + <children> + <node name="arp" owner="${vyos_conf_scripts_dir}/protocols_static_arp.py"> + <properties> + <help>Static ARP translation</help> + <priority>481</priority> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Interface configuration</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + <tagNode name="address"> + <properties> + <help>IP address for static ARP entry</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 destination address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/interface/mac.xml.i> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_static_multicast.xml.in b/interface-definitions/protocols_static_multicast.xml.in new file mode 100644 index 0000000..caf95ed --- /dev/null +++ b/interface-definitions/protocols_static_multicast.xml.in @@ -0,0 +1,95 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="static"> + <children> + <node name="multicast" owner="${vyos_conf_scripts_dir}/protocols_static_multicast.py"> + <properties> + <help>Multicast static route</help> + <priority>481</priority> + </properties> + <children> + <tagNode name="route"> + <properties> + <help>Configure static unicast route into MRIB for multicast RPF lookup</help> + <valueHelp> + <format>ipv4net</format> + <description>Network</description> + </valueHelp> + <constraint> + <validator name="ip-prefix"/> + </constraint> + </properties> + <children> + <tagNode name="next-hop"> + <properties> + <help>Nexthop IPv4 address</help> + <valueHelp> + <format>ipv4</format> + <description>Nexthop IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <children> + <leafNode name="distance"> + <properties> + <help>Distance value for this route</help> + <valueHelp> + <format>u32:1-255</format> + <description>Distance for this route</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="interface-route"> + <properties> + <help>Multicast interface based route</help> + <valueHelp> + <format>ipv4net</format> + <description>Network</description> + </valueHelp> + <constraint> + <validator name="ip-prefix"/> + </constraint> + </properties> + <children> + <tagNode name="next-hop-interface"> + <properties> + <help>Next-hop interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + <leafNode name="distance"> + <properties> + <help>Distance value for this route</help> + <valueHelp> + <format>u32:1-255</format> + <description>Distance for this route</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols_static_neighbor-proxy.xml.in b/interface-definitions/protocols_static_neighbor-proxy.xml.in new file mode 100644 index 0000000..7347976 --- /dev/null +++ b/interface-definitions/protocols_static_neighbor-proxy.xml.in @@ -0,0 +1,49 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="static"> + <children> + <node name="neighbor-proxy" owner="${vyos_conf_scripts_dir}/protocols_static_neighbor-proxy.py"> + <properties> + <help>Neighbor proxy parameters</help> + <priority>481</priority> + </properties> + <children> + <tagNode name="arp"> + <properties> + <help>IP address for selective ARP proxy</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 destination address allowed for proxy-arp</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <children> + #include <include/generic-interface-multi.xml.i> + </children> + </tagNode> + <tagNode name="nd"> + <properties> + <help>IPv6 address for selective NDP proxy</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 destination address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + <children> + #include <include/generic-interface-multi.xml.i> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/qos.xml.in b/interface-definitions/qos.xml.in new file mode 100644 index 0000000..927594c --- /dev/null +++ b/interface-definitions/qos.xml.in @@ -0,0 +1,874 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="qos" owner="${vyos_conf_scripts_dir}/qos.py"> + <properties> + <help>Quality of Service (QoS)</help> + <priority>900</priority> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Interface to apply QoS policy</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + <leafNode name="ingress"> + <properties> + <help>Interface ingress traffic policy</help> + <completionHelp> + <path>qos policy limiter</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>QoS policy to use</description> + </valueHelp> + <constraint> + <regex>[[:alnum:]][-_[:alnum:]]*</regex> + </constraint> + <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="egress"> + <properties> + <help>Interface egress traffic policy</help> + <completionHelp> + <path>qos policy cake</path> + <path>qos policy drop-tail</path> + <path>qos policy fair-queue</path> + <path>qos policy fq-codel</path> + <path>qos policy network-emulator</path> + <path>qos policy priority-queue</path> + <path>qos policy random-detect</path> + <path>qos policy rate-control</path> + <path>qos policy round-robin</path> + <path>qos policy shaper</path> + <path>qos policy shaper-hfsc</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>QoS policy to use</description> + </valueHelp> + <constraint> + <regex>[[:alnum:]][-_[:alnum:]]*</regex> + </constraint> + <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + <node name="policy"> + <properties> + <help>Service Policy definitions</help> + </properties> + <children> + <tagNode name="cake"> + <properties> + <help>Common Applications Kept Enhanced (CAKE)</help> + <valueHelp> + <format>txt</format> + <description>Policy name</description> + </valueHelp> + <constraint> + <regex>[[:alnum:]][-_[:alnum:]]*</regex> + </constraint> + <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/qos/bandwidth.xml.i> + <node name="flow-isolation"> + <properties> + <help>Flow isolation settings</help> + </properties> + <children> + <leafNode name="blind"> + <properties> + <help>Disables flow isolation, all traffic passes through a single queue</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="src-host"> + <properties> + <help>Flows are defined only by source address</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="dst-host"> + <properties> + <help>Flows are defined only by destination address</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="host"> + <properties> + <help>Flows are defined by source-destination host pairs</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="flow"> + <properties> + <help>Flows are defined by the entire 5-tuple</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="dual-src-host"> + <properties> + <help>Flows are defined by the 5-tuple, fairness is applied first over source addresses, then over individual flows</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="dual-dst-host"> + <properties> + <help>Flows are defined by the 5-tuple, fairness is applied first over destination addresses, then over individual flows</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="triple-isolate"> + <properties> + <help>Flows are defined by the 5-tuple, fairness is applied over source and destination addresses and also over individual flows (default)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="nat"> + <properties> + <help>Perform NAT lookup before applying flow-isolation rules</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="rtt"> + <properties> + <help>Round-Trip-Time for Active Queue Management (AQM)</help> + <valueHelp> + <format>u32:1-3600000</format> + <description>RTT in ms</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-3600000"/> + </constraint> + <constraintErrorMessage>RTT must be in range 1 to 3600000 milli-seconds</constraintErrorMessage> + </properties> + <defaultValue>100</defaultValue> + </leafNode> + </children> + </tagNode> + <tagNode name="drop-tail"> + <properties> + <help>Packet limited First In, First Out queue</help> + <valueHelp> + <format>txt</format> + <description>Policy name</description> + </valueHelp> + <constraint> + <regex>[[:alnum:]][-_[:alnum:]]*</regex> + </constraint> + <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/qos/queue-limit-1-4294967295.xml.i> + </children> + </tagNode> + <tagNode name="fair-queue"> + <properties> + <help>Stochastic Fairness Queueing</help> + <valueHelp> + <format>txt</format> + <description>Policy name</description> + </valueHelp> + <constraint> + <regex>[[:alnum:]][-_[:alnum:]]*</regex> + </constraint> + <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <leafNode name="hash-interval"> + <properties> + <help>Interval in seconds for queue algorithm perturbation</help> + <valueHelp> + <format>u32:0</format> + <description>No perturbation</description> + </valueHelp> + <valueHelp> + <format>u32:1-127</format> + <description>Interval in seconds for queue algorithm perturbation (advised: 10)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-127"/> + </constraint> + <constraintErrorMessage>Interval must be in range 0 to 127</constraintErrorMessage> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="queue-limit"> + <properties> + <help>Upper limit of the SFQ</help> + <valueHelp> + <format>u32:1-127</format> + <description>Queue size in packets</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-127"/> + </constraint> + <constraintErrorMessage>Queue limit must be in range 1 to 127</constraintErrorMessage> + </properties> + <defaultValue>127</defaultValue> + </leafNode> + </children> + </tagNode> + <tagNode name="fq-codel"> + <properties> + <help>Fair Queuing (FQ) with Controlled Delay (CoDel)</help> + <valueHelp> + <format>txt</format> + <description>Policy name</description> + </valueHelp> + <constraint> + <regex>[[:alnum:]][-_[:alnum:]]*</regex> + </constraint> + <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/qos/codel-quantum.xml.i> + #include <include/qos/flows.xml.i> + #include <include/qos/interval.xml.i> + #include <include/qos/queue-limit-2-10999.xml.i> + #include <include/qos/target.xml.i> + </children> + </tagNode> + <tagNode name="limiter"> + <properties> + <help>Traffic input limiting policy</help> + <valueHelp> + <format>txt</format> + <description>Policy name</description> + </valueHelp> + <constraint> + <regex>[[:alnum:]][-_[:alnum:]]*</regex> + </constraint> + <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <tagNode name="class"> + <properties> + <help>Class ID</help> + <valueHelp> + <format>u32:1-4090</format> + <description>Class Identifier</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4090"/> + </constraint> + <constraintErrorMessage>Class identifier must be between 1 and 4090</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/qos/bandwidth.xml.i> + #include <include/qos/burst.xml.i> + #include <include/qos/mtu.xml.i> + #include <include/qos/class-police-exceed.xml.i> + #include <include/qos/class-match.xml.i> + #include <include/qos/class-match-group.xml.i> + #include <include/qos/class-priority.xml.i> + <leafNode name="priority"> + <defaultValue>20</defaultValue> + </leafNode> + </children> + </tagNode> + <node name="default"> + <properties> + <help>Default policy</help> + </properties> + <children> + #include <include/qos/bandwidth.xml.i> + #include <include/qos/burst.xml.i> + #include <include/qos/mtu.xml.i> + #include <include/qos/class-police-exceed.xml.i> + </children> + </node> + </children> + </tagNode> + <tagNode name="network-emulator"> + <properties> + <help>Network emulator policy</help> + <valueHelp> + <format>txt</format> + <description>Policy name</description> + </valueHelp> + <constraint> + <regex>[[:alnum:]][-_[:alnum:]]*</regex> + </constraint> + <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/qos/bandwidth.xml.i> + <leafNode name="delay"> + <properties> + <help>Adds delay to packets outgoing to chosen network interface</help> + <valueHelp> + <format><number></format> + <description>Time in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + <constraintErrorMessage>Priority must be between 0 and 65535</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="corruption"> + <properties> + <help>Introducing error in a random position for chosen percent of packets</help> + <valueHelp> + <format><number></format> + <description>Percentage of packets affected</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-100"/> + </constraint> + <constraintErrorMessage>Priority must be between 0 and 100</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="duplicate"> + <properties> + <help>Cosen percent of packets is duplicated before queuing them</help> + <valueHelp> + <format><number></format> + <description>Percentage of packets affected</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-100"/> + </constraint> + <constraintErrorMessage>Priority must be between 0 and 100</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="loss"> + <properties> + <help>Add independent loss probability to the packets outgoing to chosen network interface</help> + <valueHelp> + <format><number></format> + <description>Percentage of packets affected</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-100"/> + </constraint> + <constraintErrorMessage>Must be between 0 and 100</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="reordering"> + <properties> + <help>Emulated packet reordering percentage</help> + <valueHelp> + <format><number></format> + <description>Percentage of packets affected</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-100"/> + </constraint> + <constraintErrorMessage>Must be between 0 and 100</constraintErrorMessage> + </properties> + </leafNode> + #include <include/qos/queue-limit-1-4294967295.xml.i> + </children> + </tagNode> + <tagNode name="priority-queue"> + <properties> + <help>Priority queuing based policy</help> + <valueHelp> + <format>txt</format> + <description>Policy name</description> + </valueHelp> + <constraint> + <regex>[[:alnum:]][-_[:alnum:]]*</regex> + </constraint> + <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <tagNode name="class"> + <properties> + <help>Class Handle</help> + <valueHelp> + <format>u32:1-7</format> + <description>Priority</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-7"/> + </constraint> + <constraintErrorMessage>Class handle must be between 1 and 7</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/qos/codel-quantum.xml.i> + #include <include/qos/flows.xml.i> + #include <include/qos/interval.xml.i> + #include <include/qos/class-match.xml.i> + #include <include/qos/class-match-group.xml.i> + #include <include/qos/queue-limit-1-4294967295.xml.i> + #include <include/qos/queue-type.xml.i> + <leafNode name="queue-type"> + <defaultValue>drop-tail</defaultValue> + </leafNode> + #include <include/qos/target.xml.i> + </children> + </tagNode> + <node name="default"> + <properties> + <help>Default policy</help> + </properties> + <children> + #include <include/qos/codel-quantum.xml.i> + #include <include/qos/flows.xml.i> + #include <include/qos/interval.xml.i> + #include <include/qos/queue-limit-1-4294967295.xml.i> + #include <include/qos/queue-type.xml.i> + <leafNode name="queue-type"> + <defaultValue>drop-tail</defaultValue> + </leafNode> + #include <include/qos/target.xml.i> + </children> + </node> + </children> + </tagNode> + <tagNode name="random-detect"> + <properties> + <help>Weighted Random Early Detect policy</help> + <valueHelp> + <format>txt</format> + <description>Policy name</description> + </valueHelp> + <constraint> + <regex>[[:alnum:]][-_[:alnum:]]*</regex> + </constraint> + <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/qos/bandwidth-auto.xml.i> + <tagNode name="precedence"> + <properties> + <help>IP precedence</help> + <valueHelp> + <format>u32:0-7</format> + <description>IP precedence value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-7"/> + </constraint> + <constraintErrorMessage>IP precedence value must be between 0 and 7</constraintErrorMessage> + </properties> + <children> + #include <include/qos/queue-limit-1-4294967295.xml.i> + #include <include/qos/queue-average-packet.xml.i> + #include <include/qos/queue-maximum-threshold.xml.i> + #include <include/qos/queue-minimum-threshold.xml.i> + #include <include/qos/queue-mark-probability.xml.i> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="rate-control"> + <properties> + <help>Rate limiting policy (Token Bucket Filter)</help> + <valueHelp> + <format>txt</format> + <description>Policy name</description> + </valueHelp> + <constraint> + <regex>[[:alnum:]][-_[:alnum:]]*</regex> + </constraint> + <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/qos/bandwidth.xml.i> + #include <include/qos/burst.xml.i> + <leafNode name="latency"> + <properties> + <help>Maximum latency</help> + <valueHelp> + <format><number></format> + <description>Time in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4096"/> + </constraint> + <constraintErrorMessage>Threshold must be between 0 and 4096</constraintErrorMessage> + </properties> + <defaultValue>50</defaultValue> + </leafNode> + </children> + </tagNode> + <tagNode name="round-robin"> + <properties> + <help>Deficit Round Robin Scheduler</help> + <valueHelp> + <format>txt</format> + <description>Policy name</description> + </valueHelp> + <constraint> + <regex>[[:alnum:]][-_[:alnum:]]*</regex> + </constraint> + <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <tagNode name="class"> + <properties> + <help>Class ID</help> + <valueHelp> + <format>u32:1-4095</format> + <description>Class Identifier</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4095"/> + </constraint> + <constraintErrorMessage>Class identifier must be between 1 and 4095</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/qos/codel-quantum.xml.i> + #include <include/qos/flows.xml.i> + #include <include/qos/interval.xml.i> + #include <include/qos/class-match.xml.i> + #include <include/qos/class-match-group.xml.i> + + <leafNode name="quantum"> + <properties> + <help>Packet scheduling quantum</help> + <valueHelp> + <format>u32:1-4294967295</format> + <description>Packet scheduling quantum (bytes)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + <constraintErrorMessage>Quantum must be in range 1 to 4294967295</constraintErrorMessage> + </properties> + </leafNode> + #include <include/qos/queue-limit-1-4294967295.xml.i> + #include <include/qos/queue-type.xml.i> + <leafNode name="queue-type"> + <defaultValue>drop-tail</defaultValue> + </leafNode> + #include <include/qos/target.xml.i> + </children> + </tagNode> + <node name="default"> + <properties> + <help>Default policy</help> + </properties> + <children> + #include <include/qos/codel-quantum.xml.i> + #include <include/qos/flows.xml.i> + #include <include/qos/interval.xml.i> + #include <include/qos/queue-limit-1-4294967295.xml.i> + #include <include/qos/queue-type.xml.i> + <leafNode name="queue-type"> + <defaultValue>fair-queue</defaultValue> + </leafNode> + #include <include/qos/target.xml.i> + </children> + </node> + </children> + </tagNode> + <tagNode name="shaper"> + <properties> + <help>Traffic shaping based policy (Hierarchy Token Bucket)</help> + <valueHelp> + <format>txt</format> + <description>Policy name</description> + </valueHelp> + <constraint> + <regex>[[:alnum:]][-_[:alnum:]]*</regex> + </constraint> + <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/qos/bandwidth-auto.xml.i> + <tagNode name="class"> + <properties> + <help>Class ID</help> + <valueHelp> + <format>u32:2-4095</format> + <description>Class Identifier</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 2-4095"/> + </constraint> + <constraintErrorMessage>Class identifier must be between 2 and 4095</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/qos/bandwidth-auto.xml.i> + #include <include/qos/burst.xml.i> + <leafNode name="ceiling"> + <properties> + <help>Bandwidth limit for this class</help> + <valueHelp> + <format><number></format> + <description>Rate in kbit (kilobit per second)</description> + </valueHelp> + <valueHelp> + <format><number>%%</format> + <description>Percentage of overall rate</description> + </valueHelp> + <valueHelp> + <format><number>bit</format> + <description>bit(1), kbit(10^3), mbit(10^6), gbit, tbit</description> + </valueHelp> + <valueHelp> + <format><number>ibit</format> + <description>kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4)</description> + </valueHelp> + <valueHelp> + <format><number>ibps</format> + <description>kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec</description> + </valueHelp> + <valueHelp> + <format><number>bps</format> + <description>bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec</description> + </valueHelp> + </properties> + </leafNode> + #include <include/qos/codel-quantum.xml.i> + #include <include/qos/flows.xml.i> + #include <include/qos/interval.xml.i> + #include <include/qos/class-match.xml.i> + #include <include/qos/class-match-group.xml.i> + #include <include/qos/class-priority.xml.i> + #include <include/qos/queue-average-packet.xml.i> + #include <include/qos/queue-maximum-threshold.xml.i> + #include <include/qos/queue-minimum-threshold.xml.i> + #include <include/qos/queue-mark-probability.xml.i> + #include <include/qos/queue-limit-1-4294967295.xml.i> + #include <include/qos/queue-type.xml.i> + <leafNode name="queue-type"> + <defaultValue>fq-codel</defaultValue> + </leafNode> + #include <include/qos/set-dscp.xml.i> + #include <include/qos/target.xml.i> + </children> + </tagNode> + <node name="default"> + <properties> + <help>Default policy</help> + </properties> + <children> + #include <include/qos/bandwidth.xml.i> + #include <include/qos/burst.xml.i> + <leafNode name="ceiling"> + <properties> + <help>Bandwidth limit for this class</help> + <valueHelp> + <format><number></format> + <description>Rate in kbit (kilobit per second)</description> + </valueHelp> + <valueHelp> + <format><number>%%</format> + <description>Percentage of overall rate</description> + </valueHelp> + <valueHelp> + <format><number>bit</format> + <description>bit(1), kbit(10^3), mbit(10^6), gbit, tbit</description> + </valueHelp> + <valueHelp> + <format><number>ibit</format> + <description>kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4)</description> + </valueHelp> + <valueHelp> + <format><number>ibps</format> + <description>kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec</description> + </valueHelp> + <valueHelp> + <format><number>bps</format> + <description>bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec</description> + </valueHelp> + </properties> + </leafNode> + #include <include/qos/codel-quantum.xml.i> + #include <include/qos/flows.xml.i> + #include <include/qos/interval.xml.i> + <leafNode name="priority"> + <properties> + <help>Priority for usage of excess bandwidth</help> + <valueHelp> + <format>u32:0-7</format> + <description>Priority order for bandwidth pool</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-7"/> + </constraint> + <constraintErrorMessage>Priority must be between 0 and 7</constraintErrorMessage> + </properties> + <defaultValue>20</defaultValue> + </leafNode> + #include <include/qos/queue-average-packet.xml.i> + #include <include/qos/queue-maximum-threshold.xml.i> + #include <include/qos/queue-minimum-threshold.xml.i> + #include <include/qos/queue-mark-probability.xml.i> + #include <include/qos/queue-limit-1-4294967295.xml.i> + #include <include/qos/queue-type.xml.i> + <leafNode name="queue-type"> + <defaultValue>fq-codel</defaultValue> + </leafNode> + #include <include/qos/set-dscp.xml.i> + #include <include/qos/target.xml.i> + </children> + </node> + </children> + </tagNode> + <tagNode name="shaper-hfsc"> + <properties> + <help>Hierarchical Fair Service Curve's policy</help> + <valueHelp> + <format>txt</format> + <description>Policy name</description> + </valueHelp> + <constraint> + <regex>[[:alnum:]][-_[:alnum:]]*</regex> + </constraint> + <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/qos/bandwidth-auto.xml.i> + <tagNode name="class"> + <properties> + <help>Class ID</help> + <valueHelp> + <format>u32:1-4095</format> + <description>Class Identifier</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4095"/> + </constraint> + <constraintErrorMessage>Class identifier must be between 1 and 4095</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <node name="linkshare"> + <properties> + <help>Linkshare class settings</help> + </properties> + <children> + #include <include/qos/hfsc-d.xml.i> + #include <include/qos/hfsc-m1.xml.i> + #include <include/qos/hfsc-m2.xml.i> + </children> + </node> + #include <include/qos/class-match.xml.i> + #include <include/qos/class-match-group.xml.i> + <node name="realtime"> + <properties> + <help>Realtime class settings</help> + </properties> + <children> + #include <include/qos/hfsc-d.xml.i> + #include <include/qos/hfsc-m1.xml.i> + #include <include/qos/hfsc-m2.xml.i> + </children> + </node> + <node name="upperlimit"> + <properties> + <help>Upperlimit class settings</help> + </properties> + <children> + #include <include/qos/hfsc-d.xml.i> + #include <include/qos/hfsc-m1.xml.i> + #include <include/qos/hfsc-m2.xml.i> + </children> + </node> + </children> + </tagNode> + <node name="default"> + <properties> + <help>Default policy</help> + </properties> + <children> + <node name="linkshare"> + <properties> + <help>Linkshare class settings</help> + </properties> + <children> + #include <include/qos/hfsc-d.xml.i> + #include <include/qos/hfsc-m1.xml.i> + #include <include/qos/hfsc-m2.xml.i> + </children> + </node> + <node name="realtime"> + <properties> + <help>Realtime class settings</help> + </properties> + <children> + #include <include/qos/hfsc-d.xml.i> + #include <include/qos/hfsc-m1.xml.i> + #include <include/qos/hfsc-m2.xml.i> + </children> + </node> + <node name="upperlimit"> + <properties> + <help>Upperlimit class settings</help> + </properties> + <children> + #include <include/qos/hfsc-d.xml.i> + #include <include/qos/hfsc-m1.xml.i> + #include <include/qos/hfsc-m2.xml.i> + </children> + </node> + </children> + </node> + </children> + </tagNode> + </children> + </node> + <tagNode name="traffic-match-group"> + <properties> + <help>Filter group for QoS policy</help> + <valueHelp> + <format>txt</format> + <description>Match group name</description> + </valueHelp> + <constraint> + <regex>[^-].*</regex> + </constraint> + <constraintErrorMessage>Match group name cannot start with hyphen</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <tagNode name="match"> + <properties> + <help>Class matching rule name</help> + <constraint> + <regex>[^-].*</regex> + </constraint> + <constraintErrorMessage>Match queue name cannot start with hyphen</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/qos/class-match-ipv4.xml.i> + #include <include/qos/class-match-ipv6.xml.i> + #include <include/qos/class-match-mark.xml.i> + #include <include/qos/class-match-vif.xml.i> + </children> + </tagNode> + #include <include/qos/class-match-group.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_aws_glb.xml.in b/interface-definitions/service_aws_glb.xml.in new file mode 100644 index 0000000..71de1f0 --- /dev/null +++ b/interface-definitions/service_aws_glb.xml.in @@ -0,0 +1,127 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="aws"> + <properties> + <help>Amazon Web Service</help> + </properties> + <children> + <node name="glb" owner="${vyos_conf_scripts_dir}/service_aws_glb.py"> + <properties> + <help>Gateway load-balancer tunnel handler</help> + <priority>1280</priority> + </properties> + <children> + <node name="script"> + <properties> + <help>Script executed on create or destroy tunnel</help> + </properties> + <children> + <leafNode name="on-create"> + <properties> + <help>Script to run when interface is created</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + <leafNode name="on-destroy"> + <properties> + <help>Script to run when interface is destroyed</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="status"> + <properties> + <help>Status</help> + </properties> + <children> + <leafNode name="format"> + <properties> + <help>Statistic format</help> + <completionHelp> + <list>simple full</list> + </completionHelp> + <valueHelp> + <format>simple</format> + <description>Simple format</description> + </valueHelp> + <valueHelp> + <format>full</format> + <description>Full format</description> + </valueHelp> + <constraint> + <regex>(simple|full)</regex> + </constraint> + </properties> + </leafNode> + #include <include/port-number.xml.i> + </children> + </node> + <node name="threads"> + <properties> + <help>Threads settings</help> + </properties> + <children> + <leafNode name="tunnel"> + <properties> + <help>Number of threads for each tunnel processor</help> + <valueHelp> + <format>u32:1-256</format> + <description>Number of threads</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-256"/> + </constraint> + </properties> + </leafNode> + <leafNode name="tunnel-affinity"> + <properties> + <help>List of cores worker threads</help> + <valueHelp> + <format><idN>-<idM></format> + <description>CPU core id range (use '-' as delimiter)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 0-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="udp"> + <properties> + <help>Number of threads for UDP receiver</help> + <valueHelp> + <format>u32:1-256</format> + <description>Number of threads</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-256"/> + </constraint> + </properties> + </leafNode> + <leafNode name="udp-affinity"> + <properties> + <help>List of cores worker threads</help> + <valueHelp> + <format><idN>-<idM></format> + <description>CPU core id range (use '-' as delimiter)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 0-255"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_broadcast-relay.xml.in b/interface-definitions/service_broadcast-relay.xml.in new file mode 100644 index 0000000..2e4330e --- /dev/null +++ b/interface-definitions/service_broadcast-relay.xml.in @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="broadcast-relay" owner="${vyos_conf_scripts_dir}/service_broadcast-relay.py"> + <properties> + <help>UDP broadcast relay service</help> + <priority>990</priority> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <tagNode name="id"> + <properties> + <help>Unique ID for each UDP port to forward</help> + <valueHelp> + <format>u32:1-99</format> + <description>Broadcast relay instance ID</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-99"/> + </constraint> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <leafNode name="address"> + <properties> + <help>Set source IP of forwarded packets, otherwise original senders address is used</help> + <valueHelp> + <format>ipv4</format> + <description>Optional source address for forwarded packets</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + #include <include/generic-description.xml.i> + #include <include/generic-interface-multi.xml.i> + #include <include/port-number.xml.i> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_config-sync.xml.in b/interface-definitions/service_config-sync.xml.in new file mode 100644 index 0000000..af4e8ed --- /dev/null +++ b/interface-definitions/service_config-sync.xml.in @@ -0,0 +1,529 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="config-sync" owner="${vyos_conf_scripts_dir}/service_config-sync.py"> + <properties> + <help>Configuration synchronization</help> + <priority>10000</priority> + </properties> + <children> + <node name="secondary"> + <properties> + <help>Secondary server parameters</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IP address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address to match</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>FQDN address to match</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv6-address"/> + <validator name="fqdn"/> + </constraint> + </properties> + </leafNode> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>443</defaultValue> + </leafNode> + <leafNode name="timeout"> + <properties> + <help>Connection API timeout</help> + <valueHelp> + <format>u32:1-3600</format> + <description>Connection API timeout</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-3600"/> + </constraint> + </properties> + <defaultValue>60</defaultValue> + </leafNode> + <leafNode name="key"> + <properties> + <help>HTTP API key</help> + </properties> + </leafNode> + </children> + </node> + <leafNode name="mode"> + <properties> + <help>Synchronization mode</help> + <completionHelp> + <list>load set</list> + </completionHelp> + <valueHelp> + <format>load</format> + <description>Load and replace configuration section</description> + </valueHelp> + <valueHelp> + <format>set</format> + <description>Set configuration section</description> + </valueHelp> + <constraint> + <regex>(load|set)</regex> + </constraint> + </properties> + </leafNode> + <node name="section"> + <properties> + <help>Section for synchronization</help> + </properties> + <children> + <leafNode name="firewall"> + <properties> + <help>Firewall</help> + <valueless/> + </properties> + </leafNode> + <node name="interfaces"> + <properties> + <help>Interfaces</help> + </properties> + <children> + <leafNode name="bonding"> + <properties> + <help>Bonding interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="bridge"> + <properties> + <help>Bridge interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="dummy"> + <properties> + <help>Dummy interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ethernet"> + <properties> + <help>Ethernet interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="geneve"> + <properties> + <help>GENEVE interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="input"> + <properties> + <help>Input interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="l2tpv3"> + <properties> + <help>L2TPv3 interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="loopback"> + <properties> + <help>Loopback interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="macsec"> + <properties> + <help>MACsec interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="openvpn"> + <properties> + <help>OpenVPN interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="pppoe"> + <properties> + <help>PPPoE interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="pseudo-ethernet"> + <properties> + <help>Pseudo-Ethernet interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="sstpc"> + <properties> + <help>SSTP client interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="tunnel"> + <properties> + <help>Tunnel interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="virtual-ethernet"> + <properties> + <help>Virtual Ethernet interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="vti"> + <properties> + <help>Virtual tunnel interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="vxlan"> + <properties> + <help>VXLAN interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="wireguard"> + <properties> + <help>Wireguard interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="wireless"> + <properties> + <help>Wireless interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="wwan"> + <properties> + <help>WWAN interface</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="nat"> + <properties> + <help>NAT</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="nat66"> + <properties> + <help>NAT66</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="pki"> + <properties> + <help>Public key infrastructure (PKI)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="policy"> + <properties> + <help>Routing policy</help> + <valueless/> + </properties> + </leafNode> + <node name="protocols"> + <properties> + <help>Routing protocols</help> + </properties> + <children> + <leafNode name="babel"> + <properties> + <help>Babel Routing Protocol</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="bfd"> + <properties> + <help>Bidirectional Forwarding Detection (BFD)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="bgp"> + <properties> + <help>Border Gateway Protocol (BGP)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="failover"> + <properties> + <help>Failover route</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="igmp-proxy"> + <properties> + <help>Internet Group Management Protocol (IGMP) proxy</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="isis"> + <properties> + <help>Intermediate System to Intermediate System (IS-IS)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="mpls"> + <properties> + <help>Multiprotocol Label Switching (MPLS)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="nhrp"> + <properties> + <help>Next Hop Resolution Protocol (NHRP) parameters</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ospf"> + <properties> + <help>Open Shortest Path First (OSPF)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ospfv3"> + <properties> + <help>Open Shortest Path First (OSPF) for IPv6</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="pim"> + <properties> + <help>Protocol Independent Multicast (PIM) and IGMP</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="pim6"> + <properties> + <help>Protocol Independent Multicast for IPv6 (PIMv6) and MLD</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="rip"> + <properties> + <help>Routing Information Protocol (RIP) parameters</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ripng"> + <properties> + <help>Routing Information Protocol (RIPng) parameters</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="rpki"> + <properties> + <help>Resource Public Key Infrastructure (RPKI)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="segment-routing"> + <properties> + <help>Segment Routing</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="static"> + <properties> + <help>Static Routing</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="qos"> + <properties> + <help>Quality of Service (QoS)</help> + </properties> + <children> + <leafNode name="interface"> + <properties> + <help>Interface to apply QoS policy</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="policy"> + <properties> + <help>Service Policy definitions</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="service"> + <properties> + <help>System services</help> + </properties> + <children> + <leafNode name="console-server"> + <properties> + <help>Serial Console Server</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="dhcp-relay"> + <properties> + <help>Host Configuration Protocol (DHCP) relay agent</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="dhcp-server"> + <properties> + <help>Dynamic Host Configuration Protocol (DHCP) for DHCP server</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="dhcpv6-relay"> + <properties> + <help>DHCPv6 Relay Agent parameters</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="dhcpv6-server"> + <properties> + <help>DHCP for IPv6 (DHCPv6) server</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="dns"> + <properties> + <help>Domain Name System (DNS) related services</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="lldp"> + <properties> + <help>LLDP settings</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="mdns"> + <properties> + <help>Multicast DNS (mDNS) parameters</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="monitoring"> + <properties> + <help>Monitoring services</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ndp-proxy"> + <properties> + <help>Neighbor Discovery Protocol (NDP) Proxy</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ntp"> + <properties> + <help>Network Time Protocol (NTP) configuration</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="snmp"> + <properties> + <help>Simple Network Management Protocol (SNMP)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="tftp-server"> + <properties> + <help>Trivial File Transfer Protocol (TFTP) server</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="webproxy"> + <properties> + <help>Webproxy service settings</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="system"> + <properties> + <help>System parameters</help> + </properties> + <children> + <leafNode name="conntrack"> + <properties> + <help>Connection Tracking</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="flow-accounting"> + <properties> + <help>Flow accounting</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="option"> + <properties> + <help>System Options</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="sflow"> + <properties> + <help>sFlow</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="static-host-mapping"> + <properties> + <help>Map host names to addresses</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="sysctl"> + <properties> + <help>Configure kernel parameters at runtime</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="time-zone"> + <properties> + <help>Local time zone</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="vpn"> + <properties> + <help>Virtual Private Network (VPN)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="vrf"> + <properties> + <help>Virtual Routing and Forwarding</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_conntrack-sync.xml.in b/interface-definitions/service_conntrack-sync.xml.in new file mode 100644 index 0000000..631c830 --- /dev/null +++ b/interface-definitions/service_conntrack-sync.xml.in @@ -0,0 +1,185 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="conntrack-sync" owner="${vyos_conf_scripts_dir}/service_conntrack-sync.py"> + <properties> + <help>Connection tracking synchronization</help> + <!-- before VRRP / HA --> + <priority>799</priority> + </properties> + <children> + <leafNode name="accept-protocol"> + <properties> + <help>Protocols for which local conntrack entries will be synced</help> + <completionHelp> + <list>tcp udp icmp icmp6 sctp dccp</list> + </completionHelp> + <valueHelp> + <format>tcp</format> + <description>Sync Transmission Control Protocol entries</description> + </valueHelp> + <valueHelp> + <format>udp</format> + <description>Sync User Datagram Protocol entries</description> + </valueHelp> + <valueHelp> + <format>icmp</format> + <description>Sync Internet Control Message Protocol entries</description> + </valueHelp> + <valueHelp> + <format>icmp6</format> + <description>Sync IPv6 Internet Control Message Protocol entries</description> + </valueHelp> + <valueHelp> + <format>sctp</format> + <description>Sync Stream Control Transmission Protocol entries</description> + </valueHelp> + <valueHelp> + <format>dccp</format> + <description>Sync Datagram Congestion Control Protocol entries</description> + </valueHelp> + <constraint> + <regex>(tcp|udp|icmp|icmp6|sctp|dccp)</regex> + </constraint> + <constraintErrorMessage>Allowed protocols: tcp udp icmp or sctp</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + <leafNode name="disable-external-cache"> + <properties> + <help>Directly injects the flow-states into the in-kernel Connection Tracking System of the backup firewall.</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="disable-syslog"> + <properties> + <help>Disable connection logging via Syslog</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="event-listen-queue-size"> + <properties> + <help>Queue size for local conntrack events</help> + <valueHelp> + <format>u32</format> + <description>Queue size in MB</description> + </valueHelp> + </properties> + <defaultValue>8</defaultValue> + </leafNode> + <leafNode name="expect-sync"> + <properties> + <help>Protocol for which expect entries need to be synchronized</help> + <completionHelp> + <list>all ftp sip h323 nfs sqlnet</list> + </completionHelp> + <constraint> + <regex>(all|ftp|sip|h323|nfs|sqlnet)</regex> + </constraint> + <constraintErrorMessage>Invalid protocol</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + <leafNode name="startup-resync"> + <properties> + <help>Order conntrackd to request a complete conntrack table resync against the other node at startup</help> + <valueless/> + </properties> + </leafNode> + <node name="failover-mechanism"> + <properties> + <help>Failover mechanism to use for conntrack-sync</help> + </properties> + <children> + <node name="vrrp"> + <properties> + <help>VRRP as failover-mechanism to use for conntrack-sync</help> + </properties> + <children> + <leafNode name="sync-group"> + <properties> + <help>VRRP sync group</help> + <completionHelp> + <path>high-availability vrrp sync-group</path> + </completionHelp> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="ignore-address"> + <properties> + <help>IP addresses for which local conntrack entries will not be synced</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to ignore</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to ignore</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address to ignore</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix to ignore</description> + </valueHelp> + <constraint> + <validator name="ipv4"/> + <validator name="ipv6"/> + </constraint> + <multi/> + </properties> + </leafNode> + <tagNode name="interface"> + <properties> + <help>Interface to use for syncing conntrack entries</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --bridgeable</script> + </completionHelp> + </properties> + <children> + <leafNode name="peer"> + <properties> + <help>IP address of the peer to send the UDP conntrack info too. This disable multicast.</help> + <valueHelp> + <format>ipv4</format> + <description>IP address to listen for incoming connections</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + #include <include/port-number.xml.i> + </children> + </tagNode> + #include <include/listen-address-ipv4.xml.i> + <leafNode name="mcast-group"> + <properties> + <help>Multicast group to use for syncing conntrack entries</help> + <constraint> + <validator name="ipv4-multicast"/> + </constraint> + </properties> + <defaultValue>225.0.0.50</defaultValue> + </leafNode> + <leafNode name="sync-queue-size"> + <properties> + <help>Queue size for syncing conntrack entries</help> + <valueHelp> + <format>u32</format> + <description>Queue size in MB</description> + </valueHelp> + </properties> + <defaultValue>1</defaultValue> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_console-server.xml.in b/interface-definitions/service_console-server.xml.in new file mode 100644 index 0000000..68835da --- /dev/null +++ b/interface-definitions/service_console-server.xml.in @@ -0,0 +1,101 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="console-server" owner="${vyos_conf_scripts_dir}/service_console-server.py"> + <properties> + <help>Serial Console Server</help> + <priority>2</priority> + </properties> + <children> + <tagNode name="device"> + <properties> + <help>System serial interface name (ttyS or ttyUSB)</help> + <completionHelp> + <script>ls -1 /dev | grep ttyS</script> + <script>if [ -d /dev/serial/by-bus ]; then ls -1 /dev/serial/by-bus; fi</script> + </completionHelp> + <valueHelp> + <format>ttySxxx</format> + <description>Regular serial interface</description> + </valueHelp> + <valueHelp> + <format>usbxbxpx</format> + <description>USB based serial interface</description> + </valueHelp> + <constraint> + <regex>(ttyS\d+|usb\d+b.*p.*)</regex> + </constraint> + </properties> + <children> + #include <include/generic-description.xml.i> + <leafNode name="alias"> + <properties> + <help>Human-readable name for this console</help> + <constraint> + <regex>[-_a-zA-Z0-9.]{1,128}</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="speed"> + <properties> + <help>Serial port baud rate</help> + <completionHelp> + <list>300 1200 2400 4800 9600 19200 38400 57600 115200</list> + </completionHelp> + <constraint> + <regex>(300|1200|2400|4800|9600|19200|38400|57600|115200)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="data-bits"> + <properties> + <help>Serial port data bits</help> + <completionHelp> + <list>7 8</list> + </completionHelp> + <constraint> + <validator name="numeric" argument="--range 7-8"/> + </constraint> + </properties> + <defaultValue>8</defaultValue> + </leafNode> + <leafNode name="stop-bits"> + <properties> + <help>Serial port stop bits</help> + <completionHelp> + <list>1 2</list> + </completionHelp> + <constraint> + <validator name="numeric" argument="--range 1-2"/> + </constraint> + </properties> + <defaultValue>1</defaultValue> + </leafNode> + <leafNode name="parity"> + <properties> + <help>Parity setting</help> + <completionHelp> + <list>even odd none</list> + </completionHelp> + <constraint> + <regex>(even|odd|none)</regex> + </constraint> + </properties> + <defaultValue>none</defaultValue> + </leafNode> + <node name="ssh"> + <properties> + <help>SSH remote access to this console</help> + </properties> + <children> + #include <include/port-number.xml.i> + </children> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_dhcp-relay.xml.in b/interface-definitions/service_dhcp-relay.xml.in new file mode 100644 index 0000000..9fdd958 --- /dev/null +++ b/interface-definitions/service_dhcp-relay.xml.in @@ -0,0 +1,126 @@ +<?xml version="1.0"?> +<!-- DHCP relay configuration --> +<interfaceDefinition> + <node name="service"> + <children> + <node name="dhcp-relay" owner="${vyos_conf_scripts_dir}/service_dhcp-relay.py"> + <properties> + <help>Host Configuration Protocol (DHCP) relay agent</help> + <priority>910</priority> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/generic-interface-multi-broadcast.xml.i> + <leafNode name="listen-interface"> + <properties> + <help>Interface for DHCP Relay Agent to listen for requests</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="upstream-interface"> + <properties> + <help>Interface for DHCP Relay Agent forward requests out</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + <multi/> + </properties> + </leafNode> + <node name="relay-options"> + <properties> + <help>Relay options</help> + </properties> + <children> + <leafNode name="hop-count"> + <properties> + <help>Policy to discard packets that have reached specified hop-count</help> + <valueHelp> + <format>u32:1-255</format> + <description>Hop count</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + <constraintErrorMessage>hop-count must be a value between 1 and 255</constraintErrorMessage> + </properties> + <defaultValue>10</defaultValue> + </leafNode> + <leafNode name="max-size"> + <properties> + <help>Maximum packet size to send to a DHCPv4/BOOTP server</help> + <valueHelp> + <format>u32:64-1400</format> + <description>Maximum packet size</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 64-1400"/> + </constraint> + <constraintErrorMessage>max-size must be a value between 64 and 1400</constraintErrorMessage> + </properties> + <defaultValue>576</defaultValue> + </leafNode> + <leafNode name="relay-agents-packets"> + <properties> + <help>Policy to handle incoming DHCPv4 packets which already contain relay agent options</help> + <completionHelp> + <list>append replace forward discard</list> + </completionHelp> + <valueHelp> + <format>append</format> + <description>append own relay options to packet</description> + </valueHelp> + <valueHelp> + <format>replace</format> + <description>replace existing agent option field</description> + </valueHelp> + <valueHelp> + <format>forward</format> + <description>forward packet unchanged</description> + </valueHelp> + <valueHelp> + <format>discard</format> + <description>discard packet (default action if giaddr not set in packet)</description> + </valueHelp> + <constraint> + <regex>(append|replace|forward|discard)</regex> + </constraint> + </properties> + <defaultValue>forward</defaultValue> + </leafNode> + </children> + </node> + <leafNode name="server"> + <properties> + <help>DHCP server address</help> + <valueHelp> + <format>ipv4</format> + <description>DHCP server IPv4 address</description> + </valueHelp> + <multi/> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_dhcp-server.xml.in b/interface-definitions/service_dhcp-server.xml.in new file mode 100644 index 0000000..cb5f9a8 --- /dev/null +++ b/interface-definitions/service_dhcp-server.xml.in @@ -0,0 +1,250 @@ +<?xml version="1.0"?> +<!-- DHCP server configuration --> +<interfaceDefinition> + <node name="service"> + <children> + <node name="dhcp-server" owner="${vyos_conf_scripts_dir}/service_dhcp-server.py"> + <properties> + <help>Dynamic Host Configuration Protocol (DHCP) for DHCP server</help> + <priority>911</priority> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <leafNode name="dynamic-dns-update"> + <properties> + <help>Dynamically update Domain Name System (RFC4702)</help> + <valueless/> + </properties> + </leafNode> + <node name="high-availability"> + <properties> + <help>DHCP high availability configuration</help> + </properties> + <children> + #include <include/source-address-ipv4.xml.i> + <leafNode name="mode"> + <properties> + <help>Configure high availability mode</help> + <completionHelp> + <list>active-active active-passive</list> + </completionHelp> + <valueHelp> + <format>active-active</format> + <description>Both server attend DHCP requests</description> + </valueHelp> + <valueHelp> + <format>active-passive</format> + <description>Only primary server attends DHCP requests</description> + </valueHelp> + <constraint> + <regex>(active-active|active-passive)</regex> + </constraint> + <constraintErrorMessage>Invalid DHCP high availability mode</constraintErrorMessage> + </properties> + <defaultValue>active-active</defaultValue> + </leafNode> + <leafNode name="remote"> + <properties> + <help>IPv4 remote address used for connection</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address of high availability peer</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="name"> + <properties> + <help>Peer name used to identify connection</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Invalid failover peer name. May only contain letters, numbers and .-_</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="status"> + <properties> + <help>High availability hierarchy</help> + <completionHelp> + <list>primary secondary</list> + </completionHelp> + <valueHelp> + <format>primary</format> + <description>Configure this server to be the primary node</description> + </valueHelp> + <valueHelp> + <format>secondary</format> + <description>Configure this server to be the secondary node</description> + </valueHelp> + <constraint> + <regex>(primary|secondary)</regex> + </constraint> + <constraintErrorMessage>Invalid DHCP high availability peer status</constraintErrorMessage> + </properties> + </leafNode> + #include <include/pki/ca-certificate.xml.i> + #include <include/pki/certificate.xml.i> + </children> + </node> + <leafNode name="hostfile-update"> + <properties> + <help>Updating /etc/hosts file (per client lease)</help> + <valueless/> + </properties> + </leafNode> + #include <include/listen-address-ipv4.xml.i> + #include <include/listen-interface-multi-broadcast.xml.i> + <tagNode name="shared-network-name"> + <properties> + <help>Name of DHCP shared network</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Invalid shared network name. May only contain letters, numbers and .-_</constraintErrorMessage> + </properties> + <children> + <leafNode name="authoritative"> + <properties> + <help>Option to make DHCP server authoritative for this physical network</help> + <valueless/> + </properties> + </leafNode> + #include <include/dhcp/option-v4.xml.i> + #include <include/generic-description.xml.i> + #include <include/generic-disable-node.xml.i> + <tagNode name="subnet"> + <properties> + <help>DHCP subnet for shared network</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + <constraintErrorMessage>Invalid IPv4 subnet definition</constraintErrorMessage> + </properties> + <children> + #include <include/dhcp/option-v4.xml.i> + #include <include/generic-description.xml.i> + #include <include/generic-disable-node.xml.i> + <leafNode name="exclude"> + <properties> + <help>IP address to exclude from DHCP lease range</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to exclude from lease range</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="ignore-client-id"> + <properties> + <help>Ignore client identifier for lease lookups</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="lease"> + <properties> + <help>Lease timeout in seconds</help> + <valueHelp> + <format>u32</format> + <description>DHCP lease time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + <constraintErrorMessage>DHCP lease time must be between 0 and 4294967295 (49 days)</constraintErrorMessage> + </properties> + <defaultValue>86400</defaultValue> + </leafNode> + <tagNode name="range"> + <properties> + <help>DHCP lease range</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Invalid range name, may only be alphanumeric, dot and hyphen</constraintErrorMessage> + </properties> + <children> + #include <include/dhcp/option-v4.xml.i> + <leafNode name="start"> + <properties> + <help>First IP address for DHCP lease range</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 start address of pool</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="stop"> + <properties> + <help>Last IP address for DHCP lease range</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 end address of pool</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="static-mapping"> + <properties> + <help>Hostname for static mapping reservation</help> + <constraint> + <validator name="fqdn"/> + </constraint> + <constraintErrorMessage>Invalid static mapping hostname</constraintErrorMessage> + </properties> + <children> + #include <include/dhcp/option-v4.xml.i> + #include <include/generic-description.xml.i> + #include <include/generic-disable-node.xml.i> + <leafNode name="ip-address"> + <properties> + <help>Fixed IP address of static mapping</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address used in static mapping</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + #include <include/interface/mac.xml.i> + #include <include/interface/duid.xml.i> + </children> + </tagNode> + <leafNode name="subnet-id"> + <properties> + <help>Unique ID mapped to leases in the lease file</help> + <valueHelp> + <format>u32</format> + <description>Unique subnet ID</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_dhcpv6-relay.xml.in b/interface-definitions/service_dhcpv6-relay.xml.in new file mode 100644 index 0000000..40679d1 --- /dev/null +++ b/interface-definitions/service_dhcpv6-relay.xml.in @@ -0,0 +1,82 @@ +<?xml version="1.0"?> +<!-- DHCPv6 relay configuration --> +<interfaceDefinition> + <node name="service"> + <children> + <node name="dhcpv6-relay" owner="${vyos_conf_scripts_dir}/service_dhcpv6-relay.py"> + <properties> + <help>DHCPv6 Relay Agent parameters</help> + <priority>900</priority> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <tagNode name="listen-interface"> + <properties> + <help>Interface for DHCPv6 Relay Agent to listen for requests</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IPv6 address on listen-interface listen for requests on</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address on listen interface</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="max-hop-count"> + <properties> + <help>Maximum hop count for which requests will be processed</help> + <valueHelp> + <format>u32:1-255</format> + <description>Hop count</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + <constraintErrorMessage>max-hop-count must be a value between 1 and 255</constraintErrorMessage> + </properties> + <defaultValue>10</defaultValue> + </leafNode> + <tagNode name="upstream-interface"> + <properties> + <help>Interface for DHCPv6 Relay Agent forward requests out</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IPv6 address to forward requests to</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of the DHCP server</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="use-interface-id-option"> + <properties> + <help>Option to set DHCPv6 interface-ID option</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_dhcpv6-server.xml.in b/interface-definitions/service_dhcpv6-server.xml.in new file mode 100644 index 0000000..cf14388 --- /dev/null +++ b/interface-definitions/service_dhcpv6-server.xml.in @@ -0,0 +1,317 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="dhcpv6-server" owner="${vyos_conf_scripts_dir}/service_dhcpv6-server.py"> + <properties> + <help>DHCP for IPv6 (DHCPv6) server</help> + <priority>900</priority> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/listen-interface-multi-broadcast.xml.i> + <leafNode name="disable-route-autoinstall"> + <properties> + <help>Do not install routes for delegated prefixes</help> + <valueless/> + </properties> + </leafNode> + <node name="global-parameters"> + <properties> + <help>Additional global parameters for DHCPv6 server</help> + </properties> + <children> + #include <include/name-server-ipv6.xml.i> + </children> + </node> + <leafNode name="preference"> + <properties> + <help>Preference of this DHCPv6 server compared with others</help> + <valueHelp> + <format>u32:0-255</format> + <description>DHCPv6 server preference (0-255)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + <constraintErrorMessage>Preference must be between 0 and 255</constraintErrorMessage> + </properties> + </leafNode> + <tagNode name="shared-network-name"> + <properties> + <help>DHCPv6 shared network name</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Invalid DHCPv6 shared network name. May only contain letters, numbers and .-_</constraintErrorMessage> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/generic-description.xml.i> + <leafNode name="interface"> + <properties> + <help>Optional interface for this shared network to accept requests from</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + </leafNode> + #include <include/dhcp/option-v6.xml.i> + <tagNode name="subnet"> + <properties> + <help>IPv6 DHCP subnet for this shared network</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + #include <include/dhcp/option-v6.xml.i> + <leafNode name="interface"> + <properties> + <help>Optional interface for this subnet to accept requests from</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + </leafNode> + <tagNode name="range"> + <properties> + <help>Parameters setting ranges for assigning IPv6 addresses</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Invalid range name, may only be alphanumeric, dot and hyphen</constraintErrorMessage> + </properties> + <children> + #include <include/dhcp/option-v6.xml.i> + <leafNode name="prefix"> + <properties> + <help>IPv6 prefix defining range of addresses to assign</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + <leafNode name="start"> + <properties> + <help>First in range of consecutive IPv6 addresses to assign</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="stop"> + <properties> + <help>Last in range of consecutive IPv6 addresses</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + <node name="lease-time"> + <properties> + <help>Parameters relating to the lease time</help> + </properties> + <children> + <leafNode name="default"> + <properties> + <help>Default time (in seconds) that will be assigned to a lease</help> + <valueHelp> + <format>u32:1-4294967295</format> + <description>DHCPv6 valid lifetime</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="maximum"> + <properties> + <help>Maximum time (in seconds) that will be assigned to a lease</help> + <valueHelp> + <format>u32:1-4294967295</format> + <description>Maximum lease time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="minimum"> + <properties> + <help>Minimum time (in seconds) that will be assigned to a lease</help> + <valueHelp> + <format>u32:1-4294967295</format> + <description>Minimum lease time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="prefix-delegation"> + <properties> + <help>Parameters relating to IPv6 prefix delegation</help> + </properties> + <children> + <tagNode name="prefix"> + <properties> + <help>IPv6 prefix to be used in prefix delegation</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 prefix used in prefix delegation</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + <children> + <leafNode name="prefix-length"> + <properties> + <help>Length in bits of prefix</help> + <valueHelp> + <format>u32:32-64</format> + <description>Prefix length (32-64)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 32-64"/> + </constraint> + <constraintErrorMessage>Prefix length must be between 32 and 64</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="delegated-length"> + <properties> + <help>Length in bits of prefixes to be delegated</help> + <valueHelp> + <format>u32:32-64</format> + <description>Delegated prefix length (32-64)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 32-96"/> + </constraint> + <constraintErrorMessage>Delegated prefix length must be between 32 and 96</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="excluded-prefix"> + <properties> + <help>IPv6 prefix to be excluded from prefix delegation</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 prefix excluded from prefix delegation</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="excluded-prefix-length"> + <properties> + <help>Length in bits of excluded prefix</help> + <valueHelp> + <format>u32:33-64</format> + <description>Excluded prefix length (33-128)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 33-128"/> + </constraint> + <constraintErrorMessage>Prefix length must be between 33 and 128</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + <tagNode name="static-mapping"> + <properties> + <help>Hostname for static mapping reservation</help> + <constraint> + <validator name="fqdn"/> + </constraint> + <constraintErrorMessage>Invalid static mapping hostname</constraintErrorMessage> + </properties> + <children> + #include <include/dhcp/option-v6.xml.i> + #include <include/generic-disable-node.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/duid.xml.i> + <leafNode name="ipv6-address"> + <properties> + <help>Client IPv6 address for this static mapping</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address for this static mapping</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="ipv6-prefix"> + <properties> + <help>Client IPv6 prefix for this static mapping</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix for this static mapping</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="subnet-id"> + <properties> + <help>Unique ID mapped to leases in the lease file</help> + <valueHelp> + <format>u32</format> + <description>Unique subnet ID</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_dns_dynamic.xml.in b/interface-definitions/service_dns_dynamic.xml.in new file mode 100644 index 0000000..75e5520 --- /dev/null +++ b/interface-definitions/service_dns_dynamic.xml.in @@ -0,0 +1,200 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="dns"> + <properties> + <help>Domain Name System (DNS) related services</help> + </properties> + <children> + <node name="dynamic" owner="${vyos_conf_scripts_dir}/service_dns_dynamic.py"> + <properties> + <help>Dynamic DNS</help> + <priority>990</priority> + </properties> + <children> + <tagNode name="name"> + <properties> + <help>Dynamic DNS configuration</help> + <valueHelp> + <format>txt</format> + <description>Dynamic DNS service name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Dynamic DNS service name must be alphanumeric and can contain hyphens and underscores</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <leafNode name="protocol"> + <properties> + <help>ddclient protocol used for Dynamic DNS service</help> + <completionHelp> + <script>${vyos_completion_dir}/list_ddclient_protocols.sh</script> + </completionHelp> + <constraint> + <validator name="ddclient-protocol"/> + </constraint> + </properties> + </leafNode> + <node name="address"> + <properties> + <help>Obtain IP address to send Dynamic DNS update for</help> + </properties> + <children> + #include <include/generic-interface.xml.i> + <node name="web"> + <properties> + <help>HTTP(S) web request to use</help> + </properties> + <children> + #include <include/url-http-https.xml.i> + <leafNode name="skip"> + <properties> + <help>Pattern to skip from the HTTP(S) respose</help> + <valueHelp> + <format>txt</format> + <description>Pattern to skip from the HTTP(S) respose to extract the external IP address</description> + </valueHelp> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="ip-version"> + <properties> + <help>IP address version to use</help> + <valueHelp> + <format>_ipv4</format> + <description>Use only IPv4 address</description> + </valueHelp> + <valueHelp> + <format>_ipv6</format> + <description>Use only IPv6 address</description> + </valueHelp> + <valueHelp> + <format>both</format> + <description>Use both IPv4 and IPv6 address</description> + </valueHelp> + <completionHelp> + <list>ipv4 ipv6 both</list> + </completionHelp> + <constraint> + <regex>(ipv[46]|both)</regex> + </constraint> + <constraintErrorMessage>IP Version must be literal 'ipv4', 'ipv6' or 'both'</constraintErrorMessage> + </properties> + <defaultValue>ipv4</defaultValue> + </leafNode> + <leafNode name="host-name"> + <properties> + <help>Hostname to register with Dynamic DNS service</help> + <constraint> + #include <include/constraint/host-name.xml.i> + <regex>(\@|\*)[-.A-Za-z0-9]*</regex> + </constraint> + <constraintErrorMessage>Host-name must be alphanumeric, can contain hyphens and can be prefixed with '@' or '*'</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + <leafNode name="server"> + <properties> + <help>Remote Dynamic DNS server to send updates to</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address of the remote server</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of the remote server</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>Fully qualified domain name of the remote server</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + <validator name="fqdn"/> + </constraint> + <constraintErrorMessage>Remote server must be IP address or fully qualified domain name</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="zone"> + <properties> + <help>DNS zone to be updated</help> + <valueHelp> + <format>txt</format> + <description>Name of DNS zone</description> + </valueHelp> + <constraint> + <validator name="fqdn"/> + </constraint> + </properties> + </leafNode> + #include <include/generic-username.xml.i> + #include <include/generic-password.xml.i> + <leafNode name="key"> + <properties> + <help>File containing TSIG authentication key for RFC2136 nsupdate on remote DNS server</help> + <valueHelp> + <format>filename</format> + <description>File in /config/auth directory</description> + </valueHelp> + <constraint> + <validator name="file-path" argument="--strict --parent-dir /config/auth"/> + </constraint> + </properties> + </leafNode> + #include <include/dns/time-to-live.xml.i> + <leafNode name="wait-time"> + <properties> + <help>Time in seconds to wait between update attempts</help> + <valueHelp> + <format>u32:60-86400</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 60-86400"/> + </constraint> + <constraintErrorMessage>Wait time must be between 60 and 86400 seconds</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="expiry-time"> + <properties> + <help>Time in seconds for the hostname to be marked expired in cache</help> + <valueHelp> + <format>u32:300-2160000</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 300-2160000"/> + </constraint> + <constraintErrorMessage>Expiry time must be between 300 and 2160000 seconds</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="interval"> + <properties> + <help>Interval in seconds to wait between Dynamic DNS updates</help> + <valueHelp> + <format>u32:60-3600</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 60-3600"/> + </constraint> + <constraintErrorMessage>Interval must be between 60 and 3600 seconds</constraintErrorMessage> + </properties> + <defaultValue>300</defaultValue> + </leafNode> + #include <include/interface/vrf.xml.i> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_dns_forwarding.xml.in b/interface-definitions/service_dns_forwarding.xml.in new file mode 100644 index 0000000..d0bc2e6 --- /dev/null +++ b/interface-definitions/service_dns_forwarding.xml.in @@ -0,0 +1,975 @@ +<?xml version="1.0"?> +<!-- DNS forwarder configuration --> +<interfaceDefinition> + <node name="service"> + <children> + <node name="dns"> + <properties> + <help>Domain Name System (DNS) related services</help> + </properties> + <children> + <node name="forwarding" owner="${vyos_conf_scripts_dir}/service_dns_forwarding.py"> + <properties> + <help>DNS forwarding</help> + <priority>918</priority> + </properties> + <children> + <leafNode name="cache-size"> + <properties> + <help>DNS forwarding cache size</help> + <valueHelp> + <format>u32:0-2147483647</format> + <description>DNS forwarding cache size</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2147483647"/> + </constraint> + </properties> + <defaultValue>10000</defaultValue> + </leafNode> + <leafNode name="dhcp"> + <properties> + <help>Interfaces whose DHCP client nameservers to forward requests to</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <multi/> + </properties> + </leafNode> + <leafNode name="dns64-prefix"> + <properties> + <help>Help to communicate between IPv6-only client and IPv4-only server</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and /96 only prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + <leafNode name="dnssec"> + <properties> + <help>DNSSEC mode</help> + <completionHelp> + <list>off process-no-validate process log-fail validate</list> + </completionHelp> + <valueHelp> + <format>off</format> + <description>No DNSSEC processing whatsoever!</description> + </valueHelp> + <valueHelp> + <format>process-no-validate</format> + <description>Respond with DNSSEC records to clients that ask for it. No validation done at all!</description> + </valueHelp> + <valueHelp> + <format>process</format> + <description>Respond with DNSSEC records to clients that ask for it. Validation for clients that request it.</description> + </valueHelp> + <valueHelp> + <format>log-fail</format> + <description>Similar behaviour to process, but validate RRSIGs on responses and log bogus responses.</description> + </valueHelp> + <valueHelp> + <format>validate</format> + <description>Full blown DNSSEC validation. Send SERVFAIL to clients on bogus responses.</description> + </valueHelp> + <constraint> + <regex>(off|process-no-validate|process|log-fail|validate)</regex> + </constraint> + </properties> + <defaultValue>process-no-validate</defaultValue> + </leafNode> + <tagNode name="domain"> + <properties> + <help>Domain to forward to a custom DNS server</help> + <valueHelp> + <format>txt</format> + <description>An absolute DNS domain name</description> + </valueHelp> + <constraint> + <validator name="fqdn"/> + </constraint> + </properties> + <children> + #include <include/name-server-ipv4-ipv6-port.xml.i> + <leafNode name="addnta"> + <properties> + <help>Add NTA (negative trust anchor) for this domain (must be set if the domain does not support DNSSEC)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="recursion-desired"> + <properties> + <help>Set the "recursion desired" bit in requests to the upstream nameserver</help> + <valueless/> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="authoritative-domain"> + <properties> + <help>Domain to host authoritative records for</help> + <valueHelp> + <format>txt</format> + <description>An absolute DNS domain name</description> + </valueHelp> + <constraint> + <regex>((?!-)[-_a-zA-Z0-9.]{1,63}|@|any)(?<!\.)</regex> + </constraint> + </properties> + <children> + <node name="records"> + <properties> + <help>DNS zone records</help> + </properties> + <children> + <tagNode name="a"> + <properties> + <help>A record</help> + <valueHelp> + <format>txt</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <valueHelp> + <format>any</format> + <description>Wildcard record (any subdomain)</description> + </valueHelp> + <constraint> + <regex>([-_a-zA-Z0-9.]{1,63}|@|any)(?<!\.)</regex> + </constraint> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IPv4 address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <multi/> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + #include <include/dns/time-to-live.xml.i> + <leafNode name="ttl"> + <defaultValue>300</defaultValue> + </leafNode> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="aaaa"> + <properties> + <help>AAAA record</help> + <valueHelp> + <format>txt</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <valueHelp> + <format>any</format> + <description>Wildcard record (any subdomain)</description> + </valueHelp> + <constraint> + <regex>([-_a-zA-Z0-9.]{1,63}|@|any)(?<!\.)</regex> + </constraint> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IPv6 address</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <multi/> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + #include <include/dns/time-to-live.xml.i> + <leafNode name="ttl"> + <defaultValue>300</defaultValue> + </leafNode> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="cname"> + <properties> + <help>CNAME record</help> + <valueHelp> + <format>txt</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <constraint> + <regex>([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)</regex> + </constraint> + </properties> + <children> + <leafNode name="target"> + <properties> + <help>Target DNS name</help> + <valueHelp> + <format>name.example.com</format> + <description>Absolute DNS name</description> + </valueHelp> + <constraint> + <regex>[-_a-zA-Z0-9.]{1,63}(?<!\.)</regex> + </constraint> + </properties> + </leafNode> + #include <include/dns/time-to-live.xml.i> + <leafNode name="ttl"> + <defaultValue>300</defaultValue> + </leafNode> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="mx"> + <properties> + <help>MX record</help> + <valueHelp> + <format>txt</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <constraint> + <regex>([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)</regex> + </constraint> + </properties> + <children> + <tagNode name="server"> + <properties> + <help>Mail server</help> + <valueHelp> + <format>name.example.com</format> + <description>Absolute DNS name</description> + </valueHelp> + <constraint> + <regex>[-_a-zA-Z0-9.]{1,63}(?<!\.)</regex> + </constraint> + </properties> + <children> + <leafNode name="priority"> + <properties> + <help>Server priority</help> + <valueHelp> + <format>u32:1-999</format> + <description>Server priority (lower numbers are higher priority)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999"/> + </constraint> + </properties> + <defaultValue>10</defaultValue> + </leafNode> + </children> + </tagNode> + #include <include/dns/time-to-live.xml.i> + <leafNode name="ttl"> + <defaultValue>300</defaultValue> + </leafNode> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="ns"> + <properties> + <help>NS record</help> + <valueHelp> + <format>txt</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <constraint> + <regex>([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)</regex> + </constraint> + </properties> + <children> + <leafNode name="target"> + <properties> + <help>Target DNS server authoritative for subdomain</help> + <valueHelp> + <format>nsXX.example.com</format> + <description>Absolute DNS name</description> + </valueHelp> + <constraint> + <regex>[-_a-zA-Z0-9.]{1,63}(?<!\.)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + #include <include/dns/time-to-live.xml.i> + <leafNode name="ttl"> + <defaultValue>300</defaultValue> + </leafNode> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="ptr"> + <properties> + <help>PTR record</help> + <valueHelp> + <format>txt</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <constraint> + <regex>([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)</regex> + </constraint> + </properties> + <children> + <leafNode name="target"> + <properties> + <help>Target DNS name</help> + <valueHelp> + <format>name.example.com</format> + <description>Absolute DNS name</description> + </valueHelp> + <constraint> + <regex>[-_a-zA-Z0-9.]{1,63}(?<!\.)</regex> + </constraint> + </properties> + </leafNode> + #include <include/dns/time-to-live.xml.i> + <leafNode name="ttl"> + <defaultValue>300</defaultValue> + </leafNode> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="txt"> + <properties> + <help>TXT record</help> + <valueHelp> + <format>txt</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <constraint> + <regex>([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)</regex> + </constraint> + </properties> + <children> + <leafNode name="value"> + <properties> + <help>Record contents</help> + <valueHelp> + <format>txt</format> + <description>Record contents</description> + </valueHelp> + <multi/> + </properties> + </leafNode> + #include <include/dns/time-to-live.xml.i> + <leafNode name="ttl"> + <defaultValue>300</defaultValue> + </leafNode> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="spf"> + <properties> + <help>SPF record</help> + <valueHelp> + <format>txt</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <constraint> + <regex>([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)</regex> + </constraint> + </properties> + <children> + <leafNode name="value"> + <properties> + <help>Record contents</help> + <valueHelp> + <format>txt</format> + <description>Record contents</description> + </valueHelp> + </properties> + </leafNode> + #include <include/dns/time-to-live.xml.i> + <leafNode name="ttl"> + <defaultValue>300</defaultValue> + </leafNode> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="srv"> + <properties> + <help>SRV record</help> + <valueHelp> + <format>txt</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <constraint> + <regex>([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)</regex> + </constraint> + </properties> + <children> + <tagNode name="entry"> + <properties> + <help>Service entry</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Entry number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + <children> + <leafNode name="hostname"> + <properties> + <help>Server hostname</help> + <valueHelp> + <format>name.example.com</format> + <description>Absolute DNS name</description> + </valueHelp> + <constraint> + <regex>[-_a-zA-Z0-9.]{1,63}(?<!\.)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="port"> + <properties> + <help>Port number</help> + <valueHelp> + <format>u32:0-65535</format> + <description>TCP/UDP port number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65536"/> + </constraint> + </properties> + </leafNode> + <leafNode name="priority"> + <properties> + <help>Entry priority</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Entry priority (lower numbers are higher priority)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + <defaultValue>10</defaultValue> + </leafNode> + <leafNode name="weight"> + <properties> + <help>Entry weight</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Entry weight</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + </children> + </tagNode> + #include <include/dns/time-to-live.xml.i> + <leafNode name="ttl"> + <defaultValue>300</defaultValue> + </leafNode> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="naptr"> + <properties> + <help>NAPTR record</help> + <valueHelp> + <format>txt</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <constraint> + <regex>([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)</regex> + </constraint> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>NAPTR rule</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Rule number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + <children> + <leafNode name="order"> + <properties> + <help>Rule order</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Rule order (lower order is evaluated first)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="preference"> + <properties> + <help>Rule preference</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Rule preference</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="lookup-srv"> + <properties> + <help>S flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="lookup-a"> + <properties> + <help>A flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="resolve-uri"> + <properties> + <help>U flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="protocol-specific"> + <properties> + <help>P flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="service"> + <properties> + <help>Service type</help> + <constraint> + <regex>[a-zA-Z][a-zA-Z0-9]{0,31}(\+[a-zA-Z][a-zA-Z0-9]{0,31})?</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="regexp"> + <properties> + <help>Regular expression</help> + </properties> + </leafNode> + <leafNode name="replacement"> + <properties> + <help>Replacement DNS name</help> + <valueHelp> + <format>name.example.com</format> + <description>Absolute DNS name</description> + </valueHelp> + <constraint> + <regex>[-_a-zA-Z0-9.]{1,63}(?<!\.)</regex> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + #include <include/dns/time-to-live.xml.i> + <leafNode name="ttl"> + <defaultValue>300</defaultValue> + </leafNode> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + </children> + </node> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <leafNode name="ignore-hosts-file"> + <properties> + <help>Do not use local /etc/hosts file in name resolution</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="no-serve-rfc1918"> + <properties> + <help>Makes the server authoritatively not aware of RFC1918 addresses</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="allow-from"> + <properties> + <help>Networks allowed to query this server</help> + <valueHelp> + <format>ipv4net</format> + <description>IP address and prefix length</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <multi/> + <constraint> + <validator name="ip-prefix"/> + </constraint> + </properties> + </leafNode> + #include <include/listen-address.xml.i> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>53</defaultValue> + </leafNode> + <leafNode name="negative-ttl"> + <properties> + <help>Maximum amount of time negative entries are cached</help> + <valueHelp> + <format>u32:0-7200</format> + <description>Seconds to cache NXDOMAIN entries</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-7200"/> + </constraint> + </properties> + <defaultValue>3600</defaultValue> + </leafNode> + <leafNode name="serve-stale-extension"> + <properties> + <help>Number of times the expired TTL of a record is extended by 30 seconds when serving stale</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Number of times to extend the TTL</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="timeout"> + <properties> + <help>Number of milliseconds to wait for a remote authoritative server to respond</help> + <valueHelp> + <format>u32:10-60000</format> + <description>Network timeout in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 10-60000"/> + </constraint> + </properties> + <defaultValue>1500</defaultValue> + </leafNode> + #include <include/name-server-ipv4-ipv6-port.xml.i> + #include <include/source-address-ipv4-ipv6-multi.xml.i> + <leafNode name="source-address"> + <defaultValue>0.0.0.0 ::</defaultValue> + </leafNode> + <leafNode name="system"> + <properties> + <help>Use system name servers</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="exclude-throttle-address"> + <properties> + <help>IP address or subnet</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address</description> + </valueHelp> + <multi/> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv4-prefix"/> + <validator name="ipv6-address"/> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + <node name="options"> + <properties> + <help>DNS server options</help> + </properties> + <children> + <leafNode name="ecs-add-for"> + <properties> + <help>Client netmask for which EDNS Client Subnet will be added</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <valueHelp> + <format>!ipv4net</format> + <description>Match everything except the specified IPv4 prefix</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix to match</description> + </valueHelp> + <valueHelp> + <format>!ipv6net</format> + <description>Match everything except the specified IPv6 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv4-prefix-exclude"/> + <validator name="ipv6-prefix"/> + <validator name="ipv6-prefix-exclude"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="ecs-ipv4-bits"> + <properties> + <help>Number of bits of IPv4 address to pass for EDNS Client Subnet</help> + <valueHelp> + <format>u32:0-32</format> + <description>Number of bits of IPv4 address</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-32"/> + </constraint> + </properties> + </leafNode> + <leafNode name="edns-subnet-allow-list"> + <properties> + <help>Netmask or domain that we should enable EDNS subnet for</help> + <valueHelp> + <format>txt</format> + <description>Netmask or domain</description> + </valueHelp> + <multi/> + </properties> + </leafNode> + </children> + </node> + <tagNode name="zone-cache"> + <properties> + <help>Load a zone into the recursor cache</help> + <valueHelp> + <format>txt</format> + <description>Domain name</description> + </valueHelp> + <constraint> + <validator name="fqdn"/> + </constraint> + </properties> + <children> + <node name="source"> + <properties> + <help>Zone source</help> + </properties> + <children> + <leafNode name="axfr"> + <properties> + <help>DNS server address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="url"> + <properties> + <help>Source URL</help> + <valueHelp> + <format>url</format> + <description>Zone file URL</description> + </valueHelp> + <constraint> + <validator name="url" argument="--scheme http --scheme https"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="options"> + <properties> + <help>Zone caching options</help> + </properties> + <children> + <leafNode name="timeout"> + <properties> + <help>Zone retrieval timeout</help> + <valueHelp> + <format>u32:1-3600</format> + <description>Request timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-3600"/> + </constraint> + </properties> + <defaultValue>20</defaultValue> + </leafNode> + <node name="refresh"> + <properties> + <help>Zone caching options</help> + </properties> + <children> + <leafNode name="on-reload"> + <properties> + <help>Retrieval zone only at startup and on reload</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="interval"> + <properties> + <help>Periodic zone retrieval interval</help> + <valueHelp> + <format>u32:0-31536000</format> + <description>Retrieval interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-31536000"/> + </constraint> + </properties> + <defaultValue>86400</defaultValue> + </leafNode> + </children> + </node> + <leafNode name="retry-interval"> + <properties> + <help>Retry interval after zone retrieval errors</help> + <valueHelp> + <format>u32:1-86400</format> + <description>Retry period in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-86400"/> + </constraint> + </properties> + <defaultValue>60</defaultValue> + </leafNode> + <leafNode name="max-zone-size"> + <properties> + <help>Maximum zone size in megabytes</help> + <valueHelp> + <format>u32:0</format> + <description>No restriction</description> + </valueHelp> + <valueHelp> + <format>u32:1-1024</format> + <description>Size in megabytes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-1024"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="zonemd"> + <properties> + <help>Message Digest for DNS Zones (RFC 8976)</help> + <completionHelp> + <list>ignore validate require</list> + </completionHelp> + <valueHelp> + <format>ignore</format> + <description>Ignore ZONEMD records</description> + </valueHelp> + <valueHelp> + <format>validate</format> + <description>Validate ZONEMD if present</description> + </valueHelp> + <valueHelp> + <format>require</format> + <description>Require valid ZONEMD record to be present</description> + </valueHelp> + <constraint> + <regex>(ignore|validate|require)</regex> + </constraint> + </properties> + <defaultValue>validate</defaultValue> + </leafNode> + <leafNode name="dnssec"> + <properties> + <help>DNSSEC mode</help> + <completionHelp> + <list>ignore validate require</list> + </completionHelp> + <valueHelp> + <format>ignore</format> + <description>Do not do DNSSEC validation</description> + </valueHelp> + <valueHelp> + <format>validate</format> + <description>Reject zones with incorrect signatures but accept unsigned zones</description> + </valueHelp> + <valueHelp> + <format>require</format> + <description>Require DNSSEC validation</description> + </valueHelp> + <constraint> + <regex>(ignore|validate|require)</regex> + </constraint> + </properties> + <defaultValue>validate</defaultValue> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_event-handler.xml.in b/interface-definitions/service_event-handler.xml.in new file mode 100644 index 0000000..4154081 --- /dev/null +++ b/interface-definitions/service_event-handler.xml.in @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="event-handler" owner="${vyos_conf_scripts_dir}/service_event-handler.py"> + <properties> + <help>Service event handler</help> + <priority>2</priority> + </properties> + <children> + <tagNode name="event"> + <properties> + <help>Event handler name</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Logs filter settings</help> + </properties> + <children> + <leafNode name="pattern"> + <properties> + <help>Match pattern (regex)</help> + </properties> + </leafNode> + <leafNode name="syslog-identifier"> + <properties> + <help>Identifier of a process in syslog (string)</help> + </properties> + </leafNode> + </children> + </node> + <node name="script"> + <properties> + <help>Event handler script file</help> + </properties> + <children> + <leafNode name="arguments"> + <properties> + <help>Script arguments</help> + </properties> + </leafNode> + <tagNode name="environment"> + <properties> + <help>Script environment arguments</help> + </properties> + <children> + <leafNode name="value"> + <properties> + <help>Environment value</help> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="path"> + <properties> + <help>Path to the script</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_https.xml.in b/interface-definitions/service_https.xml.in new file mode 100644 index 0000000..afe430c --- /dev/null +++ b/interface-definitions/service_https.xml.in @@ -0,0 +1,190 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="https" owner="${vyos_conf_scripts_dir}/service_https.py"> + <properties> + <help>HTTPS configuration</help> + <priority>1001</priority> + </properties> + <children> + <node name="api"> + <properties> + <help>VyOS HTTP API configuration</help> + </properties> + <children> + <node name="keys"> + <properties> + <help>HTTP API keys</help> + </properties> + <children> + <tagNode name="id"> + <properties> + <help>HTTP API id</help> + </properties> + <children> + <leafNode name="key"> + <properties> + <help>HTTP API plaintext key</help> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + <leafNode name="strict"> + <properties> + <help>Enforce strict path checking</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="debug"> + <properties> + <help>Debug</help> + <valueless/> + <hidden/> + </properties> + </leafNode> + <node name="graphql"> + <properties> + <help>GraphQL support</help> + </properties> + <children> + <leafNode name="introspection"> + <properties> + <help>Schema introspection</help> + <valueless/> + </properties> + </leafNode> + <node name="authentication"> + <properties> + <help>GraphQL authentication</help> + </properties> + <children> + <leafNode name="type"> + <properties> + <help>Authentication type</help> + <completionHelp> + <list>key token</list> + </completionHelp> + <valueHelp> + <format>key</format> + <description>Use API keys</description> + </valueHelp> + <valueHelp> + <format>token</format> + <description>Use JWT token</description> + </valueHelp> + <constraint> + <regex>(key|token)</regex> + </constraint> + </properties> + <defaultValue>key</defaultValue> + </leafNode> + <leafNode name="expiration"> + <properties> + <help>Token time to expire in seconds</help> + <valueHelp> + <format>u32:60-31536000</format> + <description>Token lifetime in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 60-31536000"/> + </constraint> + </properties> + <defaultValue>3600</defaultValue> + </leafNode> + <leafNode name="secret-length"> + <properties> + <help>Length of shared secret in bytes</help> + <valueHelp> + <format>u32:16-65535</format> + <description>Byte length of generated shared secret</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 16-65535"/> + </constraint> + </properties> + <defaultValue>32</defaultValue> + </leafNode> + </children> + </node> + </children> + </node> + <node name="cors"> + <properties> + <help>Set CORS options</help> + </properties> + <children> + <leafNode name="allow-origin"> + <properties> + <help>Allow resource request from origin</help> + <multi/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + #include <include/allow-client.xml.i> + <leafNode name="enable-http-redirect"> + <properties> + <help>Enable HTTP to HTTPS redirect</help> + <valueless/> + </properties> + </leafNode> + #include <include/listen-address.xml.i> + #include <include/port-number.xml.i> + <leafNode name='port'> + <defaultValue>443</defaultValue> + </leafNode> + <leafNode name="request-body-size-limit"> + <properties> + <help>Maximum request body size in megabytes</help> + <valueHelp> + <format>u32:1-256</format> + <description>Request body size in megabytes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-256"/> + </constraint> + </properties> + <defaultValue>1</defaultValue> + </leafNode> + <node name="certificates"> + <properties> + <help>TLS certificates</help> + </properties> + <children> + #include <include/pki/ca-certificate.xml.i> + #include <include/pki/certificate.xml.i> + #include <include/pki/dh-params.xml.i> + </children> + </node> + <leafNode name="tls-version"> + <properties> + <help>Specify available TLS version(s)</help> + <completionHelp> + <list>1.2 1.3</list> + </completionHelp> + <valueHelp> + <format>1.2</format> + <description>TLSv1.2</description> + </valueHelp> + <valueHelp> + <format>1.3</format> + <description>TLSv1.3</description> + </valueHelp> + <constraint> + <regex>(1.2|1.3)</regex> + </constraint> + <multi/> + </properties> + <defaultValue>1.2 1.3</defaultValue> + </leafNode> + #include <include/interface/vrf.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_ids_ddos-protection.xml.in b/interface-definitions/service_ids_ddos-protection.xml.in new file mode 100644 index 0000000..3ef2640 --- /dev/null +++ b/interface-definitions/service_ids_ddos-protection.xml.in @@ -0,0 +1,167 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="ids"> + <properties> + <help>Intrusion Detection System</help> + </properties> + <children> + <node name="ddos-protection" owner="${vyos_conf_scripts_dir}/service_ids_ddos-protection.py"> + <properties> + <help>FastNetMon detection and protection parameters</help> + <priority>731</priority> + </properties> + <children> + <leafNode name="alert-script"> + <properties> + <help>Path to fastnetmon alert script</help> + </properties> + </leafNode> + <leafNode name="ban-time"> + <properties> + <help>How long we should keep an IP in blocked state</help> + <valueHelp> + <format>u32:1-4294967294</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967294"/> + </constraint> + </properties> + <defaultValue>1900</defaultValue> + </leafNode> + <leafNode name="direction"> + <properties> + <help>Direction for processing traffic</help> + <completionHelp> + <list>in out</list> + </completionHelp> + <constraint> + <regex>(in|out)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="excluded-network"> + <properties> + <help>Specify IPv4 and IPv6 networks which are going to be excluded from protection</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix(es) to exclude</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix(es) to exclude</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv6-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="listen-interface"> + <properties> + <help>Listen interface for mirroring traffic</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <multi/> + </properties> + </leafNode> + <leafNode name="mode"> + <properties> + <help>Traffic capture mode</help> + <completionHelp> + <list>mirror sflow</list> + </completionHelp> + <valueHelp> + <format>mirror</format> + <description>Listen to mirrored traffic</description> + </valueHelp> + <valueHelp> + <format>sflow</format> + <description>Capture sFlow flows</description> + </valueHelp> + <constraint> + <regex>(mirror|sflow)</regex> + </constraint> + </properties> + </leafNode> + <node name="sflow"> + <properties> + <help>Sflow settings</help> + </properties> + <children> + #include <include/listen-address-ipv4-single.xml.i> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>6343</defaultValue> + </leafNode> + </children> + </node> + <leafNode name="network"> + <properties> + <help>Specify IPv4 and IPv6 networks which belong to you</help> + <valueHelp> + <format>ipv4net</format> + <description>Your IPv4 prefix(es)</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>Your IPv6 prefix(es)</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv6-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + <node name="threshold"> + <properties> + <help>Attack limits thresholds</help> + </properties> + <children> + <node name="general"> + <properties> + <help>General threshold</help> + </properties> + <children> + #include <include/ids/threshold.xml.i> + </children> + </node> + <node name="tcp"> + <properties> + <help>TCP threshold</help> + </properties> + <children> + #include <include/ids/threshold.xml.i> + </children> + </node> + <node name="udp"> + <properties> + <help>UDP threshold</help> + </properties> + <children> + #include <include/ids/threshold.xml.i> + </children> + </node> + <node name="icmp"> + <properties> + <help>ICMP threshold</help> + </properties> + <children> + #include <include/ids/threshold.xml.i> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service_ipoe-server.xml.in new file mode 100644 index 0000000..25bc43c --- /dev/null +++ b/interface-definitions/service_ipoe-server.xml.in @@ -0,0 +1,198 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="ipoe-server" owner="${vyos_conf_scripts_dir}/service_ipoe-server.py"> + <properties> + <help>Internet Protocol over Ethernet (IPoE) Server</help> + <priority>900</priority> + </properties> + <children> + <node name="authentication"> + <properties> + <help>Client authentication methods</help> + </properties> + <children> + #include <include/accel-ppp/auth-mode.xml.i> + <tagNode name="interface"> + <properties> + <help>Network interface for client MAC addresses</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + <tagNode name="mac"> + <properties> + <help>Media Access Control (MAC) address</help> + <valueHelp> + <format>macaddr</format> + <description>Hardware (MAC) address</description> + </valueHelp> + <constraint> + <validator name="mac-address"/> + </constraint> + </properties> + <children> + <node name="rate-limit"> + <properties> + <help>Upload/Download speed limits</help> + </properties> + <children> + <leafNode name="upload"> + <properties> + <help>Upload bandwidth limit in kbits/sec</help> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="download"> + <properties> + <help>Download bandwidth limit in kbits/sec</help> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <leafNode name="vlan"> + <properties> + <help>VLAN monitor for automatic creation of VLAN interfaces</help> + <valueHelp> + <format>u32:1-4094</format> + <description>Client VLAN id</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4094"/> + </constraint> + <constraintErrorMessage>VLAN IDs need to be in range 1-4094</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + #include <include/radius-auth-server-ipv4.xml.i> + #include <include/accel-ppp/radius-additions.xml.i> + <node name="radius"> + <children> + #include <include/accel-ppp/radius-additions-rate-limit.xml.i> + </children> + </node> + </children> + </node> + <tagNode name="interface"> + <properties> + <help>Interface to listen dhcp or unclassified packets</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + <leafNode name="mode"> + <properties> + <help>Client connectivity mode</help> + <completionHelp> + <list>l2 l3</list> + </completionHelp> + <valueHelp> + <format>l2</format> + <description>Client located on same interface as server</description> + </valueHelp> + <valueHelp> + <format>l3</format> + <description>Client located behind a router</description> + </valueHelp> + <constraint> + <regex>(l2|l3)</regex> + </constraint> + </properties> + <defaultValue>l2</defaultValue> + </leafNode> + <leafNode name="network"> + <properties> + <help>Enables clients to share the same network or each client has its own vlan</help> + <completionHelp> + <list>shared vlan</list> + </completionHelp> + <constraint> + <regex>(shared|vlan)</regex> + </constraint> + <valueHelp> + <format>shared</format> + <description>Multiple clients share the same network</description> + </valueHelp> + <valueHelp> + <format>vlan</format> + <description>One VLAN per client</description> + </valueHelp> + </properties> + <defaultValue>shared</defaultValue> + </leafNode> + <leafNode name="client-subnet"> + <properties> + <help>Client address pool</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + </leafNode> + <node name="external-dhcp"> + <properties> + <help>DHCP requests will be forwarded</help> + </properties> + <children> + <leafNode name="dhcp-relay"> + <properties> + <help>DHCP Server the request will be redirected to.</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address of the DHCP Server</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="giaddr"> + <properties> + <help>Relay Agent IPv4 Address</help> + <valueHelp> + <format>ipv4</format> + <description>Gateway IP address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + #include <include/accel-ppp/vlan.xml.i> + #include <include/accel-ppp/vlan-mon.xml.i> + </children> + </tagNode> + #include <include/accel-ppp/client-ip-pool.xml.i> + #include <include/accel-ppp/client-ipv6-pool.xml.i> + #include <include/accel-ppp/default-pool.xml.i> + #include <include/accel-ppp/default-ipv6-pool.xml.i> + #include <include/accel-ppp/extended-scripts.xml.i> + #include <include/accel-ppp/gateway-address-multi.xml.i> + #include <include/accel-ppp/limits.xml.i> + #include <include/accel-ppp/max-concurrent-sessions.xml.i> + #include <include/accel-ppp/shaper.xml.i> + #include <include/accel-ppp/snmp.xml.i> + #include <include/generic-description.xml.i> + #include <include/name-server-ipv4-ipv6.xml.i> + #include <include/accel-ppp/log.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_lldp.xml.in b/interface-definitions/service_lldp.xml.in new file mode 100644 index 0000000..51a9f9c --- /dev/null +++ b/interface-definitions/service_lldp.xml.in @@ -0,0 +1,192 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="lldp" owner="${vyos_conf_scripts_dir}/service_lldp.py"> + <properties> + <help>LLDP settings</help> + <priority>985</priority> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Location data for interface</help> + <valueHelp> + <format>all</format> + <description>Location data all interfaces</description> + </valueHelp> + <valueHelp> + <format>txt</format> + <description>Location data for a specific interface</description> + </valueHelp> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + <list>all</list> + </completionHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + <regex>all</regex> + </constraint> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <node name="location"> + <properties> + <help>LLDP-MED location data</help> + </properties> + <children> + <node name="coordinate-based"> + <properties> + <help>Coordinate based location</help> + </properties> + <children> + <leafNode name="altitude"> + <properties> + <help>Altitude in meters</help> + <valueHelp> + <format>0</format> + <description>No altitude</description> + </valueHelp> + <valueHelp> + <format>[+-]<meters></format> + <description>Altitude in meters</description> + </valueHelp> + <constraintErrorMessage>Altitude should be a positive or negative number</constraintErrorMessage> + <constraint> + <validator name="numeric"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="datum"> + <properties> + <help>Coordinate datum type</help> + <valueHelp> + <format>WGS84</format> + <description>WGS84</description> + </valueHelp> + <valueHelp> + <format>NAD83</format> + <description>NAD83</description> + </valueHelp> + <valueHelp> + <format>MLLW</format> + <description>NAD83/MLLW</description> + </valueHelp> + <completionHelp> + <list>WGS84 NAD83 MLLW</list> + </completionHelp> + <constraintErrorMessage>Datum should be WGS84, NAD83, or MLLW</constraintErrorMessage> + <constraint> + <regex>(WGS84|NAD83|MLLW)</regex> + </constraint> + </properties> + <defaultValue>WGS84</defaultValue> + </leafNode> + <leafNode name="latitude"> + <properties> + <help>Latitude</help> + <valueHelp> + <format><latitude></format> + <description>Latitude (example "37.524449N")</description> + </valueHelp> + <constraintErrorMessage>Latitude should be a number followed by S or N</constraintErrorMessage> + <constraint> + <regex>(\d+)(\.\d+)?[nNsS]</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="longitude"> + <properties> + <help>Longitude</help> + <valueHelp> + <format><longitude></format> + <description>Longitude (example "122.267255W")</description> + </valueHelp> + <constraintErrorMessage>Longiture should be a number followed by E or W</constraintErrorMessage> + <constraint> + <regex>(\d+)(\.\d+)?[eEwW]</regex> + </constraint> + </properties> + </leafNode> + </children> + </node> + <leafNode name="elin"> + <properties> + <help>ECS ELIN (Emergency location identifier number)</help> + <valueHelp> + <format>u32:0-9999999999</format> + <description>Emergency Call Service ELIN number (between 10-25 numbers)</description> + </valueHelp> + <constraint> + <regex>[0-9]{10,25}</regex> + </constraint> + <constraintErrorMessage>ELIN number must be between 10-25 numbers</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + <node name="legacy-protocols"> + <properties> + <help>Legacy (vendor specific) protocols</help> + </properties> + <children> + <leafNode name="cdp"> + <properties> + <help>Listen for CDP for Cisco routers/switches</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="edp"> + <properties> + <help>Listen for EDP for Extreme routers/switches</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="fdp"> + <properties> + <help>Listen for FDP for Foundry routers/switches</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="sonmp"> + <properties> + <help>Listen for SONMP for Nortel routers/switches</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="management-address"> + <properties> + <help>Management IP Address</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 Management Address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 Management Address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="snmp"> + <properties> + <help>Enable SNMP queries of the LLDP database</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_mdns_repeater.xml.in b/interface-definitions/service_mdns_repeater.xml.in new file mode 100644 index 0000000..5d6f61d --- /dev/null +++ b/interface-definitions/service_mdns_repeater.xml.in @@ -0,0 +1,82 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="mdns"> + <properties> + <help>Multicast DNS (mDNS) parameters</help> + </properties> + <children> + <node name="repeater" owner="${vyos_conf_scripts_dir}/service_mdns_repeater.py"> + <properties> + <help>mDNS repeater configuration</help> + <priority>990</priority> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/generic-interface-multi.xml.i> + <leafNode name="ip-version"> + <properties> + <help>IP address version to use</help> + <valueHelp> + <format>_ipv4</format> + <description>Use only IPv4 address</description> + </valueHelp> + <valueHelp> + <format>_ipv6</format> + <description>Use only IPv6 address</description> + </valueHelp> + <valueHelp> + <format>both</format> + <description>Use both IPv4 and IPv6 address</description> + </valueHelp> + <completionHelp> + <list>ipv4 ipv6 both</list> + </completionHelp> + <constraint> + <regex>(ipv[46]|both)</regex> + </constraint> + <constraintErrorMessage>IP Version must be literal 'ipv4', 'ipv6' or 'both'</constraintErrorMessage> + </properties> + <defaultValue>both</defaultValue> + </leafNode> + <leafNode name="browse-domain"> + <properties> + <help>mDNS browsing domains in addition to the default one</help> + <valueHelp> + <format>txt</format> + <description>mDNS browsing domain</description> + </valueHelp> + <constraint> + <validator name="fqdn"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="allow-service"> + <properties> + <help>Allowed mDNS services to be repeated</help> + <valueHelp> + <format>txt</format> + <description>mDNS service</description> + </valueHelp> + <constraint> + <regex>[-_.a-zA-Z0-9]+</regex> + </constraint> + <constraintErrorMessage>Service name must be alphanumeric and can contain hyphens and underscores</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + <leafNode name="vrrp-disable"> + <properties> + <help>Disables mDNS repeater on VRRP interfaces not in MASTER state</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_monitoring_telegraf.xml.in b/interface-definitions/service_monitoring_telegraf.xml.in new file mode 100644 index 0000000..2ac0d94 --- /dev/null +++ b/interface-definitions/service_monitoring_telegraf.xml.in @@ -0,0 +1,317 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="monitoring"> + <properties> + <help>Monitoring services</help> + </properties> + <children> + <node name="telegraf" owner="${vyos_conf_scripts_dir}/service_monitoring_telegraf.py"> + <properties> + <help>Telegraf metric collector</help> + <priority>1280</priority> + </properties> + <children> + <node name="influxdb"> + <properties> + <help>Output plugin InfluxDB</help> + </properties> + <children> + <node name="authentication"> + <properties> + <help>Authentication parameters</help> + </properties> + <children> + <leafNode name="organization"> + <properties> + <help>Authentication organization for InfluxDB v2</help> + <constraint> + <regex>[a-zA-Z][1-9a-zA-Z@_\-.]{2,50}</regex> + </constraint> + <constraintErrorMessage>Organization name must be alphanumeric and can contain hyphens, underscores and at symbol.</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="token"> + <properties> + <help>Authentication token for InfluxDB v2</help> + <valueHelp> + <format>txt</format> + <description>Authentication token</description> + </valueHelp> + <constraint> + <regex>[a-zA-Z0-9-_]{86}==</regex> + </constraint> + <constraintErrorMessage>Token must be 88 characters long and must contain only [a-zA-Z0-9-_] and '==' characters.</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + <leafNode name="bucket"> + <properties> + <help>Remote bucket</help> + </properties> + <defaultValue>main</defaultValue> + </leafNode> + #include <include/url-http-https.xml.i> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>8086</defaultValue> + </leafNode> + </children> + </node> + <node name="azure-data-explorer"> + <properties> + <help>Output plugin Azure Data Explorer</help> + </properties> + <children> + <node name="authentication"> + <properties> + <help>Authentication parameters</help> + </properties> + <children> + <leafNode name="client-id"> + <properties> + <help>Application client id</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Client-id is limited to alphanumerical characters and can contain hyphen and underscores</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="client-secret"> + <properties> + <help>Application client secret</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Client-secret is limited to alphanumerical characters and can contain hyphen and underscores</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="tenant-id"> + <properties> + <help>Set tenant id</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Tenant-id is limited to alphanumerical characters and can contain hyphen and underscores</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + <leafNode name="database"> + <properties> + <help>Remote database name</help> + <valueHelp> + <format>txt</format> + <description>Remote database name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Database is limited to alphanumerical characters and can contain hyphen and underscores</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="group-metrics"> + <properties> + <help>Type of metrics grouping when push to Azure Data Explorer</help> + <completionHelp> + <list>single-table table-per-metric</list> + </completionHelp> + <valueHelp> + <format>single-table</format> + <description>Metrics stores in one table</description> + </valueHelp> + <valueHelp> + <format>table-per-metric</format> + <description>One table per gorups of metric by the metric name</description> + </valueHelp> + <constraint> + <regex>(single-table|table-per-metric)</regex> + </constraint> + </properties> + <defaultValue>table-per-metric</defaultValue> + </leafNode> + <leafNode name="table"> + <properties> + <help>Name of the single table [Only if set group-metrics single-table]</help> + <valueHelp> + <format>txt</format> + <description>Table name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + <constraintErrorMessage>Table is limited to alphanumerical characters and can contain hyphen and underscores</constraintErrorMessage> + </properties> + </leafNode> + #include <include/url-http-https.xml.i> + </children> + </node> + <node name="loki"> + <properties> + <help>Output plugin Loki</help> + </properties> + <children> + <node name="authentication"> + <properties> + <help>HTTP basic authentication parameters</help> + </properties> + <children> + #include <include/generic-username.xml.i> + #include <include/generic-password.xml.i> + </children> + </node> + <leafNode name="metric-name-label"> + <properties> + <help>Metric name label</help> + <valueHelp> + <format>txt</format> + <description>Label to use for the metric name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + </properties> + </leafNode> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>3100</defaultValue> + </leafNode> + #include <include/url-http-https.xml.i> + </children> + </node> + <leafNode name="source"> + <properties> + <help>Source parameters for monitoring</help> + <completionHelp> + <list>all hardware-utilization logs network system telegraf</list> + </completionHelp> + <valueHelp> + <format>all</format> + <description>All parameters</description> + </valueHelp> + <valueHelp> + <format>hardware-utilization</format> + <description>Hardware-utilization parameters (CPU, disk, memory)</description> + </valueHelp> + <valueHelp> + <format>logs</format> + <description>Logs parameters</description> + </valueHelp> + <valueHelp> + <format>network</format> + <description>Network parameters (net, netstat, nftables)</description> + </valueHelp> + <valueHelp> + <format>system</format> + <description>System parameters (system, processes, interrupts)</description> + </valueHelp> + <valueHelp> + <format>telegraf</format> + <description>Telegraf internal statistics</description> + </valueHelp> + <constraint> + <regex>(all|hardware-utilization|logs|network|system|telegraf)</regex> + </constraint> + <multi/> + </properties> + <defaultValue>all</defaultValue> + </leafNode> + <node name="prometheus-client"> + <properties> + <help>Output plugin Prometheus client</help> + </properties> + <children> + <node name="authentication"> + <properties> + <help>HTTP basic authentication parameters</help> + </properties> + <children> + <leafNode name="username"> + <properties> + <help>Authentication username</help> + </properties> + </leafNode> + <leafNode name="password"> + <properties> + <help>Authentication password</help> + <valueHelp> + <format>txt</format> + <description>Authentication password</description> + </valueHelp> + </properties> + </leafNode> + </children> + </node> + <leafNode name="allow-from"> + <properties> + <help>Networks allowed to query this server</help> + <valueHelp> + <format>ipv4net</format> + <description>IP address and prefix length</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <multi/> + <constraint> + <validator name="ip-prefix"/> + </constraint> + </properties> + </leafNode> + #include <include/listen-address-single.xml.i> + <leafNode name="metric-version"> + <properties> + <help>Metric version control mapping from Telegraf to Prometheus format</help> + <valueHelp> + <format>u32:1-2</format> + <description>Metric version (default: 2)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2"/> + </constraint> + </properties> + <defaultValue>2</defaultValue> + </leafNode> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>9273</defaultValue> + </leafNode> + </children> + </node> + <node name="splunk"> + <properties> + <help>Output plugin Splunk</help> + </properties> + <children> + <node name="authentication"> + <properties> + <help>HTTP basic authentication parameters</help> + </properties> + <children> + <leafNode name="token"> + <properties> + <help>Authorization token</help> + </properties> + </leafNode> + <leafNode name="insecure"> + <properties> + <help>Use TLS but skip host validation</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + #include <include/url-http-https.xml.i> + </children> + </node> + #include <include/interface/vrf.xml.i> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_monitoring_zabbix-agent.xml.in b/interface-definitions/service_monitoring_zabbix-agent.xml.in new file mode 100644 index 0000000..e44b313 --- /dev/null +++ b/interface-definitions/service_monitoring_zabbix-agent.xml.in @@ -0,0 +1,195 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="monitoring"> + <children> + <node name="zabbix-agent" owner="${vyos_conf_scripts_dir}/service_monitoring_zabbix-agent.py"> + <properties> + <help>Zabbix-agent settings</help> + <priority>1280</priority> + </properties> + <children> + <leafNode name="directory"> + <properties> + <help>Folder containing individual Zabbix-agent configuration files</help> + <constraint> + <validator name="file-path" argument="--directory"/> + </constraint> + </properties> + </leafNode> + <leafNode name="host-name"> + <properties> + <help>Zabbix agent hostname</help> + <constraint> + #include <include/constraint/host-name.xml.i> + </constraint> + <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage> + </properties> + </leafNode> + <node name="limits"> + <properties> + <help>Limit settings</help> + </properties> + <children> + <leafNode name="buffer-flush-interval"> + <properties> + <help>Do not keep data longer than N seconds in buffer</help> + <valueHelp> + <format>u32:1-3600</format> + <description>Seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-3600"/> + </constraint> + <constraintErrorMessage>buffer-flush-interval must be between 1 and 3600 seconds</constraintErrorMessage> + </properties> + <defaultValue>5</defaultValue> + </leafNode> + <leafNode name="buffer-size"> + <properties> + <help>Maximum number of values in a memory buffer</help> + <valueHelp> + <format>u32:2-65535</format> + <description>Maximum number of values in a memory buffer</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 2-65535"/> + </constraint> + <constraintErrorMessage>Buffer-size must be between 2 and 65535</constraintErrorMessage> + </properties> + <defaultValue>100</defaultValue> + </leafNode> + </children> + </node> + <node name="log"> + <properties> + <help>Log settings</help> + </properties> + <children> + <leafNode name="debug-level"> + <properties> + <help>Debug level</help> + <completionHelp> + <list>basic critical error warning debug extended-debug</list> + </completionHelp> + <valueHelp> + <format>basic</format> + <description>Basic information</description> + </valueHelp> + <valueHelp> + <format>critical</format> + <description>Critical information</description> + </valueHelp> + <valueHelp> + <format>error</format> + <description>Error information</description> + </valueHelp> + <valueHelp> + <format>warning</format> + <description>Warnings</description> + </valueHelp> + <valueHelp> + <format>debug</format> + <description>Debug information</description> + </valueHelp> + <valueHelp> + <format>extended-debug</format> + <description>Extended debug information</description> + </valueHelp> + <constraint> + <regex>(basic|critical|error|warning|debug|extended-debug)</regex> + </constraint> + </properties> + <defaultValue>warning</defaultValue> + </leafNode> + <leafNode name="remote-commands"> + <properties> + <help>Enable logging of executed shell commands as warnings</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="size"> + <properties> + <help>Log file size in megabytes</help> + <valueHelp> + <format>u32:0-1024</format> + <description>Megabytes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-1024"/> + </constraint> + <constraintErrorMessage>Size must be between 0 and 1024 Megabytes</constraintErrorMessage> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + </children> + </node> + #include <include/listen-address.xml.i> + <leafNode name="listen-address"> + <defaultValue>0.0.0.0</defaultValue> + </leafNode> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>10050</defaultValue> + </leafNode> + <leafNode name="server"> + <properties> + <help>Remote server to connect to</help> + <valueHelp> + <format>ipv4</format> + <description>Server IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Server IPv6 address</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>Server hostname/FQDN</description> + </valueHelp> + <multi/> + </properties> + </leafNode> + <tagNode name="server-active"> + <properties> + <help>Remote server address to get active checks from</help> + <valueHelp> + <format>ipv4</format> + <description>Server IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Server IPv6 address</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>Server hostname/FQDN</description> + </valueHelp> + </properties> + <children> + #include <include/port-number.xml.i> + </children> + </tagNode> + <leafNode name="timeout"> + <properties> + <help>Item processing timeout in seconds</help> + <valueHelp> + <format>u32:1-30</format> + <description>Item processing timeout</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-30"/> + </constraint> + <constraintErrorMessage>Timeout must be between 1 and 30 seconds</constraintErrorMessage> + </properties> + <defaultValue>3</defaultValue> + </leafNode> + #include <include/interface/vrf.xml.i> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_ndp-proxy.xml.in b/interface-definitions/service_ndp-proxy.xml.in new file mode 100644 index 0000000..aabba3f --- /dev/null +++ b/interface-definitions/service_ndp-proxy.xml.in @@ -0,0 +1,133 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="ndp-proxy" owner="${vyos_conf_scripts_dir}/service_ndp-proxy.py"> + <properties> + <help>Neighbor Discovery Protocol (NDP) Proxy</help> + <priority>600</priority> + </properties> + <children> + <leafNode name="route-refresh"> + <properties> + <help>Refresh interval for IPv6 routes</help> + <valueHelp> + <format>u32:10000-120000</format> + <description>Time in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 10000-120000"/> + </constraint> + <constraintErrorMessage>Route-refresh must be between 10000 and 120000 milliseconds</constraintErrorMessage> + </properties> + <defaultValue>30000</defaultValue> + </leafNode> + <tagNode name="interface"> + <properties> + <help>NDP proxy listener interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <leafNode name="enable-router-bit"> + <properties> + <help>Enable router bit in Neighbor Advertisement messages</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="timeout"> + <properties> + <help>Timeout for Neighbor Advertisement after Neighbor Solicitation message</help> + <valueHelp> + <format>u32:500-120000</format> + <description>Timeout in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 500-120000"/> + </constraint> + <constraintErrorMessage>Timeout must be between 500 and 120000 milliseconds</constraintErrorMessage> + </properties> + <defaultValue>500</defaultValue> + </leafNode> + <leafNode name="ttl"> + <properties> + <help>Proxy entry cache Time-To-Live</help> + <valueHelp> + <format>u32:10000-120000</format> + <description>Time in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 10000-120000"/> + </constraint> + <constraintErrorMessage>TTL must be between 10000 and 120000 milliseconds</constraintErrorMessage> + </properties> + <defaultValue>30000</defaultValue> + </leafNode> + <tagNode name="prefix"> + <properties> + <help>Prefix target addresses are matched against</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 network prefix</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + <validator name="ipv6-address"/> + </constraint> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <leafNode name="mode"> + <properties> + <help>Specify the running mode of the rule</help> + <completionHelp> + <list>static auto interface</list> + </completionHelp> + <valueHelp> + <format>static</format> + <description>Immediately answer any Neighbor Solicitation Messages</description> + </valueHelp> + <valueHelp> + <format>auto</format> + <description>Check for a matching route in /proc/net/ipv6_route</description> + </valueHelp> + <valueHelp> + <format>interface</format> + <description>Forward Neighbor Solicitation message through specified interface</description> + </valueHelp> + <constraint> + <regex>(static|auto|interface)</regex> + </constraint> + <constraintErrorMessage>Mode must be either one of: static, auto or interface</constraintErrorMessage> + </properties> + <defaultValue>static</defaultValue> + </leafNode> + <leafNode name="interface"> + <properties> + <help>Interface to forward Neighbor Solicitation message through. Required for "iface" mode</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_ntp.xml.in b/interface-definitions/service_ntp.xml.in new file mode 100644 index 0000000..5dc0cd2 --- /dev/null +++ b/interface-definitions/service_ntp.xml.in @@ -0,0 +1,175 @@ +<?xml version="1.0"?> +<!-- NTP configuration --> +<interfaceDefinition> + <node name="service"> + <children> + <node name="ntp" owner="${vyos_conf_scripts_dir}/service_ntp.py"> + <properties> + <help>Network Time Protocol (NTP) configuration</help> + <priority>900</priority> + </properties> + <children> + #include <include/allow-client.xml.i> + #include <include/generic-interface.xml.i> + #include <include/listen-address.xml.i> + #include <include/interface/vrf.xml.i> + <node name="ptp"> + <properties> + <help>Enable Precision Time Protocol (PTP) transport</help> + </properties> + <children> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>319</defaultValue> + </leafNode> + <node name="timestamp"> + <properties> + <help>Enable timestamping of packets in the NIC hardware</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Interface to enable timestamping on</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + <list>all</list> + </completionHelp> + <valueHelp> + <format>all</format> + <description>Select all interfaces</description> + </valueHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + <regex>all</regex> + </constraint> + </properties> + <children> + <leafNode name="receive-filter"> + <properties> + <help>Selects which inbound packets are timestamped by the NIC</help> + <completionHelp> + <list>all ntp ptp none</list> + </completionHelp> + <valueHelp> + <format>all</format> + <description>All packets are timestamped</description> + </valueHelp> + <valueHelp> + <format>ntp</format> + <description>Only NTP packets are timestamped</description> + </valueHelp> + <valueHelp> + <format>ptp</format> + <description>Only PTP or NTP packets using the PTP transport are timestamped</description> + </valueHelp> + <valueHelp> + <format>none</format> + <description>No packet is timestamped</description> + </valueHelp> + <constraint> + <regex>(all|ntp|ptp|none)</regex> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + <leafNode name="leap-second"> + <properties> + <help>Leap second behavior</help> + <completionHelp> + <list>ignore smear system timezone</list> + </completionHelp> + <valueHelp> + <format>ignore</format> + <description>No correction is applied to the clock for the leap second</description> + </valueHelp> + <valueHelp> + <format>smear</format> + <description>Correct served time slowly be slewing instead of stepping</description> + </valueHelp> + <valueHelp> + <format>system</format> + <description>Kernel steps the system clock forward or backward</description> + </valueHelp> + <valueHelp> + <format>timezone</format> + <description>Use UTC timezone database to determine when will the next leap second occur</description> + </valueHelp> + <constraint> + <regex>(ignore|smear|system|timezone)</regex> + </constraint> + </properties> + <defaultValue>timezone</defaultValue> + </leafNode> + <tagNode name="server"> + <properties> + <help>Network Time Protocol (NTP) server</help> + <valueHelp> + <format>ipv4</format> + <description>IP address of NTP server</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of NTP server</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>Fully qualified domain name of NTP server</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + <validator name="fqdn"/> + </constraint> + </properties> + <children> + <leafNode name="noselect"> + <properties> + <help>Marks the server as unused</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="nts"> + <properties> + <help>Enable Network Time Security (NTS) for the server</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="pool"> + <properties> + <help>Associate with a number of remote servers</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="prefer"> + <properties> + <help>Marks the server as preferred</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ptp"> + <properties> + <help>Use Precision Time Protocol (PTP) transport for the server</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="interleave"> + <properties> + <help>Use the interleaved mode for the server</help> + <valueless/> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service_pppoe-server.xml.in new file mode 100644 index 0000000..0c99fd2 --- /dev/null +++ b/interface-definitions/service_pppoe-server.xml.in @@ -0,0 +1,180 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="pppoe-server" owner="${vyos_conf_scripts_dir}/service_pppoe-server.py"> + <properties> + <help>Point to Point over Ethernet (PPPoE) Server</help> + <priority>900</priority> + </properties> + <children> + #include <include/pppoe-access-concentrator.xml.i> + <leafNode name="access-concentrator"> + <defaultValue>vyos-ac</defaultValue> + </leafNode> + <node name="authentication"> + <properties> + <help>Authentication for remote access PPPoE Server</help> + </properties> + <children> + #include <include/accel-ppp/auth-local-users.xml.i> + #include <include/accel-ppp/auth-mode.xml.i> + #include <include/accel-ppp/auth-protocols.xml.i> + #include <include/radius-auth-server-ipv4.xml.i> + #include <include/accel-ppp/radius-additions.xml.i> + <node name="radius"> + <children> + #include <include/accel-ppp/radius-additions-rate-limit.xml.i> + <leafNode name="called-sid-format"> + <properties> + <help>Format of Called-Station-Id attribute</help> + <completionHelp> + <list>ifname ifname:mac</list> + </completionHelp> + <constraint> + <regex>(ifname|ifname:mac)</regex> + </constraint> + <constraintErrorMessage>Invalid Called-Station-Id format</constraintErrorMessage> + <valueHelp> + <format>ifname</format> + <description>NAS-Port-Id - should contain root interface name (NAS-Port-Id=eth1)</description> + </valueHelp> + <valueHelp> + <format>ifname:mac</format> + <description>NAS-Port-Id - should contain root interface name and mac address (NAS-Port-Id=eth1:00:00:00:00:00:00)</description> + </valueHelp> + </properties> + </leafNode> + </children> + </node> + <leafNode name="any-login"> + <properties> + <help>Authentication with any login</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <tagNode name="interface"> + <properties> + <help>interface(s) to listen on</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + #include <include/accel-ppp/vlan.xml.i> + #include <include/accel-ppp/vlan-mon.xml.i> + </children> + </tagNode> + <leafNode name="service-name"> + <properties> + <help>Service name</help> + <constraint> + <regex>[a-zA-Z0-9\-]{1,100}</regex> + </constraint> + <constraintErrorMessage>Service-name can contain aplhanumerical characters and dashes only (max. 100)</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + <leafNode name="accept-any-service"> + <properties> + <help>Accept any service name in PPPoE Active Discovery Request (PADR)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="accept-blank-service"> + <properties> + <help>Accept blank service name in PADR</help> + <valueless/> + </properties> + </leafNode> + <tagNode name="pado-delay"> + <properties> + <help>PADO delays</help> + <valueHelp> + <format>disable</format> + <description>Disable new connections</description> + </valueHelp> + <completionHelp> + <list>disable</list> + </completionHelp> + <valueHelp> + <format>u32:1-999999</format> + <description>Number in ms</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + <regex>disable</regex> + </constraint> + <constraintErrorMessage>Invalid PADO delay</constraintErrorMessage> + </properties> + <children> + <leafNode name="sessions"> + <properties> + <help>Number of sessions</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number of sessions</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Invalid number of delayed sessions</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="session-control"> + <properties> + <help>control sessions count</help> + <constraint> + <regex>(deny|disable|replace)</regex> + </constraint> + <constraintErrorMessage>Invalid value</constraintErrorMessage> + <valueHelp> + <format>disable</format> + <description>Disables session control</description> + </valueHelp> + <valueHelp> + <format>deny</format> + <description>Deny second session authorization</description> + </valueHelp> + <valueHelp> + <format>replace</format> + <description>Terminate first session when second is authorized</description> + </valueHelp> + <completionHelp> + <list>deny disable replace</list> + </completionHelp> + </properties> + <defaultValue>replace</defaultValue> + </leafNode> + #include <include/accel-ppp/client-ip-pool.xml.i> + #include <include/accel-ppp/client-ipv6-pool.xml.i> + #include <include/accel-ppp/default-pool.xml.i> + #include <include/accel-ppp/default-ipv6-pool.xml.i> + #include <include/accel-ppp/extended-scripts.xml.i> + #include <include/accel-ppp/gateway-address.xml.i> + #include <include/accel-ppp/limits.xml.i> + #include <include/accel-ppp/max-concurrent-sessions.xml.i> + #include <include/accel-ppp/mtu-128-16384.xml.i> + #include <include/accel-ppp/ppp-options.xml.i> + <node name="ppp-options"> + <children> + <leafNode name="min-mtu"> + <defaultValue>1280</defaultValue> + </leafNode> + </children> + </node> + #include <include/accel-ppp/shaper.xml.i> + #include <include/accel-ppp/snmp.xml.i> + #include <include/accel-ppp/wins-server.xml.i> + #include <include/generic-description.xml.i> + #include <include/name-server-ipv4-ipv6.xml.i> + #include <include/accel-ppp/log.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_router-advert.xml.in b/interface-definitions/service_router-advert.xml.in new file mode 100644 index 0000000..3fd3354 --- /dev/null +++ b/interface-definitions/service_router-advert.xml.in @@ -0,0 +1,405 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="router-advert" owner="${vyos_conf_scripts_dir}/service_router-advert.py"> + <properties> + <help>IPv6 Router Advertisements (RAs) service</help> + <priority>900</priority> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Interface to send RA on</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + <leafNode name="hop-limit"> + <properties> + <help>Set Hop Count field of the IP header for outgoing packets</help> + <valueHelp> + <format>u32:0</format> + <description>Unspecified (by this router)</description> + </valueHelp> + <valueHelp> + <format>u32:1-255</format> + <description>Value should represent current diameter of the Internet</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + <constraintErrorMessage>Hop count must be between 0 and 255</constraintErrorMessage> + </properties> + <defaultValue>64</defaultValue> + </leafNode> + <leafNode name="default-lifetime"> + <properties> + <help>Lifetime associated with the default router in units of seconds</help> + <valueHelp> + <format>u32:4-9000</format> + <description>Router Lifetime in seconds</description> + </valueHelp> + <valueHelp> + <format>0</format> + <description>Not a default router</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-0 --range 4-9000"/> + </constraint> + <constraintErrorMessage>Default router livetime bust be 0 or between 4 and 9000</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="default-preference"> + <properties> + <help>Preference associated with the default router,</help> + <completionHelp> + <list>low medium high</list> + </completionHelp> + <valueHelp> + <format>low</format> + <description>Default router has low preference</description> + </valueHelp> + <valueHelp> + <format>medium</format> + <description>Default router has medium preference</description> + </valueHelp> + <valueHelp> + <format>high</format> + <description>Default router has high preference</description> + </valueHelp> + <constraint> + <regex>(low|medium|high)</regex> + </constraint> + <constraintErrorMessage>Default preference must be low, medium or high</constraintErrorMessage> + </properties> + <defaultValue>medium</defaultValue> + </leafNode> + <leafNode name="dnssl"> + <properties> + <help>DNS search list</help> + <multi/> + </properties> + </leafNode> + <leafNode name="link-mtu"> + <properties> + <help>Link MTU value placed in RAs, exluded in RAs if unset</help> + <valueHelp> + <format>u32:1280-9000</format> + <description>Link MTU value in RAs</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1280-9000"/> + </constraint> + <constraintErrorMessage>Link MTU must be between 1280 and 9000</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="managed-flag"> + <properties> + <help>Hosts use the administered (stateful) protocol for address autoconfiguration in addition to any addresses autoconfigured using SLAAC</help> + <valueless/> + </properties> + </leafNode> + <node name="interval"> + <properties> + <help>Set interval between unsolicited multicast RAs</help> + </properties> + <children> + <leafNode name="max"> + <properties> + <help>Maximum interval between unsolicited multicast RAs</help> + <valueHelp> + <format>u32:4-1800</format> + <description>Maximum interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 4-1800"/> + </constraint> + <constraintErrorMessage>Maximum interval must be between 4 and 1800 seconds</constraintErrorMessage> + </properties> + <defaultValue>600</defaultValue> + </leafNode> + <leafNode name="min"> + <properties> + <help>Minimum interval between unsolicited multicast RAs</help> + <valueHelp> + <format>u32:3-1350</format> + <description>Minimum interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 3-1350"/> + </constraint> + <constraintErrorMessage>Minimum interval must be between 3 and 1350 seconds</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + #include <include/name-server-ipv6.xml.i> + <leafNode name="name-server-lifetime"> + <properties> + <help>Maximum duration how long the RDNSS entries are used</help> + <valueHelp> + <format>u32:0</format> + <description>Name-servers should no longer be used</description> + </valueHelp> + <valueHelp> + <format>u32:1-7200</format> + <description>Maximum interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-7200"/> + </constraint> + <constraintErrorMessage>Maximum interval must be between 1 and 7200 seconds</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="other-config-flag"> + <properties> + <help>Hosts use the administered (stateful) protocol for autoconfiguration of other (non-address) information</help> + <valueless/> + </properties> + </leafNode> + <tagNode name="route"> + <properties> + <help>IPv6 route to be advertised in Router Advertisements (RAs)</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 route to be advertized</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="valid-lifetime"> + <properties> + <help>Time in seconds that the route will remain valid</help> + <completionHelp> + <list>infinity</list> + </completionHelp> + <valueHelp> + <format>u32:1-4294967295</format> + <description>Time in seconds that the route will remain valid</description> + </valueHelp> + <valueHelp> + <format>infinity</format> + <description>Route will remain preferred forever</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + <regex>(infinity)</regex> + </constraint> + </properties> + <defaultValue>1800</defaultValue> + </leafNode> + <leafNode name="route-preference"> + <properties> + <help>Preference associated with the route,</help> + <completionHelp> + <list>low medium high</list> + </completionHelp> + <valueHelp> + <format>low</format> + <description>Route has low preference</description> + </valueHelp> + <valueHelp> + <format>medium</format> + <description>Route has medium preference</description> + </valueHelp> + <valueHelp> + <format>high</format> + <description>Route has high preference</description> + </valueHelp> + <constraint> + <regex>(low|medium|high)</regex> + </constraint> + <constraintErrorMessage>Route preference must be low, medium or high</constraintErrorMessage> + </properties> + <defaultValue>medium</defaultValue> + </leafNode> + <leafNode name="no-remove-route"> + <properties> + <help>Do not announce this route with a zero second lifetime upon shutdown</help> + <valueless/> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="nat64prefix"> + <properties> + <help>NAT64 prefix included in the router advertisements</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix to be advertized</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="valid-lifetime"> + <properties> + <help>Time in seconds that the prefix will remain valid</help> + <completionHelp> + <list>infinity</list> + </completionHelp> + <valueHelp> + <format>u32:4-65528</format> + <description>Time in seconds that the prefix will remain valid</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 4-65528"/> + </constraint> + </properties> + <defaultValue>65528</defaultValue> + </leafNode> + </children> + </tagNode> + <tagNode name="prefix"> + <properties> + <help>IPv6 prefix to be advertised in Router Advertisements (RAs)</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix to be advertized</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="no-autonomous-flag"> + <properties> + <help>Prefix can not be used for stateless address auto-configuration</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="no-on-link-flag"> + <properties> + <help>Prefix can not be used for on-link determination</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="deprecate-prefix"> + <properties> + <help>Upon shutdown, this option will deprecate the prefix by announcing it in the shutdown RA</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="decrement-lifetime"> + <properties> + <help>Lifetime is decremented by the number of seconds since the last RA - use in conjunction with a DHCPv6-PD prefix</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="preferred-lifetime"> + <properties> + <help>Time in seconds that the prefix will remain preferred</help> + <completionHelp> + <list>infinity</list> + </completionHelp> + <valueHelp> + <format>u32</format> + <description>Time in seconds that the prefix will remain preferred</description> + </valueHelp> + <valueHelp> + <format>infinity</format> + <description>Prefix will remain preferred forever</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + <regex>(infinity)</regex> + </constraint> + </properties> + <defaultValue>14400</defaultValue> + </leafNode> + <leafNode name="valid-lifetime"> + <properties> + <help>Time in seconds that the prefix will remain valid</help> + <completionHelp> + <list>infinity</list> + </completionHelp> + <valueHelp> + <format>u32:1-4294967295</format> + <description>Time in seconds that the prefix will remain valid</description> + </valueHelp> + <valueHelp> + <format>infinity</format> + <description>Prefix will remain preferred forever</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + <regex>(infinity)</regex> + </constraint> + </properties> + <defaultValue>2592000</defaultValue> + </leafNode> + </children> + </tagNode> + <leafNode name="source-address"> + <properties> + <help>Use IPv6 address as source address. Useful with VRRP.</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address to be advertized (must be configured on interface)</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="reachable-time"> + <properties> + <help>Time, in milliseconds, that a node assumes a neighbor is reachable after having received a reachability confirmation</help> + <valueHelp> + <format>u32:0</format> + <description>Reachable Time unspecified by this router</description> + </valueHelp> + <valueHelp> + <format>u32:1-3600000</format> + <description>Reachable Time value in RAs (in milliseconds)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-0 --range 1-3600000"/> + </constraint> + <constraintErrorMessage>Reachable time must be 0 or between 1 and 3600000 milliseconds</constraintErrorMessage> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="retrans-timer"> + <properties> + <help>Time in milliseconds between retransmitted Neighbor Solicitation messages</help> + <valueHelp> + <format>u32:0</format> + <description>Time, in milliseconds, between retransmitted Neighbor Solicitation messages</description> + </valueHelp> + <valueHelp> + <format>u32:1-4294967295</format> + <description>Minimum interval in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-0 --range 1-4294967295"/> + </constraint> + <constraintErrorMessage>Retransmit interval must be 0 or between 1 and 4294967295 milliseconds</constraintErrorMessage> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="no-send-advert"> + <properties> + <help>Do not send router adverts</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="no-send-interval"> + <properties> + <help>Do not send Advertisement Interval option in RAs</help> + <valueless/> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_salt-minion.xml.in b/interface-definitions/service_salt-minion.xml.in new file mode 100644 index 0000000..eaa2899 --- /dev/null +++ b/interface-definitions/service_salt-minion.xml.in @@ -0,0 +1,74 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="salt-minion" owner="${vyos_conf_scripts_dir}/service_salt-minion.py"> + <properties> + <help>Salt Minion</help> + <priority>500</priority> + </properties> + <children> + <leafNode name="hash"> + <properties> + <help>Hash used when discovering file on master server (default: sha256)</help> + <completionHelp> + <list>md5 sha1 sha224 sha256 sha384 sha512</list> + </completionHelp> + <constraint> + <regex>(md5|sha1|sha224|sha256|sha384|sha512)</regex> + </constraint> + </properties> + <defaultValue>sha256</defaultValue> + </leafNode> + <leafNode name="master"> + <properties> + <help>Hostname or IP address of the Salt master server</help> + <valueHelp> + <format>ipv4</format> + <description>Salt server IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Salt server IPv6 address</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>Salt server FQDN address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + <validator name="fqdn"/> + </constraint> + <constraintErrorMessage>Invalid FQDN or IP address</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + <leafNode name="id"> + <properties> + <help>Explicitly declare ID for this minion to use (default: hostname)</help> + </properties> + </leafNode> + <leafNode name="interval"> + <properties> + <help>Interval in minutes between updates (default: 60)</help> + <valueHelp> + <format>u32:1-1440</format> + <description>Update interval in minutes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-1440"/> + </constraint> + </properties> + <defaultValue>60</defaultValue> + </leafNode> + <leafNode name="master-key"> + <properties> + <help>URL with signature of master for auth reply verification</help> + </properties> + </leafNode> + #include <include/source-interface.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_sla.xml.in b/interface-definitions/service_sla.xml.in new file mode 100644 index 0000000..2cd6819 --- /dev/null +++ b/interface-definitions/service_sla.xml.in @@ -0,0 +1,37 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="sla" owner="${vyos_conf_scripts_dir}/service_sla.py"> + <properties> + <help>Service level agreement (SLA)</help> + <priority>2</priority> + </properties> + <children> + <node name="owamp-server"> + <properties> + <help>One-way active measurement protocol (OWAMP) server</help> + </properties> + <children> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>861</defaultValue> + </leafNode> + </children> + </node> + <node name="twamp-server"> + <properties> + <help>Two-way active measurement protocol (TWAMP) server</help> + </properties> + <children> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>862</defaultValue> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_snmp.xml.in b/interface-definitions/service_snmp.xml.in new file mode 100644 index 0000000..f23151e --- /dev/null +++ b/interface-definitions/service_snmp.xml.in @@ -0,0 +1,599 @@ +<?xml version="1.0"?> +<!-- SNMP forwarder configuration --> +<interfaceDefinition> + <node name="service"> + <children> + <node name="snmp" owner="${vyos_conf_scripts_dir}/service_snmp.py"> + <properties> + <help>Simple Network Management Protocol (SNMP)</help> + <priority>900</priority> + </properties> + <children> + <tagNode name="community"> + <properties> + <help>Community name</help> + <constraint> + <regex>[[:alnum:]-_!@*#]{1,100}</regex> + </constraint> + <constraintErrorMessage>Community string is limited to alphanumerical characters, -, _, !, @, *, and # with a total lenght of 100</constraintErrorMessage> + </properties> + <children> + <leafNode name="authorization"> + <properties> + <help>Authorization type</help> + <completionHelp> + <list>ro rw</list> + </completionHelp> + <valueHelp> + <format>ro</format> + <description>Read-Only</description> + </valueHelp> + <valueHelp> + <format>rw</format> + <description>Read-Write</description> + </valueHelp> + <constraint> + <regex>(ro|rw)</regex> + </constraint> + <constraintErrorMessage>Authorization type must be either 'rw' or 'ro'</constraintErrorMessage> + </properties> + <defaultValue>ro</defaultValue> + </leafNode> + <leafNode name="client"> + <properties> + <help>IP address of SNMP client allowed to contact system</help> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="network"> + <properties> + <help>Subnet of SNMP client(s) allowed to contact system</help> + <valueHelp> + <format>ipv4net</format> + <description>IP address and prefix length</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ip-prefix"/> + </constraint> + <multi/> + </properties> + <defaultValue>0.0.0.0/0 ::/0</defaultValue> + </leafNode> + </children> + </tagNode> + <leafNode name="contact"> + <properties> + <help>Contact information</help> + <constraint> + <regex>.{1,255}</regex> + </constraint> + <constraintErrorMessage>Contact information is limited to 255 characters or less</constraintErrorMessage> + </properties> + </leafNode> + #include <include/generic-description.xml.i> + <node name="mib"> + <properties> + <help>Management information base (MIB)</help> + </properties> + <children> + <leafNode name="interface-max"> + <properties> + <help>Sets the maximum number of interfaces included in IF-MIB data collection</help> + <valueHelp> + <format>u32:1-4294967295</format> + <description>Sets the maximum number of interfaces included in IF-MIB data collection</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="interface"> + <properties> + <help>Sets the interface name prefix to include in the IF-MIB data collection</help> + <completionHelp> + <list>br bond dum eth gnv macsec peth sstpc tun veth vti vtun vxlan wg wlan wwan</list> + </completionHelp> + <valueHelp> + <format>br</format> + <description>Allow prefix for IF-MIB data collection</description> + </valueHelp> + <valueHelp> + <format>bond</format> + <description>Allow prefix for IF-MIB data collection</description> + </valueHelp> + <valueHelp> + <format>dum</format> + <description>Allow prefix for IF-MIB data collection</description> + </valueHelp> + <valueHelp> + <format>eth</format> + <description>Allow prefix for IF-MIB data collection</description> + </valueHelp> + <valueHelp> + <format>gnv</format> + <description>Allow prefix for IF-MIB data collection</description> + </valueHelp> + <valueHelp> + <format>macsec</format> + <description>Allow prefix for IF-MIB data collection</description> + </valueHelp> + <valueHelp> + <format>peth</format> + <description>Allow prefix for IF-MIB data collection</description> + </valueHelp> + <valueHelp> + <format>sstpc</format> + <description>Allow prefix for IF-MIB data collection</description> + </valueHelp> + <valueHelp> + <format>tun</format> + <description>Allow prefix for IF-MIB data collection</description> + </valueHelp> + <valueHelp> + <format>veth</format> + <description>Allow prefix for IF-MIB data collection</description> + </valueHelp> + <valueHelp> + <format>vti</format> + <description>Allow prefix for IF-MIB data collection</description> + </valueHelp> + <valueHelp> + <format>vtun</format> + <description>Allow prefix for IF-MIB data collection</description> + </valueHelp> + <valueHelp> + <format>vxlan</format> + <description>Allow prefix for IF-MIB data collection</description> + </valueHelp> + <valueHelp> + <format>wg</format> + <description>Allow prefix for IF-MIB data collection</description> + </valueHelp> + <valueHelp> + <format>wlan</format> + <description>Allow prefix for IF-MIB data collection</description> + </valueHelp> + <valueHelp> + <format>wwan</format> + <description>Allow prefix for IF-MIB data collection</description> + </valueHelp> + <constraint> + <regex>(br|bond|dum|eth|gnv|macsec|peth|sstpc|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + <tagNode name="listen-address"> + <properties> + <help>IP address to listen for incoming SNMP requests</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to listen for incoming SNMP requests</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address to listen for incoming SNMP requests</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>161</defaultValue> + </leafNode> + </children> + </tagNode> + <leafNode name="location"> + <properties> + <help>Location information</help> + <constraint> + <regex>.{1,255}</regex> + </constraint> + <constraintErrorMessage>Location is limited to 255 characters or less</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="oid-enable"> + <properties> + <help>Enable specific OIDs that by default are disable</help> + <completionHelp> + <list>ip-forward ip-route-table ip-net-to-media-table ip-net-to-physical-phys-address</list> + </completionHelp> + <valueHelp> + <format>ip-forward</format> + <description>Enable ipForward: .1.3.6.1.2.1.4.24</description> + </valueHelp> + <valueHelp> + <format>ip-route-table</format> + <description>Enable ipRouteTable: .1.3.6.1.2.1.4.21</description> + </valueHelp> + <valueHelp> + <format>ip-net-to-media-table</format> + <description>Enable ipNetToMediaTable: .1.3.6.1.2.1.4.22</description> + </valueHelp> + <valueHelp> + <format>ip-net-to-physical-phys-address</format> + <description>Enable ipNetToPhysicalPhysAddress: .1.3.6.1.2.1.4.35</description> + </valueHelp> + <constraint> + <regex>(ip-forward|ip-route-table|ip-net-to-media-table|ip-net-to-physical-phys-address)</regex> + </constraint> + <constraintErrorMessage>OID must be one of the liste options</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + #include <include/protocol-tcp-udp.xml.i> + <leafNode name="smux-peer"> + <properties> + <help>Register a subtree for SMUX-based processing</help> + <valueHelp> + <format>txt</format> + <description>SNMP Object Identifier</description> + </valueHelp> + <multi/> + </properties> + </leafNode> + <leafNode name="trap-source"> + <properties> + <help>SNMP trap source address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> + <tagNode name="trap-target"> + <properties> + <help>Address of trap target</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + <leafNode name="community"> + <properties> + <help>Community used when sending trap information</help> + </properties> + </leafNode> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>162</defaultValue> + </leafNode> + </children> + </tagNode> + <node name="v3"> + <properties> + <help>Simple Network Management Protocol (SNMP) v3</help> + </properties> + <children> + <leafNode name="engineid"> + <properties> + <help>Specifies the EngineID that uniquely identify an agent (e.g. 000000000000000000000002)</help> + <constraint> + <regex>([0-9a-f][0-9a-f]){1,18}</regex> + </constraint> + <constraintErrorMessage>ID must contain an even number (from 2 to 36) of hex digits</constraintErrorMessage> + </properties> + <defaultValue></defaultValue> + </leafNode> + <tagNode name="group"> + <properties> + <help>Specifies the group with name groupname</help> + </properties> + <children> + #include <include/snmp/access-mode.xml.i> + <leafNode name="seclevel"> + <properties> + <help>Security levels</help> + <completionHelp> + <list>noauth auth priv</list> + </completionHelp> + <valueHelp> + <format>noauth</format> + <description>Messages not authenticated and not encrypted (noAuthNoPriv)</description> + </valueHelp> + <valueHelp> + <format>auth</format> + <description>Messages are authenticated but not encrypted (authNoPriv)</description> + </valueHelp> + <valueHelp> + <format>priv</format> + <description>Messages are authenticated and encrypted (authPriv)</description> + </valueHelp> + <constraint> + <regex>(noauth|auth|priv)</regex> + </constraint> + </properties> + <defaultValue>auth</defaultValue> + </leafNode> + <leafNode name="view"> + <properties> + <help>Defines the name of view</help> + <completionHelp> + <path>service snmp v3 view</path> + </completionHelp> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="trap-target"> + <properties> + <help>Defines SNMP target for inform or traps for IP</help> + <valueHelp> + <format>ipv4</format> + <description>IP address of trap target</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of trap target</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv6-address"/> + </constraint> + </properties> + <children> + <node name="auth"> + <properties> + <help>Defines the privacy</help> + </properties> + <children> + <leafNode name="encrypted-password"> + <properties> + <help>Defines the encrypted key for authentication</help> + <constraint> + <regex>[0-9a-f]*</regex> + </constraint> + <constraintErrorMessage>Encrypted key must only contain hex digits</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="plaintext-password"> + <properties> + <help>Defines the clear text key for authentication</help> + <constraint> + <regex>.{8,}</regex> + </constraint> + <constraintErrorMessage>Key must contain 8 or more characters</constraintErrorMessage> + </properties> + </leafNode> + #include <include/snmp/authentication-type.xml.i> + </children> + </node> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>162</defaultValue> + </leafNode> + <node name="privacy"> + <properties> + <help>Defines the privacy</help> + </properties> + <children> + <leafNode name="encrypted-password"> + <properties> + <help>Defines the encrypted key for privacy protocol</help> + <constraint> + <regex>[0-9a-f]*</regex> + </constraint> + <constraintErrorMessage>Encrypted key must only contain hex digits</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="plaintext-password"> + <properties> + <help>Defines the clear text key for privacy protocol</help> + <constraint> + <regex>.{8,}</regex> + </constraint> + <constraintErrorMessage>Key must contain 8 or more characters</constraintErrorMessage> + </properties> + </leafNode> + #include <include/snmp/privacy-type.xml.i> + </children> + </node> + #include <include/protocol-tcp-udp.xml.i> + <leafNode name="type"> + <properties> + <help>Specifies the type of notification between inform and trap</help> + <completionHelp> + <list>inform trap</list> + </completionHelp> + <valueHelp> + <format>inform</format> + <description>Use INFORM</description> + </valueHelp> + <valueHelp> + <format>trap</format> + <description>Use TRAP</description> + </valueHelp> + <constraint> + <regex>(inform|trap)</regex> + </constraint> + </properties> + <defaultValue>inform</defaultValue> + </leafNode> + <leafNode name="user"> + <properties> + <help>Defines username for authentication</help> + <completionHelp> + <path>service snmp v3 user</path> + </completionHelp> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="user"> + <properties> + <help>Specifies the user with name username</help> + <constraint> + <regex>[^\(\)\|\-]+</regex> + </constraint> + <constraintErrorMessage>Illegal characters in name</constraintErrorMessage> + </properties> + <children> + <node name="auth"> + <properties> + <help>Specifies the auth</help> + </properties> + <children> + <leafNode name="encrypted-password"> + <properties> + <help>Defines the encrypted key for authentication</help> + <constraint> + <regex>[0-9a-f]*</regex> + </constraint> + <constraintErrorMessage>Encrypted key must only contain hex digits</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="plaintext-password"> + <properties> + <help>Defines the clear text key for authentication</help> + <constraint> + <regex>.{8,}</regex> + </constraint> + <constraintErrorMessage>Key must contain 8 or more characters</constraintErrorMessage> + </properties> + </leafNode> + #include <include/snmp/authentication-type.xml.i> + </children> + </node> + <leafNode name="group"> + <properties> + <help>Specifies group for user name</help> + <completionHelp> + <path>service snmp v3 group</path> + </completionHelp> + </properties> + </leafNode> + #include <include/snmp/access-mode.xml.i> + <node name="privacy"> + <properties> + <help>Defines the privacy</help> + </properties> + <children> + <leafNode name="encrypted-password"> + <properties> + <help>Defines the encrypted key for privacy protocol</help> + <constraint> + <regex>[0-9a-f]*</regex> + </constraint> + <constraintErrorMessage>Encrypted key must only contain hex digits</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="plaintext-password"> + <properties> + <help>Defines the clear text key for privacy protocol</help> + <constraint> + <regex>.{8,}</regex> + </constraint> + <constraintErrorMessage>Key must contain 8 or more characters</constraintErrorMessage> + </properties> + </leafNode> + #include <include/snmp/privacy-type.xml.i> + </children> + </node> + </children> + </tagNode> + <tagNode name="view"> + <properties> + <help>Specifies the view with name viewname</help> + <constraint> + <regex>[^\(\)\|\-]+</regex> + </constraint> + <constraintErrorMessage>Illegal characters in name</constraintErrorMessage> + </properties> + <children> + <tagNode name="oid"> + <properties> + <help>Specifies the oid</help> + <constraint> + <regex>[0-9]+(\.[0-9]+)*</regex> + </constraint> + <constraintErrorMessage>OID must start from a number</constraintErrorMessage> + </properties> + <children> + <leafNode name="exclude"> + <properties> + <help>Exclude is an optional argument</help> + <multi/> + </properties> + </leafNode> + <leafNode name="mask"> + <properties> + <help>Defines a bit-mask that is indicating which subidentifiers of the associated subtree OID should be regarded as significant</help> + <constraint> + <regex>[0-9a-f]{2}([\.:][0-9a-f]{2})*</regex> + </constraint> + <constraintErrorMessage>MASK is a list of hex octets, separated by '.' or ':'</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + <node name="script-extensions"> + <properties> + <help>SNMP script extensions</help> + </properties> + <children> + <tagNode name="extension-name"> + <properties> + <help>Extension name</help> + <constraint> + <regex>[a-z0-9\.\-\_]+</regex> + </constraint> + <constraintErrorMessage>Script extension contains invalid characters</constraintErrorMessage> + </properties> + <children> + <leafNode name="script"> + <properties> + <help>Script location and name</help> + <completionHelp> + <script>ls /config/user-data</script> + </completionHelp> + <constraint> + <regex>[a-z0-9\.\-\_\/]+</regex> + </constraint> + <constraintErrorMessage>Script extension contains invalid characters</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + #include <include/interface/vrf.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_ssh.xml.in b/interface-definitions/service_ssh.xml.in new file mode 100644 index 0000000..221e451 --- /dev/null +++ b/interface-definitions/service_ssh.xml.in @@ -0,0 +1,283 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <properties> + <help>System services</help> + </properties> + <children> + <node name="ssh" owner="${vyos_conf_scripts_dir}/service_ssh.py"> + <properties> + <help>Secure Shell (SSH)</help> + <priority>1000</priority> + </properties> + <children> + <node name="access-control"> + <properties> + <help>SSH user/group access controls</help> + </properties> + <children> + <node name="allow"> + <properties> + <help>Allow user/group SSH access</help> + </properties> + <children> + #include <include/ssh-group.xml.i> + #include <include/ssh-user.xml.i> + </children> + </node> + <node name="deny"> + <properties> + <help>Deny user/group SSH access</help> + </properties> + <children> + #include <include/ssh-group.xml.i> + #include <include/ssh-user.xml.i> + </children> + </node> + </children> + </node> + <leafNode name="ciphers"> + <properties> + <help>Allowed ciphers</help> + <completionHelp> + <!-- generated by ssh -Q cipher | tr '\n' ' ' as this will not change dynamically --> + <list>3des-cbc aes128-cbc aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se aes128-ctr aes192-ctr aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com chacha20-poly1305@openssh.com</list> + </completionHelp> + <constraint> + <regex>(3des-cbc|aes128-cbc|aes192-cbc|aes256-cbc|rijndael-cbc@lysator.liu.se|aes128-ctr|aes192-ctr|aes256-ctr|aes128-gcm@openssh.com|aes256-gcm@openssh.com|chacha20-poly1305@openssh.com)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="disable-host-validation"> + <properties> + <help>Disable IP Address to Hostname lookup</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="disable-password-authentication"> + <properties> + <help>Disable password-based authentication</help> + <valueless/> + </properties> + </leafNode> + <node name="dynamic-protection"> + <properties> + <help>Allow dynamic protection</help> + </properties> + <children> + <leafNode name="block-time"> + <properties> + <help>Block source IP in seconds. Subsequent blocks increase by a factor of 1.5</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Time interval in seconds for blocking</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>120</defaultValue> + </leafNode> + <leafNode name="detect-time"> + <properties> + <help>Remember source IP in seconds before reset their score</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Time interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>1800</defaultValue> + </leafNode> + <leafNode name="threshold"> + <properties> + <help>Block source IP when their cumulative attack score exceeds threshold</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Threshold score</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>30</defaultValue> + </leafNode> + <leafNode name="allow-from"> + <properties> + <help>Always allow inbound connections from these systems</help> + <valueHelp> + <format>ipv4</format> + <description>Address to match against</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 address and prefix length</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address to match against</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + <validator name="ip-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="hostkey-algorithm"> + <properties> + <help>Allowed host key signature algorithms</help> + <completionHelp> + <!-- generated by ssh -Q HostKeyAlgorithms | tr '\n' ' ' as this will not change dynamically --> + <list>ssh-ed25519 ssh-ed25519-cert-v01@openssh.com sk-ssh-ed25519@openssh.com sk-ssh-ed25519-cert-v01@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512 ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 sk-ecdsa-sha2-nistp256@openssh.com webauthn-sk-ecdsa-sha2-nistp256@openssh.com ssh-rsa-cert-v01@openssh.com rsa-sha2-256-cert-v01@openssh.com rsa-sha2-512-cert-v01@openssh.com ssh-dss-cert-v01@openssh.com ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp384-cert-v01@openssh.com ecdsa-sha2-nistp521-cert-v01@openssh.com sk-ecdsa-sha2-nistp256-cert-v01@openssh.com</list> + </completionHelp> + <multi/> + <constraint> + <regex>(ssh-ed25519|ssh-ed25519-cert-v01@openssh.com|sk-ssh-ed25519@openssh.com|sk-ssh-ed25519-cert-v01@openssh.com|ssh-rsa|rsa-sha2-256|rsa-sha2-512|ssh-dss|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|sk-ecdsa-sha2-nistp256@openssh.com|webauthn-sk-ecdsa-sha2-nistp256@openssh.com|ssh-rsa-cert-v01@openssh.com|rsa-sha2-256-cert-v01@openssh.com|rsa-sha2-512-cert-v01@openssh.com|ssh-dss-cert-v01@openssh.com|ecdsa-sha2-nistp256-cert-v01@openssh.com|ecdsa-sha2-nistp384-cert-v01@openssh.com|ecdsa-sha2-nistp521-cert-v01@openssh.com|sk-ecdsa-sha2-nistp256-cert-v01@openssh.com)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="pubkey-accepted-algorithm"> + <properties> + <help>Allowed pubkey signature algorithms</help> + <completionHelp> + <!-- generated by ssh -Q PubkeyAcceptedAlgorithms | tr '\n' ' ' as this will not change dynamically --> + <list>ssh-ed25519 ssh-ed25519-cert-v01@openssh.com sk-ssh-ed25519@openssh.com sk-ssh-ed25519-cert-v01@openssh.com ecdsa-sha2-nistp256 ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp384 ecdsa-sha2-nistp384-cert-v01@openssh.com ecdsa-sha2-nistp521 ecdsa-sha2-nistp521-cert-v01@openssh.com sk-ecdsa-sha2-nistp256@openssh.com sk-ecdsa-sha2-nistp256-cert-v01@openssh.com webauthn-sk-ecdsa-sha2-nistp256@openssh.com ssh-dss ssh-dss-cert-v01@openssh.com ssh-rsa ssh-rsa-cert-v01@openssh.com rsa-sha2-256 rsa-sha2-256-cert-v01@openssh.com rsa-sha2-512 rsa-sha2-512-cert-v01@openssh.com</list> + </completionHelp> + <multi/> + <constraint> + <regex>(ssh-ed25519|ssh-ed25519-cert-v01@openssh.com|sk-ssh-ed25519@openssh.com|sk-ssh-ed25519-cert-v01@openssh.com|ecdsa-sha2-nistp256|ecdsa-sha2-nistp256-cert-v01@openssh.com|ecdsa-sha2-nistp384|ecdsa-sha2-nistp384-cert-v01@openssh.com|ecdsa-sha2-nistp521|ecdsa-sha2-nistp521-cert-v01@openssh.com|sk-ecdsa-sha2-nistp256@openssh.com|sk-ecdsa-sha2-nistp256-cert-v01@openssh.com|webauthn-sk-ecdsa-sha2-nistp256@openssh.com|ssh-dss|ssh-dss-cert-v01@openssh.com|ssh-rsa|ssh-rsa-cert-v01@openssh.com|rsa-sha2-256|rsa-sha2-256-cert-v01@openssh.com|rsa-sha2-512|rsa-sha2-512-cert-v01@openssh.com)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="key-exchange"> + <properties> + <help>Allowed key exchange (KEX) algorithms</help> + <completionHelp> + <!-- generated by ssh -Q kex | tr '\n' ' ' as this will not change dynamically --> + <list>diffie-hellman-group1-sha1 diffie-hellman-group14-sha1 diffie-hellman-group14-sha256 diffie-hellman-group16-sha512 diffie-hellman-group18-sha512 diffie-hellman-group-exchange-sha1 diffie-hellman-group-exchange-sha256 ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521 curve25519-sha256 curve25519-sha256@libssh.org</list> + </completionHelp> + <multi/> + <constraint> + <regex>(diffie-hellman-group1-sha1|diffie-hellman-group14-sha1|diffie-hellman-group14-sha256|diffie-hellman-group16-sha512|diffie-hellman-group18-sha512|diffie-hellman-group-exchange-sha1|diffie-hellman-group-exchange-sha256|ecdh-sha2-nistp256|ecdh-sha2-nistp384|ecdh-sha2-nistp521|curve25519-sha256|curve25519-sha256@libssh.org)</regex> + </constraint> + </properties> + </leafNode> + #include <include/listen-address.xml.i> + <leafNode name="loglevel"> + <properties> + <help>Log level</help> + <completionHelp> + <list>quiet fatal error info verbose</list> + </completionHelp> + <valueHelp> + <format>quiet</format> + <description>stay silent</description> + </valueHelp> + <valueHelp> + <format>fatal</format> + <description>log fatals only</description> + </valueHelp> + <valueHelp> + <format>error</format> + <description>log errors and fatals only</description> + </valueHelp> + <valueHelp> + <format>info</format> + <description>default log level</description> + </valueHelp> + <valueHelp> + <format>verbose</format> + <description>enable logging of failed login attempts</description> + </valueHelp> + <constraint> + <regex>(quiet|fatal|error|info|verbose)</regex> + </constraint> + </properties> + <defaultValue>info</defaultValue> + </leafNode> + <leafNode name="mac"> + <properties> + <help>Allowed message authentication code (MAC) algorithms</help> + <completionHelp> + <!-- generated by ssh -Q mac | tr '\n' ' ' as this will not change dynamically --> + <list>hmac-sha1 hmac-sha1-96 hmac-sha2-256 hmac-sha2-512 hmac-md5 hmac-md5-96 umac-64@openssh.com umac-128@openssh.com hmac-sha1-etm@openssh.com hmac-sha1-96-etm@openssh.com hmac-sha2-256-etm@openssh.com hmac-sha2-512-etm@openssh.com hmac-md5-etm@openssh.com hmac-md5-96-etm@openssh.com umac-64-etm@openssh.com umac-128-etm@openssh.com</list> + </completionHelp> + <constraint> + <regex>(hmac-sha1|hmac-sha1-96|hmac-sha2-256|hmac-sha2-512|hmac-md5|hmac-md5-96|umac-64@openssh.com|umac-128@openssh.com|hmac-sha1-etm@openssh.com|hmac-sha1-96-etm@openssh.com|hmac-sha2-256-etm@openssh.com|hmac-sha2-512-etm@openssh.com|hmac-md5-etm@openssh.com|hmac-md5-96-etm@openssh.com|umac-64-etm@openssh.com|umac-128-etm@openssh.com)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="port"> + <properties> + <help>Port for SSH service</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <multi/> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>22</defaultValue> + </leafNode> + <node name="rekey"> + <properties> + <help>SSH session rekey limit</help> + </properties> + <children> + <leafNode name="data"> + <properties> + <help>Threshold data in megabytes</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Megabytes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="time"> + <properties> + <help>Threshold time in minutes</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Minutes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <leafNode name="client-keepalive-interval"> + <properties> + <help>Enable transmission of keepalives from server to client</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Time interval in seconds for keepalive message</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + #include <include/vrf-multi.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_stunnel.xml.in b/interface-definitions/service_stunnel.xml.in new file mode 100644 index 0000000..d88909b --- /dev/null +++ b/interface-definitions/service_stunnel.xml.in @@ -0,0 +1,130 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <properties> + <help>System services</help> + </properties> + <children> + <node name="stunnel" owner="${vyos_conf_scripts_dir}/service_stunnel.py"> + <properties> + <help>Stunnel TLS Proxy</help> + <priority>1000</priority> + </properties> + <children> + <tagNode name="server"> + <properties> + <help>Stunnel server config</help> + </properties> + <children> + #include <include/stunnel/connect.xml.i> + #include <include/stunnel/listen.xml.i> + #include <include/stunnel/ssl.xml.i> + #include <include/stunnel/psk.xml.i> + <leafNode name="protocol"> + <properties> + <help>Application protocol to negotiate TLS</help> + <completionHelp> + <list>cifs imap pgsql pop3 proxy smtp socks</list> + </completionHelp> + #include <include/stunnel/protocol-value-cifs.xml.i> + #include <include/stunnel/protocol-value-imap.xml.i> + #include <include/stunnel/protocol-value-pgsql.xml.i> + #include <include/stunnel/protocol-value-pop3.xml.i> + #include <include/stunnel/protocol-value-proxy.xml.i> + #include <include/stunnel/protocol-value-smtp.xml.i> + #include <include/stunnel/protocol-value-socks.xml.i> + <constraint> + <regex>(cifs|imap|pgsql|pop3|proxy|smtp|socks)</regex> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="client"> + <properties> + <help>Stunnel client config</help> + </properties> + <children> + #include <include/stunnel/connect.xml.i> + #include <include/stunnel/listen.xml.i> + #include <include/stunnel/ssl.xml.i> + #include <include/stunnel/psk.xml.i> + <leafNode name="protocol"> + <properties> + <help>Application protocol to negotiate TLS</help> + <completionHelp> + <list>cifs connect imap nntp pgsql pop3 proxy smtp socks</list> + </completionHelp> + #include <include/stunnel/protocol-value-cifs.xml.i> + #include <include/stunnel/protocol-value-connect.xml.i> + #include <include/stunnel/protocol-value-imap.xml.i> + #include <include/stunnel/protocol-value-nntp.xml.i> + #include <include/stunnel/protocol-value-pgsql.xml.i> + #include <include/stunnel/protocol-value-pop3.xml.i> + #include <include/stunnel/protocol-value-proxy.xml.i> + #include <include/stunnel/protocol-value-smtp.xml.i> + #include <include/stunnel/protocol-value-socks.xml.i> + <constraint> + <regex>(cifs|connect|imap|nntp|pgsql|pop3|proxy|smtp|socks)</regex> + </constraint> + </properties> + </leafNode> + #include <include/stunnel/protocol-options.xml.i> + </children> + </tagNode> + <node name="log"> + <properties> + <help>Service logging</help> + </properties> + <children> + <leafNode name="level"> + <properties> + <help>Specifies log level.</help> + <completionHelp> + <list>emerg alert crit err warning notice info debug</list> + </completionHelp> + <valueHelp> + <format>emerg</format> + <description>Emerg log level</description> + </valueHelp> + <valueHelp> + <format>alert</format> + <description>Alert log level</description> + </valueHelp> + <valueHelp> + <format>crit</format> + <description>Critical log level</description> + </valueHelp> + <valueHelp> + <format>err</format> + <description>Error log level</description> + </valueHelp> + <valueHelp> + <format>warning</format> + <description>Warning log level</description> + </valueHelp> + <valueHelp> + <format>notice</format> + <description>Notice log level</description> + </valueHelp> + <valueHelp> + <format>info</format> + <description>Info log level</description> + </valueHelp> + <valueHelp> + <format>debug</format> + <description>Debug log level</description> + </valueHelp> + <constraint> + <regex>(emerg|alert|crit|err|warning|notice|info|debug)</regex> + </constraint> + </properties> + <defaultValue>notice</defaultValue> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_suricata.xml.in b/interface-definitions/service_suricata.xml.in new file mode 100644 index 0000000..e0159e2 --- /dev/null +++ b/interface-definitions/service_suricata.xml.in @@ -0,0 +1,238 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="suricata" owner="${vyos_conf_scripts_dir}/service_suricata.py"> + <properties> + <help>Network IDS, IPS and Security Monitoring</help> + <priority>740</priority> + </properties> + <children> + #include <include/generic-interface-multi.xml.i> + <tagNode name="address-group"> + <properties> + <help>Address group name</help> + <constraint> + <regex>[a-z0-9-]+</regex> + </constraint> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IP address or subnet</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix to match</description> + </valueHelp> + <valueHelp> + <format>!ipv4</format> + <description>Exclude the specified IPv4 address from matches</description> + </valueHelp> + <valueHelp> + <format>!ipv6</format> + <description>Exclude the specified IPv6 address from matches</description> + </valueHelp> + <valueHelp> + <format>!ipv4net</format> + <description>Exclude the specified IPv6 prefix from matches</description> + </valueHelp> + <valueHelp> + <format>!ipv6net</format> + <description>Exclude the specified IPv6 prefix from matches</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv6-address"/> + <validator name="ipv4-prefix"/> + <validator name="ipv6-prefix"/> + <validator name="ipv4-address-exclude"/> + <validator name="ipv6-address-exclude"/> + <validator name="ipv4-prefix-exclude"/> + <validator name="ipv6-prefix-exclude"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="group"> + <properties> + <help>Address group</help> + <completionHelp> + <path>service ids suricata address-group</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Address group to match</description> + </valueHelp> + <valueHelp> + <format>!txt</format> + <description>Exclude the specified address group from matches</description> + </valueHelp> + <constraint> + <regex>!?[a-z0-9-]+</regex> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="port-group"> + <properties> + <help>Port group name</help> + <constraint> + <regex>[a-z0-9-]+</regex> + </constraint> + </properties> + <children> + <leafNode name="port"> + <properties> + <help>Port number</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Numeric port to match</description> + </valueHelp> + <valueHelp> + <format>!u32:1-65535</format> + <description>Numeric port to exclude from matches</description> + </valueHelp> + <valueHelp> + <format>start-end</format> + <description>Numbered port range (e.g. 1001-1005) to match</description> + </valueHelp> + <valueHelp> + <format>!start-end</format> + <description>Numbered port range (e.g. !1001-1005) to exclude from matches</description> + </valueHelp> + <constraint> + <validator name="port-range"/> + <validator name="port-range-exclude"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="group"> + <properties> + <help>Port group</help> + <completionHelp> + <path>service ids suricata port-group</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Port group to match</description> + </valueHelp> + <valueHelp> + <format>!txt</format> + <description>Exclude the specified port group from matches</description> + </valueHelp> + <constraint> + <regex>!?[a-z0-9-]+</regex> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + <node name="log"> + <properties> + <help>Suricata log outputs</help> + </properties> + <children> + <node name="eve"> + <properties> + <help>Extensible Event Format (EVE)</help> + </properties> + <children> + <leafNode name="filetype"> + <properties> + <help>EVE logging destination</help> + <completionHelp> + <list>regular syslog</list> + </completionHelp> + <valueHelp> + <format>regular</format> + <description>Log to filename</description> + </valueHelp> + <valueHelp> + <format>syslog</format> + <description>Log to syslog</description> + </valueHelp> + <constraint> + <regex>(regular|syslog)</regex> + </constraint> + </properties> + <defaultValue>regular</defaultValue> + </leafNode> + <leafNode name="filename"> + <properties> + <help>Log file</help> + <valueHelp> + <format>filename</format> + <description>File name in default Suricata log directory</description> + </valueHelp> + <valueHelp> + <format>/path</format> + <description>Absolute file path</description> + </valueHelp> + </properties> + <defaultValue>eve.json</defaultValue> + </leafNode> + <leafNode name="type"> + <properties> + <help>Log types</help> + <completionHelp> + <list>alert anomaly drop files http dns tls smtp dnp3 ftp rdp nfs smb tftp ikev2 dcerpc krb5 snmp rfb sip dhcp ssh mqtt http2 flow netflow</list> + </completionHelp> + <valueHelp> + <format>alert</format> + <description>Record events for rule matches</description> + </valueHelp> + <valueHelp> + <format>anomaly</format> + <description>Record unexpected conditions such as truncated packets, packets with invalid IP/UDP/TCP length values, and other events that render the packet invalid for further processing or describe unexpected behavior on an established stream</description> + </valueHelp> + <valueHelp> + <format>drop</format> + <description>Record events for dropped packets</description> + </valueHelp> + <valueHelp> + <format>file</format> + <description>Record file details (e.g., MD5) for files extracted from application protocols (e.g., HTTP)</description> + </valueHelp> + <valueHelp> + <format>application (http, dns, tls, ...)</format> + <description>Record application-level transactions</description> + </valueHelp> + <valueHelp> + <format>flow</format> + <description>Record bi-directional flows</description> + </valueHelp> + <valueHelp> + <format>netflow</format> + <description>Record uni-directional flows</description> + </valueHelp> + <constraint> + <regex>(alert|anomaly|http|dns|tls|files|drop|smtp|dnp3|ftp|rdp|nfs|smb|tftp|ikev2|dcerpc|krb5|snmp|rfb|sip|dhcp|ssh|mqtt|http2|flow|netflow)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_tftp-server.xml.in b/interface-definitions/service_tftp-server.xml.in new file mode 100644 index 0000000..e48b5a3 --- /dev/null +++ b/interface-definitions/service_tftp-server.xml.in @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<!-- TFTP configuration --> +<interfaceDefinition> + <node name="service"> + <children> + <node name="tftp-server" owner="${vyos_conf_scripts_dir}/service_tftp-server.py"> + <properties> + <help>Trivial File Transfer Protocol (TFTP) server</help> + <priority>990</priority> + </properties> + <children> + <leafNode name="directory"> + <properties> + <help>Folder containing files served by TFTP</help> + </properties> + </leafNode> + <leafNode name="allow-upload"> + <properties> + <help>Allow TFTP file uploads</help> + <valueless/> + </properties> + </leafNode> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>69</defaultValue> + </leafNode> + #include <include/listen-address-vrf.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_webproxy.xml.in b/interface-definitions/service_webproxy.xml.in new file mode 100644 index 0000000..637d578 --- /dev/null +++ b/interface-definitions/service_webproxy.xml.in @@ -0,0 +1,654 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="service"> + <children> + <node name="webproxy" owner="${vyos_conf_scripts_dir}/service_webproxy.py"> + <properties> + <help>Webproxy service settings</help> + <priority>500</priority> + </properties> + <children> + <leafNode name="safe-ports"> + <properties> + <help>Safe port ACL</help> + <valueHelp> + <format>u32:1-1024</format> + <description>Port number. Ports included by default: 21,70,80,210,280,443,488,591,777,873,1025-65535</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-20 --range 22-69 --range 71-79 --range 81-209 --range 211-279 --range 281-442 --range 444-487 --range 489-590 --range 592-776 --range 778-872 --range 874-1024"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="ssl-safe-ports"> + <properties> + <help>SSL safe port</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Port number. Ports included by default: 443</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-442 --range 444-65535"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="append-domain"> + <properties> + <help>Default domain name</help> + <valueHelp> + <format>domain</format> + <description>Domain to use for urls that do not contain a '.'</description> + </valueHelp> + <constraint> + <regex>[.][A-Za-z0-9][-.A-Za-z0-9]*</regex> + </constraint> + <constraintErrorMessage>Must start append-domain with a '.'</constraintErrorMessage> + </properties> + </leafNode> + <node name="authentication"> + <properties> + <help>Proxy Authentication Settings</help> + </properties> + <children> + <leafNode name="children"> + <properties> + <help>Number of authentication helper processes</help> + <valueHelp> + <format>n</format> + <description>Number of authentication helper processes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-500"/> + </constraint> + </properties> + <defaultValue>5</defaultValue> + </leafNode> + <leafNode name="credentials-ttl"> + <properties> + <help>Authenticated session time to live in minutes</help> + <valueHelp> + <format>n</format> + <description>Authenticated session timeout</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-600"/> + </constraint> + </properties> + <defaultValue>60</defaultValue> + </leafNode> + <node name="ldap"> + <properties> + <help>LDAP authentication settings</help> + </properties> + <children> + <leafNode name="base-dn"> + <properties> + <help>LDAP Base DN to search</help> + </properties> + </leafNode> + <leafNode name="bind-dn"> + <properties> + <help>LDAP DN used to bind to server</help> + </properties> + </leafNode> + <leafNode name="filter-expression"> + <properties> + <help>Filter expression to perform LDAP search with</help> + </properties> + </leafNode> + <leafNode name="password"> + <properties> + <help>LDAP password to bind with</help> + </properties> + </leafNode> + <leafNode name="persistent-connection"> + <properties> + <help>Use persistent LDAP connection</help> + <valueless/> + </properties> + </leafNode> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>389</defaultValue> + </leafNode> + <leafNode name="server"> + <properties> + <help>LDAP server to use</help> + </properties> + </leafNode> + <leafNode name="use-ssl"> + <properties> + <help>Use SSL/TLS for LDAP connection</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="username-attribute"> + <properties> + <help>LDAP username attribute</help> + </properties> + </leafNode> + <leafNode name="version"> + <properties> + <help>LDAP protocol version</help> + <completionHelp> + <list>2 3</list> + </completionHelp> + <valueHelp> + <format>2</format> + <description>LDAP protocol version 2</description> + </valueHelp> + <valueHelp> + <format>3</format> + <description>LDAP protocol version 2</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 2-3"/> + </constraint> + </properties> + <defaultValue>3</defaultValue> + </leafNode> + </children> + </node> + <leafNode name="method"> + <properties> + <help>Authentication Method</help> + <completionHelp> + <list>ldap</list> + </completionHelp> + <valueHelp> + <format>ldap</format> + <description>Lightweight Directory Access Protocol</description> + </valueHelp> + <constraint> + <regex>(ldap)</regex> + </constraint> + <constraintErrorMessage>The only supported method currently is LDAP</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="realm"> + <properties> + <help>Name of authentication realm (e.g. "My Company proxy server")</help> + </properties> + </leafNode> + </children> + </node> + <tagNode name="cache-peer"> + <properties> + <help>Specify other caches in a hierarchy</help> + <valueHelp> + <format>hostname</format> + <description>Cache peers FQDN</description> + </valueHelp> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>Hostname or IP address of peer</help> + <valueHelp> + <format>ipv4</format> + <description>Squid cache-peer IPv4 address</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>Squid cache-peer hostname</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + <validator name="fqdn"/> + </constraint> + <constraintErrorMessage>Invalid FQDN or IP address</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="http-port"> + <properties> + <help>Default Proxy Port</help> + <valueHelp> + <format>u32:1025-65535</format> + <description>Default port number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1025-65535"/> + </constraint> + </properties> + <defaultValue>3128</defaultValue> + </leafNode> + <leafNode name="icp-port"> + <properties> + <help>Cache peer ICP port</help> + <valueHelp> + <format>u32:0</format> + <description>Cache peer disabled</description> + </valueHelp> + <valueHelp> + <format>u32:1-65535</format> + <description>Cache peer ICP port</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="options"> + <properties> + <help>Cache peer options</help> + <valueHelp> + <format>txt</format> + <description>Cache peer options</description> + </valueHelp> + </properties> + <defaultValue>no-query default</defaultValue> + </leafNode> + <leafNode name="type"> + <properties> + <help>Squid peer type (default parent)</help> + <completionHelp> + <list>parent sibling multicast</list> + </completionHelp> + <valueHelp> + <format>parent</format> + <description>Peer is a parent</description> + </valueHelp> + <valueHelp> + <format>sibling</format> + <description>Peer is a sibling</description> + </valueHelp> + <valueHelp> + <format>multicast</format> + <description>Peer is a member of a multicast group</description> + </valueHelp> + <constraint> + <regex>(parent|sibling|multicast)</regex> + </constraint> + </properties> + <defaultValue>parent</defaultValue> + </leafNode> + </children> + </tagNode> + <leafNode name="cache-size"> + <properties> + <help>Disk cache size in MB</help> + <valueHelp> + <format>u32</format> + <description>Disk cache size in MB</description> + </valueHelp> + <valueHelp> + <format>0</format> + <description>Disable disk caching</description> + </valueHelp> + </properties> + <defaultValue>100</defaultValue> + </leafNode> + <leafNode name="default-port"> + <properties> + <help>Default Proxy Port</help> + <valueHelp> + <format>u32:1025-65535</format> + <description>Default port number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1025-65535"/> + </constraint> + </properties> + <defaultValue>3128</defaultValue> + </leafNode> + <leafNode name="disable-access-log"> + <properties> + <help>Disable logging of HTTP accesses</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="domain-block"> + <properties> + <help>Domain name to block</help> + <multi/> + </properties> + </leafNode> + <leafNode name="domain-noncache"> + <properties> + <help>Domain name to access without caching</help> + <multi/> + </properties> + </leafNode> + <tagNode name="listen-address"> + <properties> + <help>IPv4 listen-address for WebProxy</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address listen on</description> + </valueHelp> + </properties> + <children> + <leafNode name="port"> + <properties> + <help>Default Proxy Port</help> + <valueHelp> + <format>u32:1025-65535</format> + <description>Default port number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1025-65535"/> + </constraint> + </properties> + <!-- no defaultValue specified as there is default-port --> + </leafNode> + <leafNode name="disable-transparent"> + <properties> + <help>Disable transparent mode</help> + <valueless/> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="maximum-object-size"> + <properties> + <help>Maximum size of object to be stored in cache in kilobytes</help> + <valueHelp> + <format>u32</format> + <description>Object size in KB</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-1000000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="mem-cache-size"> + <properties> + <help>Memory cache size in MB</help> + <valueHelp> + <format>u32</format> + <description>Memory cache size in MB </description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-100000"/> + </constraint> + </properties> + <defaultValue>20</defaultValue> + </leafNode> + <leafNode name="minimum-object-size"> + <properties> + <help>Maximum size of object to be stored in cache in kilobytes</help> + <valueHelp> + <format>u32</format> + <description>Object size in KB</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-100000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="outgoing-address"> + <properties> + <help>Outgoing IP address for webproxy</help> + </properties> + </leafNode> + <leafNode name="reply-block-mime"> + <properties> + <help>MIME type to block</help> + <completionHelp> + <list>image/gif www/mime application/macbinary application/oda application/octet-stream application/pdf application/postscript application/postscript application/postscript text/rtf application/octet-stream application/octet-stream application/x-tar application/x-csh application/x-dvi application/x-hdf application/x-latex text/plain application/x-netcdf application/x-netcdf application/x-sh application/x-tcl application/x-tex application/x-texinfo application/x-texinfo application/x-troff application/x-troff application/x-troff application/x-troff-man application/x-troff-me application/x-troff-ms application/x-wais-source application/zip application/x-bcpio application/x-cpio application/x-gtar application/x-rpm application/x-shar application/x-sv4cpio application/x-sv4crc application/x-tar application/x-ustar audio/basic audio/basic audio/mpeg audio/mpeg audio/mpeg audio/x-aiff audio/x-aiff audio/x-aiff audio/x-wav image/bmp image/ief image/jpeg image/jpeg image/jpeg image/tiff image/tiff image/x-cmu-raster image/x-portable-anymap image/x-portable-bitmap image/x-portable-graymap image/x-portable-pixmap image/x-rgb image/x-xbitmap image/x-xpixmap image/x-xwindowdump text/html text/html text/css application/x-javascript text/plain text/plain text/plain text/plain text/plain text/plain text/plain text/plain text/plain text/richtext text/tab-separated-values text/x-setext video/mpeg video/mpeg video/mpeg video/quicktime video/quicktime video/x-msvideo video/x-sgi-movie application/mac-compactpro application/mac-binhex40 application/macwriteii application/msword application/msword application/vnd.ms-excel application/vnd.ms-powerpoint application/vnd.lotus-1-2-3 application/vnd.mif application/x-stuffit application/pict application/pict application/x-arj-compressed application/x-lha-compressed application/x-lha-compressed application/x-deflate text/plain application/octet-stream application/octet-stream image/png application/octet-stream application/x-xpinstall application/octet-stream text/plain application/x-director application/x-director application/x-director image/vnd.djvu image/vnd.djvu application/octet-stream application/octet-stream application/andrew-inset x-conference/x-cooltalk model/iges model/iges audio/midi audio/midi audio/midi model/mesh model/mesh video/vnd.mpegurl chemical/x-pdb application/x-chess-pgn audio/x-realaudio audio/x-pn-realaudio audio/x-pn-realaudio text/sgml text/sgml application/x-koan application/x-koan application/x-koan application/x-koan application/smil application/smil application/octet-stream application/x-futuresplash application/x-shockwave-flash application/x-cdlink model/vrml image/vnd.wap.wbmp application/vnd.wap.wbxml application/vnd.wap.wmlc application/vnd.wap.wmlscriptc application/vnd.wap.wmlscript application/xhtml application/xhtml text/xml text/xml chemical/x-xyz text/plain</list> + </completionHelp> + <constraint> + <regex>(image/gif|www/mime|application/macbinary|application/oda|application/octet-stream|application/pdf|application/postscript|application/postscript|application/postscript|text/rtf|application/octet-stream|application/octet-stream|application/x-tar|application/x-csh|application/x-dvi|application/x-hdf|application/x-latex|text/plain|application/x-netcdf|application/x-netcdf|application/x-sh|application/x-tcl|application/x-tex|application/x-texinfo|application/x-texinfo|application/x-troff|application/x-troff|application/x-troff|application/x-troff-man|application/x-troff-me|application/x-troff-ms|application/x-wais-source|application/zip|application/x-bcpio|application/x-cpio|application/x-gtar|application/x-rpm|application/x-shar|application/x-sv4cpio|application/x-sv4crc|application/x-tar|application/x-ustar|audio/basic|audio/basic|audio/mpeg|audio/mpeg|audio/mpeg|audio/x-aiff|audio/x-aiff|audio/x-aiff|audio/x-wav|image/bmp|image/ief|image/jpeg|image/jpeg|image/jpeg|image/tiff|image/tiff|image/x-cmu-raster|image/x-portable-anymap|image/x-portable-bitmap|image/x-portable-graymap|image/x-portable-pixmap|image/x-rgb|image/x-xbitmap|image/x-xpixmap|image/x-xwindowdump|text/html|text/html|text/css|application/x-javascript|text/plain|text/plain|text/plain|text/plain|text/plain|text/plain|text/plain|text/plain|text/plain|text/richtext|text/tab-separated-values|text/x-setext|video/mpeg|video/mpeg|video/mpeg|video/quicktime|video/quicktime|video/x-msvideo|video/x-sgi-movie|application/mac-compactpro|application/mac-binhex40|application/macwriteii|application/msword|application/msword|application/vnd.ms-excel|application/vnd.ms-powerpoint|application/vnd.lotus-1-2-3|application/vnd.mif|application/x-stuffit|application/pict|application/pict|application/x-arj-compressed|application/x-lha-compressed|application/x-lha-compressed|application/x-deflate|text/plain|application/octet-stream|application/octet-stream|image/png|application/octet-stream|application/x-xpinstall|application/octet-stream|text/plain|application/x-director|application/x-director|application/x-director|image/vnd.djvu|image/vnd.djvu|application/octet-stream|application/octet-stream|application/andrew-inset|x-conference/x-cooltalk|model/iges|model/iges|audio/midi|audio/midi|audio/midi|model/mesh|model/mesh|video/vnd.mpegurl|chemical/x-pdb|application/x-chess-pgn|audio/x-realaudio|audio/x-pn-realaudio|audio/x-pn-realaudio|text/sgml|text/sgml|application/x-koan|application/x-koan|application/x-koan|application/x-koan|application/smil|application/smil|application/octet-stream|application/x-futuresplash|application/x-shockwave-flash|application/x-cdlink|model/vrml|image/vnd.wap.wbmp|application/vnd.wap.wbxml|application/vnd.wap.wmlc|application/vnd.wap.wmlscriptc|application/vnd.wap.wmlscript|application/xhtml|application/xhtml|text/xml|text/xml|chemical/x-xyz|text/plain)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="reply-body-max-size"> + <properties> + <help>Maximum reply body size in KB</help> + <valueHelp> + <format>u32</format> + <description>Reply size in KB</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-100000"/> + </constraint> + </properties> + </leafNode> + <node name="url-filtering"> + <properties> + <help>URL filtering settings</help> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <node name="squidguard"> + <properties> + <help>URL filtering via squidGuard redirector</help> + </properties> + <children> + #include <include/webproxy-url-filtering.xml.i> + <node name="auto-update"> + <properties> + <help>Auto update settings</help> + </properties> + <children> + <leafNode name="update-hour"> + <properties> + <help>Hour of day for database update</help> + <valueHelp> + <format>u32:0-23</format> + <description>Hour for database update</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-23"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + </children> + </node> + <leafNode name="redirect-url"> + <properties> + <help>Redirect URL for filtered websites</help> + <valueHelp> + <format>url</format> + <description>URL for redirect</description> + </valueHelp> + </properties> + <defaultValue>block.vyos.net</defaultValue> + </leafNode> + <tagNode name="rule"> + <properties> + <help>URL filter rule for a source-group</help> + <valueHelp> + <format>u32:1-1024</format> + <description>Rule Number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-1024"/> + </constraint> + <constraintErrorMessage>SquidGuard rule must between 1-1024</constraintErrorMessage> + </properties> + <children> + #include <include/webproxy-url-filtering.xml.i> + <leafNode name="redirect-url"> + <properties> + <help>Redirect URL for filtered websites</help> + <valueHelp> + <format>url</format> + <description>URL for redirect</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="source-group"> + <properties> + <help>Source-group for this rule</help> + <valueHelp> + <format>group</format> + <description>Source group identifier for this rule</description> + </valueHelp> + <completionHelp> + <path>service webproxy url-filtering squidguard source-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="time-period"> + <properties> + <help>Time-period for this rule</help> + <valueHelp> + <format>period</format> + <description>Time period for this rule</description> + </valueHelp> + <completionHelp> + <path>service webproxy url-filtering squidguard time-period</path> + </completionHelp> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="source-group"> + <properties> + <help>Source group name</help> + <valueHelp> + <format>name</format> + <description>Name of source group</description> + </valueHelp> + <constraint> + <regex>[^0-9][a-zA-Z_][a-zA-Z0-9][\w\-\.]*</regex> + </constraint> + <constraintErrorMessage>URL-filter source-group cannot start with a number!</constraintErrorMessage> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>Address for source-group</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <valueHelp> + <format>ipv4range</format> + <description>IPv4 address range to match</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv4-prefix"/> + <validator name="ipv4-range"/> + </constraint> + <multi/> + </properties> + </leafNode> + #include <include/generic-description.xml.i> + <leafNode name="domain"> + <properties> + <help>Domain for source-group</help> + <valueHelp> + <format>domain</format> + <description>Domain name for the source-group</description> + </valueHelp> + <multi/> + </properties> + </leafNode> + <leafNode name="ldap-ip-search"> + <properties> + <help>LDAP search expression for an IP address list</help> + <multi/> + </properties> + </leafNode> + <leafNode name="ldap-user-search"> + <properties> + <help>LDAP search expression for a user group</help> + <multi/> + </properties> + </leafNode> + <leafNode name="user"> + <properties> + <help>List of user names</help> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="time-period"> + <properties> + <help>Time period name</help> + </properties> + <children> + <tagNode name="days"> + <properties> + <help>Time-period days</help> + <completionHelp> + <list>Sun Mon Tue Wed Thu Fri Sat weekdays weekend all</list> + </completionHelp> + <valueHelp> + <format>Sun</format> + <description>Sunday</description> + </valueHelp> + <valueHelp> + <format>Mon</format> + <description>Monday</description> + </valueHelp> + <valueHelp> + <format>Tue</format> + <description>Tuesday</description> + </valueHelp> + <valueHelp> + <format>Wed</format> + <description>Wednesday</description> + </valueHelp> + <valueHelp> + <format>Thu</format> + <description>Thursday</description> + </valueHelp> + <valueHelp> + <format>Fri</format> + <description>Friday</description> + </valueHelp> + <valueHelp> + <format>Sat</format> + <description>Saturday</description> + </valueHelp> + <valueHelp> + <format>weekdays</format> + <description>Monday through Friday</description> + </valueHelp> + <valueHelp> + <format>weekend</format> + <description>Saturday and Sunday</description> + </valueHelp> + <valueHelp> + <format>all</format> + <description>All days of the week</description> + </valueHelp> + <constraint> + <regex>(Sun|Mon|Tue|Wed|Thu|Fri|Sat|weekdays|weekend|all)</regex> + </constraint> + </properties> + <children> + <leafNode name="time"> + <properties> + <help>Time for time-period</help> + <valueHelp> + <format><hh:mm - hh:mm></format> + <description>Time range in 24hr time</description> + </valueHelp> + <constraint> + <!-- time range example: 12:00-13:00 --> + <regex>(\d\d:\d\d)-(\d\d:\d\d)</regex> + </constraint> + <constraintErrorMessage>Expected time format hh:mm - hh:mm in 24hr time</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + #include <include/generic-description.xml.i> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_acceleration.xml.in b/interface-definitions/system_acceleration.xml.in new file mode 100644 index 0000000..fb5c9d4 --- /dev/null +++ b/interface-definitions/system_acceleration.xml.in @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="acceleration" owner="${vyos_conf_scripts_dir}/system_acceleration.py"> + <properties> + <help>Acceleration components</help> + <priority>50</priority> + </properties> + <children> + <leafNode name="qat"> + <properties> + <help>Enable Intel QAT (Quick Assist Technology) for cryptographic acceleration</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_config-management.xml.in b/interface-definitions/system_config-management.xml.in new file mode 100644 index 0000000..e666633 --- /dev/null +++ b/interface-definitions/system_config-management.xml.in @@ -0,0 +1,74 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="config-management" owner="${vyos_conf_scripts_dir}/system_config-management.py"> + <properties> + <help>Configuration management settings</help> + <priority>400</priority> + </properties> + <children> + <node name="commit-archive"> + <properties> + <help>Commit archive settings</help> + </properties> + <children> + <leafNode name="location"> + <properties> + <help>Commit archive location</help> + <valueHelp> + <format>http://<user>:<passwd>@<host>/<path></format> + <description/> + </valueHelp> + <valueHelp> + <format>https://<user>:<passwd>@<host>/<path></format> + <description/> + </valueHelp> + <valueHelp> + <format>ftp://<user>:<passwd>@<host>/<path></format> + <description/> + </valueHelp> + <valueHelp> + <format>sftp://<user>:<passwd>@<host>/<path></format> + <description/> + </valueHelp> + <valueHelp> + <format>scp://<user>:<passwd>@<host>/<path></format> + <description/> + </valueHelp> + <valueHelp> + <format>tftp://<host>/<path></format> + <description/> + </valueHelp> + <valueHelp> + <format>git+https://<user>:<passwd>@<host>/<path></format> + <description/> + </valueHelp> + <constraint> + <validator name="url --file-transport"/> + <regex>(ssh|git|git\+(\w+)):\/\/.*</regex> + </constraint> + <multi/> + </properties> + </leafNode> + #include <include/source-address-ipv4-ipv6.xml.i> + </children> + </node> + <leafNode name="commit-revisions"> + <properties> + <help>Commit revisions</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Number of config backups to keep</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + <constraintErrorMessage>Number of revisions must be between 0 and 65535</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_conntrack.xml.in b/interface-definitions/system_conntrack.xml.in new file mode 100644 index 0000000..cd59d13 --- /dev/null +++ b/interface-definitions/system_conntrack.xml.in @@ -0,0 +1,555 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="conntrack" owner="${vyos_conf_scripts_dir}/system_conntrack.py"> + <properties> + <help>Connection Tracking Engine Options</help> + <!-- Before NAT and conntrack-sync are configured --> + <priority>218</priority> + </properties> + <children> + <leafNode name="flow-accounting"> + <properties> + <help>Enable connection tracking flow accounting</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="expect-table-size"> + <properties> + <help>Size of connection tracking expect table</help> + <valueHelp> + <format>u32:1-50000000</format> + <description>Number of entries allowed in connection tracking expect table</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-50000000"/> + </constraint> + </properties> + <defaultValue>2048</defaultValue> + </leafNode> + <leafNode name="hash-size"> + <properties> + <help>Hash size for connection tracking table</help> + <valueHelp> + <format>u32:1-50000000</format> + <description>Size of hash to use for connection tracking table</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-50000000"/> + </constraint> + </properties> + <defaultValue>32768</defaultValue> + </leafNode> + <node name="ignore"> + <properties> + <help>Customized rules to ignore selective connection tracking</help> + </properties> + <children> + <node name="ipv4"> + <properties> + <help>IPv4 rules</help> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>Rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number of conntrack ignore rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/source-destination-group-ipv4.xml.i> + #include <include/nat-address.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> + <leafNode name="inbound-interface"> + <properties> + <help>Interface to ignore connections tracking on</help> + <completionHelp> + <list>any</list> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + </leafNode> + #include <include/ip-protocol.xml.i> + <leafNode name="protocol"> + <properties> + <help>Protocol to match (protocol name, number, or "all")</help> + <completionHelp> + <script>${vyos_completion_dir}/list_protocols.sh</script> + <list>all tcp_udp</list> + </completionHelp> + <valueHelp> + <format>all</format> + <description>All IP protocols</description> + </valueHelp> + <valueHelp> + <format>tcp_udp</format> + <description>Both TCP and UDP</description> + </valueHelp> + <valueHelp> + <format>u32:0-255</format> + <description>IP protocol number</description> + </valueHelp> + <valueHelp> + <format><protocol></format> + <description>IP protocol name</description> + </valueHelp> + <valueHelp> + <format>!<protocol></format> + <description>IP protocol name</description> + </valueHelp> + <constraint> + <validator name="ip-protocol"/> + </constraint> + </properties> + </leafNode> + <node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/source-destination-group-ipv4.xml.i> + #include <include/nat-address.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> + #include <include/firewall/tcp-flags.xml.i> + </children> + </tagNode> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 rules</help> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>Rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number of conntrack ignore rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/firewall/source-destination-group-ipv6.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> + <leafNode name="inbound-interface"> + <properties> + <help>Interface to ignore connections tracking on</help> + <completionHelp> + <list>any</list> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + </leafNode> + #include <include/ip-protocol.xml.i> + <leafNode name="protocol"> + <properties> + <help>Protocol to match (protocol name, number, or "all")</help> + <completionHelp> + <script>${vyos_completion_dir}/list_protocols.sh</script> + <list>all tcp_udp</list> + </completionHelp> + <valueHelp> + <format>all</format> + <description>All IP protocols</description> + </valueHelp> + <valueHelp> + <format>tcp_udp</format> + <description>Both TCP and UDP</description> + </valueHelp> + <valueHelp> + <format>u32:0-255</format> + <description>IP protocol number</description> + </valueHelp> + <valueHelp> + <format><protocol></format> + <description>IP protocol name</description> + </valueHelp> + <valueHelp> + <format>!<protocol></format> + <description>IP protocol name</description> + </valueHelp> + <constraint> + <validator name="ip-protocol"/> + </constraint> + </properties> + </leafNode> + <node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/firewall/source-destination-group-ipv6.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> + #include <include/firewall/tcp-flags.xml.i> + </children> + </tagNode> + </children> + </node> + + </children> + </node> + <node name="log"> + <properties> + <help>Log connection tracking</help> + </properties> + <children> + <node name="event"> + <properties> + <help>Event type and protocol</help> + </properties> + <children> + <node name="destroy"> + <properties> + <help>Log connection deletion</help> + </properties> + <children> + #include <include/conntrack/log-protocols.xml.i> + </children> + </node> + <node name="new"> + <properties> + <help>Log connection creation</help> + </properties> + <children> + #include <include/conntrack/log-protocols.xml.i> + </children> + </node> + <node name="update"> + <properties> + <help>Log connection updates</help> + </properties> + <children> + #include <include/conntrack/log-protocols.xml.i> + </children> + </node> + </children> + </node> + <leafNode name="timestamp"> + <properties> + <help>Log connection tracking events include flow-based timestamp</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="queue-size"> + <properties> + <help>Internal message queue size</help> + <valueHelp> + <format>u32:100-999999</format> + <description>Queue size</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Queue size must be between 100 and 999999</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="log-level"> + <properties> + <help>Set log-level. Log must be enable.</help> + <completionHelp> + <list>info debug</list> + </completionHelp> + <valueHelp> + <format>info</format> + <description>Info log level</description> + </valueHelp> + <valueHelp> + <format>debug</format> + <description>Debug log level</description> + </valueHelp> + <constraint> + <regex>(info|debug)</regex> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="modules"> + <properties> + <help>Connection tracking modules</help> + </properties> + <children> + <leafNode name="ftp"> + <properties> + <help>FTP connection tracking</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="h323"> + <properties> + <help>H.323 connection tracking</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="nfs"> + <properties> + <help>NFS connection tracking</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="pptp"> + <properties> + <help>PPTP connection tracking</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="rtsp"> + <properties> + <help>RTSP connection tracking</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="sip"> + <properties> + <help>SIP connection tracking</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="sqlnet"> + <properties> + <help>SQLnet connection tracking</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="tftp"> + <properties> + <help>TFTP connection tracking</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="table-size"> + <properties> + <help>Size of connection tracking table</help> + <valueHelp> + <format>u32:1-50000000</format> + <description>Number of entries allowed in connection tracking table</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-50000000"/> + </constraint> + </properties> + <defaultValue>262144</defaultValue> + </leafNode> + <node name="tcp"> + <properties> + <help>TCP options</help> + </properties> + <children> + <leafNode name="half-open-connections"> + <properties> + <help>Maximum number of TCP half-open connections</help> + <valueHelp> + <format>u32:1-2147483647</format> + <description>Generic connection timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2147483647"/> + </constraint> + </properties> + <defaultValue>512</defaultValue> + </leafNode> + <leafNode name="loose"> + <properties> + <help>Policy to track previously established connections</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Allow tracking of previously established connections</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Do not allow tracking of previously established connections</description> + </valueHelp> + <constraint> + <regex>(enable|disable)</regex> + </constraint> + </properties> + <defaultValue>enable</defaultValue> + </leafNode> + <leafNode name="max-retrans"> + <properties> + <help>Maximum number of packets that can be retransmitted without received an ACK</help> + <valueHelp> + <format>u32:1-255</format> + <description>Number of packets to be retransmitted</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <defaultValue>3</defaultValue> + </leafNode> + </children> + </node> + <node name="timeout"> + <properties> + <help>Connection timeout options</help> + </properties> + <children> + <node name="custom"> + <properties> + <help>Define custom timeouts per connection</help> + </properties> + <children> + <node name="ipv4"> + <properties> + <help>IPv4 rules</help> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>Rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number of conntrack rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Timeout rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/nat-address.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> + <leafNode name="inbound-interface"> + <properties> + <help>Interface to apply custom connection timers on</help> + <completionHelp> + <list>any</list> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + </leafNode> + <node name="protocol"> + <properties> + <help>Customize protocol specific timers, one protocol configuration per rule</help> + </properties> + <children> + #include <include/conntrack/timeout-custom-protocols.xml.i> + </children> + </node> + <node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/nat-address.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> + </children> + </tagNode> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 rules</help> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>Rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number of conntrack rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Timeout rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/generic-description.xml.i> + <node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> + <leafNode name="inbound-interface"> + <properties> + <help>Interface to apply custom connection timers on</help> + <completionHelp> + <list>any</list> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + </leafNode> + <node name="protocol"> + <properties> + <help>Customize protocol specific timers, one protocol configuration per rule</help> + </properties> + <children> + #include <include/conntrack/timeout-custom-protocols.xml.i> + </children> + </node> + <node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_console.xml.in b/interface-definitions/system_console.xml.in new file mode 100644 index 0000000..5acd3e9 --- /dev/null +++ b/interface-definitions/system_console.xml.in @@ -0,0 +1,91 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="console" owner="${vyos_conf_scripts_dir}/system_console.py"> + <properties> + <help>Serial console configuration</help> + <priority>100</priority> + </properties> + <children> + <tagNode name="device"> + <properties> + <help>Serial console device name</help> + <completionHelp> + <script>ls -1 /dev | grep -e ttyS -e hvc</script> + <script>if [ -d /dev/serial/by-bus ]; then ls -1 /dev/serial/by-bus; fi</script> + </completionHelp> + <valueHelp> + <format>ttySN</format> + <description>TTY device name, regular serial port</description> + </valueHelp> + <valueHelp> + <format>usbNbXpY</format> + <description>TTY device name, USB based</description> + </valueHelp> + <valueHelp> + <format>hvcN</format> + <description>Xen console</description> + </valueHelp> + <constraint> + <regex>(ttyS[0-9]+|hvc[0-9]+|usb[0-9]+b.*)</regex> + </constraint> + </properties> + <children> + <leafNode name="speed"> + <properties> + <help>Console baud rate</help> + <completionHelp> + <list>1200 2400 4800 9600 19200 38400 57600 115200</list> + </completionHelp> + <valueHelp> + <format>1200</format> + <description>1200 bps</description> + </valueHelp> + <valueHelp> + <format>2400</format> + <description>2400 bps</description> + </valueHelp> + <valueHelp> + <format>4800</format> + <description>4800 bps</description> + </valueHelp> + <valueHelp> + <format>9600</format> + <description>9600 bps</description> + </valueHelp> + <valueHelp> + <format>19200</format> + <description>19200 bps</description> + </valueHelp> + <valueHelp> + <format>38400</format> + <description>38400 bps</description> + </valueHelp> + <valueHelp> + <format>57600</format> + <description>57600 bps</description> + </valueHelp> + <valueHelp> + <format>115200</format> + <description>115200 bps</description> + </valueHelp> + <constraint> + <regex>(1200|2400|4800|9600|19200|38400|57600|115200)</regex> + </constraint> + </properties> + <defaultValue>115200</defaultValue> + </leafNode> + </children> + </tagNode> + <leafNode name="powersave"> + <properties> + <help>Enable screen blank powersaving on VGA console</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_domain-name.xml.in b/interface-definitions/system_domain-name.xml.in new file mode 100644 index 0000000..695af29 --- /dev/null +++ b/interface-definitions/system_domain-name.xml.in @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <leafNode name="domain-name" owner="${vyos_conf_scripts_dir}/system_host-name.py"> + <properties> + <help>System domain name</help> + <priority>6</priority> + <constraint> + <validator name="fqdn"/> + </constraint> + </properties> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_domain-search.xml.in b/interface-definitions/system_domain-search.xml.in new file mode 100644 index 0000000..eb6c8a8 --- /dev/null +++ b/interface-definitions/system_domain-search.xml.in @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <leafNode name="domain-search" owner="${vyos_conf_scripts_dir}/system_host-name.py"> + <properties> + <help>Domain Name Server (DNS) domain completion order</help> + <priority>400</priority> + <constraint> + <validator name="fqdn"/> + </constraint> + <constraintErrorMessage>Invalid domain name (RFC 1123 section 2).\nMay only contain letters, numbers and period.</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_flow-accounting.xml.in b/interface-definitions/system_flow-accounting.xml.in new file mode 100644 index 0000000..83a2480 --- /dev/null +++ b/interface-definitions/system_flow-accounting.xml.in @@ -0,0 +1,437 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- flow-accounting configuration --> +<interfaceDefinition> + <node name="system"> + <children> + <node name="flow-accounting" owner="${vyos_conf_scripts_dir}/system_flow-accounting.py"> + <properties> + <help>Flow accounting settings</help> + <priority>990</priority> + </properties> + <children> + <leafNode name="buffer-size"> + <properties> + <help>Buffer size</help> + <valueHelp> + <format>u32</format> + <description>Buffer size in MiB</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + <defaultValue>10</defaultValue> + </leafNode> + <leafNode name="packet-length"> + <properties> + <help>Specifies the maximum number of bytes to capture for each packet</help> + <valueHelp> + <format>u32:128-750</format> + <description>Packet length in bytes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 128-750"/> + </constraint> + </properties> + <defaultValue>128</defaultValue> + </leafNode> + <leafNode name="enable-egress"> + <properties> + <help>Enable egress flow accounting</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="disable-imt"> + <properties> + <help>Disable in memory table plugin</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="syslog-facility"> + <properties> + <help>Syslog facility for flow-accounting</help> + <completionHelp> + <list>auth authpriv cron daemon kern lpr mail mark news protocols security syslog user uucp local0 local1 local2 local3 local4 local5 local6 local7 all</list> + </completionHelp> + <valueHelp> + <format>auth</format> + <description>Authentication and authorization</description> + </valueHelp> + <valueHelp> + <format>authpriv</format> + <description>Non-system authorization</description> + </valueHelp> + <valueHelp> + <format>cron</format> + <description>Cron daemon</description> + </valueHelp> + <valueHelp> + <format>daemon</format> + <description>System daemons</description> + </valueHelp> + <valueHelp> + <format>kern</format> + <description>Kernel</description> + </valueHelp> + <valueHelp> + <format>lpr</format> + <description>Line printer spooler</description> + </valueHelp> + <valueHelp> + <format>mail</format> + <description>Mail subsystem</description> + </valueHelp> + <valueHelp> + <format>mark</format> + <description>Timestamp</description> + </valueHelp> + <valueHelp> + <format>news</format> + <description>USENET subsystem</description> + </valueHelp> + <valueHelp> + <format>protocols</format> + <description>Routing protocols (local7)</description> + </valueHelp> + <valueHelp> + <format>security</format> + <description>Authentication and authorization</description> + </valueHelp> + <valueHelp> + <format>syslog</format> + <description>Authentication and authorization</description> + </valueHelp> + <valueHelp> + <format>user</format> + <description>Application processes</description> + </valueHelp> + <valueHelp> + <format>uucp</format> + <description>UUCP subsystem</description> + </valueHelp> + <valueHelp> + <format>local0</format> + <description>Local facility 0</description> + </valueHelp> + <valueHelp> + <format>local1</format> + <description>Local facility 1</description> + </valueHelp> + <valueHelp> + <format>local2</format> + <description>Local facility 2</description> + </valueHelp> + <valueHelp> + <format>local3</format> + <description>Local facility 3</description> + </valueHelp> + <valueHelp> + <format>local4</format> + <description>Local facility 4</description> + </valueHelp> + <valueHelp> + <format>local5</format> + <description>Local facility 5</description> + </valueHelp> + <valueHelp> + <format>local6</format> + <description>Local facility 6</description> + </valueHelp> + <valueHelp> + <format>local7</format> + <description>Local facility 7</description> + </valueHelp> + <valueHelp> + <format>all</format> + <description>Authentication and authorization</description> + </valueHelp> + <constraint> + <regex>(auth|authpriv|cron|daemon|kern|lpr|mail|mark|news|protocols|security|syslog|user|uucp|local0|local1|local2|local3|local4|local5|local6|local7|all)</regex> + </constraint> + </properties> + </leafNode> + #include <include/generic-interface-multi.xml.i> + <node name="netflow"> + <properties> + <help>NetFlow settings</help> + </properties> + <children> + <leafNode name="engine-id"> + <properties> + <help>NetFlow engine-id</help> + <valueHelp> + <format>0-255 or 0-255:0-255</format> + <description>NetFlow engine-id for v5</description> + </valueHelp> + <valueHelp> + <format>u32</format> + <description>NetFlow engine-id for v9 / IPFIX</description> + </valueHelp> + <constraint> + <regex>(\d|[1-9]\d{1,8}|[1-3]\d{9}|4[01]\d{8}|42[0-8]\d{7}|429[0-3]\d{6}|4294[0-8]\d{5}|42949[0-5]\d{4}|429496[0-6]\d{3}|4294967[01]\d{2}|42949672[0-8]\d|429496729[0-5])$|^(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]):(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="max-flows"> + <properties> + <help>NetFlow maximum flows</help> + <valueHelp> + <format>u32</format> + <description>NetFlow maximum flows</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="sampling-rate"> + <properties> + <help>NetFlow sampling-rate</help> + <valueHelp> + <format>u32</format> + <description>Sampling rate (1 in N packets)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + #include <include/source-address-ipv4-ipv6.xml.i> + <leafNode name="version"> + <properties> + <help>NetFlow version to export</help> + <completionHelp> + <list>5 9 10</list> + </completionHelp> + <valueHelp> + <format>5</format> + <description>NetFlow version 5</description> + </valueHelp> + <valueHelp> + <format>9</format> + <description>NetFlow version 9</description> + </valueHelp> + <valueHelp> + <format>10</format> + <description>Internet Protocol Flow Information Export (IPFIX)</description> + </valueHelp> + </properties> + <defaultValue>9</defaultValue> + </leafNode> + <tagNode name="server"> + <properties> + <help>NetFlow destination server</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 server to export NetFlow</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 server to export NetFlow</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + <leafNode name="port"> + <properties> + <help>NetFlow port number</help> + <valueHelp> + <format>u32:1025-65535</format> + <description>NetFlow port number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1025-65535"/> + </constraint> + </properties> + <defaultValue>2055</defaultValue> + </leafNode> + </children> + </tagNode> + <node name="timeout"> + <properties> + <help>NetFlow timeout values</help> + </properties> + <children> + <leafNode name="expiry-interval"> + <properties> + <help>Expiry scan interval</help> + <valueHelp> + <format>u32:0-2147483647</format> + <description>Expiry scan interval</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2147483647"/> + </constraint> + </properties> + <defaultValue>60</defaultValue> + </leafNode> + <leafNode name="flow-generic"> + <properties> + <help>Generic flow timeout value</help> + <valueHelp> + <format>u32:0-2147483647</format> + <description>Generic flow timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2147483647"/> + </constraint> + </properties> + <defaultValue>3600</defaultValue> + </leafNode> + <leafNode name="icmp"> + <properties> + <help>ICMP timeout value</help> + <valueHelp> + <format>u32:0-2147483647</format> + <description>ICMP timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2147483647"/> + </constraint> + </properties> + <defaultValue>300</defaultValue> + </leafNode> + <leafNode name="max-active-life"> + <properties> + <help>Max active timeout value</help> + <valueHelp> + <format>u32:0-2147483647</format> + <description>Max active timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2147483647"/> + </constraint> + </properties> + <defaultValue>604800</defaultValue> + </leafNode> + <leafNode name="tcp-fin"> + <properties> + <help>TCP finish timeout value</help> + <valueHelp> + <format>u32:0-2147483647</format> + <description>TCP FIN timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2147483647"/> + </constraint> + </properties> + <defaultValue>300</defaultValue> + </leafNode> + <leafNode name="tcp-generic"> + <properties> + <help>TCP generic timeout value</help> + <valueHelp> + <format>u32:0-2147483647</format> + <description>TCP generic timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2147483647"/> + </constraint> + </properties> + <defaultValue>3600</defaultValue> + </leafNode> + <leafNode name="tcp-rst"> + <properties> + <help>TCP reset timeout value</help> + <valueHelp> + <format>u32:0-2147483647</format> + <description>TCP RST timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2147483647"/> + </constraint> + </properties> + <defaultValue>120</defaultValue> + </leafNode> + <leafNode name="udp"> + <properties> + <help>UDP timeout value</help> + <valueHelp> + <format>u32:0-2147483647</format> + <description>UDP timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2147483647"/> + </constraint> + </properties> + <defaultValue>300</defaultValue> + </leafNode> + </children> + </node> + </children> + </node> + <node name="sflow"> + <properties> + <help>sFlow settings</help> + </properties> + <children> + <leafNode name="agent-address"> + <properties> + <help>sFlow agent IPv4 address</help> + <completionHelp> + <list>auto</list> + <script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>sFlow IPv4 agent address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="sampling-rate"> + <properties> + <help>sFlow sampling-rate</help> + <valueHelp> + <format>u32</format> + <description>Sampling rate (1 in N packets)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + <tagNode name="server"> + <properties> + <help>sFlow destination server</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 server to export sFlow</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 server to export sFlow</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + <leafNode name="port"> + <properties> + <help>sFlow port number</help> + <valueHelp> + <format>u32:1025-65535</format> + <description>sFlow port number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1025-65535"/> + </constraint> + </properties> + <defaultValue>6343</defaultValue> + </leafNode> + </children> + </tagNode> + #include <include/source-address-ipv4-ipv6.xml.i> + </children> + </node> + #include <include/interface/vrf.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_frr.xml.in b/interface-definitions/system_frr.xml.in new file mode 100644 index 0000000..28242df --- /dev/null +++ b/interface-definitions/system_frr.xml.in @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="frr" owner="${vyos_conf_scripts_dir}/system_frr.py"> + <properties> + <help>Configure FRRouting parameters</help> + <!-- Before components that use FRR --> + <priority>150</priority> + </properties> + <children> + <leafNode name="bmp"> + <properties> + <help>Enable BGP Monitoring Protocol support</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="descriptors"> + <properties> + <help>Number of open file descriptors a process is allowed to use</help> + <valueHelp> + <format>u32:1024-8192</format> + <description>Number of file descriptors</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1024-8192"/> + </constraint> + <constraintErrorMessage>Port number must be in range 1024 to 8192</constraintErrorMessage> + </properties> + <defaultValue>1024</defaultValue> + </leafNode> + <leafNode name="irdp"> + <properties> + <help>Enable ICMP Router Discovery Protocol support</help> + <valueless/> + </properties> + </leafNode> + <node name="snmp"> + <properties> + <help>Enable SNMP integration for next daemons</help> + </properties> + <children> + <leafNode name="bgpd"> + <properties> + <help>BGP</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="isisd"> + <properties> + <help>IS-IS</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ldpd"> + <properties> + <help>LDP</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ospf6d"> + <properties> + <help>OSPFv3</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ospfd"> + <properties> + <help>OSPFv2</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ripd"> + <properties> + <help>RIP</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="zebra"> + <properties> + <help>Zebra (IP routing manager)</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_host-name.xml.in b/interface-definitions/system_host-name.xml.in new file mode 100644 index 0000000..f74baab --- /dev/null +++ b/interface-definitions/system_host-name.xml.in @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <!-- script does not use XML defaults so far --> + <leafNode name="host-name" owner="${vyos_conf_scripts_dir}/system_host-name.py"> + <properties> + <help>System host name (default: vyos)</help> + <priority>5</priority> + <constraint> + #include <include/constraint/host-name.xml.i> + </constraint> + </properties> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_ip.xml.in b/interface-definitions/system_ip.xml.in new file mode 100644 index 0000000..b4b5092 --- /dev/null +++ b/interface-definitions/system_ip.xml.in @@ -0,0 +1,109 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="ip" owner="${vyos_conf_scripts_dir}/system_ip.py"> + <properties> + <help>IPv4 Settings</help> + <!-- must be before any interface, check /opt/vyatta/sbin/priority.pl --> + <priority>290</priority> + </properties> + <children> + <node name="arp"> + <properties> + <help>Parameters for ARP cache</help> + </properties> + <children> + #include <include/arp-ndp-table-size.xml.i> + </children> + </node> + <leafNode name="disable-forwarding"> + <properties> + <help>Disable IPv4 forwarding on all interfaces</help> + <valueless/> + </properties> + </leafNode> + <node name="multipath"> + <properties> + <help>IPv4 multipath settings</help> + </properties> + <children> + <leafNode name="ignore-unreachable-nexthops"> + <properties> + <help>Ignore next hops that are not in the ARP table</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="layer4-hashing"> + <properties> + <help>Use layer 4 information for ECMP hashing</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + #include <include/system-ip-nht.xml.i> + <node name="tcp"> + <properties> + <help>IPv4 TCP parameters</help> + </properties> + <children> + <node name="mss"> + <properties> + <help>IPv4 TCP MSS probing options</help> + </properties> + <children> + <leafNode name="probing"> + <properties> + <help>Attempt to lower the MSS if TCP connections fail to establish</help> + <completionHelp> + <list>on-icmp-black-hole force</list> + </completionHelp> + <valueHelp> + <format>on-icmp-black-hole</format> + <description>Attempt TCP MSS probing when an ICMP black hole is detected</description> + </valueHelp> + <valueHelp> + <format>force</format> + <description>Attempt TCP MSS probing by default</description> + </valueHelp> + <constraint> + <regex>(on-icmp-black-hole|force)</regex> + </constraint> + <constraintErrorMessage>Must be on-icmp-black-hole or force</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="base"> + <properties> + <help>Base MSS to start probing from (applicable to "probing force")</help> + <valueHelp> + <format>u32:48-1460</format> + <description>Base MSS value for probing (default: 1024)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 48-1460"/> + </constraint> + </properties> + </leafNode> + <leafNode name="floor"> + <properties> + <help>Minimum MSS to stop probing at (default: 48)</help> + <valueHelp> + <format>u32:48-1460</format> + <description>Minimum MSS value to probe</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 48-1460"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + #include <include/system-ip-protocol.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_ipv6.xml.in b/interface-definitions/system_ipv6.xml.in new file mode 100644 index 0000000..dda00af --- /dev/null +++ b/interface-definitions/system_ipv6.xml.in @@ -0,0 +1,51 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="ipv6" owner="${vyos_conf_scripts_dir}/system_ipv6.py"> + <properties> + <help>IPv6 Settings</help> + <!-- must be before any interface, check /opt/vyatta/sbin/priority.pl --> + <priority>290</priority> + </properties> + <children> + <leafNode name="disable-forwarding"> + <properties> + <help>Disable IPv6 forwarding on all interfaces</help> + <valueless/> + </properties> + </leafNode> + <node name="multipath"> + <properties> + <help>IPv6 multipath settings</help> + </properties> + <children> + <leafNode name="layer4-hashing"> + <properties> + <help>Use layer 4 information for ECMP hashing</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="neighbor"> + <properties> + <help>Parameters for neighbor discovery cache</help> + </properties> + <children> + #include <include/arp-ndp-table-size.xml.i> + </children> + </node> + #include <include/system-ip-nht.xml.i> + #include <include/system-ipv6-protocol.xml.i> + <leafNode name="strict-dad"> + <properties> + <help>Disable IPv6 operation on interface when DAD fails on LL addr</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_lcd.xml.in b/interface-definitions/system_lcd.xml.in new file mode 100644 index 0000000..0cf4de3 --- /dev/null +++ b/interface-definitions/system_lcd.xml.in @@ -0,0 +1,70 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="lcd" owner="${vyos_conf_scripts_dir}/system_lcd.py"> + <properties> + <help>System LCD display</help> + <priority>100</priority> + </properties> + <children> + <leafNode name="model"> + <properties> + <help>Model of the display attached to this system</help> + <completionHelp> + <list>cfa-533 cfa-631 cfa-633 cfa-635 hd44780 sdec</list> + </completionHelp> + <valueHelp> + <format>cfa-533</format> + <description>Crystalfontz CFA-533</description> + </valueHelp> + <valueHelp> + <format>cfa-631</format> + <description>Crystalfontz CFA-631</description> + </valueHelp> + <valueHelp> + <format>cfa-633</format> + <description>Crystalfontz CFA-633</description> + </valueHelp> + <valueHelp> + <format>cfa-635</format> + <description>Crystalfontz CFA-635</description> + </valueHelp> + <valueHelp> + <format>hd44780</format> + <description>Hitachi HD44780, Caswell Appliances</description> + </valueHelp> + <valueHelp> + <format>sdec</format> + <description>Lanner, Watchguard, Nexcom NSA, Sophos UTM appliances</description> + </valueHelp> + <constraint> + <regex>(cfa-533|cfa-631|cfa-633|cfa-635|hd44780|sdec)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="device"> + <properties> + <help>Physical device used by LCD display</help> + <completionHelp> + <script>ls -1 /dev | grep ttyS</script> + <script>if [ -d /dev/serial/by-bus ]; then ls -1 /dev/serial/by-bus; fi</script> + </completionHelp> + <valueHelp> + <format>ttySXX</format> + <description>TTY device name, regular serial port</description> + </valueHelp> + <valueHelp> + <format>usbNbXpY</format> + <description>TTY device name, USB based</description> + </valueHelp> + <constraint> + <regex>(ttyS[0-9]+|usb[0-9]+b.*)</regex> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_login.xml.in b/interface-definitions/system_login.xml.in new file mode 100644 index 0000000..f6c8021 --- /dev/null +++ b/interface-definitions/system_login.xml.in @@ -0,0 +1,294 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="login" owner="${vyos_conf_scripts_dir}/system_login.py"> + <properties> + <help>System User Login Configuration</help> + <priority>400</priority> + </properties> + <children> + <tagNode name="user"> + <properties> + <help>Local user account information</help> + <constraint> + #include <include/constraint/login-username.xml.i> + </constraint> + <constraintErrorMessage>Username contains illegal characters or\nexceeds 100 character limitation.</constraintErrorMessage> + </properties> + <children> + <node name="authentication"> + <properties> + <help>Authentication settings</help> + </properties> + <children> + <leafNode name="encrypted-password"> + <properties> + <help>Encrypted password</help> + <constraint> + <regex>(\*|\!)</regex> + <regex>[a-zA-Z0-9\.\/]{13}</regex> + <regex>\$1\$[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{22}</regex> + <regex>\$5\$(rounds=[0-9]+\$)?[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{43}</regex> + <regex>\$6\$(rounds=[0-9]+\$)?[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{86}</regex> + </constraint> + <constraintErrorMessage>Invalid encrypted password for $VAR(../../@).</constraintErrorMessage> + </properties> + <defaultValue>!</defaultValue> + </leafNode> + <node name="otp"> + <properties> + <help>One-Time-Pad (two-factor) authentication parameters</help> + </properties> + <children> + <leafNode name="rate-limit"> + <properties> + <help>Limit number of logins (rate-limit) per rate-time</help> + <valueHelp> + <format>u32:1-10</format> + <description>Number of attempts</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-10"/> + </constraint> + <constraintErrorMessage>Number of login attempts must me between 1 and 10</constraintErrorMessage> + </properties> + <defaultValue>3</defaultValue> + </leafNode> + <leafNode name="rate-time"> + <properties> + <help>Limit number of logins (rate-limit) per rate-time</help> + <valueHelp> + <format>u32:15-600</format> + <description>Time interval</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 15-600"/> + </constraint> + <constraintErrorMessage>Rate limit time interval must be between 15 and 600 seconds</constraintErrorMessage> + </properties> + <defaultValue>30</defaultValue> + </leafNode> + <leafNode name="window-size"> + <properties> + <help>Set window of concurrently valid codes</help> + <valueHelp> + <format>u32:1-21</format> + <description>Window size</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21"/> + </constraint> + <constraintErrorMessage>Window of concurrently valid codes must be between 1 and 21</constraintErrorMessage> + </properties> + <defaultValue>3</defaultValue> + </leafNode> + <leafNode name="key"> + <properties> + <help>Key/secret the token algorithm (see RFC4226)</help> + <valueHelp> + <format>txt</format> + <description>Base32 encoded key/token</description> + </valueHelp> + <constraint> + <regex>[a-zA-Z2-7]{26,10000}</regex> + </constraint> + <constraintErrorMessage>Key must only include base32 characters and be at least 26 characters long</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + <leafNode name="plaintext-password"> + <properties> + <help>Plaintext password used for encryption</help> + </properties> + </leafNode> + <tagNode name="public-keys"> + <properties> + <help>Remote access public keys</help> + <valueHelp> + <format>txt</format> + <description>Key identifier used by ssh-keygen (usually of form user@host)</description> + </valueHelp> + </properties> + <children> + <leafNode name="key"> + <properties> + <help>Public key value (Base64 encoded)</help> + <constraint> + <validator name="base64"/> + </constraint> + </properties> + </leafNode> + <leafNode name="options"> + <properties> + <help>Optional public key options</help> + </properties> + </leafNode> + <leafNode name="type"> + <properties> + <help>SSH public key type</help> + <completionHelp> + <list>ssh-dss ssh-rsa ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 sk-ecdsa-sha2-nistp256@openssh.com sk-ssh-ed25519@openssh.com</list> + </completionHelp> + <valueHelp> + <format>ssh-dss</format> + <description>Digital Signature Algorithm (DSA) key support</description> + </valueHelp> + <valueHelp> + <format>ssh-rsa</format> + <description>Key pair based on RSA algorithm</description> + </valueHelp> + <valueHelp> + <format>ecdsa-sha2-nistp256</format> + <description>Elliptic Curve DSA with NIST P-256 curve</description> + </valueHelp> + <valueHelp> + <format>ecdsa-sha2-nistp384</format> + <description>Elliptic Curve DSA with NIST P-384 curve</description> + </valueHelp> + <valueHelp> + <format>ecdsa-sha2-nistp521</format> + <description>Elliptic Curve DSA with NIST P-521 curve</description> + </valueHelp> + <valueHelp> + <format>ssh-ed25519</format> + <description>Edwards-curve DSA with elliptic curve 25519</description> + </valueHelp> + <valueHelp> + <format>sk-ecdsa-sha2-nistp256@openssh.com</format> + <description>Elliptic Curve DSA security key</description> + </valueHelp> + <valueHelp> + <format>sk-ssh-ed25519@openssh.com</format> + <description>Elliptic curve 25519 security key</description> + </valueHelp> + <constraint> + <regex>(ssh-dss|ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|ssh-ed25519|sk-ecdsa-sha2-nistp256@openssh.com|sk-ssh-ed25519@openssh.com)</regex> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + #include <include/generic-disable-node.xml.i> + <leafNode name="full-name"> + <properties> + <help>Full name of the user (use quotes for names with spaces)</help> + <constraint> + <regex>[^:]*</regex> + </constraint> + <constraintErrorMessage>Cannot use ':' in full name</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="home-directory"> + <properties> + <help>Home directory</help> + <valueHelp> + <format>txt</format> + <description>Path to home directory</description> + </valueHelp> + <constraint> + <regex>\/$|(\/[a-zA-Z_0-9-.]+)+</regex> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + #include <include/radius-server-ipv4-ipv6.xml.i> + <node name="radius"> + <children> + <tagNode name="server"> + <children> + #include <include/radius-timeout.xml.i> + #include <include/radius-priority.xml.i> + <leafNode name="priority"> + <defaultValue>255</defaultValue> + </leafNode> + </children> + </tagNode> + #include <include/interface/vrf.xml.i> + </children> + </node> + <node name="tacacs"> + <properties> + <help>TACACS+ based user authentication</help> + </properties> + <children> + <tagNode name="server"> + <properties> + <help>TACACS+ server configuration</help> + <valueHelp> + <format>ipv4</format> + <description>TACACS+ server IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/radius-server-key.xml.i> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>49</defaultValue> + </leafNode> + </children> + </tagNode> + #include <include/source-address-ipv4.xml.i> + <leafNode name="security-mode"> + <properties> + <help>Security mode for TACACS+ authentication</help> + <completionHelp> + <list>mandatory optional</list> + </completionHelp> + <valueHelp> + <format>mandatory</format> + <description>Deny access immediately if TACACS+ answers with REJECT</description> + </valueHelp> + <valueHelp> + <format>optional</format> + <description>Pass to the next authentication method if TACACS+ answers with REJECT</description> + </valueHelp> + <constraint> + <regex>(mandatory|optional)</regex> + </constraint> + </properties> + <defaultValue>optional</defaultValue> + </leafNode> + #include <include/radius-timeout.xml.i> + #include <include/interface/vrf.xml.i> + </children> + </node> + <leafNode name="max-login-session"> + <properties> + <help>Maximum number of all login sessions</help> + <valueHelp> + <format>u32:1-65536</format> + <description>Maximum number of all login sessions</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65536"/> + </constraint> + <constraintErrorMessage>Maximum logins must be between 1 and 65536</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="timeout"> + <properties> + <help>Session timeout</help> + <valueHelp> + <format>u32:5-604800</format> + <description>Session timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 5-604800"/> + </constraint> + <constraintErrorMessage>Timeout must be between 5 and 604800 seconds</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_login_banner.xml.in b/interface-definitions/system_login_banner.xml.in new file mode 100644 index 0000000..c90e38c --- /dev/null +++ b/interface-definitions/system_login_banner.xml.in @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="login" owner="${vyos_conf_scripts_dir}/system_login.py"> + <properties> + <help>System User Login Configuration</help> + <priority>400</priority> + </properties> + <children> + <node name="banner" owner="${vyos_conf_scripts_dir}/system_login_banner.py"> + <properties> + <help>System login banners</help> + <priority>410</priority> + </properties> + <children> + <leafNode name="post-login"> + <properties> + <help>A system banner after the user logs in </help> + </properties> + </leafNode> + <leafNode name="pre-login"> + <properties> + <help>A system banner before the user logs in</help> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_logs.xml.in b/interface-definitions/system_logs.xml.in new file mode 100644 index 0000000..b34cbdc --- /dev/null +++ b/interface-definitions/system_logs.xml.in @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="logs" owner="${vyos_conf_scripts_dir}/system_logs.py"> + <properties> + <help>Logging options</help> + <priority>9999</priority> + </properties> + <children> + <node name="logrotate"> + <properties> + <help>Logrotate options</help> + </properties> + <children> + <node name="atop"> + <properties> + <help>Atop logs options (system resources usage)</help> + </properties> + <children> + <leafNode name="max-size"> + <properties> + <help>Size of a single log file that triggers rotation</help> + <valueHelp> + <format>u32:1-1024</format> + <description>Size in MB</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-1024" /> + </constraint> + <constraintErrorMessage>The size must be between 1 and 1024 MB</constraintErrorMessage> + </properties> + <defaultValue>10</defaultValue> + </leafNode> + <leafNode name="rotate"> + <properties> + <help>Count of rotations before old logs will be deleted</help> + <valueHelp> + <format>u32:1-100</format> + <description>Rotations</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-100" /> + </constraint> + <constraintErrorMessage>The count must be between 1 and 100</constraintErrorMessage> + </properties> + <defaultValue>10</defaultValue> + </leafNode> + </children> + </node> + <node name="messages"> + <properties> + <help>The /var/log/messages file rotation</help> + </properties> + <children> + <leafNode name="max-size"> + <properties> + <help>Size of a single log file that triggers rotation</help> + <valueHelp> + <format>u32:1-1024</format> + <description>Size in MB</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-1024" /> + </constraint> + <constraintErrorMessage>The size must be between 1 and 1024 MB</constraintErrorMessage> + </properties> + <defaultValue>1</defaultValue> + </leafNode> + <leafNode name="rotate"> + <properties> + <help>Count of rotations before old logs will be deleted</help> + <valueHelp> + <format>u32:1-100</format> + <description>Rotations</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-100" /> + </constraint> + <constraintErrorMessage>The count must be between 1 and 100</constraintErrorMessage> + </properties> + <defaultValue>10</defaultValue> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_name-server.xml.in b/interface-definitions/system_name-server.xml.in new file mode 100644 index 0000000..2f750ab --- /dev/null +++ b/interface-definitions/system_name-server.xml.in @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <leafNode name="name-server" owner="${vyos_conf_scripts_dir}/system_host-name.py"> + <properties> + <help>System Domain Name Servers (DNS)</help> + <priority>400</priority> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>Domain Name Server IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Domain Name Server IPv6 address</description> + </valueHelp> + <valueHelp> + <format>txt</format> + <description>Use Domain Name Server from DHCP interface</description> + </valueHelp> + <multi/> + <constraint> + <validator name="ip-address"/> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_option.xml.in b/interface-definitions/system_option.xml.in new file mode 100644 index 0000000..dc9958f --- /dev/null +++ b/interface-definitions/system_option.xml.in @@ -0,0 +1,229 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="option" owner="${vyos_conf_scripts_dir}/system_option.py"> + <properties> + <help>System Options</help> + <priority>9999</priority> + </properties> + <children> + <leafNode name="ctrl-alt-delete"> + <properties> + <help>System action on Ctrl-Alt-Delete keystroke</help> + <completionHelp> + <list>ignore reboot poweroff</list> + </completionHelp> + <valueHelp> + <format>ignore</format> + <description>Ignore key sequence</description> + </valueHelp> + <valueHelp> + <format>reboot</format> + <description>Reboot system</description> + </valueHelp> + <valueHelp> + <format>poweroff</format> + <description>Poweroff system</description> + </valueHelp> + <constraint> + <regex>(ignore|reboot|poweroff)</regex> + </constraint> + <constraintErrorMessage>Must be ignore, reboot, or poweroff</constraintErrorMessage> + </properties> + </leafNode> + <node name="kernel"> + <properties> + <help>Kernel boot parameters</help> + </properties> + <children> + <leafNode name="disable-mitigations"> + <properties> + <help>Disable all optional CPU mitigations</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="disable-power-saving"> + <properties> + <help>Disable CPU power saving mechanisms also known as C states</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="amd-pstate-driver"> + <properties> + <help>Enables and configures pstate driver for AMD Ryzen and Epyc CPUs</help> + <completionHelp> + <list>active passive guided</list> + </completionHelp> + <valueHelp> + <format>active</format> + <description>The firmware controls performance states and the system governor has no effect</description> + </valueHelp> + <valueHelp> + <format>passive</format> + <description>Allow the system governor to manage performance states</description> + </valueHelp> + <valueHelp> + <format>guided</format> + <description>The firmware controls performance states guided by the system governor</description> + </valueHelp> + </properties> + </leafNode> + <node name="debug"> + <properties> + <help>Dynamic debugging for kernel module</help> + </properties> + <children> + <leafNode name="wireguard"> + <properties> + <help>Dynamic debugging for Wireguard module</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="keyboard-layout"> + <properties> + <help>System keyboard layout, type ISO2</help> + <completionHelp> + <list>us uk fr de es fi jp106 no dk se-latin1 dvorak</list> + </completionHelp> + <valueHelp> + <format>us</format> + <description>United States</description> + </valueHelp> + <valueHelp> + <format>uk</format> + <description>United Kingdom</description> + </valueHelp> + <valueHelp> + <format>fr</format> + <description>France</description> + </valueHelp> + <valueHelp> + <format>de</format> + <description>Germany</description> + </valueHelp> + <valueHelp> + <format>es</format> + <description>Spain</description> + </valueHelp> + <valueHelp> + <format>fi</format> + <description>Finland</description> + </valueHelp> + <valueHelp> + <format>jp106</format> + <description>Japan</description> + </valueHelp> + <valueHelp> + <format>no</format> + <description>Norway</description> + </valueHelp> + <valueHelp> + <format>dk</format> + <description>Denmark</description> + </valueHelp> + <valueHelp> + <format>se-latin1</format> + <description>Sweden</description> + </valueHelp> + <valueHelp> + <format>dvorak</format> + <description>Dvorak</description> + </valueHelp> + <constraint> + <regex>(us|uk|fr|de|es|fi|jp106|no|dk|se-latin1|dvorak)</regex> + </constraint> + <constraintErrorMessage>Invalid keyboard layout</constraintErrorMessage> + </properties> + <defaultValue>us</defaultValue> + </leafNode> + <leafNode name="performance"> + <properties> + <help>Tune system performance</help> + <completionHelp> + <list>throughput latency</list> + </completionHelp> + <valueHelp> + <format>throughput</format> + <description>Tune for maximum network throughput</description> + </valueHelp> + <valueHelp> + <format>latency</format> + <description>Tune for low network latency</description> + </valueHelp> + <constraint> + <regex>(throughput|latency)</regex> + </constraint> + </properties> + </leafNode> + <node name="http-client"> + <properties> + <help>Global options used for HTTP client</help> + </properties> + <children> + #include <include/source-interface.xml.i> + #include <include/source-address-ipv4-ipv6.xml.i> + </children> + </node> + <leafNode name="reboot-on-panic"> + <properties> + <help>Reboot system on kernel panic</help> + <valueless/> + </properties> + </leafNode> + <node name="ssh-client"> + <properties> + <help>Global options used for SSH client</help> + </properties> + <children> + #include <include/source-address-ipv4-ipv6.xml.i> + #include <include/source-interface.xml.i> + </children> + </node> + <leafNode name="startup-beep"> + <properties> + <help>plays sound via system speaker when you can login</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="root-partition-auto-resize"> + <properties> + <help>Enable root partition auto-extention on system boot</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="time-format"> + <properties> + <help>System time-format</help> + <completionHelp> + <list>12-hour 24-hour</list> + </completionHelp> + <valueHelp> + <format>12-hour</format> + <description>12 hour time format</description> + </valueHelp> + <valueHelp> + <format>24-hour</format> + <description>24 hour time format</description> + </valueHelp> + <constraint> + <regex>(12-hour|24-hour)</regex> + </constraint> + </properties> + <defaultValue>12-hour</defaultValue> + </leafNode> + <leafNode name="disable-usb-autosuspend"> + <properties> + <help>Disable autosuspend for all USB devices</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_proxy.xml.in b/interface-definitions/system_proxy.xml.in new file mode 100644 index 0000000..5b0df5c --- /dev/null +++ b/interface-definitions/system_proxy.xml.in @@ -0,0 +1,26 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="proxy" owner="${vyos_conf_scripts_dir}/system_proxy.py"> + <properties> + <help>Sets a proxy for system wide use</help> + <priority>100</priority> + </properties> + <children> + <leafNode name="url"> + <properties> + <help>Proxy URL</help> + <constraint> + <regex>http(s)?:\/\/[a-z0-9-\.]+</regex> + </constraint> + </properties> + </leafNode> + #include <include/port-number.xml.i> + #include <include/generic-username.xml.i> + #include <include/generic-password.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_sflow.xml.in b/interface-definitions/system_sflow.xml.in new file mode 100644 index 0000000..aaf4033 --- /dev/null +++ b/interface-definitions/system_sflow.xml.in @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- sflow configuration --> +<interfaceDefinition> + <node name="system"> + <children> + <node name="sflow" owner="${vyos_conf_scripts_dir}/system_sflow.py"> + <properties> + <help>sFlow settings</help> + <priority>990</priority> + </properties> + <children> + <leafNode name="agent-address"> + <properties> + <help>sFlow agent IPv4 or IPv6 address</help> + <completionHelp> + <list>auto</list> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>sFlow IPv4 agent address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>sFlow IPv6 agent address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + <validator name="ipv6-link-local"/> + </constraint> + </properties> + </leafNode> + <leafNode name="agent-interface"> + <properties> + <help>IP address associated with this interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + </leafNode> + <leafNode name="drop-monitor-limit"> + <properties> + <help>Export headers of dropped by kernel packets</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Maximum rate limit of N drops per second send out in the sFlow datagrams</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + #include <include/generic-interface-multi.xml.i> + <leafNode name="polling"> + <properties> + <help>Schedule counter-polling in seconds</help> + <valueHelp> + <format>u32:1-600</format> + <description>Polling rate in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-600"/> + </constraint> + </properties> + <defaultValue>30</defaultValue> + </leafNode> + <leafNode name="sampling-rate"> + <properties> + <help>sFlow sampling-rate</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Sampling rate (1 in N packets)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>1000</defaultValue> + </leafNode> + <tagNode name="server"> + <properties> + <help>sFlow destination server</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 server to export sFlow</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 server to export sFlow</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>6343</defaultValue> + </leafNode> + </children> + </tagNode> + #include <include/interface/vrf.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_static-host-mapping.xml.in b/interface-definitions/system_static-host-mapping.xml.in new file mode 100644 index 0000000..492741f --- /dev/null +++ b/interface-definitions/system_static-host-mapping.xml.in @@ -0,0 +1,53 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="static-host-mapping" owner="${vyos_conf_scripts_dir}/system_host-name.py"> + <properties> + <help>Map host names to addresses</help> + <priority>400</priority> + </properties> + <children> + <tagNode name="host-name"> + <properties> + <help>Host name for static address mapping</help> + <constraint> + #include <include/constraint/host-name.xml.i> + </constraint> + <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage> + </properties> + <children> + <leafNode name="alias"> + <properties> + <help>Alias for this address</help> + <constraint> + <regex>.{1,63}</regex> + </constraint> + <constraintErrorMessage>invalid alias hostname, needs to be between 1 and 63 charactes</constraintErrorMessage> + <multi /> + </properties> + </leafNode> + <leafNode name="inet"> + <properties> + <help>IP Address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_sysctl.xml.in b/interface-definitions/system_sysctl.xml.in new file mode 100644 index 0000000..bf118c2 --- /dev/null +++ b/interface-definitions/system_sysctl.xml.in @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <properties> + <help>System parameters</help> + </properties> + <children> + <node name="sysctl" owner="${vyos_conf_scripts_dir}/system_sysctl.py"> + <properties> + <help>Configure kernel parameters at runtime</help> + <priority>318</priority> + </properties> + <children> + <tagNode name="parameter"> + <properties> + <help>Sysctl key name</help> + <completionHelp> + <script>${vyos_completion_dir}/list_sysctl_parameters.sh</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Sysctl key name</description> + </valueHelp> + <constraint> + <validator name="sysctl"/> + </constraint> + </properties> + <children> + <leafNode name="value"> + <properties> + <help>Sysctl configuration value</help> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_syslog.xml.in b/interface-definitions/system_syslog.xml.in new file mode 100644 index 0000000..0a9a005 --- /dev/null +++ b/interface-definitions/system_syslog.xml.in @@ -0,0 +1,161 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="syslog" owner="${vyos_conf_scripts_dir}/system_syslog.py"> + <properties> + <help>System logging</help> + <priority>400</priority> + </properties> + <children> + <tagNode name="user"> + <properties> + <help>Logging to specific terminal of given user</help> + <completionHelp> + <path>system login user</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Local user account</description> + </valueHelp> + <constraint> + #include <include/constraint/login-username.xml.i> + </constraint> + <constraintErrorMessage>illegal characters in user</constraintErrorMessage> + </properties> + <children> + #include <include/syslog-facility.xml.i> + </children> + </tagNode> + <tagNode name="host"> + <properties> + <help>Logging to remote host</help> + <constraint> + <validator name="ip-address"/> + <validator name="fqdn"/> + </constraint> + <constraintErrorMessage>Invalid host (FQDN or IP address)</constraintErrorMessage> + <valueHelp> + <format>ipv4</format> + <description>Remote syslog server IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Remote syslog server IPv6 address</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>Remote syslog server FQDN</description> + </valueHelp> + </properties> + <children> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>514</defaultValue> + </leafNode> + #include <include/protocol-tcp-udp.xml.i> + #include <include/syslog-facility.xml.i> + <node name="format"> + <properties> + <help>Logging format</help> + </properties> + <children> + <leafNode name="octet-counted"> + <properties> + <help>Allows for the transmission of all characters inside a syslog message</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="include-timezone"> + <properties> + <help>Include system timezone in syslog message</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + <node name="global"> + <properties> + <help>Logging to system standard location</help> + </properties> + <children> + #include <include/syslog-facility.xml.i> + <node name="marker"> + <properties> + <help>mark messages sent to syslog</help> + </properties> + <children> + <leafNode name="interval"> + <properties> + <help>time interval how often a mark message is being sent in seconds</help> + <constraint> + <validator name="numeric" argument="--positive"/> + </constraint> + </properties> + <defaultValue>1200</defaultValue> + </leafNode> + </children> + </node> + <leafNode name="preserve-fqdn"> + <properties> + <help>uses FQDN for logging</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <tagNode name="file"> + <properties> + <help>Logging to a file</help> + <constraint> + <regex>[a-zA-Z0-9\-_.]{1,255}</regex> + </constraint> + <constraintErrorMessage>illegal characters in filename or filename longer than 255 characters</constraintErrorMessage> + </properties> + <children> + <node name="archive"> + <properties> + <help>Log file size and rotation characteristics</help> + </properties> + <children> + <leafNode name="file"> + <properties> + <help>Number of saved files</help> + <constraint> + <regex>[0-9]+</regex> + </constraint> + <constraintErrorMessage>illegal characters in number of files</constraintErrorMessage> + </properties> + <defaultValue>5</defaultValue> + </leafNode> + <leafNode name="size"> + <properties> + <help>Size of log files in kbytes</help> + <constraint> + <regex>[0-9]+</regex> + </constraint> + <constraintErrorMessage>illegal characters in size</constraintErrorMessage> + </properties> + <defaultValue>256</defaultValue> + </leafNode> + </children> + </node> + #include <include/syslog-facility.xml.i> + </children> + </tagNode> + <node name="console"> + <properties> + <help>logging to serial console</help> + </properties> + <children> + #include <include/syslog-facility.xml.i> + </children> + </node> + #include <include/interface/vrf.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_task-scheduler.xml.in b/interface-definitions/system_task-scheduler.xml.in new file mode 100644 index 0000000..597d588 --- /dev/null +++ b/interface-definitions/system_task-scheduler.xml.in @@ -0,0 +1,72 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="task-scheduler"> + <properties> + <help>Task scheduler settings</help> + </properties> + <children> + <tagNode name="task" owner="${vyos_conf_scripts_dir}/system_task-scheduler.py"> + <properties> + <help>Scheduled task</help> + <valueHelp> + <format>txt</format> + <description>Task name</description> + </valueHelp> + <priority>999</priority> + </properties> + <children> + <leafNode name="crontab-spec"> + <properties> + <help>UNIX crontab time specification string</help> + </properties> + </leafNode> + <leafNode name="interval"> + <properties> + <help>Execution interval</help> + <valueHelp> + <format><minutes></format> + <description>Execution interval in minutes</description> + </valueHelp> + <valueHelp> + <format><minutes>m</format> + <description>Execution interval in minutes</description> + </valueHelp> + <valueHelp> + <format><hours>h</format> + <description>Execution interval in hours</description> + </valueHelp> + <valueHelp> + <format><days>d</format> + <description>Execution interval in days</description> + </valueHelp> + <constraint> + <regex>[1-9]([0-9]*)([mhd]{0,1})</regex> + </constraint> + </properties> + </leafNode> + <node name="executable"> + <properties> + <help>Executable path and arguments</help> + </properties> + <children> + <leafNode name="path"> + <properties> + <help>Path to executable</help> + </properties> + </leafNode> + <leafNode name="arguments"> + <properties> + <help>Arguments passed to the executable</help> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_time-zone.xml.in b/interface-definitions/system_time-zone.xml.in new file mode 100644 index 0000000..65cce9e --- /dev/null +++ b/interface-definitions/system_time-zone.xml.in @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <leafNode name="time-zone" owner="${vyos_conf_scripts_dir}/system_timezone.py"> + <properties> + <help>Local time zone (default UTC)</help> + <priority>100</priority> + <completionHelp> + <script>timedatectl list-timezones</script> + </completionHelp> + <constraint> + <validator name="timezone" argument="--validate"/> + </constraint> + </properties> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_update-check.xml.in b/interface-definitions/system_update-check.xml.in new file mode 100644 index 0000000..14570b0 --- /dev/null +++ b/interface-definitions/system_update-check.xml.in @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="update-check" owner="${vyos_conf_scripts_dir}/system_update-check.py"> + <properties> + <help>Check available update images</help> + <priority>9999</priority> + </properties> + <children> + <leafNode name="auto-check"> + <properties> + <help>Enable auto check for new images</help> + <valueless/> + </properties> + </leafNode> + #include <include/url-http-https.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/system_wireless.xml.in b/interface-definitions/system_wireless.xml.in new file mode 100644 index 0000000..834f8b6 --- /dev/null +++ b/interface-definitions/system_wireless.xml.in @@ -0,0 +1,36 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="system"> + <children> + <node name="wireless" owner="${vyos_conf_scripts_dir}/system_wireless.py"> + <properties> + <help>Wireless (IEEE-802.11) subsystem settings</help> + <!-- must be before interface wireless, check /opt/vyatta/sbin/priority.pl --> + <priority>317</priority> + </properties> + <children> + <leafNode name="country-code"> + <properties> + <help>Indicate country in which device is operating</help> + <completionHelp> + <list>00 ad ae af ai al am an ar as at au aw az ba bb bd be bf bg bh bl bm bn bo br bs bt by bz ca cf ch ci cl cn co cr cu cx cy cz de dk dm do dz ec ee eg es et fi fm fr gb gd ge gf gh gl gp gr gt gu gy hk hn hr ht hu id ie il in ir is it jm jo jp ke kh kn kp kr kw ky kz lb lc li lk ls lt lu lv ma mc md me mf mh mk mn mo mp mq mr mt mu mv mw mx my ng ni nl no np nz om pa pe pf pg ph pk pl pm pr pt pw py qa re ro rs ru rw sa se sg si sk sn sr sv sy tc td tg th tn tr tt tw tz ua ug us uy uz vc ve vi vn vu wf ws ye yt za zw</list> + </completionHelp> + <valueHelp> + <format>00</format> + <description>World regulatory domain</description> + </valueHelp> + <valueHelp> + <format>txt</format> + <description>ISO/IEC 3166-1 Country Code</description> + </valueHelp> + <constraint> + <regex>(00|ad|ae|af|ai|al|am|an|ar|as|at|au|aw|az|ba|bb|bd|be|bf|bg|bh|bl|bm|bn|bo|br|bs|bt|by|bz|ca|cf|ch|ci|cl|cn|co|cr|cu|cx|cy|cz|de|dk|dm|do|dz|ec|ee|eg|es|et|fi|fm|fr|gb|gd|ge|gf|gh|gl|gp|gr|gt|gu|gy|hk|hn|hr|ht|hu|id|ie|il|in|ir|is|it|jm|jo|jp|ke|kh|kn|kp|kr|kw|ky|kz|lb|lc|li|lk|ls|lt|lu|lv|ma|mc|md|me|mf|mh|mk|mn|mo|mp|mq|mr|mt|mu|mv|mw|mx|my|ng|ni|nl|no|np|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pr|pt|pw|py|qa|re|ro|rs|ru|rw|sa|se|sg|si|sk|sn|sr|sv|sy|tc|td|tg|th|tn|tr|tt|tw|tz|ua|ug|us|uy|uz|vc|ve|vi|vn|vu|wf|ws|ye|yt|za|zw)</regex> + </constraint> + <constraintErrorMessage>Invalid ISO/IEC 3166-1 Country Code</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in new file mode 100644 index 0000000..d9d6fd9 --- /dev/null +++ b/interface-definitions/vpn_ipsec.xml.in @@ -0,0 +1,1256 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="vpn"> + <properties> + <help>Virtual Private Network (VPN)</help> + </properties> + <children> + <node name="ipsec" owner="${vyos_conf_scripts_dir}/vpn_ipsec.py"> + <properties> + <help>VPN IP security (IPsec) parameters</help> + <priority>901</priority> + </properties> + <children> + <node name="authentication"> + <properties> + <help>Authentication</help> + </properties> + <children> + <tagNode name="psk"> + <properties> + <help>Pre-shared key name</help> + </properties> + <children> + #include <include/dhcp-interface-multi.xml.i> + <leafNode name="id"> + <properties> + <help>ID for authentication</help> + <valueHelp> + <format>txt</format> + <description>ID used for authentication</description> + </valueHelp> + <multi/> + </properties> + </leafNode> + <leafNode name="secret"> + <properties> + <help>IKE pre-shared secret key</help> + <valueHelp> + <format>txt</format> + <description>IKE pre-shared secret key</description> + </valueHelp> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + <leafNode name="disable-uniqreqids"> + <properties> + <help>Disable requirement for unique IDs in the Security Database</help> + <valueless/> + </properties> + </leafNode> + <tagNode name="esp-group"> + <properties> + <help>Encapsulating Security Payload (ESP) group name</help> + </properties> + <children> + <leafNode name="compression"> + <properties> + <help>Enable ESP compression</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="lifetime"> + <properties> + <help>Security Association time to expire</help> + <valueHelp> + <format>u32:30-86400</format> + <description>SA lifetime in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 30-86400"/> + </constraint> + </properties> + <defaultValue>3600</defaultValue> + </leafNode> + <leafNode name="life-bytes"> + <properties> + <help>Security Association byte count to expire</help> + <valueHelp> + <format>u32:1024-26843545600000</format> + <description>SA life in bytes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1024-26843545600000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="life-packets"> + <properties> + <help>Security Association packet count to expire</help> + <valueHelp> + <format>u32:1000-26843545600000</format> + <description>SA life in packets</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1000-26843545600000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="disable-rekey"> + <properties> + <help>Do not locally initiate a re-key of the SA, remote peer must re-key before expiration</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="mode"> + <properties> + <help>ESP mode</help> + <completionHelp> + <list>tunnel transport</list> + </completionHelp> + <valueHelp> + <format>tunnel</format> + <description>Tunnel mode</description> + </valueHelp> + <valueHelp> + <format>transport</format> + <description>Transport mode</description> + </valueHelp> + <constraint> + <regex>(tunnel|transport)</regex> + </constraint> + </properties> + <defaultValue>tunnel</defaultValue> + </leafNode> + <leafNode name="pfs"> + <properties> + <help>ESP Perfect Forward Secrecy</help> + <completionHelp> + <list>enable dh-group1 dh-group2 dh-group5 dh-group14 dh-group15 dh-group16 dh-group17 dh-group18 dh-group19 dh-group20 dh-group21 dh-group22 dh-group23 dh-group24 dh-group25 dh-group26 dh-group27 dh-group28 dh-group29 dh-group30 dh-group31 dh-group32 disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Inherit Diffie-Hellman group from the IKE group</description> + </valueHelp> + <valueHelp> + <format>dh-group1</format> + <description>Use Diffie-Hellman group 1 (modp768)</description> + </valueHelp> + <valueHelp> + <format>dh-group2</format> + <description>Use Diffie-Hellman group 2 (modp1024)</description> + </valueHelp> + <valueHelp> + <format>dh-group5</format> + <description>Use Diffie-Hellman group 5 (modp1536)</description> + </valueHelp> + <valueHelp> + <format>dh-group14</format> + <description>Use Diffie-Hellman group 14 (modp2048)</description> + </valueHelp> + <valueHelp> + <format>dh-group15</format> + <description>Use Diffie-Hellman group 15 (modp3072)</description> + </valueHelp> + <valueHelp> + <format>dh-group16</format> + <description>Use Diffie-Hellman group 16 (modp4096)</description> + </valueHelp> + <valueHelp> + <format>dh-group17</format> + <description>Use Diffie-Hellman group 17 (modp6144)</description> + </valueHelp> + <valueHelp> + <format>dh-group18</format> + <description>Use Diffie-Hellman group 18 (modp8192)</description> + </valueHelp> + <valueHelp> + <format>dh-group19</format> + <description>Use Diffie-Hellman group 19 (ecp256)</description> + </valueHelp> + <valueHelp> + <format>dh-group20</format> + <description>Use Diffie-Hellman group 20 (ecp384)</description> + </valueHelp> + <valueHelp> + <format>dh-group21</format> + <description>Use Diffie-Hellman group 21 (ecp521)</description> + </valueHelp> + <valueHelp> + <format>dh-group22</format> + <description>Use Diffie-Hellman group 22 (modp1024s160)</description> + </valueHelp> + <valueHelp> + <format>dh-group23</format> + <description>Use Diffie-Hellman group 23 (modp2048s224)</description> + </valueHelp> + <valueHelp> + <format>dh-group24</format> + <description>Use Diffie-Hellman group 24 (modp2048s256)</description> + </valueHelp> + <valueHelp> + <format>dh-group25</format> + <description>Use Diffie-Hellman group 25 (ecp192)</description> + </valueHelp> + <valueHelp> + <format>dh-group26</format> + <description>Use Diffie-Hellman group 26 (ecp224)</description> + </valueHelp> + <valueHelp> + <format>dh-group27</format> + <description>Use Diffie-Hellman group 27 (ecp224bp)</description> + </valueHelp> + <valueHelp> + <format>dh-group28</format> + <description>Use Diffie-Hellman group 28 (ecp256bp)</description> + </valueHelp> + <valueHelp> + <format>dh-group29</format> + <description>Use Diffie-Hellman group 29 (ecp384bp)</description> + </valueHelp> + <valueHelp> + <format>dh-group30</format> + <description>Use Diffie-Hellman group 30 (ecp512bp)</description> + </valueHelp> + <valueHelp> + <format>dh-group31</format> + <description>Use Diffie-Hellman group 31 (curve25519)</description> + </valueHelp> + <valueHelp> + <format>dh-group32</format> + <description>Use Diffie-Hellman group 32 (curve448)</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable PFS</description> + </valueHelp> + <constraint> + <regex>(enable|dh-group1|dh-group2|dh-group5|dh-group14|dh-group15|dh-group16|dh-group17|dh-group18|dh-group19|dh-group20|dh-group21|dh-group22|dh-group23|dh-group24|dh-group25|dh-group26|dh-group27|dh-group28|dh-group29|dh-group30|dh-group31|dh-group32|disable)</regex> + </constraint> + </properties> + <defaultValue>enable</defaultValue> + </leafNode> + <tagNode name="proposal"> + <properties> + <help>ESP group proposal</help> + <valueHelp> + <format>u32:1-65535</format> + <description>ESP group proposal number</description> + </valueHelp> + </properties> + <children> + #include <include/vpn-ipsec-encryption.xml.i> + #include <include/vpn-ipsec-hash.xml.i> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="ike-group"> + <properties> + <help>Internet Key Exchange (IKE) group name</help> + </properties> + <children> + <leafNode name="close-action"> + <properties> + <help>Action to take if a child SA is unexpectedly closed</help> + <completionHelp> + <list>none trap start</list> + </completionHelp> + <valueHelp> + <format>none</format> + <description>Do nothing</description> + </valueHelp> + <valueHelp> + <format>trap</format> + <description>Attempt to re-negotiate when matching traffic is seen</description> + </valueHelp> + <valueHelp> + <format>start</format> + <description>Attempt to re-negotiate the connection immediately</description> + </valueHelp> + <constraint> + <regex>(none|trap|start)</regex> + </constraint> + </properties> + <defaultValue>none</defaultValue> + </leafNode> + <node name="dead-peer-detection"> + <properties> + <help>Dead Peer Detection (DPD)</help> + </properties> + <children> + <leafNode name="action"> + <properties> + <help>Keep-alive failure action</help> + <completionHelp> + <list>trap clear restart</list> + </completionHelp> + <valueHelp> + <format>trap</format> + <description>Attempt to re-negotiate the connection when matching traffic is seen</description> + </valueHelp> + <valueHelp> + <format>clear</format> + <description>Remove the connection immediately</description> + </valueHelp> + <valueHelp> + <format>restart</format> + <description>Attempt to re-negotiate the connection immediately</description> + </valueHelp> + <constraint> + <regex>(trap|clear|restart)</regex> + </constraint> + </properties> + <defaultValue>clear</defaultValue> + </leafNode> + <leafNode name="interval"> + <properties> + <help>Keep-alive interval</help> + <valueHelp> + <format>u32:2-86400</format> + <description>Keep-alive interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 2-86400"/> + </constraint> + </properties> + <defaultValue>30</defaultValue> + </leafNode> + <leafNode name="timeout"> + <properties> + <help>Dead Peer Detection keep-alive timeout (IKEv1 only)</help> + <valueHelp> + <format>u32:2-86400</format> + <description>Keep-alive timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 2-86400"/> + </constraint> + </properties> + <defaultValue>120</defaultValue> + </leafNode> + </children> + </node> + <leafNode name="ikev2-reauth"> + <properties> + <help>Re-authentication of the remote peer during an IKE re-key (IKEv2 only)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="key-exchange"> + <properties> + <help>IKE version</help> + <completionHelp> + <list>ikev1 ikev2</list> + </completionHelp> + <valueHelp> + <format>ikev1</format> + <description>Use IKEv1 for key exchange</description> + </valueHelp> + <valueHelp> + <format>ikev2</format> + <description>Use IKEv2 for key exchange</description> + </valueHelp> + <constraint> + <regex>(ikev1|ikev2)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="lifetime"> + <properties> + <help>IKE lifetime</help> + <valueHelp> + <format>u32:0-86400</format> + <description>IKE lifetime in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-86400"/> + </constraint> + </properties> + <defaultValue>28800</defaultValue> + </leafNode> + <leafNode name="disable-mobike"> + <properties> + <help>Disable MOBIKE Support (IKEv2 only)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="mode"> + <properties> + <help>IKEv1 phase 1 mode</help> + <completionHelp> + <list>main aggressive</list> + </completionHelp> + <valueHelp> + <format>main</format> + <description>Use the main mode (recommended)</description> + </valueHelp> + <valueHelp> + <format>aggressive</format> + <description>Use the aggressive mode (insecure, not recommended)</description> + </valueHelp> + <constraint> + <regex>(main|aggressive)</regex> + </constraint> + </properties> + <defaultValue>main</defaultValue> + </leafNode> + <tagNode name="proposal"> + <properties> + <help>IKE proposal</help> + <valueHelp> + <format>u32:1-65535</format> + <description>IKE group proposal</description> + </valueHelp> + </properties> + <children> + <leafNode name="dh-group"> + <properties> + <help>dh-grouphelp</help> + <completionHelp> + <list>1 2 5 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32</list> + </completionHelp> + <valueHelp> + <format>1</format> + <description>Diffie-Hellman group 1 (modp768)</description> + </valueHelp> + <valueHelp> + <format>2</format> + <description>Diffie-Hellman group 2 (modp1024)</description> + </valueHelp> + <valueHelp> + <format>5</format> + <description>Diffie-Hellman group 5 (modp1536)</description> + </valueHelp> + <valueHelp> + <format>14</format> + <description>Diffie-Hellman group 14 (modp2048)</description> + </valueHelp> + <valueHelp> + <format>15</format> + <description>Diffie-Hellman group 15 (modp3072)</description> + </valueHelp> + <valueHelp> + <format>16</format> + <description>Diffie-Hellman group 16 (modp4096)</description> + </valueHelp> + <valueHelp> + <format>17</format> + <description>Diffie-Hellman group 17 (modp6144)</description> + </valueHelp> + <valueHelp> + <format>18</format> + <description>Diffie-Hellman group 18 (modp8192)</description> + </valueHelp> + <valueHelp> + <format>19</format> + <description>Diffie-Hellman group 19 (ecp256)</description> + </valueHelp> + <valueHelp> + <format>20</format> + <description>Diffie-Hellman group 20 (ecp384)</description> + </valueHelp> + <valueHelp> + <format>21</format> + <description>Diffie-Hellman group 21 (ecp521)</description> + </valueHelp> + <valueHelp> + <format>22</format> + <description>Diffie-Hellman group 22 (modp1024s160)</description> + </valueHelp> + <valueHelp> + <format>23</format> + <description>Diffie-Hellman group 23 (modp2048s224)</description> + </valueHelp> + <valueHelp> + <format>24</format> + <description>Diffie-Hellman group 24 (modp2048s256)</description> + </valueHelp> + <valueHelp> + <format>25</format> + <description>Diffie-Hellman group 25 (ecp192)</description> + </valueHelp> + <valueHelp> + <format>26</format> + <description>Diffie-Hellman group 26 (ecp224)</description> + </valueHelp> + <valueHelp> + <format>27</format> + <description>Diffie-Hellman group 27 (ecp224bp)</description> + </valueHelp> + <valueHelp> + <format>28</format> + <description>Diffie-Hellman group 28 (ecp256bp)</description> + </valueHelp> + <valueHelp> + <format>29</format> + <description>Diffie-Hellman group 29 (ecp384bp)</description> + </valueHelp> + <valueHelp> + <format>30</format> + <description>Diffie-Hellman group 30 (ecp512bp)</description> + </valueHelp> + <valueHelp> + <format>31</format> + <description>Diffie-Hellman group 31 (curve25519)</description> + </valueHelp> + <valueHelp> + <format>32</format> + <description>Diffie-Hellman group 32 (curve448)</description> + </valueHelp> + <constraint> + <regex>(1|2|5|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32)</regex> + </constraint> + </properties> + <defaultValue>2</defaultValue> + </leafNode> + <leafNode name="prf"> + <properties> + <help>Pseudo-Random Functions</help> + <completionHelp> + <list>prfmd5 prfsha1 prfaesxcbc prfaescmac prfsha256 prfsha384 prfsha512</list> + </completionHelp> + <valueHelp> + <format>prfmd5</format> + <description>MD5 PRF</description> + </valueHelp> + <valueHelp> + <format>prfsha1</format> + <description>SHA1 PRF</description> + </valueHelp> + <valueHelp> + <format>prfaesxcbc</format> + <description>AES XCBC PRF</description> + </valueHelp> + <valueHelp> + <format>prfaescmac</format> + <description>AES CMAC PRF</description> + </valueHelp> + <valueHelp> + <format>prfsha256</format> + <description>SHA2_256 PRF</description> + </valueHelp> + <valueHelp> + <format>prfsha384</format> + <description>SHA2_384 PRF</description> + </valueHelp> + <valueHelp> + <format>prfsha512</format> + <description>SHA2_512 PRF</description> + </valueHelp> + <constraint> + <regex>(prfmd5|prfsha1|prfaesxcbc|prfaescmac|prfsha256|prfsha384|prfsha512)</regex> + </constraint> + </properties> + </leafNode> + #include <include/vpn-ipsec-encryption.xml.i> + #include <include/vpn-ipsec-hash.xml.i> + </children> + </tagNode> + </children> + </tagNode> + #include <include/generic-interface-multi.xml.i> + <node name="log"> + <properties> + <help>IPsec logging</help> + </properties> + <children> + <leafNode name="level"> + <properties> + <help>Global IPsec logging Level</help> + <valueHelp> + <format>0</format> + <description>Very basic auditing logs (e.g., SA up/SA down)</description> + </valueHelp> + <valueHelp> + <format>1</format> + <description>Generic control flow with errors, a good default to see whats going on</description> + </valueHelp> + <valueHelp> + <format>2</format> + <description>More detailed debugging control flow</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="subsystem"> + <properties> + <help>Subsystem logging levels</help> + <completionHelp> + <list>dmn mgr ike chd job cfg knl net asn enc lib esp tls tnc imc imv pts any</list> + </completionHelp> + <valueHelp> + <format>dmn</format> + <description>Main daemon setup/cleanup/signal handling</description> + </valueHelp> + <valueHelp> + <format>mgr</format> + <description>IKE_SA manager, handling synchronization for IKE_SA access</description> + </valueHelp> + <valueHelp> + <format>ike</format> + <description>IKE_SA/ISAKMP SA</description> + </valueHelp> + <valueHelp> + <format>chd</format> + <description>CHILD_SA/IPsec SA</description> + </valueHelp> + <valueHelp> + <format>job</format> + <description>Jobs queuing/processing and thread pool management</description> + </valueHelp> + <valueHelp> + <format>cfg</format> + <description>Configuration management and plugins</description> + </valueHelp> + <valueHelp> + <format>knl</format> + <description>IPsec/Networking kernel interface</description> + </valueHelp> + <valueHelp> + <format>net</format> + <description>IKE network communication</description> + </valueHelp> + <valueHelp> + <format>asn</format> + <description>Low-level encoding/decoding (ASN.1, X.509 etc.)</description> + </valueHelp> + <valueHelp> + <format>enc</format> + <description>Packet encoding/decoding encryption/decryption operations</description> + </valueHelp> + <valueHelp> + <format>lib</format> + <description>libstrongswan library messages</description> + </valueHelp> + <valueHelp> + <format>esp</format> + <description>libipsec library messages</description> + </valueHelp> + <valueHelp> + <format>tls</format> + <description> libtls library messages</description> + </valueHelp> + <valueHelp> + <format>tnc</format> + <description>Trusted Network Connect</description> + </valueHelp> + <valueHelp> + <format>imc</format> + <description>Integrity Measurement Collector</description> + </valueHelp> + <valueHelp> + <format>imv</format> + <description>Integrity Measurement Verifier</description> + </valueHelp> + <valueHelp> + <format>pts</format> + <description> Platform Trust Service</description> + </valueHelp> + <valueHelp> + <format>any</format> + <description>Any subsystem</description> + </valueHelp> + <constraint> + <regex>(dmn|mgr|ike|chd|job|cfg|knl|net|asn|enc|lib|esp|tls|tnc|imc|imv|pts|any)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + <node name="options"> + <properties> + <help>Global IPsec settings</help> + </properties> + <children> + <leafNode name="disable-route-autoinstall"> + <properties> + <help>Do not automatically install routes to remote networks</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="flexvpn"> + <properties> + <help>Allow FlexVPN vendor ID payload (IKEv2 only)</help> + <valueless/> + </properties> + </leafNode> + #include <include/generic-interface.xml.i> + <leafNode name="virtual-ip"> + <properties> + <help>Allow install virtual-ip addresses</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <tagNode name="profile"> + <properties> + <help>VPN IPsec profile</help> + <valueHelp> + <format>txt</format> + <description>Profile name</description> + </valueHelp> + <constraint> + <regex>[a-zA-Z][0-9a-zA-Z_-]+</regex> + </constraint> + <constraintErrorMessage>Profile name must be alphanumeric and can contain hyphen(s) and underscore(s)</constraintErrorMessage> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <node name="authentication"> + <properties> + <help>Authentication</help> + </properties> + <children> + <leafNode name="mode"> + <properties> + <help>Authentication mode</help> + <completionHelp> + <list>pre-shared-secret</list> + </completionHelp> + <valueHelp> + <format>pre-shared-secret</format> + <description>Use a pre-shared secret key</description> + </valueHelp> + </properties> + </leafNode> + #include <include/ipsec/authentication-pre-shared-secret.xml.i> + </children> + </node> + <node name="bind"> + <properties> + <help>DMVPN tunnel configuration</help> + </properties> + <children> + <leafNode name="tunnel"> + <properties> + <help>Tunnel interface associated with this profile</help> + <completionHelp> + <path>interfaces tunnel</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Associated interface to this profile</description> + </valueHelp> + <multi/> + </properties> + </leafNode> + </children> + </node> + #include <include/ipsec/esp-group.xml.i> + #include <include/ipsec/ike-group.xml.i> + </children> + </tagNode> + <node name="remote-access"> + <properties> + <help>IKEv2 remote access VPN</help> + </properties> + <children> + <tagNode name="connection"> + <properties> + <help>IKEv2 VPN connection name</help> + <valueHelp> + <format>txt</format> + <description>Connection name</description> + </valueHelp> + <constraint> + <regex>[a-zA-Z][0-9a-zA-Z_-]+</regex> + </constraint> + <constraintErrorMessage>Profile name must be alphanumeric and can contain hyphen(s) and underscore(s)</constraintErrorMessage> + </properties> + <children> + <node name="authentication"> + <properties> + <help>Authentication for remote access</help> + </properties> + <children> + #include <include/ipsec/authentication-id.xml.i> + #include <include/ipsec/authentication-x509.xml.i> + <leafNode name="eap-id"> + <properties> + <help>Remote EAP ID for client authentication</help> + <valueHelp> + <format>txt</format> + <description>Remote EAP ID for client authentication</description> + </valueHelp> + <completionHelp> + <list>any</list> + </completionHelp> + <valueHelp> + <format>any</format> + <description>Allow any EAP ID</description> + </valueHelp> + <constraint> + <regex>[[:ascii:]]{1,64}</regex> + </constraint> + </properties> + <defaultValue>any</defaultValue> + </leafNode> + <leafNode name="client-mode"> + <properties> + <help>Client authentication mode</help> + <completionHelp> + <list>x509 eap-tls eap-mschapv2 eap-radius</list> + </completionHelp> + <valueHelp> + <format>x509</format> + <description>Use IPsec x.509 certificate authentication</description> + </valueHelp> + <valueHelp> + <format>eap-tls</format> + <description>Use EAP-TLS authentication</description> + </valueHelp> + <valueHelp> + <format>eap-mschapv2</format> + <description>Use EAP-MSCHAPv2 authentication</description> + </valueHelp> + <valueHelp> + <format>eap-radius</format> + <description>Use EAP-RADIUS authentication</description> + </valueHelp> + <constraint> + <regex>(x509|eap-tls|eap-mschapv2|eap-radius)</regex> + </constraint> + </properties> + <defaultValue>eap-mschapv2</defaultValue> + </leafNode> + #include <include/auth-local-users.xml.i> + <leafNode name="server-mode"> + <properties> + <help>Server authentication mode</help> + <completionHelp> + <list>pre-shared-secret x509</list> + </completionHelp> + <valueHelp> + <format>pre-shared-secret</format> + <description>Use a pre-shared secret key</description> + </valueHelp> + <valueHelp> + <format>x509</format> + <description>Use x.509 certificate</description> + </valueHelp> + <constraint> + <regex>(pre-shared-secret|x509)</regex> + </constraint> + </properties> + <defaultValue>x509</defaultValue> + </leafNode> + #include <include/ipsec/authentication-pre-shared-secret.xml.i> + </children> + </node> + #include <include/generic-description.xml.i> + #include <include/generic-disable-node.xml.i> + #include <include/ipsec/esp-group.xml.i> + #include <include/ipsec/ike-group.xml.i> + #include <include/ipsec/local-address.xml.i> + #include <include/dhcp-interface.xml.i> + #include <include/ipsec/local-traffic-selector.xml.i> + #include <include/ipsec/replay-window.xml.i> + #include <include/ipsec/bind.xml.i> + <leafNode name="timeout"> + <properties> + <help>Timeout to close connection if no data is transmitted</help> + <valueHelp> + <format>u32:0</format> + <description>Disable inactivity checks</description> + </valueHelp> + <valueHelp> + <format>u32:1-86400</format> + <description>Timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-86400"/> + </constraint> + </properties> + <defaultValue>28800</defaultValue> + </leafNode> + <leafNode name="pool"> + <properties> + <help>IP address pool</help> + <completionHelp> + <path>vpn ipsec remote-access pool</path> + <list>dhcp radius</list> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Predefined IP pool name</description> + </valueHelp> + <valueHelp> + <format>dhcp</format> + <description>Forward requests for virtual IP addresses to a DHCP server</description> + </valueHelp> + <valueHelp> + <format>radius</format> + <description>Forward requests for virtual IP addresses to a RADIUS server</description> + </valueHelp> + <multi/> + </properties> + </leafNode> + <leafNode name="unique"> + <properties> + <help>Connection uniqueness enforcement policy</help> + <completionHelp> + <list>never keep replace</list> + </completionHelp> + <valueHelp> + <format>never</format> + <description>Never enforce connection uniqueness</description> + </valueHelp> + <valueHelp> + <format>keep</format> + <description>Reject new connection attempts if the same user already has an active connection</description> + </valueHelp> + <valueHelp> + <format>replace</format> + <description>Delete any existing connection if a new one for the same user gets established</description> + </valueHelp> + <constraint> + <regex>(never|keep|replace)</regex> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + <node name="dhcp"> + <properties> + <help>DHCP pool options for remote access</help> + </properties> + <children> + #include <include/generic-interface.xml.i> + <leafNode name="server"> + <properties> + <help>DHCP server address</help> + <valueHelp> + <format>ipv4</format> + <description>DHCP server IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <tagNode name="pool"> + <properties> + <help>IP address pool for remote access users</help> + </properties> + <children> + <leafNode name="exclude"> + <properties> + <help>Local IPv4 or IPv6 pool prefix exclusions</help> + <valueHelp> + <format>ipv4net</format> + <description>Local IPv4 pool prefix exclusion</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>Local IPv6 pool prefix exclusion</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv6-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="prefix"> + <properties> + <help>Local IPv4 or IPv6 pool prefix</help> + <valueHelp> + <format>ipv4net</format> + <description>Local IPv4 pool prefix</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>Local IPv6 pool prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + <node name="range"> + <properties> + <help>Local IPv4 or IPv6 pool range</help> + </properties> + <children> + <leafNode name="start"> + <properties> + <help>First IP address for local pool range</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 start address of pool</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 start address of pool</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="stop"> + <properties> + <help>Last IP address for local pool range</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 end address of pool</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 end address of pool</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + #include <include/name-server-ipv4-ipv6.xml.i> + </children> + </tagNode> + #include <include/radius-auth-server-ipv4.xml.i> + <node name="radius"> + <children> + #include <include/radius-nas-identifier.xml.i> + #include <include/radius-timeout.xml.i> + <tagNode name="server"> + <children> + #include <include/accel-ppp/radius-additions-disable-accounting.xml.i> + </children> + </tagNode> + </children> + </node> + </children> + </node> + <node name="site-to-site"> + <properties> + <help>Site-to-site VPN</help> + </properties> + <children> + <tagNode name="peer"> + <properties> + <help>Connection name of the peer</help> + <valueHelp> + <format>txt</format> + <description>Connection name of the peer</description> + </valueHelp> + <constraint> + <regex>[-_a-zA-Z0-9|@]+</regex> + </constraint> + <constraintErrorMessage>Peer connection name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <node name="authentication"> + <properties> + <help>Peer authentication</help> + </properties> + <children> + #include <include/ipsec/authentication-id.xml.i> + #include <include/ipsec/authentication-rsa.xml.i> + #include <include/ipsec/authentication-x509.xml.i> + <leafNode name="mode"> + <properties> + <help>Authentication mode</help> + <completionHelp> + <list>pre-shared-secret rsa x509</list> + </completionHelp> + <valueHelp> + <format>pre-shared-secret</format> + <description>Use pre-shared secret key</description> + </valueHelp> + <valueHelp> + <format>rsa</format> + <description>Use RSA key</description> + </valueHelp> + <valueHelp> + <format>x509</format> + <description>Use x.509 certificate</description> + </valueHelp> + <constraint> + <regex>(pre-shared-secret|rsa|x509)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="remote-id"> + <properties> + <help>ID for remote authentication</help> + <valueHelp> + <format>txt</format> + <description>ID used for peer authentication</description> + </valueHelp> + </properties> + <defaultValue>%any</defaultValue> + </leafNode> + <leafNode name="use-x509-id"> + <properties> + <help>Use certificate common name as ID</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="connection-type"> + <properties> + <help>Connection type</help> + <completionHelp> + <list>initiate respond none</list> + </completionHelp> + <valueHelp> + <format>initiate</format> + <description>Bring the connection up immediately</description> + </valueHelp> + <valueHelp> + <format>respond</format> + <description>Wait for the peer to initiate the connection</description> + </valueHelp> + <valueHelp> + <format>none</format> + <description>Load the connection only</description> + </valueHelp> + <constraint> + <regex>(initiate|respond|none)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="default-esp-group"> + <properties> + <help>Defult ESP group name</help> + <completionHelp> + <path>vpn ipsec esp-group</path> + </completionHelp> + </properties> + </leafNode> + #include <include/generic-description.xml.i> + #include <include/dhcp-interface.xml.i> + <leafNode name="force-udp-encapsulation"> + <properties> + <help>Force UDP encapsulation</help> + <valueless/> + </properties> + </leafNode> + #include <include/ipsec/ike-group.xml.i> + <leafNode name="ikev2-reauth"> + <properties> + <help>Re-authentication of the remote peer during an IKE re-key (IKEv2 only)</help> + <completionHelp> + <list>yes no inherit</list> + </completionHelp> + <valueHelp> + <format>yes</format> + <description>Enable remote host re-autentication during an IKE re-key. Currently broken due to a strong swan bug</description> + </valueHelp> + <valueHelp> + <format>no</format> + <description>Disable remote host re-authenticaton during an IKE re-key.</description> + </valueHelp> + <valueHelp> + <format>inherit</format> + <description>Inherit the reauth configuration form your IKE-group</description> + </valueHelp> + <constraint> + <regex>(yes|no|inherit)</regex> + </constraint> + </properties> + </leafNode> + #include <include/ipsec/local-address.xml.i> + #include <include/ipsec/remote-address.xml.i> + #include <include/ipsec/replay-window.xml.i> + <tagNode name="tunnel"> + <properties> + <help>Peer tunnel</help> + <valueHelp> + <format>u32</format> + <description>Peer tunnel</description> + </valueHelp> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/ipsec/esp-group.xml.i> + #include <include/ipsec/local-traffic-selector.xml.i> + #include <include/ip-protocol.xml.i> + <leafNode name="priority"> + <properties> + <help>Priority for IPsec policy (lowest value more preferable)</help> + <valueHelp> + <format>u32:1-100</format> + <description>Priority for IPsec policy (lowest value more preferable)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-100"/> + </constraint> + </properties> + </leafNode> + <node name="remote"> + <properties> + <help>Match remote addresses</help> + </properties> + <children> + #include <include/port-number.xml.i> + <leafNode name="prefix"> + <properties> + <help>Remote IPv4 or IPv6 prefix</help> + <valueHelp> + <format>ipv4net</format> + <description>Remote IPv4 prefix</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>Remote IPv6 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv6-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + <leafNode name="virtual-address"> + <properties> + <help>Initiator request virtual-address from peer</help> + <valueHelp> + <format>ipv4</format> + <description>Request IPv4 address from peer</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Request IPv6 address from peer</description> + </valueHelp> + <multi/> + </properties> + </leafNode> + <node name="vti"> + <properties> + <help>Virtual tunnel interface</help> + </properties> + <children> + #include <include/ipsec/bind.xml.i> + #include <include/ipsec/esp-group.xml.i> + </children> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/vpn_l2tp.xml.in b/interface-definitions/vpn_l2tp.xml.in new file mode 100644 index 0000000..c00e825 --- /dev/null +++ b/interface-definitions/vpn_l2tp.xml.in @@ -0,0 +1,150 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="vpn"> + <children> + <node name="l2tp" owner="${vyos_conf_scripts_dir}/vpn_l2tp.py"> + <properties> + <help>L2TP Virtual Private Network (VPN)</help> + <priority>902</priority> + </properties> + <children> + <node name="remote-access"> + <properties> + <help>Remote access L2TP VPN</help> + </properties> + <children> + <node name="authentication"> + <properties> + <help>Authentication for remote access L2TP VPN</help> + </properties> + <children> + #include <include/accel-ppp/auth-local-users.xml.i> + #include <include/accel-ppp/auth-mode.xml.i> + #include <include/accel-ppp/auth-protocols.xml.i> + #include <include/radius-auth-server-ipv4.xml.i> + #include <include/accel-ppp/radius-additions.xml.i> + <node name="radius"> + <children> + #include <include/accel-ppp/radius-additions-rate-limit.xml.i> + </children> + </node> + </children> + </node> + <node name="ipsec-settings"> + <properties> + <help>Internet Protocol Security (IPsec) for remote access L2TP VPN</help> + </properties> + <children> + <node name="authentication"> + <properties> + <help>IPsec authentication settings</help> + </properties> + <children> + <leafNode name="mode"> + <properties> + <help>Authentication mode for IPsec</help> + <valueHelp> + <format>pre-shared-secret</format> + <description>Use pre-shared secret for IPsec authentication</description> + </valueHelp> + <valueHelp> + <format>x509</format> + <description>Use X.509 certificate for IPsec authentication</description> + </valueHelp> + <constraint> + <regex>(pre-shared-secret|x509)</regex> + </constraint> + <completionHelp> + <list>pre-shared-secret x509</list> + </completionHelp> + </properties> + </leafNode> + #include <include/ipsec/authentication-pre-shared-secret.xml.i> + #include <include/ipsec/authentication-x509.xml.i> + </children> + </node> + <leafNode name="ike-lifetime"> + <properties> + <help>IKE lifetime</help> + <valueHelp> + <format>u32:30-86400</format> + <description>IKE lifetime in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 30-86400"/> + </constraint> + </properties> + <defaultValue>3600</defaultValue> + </leafNode> + <leafNode name="lifetime"> + <properties> + <help>ESP lifetime</help> + <valueHelp> + <format>u32:30-86400</format> + <description>IKE lifetime in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 30-86400"/> + </constraint> + </properties> + <defaultValue>3600</defaultValue> + </leafNode> + #include <include/ipsec/esp-group.xml.i> + #include <include/ipsec/ike-group.xml.i> + </children> + </node> + <node name="lns"> + <properties> + <help>L2TP Network Server (LNS)</help> + </properties> + <children> + <leafNode name="shared-secret"> + <properties> + <help>Tunnel password used to authenticate the client (LAC)</help> + </properties> + </leafNode> + <leafNode name="host-name"> + <properties> + <help>Sent to the client (LAC) in the Host-Name attribute</help> + <constraint> + #include <include/constraint/host-name.xml.i> + </constraint> + <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + <leafNode name="outside-address"> + <properties> + <help>External IP address to which VPN clients will connect</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + #include <include/accel-ppp/client-ip-pool.xml.i> + #include <include/accel-ppp/client-ipv6-pool.xml.i> + #include <include/accel-ppp/default-pool.xml.i> + #include <include/accel-ppp/default-ipv6-pool.xml.i> + #include <include/accel-ppp/extended-scripts.xml.i> + #include <include/accel-ppp/gateway-address.xml.i> + #include <include/accel-ppp/limits.xml.i> + #include <include/accel-ppp/max-concurrent-sessions.xml.i> + #include <include/accel-ppp/mtu-128-16384.xml.i> + <leafNode name="mtu"> + <defaultValue>1436</defaultValue> + </leafNode> + #include <include/accel-ppp/ppp-options.xml.i> + #include <include/accel-ppp/shaper.xml.i> + #include <include/accel-ppp/snmp.xml.i> + #include <include/accel-ppp/wins-server.xml.i> + #include <include/generic-description.xml.i> + #include <include/name-server-ipv4-ipv6.xml.i> + #include <include/accel-ppp/log.xml.i> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/vpn_openconnect.xml.in b/interface-definitions/vpn_openconnect.xml.in new file mode 100644 index 0000000..a2f040b --- /dev/null +++ b/interface-definitions/vpn_openconnect.xml.in @@ -0,0 +1,396 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="vpn"> + <children> + <node name="openconnect" owner="${vyos_conf_scripts_dir}/vpn_openconnect.py"> + <properties> + <help>SSL VPN OpenConnect, AnyConnect compatible server</help> + <priority>901</priority> + </properties> + <children> + <node name="accounting"> + <properties> + <help>Accounting for users OpenConnect VPN Sessions</help> + </properties> + <children> + <node name="mode"> + <properties> + <help>Accounting mode used by this server</help> + </properties> + <children> + <leafNode name="radius"> + <properties> + <help>Use RADIUS server for accounting</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + #include <include/radius-acct-server-ipv4.xml.i> + </children> + </node> + <node name="authentication"> + <properties> + <help>Authentication for remote access SSL VPN Server</help> + </properties> + <children> + <node name="mode"> + <properties> + <help>Authentication mode used by this server</help> + </properties> + <children> + <leafNode name="local"> + <properties> + <help>Use local username/password configuration (OTP supported)</help> + <valueHelp> + <format>password</format> + <description>Password-only local authentication</description> + </valueHelp> + <valueHelp> + <format>otp</format> + <description>OTP-only local authentication</description> + </valueHelp> + <valueHelp> + <format>password-otp</format> + <description>Password (first) + OTP local authentication</description> + </valueHelp> + <constraint> + <regex>(password|otp|password-otp)</regex> + </constraint> + <constraintErrorMessage>Invalid authentication mode. Must be one of: password, otp or password-otp </constraintErrorMessage> + <completionHelp> + <list>otp password password-otp</list> + </completionHelp> + </properties> + </leafNode> + <leafNode name="radius"> + <properties> + <help>Use RADIUS server for user autentication</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="identity-based-config"> + <properties> + <help>Include configuration file by username or RADIUS group attribute</help> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + <leafNode name="mode"> + <properties> + <help>Select per user or per group configuration file - ignored if authentication group is configured</help> + <completionHelp> + <list>user group</list> + </completionHelp> + <valueHelp> + <format>user</format> + <description>Match configuration file on username</description> + </valueHelp> + <valueHelp> + <format>group</format> + <description>Match RADIUS response class attribute as file name</description> + </valueHelp> + <constraint> + <regex>(user|group)</regex> + </constraint> + <constraintErrorMessage>Invalid mode, must be either user or group</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="directory"> + <properties> + <help>Directory to containing configuration files</help> + <valueHelp> + <format>path</format> + <description>Path to configuration directory, must be under /config/auth</description> + </valueHelp> + <constraint> + <validator name="file-path" argument="--directory --parent-dir /config/auth --strict"/> + </constraint> + </properties> + </leafNode> + <leafNode name="default-config"> + <properties> + <help>Default configuration if discrete config could not be found</help> + <valueHelp> + <format>filename</format> + <description>Default configuration filename, must be under /config/auth</description> + </valueHelp> + <constraint> + <validator name="file-path" argument="--file --parent-dir /config/auth --strict"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <leafNode name="group"> + <properties> + <help>Group that a client is allowed to select (from a list). Maps to RADIUS Class attribute.</help> + <valueHelp> + <format>txt</format> + <description>Group string. The group may be followed by a user-friendly name in brackets: group1[First Group]</description> + </valueHelp> + <multi/> + </properties> + </leafNode> + #include <include/auth-local-users.xml.i> + <node name="local-users"> + <children> + <tagNode name="username"> + <children> + <node name="otp"> + <properties> + <help>2FA OTP authentication parameters</help> + </properties> + <children> + <leafNode name="key"> + <properties> + <help>Token Key Secret key for the token algorithm (see RFC 4226)</help> + <valueHelp> + <format>txt</format> + <description>OTP key in hex-encoded format</description> + </valueHelp> + <constraint> + <regex>[a-fA-F0-9]{20,10000}</regex> + </constraint> + <constraintErrorMessage>Key name must only include hex characters and be at least 20 characters long</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="otp-length"> + <properties> + <help>Number of digits in OTP code</help> + <valueHelp> + <format>u32:6-8</format> + <description>Number of digits in OTP code</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 6-8"/> + </constraint> + <constraintErrorMessage>Number of digits in OTP code must be between 6 and 8</constraintErrorMessage> + </properties> + <defaultValue>6</defaultValue> + </leafNode> + <leafNode name="interval"> + <properties> + <help>Time tokens interval in seconds</help> + <valueHelp> + <format>u32:5-86400</format> + <description>Time tokens interval in seconds.</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 5-86400"/> + </constraint> + <constraintErrorMessage>Time token interval must be between 5 and 86400 seconds</constraintErrorMessage> + </properties> + <defaultValue>30</defaultValue> + </leafNode> + <leafNode name="token-type"> + <properties> + <help>Token type</help> + <valueHelp> + <format>hotp-time</format> + <description>Time-based OTP algorithm</description> + </valueHelp> + <valueHelp> + <format>hotp-event</format> + <description>Event-based OTP algorithm</description> + </valueHelp> + <constraint> + <regex>(hotp-time|hotp-event)</regex> + </constraint> + <completionHelp> + <list>hotp-time hotp-event</list> + </completionHelp> + </properties> + <defaultValue>hotp-time</defaultValue> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> + </node> + #include <include/radius-auth-server-ipv4.xml.i> + <node name="radius"> + <children> + #include <include/radius-timeout.xml.i> + <leafNode name="groupconfig"> + <properties> + <help>If the groupconfig option is set, then config-per-user will be overriden, and all configuration will be read from RADIUS.</help> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + #include <include/listen-address-ipv4-single.xml.i> + <leafNode name="listen-address"> + <defaultValue>0.0.0.0</defaultValue> + </leafNode> + <node name="listen-ports"> + <properties> + <help>Specify custom ports to use for client connections</help> + </properties> + <children> + <leafNode name="tcp"> + <properties> + <help>tcp port number to accept connections</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>443</defaultValue> + </leafNode> + <leafNode name="udp"> + <properties> + <help>udp port number to accept connections</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>443</defaultValue> + </leafNode> + </children> + </node> + <leafNode name="http-security-headers"> + <properties> + <help>Enable HTTP security headers</help> + <valueless/> + </properties> + </leafNode> + #include <include/tls-version-min.xml.i> + <leafNode name="tls-version-min"> + <defaultValue>1.2</defaultValue> + </leafNode> + <node name="ssl"> + <properties> + <help>SSL Certificate, SSL Key and CA</help> + </properties> + <children> + #include <include/pki/ca-certificate-multi.xml.i> + #include <include/pki/certificate-key.xml.i> + </children> + </node> + <node name="network-settings"> + <properties> + <help>Network settings</help> + </properties> + <children> + <leafNode name="push-route"> + <properties> + <help>Route to be pushed to the client</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 network and prefix length</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 network and prefix length</description> + </valueHelp> + <constraint> + <validator name="ip-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + <node name="client-ip-settings"> + <properties> + <help>Client IP pools settings</help> + </properties> + <children> + <leafNode name="subnet"> + <properties> + <help>Client IP subnet (CIDR notation)</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + <constraintErrorMessage>Not a valid CIDR formatted prefix</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + <node name="client-ipv6-pool"> + <properties> + <help>Pool of client IPv6 addresses</help> + </properties> + <children> + <leafNode name="prefix"> + <properties> + <help>Pool of addresses used to assign to clients</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + <leafNode name="mask"> + <properties> + <help>Prefix length used for individual client</help> + <valueHelp> + <format>u32:48-128</format> + <description>Client prefix length</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 48-128"/> + </constraint> + </properties> + <defaultValue>64</defaultValue> + </leafNode> + </children> + </node> + #include <include/name-server-ipv4-ipv6.xml.i> + <leafNode name="split-dns"> + <properties> + <help>Domains over which the provided DNS should be used</help> + <valueHelp> + <format>txt</format> + <description>Client prefix length</description> + </valueHelp> + <constraint> + <validator name="fqdn"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="tunnel-all-dns"> + <properties> + <help>If the tunnel-all-dns option is set to yes, tunnel all DNS queries via the VPN. This is the default when a default route is set.</help> + <completionHelp> + <list>yes no</list> + </completionHelp> + <valueHelp> + <format>yes</format> + <description>Enable tunneling of all DNS traffic</description> + </valueHelp> + <valueHelp> + <format>no</format> + <description>Disable tunneling of all DNS traffic</description> + </valueHelp> + <constraint> + <regex>(yes|no)</regex> + </constraint> + </properties> + <defaultValue>no</defaultValue> + </leafNode> + </children> + </node> + </children> + </node> + </children> +</node> +</interfaceDefinition> diff --git a/interface-definitions/vpn_pptp.xml.in b/interface-definitions/vpn_pptp.xml.in new file mode 100644 index 0000000..8aec0cb --- /dev/null +++ b/interface-definitions/vpn_pptp.xml.in @@ -0,0 +1,66 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="vpn"> + <children> + <node name="pptp" owner="${vyos_conf_scripts_dir}/vpn_pptp.py"> + <properties> + <help>Point to Point Tunneling Protocol (PPTP) Virtual Private Network (VPN)</help> + <priority>901</priority> + </properties> + <children> + <node name="remote-access"> + <properties> + <help>Remote access PPTP VPN</help> + </properties> + <children> + <node name="authentication"> + <properties> + <help>Authentication for remote access PPTP VPN</help> + </properties> + <children> + #include <include/accel-ppp/auth-local-users.xml.i> + #include <include/accel-ppp/auth-mode.xml.i> + #include <include/accel-ppp/auth-protocols.xml.i> + #include <include/radius-auth-server-ipv4.xml.i> + #include <include/accel-ppp/radius-additions.xml.i> + <node name="radius"> + <children> + #include <include/accel-ppp/radius-additions-rate-limit.xml.i> + </children> + </node> + </children> + </node> + <leafNode name="outside-address"> + <properties> + <help>External IP address to which VPN clients will connect</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + #include <include/accel-ppp/client-ip-pool.xml.i> + #include <include/accel-ppp/client-ipv6-pool.xml.i> + #include <include/accel-ppp/default-pool.xml.i> + #include <include/accel-ppp/default-ipv6-pool.xml.i> + #include <include/accel-ppp/extended-scripts.xml.i> + #include <include/accel-ppp/gateway-address.xml.i> + #include <include/accel-ppp/limits.xml.i> + #include <include/accel-ppp/max-concurrent-sessions.xml.i> + #include <include/accel-ppp/mtu-128-16384.xml.i> + <leafNode name="mtu"> + <defaultValue>1436</defaultValue> + </leafNode> + #include <include/accel-ppp/ppp-options.xml.i> + #include <include/accel-ppp/shaper.xml.i> + #include <include/accel-ppp/snmp.xml.i> + #include <include/accel-ppp/wins-server.xml.i> + #include <include/generic-description.xml.i> + #include <include/name-server-ipv4-ipv6.xml.i> + #include <include/accel-ppp/log.xml.i> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/vpn_sstp.xml.in b/interface-definitions/vpn_sstp.xml.in new file mode 100644 index 0000000..5fd5c95 --- /dev/null +++ b/interface-definitions/vpn_sstp.xml.in @@ -0,0 +1,70 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="vpn"> + <children> + <node name="sstp" owner="${vyos_conf_scripts_dir}/vpn_sstp.py"> + <properties> + <help>Secure Socket Tunneling Protocol (SSTP) server</help> + <priority>901</priority> + </properties> + <children> + <node name="authentication"> + <properties> + <help>Authentication for remote access SSTP Server</help> + </properties> + <children> + #include <include/accel-ppp/auth-local-users.xml.i> + #include <include/accel-ppp/auth-mode.xml.i> + #include <include/accel-ppp/auth-protocols.xml.i> + #include <include/radius-auth-server-ipv4.xml.i> + #include <include/accel-ppp/radius-additions.xml.i> + <node name="radius"> + <children> + #include <include/accel-ppp/radius-additions-rate-limit.xml.i> + </children> + </node> + </children> + </node> + <node name="ssl"> + <properties> + <help>SSL Certificate, SSL Key and CA</help> + </properties> + <children> + #include <include/pki/ca-certificate.xml.i> + #include <include/pki/certificate.xml.i> + </children> + </node> + #include <include/accel-ppp/client-ip-pool.xml.i> + #include <include/accel-ppp/client-ipv6-pool.xml.i> + #include <include/accel-ppp/default-pool.xml.i> + #include <include/accel-ppp/default-ipv6-pool.xml.i> + #include <include/accel-ppp/extended-scripts.xml.i> + #include <include/accel-ppp/gateway-address.xml.i> + #include <include/accel-ppp/limits.xml.i> + #include <include/accel-ppp/max-concurrent-sessions.xml.i> + #include <include/interface/mtu-68-1500.xml.i> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>443</defaultValue> + </leafNode> + #include <include/accel-ppp/ppp-options.xml.i> + #include <include/accel-ppp/shaper.xml.i> + #include <include/accel-ppp/snmp.xml.i> + #include <include/accel-ppp/wins-server.xml.i> + #include <include/generic-description.xml.i> + #include <include/name-server-ipv4-ipv6.xml.i> + <leafNode name="host-name"> + <properties> + <help>Only allow connection to specified host with the same TLS SNI</help> + <constraint> + #include <include/constraint/host-name.xml.i> + </constraint> + <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage> + </properties> + </leafNode> + #include <include/accel-ppp/log.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in new file mode 100644 index 0000000..a20be99 --- /dev/null +++ b/interface-definitions/vrf.xml.in @@ -0,0 +1,128 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="vrf" owner="${vyos_conf_scripts_dir}/vrf.py"> + <properties> + <help>Virtual Routing and Forwarding</help> + <!-- must be before any interface, check /opt/vyatta/sbin/priority.pl --> + <priority>11</priority> + </properties> + <children> + <leafNode name="bind-to-all"> + <properties> + <help>Enable binding services to all VRFs</help> + <valueless/> + </properties> + </leafNode> + <tagNode name="name"> + <properties> + <help>Virtual Routing and Forwarding instance</help> + #include <include/constraint/vrf.xml.i> + <valueHelp> + <format>txt</format> + <description>VRF instance name</description> + </valueHelp> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/interface/disable.xml.i> + <node name="ip"> + <properties> + <help>IPv4 routing parameters</help> + </properties> + <children> + #include <include/interface/disable-forwarding.xml.i> + #include <include/system-ip-nht.xml.i> + #include <include/system-ip-protocol.xml.i> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 routing parameters</help> + </properties> + <children> + #include <include/interface/disable-forwarding.xml.i> + #include <include/system-ip-nht.xml.i> + #include <include/system-ipv6-protocol.xml.i> + </children> + </node> + <node name="protocols"> + <properties> + <help>Routing protocol parameters</help> + </properties> + <children> + <node name="bgp" owner="${vyos_conf_scripts_dir}/protocols_bgp.py $VAR(../../@)"> + <properties> + <help>Border Gateway Protocol (BGP)</help> + <priority>821</priority> + </properties> + <children> + #include <include/bgp/protocol-common-config.xml.i> + </children> + </node> + <node name="eigrp" owner="${vyos_conf_scripts_dir}/protocols_eigrp.py $VAR(../../@)"> + <properties> + <help>Enhanced Interior Gateway Routing Protocol (EIGRP)</help> + <priority>821</priority> + </properties> + <children> + #include <include/eigrp/protocol-common-config.xml.i> + </children> + </node> + <node name="isis" owner="${vyos_conf_scripts_dir}/protocols_isis.py $VAR(../../@)"> + <properties> + <help>Intermediate System to Intermediate System (IS-IS)</help> + <priority>611</priority> + </properties> + <children> + #include <include/isis/protocol-common-config.xml.i> + </children> + </node> + <node name="ospf" owner="${vyos_conf_scripts_dir}/protocols_ospf.py $VAR(../../@)"> + <properties> + <help>Open Shortest Path First (OSPF)</help> + <priority>621</priority> + </properties> + <children> + #include <include/ospf/protocol-common-config.xml.i> + </children> + </node> + <node name="ospfv3" owner="${vyos_conf_scripts_dir}/protocols_ospfv3.py $VAR(../../@)"> + <properties> + <help>Open Shortest Path First (OSPF) for IPv6</help> + <priority>621</priority> + </properties> + <children> + #include <include/ospfv3/protocol-common-config.xml.i> + </children> + </node> + <node name="static" owner="${vyos_conf_scripts_dir}/protocols_static.py $VAR(../../@)"> + <properties> + <help>Static Routing</help> + <priority>481</priority> + </properties> + <children> + #include <include/static/static-route.xml.i> + #include <include/static/static-route6.xml.i> + </children> + </node> + </children> + </node> + <leafNode name="table"> + <properties> + <help>Routing table associated with this instance</help> + <valueHelp> + <format>u32:100-65535</format> + <description>Routing table ID</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 100-65535"/> + </constraint> + <constraintErrorMessage>VRF routing table must be in range from 100 to 65535</constraintErrorMessage> + </properties> + </leafNode> + #include <include/vni.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/xml-component-version.xml.in b/interface-definitions/xml-component-version.xml.in new file mode 100644 index 0000000..67d86a1 --- /dev/null +++ b/interface-definitions/xml-component-version.xml.in @@ -0,0 +1,52 @@ +<?xml version="1.0"?> +<interfaceDefinition> + #include <include/version/bgp-version.xml.i> + #include <include/version/broadcast-relay-version.xml.i> + #include <include/version/cluster-version.xml.i> + #include <include/version/config-management-version.xml.i> + #include <include/version/conntrack-sync-version.xml.i> + #include <include/version/conntrack-version.xml.i> + #include <include/version/container-version.xml.i> + #include <include/version/dhcp-relay-version.xml.i> + #include <include/version/dhcp-server-version.xml.i> + #include <include/version/dhcpv6-server-version.xml.i> + #include <include/version/dns-dynamic-version.xml.i> + #include <include/version/dns-forwarding-version.xml.i> + #include <include/version/firewall-version.xml.i> + #include <include/version/flow-accounting-version.xml.i> + #include <include/version/https-version.xml.i> + #include <include/version/interfaces-version.xml.i> + #include <include/version/ids-version.xml.i> + #include <include/version/ipoe-server-version.xml.i> + #include <include/version/ipsec-version.xml.i> + #include <include/version/openvpn-version.xml.i> + #include <include/version/isis-version.xml.i> + #include <include/version/l2tp-version.xml.i> + #include <include/version/lldp-version.xml.i> + #include <include/version/mdns-version.xml.i> + #include <include/version/monitoring-version.xml.i> + #include <include/version/nat66-version.xml.i> + #include <include/version/nat-version.xml.i> + #include <include/version/ntp-version.xml.i> + #include <include/version/openconnect-version.xml.i> + #include <include/version/ospf-version.xml.i> + #include <include/version/pim-version.xml.i> + #include <include/version/policy-version.xml.i> + #include <include/version/pppoe-server-version.xml.i> + #include <include/version/pptp-version.xml.i> + #include <include/version/qos-version.xml.i> + #include <include/version/quagga-version.xml.i> + #include <include/version/rip-version.xml.i> + #include <include/version/rpki-version.xml.i> + #include <include/version/salt-version.xml.i> + #include <include/version/snmp-version.xml.i> + #include <include/version/ssh-version.xml.i> + #include <include/version/sstp-version.xml.i> + #include <include/version/system-version.xml.i> + #include <include/version/vrf-version.xml.i> + #include <include/version/vrrp-version.xml.i> + #include <include/version/vyos-accel-ppp-version.xml.i> + #include <include/version/wanloadbalance-version.xml.i> + #include <include/version/webproxy-version.xml.i> + #include <include/version/reverseproxy-version.xml.i> +</interfaceDefinition> diff --git a/mibs/AGENTX-MIB.txt b/mibs/AGENTX-MIB.txt new file mode 100644 index 0000000..f9e5acd --- /dev/null +++ b/mibs/AGENTX-MIB.txt @@ -0,0 +1,527 @@ +AGENTX-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, Unsigned32, mib-2 + FROM SNMPv2-SMI + SnmpAdminString + FROM SNMP-FRAMEWORK-MIB + MODULE-COMPLIANCE, OBJECT-GROUP + FROM SNMPv2-CONF + TEXTUAL-CONVENTION, TimeStamp, TruthValue, TDomain + FROM SNMPv2-TC; +agentxMIB MODULE-IDENTITY + LAST-UPDATED "200001100000Z" -- Midnight 10 January 2000 + ORGANIZATION "AgentX Working Group" + CONTACT-INFO "WG-email: agentx@dorothy.bmc.com + Subscribe: agentx-request@dorothy.bmc.com + WG-email Archive: ftp://ftp.peer.com/pub/agentx/archives + FTP repository: ftp://ftp.peer.com/pub/agentx + http://www.ietf.org/html.charters/agentx-charter.html + + Chair: Bob Natale + ACE*COMM Corporation + Email: bnatale@acecomm.com + + WG editor: Mark Ellison + Ellison Software Consulting, Inc. + Email: ellison@world.std.com + + Co-author: Lauren Heintz + Cisco Systems, + EMail: lheintz@cisco.com + + Co-author: Smitha Gudur + Independent Consultant + Email: sgudur@hotmail.com + " + DESCRIPTION "This is the MIB module for the SNMP Agent Extensibility + Protocol (AgentX). This MIB module will be implemented by + the master agent. + " + + REVISION "200001100000Z" -- Midnight 10 January 2000 + DESCRIPTION + "Initial version published as RFC 2742." + ::= { mib-2 74 } + + -- Textual Conventions + + AgentxTAddress ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Denotes a transport service address. This is identical to + the TAddress textual convention (SNMPv2-SMI) except that + zero-length values are permitted. + " + SYNTAX OCTET STRING (SIZE (0..255)) + + -- Administrative assignments + + agentxObjects OBJECT IDENTIFIER ::= { agentxMIB 1 } + agentxGeneral OBJECT IDENTIFIER ::= { agentxObjects 1 } + agentxConnection OBJECT IDENTIFIER ::= { agentxObjects 2 } + agentxSession OBJECT IDENTIFIER ::= { agentxObjects 3 } + agentxRegistration OBJECT IDENTIFIER ::= { agentxObjects 4 } + + agentxDefaultTimeout OBJECT-TYPE + SYNTAX INTEGER (0..255) + UNITS "seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The default length of time, in seconds, that the master + agent should allow to elapse after dispatching a message + to a session before it regards the subagent as not + responding. This is a system-wide value that may + override the timeout value associated with a particular + session (agentxSessionTimeout) or a particular registered + MIB region (agentxRegTimeout). If the associated value of + agentxSessionTimeout and agentxRegTimeout are zero, or + impractical in accordance with implementation-specific + procedure of the master agent, the value represented by + this object will be the effective timeout value for the + + master agent to await a response to a dispatch from a + given subagent. + " + DEFVAL { 5 } + ::= { agentxGeneral 1 } + + agentxMasterAgentXVer OBJECT-TYPE + SYNTAX INTEGER (1..255) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The AgentX protocol version supported by this master agent. + The current protocol version is 1. Note that the master agent + must also allow interaction with earlier version subagents. + " + ::= { agentxGeneral 2 } + + -- The AgentX Subagent Connection Group + + agentxConnTableLastChange OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime when the last row creation or deletion + occurred in the agentxConnectionTable. + " + ::= { agentxConnection 1 } + + agentxConnectionTable OBJECT-TYPE + SYNTAX SEQUENCE OF AgentxConnectionEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The agentxConnectionTable tracks all current AgentX transport + connections. There may be zero, one, or more AgentX sessions + carried on a given AgentX connection. + " + ::= { agentxConnection 2 } + + agentxConnectionEntry OBJECT-TYPE + SYNTAX AgentxConnectionEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An agentxConnectionEntry contains information describing a + single AgentX transport connection. A connection may be + + used to support zero or more AgentX sessions. An entry is + created when a new transport connection is established, + and is destroyed when the transport connection is terminated. + " + INDEX { agentxConnIndex } + ::= { agentxConnectionTable 1 } + + AgentxConnectionEntry ::= SEQUENCE { + agentxConnIndex Unsigned32, + agentxConnOpenTime TimeStamp, + agentxConnTransportDomain TDomain, + agentxConnTransportAddress AgentxTAddress } + + agentxConnIndex OBJECT-TYPE + SYNTAX Unsigned32 (1..4294967295) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "agentxConnIndex contains the value that uniquely identifies + an open transport connection used by this master agent + to provide AgentX service. Values of this index should + not be re-used. The value assigned to a given transport + connection is constant for the lifetime of that connection. + " + ::= { agentxConnectionEntry 1 } + + agentxConnOpenTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime when this connection was established + and, therefore, its value when this entry was added to the table. + " + ::= { agentxConnectionEntry 2 } + + agentxConnTransportDomain OBJECT-TYPE + SYNTAX TDomain + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The transport protocol in use for this connection to the + subagent. + " + ::= { agentxConnectionEntry 3 } + + agentxConnTransportAddress OBJECT-TYPE + SYNTAX AgentxTAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The transport address of the remote (subagent) end of this + connection to the master agent. This object may be zero-length + for unix-domain sockets (and possibly other types of transport + addresses) since the subagent need not bind a filename to its + local socket. + " + ::= { agentxConnectionEntry 4 } + + -- The AgentX Subagent Session Group + + agentxSessionTableLastChange OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime when the last row creation or deletion + occurred in the agentxSessionTable. + " + ::= { agentxSession 1 } + + agentxSessionTable OBJECT-TYPE + SYNTAX SEQUENCE OF AgentxSessionEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of AgentX subagent sessions currently in effect. + " + ::= { agentxSession 2 } + + agentxSessionEntry OBJECT-TYPE + SYNTAX AgentxSessionEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a single open session between the AgentX + master agent and a subagent is contained in this entry. An + entry is created when a new session is successfully established + and is destroyed either when the subagent transport connection + has terminated or when the subagent session is closed. + " + INDEX { agentxConnIndex, agentxSessionIndex } + ::= { agentxSessionTable 1 } + + AgentxSessionEntry ::= SEQUENCE { + agentxSessionIndex Unsigned32, + agentxSessionObjectID OBJECT IDENTIFIER, + agentxSessionDescr SnmpAdminString, + agentxSessionAdminStatus INTEGER, + agentxSessionOpenTime TimeStamp, + agentxSessionAgentXVer INTEGER, + agentxSessionTimeout INTEGER + } + + agentxSessionIndex OBJECT-TYPE + SYNTAX Unsigned32 (0..4294967295) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A unique index for the subagent session. It is the same as + h.sessionID defined in the agentx header. Note that if + a subagent's session with the master agent is closed for + any reason its index should not be re-used. + A value of zero(0) is specifically allowed in order + to be compatible with the definition of h.sessionId. + " + ::= { agentxSessionEntry 1 } + + agentxSessionObjectID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This is taken from the o.id field of the agentx-Open-PDU. + This attribute will report a value of '0.0' for subagents + not supporting the notion of an AgentX session object + identifier. + " + ::= { agentxSessionEntry 2 } + + agentxSessionDescr OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A textual description of the session. This is analogous to + sysDescr defined in the SNMPv2-MIB in RFC 1907 [19] and is + taken from the o.descr field of the agentx-Open-PDU. + This attribute will report a zero-length string value for + subagents not supporting the notion of a session description. + " + ::= { agentxSessionEntry 3 } + + agentxSessionAdminStatus OBJECT-TYPE + SYNTAX INTEGER { + up(1), + down(2) + } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The administrative (desired) status of the session. Setting + the value to 'down(2)' closes the subagent session (with c.reason + set to 'reasonByManager'). + " + ::= { agentxSessionEntry 4 } + + agentxSessionOpenTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime when this session was opened and, + therefore, its value when this entry was added to the table. + " + ::= { agentxSessionEntry 5 } + + agentxSessionAgentXVer OBJECT-TYPE + SYNTAX INTEGER (1..255) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The version of the AgentX protocol supported by the + session. This must be less than or equal to the value of + agentxMasterAgentXVer. + " + ::= { agentxSessionEntry 6 } + + agentxSessionTimeout OBJECT-TYPE + SYNTAX INTEGER (0..255) + UNITS "seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The length of time, in seconds, that a master agent should + allow to elapse after dispatching a message to this session + before it regards the subagent as not responding. This value + is taken from the o.timeout field of the agentx-Open-PDU. + This is a session-specific value that may be overridden by + values associated with the specific registered MIB regions + (see agentxRegTimeout). A value of zero(0) indicates that + the master agent's default timeout value should be used + + (see agentxDefaultTimeout). + " + ::= { agentxSessionEntry 7 } + + -- The AgentX Registration Group + + agentxRegistrationTableLastChange OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime when the last row creation or deletion + occurred in the agentxRegistrationTable. + " + ::= { agentxRegistration 1 } + + agentxRegistrationTable OBJECT-TYPE + SYNTAX SEQUENCE OF AgentxRegistrationEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of registered regions. + " + ::= { agentxRegistration 2 } + + agentxRegistrationEntry OBJECT-TYPE + SYNTAX AgentxRegistrationEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Contains information for a single registered region. An + entry is created when a session successfully registers a + region and is destroyed for any of three reasons: this region + is unregistered by the session, the session is closed, + or the subagent connection is closed. + " + INDEX { agentxConnIndex, agentxSessionIndex, agentxRegIndex } + ::= { agentxRegistrationTable 1 } + + AgentxRegistrationEntry ::= SEQUENCE { + agentxRegIndex Unsigned32, + agentxRegContext OCTET STRING, + agentxRegStart OBJECT IDENTIFIER, + agentxRegRangeSubId Unsigned32, + agentxRegUpperBound Unsigned32, + agentxRegPriority Unsigned32, + agentxRegTimeout INTEGER, + agentxRegInstance TruthValue } + + agentxRegIndex OBJECT-TYPE + SYNTAX Unsigned32 (1..4294967295) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "agentxRegIndex uniquely identifies a registration entry. + This value is constant for the lifetime of an entry. + " + ::= { agentxRegistrationEntry 1 } + + agentxRegContext OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The context in which the session supports the objects in this + region. A zero-length context indicates the default context. + " + ::= { agentxRegistrationEntry 2 } + + agentxRegStart OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The starting OBJECT IDENTIFIER of this registration entry. The + session identified by agentxSessionIndex implements objects + starting at this value (inclusive). Note that this value could + identify an object type, an object instance, or a partial object + instance. + " + ::= { agentxRegistrationEntry 3 } + + agentxRegRangeSubId OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "agentxRegRangeSubId is used to specify the range. This is + taken from r.region_subid in the registration PDU. If the value + of this object is zero, no range is specified. If it is non-zero, + it identifies the `nth' sub-identifier in r.region for which + this entry's agentxRegUpperBound value is substituted in the + OID for purposes of defining the region's upper bound. + " + ::= { agentxRegistrationEntry 4 } + + agentxRegUpperBound OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "agentxRegUpperBound represents the upper-bound sub-identifier in + a registration. This is taken from the r.upper_bound in the + registration PDU. If agentxRegRangeSubid (r.region_subid) is + zero, this value is also zero and is not used to define an upper + bound for this registration. + " + ::= { agentxRegistrationEntry 5 } + + agentxRegPriority OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The registration priority. Lower values have higher priority. + This value is taken from r.priority in the register PDU. + Sessions should use the value of 127 for r.priority if a + default value is desired. + " + ::= { agentxRegistrationEntry 6 } + + agentxRegTimeout OBJECT-TYPE + SYNTAX INTEGER (0..255) + UNITS "seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The timeout value, in seconds, for responses to + requests associated with this registered MIB region. + A value of zero(0) indicates the default value (indicated + by by agentxSessionTimeout or agentxDefaultTimeout) is to + be used. This value is taken from the r.timeout field of + the agentx-Register-PDU. + " + ::= { agentxRegistrationEntry 7 } + + agentxRegInstance OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of agentxRegInstance is `true' for + registrations for which the INSTANCE_REGISTRATION + was set, and is `false' for all other registrations. + " + ::= { agentxRegistrationEntry 8 } + + -- Conformance Statements for AgentX + + agentxConformance OBJECT IDENTIFIER ::= { agentxMIB 2 } + agentxMIBGroups OBJECT IDENTIFIER ::= { agentxConformance 1 } + agentxMIBCompliances OBJECT IDENTIFIER ::= { agentxConformance 2 } + + -- Compliance Statements for AgentX + + agentxMIBCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP entities that implement the + AgentX protocol. Note that a compliant agent can implement all + objects in this MIB module as read-only. + " + MODULE -- this module + MANDATORY-GROUPS { agentxMIBGroup } + + OBJECT agentxSessionAdminStatus + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. + " + ::= { agentxMIBCompliances 1 } + + agentxMIBGroup OBJECT-GROUP + OBJECTS { + agentxDefaultTimeout, + agentxMasterAgentXVer, + agentxConnTableLastChange, + agentxConnOpenTime, + agentxConnTransportDomain, + agentxConnTransportAddress, + agentxSessionTableLastChange, + agentxSessionTimeout, + agentxSessionObjectID, + agentxSessionDescr, + agentxSessionAdminStatus, + agentxSessionOpenTime, + agentxSessionAgentXVer, + agentxRegistrationTableLastChange, + agentxRegContext, + agentxRegStart, + agentxRegRangeSubId, + agentxRegUpperBound, + agentxRegPriority, + agentxRegTimeout, + agentxRegInstance + } + STATUS current + DESCRIPTION + "All accessible objects in the AgentX MIB. + " + ::= { agentxMIBGroups 1 } + + END diff --git a/mibs/BGP4-MIB.txt b/mibs/BGP4-MIB.txt new file mode 100644 index 0000000..c911316 --- /dev/null +++ b/mibs/BGP4-MIB.txt @@ -0,0 +1,929 @@ + BGP4-MIB DEFINITIONS ::= BEGIN + + IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE, + IpAddress, Integer32, Counter32, Gauge32, mib-2 + FROM SNMPv2-SMI + MODULE-COMPLIANCE, OBJECT-GROUP, NOTIFICATION-GROUP + FROM SNMPv2-CONF; + + bgp MODULE-IDENTITY + LAST-UPDATED "9902100000Z" + ORGANIZATION "IETF IDR Working Group" + CONTACT-INFO "E-mail: idr@merit.net + + Susan Hares (Editor) + Merit Network + 4251 Plymouth Road + Suite C + Ann Arbor, MI 48105-2785 + Tel: +1 734 936 2095 + Fax: +1 734 647 3185 + E-mail: skh@merit.edu + + Jeff Johnson (Editor) + RedBack Networks, Inc. + 1389 Moffett Park Drive + Sunnyvale, CA 94089-1134 + Tel: +1 408 548 3516 + Fax: +1 408 548 3599 + E-mail: jeff@redback.com" + DESCRIPTION + "The MIB module for BGP-4." + REVISION "9902100000Z" + DESCRIPTION + "Corrected duplicate OBJECT IDENTIFIER + assignment in the conformance information." + REVISION "9601080000Z" + DESCRIPTION + "1) Fixed the definitions of the traps to + make them equivalent to their initial + definition in RFC 1269. + 2) Added compliance and conformance info." + ::= { mib-2 15 } + + bgpVersion OBJECT-TYPE + SYNTAX OCTET STRING (SIZE (1..255)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Vector of supported BGP protocol version + numbers. Each peer negotiates the version + from this vector. Versions are identified + via the string of bits contained within this + object. The first octet contains bits 0 to + 7, the second octet contains bits 8 to 15, + and so on, with the most significant bit + referring to the lowest bit number in the + octet (e.g., the MSB of the first octet + refers to bit 0). If a bit, i, is present + and set, then the version (i+1) of the BGP + is supported." + ::= { bgp 1 } + + bgpLocalAs OBJECT-TYPE + SYNTAX INTEGER (0..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The local autonomous system number." + ::= { bgp 2 } + + + + -- BGP Peer table. This table contains, one entry per BGP + -- peer, information about the BGP peer. + + bgpPeerTable OBJECT-TYPE + SYNTAX SEQUENCE OF BgpPeerEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "BGP peer table. This table contains, + one entry per BGP peer, information about the + connections with BGP peers." + ::= { bgp 3 } + + bgpPeerEntry OBJECT-TYPE + SYNTAX BgpPeerEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Entry containing information about the + connection with a BGP peer." + INDEX { bgpPeerRemoteAddr } + ::= { bgpPeerTable 1 } + + BgpPeerEntry ::= SEQUENCE { + bgpPeerIdentifier + IpAddress, + bgpPeerState + INTEGER, + bgpPeerAdminStatus + INTEGER, + bgpPeerNegotiatedVersion + Integer32, + bgpPeerLocalAddr + IpAddress, + bgpPeerLocalPort + INTEGER, + bgpPeerRemoteAddr + IpAddress, + bgpPeerRemotePort + INTEGER, + bgpPeerRemoteAs + INTEGER, + bgpPeerInUpdates + Counter32, + bgpPeerOutUpdates + Counter32, + bgpPeerInTotalMessages + Counter32, + bgpPeerOutTotalMessages + Counter32, + bgpPeerLastError + OCTET STRING, + bgpPeerFsmEstablishedTransitions + Counter32, + bgpPeerFsmEstablishedTime + Gauge32, + bgpPeerConnectRetryInterval + INTEGER, + bgpPeerHoldTime + INTEGER, + bgpPeerKeepAlive + INTEGER, + bgpPeerHoldTimeConfigured + INTEGER, + bgpPeerKeepAliveConfigured + INTEGER, + bgpPeerMinASOriginationInterval + INTEGER, + bgpPeerMinRouteAdvertisementInterval + INTEGER, + bgpPeerInUpdateElapsedTime + Gauge32 + } + + bgpPeerIdentifier OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The BGP Identifier of this entry's BGP peer." + ::= { bgpPeerEntry 1 } + + bgpPeerState OBJECT-TYPE + SYNTAX INTEGER { + idle(1), + connect(2), + active(3), + opensent(4), + openconfirm(5), + established(6) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The BGP peer connection state." + ::= { bgpPeerEntry 2 } + + bgpPeerAdminStatus OBJECT-TYPE + SYNTAX INTEGER { + stop(1), + start(2) + } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The desired state of the BGP connection. A + transition from 'stop' to 'start' will cause + the BGP Start Event to be generated. A + transition from 'start' to 'stop' will cause + the BGP Stop Event to be generated. This + parameter can be used to restart BGP peer + connections. Care should be used in providing + write access to this object without adequate + authentication." + ::= { bgpPeerEntry 3 } + + bgpPeerNegotiatedVersion OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The negotiated version of BGP running between + the two peers." + ::= { bgpPeerEntry 4 } + + bgpPeerLocalAddr OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The local IP address of this entry's BGP + connection." + ::= { bgpPeerEntry 5 } + + bgpPeerLocalPort OBJECT-TYPE + SYNTAX INTEGER (0..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The local port for the TCP connection between + the BGP peers." + ::= { bgpPeerEntry 6 } + + bgpPeerRemoteAddr OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The remote IP address of this entry's BGP + peer." + ::= { bgpPeerEntry 7 } + + bgpPeerRemotePort OBJECT-TYPE + SYNTAX INTEGER (0..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The remote port for the TCP connection between + the BGP peers. Note that the objects + bgpPeerLocalAddr, bgpPeerLocalPort, + bgpPeerRemoteAddr and bgpPeerRemotePort + provide the appropriate reference to the + standard MIB TCP connection table." + ::= { bgpPeerEntry 8 } + + bgpPeerRemoteAs OBJECT-TYPE + SYNTAX INTEGER (0..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The remote autonomous system number." + ::= { bgpPeerEntry 9 } + + bgpPeerInUpdates OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of BGP UPDATE messages received on + this connection. This object should be + initialized to zero (0) when the connection is + established." + ::= { bgpPeerEntry 10 } + + bgpPeerOutUpdates OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of BGP UPDATE messages transmitted + on this connection. This object should be + initialized to zero (0) when the connection is + established." + ::= { bgpPeerEntry 11 } + + bgpPeerInTotalMessages OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of messages received from the + remote peer on this connection. This object + should be initialized to zero when the + connection is established." + ::= { bgpPeerEntry 12 } + + bgpPeerOutTotalMessages OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of messages transmitted to + the remote peer on this connection. This object + should be initialized to zero when the + connection is established." + ::= { bgpPeerEntry 13 } + + bgpPeerLastError OBJECT-TYPE + SYNTAX OCTET STRING (SIZE (2)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The last error code and subcode seen by this + peer on this connection. If no error has + occurred, this field is zero. Otherwise, the + first byte of this two byte OCTET STRING + contains the error code, and the second byte + contains the subcode." + ::= { bgpPeerEntry 14 } + + bgpPeerFsmEstablishedTransitions OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of times the BGP FSM + transitioned into the established state." + ::= { bgpPeerEntry 15 } + + bgpPeerFsmEstablishedTime OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This timer indicates how long (in seconds) this + peer has been in the Established state or how long + since this peer was last in the Established state. + It is set to zero when a new peer is configured or + the router is booted." + ::= { bgpPeerEntry 16 } + + bgpPeerConnectRetryInterval OBJECT-TYPE + SYNTAX INTEGER (1..65535) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "Time interval in seconds for the ConnectRetry + timer. The suggested value for this timer is + 120 seconds." + ::= { bgpPeerEntry 17 } + + bgpPeerHoldTime OBJECT-TYPE + SYNTAX INTEGER ( 0 | 3..65535 ) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Time interval in seconds for the Hold Timer + established with the peer. The value of this + object is calculated by this BGP speaker by + using the smaller of the value in + bgpPeerHoldTimeConfigured and the Hold Time + received in the OPEN message. This value + must be at lease three seconds if it is not + zero (0) in which case the Hold Timer has + not been established with the peer, or, the + value of bgpPeerHoldTimeConfigured is zero (0)." + ::= { bgpPeerEntry 18 } + + bgpPeerKeepAlive OBJECT-TYPE + SYNTAX INTEGER ( 0 | 1..21845 ) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Time interval in seconds for the KeepAlive + timer established with the peer. The value of + this object is calculated by this BGP speaker + such that, when compared with bgpPeerHoldTime, + it has the same proportion as what + bgpPeerKeepAliveConfigured has when compared + with bgpPeerHoldTimeConfigured. If the value + of this object is zero (0), it indicates that + the KeepAlive timer has not been established + with the peer, or, the value of + bgpPeerKeepAliveConfigured is zero (0)." + ::= { bgpPeerEntry 19 } + + bgpPeerHoldTimeConfigured OBJECT-TYPE + SYNTAX INTEGER ( 0 | 3..65535 ) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "Time interval in seconds for the Hold Time + configured for this BGP speaker with this peer. + This value is placed in an OPEN message sent to + this peer by this BGP speaker, and is compared + with the Hold Time field in an OPEN message + received from the peer when determining the Hold + Time (bgpPeerHoldTime) with the peer. This value + must not be less than three seconds if it is not + zero (0) in which case the Hold Time is NOT to be + established with the peer. The suggested value for + this timer is 90 seconds." + ::= { bgpPeerEntry 20 } + + bgpPeerKeepAliveConfigured OBJECT-TYPE + SYNTAX INTEGER ( 0 | 1..21845 ) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "Time interval in seconds for the KeepAlive timer + configured for this BGP speaker with this peer. + The value of this object will only determine the + KEEPALIVE messages' frequency relative to the value + specified in bgpPeerHoldTimeConfigured; the actual + time interval for the KEEPALIVE messages is + indicated by bgpPeerKeepAlive. A reasonable + maximum value for this timer would be configured to + be one third of that of bgpPeerHoldTimeConfigured. + If the value of this object is zero (0), no + periodical KEEPALIVE messages are sent to the peer + after the BGP connection has been established. The + suggested value for this timer is 30 seconds." + ::= { bgpPeerEntry 21 } + + bgpPeerMinASOriginationInterval OBJECT-TYPE + SYNTAX INTEGER (1..65535) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "Time interval in seconds for the + MinASOriginationInterval timer. + The suggested value for this timer is 15 seconds." + ::= { bgpPeerEntry 22 } + + bgpPeerMinRouteAdvertisementInterval OBJECT-TYPE + SYNTAX INTEGER (1..65535) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "Time interval in seconds for the + MinRouteAdvertisementInterval timer. + The suggested value for this timer is 30 seconds." + ::= { bgpPeerEntry 23 } + + bgpPeerInUpdateElapsedTime OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Elapsed time in seconds since the last BGP + UPDATE message was received from the peer. + Each time bgpPeerInUpdates is incremented, + the value of this object is set to zero (0)." + ::= { bgpPeerEntry 24 } + + + + bgpIdentifier OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The BGP Identifier of local system." + ::= { bgp 4 } + + + + -- Received Path Attribute Table. This table contains, + -- one entry per path to a network, path attributes + -- received from all peers running BGP version 3 or less. + -- This table is obsolete, having been replaced in + -- functionality with the bgp4PathAttrTable. + + bgpRcvdPathAttrTable OBJECT-TYPE + SYNTAX SEQUENCE OF BgpPathAttrEntry + MAX-ACCESS not-accessible + STATUS obsolete + DESCRIPTION + "The BGP Received Path Attribute Table contains + information about paths to destination networks + received from all peers running BGP version 3 or + less." + ::= { bgp 5 } + + bgpPathAttrEntry OBJECT-TYPE + SYNTAX BgpPathAttrEntry + MAX-ACCESS not-accessible + STATUS obsolete + DESCRIPTION + "Information about a path to a network." + INDEX { bgpPathAttrDestNetwork, + bgpPathAttrPeer } + ::= { bgpRcvdPathAttrTable 1 } + + BgpPathAttrEntry ::= SEQUENCE { + bgpPathAttrPeer + IpAddress, + bgpPathAttrDestNetwork + IpAddress, + bgpPathAttrOrigin + INTEGER, + bgpPathAttrASPath + OCTET STRING, + bgpPathAttrNextHop + IpAddress, + bgpPathAttrInterASMetric + Integer32 + } + + bgpPathAttrPeer OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The IP address of the peer where the path + information was learned." + ::= { bgpPathAttrEntry 1 } + + bgpPathAttrDestNetwork OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The address of the destination network." + ::= { bgpPathAttrEntry 2 } + + bgpPathAttrOrigin OBJECT-TYPE + SYNTAX INTEGER { + igp(1),-- networks are interior + egp(2),-- networks learned via EGP + incomplete(3) -- undetermined + } + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The ultimate origin of the path information." + ::= { bgpPathAttrEntry 3 } + + bgpPathAttrASPath OBJECT-TYPE + SYNTAX OCTET STRING (SIZE (2..255)) + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The set of ASs that must be traversed to reach + the network. This object is probably best + represented as SEQUENCE OF INTEGER. For SMI + compatibility, though, it is represented as + OCTET STRING. Each AS is represented as a pair + of octets according to the following algorithm: + + first-byte-of-pair = ASNumber / 256; + second-byte-of-pair = ASNumber & 255;" + ::= { bgpPathAttrEntry 4 } + + bgpPathAttrNextHop OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The address of the border router that should + be used for the destination network." + ::= { bgpPathAttrEntry 5 } + + bgpPathAttrInterASMetric OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The optional inter-AS metric. If this + attribute has not been provided for this route, + the value for this object is 0." + ::= { bgpPathAttrEntry 6 } + + + + -- BGP-4 Received Path Attribute Table. This table contains, + -- one entry per path to a network, path attributes + -- received from all peers running BGP-4. + + bgp4PathAttrTable OBJECT-TYPE + SYNTAX SEQUENCE OF Bgp4PathAttrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The BGP-4 Received Path Attribute Table contains + information about paths to destination networks + received from all BGP4 peers." + ::= { bgp 6 } + + bgp4PathAttrEntry OBJECT-TYPE + SYNTAX Bgp4PathAttrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a path to a network." + INDEX { bgp4PathAttrIpAddrPrefix, + bgp4PathAttrIpAddrPrefixLen, + bgp4PathAttrPeer } + ::= { bgp4PathAttrTable 1 } + + Bgp4PathAttrEntry ::= SEQUENCE { + bgp4PathAttrPeer + IpAddress, + bgp4PathAttrIpAddrPrefixLen + INTEGER, + bgp4PathAttrIpAddrPrefix + IpAddress, + bgp4PathAttrOrigin + INTEGER, + bgp4PathAttrASPathSegment + OCTET STRING, + bgp4PathAttrNextHop + IpAddress, + bgp4PathAttrMultiExitDisc + INTEGER, + bgp4PathAttrLocalPref + INTEGER, + bgp4PathAttrAtomicAggregate + INTEGER, + bgp4PathAttrAggregatorAS + INTEGER, + bgp4PathAttrAggregatorAddr + IpAddress, + bgp4PathAttrCalcLocalPref + INTEGER, + bgp4PathAttrBest + INTEGER, + bgp4PathAttrUnknown + OCTET STRING + } + + bgp4PathAttrPeer OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The IP address of the peer where the path + information was learned." + ::= { bgp4PathAttrEntry 1 } + bgp4PathAttrIpAddrPrefixLen OBJECT-TYPE + SYNTAX INTEGER (0..32) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Length in bits of the IP address prefix in the + Network Layer Reachability Information field." + ::= { bgp4PathAttrEntry 2 } + + bgp4PathAttrIpAddrPrefix OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An IP address prefix in the Network Layer + Reachability Information field. This object + is an IP address containing the prefix with + length specified by bgp4PathAttrIpAddrPrefixLen. + Any bits beyond the length specified by + bgp4PathAttrIpAddrPrefixLen are zeroed." + ::= { bgp4PathAttrEntry 3 } + + bgp4PathAttrOrigin OBJECT-TYPE + SYNTAX INTEGER { + igp(1),-- networks are interior + egp(2),-- networks learned via EGP + incomplete(3) -- undetermined + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The ultimate origin of the path information." + ::= { bgp4PathAttrEntry 4 } + + bgp4PathAttrASPathSegment OBJECT-TYPE + SYNTAX OCTET STRING (SIZE (2..255)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The sequence of AS path segments. Each AS + path segment is represented by a triple + <type, length, value>. + + The type is a 1-octet field which has two + possible values: + 1 AS_SET: unordered set of ASs a + route in the UPDATE message + has traversed + 2 AS_SEQUENCE: ordered set of ASs + a route in the UPDATE message + has traversed. + + The length is a 1-octet field containing the + number of ASs in the value field. + + The value field contains one or more AS + numbers, each AS is represented in the octet + string as a pair of octets according to the + following algorithm: + + first-byte-of-pair = ASNumber / 256; + second-byte-of-pair = ASNumber & 255;" + ::= { bgp4PathAttrEntry 5 } + + bgp4PathAttrNextHop OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The address of the border router that should + be used for the destination network." + ::= { bgp4PathAttrEntry 6 } + + bgp4PathAttrMultiExitDisc OBJECT-TYPE + SYNTAX INTEGER (-1..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This metric is used to discriminate between + multiple exit points to an adjacent autonomous + system. A value of -1 indicates the absence of + this attribute." + ::= { bgp4PathAttrEntry 7 } + + bgp4PathAttrLocalPref OBJECT-TYPE + SYNTAX INTEGER (-1..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The originating BGP4 speaker's degree of + preference for an advertised route. A value of + -1 indicates the absence of this attribute." + ::= { bgp4PathAttrEntry 8 } + + bgp4PathAttrAtomicAggregate OBJECT-TYPE + SYNTAX INTEGER { + lessSpecificRrouteNotSelected(1), + lessSpecificRouteSelected(2) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Whether or not a system has selected + a less specific route without selecting a + more specific route." + ::= { bgp4PathAttrEntry 9 } + + bgp4PathAttrAggregatorAS OBJECT-TYPE + SYNTAX INTEGER (0..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The AS number of the last BGP4 speaker that + performed route aggregation. A value of zero (0) + indicates the absence of this attribute." + ::= { bgp4PathAttrEntry 10 } + + bgp4PathAttrAggregatorAddr OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The IP address of the last BGP4 speaker that + performed route aggregation. A value of + 0.0.0.0 indicates the absence of this attribute." + ::= { bgp4PathAttrEntry 11 } + + bgp4PathAttrCalcLocalPref OBJECT-TYPE + SYNTAX INTEGER (-1..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The degree of preference calculated by the + receiving BGP4 speaker for an advertised route. + A value of -1 indicates the absence of this + attribute." + ::= { bgp4PathAttrEntry 12 } + + bgp4PathAttrBest OBJECT-TYPE + SYNTAX INTEGER { + false(1),-- not chosen as best route + true(2) -- chosen as best route + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An indication of whether or not this route + was chosen as the best BGP4 route." + ::= { bgp4PathAttrEntry 13 } + + bgp4PathAttrUnknown OBJECT-TYPE + SYNTAX OCTET STRING (SIZE(0..255)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "One or more path attributes not understood + by this BGP4 speaker. Size zero (0) indicates + the absence of such attribute(s). Octets + beyond the maximum size, if any, are not + recorded by this object." + ::= { bgp4PathAttrEntry 14 } + + + -- Traps. + + -- note that in RFC 1657, bgpTraps was incorrectly + -- assigned a value of { bgp 7 }, and each of the + -- traps had the bgpPeerRemoteAddr object inappropriately + -- removed from their OBJECTS clause. The following + -- definitions restore the semantics of the traps as + -- they were initially defined in RFC 1269. + + -- { bgp 7 } is unused + + bgpTraps OBJECT IDENTIFIER ::= { bgp 0 } + + bgpEstablished NOTIFICATION-TYPE + OBJECTS { bgpPeerRemoteAddr, + bgpPeerLastError, + bgpPeerState } + STATUS current + DESCRIPTION + "The BGP Established event is generated when + the BGP FSM enters the ESTABLISHED state." + ::= { bgpTraps 1 } + + bgpBackwardTransition NOTIFICATION-TYPE + OBJECTS { bgpPeerRemoteAddr, + bgpPeerLastError, + bgpPeerState } + STATUS current + DESCRIPTION + "The BGPBackwardTransition Event is generated + when the BGP FSM moves from a higher numbered + state to a lower numbered state." + ::= { bgpTraps 2 } + + -- conformance information + + bgpMIBConformance OBJECT IDENTIFIER ::= { bgp 8 } + bgpMIBCompliances OBJECT IDENTIFIER ::= { bgpMIBConformance 1 } + bgpMIBGroups OBJECT IDENTIFIER ::= { bgpMIBConformance 2 } + + -- compliance statements + + bgpMIBCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for entities which + implement the BGP4 mib." + MODULE -- this module + MANDATORY-GROUPS { bgp4MIBGlobalsGroup, + bgp4MIBPeerGroup, + bgp4MIBPathAttrGroup, + bgp4MIBNotificationGroup } + ::= { bgpMIBCompliances 1 } + + -- units of conformance + + bgp4MIBGlobalsGroup OBJECT-GROUP + OBJECTS { bgpVersion, + bgpLocalAs, + bgpIdentifier } + STATUS current + DESCRIPTION + "A collection of objects providing information + on global BGP state." + ::= { bgpMIBGroups 1 } + + bgp4MIBPeerGroup OBJECT-GROUP + OBJECTS { bgpPeerIdentifier, + bgpPeerState, + bgpPeerAdminStatus, + bgpPeerNegotiatedVersion, + bgpPeerLocalAddr, + bgpPeerLocalPort, + bgpPeerRemoteAddr, + bgpPeerRemotePort, + bgpPeerRemoteAs, + bgpPeerInUpdates, + bgpPeerOutUpdates, + bgpPeerInTotalMessages, + bgpPeerOutTotalMessages, + bgpPeerLastError, + bgpPeerFsmEstablishedTransitions, + bgpPeerFsmEstablishedTime, + bgpPeerConnectRetryInterval, + bgpPeerHoldTime, + bgpPeerKeepAlive, + bgpPeerHoldTimeConfigured, + bgpPeerKeepAliveConfigured, + bgpPeerMinASOriginationInterval, + bgpPeerMinRouteAdvertisementInterval, + bgpPeerInUpdateElapsedTime } + STATUS current + DESCRIPTION + "A collection of objects for managing + BGP peers." + ::= { bgpMIBGroups 2 } + + bgp4MIBRcvdPathAttrGroup OBJECT-GROUP + OBJECTS { bgpPathAttrPeer, + bgpPathAttrDestNetwork, + bgpPathAttrOrigin, + bgpPathAttrASPath, + bgpPathAttrNextHop, + bgpPathAttrInterASMetric } + STATUS obsolete + DESCRIPTION + "A collection of objects for managing BGP + path entries. + + This conformance group is obsolete, + replaced by bgp4MIBPathAttrGroup." + ::= { bgpMIBGroups 3 } + + bgp4MIBPathAttrGroup OBJECT-GROUP + OBJECTS { bgp4PathAttrPeer, + bgp4PathAttrIpAddrPrefixLen, + bgp4PathAttrIpAddrPrefix, + bgp4PathAttrOrigin, + bgp4PathAttrASPathSegment, + bgp4PathAttrNextHop, + bgp4PathAttrMultiExitDisc, + bgp4PathAttrLocalPref, + bgp4PathAttrAtomicAggregate, + bgp4PathAttrAggregatorAS, + bgp4PathAttrAggregatorAddr, + bgp4PathAttrCalcLocalPref, + bgp4PathAttrBest, + bgp4PathAttrUnknown } + STATUS current + DESCRIPTION + "A collection of objects for managing + BGP path entries." + ::= { bgpMIBGroups 4 } + + bgp4MIBNotificationGroup NOTIFICATION-GROUP + NOTIFICATIONS { bgpEstablished, + bgpBackwardTransition } + STATUS current + DESCRIPTION + "A collection of notifications for signaling + changes in BGP peer relationships." + ::= { bgpMIBGroups 5 } + + END diff --git a/mibs/BRIDGE-MIB.txt b/mibs/BRIDGE-MIB.txt new file mode 100644 index 0000000..1e77a19 --- /dev/null +++ b/mibs/BRIDGE-MIB.txt @@ -0,0 +1,1472 @@ +BRIDGE-MIB DEFINITIONS ::= BEGIN + +-- ---------------------------------------------------------- -- +-- MIB for IEEE 802.1D devices +-- ---------------------------------------------------------- -- +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE, + Counter32, Integer32, TimeTicks, mib-2 + FROM SNMPv2-SMI + TEXTUAL-CONVENTION, MacAddress + FROM SNMPv2-TC + MODULE-COMPLIANCE, OBJECT-GROUP, NOTIFICATION-GROUP + FROM SNMPv2-CONF + InterfaceIndex FROM IF-MIB + ; + +dot1dBridge MODULE-IDENTITY + LAST-UPDATED "200509190000Z" + ORGANIZATION "IETF Bridge MIB Working Group" + CONTACT-INFO + "Email: bridge-mib@ietf.org + + K.C. Norseth (Editor) + L-3 Communications + Tel: +1 801-594-2809 + Email: kenyon.c.norseth@L-3com.com + Postal: 640 N. 2200 West. + Salt Lake City, Utah 84116-0850 + + Les Bell (Editor) + 3Com Europe Limited + Phone: +44 1442 438025 + Email: elbell@ntlworld.com + Postal: 3Com Centre, Boundary Way + Hemel Hempstead + Herts. HP2 7YU + UK + + Send comments to <bridge-mib@ietf.org>" + DESCRIPTION + "The Bridge MIB module for managing devices that support + IEEE 802.1D. + + Copyright (C) The Internet Society (2005). This version of + this MIB module is part of RFC 4188; see the RFC itself for + full legal notices." + REVISION "200509190000Z" + DESCRIPTION + "Third revision, published as part of RFC 4188. + + The MIB module has been converted to SMIv2 format. + Conformance statements have been added and some + description and reference clauses have been updated. + + The object dot1dStpPortPathCost32 was added to + support IEEE 802.1t and the permissible values of + dot1dStpPriority and dot1dStpPortPriority have been + clarified for bridges supporting IEEE 802.1t or + IEEE 802.1w. + + The interpretation of dot1dStpTimeSinceTopologyChange + has been clarified for bridges supporting the Rapid + Spanning Tree Protocol (RSTP)." + REVISION "199307310000Z" + DESCRIPTION + "Second revision, published as part of RFC 1493." + REVISION "199112310000Z" + DESCRIPTION + "Initial revision, published as part of RFC 1286." + ::= { mib-2 17 } + +-- ---------------------------------------------------------- -- +-- Textual Conventions +-- ---------------------------------------------------------- -- + +BridgeId ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The Bridge-Identifier, as used in the Spanning Tree + Protocol, to uniquely identify a bridge. Its first two + octets (in network byte order) contain a priority value, + and its last 6 octets contain the MAC address used to + refer to a bridge in a unique fashion (typically, the + numerically smallest MAC address of all ports on the + bridge)." + SYNTAX OCTET STRING (SIZE (8)) + +Timeout ::= TEXTUAL-CONVENTION + DISPLAY-HINT "d" + STATUS current + DESCRIPTION + "A Spanning Tree Protocol (STP) timer in units of 1/100 + seconds. Several objects in this MIB module represent + values of timers used by the Spanning Tree Protocol. + In this MIB, these timers have values in units of + hundredths of a second (i.e., 1/100 secs). + + These timers, when stored in a Spanning Tree Protocol's + BPDU, are in units of 1/256 seconds. Note, however, that + 802.1D-1998 specifies a settable granularity of no more + than one second for these timers. To avoid ambiguity, + a conversion algorithm is defined below for converting + between the different units, which ensures a timer's + value is not distorted by multiple conversions. + + To convert a Timeout value into a value in units of + 1/256 seconds, the following algorithm should be used: + + b = floor( (n * 256) / 100) + + where: + floor = quotient [ignore remainder] + n is the value in 1/100 second units + b is the value in 1/256 second units + + To convert the value from 1/256 second units back to + 1/100 seconds, the following algorithm should be used: + + n = ceiling( (b * 100) / 256) + + where: + ceiling = quotient [if remainder is 0], or + quotient + 1 [if remainder is nonzero] + n is the value in 1/100 second units + + b is the value in 1/256 second units + + Note: it is important that the arithmetic operations are + done in the order specified (i.e., multiply first, + divide second)." + SYNTAX Integer32 + +-- ---------------------------------------------------------- -- +-- subtrees in the Bridge MIB +-- ---------------------------------------------------------- -- + +dot1dNotifications OBJECT IDENTIFIER ::= { dot1dBridge 0 } + +dot1dBase OBJECT IDENTIFIER ::= { dot1dBridge 1 } +dot1dStp OBJECT IDENTIFIER ::= { dot1dBridge 2 } + +dot1dSr OBJECT IDENTIFIER ::= { dot1dBridge 3 } +-- documented in RFC 1525 + +dot1dTp OBJECT IDENTIFIER ::= { dot1dBridge 4 } +dot1dStatic OBJECT IDENTIFIER ::= { dot1dBridge 5 } + +-- Subtrees used by Bridge MIB Extensions: +-- pBridgeMIB MODULE-IDENTITY ::= { dot1dBridge 6 } +-- qBridgeMIB MODULE-IDENTITY ::= { dot1dBridge 7 } +-- Note that the practice of registering related MIB modules +-- below dot1dBridge has been discouraged since there is no +-- robust mechanism to track such registrations. + +dot1dConformance OBJECT IDENTIFIER ::= { dot1dBridge 8 } + +-- ---------------------------------------------------------- -- +-- the dot1dBase subtree +-- ---------------------------------------------------------- -- +-- Implementation of the dot1dBase subtree is mandatory for all +-- bridges. +-- ---------------------------------------------------------- -- + +dot1dBaseBridgeAddress OBJECT-TYPE + SYNTAX MacAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The MAC address used by this bridge when it must be + referred to in a unique fashion. It is recommended + that this be the numerically smallest MAC address of + all ports that belong to this bridge. However, it is only + + required to be unique. When concatenated with + dot1dStpPriority, a unique BridgeIdentifier is formed, + which is used in the Spanning Tree Protocol." + REFERENCE + "IEEE 802.1D-1998: clauses 14.4.1.1.3 and 7.12.5" + ::= { dot1dBase 1 } + +dot1dBaseNumPorts OBJECT-TYPE + SYNTAX Integer32 + UNITS "ports" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ports controlled by this bridging + entity." + REFERENCE + "IEEE 802.1D-1998: clause 14.4.1.1.3" + ::= { dot1dBase 2 } + +dot1dBaseType OBJECT-TYPE + SYNTAX INTEGER { + unknown(1), + transparent-only(2), + sourceroute-only(3), + srt(4) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Indicates what type of bridging this bridge can + perform. If a bridge is actually performing a + certain type of bridging, this will be indicated by + entries in the port table for the given type." + ::= { dot1dBase 3 } + +-- ---------------------------------------------------------- -- +-- The Generic Bridge Port Table +-- ---------------------------------------------------------- -- +dot1dBasePortTable OBJECT-TYPE + SYNTAX SEQUENCE OF Dot1dBasePortEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table that contains generic information about every + port that is associated with this bridge. Transparent, + source-route, and srt ports are included." + ::= { dot1dBase 4 } + +dot1dBasePortEntry OBJECT-TYPE + SYNTAX Dot1dBasePortEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of information for each port of the bridge." + REFERENCE + "IEEE 802.1D-1998: clause 14.4.2, 14.6.1" + INDEX { dot1dBasePort } + ::= { dot1dBasePortTable 1 } + +Dot1dBasePortEntry ::= + SEQUENCE { + dot1dBasePort + Integer32, + dot1dBasePortIfIndex + InterfaceIndex, + dot1dBasePortCircuit + OBJECT IDENTIFIER, + dot1dBasePortDelayExceededDiscards + Counter32, + dot1dBasePortMtuExceededDiscards + Counter32 + } + +dot1dBasePort OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The port number of the port for which this entry + contains bridge management information." + ::= { dot1dBasePortEntry 1 } + +dot1dBasePortIfIndex OBJECT-TYPE + SYNTAX InterfaceIndex + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of the instance of the ifIndex object, + defined in IF-MIB, for the interface corresponding + to this port." + ::= { dot1dBasePortEntry 2 } + +dot1dBasePortCircuit OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "For a port that (potentially) has the same value of + dot1dBasePortIfIndex as another port on the same bridge. + This object contains the name of an object instance + unique to this port. For example, in the case where + multiple ports correspond one-to-one with multiple X.25 + virtual circuits, this value might identify an (e.g., + the first) object instance associated with the X.25 + virtual circuit corresponding to this port. + + For a port which has a unique value of + dot1dBasePortIfIndex, this object can have the value + { 0 0 }." + ::= { dot1dBasePortEntry 3 } + +dot1dBasePortDelayExceededDiscards OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of frames discarded by this port due + to excessive transit delay through the bridge. It + is incremented by both transparent and source + route bridges." + REFERENCE + "IEEE 802.1D-1998: clause 14.6.1.1.3" + ::= { dot1dBasePortEntry 4 } + +dot1dBasePortMtuExceededDiscards OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of frames discarded by this port due + to an excessive size. It is incremented by both + transparent and source route bridges." + REFERENCE + "IEEE 802.1D-1998: clause 14.6.1.1.3" + ::= { dot1dBasePortEntry 5 } + +-- ---------------------------------------------------------- -- +-- the dot1dStp subtree +-- ---------------------------------------------------------- -- +-- Implementation of the dot1dStp subtree is optional. It is +-- implemented by those bridges that support the Spanning Tree +-- Protocol. +-- ---------------------------------------------------------- -- + +dot1dStpProtocolSpecification OBJECT-TYPE + SYNTAX INTEGER { + unknown(1), + decLb100(2), + ieee8021d(3) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An indication of what version of the Spanning Tree + Protocol is being run. The value 'decLb100(2)' + indicates the DEC LANbridge 100 Spanning Tree protocol. + IEEE 802.1D implementations will return 'ieee8021d(3)'. + If future versions of the IEEE Spanning Tree Protocol + that are incompatible with the current version + are released a new value will be defined." + ::= { dot1dStp 1 } + +dot1dStpPriority OBJECT-TYPE + SYNTAX Integer32 (0..65535) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The value of the write-able portion of the Bridge ID + (i.e., the first two octets of the (8 octet long) Bridge + ID). The other (last) 6 octets of the Bridge ID are + given by the value of dot1dBaseBridgeAddress. + On bridges supporting IEEE 802.1t or IEEE 802.1w, + permissible values are 0-61440, in steps of 4096." + REFERENCE + "IEEE 802.1D-1998 clause 8.10.2, Table 8-4, + IEEE 802.1t clause 8.10.2, Table 8-4, clause 14.3." + ::= { dot1dStp 2 } + +dot1dStpTimeSinceTopologyChange OBJECT-TYPE + SYNTAX TimeTicks + UNITS "centi-seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The time (in hundredths of a second) since the + last time a topology change was detected by the + bridge entity. + For RSTP, this reports the time since the tcWhile + timer for any port on this Bridge was nonzero." + REFERENCE + "IEEE 802.1D-1998 clause 14.8.1.1., + IEEE 802.1w clause 14.8.1.1." + ::= { dot1dStp 3 } + +dot1dStpTopChanges OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of topology changes detected by + this bridge since the management entity was last + reset or initialized." + REFERENCE + "IEEE 802.1D-1998 clause 14.8.1.1." + ::= { dot1dStp 4 } + +dot1dStpDesignatedRoot OBJECT-TYPE + SYNTAX BridgeId + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The bridge identifier of the root of the spanning + tree, as determined by the Spanning Tree Protocol, + as executed by this node. This value is used as + the Root Identifier parameter in all Configuration + Bridge PDUs originated by this node." + REFERENCE + "IEEE 802.1D-1998: clause 8.5.3.1" + ::= { dot1dStp 5 } + +dot1dStpRootCost OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The cost of the path to the root as seen from + this bridge." + REFERENCE + "IEEE 802.1D-1998: clause 8.5.3.2" + ::= { dot1dStp 6 } + +dot1dStpRootPort OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The port number of the port that offers the lowest + cost path from this bridge to the root bridge." + REFERENCE + "IEEE 802.1D-1998: clause 8.5.3.3" + ::= { dot1dStp 7 } + +dot1dStpMaxAge OBJECT-TYPE + SYNTAX Timeout + UNITS "centi-seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The maximum age of Spanning Tree Protocol information + learned from the network on any port before it is + discarded, in units of hundredths of a second. This is + the actual value that this bridge is currently using." + REFERENCE + "IEEE 802.1D-1998: clause 8.5.3.4" + ::= { dot1dStp 8 } + +dot1dStpHelloTime OBJECT-TYPE + SYNTAX Timeout + UNITS "centi-seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The amount of time between the transmission of + Configuration bridge PDUs by this node on any port when + it is the root of the spanning tree, or trying to become + so, in units of hundredths of a second. This is the + actual value that this bridge is currently using." + REFERENCE + "IEEE 802.1D-1998: clause 8.5.3.5" + ::= { dot1dStp 9 } + +dot1dStpHoldTime OBJECT-TYPE + SYNTAX Integer32 + UNITS "centi-seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This time value determines the interval length + during which no more than two Configuration bridge + PDUs shall be transmitted by this node, in units + of hundredths of a second." + REFERENCE + "IEEE 802.1D-1998: clause 8.5.3.14" + ::= { dot1dStp 10 } + +dot1dStpForwardDelay OBJECT-TYPE + SYNTAX Timeout + UNITS "centi-seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This time value, measured in units of hundredths of a + second, controls how fast a port changes its spanning + state when moving towards the Forwarding state. The + value determines how long the port stays in each of the + Listening and Learning states, which precede the + Forwarding state. This value is also used when a + topology change has been detected and is underway, to + age all dynamic entries in the Forwarding Database. + [Note that this value is the one that this bridge is + currently using, in contrast to + dot1dStpBridgeForwardDelay, which is the value that this + bridge and all others would start using if/when this + bridge were to become the root.]" + REFERENCE + "IEEE 802.1D-1998: clause 8.5.3.6" + ::= { dot1dStp 11 } + +dot1dStpBridgeMaxAge OBJECT-TYPE + SYNTAX Timeout (600..4000) + UNITS "centi-seconds" + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The value that all bridges use for MaxAge when this + bridge is acting as the root. Note that 802.1D-1998 + specifies that the range for this parameter is related + to the value of dot1dStpBridgeHelloTime. The + granularity of this timer is specified by 802.1D-1998 to + be 1 second. An agent may return a badValue error if a + set is attempted to a value that is not a whole number + of seconds." + REFERENCE + "IEEE 802.1D-1998: clause 8.5.3.8" + ::= { dot1dStp 12 } + +dot1dStpBridgeHelloTime OBJECT-TYPE + SYNTAX Timeout (100..1000) + UNITS "centi-seconds" + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The value that all bridges use for HelloTime when this + bridge is acting as the root. The granularity of this + timer is specified by 802.1D-1998 to be 1 second. An + agent may return a badValue error if a set is attempted + + to a value that is not a whole number of seconds." + REFERENCE + "IEEE 802.1D-1998: clause 8.5.3.9" + ::= { dot1dStp 13 } + +dot1dStpBridgeForwardDelay OBJECT-TYPE + SYNTAX Timeout (400..3000) + UNITS "centi-seconds" + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The value that all bridges use for ForwardDelay when + this bridge is acting as the root. Note that + 802.1D-1998 specifies that the range for this parameter + is related to the value of dot1dStpBridgeMaxAge. The + granularity of this timer is specified by 802.1D-1998 to + be 1 second. An agent may return a badValue error if a + set is attempted to a value that is not a whole number + of seconds." + REFERENCE + "IEEE 802.1D-1998: clause 8.5.3.10" + ::= { dot1dStp 14 } + +-- ---------------------------------------------------------- -- +-- The Spanning Tree Port Table +-- ---------------------------------------------------------- -- + +dot1dStpPortTable OBJECT-TYPE + SYNTAX SEQUENCE OF Dot1dStpPortEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table that contains port-specific information + for the Spanning Tree Protocol." + ::= { dot1dStp 15 } + +dot1dStpPortEntry OBJECT-TYPE + SYNTAX Dot1dStpPortEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of information maintained by every port about + the Spanning Tree Protocol state for that port." + INDEX { dot1dStpPort } + ::= { dot1dStpPortTable 1 } + +Dot1dStpPortEntry ::= + SEQUENCE { + + dot1dStpPort + Integer32, + dot1dStpPortPriority + Integer32, + dot1dStpPortState + INTEGER, + dot1dStpPortEnable + INTEGER, + dot1dStpPortPathCost + Integer32, + dot1dStpPortDesignatedRoot + BridgeId, + dot1dStpPortDesignatedCost + Integer32, + dot1dStpPortDesignatedBridge + BridgeId, + dot1dStpPortDesignatedPort + OCTET STRING, + dot1dStpPortForwardTransitions + Counter32, + dot1dStpPortPathCost32 + Integer32 + } + +dot1dStpPort OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The port number of the port for which this entry + contains Spanning Tree Protocol management information." + REFERENCE + "IEEE 802.1D-1998: clause 14.8.2.1.2" + ::= { dot1dStpPortEntry 1 } + +dot1dStpPortPriority OBJECT-TYPE + SYNTAX Integer32 (0..255) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The value of the priority field that is contained in + the first (in network byte order) octet of the (2 octet + long) Port ID. The other octet of the Port ID is given + by the value of dot1dStpPort. + On bridges supporting IEEE 802.1t or IEEE 802.1w, + permissible values are 0-240, in steps of 16." + REFERENCE + "IEEE 802.1D-1998 clause 8.10.2, Table 8-4, + IEEE 802.1t clause 8.10.2, Table 8-4, clause 14.3." + ::= { dot1dStpPortEntry 2 } + +dot1dStpPortState OBJECT-TYPE + SYNTAX INTEGER { + disabled(1), + blocking(2), + listening(3), + learning(4), + forwarding(5), + broken(6) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The port's current state, as defined by application of + the Spanning Tree Protocol. This state controls what + action a port takes on reception of a frame. If the + bridge has detected a port that is malfunctioning, it + will place that port into the broken(6) state. For + ports that are disabled (see dot1dStpPortEnable), this + object will have a value of disabled(1)." + REFERENCE + "IEEE 802.1D-1998: clause 8.5.5.2" + ::= { dot1dStpPortEntry 3 } + +dot1dStpPortEnable OBJECT-TYPE + SYNTAX INTEGER { + enabled(1), + disabled(2) + } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The enabled/disabled status of the port." + REFERENCE + "IEEE 802.1D-1998: clause 8.5.5.2" + ::= { dot1dStpPortEntry 4 } + +dot1dStpPortPathCost OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The contribution of this port to the path cost of + paths towards the spanning tree root which include + this port. 802.1D-1998 recommends that the default + value of this parameter be in inverse proportion to + + the speed of the attached LAN. + + New implementations should support dot1dStpPortPathCost32. + If the port path costs exceeds the maximum value of this + object then this object should report the maximum value, + namely 65535. Applications should try to read the + dot1dStpPortPathCost32 object if this object reports + the maximum value." + REFERENCE "IEEE 802.1D-1998: clause 8.5.5.3" + ::= { dot1dStpPortEntry 5 } + +dot1dStpPortDesignatedRoot OBJECT-TYPE + SYNTAX BridgeId + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The unique Bridge Identifier of the Bridge + recorded as the Root in the Configuration BPDUs + transmitted by the Designated Bridge for the + segment to which the port is attached." + REFERENCE + "IEEE 802.1D-1998: clause 8.5.5.4" + ::= { dot1dStpPortEntry 6 } + +dot1dStpPortDesignatedCost OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The path cost of the Designated Port of the segment + connected to this port. This value is compared to the + Root Path Cost field in received bridge PDUs." + REFERENCE + "IEEE 802.1D-1998: clause 8.5.5.5" + ::= { dot1dStpPortEntry 7 } + +dot1dStpPortDesignatedBridge OBJECT-TYPE + SYNTAX BridgeId + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Bridge Identifier of the bridge that this + port considers to be the Designated Bridge for + this port's segment." + REFERENCE + "IEEE 802.1D-1998: clause 8.5.5.6" + ::= { dot1dStpPortEntry 8 } + +dot1dStpPortDesignatedPort OBJECT-TYPE + SYNTAX OCTET STRING (SIZE (2)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Port Identifier of the port on the Designated + Bridge for this port's segment." + REFERENCE + "IEEE 802.1D-1998: clause 8.5.5.7" + ::= { dot1dStpPortEntry 9 } + +dot1dStpPortForwardTransitions OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times this port has transitioned + from the Learning state to the Forwarding state." + ::= { dot1dStpPortEntry 10 } + +dot1dStpPortPathCost32 OBJECT-TYPE + SYNTAX Integer32 (1..200000000) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The contribution of this port to the path cost of + paths towards the spanning tree root which include + this port. 802.1D-1998 recommends that the default + value of this parameter be in inverse proportion to + the speed of the attached LAN. + + This object replaces dot1dStpPortPathCost to support + IEEE 802.1t." + REFERENCE + "IEEE 802.1t clause 8.10.2, Table 8-5." + ::= { dot1dStpPortEntry 11 } + +-- ---------------------------------------------------------- -- +-- the dot1dTp subtree +-- ---------------------------------------------------------- -- +-- Implementation of the dot1dTp subtree is optional. It is +-- implemented by those bridges that support the transparent +-- bridging mode. A transparent or SRT bridge will implement +-- this subtree. +-- ---------------------------------------------------------- -- + +dot1dTpLearnedEntryDiscards OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of Forwarding Database entries that + have been or would have been learned, but have been + discarded due to a lack of storage space in the + Forwarding Database. If this counter is increasing, it + indicates that the Forwarding Database is regularly + becoming full (a condition that has unpleasant + performance effects on the subnetwork). If this counter + has a significant value but is not presently increasing, + it indicates that the problem has been occurring but is + not persistent." + REFERENCE + "IEEE 802.1D-1998: clause 14.7.1.1.3" + ::= { dot1dTp 1 } + +dot1dTpAgingTime OBJECT-TYPE + SYNTAX Integer32 (10..1000000) + UNITS "seconds" + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The timeout period in seconds for aging out + dynamically-learned forwarding information. + 802.1D-1998 recommends a default of 300 seconds." + REFERENCE + "IEEE 802.1D-1998: clause 14.7.1.1.3" + ::= { dot1dTp 2 } + +-- ---------------------------------------------------------- -- +-- The Forwarding Database for Transparent Bridges +-- ---------------------------------------------------------- -- + +dot1dTpFdbTable OBJECT-TYPE + SYNTAX SEQUENCE OF Dot1dTpFdbEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table that contains information about unicast + entries for which the bridge has forwarding and/or + filtering information. This information is used + by the transparent bridging function in + determining how to propagate a received frame." + ::= { dot1dTp 3 } + +dot1dTpFdbEntry OBJECT-TYPE + SYNTAX Dot1dTpFdbEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a specific unicast MAC address + for which the bridge has some forwarding and/or + filtering information." + INDEX { dot1dTpFdbAddress } + ::= { dot1dTpFdbTable 1 } + +Dot1dTpFdbEntry ::= + SEQUENCE { + dot1dTpFdbAddress + MacAddress, + dot1dTpFdbPort + Integer32, + dot1dTpFdbStatus + INTEGER + } + +dot1dTpFdbAddress OBJECT-TYPE + SYNTAX MacAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A unicast MAC address for which the bridge has + forwarding and/or filtering information." + REFERENCE + "IEEE 802.1D-1998: clause 7.9.1, 7.9.2" + ::= { dot1dTpFdbEntry 1 } + +dot1dTpFdbPort OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Either the value '0', or the port number of the port on + which a frame having a source address equal to the value + of the corresponding instance of dot1dTpFdbAddress has + been seen. A value of '0' indicates that the port + number has not been learned, but that the bridge does + have some forwarding/filtering information about this + address (e.g., in the dot1dStaticTable). Implementors + are encouraged to assign the port value to this object + whenever it is learned, even for addresses for which the + corresponding value of dot1dTpFdbStatus is not + learned(3)." + ::= { dot1dTpFdbEntry 2 } + +dot1dTpFdbStatus OBJECT-TYPE + SYNTAX INTEGER { + other(1), + invalid(2), + learned(3), + self(4), + mgmt(5) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The status of this entry. The meanings of the + values are: + other(1) - none of the following. This would + include the case where some other MIB object + (not the corresponding instance of + dot1dTpFdbPort, nor an entry in the + dot1dStaticTable) is being used to determine if + and how frames addressed to the value of the + corresponding instance of dot1dTpFdbAddress are + being forwarded. + invalid(2) - this entry is no longer valid (e.g., + it was learned but has since aged out), but has + not yet been flushed from the table. + learned(3) - the value of the corresponding instance + of dot1dTpFdbPort was learned, and is being + used. + self(4) - the value of the corresponding instance of + dot1dTpFdbAddress represents one of the bridge's + addresses. The corresponding instance of + dot1dTpFdbPort indicates which of the bridge's + ports has this address. + mgmt(5) - the value of the corresponding instance of + dot1dTpFdbAddress is also the value of an + existing instance of dot1dStaticAddress." + ::= { dot1dTpFdbEntry 3 } + +-- ---------------------------------------------------------- -- +-- Port Table for Transparent Bridges +-- ---------------------------------------------------------- -- + +dot1dTpPortTable OBJECT-TYPE + SYNTAX SEQUENCE OF Dot1dTpPortEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table that contains information about every port that + is associated with this transparent bridge." + ::= { dot1dTp 4 } + +dot1dTpPortEntry OBJECT-TYPE + SYNTAX Dot1dTpPortEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of information for each port of a transparent + bridge." + INDEX { dot1dTpPort } + ::= { dot1dTpPortTable 1 } + +Dot1dTpPortEntry ::= + SEQUENCE { + dot1dTpPort + Integer32, + dot1dTpPortMaxInfo + Integer32, + dot1dTpPortInFrames + Counter32, + dot1dTpPortOutFrames + Counter32, + dot1dTpPortInDiscards + Counter32 + } + +dot1dTpPort OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The port number of the port for which this entry + contains Transparent bridging management information." + ::= { dot1dTpPortEntry 1 } + +-- It would be nice if we could use ifMtu as the size of the +-- largest INFO field, but we can't because ifMtu is defined +-- to be the size that the (inter-)network layer can use, which +-- can differ from the MAC layer (especially if several layers +-- of encapsulation are used). + +dot1dTpPortMaxInfo OBJECT-TYPE + SYNTAX Integer32 + UNITS "bytes" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The maximum size of the INFO (non-MAC) field that + + this port will receive or transmit." + ::= { dot1dTpPortEntry 2 } + +dot1dTpPortInFrames OBJECT-TYPE + SYNTAX Counter32 + UNITS "frames" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of frames that have been received by this + port from its segment. Note that a frame received on the + interface corresponding to this port is only counted by + this object if and only if it is for a protocol being + processed by the local bridging function, including + bridge management frames." + REFERENCE + "IEEE 802.1D-1998: clause 14.6.1.1.3" + ::= { dot1dTpPortEntry 3 } + +dot1dTpPortOutFrames OBJECT-TYPE + SYNTAX Counter32 + UNITS "frames" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of frames that have been transmitted by this + port to its segment. Note that a frame transmitted on + the interface corresponding to this port is only counted + by this object if and only if it is for a protocol being + processed by the local bridging function, including + bridge management frames." + REFERENCE + "IEEE 802.1D-1998: clause 14.6.1.1.3" + ::= { dot1dTpPortEntry 4 } + +dot1dTpPortInDiscards OBJECT-TYPE + SYNTAX Counter32 + UNITS "frames" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Count of received valid frames that were discarded + (i.e., filtered) by the Forwarding Process." + REFERENCE + "IEEE 802.1D-1998: clause 14.6.1.1.3" + ::= { dot1dTpPortEntry 5 } + +-- ---------------------------------------------------------- -- + +-- The Static (Destination-Address Filtering) Database +-- ---------------------------------------------------------- -- +-- Implementation of this subtree is optional. +-- ---------------------------------------------------------- -- + +dot1dStaticTable OBJECT-TYPE + SYNTAX SEQUENCE OF Dot1dStaticEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table containing filtering information configured + into the bridge by (local or network) management + specifying the set of ports to which frames received + from specific ports and containing specific destination + addresses are allowed to be forwarded. The value of + zero in this table, as the port number from which frames + with a specific destination address are received, is + used to specify all ports for which there is no specific + entry in this table for that particular destination + address. Entries are valid for unicast and for + group/broadcast addresses." + REFERENCE + "IEEE 802.1D-1998: clause 14.7.2" + ::= { dot1dStatic 1 } + +dot1dStaticEntry OBJECT-TYPE + SYNTAX Dot1dStaticEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Filtering information configured into the bridge by + (local or network) management specifying the set of + ports to which frames received from a specific port and + containing a specific destination address are allowed to + be forwarded." + REFERENCE + "IEEE 802.1D-1998: clause 14.7.2" + INDEX { dot1dStaticAddress, dot1dStaticReceivePort } + ::= { dot1dStaticTable 1 } + +Dot1dStaticEntry ::= + SEQUENCE { + dot1dStaticAddress MacAddress, + dot1dStaticReceivePort Integer32, + dot1dStaticAllowedToGoTo OCTET STRING, + dot1dStaticStatus INTEGER + } + +dot1dStaticAddress OBJECT-TYPE + SYNTAX MacAddress + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The destination MAC address in a frame to which this + entry's filtering information applies. This object can + take the value of a unicast address, a group address, or + the broadcast address." + REFERENCE + "IEEE 802.1D-1998: clause 7.9.1, 7.9.2" + ::= { dot1dStaticEntry 1 } + +dot1dStaticReceivePort OBJECT-TYPE + SYNTAX Integer32 (0..65535) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Either the value '0', or the port number of the port + from which a frame must be received in order for this + entry's filtering information to apply. A value of zero + indicates that this entry applies on all ports of the + bridge for which there is no other applicable entry." + ::= { dot1dStaticEntry 2 } + +dot1dStaticAllowedToGoTo OBJECT-TYPE + SYNTAX OCTET STRING (SIZE (0..512)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The set of ports to which frames received from a + specific port and destined for a specific MAC address, + are allowed to be forwarded. Each octet within the + value of this object specifies a set of eight ports, + with the first octet specifying ports 1 through 8, the + second octet specifying ports 9 through 16, etc. Within + each octet, the most significant bit represents the + lowest numbered port, and the least significant bit + represents the highest numbered port. Thus, each port + of the bridge is represented by a single bit within the + value of this object. If that bit has a value of '1', + then that port is included in the set of ports; the port + is not included if its bit has a value of '0'. (Note + that the setting of the bit corresponding to the port + from which a frame is received is irrelevant.) The + default value of this object is a string of ones of + appropriate length. + + The value of this object may exceed the required minimum + maximum message size of some SNMP transport (484 bytes, + in the case of SNMP over UDP, see RFC 3417, section 3.2). + SNMP engines on bridges supporting a large number of + ports must support appropriate maximum message sizes." + ::= { dot1dStaticEntry 3 } + +dot1dStaticStatus OBJECT-TYPE + SYNTAX INTEGER { + other(1), + invalid(2), + permanent(3), + deleteOnReset(4), + deleteOnTimeout(5) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object indicates the status of this entry. + The default value is permanent(3). + other(1) - this entry is currently in use but the + conditions under which it will remain so are + different from each of the following values. + invalid(2) - writing this value to the object + removes the corresponding entry. + permanent(3) - this entry is currently in use and + will remain so after the next reset of the + bridge. + deleteOnReset(4) - this entry is currently in use + and will remain so until the next reset of the + bridge. + deleteOnTimeout(5) - this entry is currently in use + and will remain so until it is aged out." + ::= { dot1dStaticEntry 4 } + +-- ---------------------------------------------------------- -- +-- Notifications for use by Bridges +-- ---------------------------------------------------------- -- +-- Notifications for the Spanning Tree Protocol +-- ---------------------------------------------------------- -- + +newRoot NOTIFICATION-TYPE + -- OBJECTS { } + STATUS current + DESCRIPTION + "The newRoot trap indicates that the sending agent has + become the new root of the Spanning Tree; the trap is + sent by a bridge soon after its election as the new + + root, e.g., upon expiration of the Topology Change Timer, + immediately subsequent to its election. Implementation + of this trap is optional." + ::= { dot1dNotifications 1 } + +topologyChange NOTIFICATION-TYPE + -- OBJECTS { } + STATUS current + DESCRIPTION + "A topologyChange trap is sent by a bridge when any of + its configured ports transitions from the Learning state + to the Forwarding state, or from the Forwarding state to + the Blocking state. The trap is not sent if a newRoot + trap is sent for the same transition. Implementation of + this trap is optional." + ::= { dot1dNotifications 2 } + +-- ---------------------------------------------------------- -- +-- IEEE 802.1D MIB - Conformance Information +-- ---------------------------------------------------------- -- + +dot1dGroups OBJECT IDENTIFIER ::= { dot1dConformance 1 } +dot1dCompliances OBJECT IDENTIFIER ::= { dot1dConformance 2 } + +-- ---------------------------------------------------------- -- +-- units of conformance +-- ---------------------------------------------------------- -- + +-- ---------------------------------------------------------- -- +-- the dot1dBase group +-- ---------------------------------------------------------- -- + +dot1dBaseBridgeGroup OBJECT-GROUP + OBJECTS { + dot1dBaseBridgeAddress, + dot1dBaseNumPorts, + dot1dBaseType + } + STATUS current + DESCRIPTION + "Bridge level information for this device." + ::= { dot1dGroups 1 } + +dot1dBasePortGroup OBJECT-GROUP + OBJECTS { + dot1dBasePort, + dot1dBasePortIfIndex, + dot1dBasePortCircuit, + dot1dBasePortDelayExceededDiscards, + dot1dBasePortMtuExceededDiscards + } + STATUS current + DESCRIPTION + "Information for each port on this device." + ::= { dot1dGroups 2 } + +-- ---------------------------------------------------------- -- +-- the dot1dStp group +-- ---------------------------------------------------------- -- + +dot1dStpBridgeGroup OBJECT-GROUP + OBJECTS { + dot1dStpProtocolSpecification, + dot1dStpPriority, + dot1dStpTimeSinceTopologyChange, + dot1dStpTopChanges, + dot1dStpDesignatedRoot, + dot1dStpRootCost, + dot1dStpRootPort, + dot1dStpMaxAge, + dot1dStpHelloTime, + dot1dStpHoldTime, + dot1dStpForwardDelay, + dot1dStpBridgeMaxAge, + dot1dStpBridgeHelloTime, + dot1dStpBridgeForwardDelay + } + STATUS current + DESCRIPTION + "Bridge level Spanning Tree data for this device." + ::= { dot1dGroups 3 } + +dot1dStpPortGroup OBJECT-GROUP + OBJECTS { + dot1dStpPort, + dot1dStpPortPriority, + dot1dStpPortState, + dot1dStpPortEnable, + dot1dStpPortPathCost, + dot1dStpPortDesignatedRoot, + dot1dStpPortDesignatedCost, + dot1dStpPortDesignatedBridge, + dot1dStpPortDesignatedPort, + dot1dStpPortForwardTransitions + } + STATUS current + DESCRIPTION + "Spanning Tree data for each port on this device." + ::= { dot1dGroups 4 } + +dot1dStpPortGroup2 OBJECT-GROUP + OBJECTS { + dot1dStpPort, + dot1dStpPortPriority, + dot1dStpPortState, + dot1dStpPortEnable, + dot1dStpPortDesignatedRoot, + dot1dStpPortDesignatedCost, + dot1dStpPortDesignatedBridge, + dot1dStpPortDesignatedPort, + dot1dStpPortForwardTransitions, + dot1dStpPortPathCost32 + } + STATUS current + DESCRIPTION + "Spanning Tree data for each port on this device." + ::= { dot1dGroups 5 } + +dot1dStpPortGroup3 OBJECT-GROUP + OBJECTS { + dot1dStpPortPathCost32 + } + STATUS current + DESCRIPTION + "Spanning Tree data for devices supporting 32-bit + path costs." + ::= { dot1dGroups 6 } + +-- ---------------------------------------------------------- -- +-- the dot1dTp group +-- ---------------------------------------------------------- -- + +dot1dTpBridgeGroup OBJECT-GROUP + OBJECTS { + dot1dTpLearnedEntryDiscards, + dot1dTpAgingTime + } + STATUS current + DESCRIPTION + "Bridge level Transparent Bridging data." + ::= { dot1dGroups 7 } + +dot1dTpFdbGroup OBJECT-GROUP + OBJECTS { + + dot1dTpFdbAddress, + dot1dTpFdbPort, + dot1dTpFdbStatus + } + STATUS current + DESCRIPTION + "Filtering Database information for the Bridge." + ::= { dot1dGroups 8 } + +dot1dTpGroup OBJECT-GROUP + OBJECTS { + dot1dTpPort, + dot1dTpPortMaxInfo, + dot1dTpPortInFrames, + dot1dTpPortOutFrames, + dot1dTpPortInDiscards + } + STATUS current + DESCRIPTION + "Dynamic Filtering Database information for each port of + the Bridge." + ::= { dot1dGroups 9 } + +-- ---------------------------------------------------------- -- +-- The Static (Destination-Address Filtering) Database +-- ---------------------------------------------------------- -- + +dot1dStaticGroup OBJECT-GROUP + OBJECTS { + dot1dStaticAddress, + dot1dStaticReceivePort, + dot1dStaticAllowedToGoTo, + dot1dStaticStatus + } + STATUS current + DESCRIPTION + "Static Filtering Database information for each port of + the Bridge." + ::= { dot1dGroups 10 } + +-- ---------------------------------------------------------- -- +-- The Trap Notification Group +-- ---------------------------------------------------------- -- + +dot1dNotificationGroup NOTIFICATION-GROUP + NOTIFICATIONS { + newRoot, + topologyChange + } + STATUS current + DESCRIPTION + "Group of objects describing notifications (traps)." + ::= { dot1dGroups 11 } + +-- ---------------------------------------------------------- -- +-- compliance statements +-- ---------------------------------------------------------- -- + +bridgeCompliance1493 MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for device support of bridging + services, as per RFC1493." + + MODULE + MANDATORY-GROUPS { + dot1dBaseBridgeGroup, + dot1dBasePortGroup + } + + GROUP dot1dStpBridgeGroup + DESCRIPTION + "Implementation of this group is mandatory for bridges + that support the Spanning Tree Protocol." + + GROUP dot1dStpPortGroup + DESCRIPTION + "Implementation of this group is mandatory for bridges + that support the Spanning Tree Protocol." + + GROUP dot1dTpBridgeGroup + DESCRIPTION + "Implementation of this group is mandatory for bridges + that support the transparent bridging mode. A + transparent or SRT bridge will implement this group." + + GROUP dot1dTpFdbGroup + DESCRIPTION + "Implementation of this group is mandatory for bridges + that support the transparent bridging mode. A + transparent or SRT bridge will implement this group." + + GROUP dot1dTpGroup + DESCRIPTION + "Implementation of this group is mandatory for bridges + + that support the transparent bridging mode. A + transparent or SRT bridge will implement this group." + + GROUP dot1dStaticGroup + DESCRIPTION + "Implementation of this group is optional." + + GROUP dot1dNotificationGroup + DESCRIPTION + "Implementation of this group is optional." + ::= { dot1dCompliances 1 } + +bridgeCompliance4188 MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for device support of bridging + services. This supports 32-bit Path Cost values and the + more restricted bridge and port priorities, as per IEEE + 802.1t. + + Full support for the 802.1D management objects requires that + the SNMPv2-MIB [RFC3418] objects sysDescr, and sysUpTime, as + well as the IF-MIB [RFC2863] objects ifIndex, ifType, + ifDescr, ifPhysAddress, and ifLastChange are implemented." + + MODULE + MANDATORY-GROUPS { + dot1dBaseBridgeGroup, + dot1dBasePortGroup + } + + GROUP dot1dStpBridgeGroup + DESCRIPTION + "Implementation of this group is mandatory for + bridges that support the Spanning Tree Protocol." + + OBJECT dot1dStpPriority + SYNTAX Integer32 (0|4096|8192|12288|16384|20480|24576 + |28672|32768|36864|40960|45056|49152 + |53248|57344|61440) + DESCRIPTION + "The possible values defined by IEEE 802.1t." + + GROUP dot1dStpPortGroup2 + DESCRIPTION + "Implementation of this group is mandatory for + bridges that support the Spanning Tree Protocol." + + GROUP dot1dStpPortGroup3 + DESCRIPTION + "Implementation of this group is mandatory for bridges + that support the Spanning Tree Protocol and 32-bit path + costs. In particular, this includes devices supporting + IEEE 802.1t and IEEE 802.1w." + + OBJECT dot1dStpPortPriority + SYNTAX Integer32 (0|16|32|48|64|80|96|112|128 + |144|160|176|192|208|224|240) + DESCRIPTION + "The possible values defined by IEEE 802.1t." + + GROUP dot1dTpBridgeGroup + DESCRIPTION + "Implementation of this group is mandatory for + bridges that support the transparent bridging + mode. A transparent or SRT bridge will implement + this group." + + GROUP dot1dTpFdbGroup + DESCRIPTION + "Implementation of this group is mandatory for + bridges that support the transparent bridging + mode. A transparent or SRT bridge will implement + this group." + + GROUP dot1dTpGroup + DESCRIPTION + "Implementation of this group is mandatory for + bridges that support the transparent bridging + mode. A transparent or SRT bridge will implement + this group." + + GROUP dot1dStaticGroup + DESCRIPTION + "Implementation of this group is optional." + + GROUP dot1dNotificationGroup + DESCRIPTION + "Implementation of this group is optional." + ::= { dot1dCompliances 2 } + +END diff --git a/mibs/DISMAN-EVENT-MIB.txt b/mibs/DISMAN-EVENT-MIB.txt new file mode 100644 index 0000000..f00c7cc --- /dev/null +++ b/mibs/DISMAN-EVENT-MIB.txt @@ -0,0 +1,1882 @@ +DISMAN-EVENT-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, + Integer32, Unsigned32, + NOTIFICATION-TYPE, Counter32, + Gauge32, mib-2, zeroDotZero FROM SNMPv2-SMI + TEXTUAL-CONVENTION, RowStatus, + TruthValue FROM SNMPv2-TC + + MODULE-COMPLIANCE, OBJECT-GROUP, + NOTIFICATION-GROUP FROM SNMPv2-CONF + sysUpTime FROM SNMPv2-MIB + SnmpTagValue FROM SNMP-TARGET-MIB + SnmpAdminString FROM SNMP-FRAMEWORK-MIB; + +dismanEventMIB MODULE-IDENTITY + LAST-UPDATED "200010160000Z" -- 16 October 2000 + ORGANIZATION "IETF Distributed Management Working Group" + CONTACT-INFO "Ramanathan Kavasseri + Cisco Systems, Inc. + 170 West Tasman Drive, + San Jose CA 95134-1706. + Phone: +1 408 526 4527 + Email: ramk@cisco.com" + DESCRIPTION + "The MIB module for defining event triggers and actions + for network management purposes." +-- Revision History + + REVISION "200010160000Z" -- 16 October 2000 + DESCRIPTION "This is the initial version of this MIB. + Published as RFC 2981" + ::= { mib-2 88 } + +dismanEventMIBObjects OBJECT IDENTIFIER ::= { dismanEventMIB 1 } + +-- Management Triggered Event (MTE) objects + +mteResource OBJECT IDENTIFIER ::= { dismanEventMIBObjects 1 } +mteTrigger OBJECT IDENTIFIER ::= { dismanEventMIBObjects 2 } +mteObjects OBJECT IDENTIFIER ::= { dismanEventMIBObjects 3 } +mteEvent OBJECT IDENTIFIER ::= { dismanEventMIBObjects 4 } + +-- +-- Textual Conventions +-- + +FailureReason ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Reasons for failures in an attempt to perform a management + request. + + The first group of errors, numbered less than 0, are related + to problems in sending the request. The existence of a + particular error code here does not imply that all + implementations are capable of sensing that error and + + returning that code. + + The second group, numbered greater than 0, are copied + directly from SNMP protocol operations and are intended to + carry exactly the meanings defined for the protocol as returned + in an SNMP response. + + localResourceLack some local resource such as memory + lacking or + mteResourceSampleInstanceMaximum + exceeded + badDestination unrecognized domain name or otherwise + invalid destination address + destinationUnreachable can't get to destination address + noResponse no response to SNMP request + badType the data syntax of a retrieved object + as not as expected + sampleOverrun another sample attempt occurred before + the previous one completed" + SYNTAX INTEGER { localResourceLack(-1), + badDestination(-2), + destinationUnreachable(-3), + noResponse(-4), + badType(-5), + sampleOverrun(-6), + noError(0), + tooBig(1), + noSuchName(2), + badValue(3), + readOnly(4), + genErr(5), + noAccess(6), + wrongType(7), + wrongLength(8), + wrongEncoding(9), + wrongValue(10), + noCreation(11), + inconsistentValue(12), + resourceUnavailable(13), + commitFailed(14), + undoFailed(15), + authorizationError(16), + notWritable(17), + inconsistentName(18) } +-- + +-- Resource Control Section +-- + +mteResourceSampleMinimum OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + UNITS "seconds" + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The minimum mteTriggerFrequency this system will + accept. A system may use the larger values of this minimum to + lessen the impact of constant sampling. For larger + sampling intervals the system samples less often and + suffers less overhead. This object provides a way to enforce + such lower overhead for all triggers created after it is + set. + + Unless explicitly resource limited, a system's value for + this object SHOULD be 1, allowing as small as a 1 second + interval for ongoing trigger sampling. + + Changing this value will not invalidate an existing setting + of mteTriggerFrequency." + ::= { mteResource 1 } + +mteResourceSampleInstanceMaximum OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "instances" + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The maximum number of instance entries this system will + support for sampling. + + These are the entries that maintain state, one for each + instance of each sampled object as selected by + mteTriggerValueID. Note that wildcarded objects result + in multiple instances of this state. + + A value of 0 indicates no preset limit, that is, the limit + is dynamic based on system operation and resources. + + Unless explicitly resource limited, a system's value for + this object SHOULD be 0. + + Changing this value will not eliminate or inhibit existing + sample state but could prevent allocation of additional state + information." + ::= { mteResource 2 } + +mteResourceSampleInstances OBJECT-TYPE + SYNTAX Gauge32 + UNITS "instances" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of currently active instance entries as + defined for mteResourceSampleInstanceMaximum." + ::= { mteResource 3 } + +mteResourceSampleInstancesHigh OBJECT-TYPE + SYNTAX Gauge32 + UNITS "instances" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The highest value of mteResourceSampleInstances that has + occurred since initialization of the management system." + ::= { mteResource 4 } + +mteResourceSampleInstanceLacks OBJECT-TYPE + SYNTAX Counter32 + UNITS "instances" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times this system could not take a new sample + because that allocation would have exceeded the limit set by + mteResourceSampleInstanceMaximum." + ::= { mteResource 5 } + +-- +-- Trigger Section +-- + +-- Counters + +mteTriggerFailures OBJECT-TYPE + SYNTAX Counter32 + UNITS "failures" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times an attempt to check for a trigger + condition has failed. This counts individually for each + attempt in a group of targets or each attempt for a + + wildcarded object." + ::= { mteTrigger 1 } + +-- +-- Trigger Table +-- + +mteTriggerTable OBJECT-TYPE + SYNTAX SEQUENCE OF MteTriggerEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of management event trigger information." + ::= { mteTrigger 2 } + +mteTriggerEntry OBJECT-TYPE + SYNTAX MteTriggerEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a single trigger. Applications create and + delete entries using mteTriggerEntryStatus." + INDEX { mteOwner, IMPLIED mteTriggerName } + ::= { mteTriggerTable 1 } + +MteTriggerEntry ::= SEQUENCE { + mteOwner SnmpAdminString, + mteTriggerName SnmpAdminString, + mteTriggerComment SnmpAdminString, + mteTriggerTest BITS, + mteTriggerSampleType INTEGER, + mteTriggerValueID OBJECT IDENTIFIER, + mteTriggerValueIDWildcard TruthValue, + mteTriggerTargetTag SnmpTagValue, + mteTriggerContextName SnmpAdminString, + mteTriggerContextNameWildcard TruthValue, + mteTriggerFrequency Unsigned32, + mteTriggerObjectsOwner SnmpAdminString, + mteTriggerObjects SnmpAdminString, + mteTriggerEnabled TruthValue, + mteTriggerEntryStatus RowStatus +} + +mteOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The owner of this entry. The exact semantics of this + string are subject to the security policy defined by the + security administrator." + ::= { mteTriggerEntry 1 } + +mteTriggerName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (1..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A locally-unique, administratively assigned name for the + trigger within the scope of mteOwner." + ::= { mteTriggerEntry 2 } + +mteTriggerComment OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A description of the trigger's function and use." + DEFVAL { ''H } + ::= { mteTriggerEntry 3 } + +mteTriggerTest OBJECT-TYPE + SYNTAX BITS { existence(0), boolean(1), threshold(2) } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The type of trigger test to perform. For 'boolean' and + 'threshold' tests, the object at mteTriggerValueID MUST + evaluate to an integer, that is, anything that ends up encoded + for transmission (that is, in BER, not ASN.1) as an integer. + + For 'existence', the specific test is as selected by + mteTriggerExistenceTest. When an object appears, vanishes + or changes value, the trigger fires. If the object's + appearance caused the trigger firing, the object MUST + vanish before the trigger can be fired again for it, and + vice versa. If the trigger fired due to a change in the + object's value, it will be fired again on every successive + value change for that object. + + For 'boolean', the specific test is as selected by + mteTriggerBooleanTest. If the test result is true the trigger + fires. The trigger will not fire again until the value has + become false and come back to true. + + For 'threshold' the test works as described below for + + mteTriggerThresholdStartup, mteTriggerThresholdRising, and + mteTriggerThresholdFalling. + + Note that combining 'boolean' and 'threshold' tests on the + same object may be somewhat redundant." + DEFVAL { { boolean } } + ::= { mteTriggerEntry 4 } + +mteTriggerSampleType OBJECT-TYPE + SYNTAX INTEGER { absoluteValue(1), deltaValue(2) } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The type of sampling to perform. + + An 'absoluteValue' sample requires only a single sample to be + meaningful, and is exactly the value of the object at + mteTriggerValueID at the sample time. + + A 'deltaValue' requires two samples to be meaningful and is + thus not available for testing until the second and subsequent + samples after the object at mteTriggerValueID is first found + to exist. It is the difference between the two samples. For + unsigned values it is always positive, based on unsigned + arithmetic. For signed values it can be positive or negative. + + For SNMP counters to be meaningful they should be sampled as a + 'deltaValue'. + + For 'deltaValue' mteTriggerDeltaTable contains further + parameters. + + If only 'existence' is set in mteTriggerTest this object has + no meaning." + DEFVAL { absoluteValue } + ::= { mteTriggerEntry 5 } + +mteTriggerValueID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The object identifier of the MIB object to sample to see + if the trigger should fire. + + This may be wildcarded by truncating all or part of the + instance portion, in which case the value is obtained + as if with a GetNext function, checking multiple values + + if they exist. If such wildcarding is applied, + mteTriggerValueIDWildcard must be 'true' and if not it must + be 'false'. + + Bad object identifiers or a mismatch between truncating the + identifier and the value of mteTriggerValueIDWildcard result + in operation as one would expect when providing the wrong + identifier to a Get or GetNext operation. The Get will fail + or get the wrong object. The GetNext will indeed get whatever + is next, proceeding until it runs past the initial part of the + identifier and perhaps many unintended objects for confusing + results. If the value syntax of those objects is not usable, + that results in a 'badType' error that terminates the scan. + + Each instance that fills the wildcard is independent of any + additional instances, that is, wildcarded objects operate + as if there were a separate table entry for each instance + that fills the wildcard without having to actually predict + all possible instances ahead of time." + DEFVAL { zeroDotZero } + ::= { mteTriggerEntry 6 } + +mteTriggerValueIDWildcard OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Control for whether mteTriggerValueID is to be treated as + fully-specified or wildcarded, with 'true' indicating wildcard." + DEFVAL { false } + ::= { mteTriggerEntry 7 } + +mteTriggerTargetTag OBJECT-TYPE + SYNTAX SnmpTagValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The tag for the target(s) from which to obtain the condition + for a trigger check. + + A length of 0 indicates the local system. In this case, + access to the objects indicated by mteTriggerValueID is under + the security credentials of the requester that set + mteTriggerEntryStatus to 'active'. Those credentials are the + input parameters for isAccessAllowed from the Architecture for + Describing SNMP Management Frameworks. + + Otherwise access rights are checked according to the security + + parameters resulting from the tag." + DEFVAL { ''H } + ::= { mteTriggerEntry 8 } + +mteTriggerContextName OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The management context from which to obtain mteTriggerValueID. + + This may be wildcarded by leaving characters off the end. For + example use 'Repeater' to wildcard to 'Repeater1', + 'Repeater2', 'Repeater-999.87b', and so on. To indicate such + wildcarding is intended, mteTriggerContextNameWildcard must + be 'true'. + + Each instance that fills the wildcard is independent of any + additional instances, that is, wildcarded objects operate + as if there were a separate table entry for each instance + that fills the wildcard without having to actually predict + all possible instances ahead of time. + + Operation of this feature assumes that the local system has a + list of available contexts against which to apply the + wildcard. If the objects are being read from the local + system, this is clearly the system's own list of contexts. + For a remote system a local version of such a list is not + defined by any current standard and may not be available, so + this function MAY not be supported." + DEFVAL { ''H } + ::= { mteTriggerEntry 9 } + +mteTriggerContextNameWildcard OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Control for whether mteTriggerContextName is to be treated as + fully-specified or wildcarded, with 'true' indicating wildcard." + DEFVAL { false } + ::= { mteTriggerEntry 10 } + +mteTriggerFrequency OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "seconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The number of seconds to wait between trigger samples. To + encourage consistency in sampling, the interval is measured + from the beginning of one check to the beginning of the next + and the timer is restarted immediately when it expires, not + when the check completes. + + If the next sample begins before the previous one completed the + system may either attempt to make the check or treat this as an + error condition with the error 'sampleOverrun'. + + A frequency of 0 indicates instantaneous recognition of the + condition. This is not possible in many cases, but may + be supported in cases where it makes sense and the system is + able to do so. This feature allows the MIB to be used in + implementations where such interrupt-driven behavior is + possible and is not likely to be supported for all MIB objects + even then since such sampling generally has to be tightly + integrated into low-level code. + + Systems that can support this SHOULD document those cases + where it can be used. In cases where it can not, setting this + object to 0 should be disallowed." + DEFVAL { 600 } + ::= { mteTriggerEntry 11 } + +mteTriggerObjectsOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "To go with mteTriggerObjects, the mteOwner of a group of + objects from mteObjectsTable." + DEFVAL { ''H } + ::= { mteTriggerEntry 12 } + +mteTriggerObjects OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The mteObjectsName of a group of objects from + mteObjectsTable. These objects are to be added to any + Notification resulting from the firing of this trigger. + + A list of objects may also be added based on the event or on + the value of mteTriggerTest. + + A length of 0 indicates no additional objects." + DEFVAL { ''H } + ::= { mteTriggerEntry 13 } + +mteTriggerEnabled OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A control to allow a trigger to be configured but not used. + When the value is 'false' the trigger is not sampled." + DEFVAL { false } + ::= { mteTriggerEntry 14 } + +mteTriggerEntryStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The control that allows creation and deletion of entries. + Once made active an entry may not be modified except to + delete it." + ::= { mteTriggerEntry 15 } + +-- +-- Trigger Delta Table +-- + +mteTriggerDeltaTable OBJECT-TYPE + SYNTAX SEQUENCE OF MteTriggerDeltaEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of management event trigger information for delta + sampling." + ::= { mteTrigger 3 } + +mteTriggerDeltaEntry OBJECT-TYPE + SYNTAX MteTriggerDeltaEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a single trigger's delta sampling. Entries + automatically exist in this this table for each mteTriggerEntry + that has mteTriggerSampleType set to 'deltaValue'." + INDEX { mteOwner, IMPLIED mteTriggerName } + ::= { mteTriggerDeltaTable 1 } + +MteTriggerDeltaEntry ::= SEQUENCE { + mteTriggerDeltaDiscontinuityID OBJECT IDENTIFIER, + mteTriggerDeltaDiscontinuityIDWildcard TruthValue, + mteTriggerDeltaDiscontinuityIDType INTEGER +} + +sysUpTimeInstance OBJECT IDENTIFIER ::= { sysUpTime 0 } + +mteTriggerDeltaDiscontinuityID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The OBJECT IDENTIFIER (OID) of a TimeTicks, TimeStamp, or + DateAndTime object that indicates a discontinuity in the value + at mteTriggerValueID. + + The OID may be for a leaf object (e.g. sysUpTime.0) or may + be wildcarded to match mteTriggerValueID. + + This object supports normal checking for a discontinuity in a + counter. Note that if this object does not point to sysUpTime + discontinuity checking MUST still check sysUpTime for an overall + discontinuity. + + If the object identified is not accessible the sample attempt + is in error, with the error code as from an SNMP request. + + Bad object identifiers or a mismatch between truncating the + identifier and the value of mteDeltaDiscontinuityIDWildcard + result in operation as one would expect when providing the + wrong identifier to a Get operation. The Get will fail or get + the wrong object. If the value syntax of those objects is not + usable, that results in an error that terminates the sample + with a 'badType' error code." + DEFVAL { sysUpTimeInstance } + ::= { mteTriggerDeltaEntry 1 } + +mteTriggerDeltaDiscontinuityIDWildcard OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "Control for whether mteTriggerDeltaDiscontinuityID is to be + treated as fully-specified or wildcarded, with 'true' + indicating wildcard. Note that the value of this object will + be the same as that of the corresponding instance of + mteTriggerValueIDWildcard when the corresponding + + mteTriggerSampleType is 'deltaValue'." + DEFVAL { false } + ::= { mteTriggerDeltaEntry 2 } + +mteTriggerDeltaDiscontinuityIDType OBJECT-TYPE + SYNTAX INTEGER { timeTicks(1), timeStamp(2), dateAndTime(3) } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The value 'timeTicks' indicates the + mteTriggerDeltaDiscontinuityID of this row is of syntax + TimeTicks. The value 'timeStamp' indicates syntax TimeStamp. + The value 'dateAndTime' indicates syntax DateAndTime." + DEFVAL { timeTicks } + ::= { mteTriggerDeltaEntry 3 } + +-- +-- Trigger Existence Table +-- + +mteTriggerExistenceTable OBJECT-TYPE + SYNTAX SEQUENCE OF MteTriggerExistenceEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of management event trigger information for existence + triggers." + ::= { mteTrigger 4 } + +mteTriggerExistenceEntry OBJECT-TYPE + SYNTAX MteTriggerExistenceEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a single existence trigger. Entries + automatically exist in this this table for each mteTriggerEntry + that has 'existence' set in mteTriggerTest." + INDEX { mteOwner, IMPLIED mteTriggerName } + ::= { mteTriggerExistenceTable 1 } + +MteTriggerExistenceEntry ::= SEQUENCE { + mteTriggerExistenceTest BITS, + mteTriggerExistenceStartup BITS, + mteTriggerExistenceObjectsOwner SnmpAdminString, + mteTriggerExistenceObjects SnmpAdminString, + mteTriggerExistenceEventOwner SnmpAdminString, + mteTriggerExistenceEvent SnmpAdminString +} + +mteTriggerExistenceTest OBJECT-TYPE + SYNTAX BITS { present(0), absent(1), changed(2) } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The type of existence test to perform. The trigger fires + when the object at mteTriggerValueID is seen to go from + present to absent, from absent to present, or to have it's + value changed, depending on which tests are selected: + + present(0) - when this test is selected, the trigger fires + when the mteTriggerValueID object goes from absent to present. + + absent(1) - when this test is selected, the trigger fires + when the mteTriggerValueID object goes from present to absent. + changed(2) - when this test is selected, the trigger fires + the mteTriggerValueID object value changes. + + Once the trigger has fired for either presence or absence it + will not fire again for that state until the object has been + to the other state. " + DEFVAL { { present, absent } } + ::= { mteTriggerExistenceEntry 1 } + +mteTriggerExistenceStartup OBJECT-TYPE + SYNTAX BITS { present(0), absent(1) } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "Control for whether an event may be triggered when this entry + is first set to 'active' and the test specified by + mteTriggerExistenceTest is true. Setting an option causes + that trigger to fire when its test is true." + DEFVAL { { present, absent } } + ::= { mteTriggerExistenceEntry 2 } + +mteTriggerExistenceObjectsOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "To go with mteTriggerExistenceObjects, the mteOwner of a + group of objects from mteObjectsTable." + DEFVAL { ''H } + ::= { mteTriggerExistenceEntry 3 } + +mteTriggerExistenceObjects OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The mteObjectsName of a group of objects from + mteObjectsTable. These objects are to be added to any + Notification resulting from the firing of this trigger for + this test. + + A list of objects may also be added based on the overall + trigger, the event or other settings in mteTriggerTest. + + A length of 0 indicates no additional objects." + DEFVAL { ''H } + ::= { mteTriggerExistenceEntry 4 } + +mteTriggerExistenceEventOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "To go with mteTriggerExistenceEvent, the mteOwner of an event + entry from the mteEventTable." + DEFVAL { ''H } + ::= { mteTriggerExistenceEntry 5 } + +mteTriggerExistenceEvent OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The mteEventName of the event to invoke when mteTriggerType is + 'existence' and this trigger fires. A length of 0 indicates no + event." + DEFVAL { ''H } + ::= { mteTriggerExistenceEntry 6 } + +-- +-- Trigger Boolean Table +-- + +mteTriggerBooleanTable OBJECT-TYPE + SYNTAX SEQUENCE OF MteTriggerBooleanEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of management event trigger information for boolean + triggers." + ::= { mteTrigger 5 } + +mteTriggerBooleanEntry OBJECT-TYPE + SYNTAX MteTriggerBooleanEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a single boolean trigger. Entries + automatically exist in this this table for each mteTriggerEntry + that has 'boolean' set in mteTriggerTest." + INDEX { mteOwner, IMPLIED mteTriggerName } + ::= { mteTriggerBooleanTable 1 } + +MteTriggerBooleanEntry ::= SEQUENCE { + mteTriggerBooleanComparison INTEGER, + mteTriggerBooleanValue Integer32, + mteTriggerBooleanStartup TruthValue, + mteTriggerBooleanObjectsOwner SnmpAdminString, + mteTriggerBooleanObjects SnmpAdminString, + mteTriggerBooleanEventOwner SnmpAdminString, + mteTriggerBooleanEvent SnmpAdminString +} + +mteTriggerBooleanComparison OBJECT-TYPE + SYNTAX INTEGER { unequal(1), equal(2), + less(3), lessOrEqual(4), + greater(5), greaterOrEqual(6) } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The type of boolean comparison to perform. + + The value at mteTriggerValueID is compared to + mteTriggerBooleanValue, so for example if + mteTriggerBooleanComparison is 'less' the result would be true + if the value at mteTriggerValueID is less than the value of + mteTriggerBooleanValue." + DEFVAL { unequal } + ::= { mteTriggerBooleanEntry 1 } + +mteTriggerBooleanValue OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The value to use for the test specified by + mteTriggerBooleanTest." + DEFVAL { 0 } + ::= { mteTriggerBooleanEntry 2 } + +mteTriggerBooleanStartup OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "Control for whether an event may be triggered when this entry + is first set to 'active' or a new instance of the object at + mteTriggerValueID is found and the test specified by + mteTriggerBooleanComparison is true. In that case an event is + triggered if mteTriggerBooleanStartup is 'true'." + DEFVAL { true } + ::= { mteTriggerBooleanEntry 3 } + +mteTriggerBooleanObjectsOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "To go with mteTriggerBooleanObjects, the mteOwner of a group + of objects from mteObjectsTable." + DEFVAL { ''H } + ::= { mteTriggerBooleanEntry 4 } + +mteTriggerBooleanObjects OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The mteObjectsName of a group of objects from + mteObjectsTable. These objects are to be added to any + Notification resulting from the firing of this trigger for + this test. + + A list of objects may also be added based on the overall + trigger, the event or other settings in mteTriggerTest. + + A length of 0 indicates no additional objects." + DEFVAL { ''H } + ::= { mteTriggerBooleanEntry 5 } + +mteTriggerBooleanEventOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "To go with mteTriggerBooleanEvent, the mteOwner of an event + entry from mteEventTable." + DEFVAL { ''H } + ::= { mteTriggerBooleanEntry 6 } + +mteTriggerBooleanEvent OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The mteEventName of the event to invoke when mteTriggerType is + 'boolean' and this trigger fires. A length of 0 indicates no + event." + DEFVAL { ''H } + ::= { mteTriggerBooleanEntry 7 } + +-- +-- Trigger Threshold Table +-- + +mteTriggerThresholdTable OBJECT-TYPE + SYNTAX SEQUENCE OF MteTriggerThresholdEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of management event trigger information for threshold + triggers." + ::= { mteTrigger 6 } + +mteTriggerThresholdEntry OBJECT-TYPE + SYNTAX MteTriggerThresholdEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a single threshold trigger. Entries + automatically exist in this table for each mteTriggerEntry + that has 'threshold' set in mteTriggerTest." + INDEX { mteOwner, IMPLIED mteTriggerName } + ::= { mteTriggerThresholdTable 1 } + +MteTriggerThresholdEntry ::= SEQUENCE { + mteTriggerThresholdStartup INTEGER, + mteTriggerThresholdRising Integer32, + mteTriggerThresholdFalling Integer32, + mteTriggerThresholdDeltaRising Integer32, + mteTriggerThresholdDeltaFalling Integer32, + mteTriggerThresholdObjectsOwner SnmpAdminString, + mteTriggerThresholdObjects SnmpAdminString, + mteTriggerThresholdRisingEventOwner SnmpAdminString, + mteTriggerThresholdRisingEvent SnmpAdminString, + mteTriggerThresholdFallingEventOwner SnmpAdminString, + mteTriggerThresholdFallingEvent SnmpAdminString, + mteTriggerThresholdDeltaRisingEventOwner SnmpAdminString, + mteTriggerThresholdDeltaRisingEvent SnmpAdminString, + mteTriggerThresholdDeltaFallingEventOwner SnmpAdminString, + mteTriggerThresholdDeltaFallingEvent SnmpAdminString +} + +mteTriggerThresholdStartup OBJECT-TYPE + SYNTAX INTEGER { rising(1), falling(2), risingOrFalling(3) } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The event that may be triggered when this entry is first + set to 'active' and a new instance of the object at + mteTriggerValueID is found. If the first sample after this + instance becomes active is greater than or equal to + mteTriggerThresholdRising and mteTriggerThresholdStartup is + equal to 'rising' or 'risingOrFalling', then one + mteTriggerThresholdRisingEvent is triggered for that instance. + If the first sample after this entry becomes active is less + than or equal to mteTriggerThresholdFalling and + mteTriggerThresholdStartup is equal to 'falling' or + 'risingOrFalling', then one mteTriggerThresholdRisingEvent is + triggered for that instance." + DEFVAL { risingOrFalling } + ::= { mteTriggerThresholdEntry 1 } + +mteTriggerThresholdRising OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "A threshold value to check against if mteTriggerType is + 'threshold'. + + When the current sampled value is greater than or equal to + this threshold, and the value at the last sampling interval + was less than this threshold, one + mteTriggerThresholdRisingEvent is triggered. That event is + also triggered if the first sample after this entry becomes + active is greater than or equal to this threshold and + mteTriggerThresholdStartup is equal to 'rising' or + 'risingOrFalling'. + + After a rising event is generated, another such event is not + triggered until the sampled value falls below this threshold + and reaches mteTriggerThresholdFalling." + DEFVAL { 0 } + ::= { mteTriggerThresholdEntry 2 } + +mteTriggerThresholdFalling OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "A threshold value to check against if mteTriggerType is + 'threshold'. + + When the current sampled value is less than or equal to this + threshold, and the value at the last sampling interval was + greater than this threshold, one + mteTriggerThresholdFallingEvent is triggered. That event is + also triggered if the first sample after this entry becomes + active is less than or equal to this threshold and + mteTriggerThresholdStartup is equal to 'falling' or + 'risingOrFalling'. + + After a falling event is generated, another such event is not + triggered until the sampled value rises above this threshold + and reaches mteTriggerThresholdRising." + DEFVAL { 0 } + ::= { mteTriggerThresholdEntry 3 } + +mteTriggerThresholdDeltaRising OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "A threshold value to check against if mteTriggerType is + 'threshold'. + + When the delta value (difference) between the current sampled + value (value(n)) and the previous sampled value (value(n-1)) + is greater than or equal to this threshold, + and the delta value calculated at the last sampling interval + (i.e. value(n-1) - value(n-2)) was less than this threshold, + one mteTriggerThresholdDeltaRisingEvent is triggered. That event + is also triggered if the first delta value calculated after this + entry becomes active, i.e. value(2) - value(1), where value(1) + is the first sample taken of that instance, is greater than or + equal to this threshold. + + After a rising event is generated, another such event is not + triggered until the delta value falls below this threshold and + reaches mteTriggerThresholdDeltaFalling." + DEFVAL { 0 } + ::= { mteTriggerThresholdEntry 4 } + +mteTriggerThresholdDeltaFalling OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "A threshold value to check against if mteTriggerType is + 'threshold'. + + When the delta value (difference) between the current sampled + value (value(n)) and the previous sampled value (value(n-1)) + is less than or equal to this threshold, + and the delta value calculated at the last sampling interval + (i.e. value(n-1) - value(n-2)) was greater than this threshold, + one mteTriggerThresholdDeltaFallingEvent is triggered. That event + is also triggered if the first delta value calculated after this + entry becomes active, i.e. value(2) - value(1), where value(1) + is the first sample taken of that instance, is less than or + equal to this threshold. + + After a falling event is generated, another such event is not + triggered until the delta value falls below this threshold and + reaches mteTriggerThresholdDeltaRising." + DEFVAL { 0 } + ::= { mteTriggerThresholdEntry 5 } + +mteTriggerThresholdObjectsOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "To go with mteTriggerThresholdObjects, the mteOwner of a group + of objects from mteObjectsTable." + DEFVAL { ''H } + ::= { mteTriggerThresholdEntry 6 } + +mteTriggerThresholdObjects OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The mteObjectsName of a group of objects from + mteObjectsTable. These objects are to be added to any + Notification resulting from the firing of this trigger for + this test. + + A list of objects may also be added based on the overall + + trigger, the event or other settings in mteTriggerTest. + + A length of 0 indicates no additional objects." + DEFVAL { ''H } + ::= { mteTriggerThresholdEntry 7 } + +mteTriggerThresholdRisingEventOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "To go with mteTriggerThresholdRisingEvent, the mteOwner of an + event entry from mteEventTable." + DEFVAL { ''H } + ::= { mteTriggerThresholdEntry 8 } + +mteTriggerThresholdRisingEvent OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The mteEventName of the event to invoke when mteTriggerType is + 'threshold' and this trigger fires based on + mteTriggerThresholdRising. A length of 0 indicates no event." + DEFVAL { ''H } + ::= { mteTriggerThresholdEntry 9 } + +mteTriggerThresholdFallingEventOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "To go with mteTriggerThresholdFallingEvent, the mteOwner of an + event entry from mteEventTable." + DEFVAL { ''H } + ::= { mteTriggerThresholdEntry 10 } + +mteTriggerThresholdFallingEvent OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The mteEventName of the event to invoke when mteTriggerType is + 'threshold' and this trigger fires based on + mteTriggerThresholdFalling. A length of 0 indicates no event." + DEFVAL { ''H } + ::= { mteTriggerThresholdEntry 11 } + +mteTriggerThresholdDeltaRisingEventOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "To go with mteTriggerThresholdDeltaRisingEvent, the mteOwner + of an event entry from mteEventTable." + DEFVAL { ''H } + ::= { mteTriggerThresholdEntry 12 } + +mteTriggerThresholdDeltaRisingEvent OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The mteEventName of the event to invoke when mteTriggerType is + 'threshold' and this trigger fires based on + mteTriggerThresholdDeltaRising. A length of 0 indicates + no event." + DEFVAL { ''H } + ::= { mteTriggerThresholdEntry 13 } + +mteTriggerThresholdDeltaFallingEventOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "To go with mteTriggerThresholdDeltaFallingEvent, the mteOwner + of an event entry from mteEventTable." + DEFVAL { ''H } + ::= { mteTriggerThresholdEntry 14 } + +mteTriggerThresholdDeltaFallingEvent OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The mteEventName of the event to invoke when mteTriggerType is + 'threshold' and this trigger fires based on + mteTriggerThresholdDeltaFalling. A length of 0 indicates + no event." + DEFVAL { ''H } + ::= { mteTriggerThresholdEntry 15 } + +-- +-- Objects Table +-- + +mteObjectsTable OBJECT-TYPE + SYNTAX SEQUENCE OF MteObjectsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of objects that can be added to notifications based + on the trigger, trigger test, or event, as pointed to by + entries in those tables." + ::= { mteObjects 1 } + +mteObjectsEntry OBJECT-TYPE + SYNTAX MteObjectsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A group of objects. Applications create and delete entries + using mteObjectsEntryStatus. + + When adding objects to a notification they are added in the + lexical order of their index in this table. Those associated + with a trigger come first, then trigger test, then event." + INDEX { mteOwner, mteObjectsName, mteObjectsIndex } + ::= { mteObjectsTable 1 } + +MteObjectsEntry ::= SEQUENCE { + mteObjectsName SnmpAdminString, + mteObjectsIndex Unsigned32, + mteObjectsID OBJECT IDENTIFIER, + mteObjectsIDWildcard TruthValue, + mteObjectsEntryStatus RowStatus + } + +mteObjectsName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (1..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A locally-unique, administratively assigned name for a group + of objects." + ::= { mteObjectsEntry 1 } + +mteObjectsIndex OBJECT-TYPE + SYNTAX Unsigned32 (1..4294967295) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An arbitrary integer for the purpose of identifying + individual objects within a mteObjectsName group. + + Objects within a group are placed in the notification in the + numerical order of this index. + + Groups are placed in the notification in the order of the + selections for overall trigger, trigger test, and event. + Within trigger test they are in the same order as the + numerical values of the bits defined for mteTriggerTest. + + Bad object identifiers or a mismatch between truncating the + identifier and the value of mteDeltaDiscontinuityIDWildcard + result in operation as one would expect when providing the + wrong identifier to a Get operation. The Get will fail or get + the wrong object. If the object is not available it is omitted + from the notification." + ::= { mteObjectsEntry 2 } + +mteObjectsID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The object identifier of a MIB object to add to a + Notification that results from the firing of a trigger. + + This may be wildcarded by truncating all or part of the + instance portion, in which case the instance portion of the + OID for obtaining this object will be the same as that used + in obtaining the mteTriggerValueID that fired. If such + wildcarding is applied, mteObjectsIDWildcard must be + 'true' and if not it must be 'false'. + + Each instance that fills the wildcard is independent of any + additional instances, that is, wildcarded objects operate + as if there were a separate table entry for each instance + that fills the wildcard without having to actually predict + all possible instances ahead of time." + DEFVAL { zeroDotZero } + ::= { mteObjectsEntry 3 } + +mteObjectsIDWildcard OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Control for whether mteObjectsID is to be treated as + fully-specified or wildcarded, with 'true' indicating wildcard." + DEFVAL { false } + ::= { mteObjectsEntry 4 } + +mteObjectsEntryStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The control that allows creation and deletion of entries. + Once made active an entry MAY not be modified except to + delete it." + ::= { mteObjectsEntry 5 } + +-- +-- Event Section +-- + +-- Counters + +mteEventFailures OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times an attempt to invoke an event + has failed. This counts individually for each + attempt in a group of targets or each attempt for a + wildcarded trigger object." + ::= { mteEvent 1 } + +-- +-- Event Table +-- + +mteEventTable OBJECT-TYPE + SYNTAX SEQUENCE OF MteEventEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of management event action information." + ::= { mteEvent 2 } + +mteEventEntry OBJECT-TYPE + SYNTAX MteEventEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a single event. Applications create and + delete entries using mteEventEntryStatus." + INDEX { mteOwner, IMPLIED mteEventName } + ::= { mteEventTable 1 } + +MteEventEntry ::= SEQUENCE { + mteEventName SnmpAdminString, + mteEventComment SnmpAdminString, + mteEventActions BITS, + mteEventEnabled TruthValue, + mteEventEntryStatus RowStatus + } + +mteEventName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (1..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A locally-unique, administratively assigned name for the + event." + ::= { mteEventEntry 1 } + +mteEventComment OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A description of the event's function and use." + DEFVAL { ''H } + ::= { mteEventEntry 2 } + +mteEventActions OBJECT-TYPE + SYNTAX BITS { notification(0), set(1) } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The actions to perform when this event occurs. + + For 'notification', Traps and/or Informs are sent according + to the configuration in the SNMP Notification MIB. + + For 'set', an SNMP Set operation is performed according to + control values in this entry." + DEFVAL { {} } -- No bits set. + ::= { mteEventEntry 3 } + +mteEventEnabled OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A control to allow an event to be configured but not used. + When the value is 'false' the event does not execute even if + + triggered." + DEFVAL { false } + ::= { mteEventEntry 4 } + +mteEventEntryStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The control that allows creation and deletion of entries. + Once made active an entry MAY not be modified except to + delete it." + ::= { mteEventEntry 5 } + +-- +-- Event Notification Table +-- + +mteEventNotificationTable OBJECT-TYPE + SYNTAX SEQUENCE OF MteEventNotificationEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of information about notifications to be sent as a + consequence of management events." + ::= { mteEvent 3 } + +mteEventNotificationEntry OBJECT-TYPE + SYNTAX MteEventNotificationEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a single event's notification. Entries + automatically exist in this this table for each mteEventEntry + that has 'notification' set in mteEventActions." + INDEX { mteOwner, IMPLIED mteEventName } + ::= { mteEventNotificationTable 1 } + +MteEventNotificationEntry ::= SEQUENCE { + mteEventNotification OBJECT IDENTIFIER, + mteEventNotificationObjectsOwner SnmpAdminString, + mteEventNotificationObjects SnmpAdminString + } + +mteEventNotification OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The object identifier from the NOTIFICATION-TYPE for the + notification to use if metEventActions has 'notification' set." + DEFVAL { zeroDotZero } + ::= { mteEventNotificationEntry 1 } + +mteEventNotificationObjectsOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "To go with mteEventNotificationObjects, the mteOwner of a + group of objects from mteObjectsTable." + DEFVAL { ''H } + ::= { mteEventNotificationEntry 2 } + +mteEventNotificationObjects OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The mteObjectsName of a group of objects from + mteObjectsTable if mteEventActions has 'notification' set. + These objects are to be added to any Notification generated by + this event. + + Objects may also be added based on the trigger that stimulated + the event. + + A length of 0 indicates no additional objects." + DEFVAL { ''H } + ::= { mteEventNotificationEntry 3 } + +-- +-- Event Set Table +-- + +mteEventSetTable OBJECT-TYPE + SYNTAX SEQUENCE OF MteEventSetEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of management event action information." + ::= { mteEvent 4 } + +mteEventSetEntry OBJECT-TYPE + SYNTAX MteEventSetEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a single event's set option. Entries + automatically exist in this this table for each mteEventEntry + that has 'set' set in mteEventActions." + INDEX { mteOwner, IMPLIED mteEventName } + ::= { mteEventSetTable 1 } + +MteEventSetEntry ::= SEQUENCE { + mteEventSetObject OBJECT IDENTIFIER, + mteEventSetObjectWildcard TruthValue, + mteEventSetValue Integer32, + mteEventSetTargetTag SnmpTagValue, + mteEventSetContextName SnmpAdminString, + mteEventSetContextNameWildcard TruthValue + } + +mteEventSetObject OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The object identifier from the MIB object to set if + mteEventActions has 'set' set. + + This object identifier may be wildcarded by leaving + sub-identifiers off the end, in which case + nteEventSetObjectWildCard must be 'true'. + + If mteEventSetObject is wildcarded the instance used to set the + object to which it points is the same as the instance from the + value of mteTriggerValueID that triggered the event. + + Each instance that fills the wildcard is independent of any + additional instances, that is, wildcarded objects operate + as if there were a separate table entry for each instance + that fills the wildcard without having to actually predict + all possible instances ahead of time. + + Bad object identifiers or a mismatch between truncating the + identifier and the value of mteSetObjectWildcard + result in operation as one would expect when providing the + wrong identifier to a Set operation. The Set will fail or set + the wrong object. If the value syntax of the destination + object is not correct, the Set fails with the normal SNMP + error code." + DEFVAL { zeroDotZero } + ::= { mteEventSetEntry 1 } + +mteEventSetObjectWildcard OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "Control over whether mteEventSetObject is to be treated as + fully-specified or wildcarded, with 'true' indicating wildcard + if mteEventActions has 'set' set." + DEFVAL { false } + ::= { mteEventSetEntry 2 } + +mteEventSetValue OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The value to which to set the object at mteEventSetObject + if mteEventActions has 'set' set." + DEFVAL { 0 } + ::= { mteEventSetEntry 3 } + +mteEventSetTargetTag OBJECT-TYPE + SYNTAX SnmpTagValue + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The tag for the target(s) at which to set the object at + mteEventSetObject to mteEventSetValue if mteEventActions + has 'set' set. + + Systems limited to self management MAY reject a non-zero + length for the value of this object. + + A length of 0 indicates the local system. In this case, + access to the objects indicated by mteEventSetObject is under + the security credentials of the requester that set + mteTriggerEntryStatus to 'active'. Those credentials are the + input parameters for isAccessAllowed from the Architecture for + Describing SNMP Management Frameworks. + + Otherwise access rights are checked according to the security + parameters resulting from the tag." + DEFVAL { ''H } + ::= { mteEventSetEntry 4 } + +mteEventSetContextName OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The management context in which to set mteEventObjectID. + if mteEventActions has 'set' set. + + This may be wildcarded by leaving characters off the end. To + indicate such wildcarding mteEventSetContextNameWildcard must + be 'true'. + + If this context name is wildcarded the value used to complete + the wildcarding of mteTriggerContextName will be appended." + DEFVAL { ''H } + ::= { mteEventSetEntry 5 } + +mteEventSetContextNameWildcard OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "Control for whether mteEventSetContextName is to be treated as + fully-specified or wildcarded, with 'true' indicating wildcard + if mteEventActions has 'set' set." + DEFVAL { false } + ::= { mteEventSetEntry 6 } + +-- +-- Notifications +-- + +dismanEventMIBNotificationPrefix OBJECT IDENTIFIER ::= + { dismanEventMIB 2 } +dismanEventMIBNotifications OBJECT IDENTIFIER ::= + { dismanEventMIBNotificationPrefix 0 } +dismanEventMIBNotificationObjects OBJECT IDENTIFIER + ::= { dismanEventMIBNotificationPrefix 1 } + +-- +-- Notification Objects +-- + +mteHotTrigger OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "The name of the trigger causing the notification." + ::= { dismanEventMIBNotificationObjects 1 } + +mteHotTargetName OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "The SNMP Target MIB's snmpTargetAddrName related to the + notification." + ::= { dismanEventMIBNotificationObjects 2 } + +mteHotContextName OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "The context name related to the notification. This MUST be as + fully-qualified as possible, including filling in wildcard + information determined in processing." + ::= { dismanEventMIBNotificationObjects 3 } + +mteHotOID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "The object identifier of the destination object related to the + notification. This MUST be as fully-qualified as possible, + including filling in wildcard information determined in + processing. + + For a trigger-related notification this is from + mteTriggerValueID. + + For a set failure this is from mteEventSetObject." + ::= { dismanEventMIBNotificationObjects 4 } + +mteHotValue OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "The value of the object at mteTriggerValueID when a + trigger fired." + ::= { dismanEventMIBNotificationObjects 5 } + +mteFailedReason OBJECT-TYPE + SYNTAX FailureReason + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "The reason for the failure of an attempt to check for a + trigger condition or set an object in response to an event." + ::= { dismanEventMIBNotificationObjects 6 } + +-- +-- Notifications +-- + +mteTriggerFired NOTIFICATION-TYPE + OBJECTS { mteHotTrigger, + mteHotTargetName, + mteHotContextName, + mteHotOID, + mteHotValue } + STATUS current + DESCRIPTION + "Notification that the trigger indicated by the object + instances has fired, for triggers with mteTriggerType + 'boolean' or 'existence'." + ::= { dismanEventMIBNotifications 1 } + +mteTriggerRising NOTIFICATION-TYPE + OBJECTS { mteHotTrigger, + mteHotTargetName, + mteHotContextName, + mteHotOID, + mteHotValue } + STATUS current + DESCRIPTION + "Notification that the rising threshold was met for triggers + with mteTriggerType 'threshold'." + ::= { dismanEventMIBNotifications 2 } + +mteTriggerFalling NOTIFICATION-TYPE + OBJECTS { mteHotTrigger, + mteHotTargetName, + mteHotContextName, + mteHotOID, + mteHotValue } + STATUS current + DESCRIPTION + "Notification that the falling threshold was met for triggers + with mteTriggerType 'threshold'." + ::= { dismanEventMIBNotifications 3 } + +mteTriggerFailure NOTIFICATION-TYPE + OBJECTS { mteHotTrigger, + mteHotTargetName, + mteHotContextName, + mteHotOID, + mteFailedReason } + STATUS current + DESCRIPTION + "Notification that an attempt to check a trigger has failed. + + The network manager must enable this notification only with + a certain fear and trembling, as it can easily crowd out more + important information. It should be used only to help diagnose + a problem that has appeared in the error counters and can not + be found otherwise." + ::= { dismanEventMIBNotifications 4 } + +mteEventSetFailure NOTIFICATION-TYPE + OBJECTS { mteHotTrigger, + mteHotTargetName, + mteHotContextName, + mteHotOID, + mteFailedReason } + STATUS current + DESCRIPTION + "Notification that an attempt to do a set in response to an + event has failed. + + The network manager must enable this notification only with + a certain fear and trembling, as it can easily crowd out more + important information. It should be used only to help diagnose + a problem that has appeared in the error counters and can not + be found otherwise." + ::= { dismanEventMIBNotifications 5 } + +-- +-- Conformance +-- + +dismanEventMIBConformance OBJECT IDENTIFIER ::= { dismanEventMIB 3 } +dismanEventMIBCompliances OBJECT IDENTIFIER ::= + { dismanEventMIBConformance 1 } +dismanEventMIBGroups OBJECT IDENTIFIER ::= + { dismanEventMIBConformance 2 } + +-- Compliance + +dismanEventMIBCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for entities which implement + the Event MIB." + MODULE -- this module + MANDATORY-GROUPS { + dismanEventResourceGroup, + dismanEventTriggerGroup, + dismanEventObjectsGroup, + dismanEventEventGroup, + dismanEventNotificationObjectGroup, + dismanEventNotificationGroup + } + + OBJECT mteTriggerTargetTag + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required, thus limiting + monitoring to the local system or pre-configured + remote systems." + + OBJECT mteEventSetTargetTag + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required, thus limiting + setting to the local system or pre-configured + remote systems." + + OBJECT mteTriggerValueIDWildcard + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required, thus allowing + the system not to implement wildcarding." + + OBJECT mteTriggerContextNameWildcard + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required, thus allowing + the system not to implement wildcarding." + + OBJECT mteObjectsIDWildcard + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required, thus allowing + the system not to implement wildcarding." + + OBJECT mteEventSetContextNameWildcard + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required, thus allowing + the system not to implement wildcarding." + ::= { dismanEventMIBCompliances 1 } + +-- Units of Conformance + +dismanEventResourceGroup OBJECT-GROUP + OBJECTS { + mteResourceSampleMinimum, + mteResourceSampleInstanceMaximum, + mteResourceSampleInstances, + mteResourceSampleInstancesHigh, + mteResourceSampleInstanceLacks + } + STATUS current + DESCRIPTION + "Event resource status and control objects." + ::= { dismanEventMIBGroups 1 } + +dismanEventTriggerGroup OBJECT-GROUP + OBJECTS { + mteTriggerFailures, + mteTriggerComment, + mteTriggerTest, + mteTriggerSampleType, + mteTriggerValueID, + mteTriggerValueIDWildcard, + mteTriggerTargetTag, + mteTriggerContextName, + mteTriggerContextNameWildcard, + mteTriggerFrequency, + mteTriggerObjectsOwner, + mteTriggerObjects, + mteTriggerEnabled, + mteTriggerEntryStatus, + mteTriggerDeltaDiscontinuityID, + mteTriggerDeltaDiscontinuityIDWildcard, + mteTriggerDeltaDiscontinuityIDType, + mteTriggerExistenceTest, + mteTriggerExistenceStartup, + mteTriggerExistenceObjectsOwner, + mteTriggerExistenceObjects, + mteTriggerExistenceEventOwner, + mteTriggerExistenceEvent, + mteTriggerBooleanComparison, + mteTriggerBooleanValue, + mteTriggerBooleanStartup, + mteTriggerBooleanObjectsOwner, + mteTriggerBooleanObjects, + mteTriggerBooleanEventOwner, + mteTriggerBooleanEvent, + mteTriggerThresholdStartup, + mteTriggerThresholdObjectsOwner, + mteTriggerThresholdObjects, + mteTriggerThresholdRising, + mteTriggerThresholdFalling, + mteTriggerThresholdDeltaRising, + mteTriggerThresholdDeltaFalling, + mteTriggerThresholdRisingEventOwner, + mteTriggerThresholdRisingEvent, + mteTriggerThresholdFallingEventOwner, + mteTriggerThresholdFallingEvent, + mteTriggerThresholdDeltaRisingEventOwner, + mteTriggerThresholdDeltaRisingEvent, + mteTriggerThresholdDeltaFallingEventOwner, + mteTriggerThresholdDeltaFallingEvent + } + STATUS current + DESCRIPTION + "Event triggers." + ::= { dismanEventMIBGroups 2 } + +dismanEventObjectsGroup OBJECT-GROUP + OBJECTS { + mteObjectsID, + mteObjectsIDWildcard, + mteObjectsEntryStatus + } + STATUS current + DESCRIPTION + "Supplemental objects." + ::= { dismanEventMIBGroups 3 } + +dismanEventEventGroup OBJECT-GROUP + OBJECTS { + mteEventFailures, + mteEventComment, + mteEventActions, + mteEventEnabled, + mteEventEntryStatus, + mteEventNotification, + mteEventNotificationObjectsOwner, + mteEventNotificationObjects, + mteEventSetObject, + mteEventSetObjectWildcard, + mteEventSetValue, + mteEventSetTargetTag, + mteEventSetContextName, + mteEventSetContextNameWildcard + } + STATUS current + DESCRIPTION + "Events." + ::= { dismanEventMIBGroups 4 } + +dismanEventNotificationObjectGroup OBJECT-GROUP + OBJECTS { + mteHotTrigger, + mteHotTargetName, + mteHotContextName, + mteHotOID, + mteHotValue, + mteFailedReason + } + STATUS current + DESCRIPTION + "Notification objects." + ::= { dismanEventMIBGroups 5 } + +dismanEventNotificationGroup NOTIFICATION-GROUP + NOTIFICATIONS { + mteTriggerFired, + mteTriggerRising, + mteTriggerFalling, + mteTriggerFailure, + mteEventSetFailure + } + STATUS current + DESCRIPTION + "Notifications." + ::= { dismanEventMIBGroups 6 } + +END diff --git a/mibs/DISMAN-EXPRESSION-MIB.txt b/mibs/DISMAN-EXPRESSION-MIB.txt new file mode 100644 index 0000000..f73e5bf --- /dev/null +++ b/mibs/DISMAN-EXPRESSION-MIB.txt @@ -0,0 +1,1182 @@ +DISMAN-EXPRESSION-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, + Integer32, Gauge32, Unsigned32, + Counter32, Counter64, IpAddress, + TimeTicks, mib-2, zeroDotZero FROM SNMPv2-SMI + RowStatus, TruthValue, TimeStamp FROM SNMPv2-TC + sysUpTime FROM SNMPv2-MIB + SnmpAdminString FROM SNMP-FRAMEWORK-MIB + MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF; + +dismanExpressionMIB MODULE-IDENTITY + LAST-UPDATED "200010160000Z" -- 16 October 2000 + ORGANIZATION "IETF Distributed Management Working Group" + CONTACT-INFO "Ramanathan Kavasseri + Cisco Systems, Inc. + 170 West Tasman Drive, + San Jose CA 95134-1706. + Phone: +1 408 527 2446 + Email: ramk@cisco.com" + DESCRIPTION + "The MIB module for defining expressions of MIB objects for + management purposes." +-- Revision History + + REVISION "200010160000Z" -- 16 October 2000 + DESCRIPTION "This is the initial version of this MIB. + Published as RFC 2982" + ::= { mib-2 90 } + +dismanExpressionMIBObjects OBJECT IDENTIFIER ::= + { dismanExpressionMIB 1 } + +expResource OBJECT IDENTIFIER ::= { dismanExpressionMIBObjects 1 } +expDefine OBJECT IDENTIFIER ::= { dismanExpressionMIBObjects 2 } +expValue OBJECT IDENTIFIER ::= { dismanExpressionMIBObjects 3 } + +-- +-- Resource Control +-- + +expResourceDeltaMinimum OBJECT-TYPE + SYNTAX Integer32 (-1 | 1..600) + UNITS "seconds" + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The minimum expExpressionDeltaInterval this system will + accept. A system may use the larger values of this minimum to + lessen the impact of constantly computing deltas. For larger + delta sampling intervals the system samples less often and + suffers less overhead. This object provides a way to enforce + such lower overhead for all expressions created after it is + set. + + The value -1 indicates that expResourceDeltaMinimum is + irrelevant as the system will not accept 'deltaValue' as a + value for expObjectSampleType. + + Unless explicitly resource limited, a system's value for + this object should be 1, allowing as small as a 1 second + interval for ongoing delta sampling. + + Changing this value will not invalidate an existing setting + of expObjectSampleType." + ::= { expResource 1 } + +expResourceDeltaWildcardInstanceMaximum OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "instances" + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "For every instance of a deltaValue object, one dynamic instance + entry is needed for holding the instance value from the previous + sample, i.e. to maintain state. + + This object limits maximum number of dynamic instance entries + this system will support for wildcarded delta objects in + expressions. For a given delta expression, the number of + dynamic instances is the number of values that meet all criteria + to exist times the number of delta values in the expression. + + A value of 0 indicates no preset limit, that is, the limit + is dynamic based on system operation and resources. + + Unless explicitly resource limited, a system's value for + this object should be 0. + + Changing this value will not eliminate or inhibit existing delta + wildcard instance objects but will prevent the creation of more + such objects. + + An attempt to allocate beyond the limit results in expErrorCode + being tooManyWildcardValues for that evaluation attempt." + ::= { expResource 2 } + +expResourceDeltaWildcardInstances OBJECT-TYPE + SYNTAX Gauge32 + UNITS "instances" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of currently active instance entries as + defined for expResourceDeltaWildcardInstanceMaximum." + ::= { expResource 3 } + +expResourceDeltaWildcardInstancesHigh OBJECT-TYPE + SYNTAX Gauge32 + UNITS "instances" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The highest value of expResourceDeltaWildcardInstances + that has occurred since initialization of the managed + system." + ::= { expResource 4 } + +expResourceDeltaWildcardInstanceResourceLacks OBJECT-TYPE + SYNTAX Counter32 + UNITS "instances" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times this system could not evaluate an + expression because that would have created a value instance in + excess of expResourceDeltaWildcardInstanceMaximum." + ::= { expResource 5 } + +-- + +-- Definition +-- +-- Expression Definition Table +-- + +expExpressionTable OBJECT-TYPE + SYNTAX SEQUENCE OF ExpExpressionEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of expression definitions." + ::= { expDefine 1 } + +expExpressionEntry OBJECT-TYPE + SYNTAX ExpExpressionEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a single expression. New expressions + can be created using expExpressionRowStatus. + + To create an expression first create the named entry in this + table. Then use expExpressionName to populate expObjectTable. + For expression evaluation to succeed all related entries in + expExpressionTable and expObjectTable must be 'active'. If + these conditions are not met the corresponding values in + expValue simply are not instantiated. + + Deleting an entry deletes all related entries in expObjectTable + and expErrorTable. + + Because of the relationships among the multiple tables for an + expression (expExpressionTable, expObjectTable, and + expValueTable) and the SNMP rules for independence in setting + object values, it is necessary to do final error checking when + an expression is evaluated, that is, when one of its instances + in expValueTable is read or a delta interval expires. Earlier + checking need not be done and an implementation may not impose + any ordering on the creation of objects related to an + expression. + + To maintain security of MIB information, when creating a new row in + this table, the managed system must record the security credentials + of the requester. These security credentials are the parameters + necessary as inputs to isAccessAllowed from the Architecture for + + Describing SNMP Management Frameworks. When obtaining the objects + that make up the expression, the system must (conceptually) use + isAccessAllowed to ensure that it does not violate security. + + The evaluation of the expression takes place under the + security credentials of the creator of its expExpressionEntry. + + Values of read-write objects in this table may be changed + + at any time." + INDEX { expExpressionOwner, expExpressionName } + ::= { expExpressionTable 1 } + +ExpExpressionEntry ::= SEQUENCE { + expExpressionOwner SnmpAdminString, + expExpressionName SnmpAdminString, + expExpression OCTET STRING, + expExpressionValueType INTEGER, + expExpressionComment SnmpAdminString, + expExpressionDeltaInterval Integer32, + expExpressionPrefix OBJECT IDENTIFIER, + expExpressionErrors Counter32, + expExpressionEntryStatus RowStatus +} + +expExpressionOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The owner of this entry. The exact semantics of this + string are subject to the security policy defined by the + security administrator." + ::= { expExpressionEntry 1 } + +expExpressionName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (1..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The name of the expression. This is locally unique, within + the scope of an expExpressionOwner." + ::= { expExpressionEntry 2 } + +expExpression OBJECT-TYPE + SYNTAX OCTET STRING (SIZE (1..1024)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The expression to be evaluated. This object is the same + as a DisplayString (RFC 1903) except for its maximum length. + + Except for the variable names the expression is in ANSI C + syntax. Only the subset of ANSI C operators and functions + listed here is allowed. + + Variables are expressed as a dollar sign ('$') and an + + integer that corresponds to an expObjectIndex. An + example of a valid expression is: + + ($1-$5)*100 + + Expressions must not be recursive, that is although an expression + may use the results of another expression, it must not contain + any variable that is directly or indirectly a result of its own + evaluation. The managed system must check for recursive + expressions. + + The only allowed operators are: + + ( ) + - (unary) + + - * / % + & | ^ << >> ~ + ! && || == != > >= < <= + + Note the parentheses are included for parenthesizing the + expression, not for casting data types. + + The only constant types defined are: + + int (32-bit signed) + long (64-bit signed) + unsigned int + unsigned long + hexadecimal + character + string + oid + + The default type for a positive integer is int unless it is too + large in which case it is long. + + All but oid are as defined for ANSI C. Note that a + hexadecimal constant may end up as a scalar or an array of + 8-bit integers. A string constant is enclosed in double + quotes and may contain back-slashed individual characters + as in ANSI C. + + An oid constant comprises 32-bit, unsigned integers and at + least one period, for example: + + 0. + .0 + 1.3.6.1 + + No additional leading or trailing subidentifiers are automatically + added to an OID constant. The constant is taken as expressed. + + Integer-typed objects are treated as 32- or 64-bit, signed + or unsigned integers, as appropriate. The results of + mixing them are as for ANSI C, including the type of the + result. Note that a 32-bit value is thus promoted to 64 bits + only in an operation with a 64-bit value. There is no + provision for larger values to handle overflow. + + Relative to SNMP data types, a resulting value becomes + unsigned when calculating it uses any unsigned value, + including a counter. To force the final value to be of + data type counter the expression must explicitly use the + counter32() or counter64() function (defined below). + + OCTET STRINGS and OBJECT IDENTIFIERs are treated as + one-dimensioned arrays of unsigned 8-bit integers and + unsigned 32-bit integers, respectively. + + IpAddresses are treated as 32-bit, unsigned integers in + network byte order, that is, the hex version of 255.0.0.0 is + 0xff000000. + + Conditional expressions result in a 32-bit, unsigned integer + of value 0 for false or 1 for true. When an arbitrary value + is used as a boolean 0 is false and non-zero is true. + + Rules for the resulting data type from an operation, based on + the operator: + + For << and >> the result is the same as the left hand operand. + + For &&, ||, ==, !=, <, <=, >, and >= the result is always + Unsigned32. + + For unary - the result is always Integer32. + + For +, -, *, /, %, &, |, and ^ the result is promoted according + to the following rules, in order from most to least preferred: + + If left hand and right hand operands are the same type, + use that. + + If either side is Counter64, use that. + + If either side is IpAddress, use that. + + If either side is TimeTicks, use that. + + If either side is Counter32, use that. + + Otherwise use Unsigned32. + + The following rules say what operators apply with what data + types. Any combination not explicitly defined does not work. + + For all operators any of the following can be the left hand or + right hand operand: Integer32, Counter32, Unsigned32, Counter64. + + The operators +, -, *, /, %, <, <=, >, and >= work with + TimeTicks. + + The operators &, |, and ^ work with IpAddress. + + The operators << and >> work with IpAddress but only as the + left hand operand. + + The + operator performs a concatenation of two OCTET STRINGs or + two OBJECT IDENTIFIERs. + + The operators &, | perform bitwise operations on OCTET STRINGs. + If the OCTET STRING happens to be a DisplayString the results + may be meaningless, but the agent system does not check this as + some such systems do not have this information. + + The operators << and >> perform bitwise operations on OCTET + STRINGs appearing as the left hand operand. + + The only functions defined are: + + counter32 + counter64 + arraySection + stringBegins + stringEnds + stringContains + oidBegins + oidEnds + oidContains + average + maximum + minimum + sum + exists + + The following function definitions indicate their parameters by + naming the data type of the parameter in the parameter's position + in the parameter list. The parameter must be of the type indicated + and generally may be a constant, a MIB object, a function, or an + expression. + + counter32(integer) - wrapped around an integer value counter32 + forces Counter32 as a data type. + + counter64(integer) - similar to counter32 except that the + resulting data type is 'counter64'. + + arraySection(array, integer, integer) - selects a piece of an + array (i.e. part of an OCTET STRING or OBJECT IDENTIFIER). The + integer arguments are in the range 0 to 4,294,967,295. The + first is an initial array index (one-dimensioned) and the second + is an ending array index. A value of 0 indicates first or last + element, respectively. If the first element is larger than the + array length the result is 0 length. If the second integer is + less than or equal to the first, the result is 0 length. If the + second is larger than the array length it indicates last + element. + + stringBegins/Ends/Contains(octetString, octetString) - looks for + the second string (which can be a string constant) in the first + and returns the one-dimensioned arrayindex where the match began. + A return value of 0 indicates no match (i.e. boolean false). + + oidBegins/Ends/Contains(oid, oid) - looks for the second OID + (which can be an OID constant) in the first and returns the + the one-dimensioned index where the match began. A return value + of 0 indicates no match (i.e. boolean false). + + average/maximum/minimum(integer) - calculates the average, + minimum, or maximum value of the integer valued object over + multiple sample times. If the object disappears for any + sample period, the accumulation and the resulting value object + cease to exist until the object reappears at which point the + calculation starts over. + + sum(integerObject*) - sums all available values of the + wildcarded integer object, resulting in an integer scalar. Must + be used with caution as it wraps on overflow with no + notification. + + exists(anyTypeObject) - verifies the object instance exists. A + return value of 0 indicates NoSuchInstance (i.e. boolean + false)." + ::= { expExpressionEntry 3 } + +expExpressionValueType OBJECT-TYPE + SYNTAX INTEGER { counter32(1), unsigned32(2), timeTicks(3), + integer32(4), ipAddress(5), octetString(6), + objectId(7), counter64(8) } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The type of the expression value. One and only one of the + value objects in expValueTable will be instantiated to match + this type. + + If the result of the expression can not be made into this type, + an invalidOperandType error will occur." + DEFVAL { counter32 } + ::= { expExpressionEntry 4 } + +expExpressionComment OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A comment to explain the use or meaning of the expression." + DEFVAL { ''H } + ::= { expExpressionEntry 5 } + +expExpressionDeltaInterval OBJECT-TYPE + SYNTAX Integer32 (0..86400) + UNITS "seconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Sampling interval for objects in this expression with + expObjectSampleType 'deltaValue'. + + This object has no effect if the the expression has no + deltaValue objects. + + A value of 0 indicates no automated sampling. In this case + the delta is the difference from the last time the expression + was evaluated. Note that this is subject to unpredictable + delta times in the face of retries or multiple managers. + + A value greater than zero is the number of seconds between + automated samples. + + Until the delta interval has expired once the delta for the + + object is effectively not instantiated and evaluating + the expression has results as if the object itself were not + instantiated. + + Note that delta values potentially consume large amounts of + system CPU and memory. Delta state and processing must + continue constantly even if the expression is not being used. + That is, the expression is being evaluated every delta interval, + even if no application is reading those values. For wildcarded + objects this can be substantial overhead. + + Note that delta intervals, external expression value sampling + intervals and delta intervals for expressions within other + expressions can have unusual interactions as they are impossible + to synchronize accurately. In general one interval embedded + below another must be enough shorter that the higher sample + sees relatively smooth, predictable behavior. So, for example, + to avoid the higher level getting the same sample twice, the + lower level should sample at least twice as fast as the higher + level does." + DEFVAL { 0 } + ::= { expExpressionEntry 6 } + +expExpressionPrefix OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An object prefix to assist an application in determining + the instance indexing to use in expValueTable, relieving the + application of the need to scan the expObjectTable to + determine such a prefix. + + See expObjectTable for information on wildcarded objects. + + If the expValueInstance portion of the value OID may + be treated as a scalar (that is, normally, 0) the value of + expExpressionPrefix is zero length, that is, no OID at all. + Note that zero length implies a null OID, not the OID 0.0. + + Otherwise, the value of expExpressionPrefix is the expObjectID + value of any one of the wildcarded objects for the expression. + This is sufficient, as the remainder, that is, the instance + fragment relevant to instancing the values, must be the same for + all wildcarded objects in the expression." + ::= { expExpressionEntry 7 } + +expExpressionErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of errors encountered while evaluating this + expression. + + Note that an object in the expression not being accessible, + is not considered an error. An example of an inaccessible + object is when the object is excluded from the view of the + user whose security credentials are used in the expression + evaluation. In such cases, it is a legitimate condition + that causes the corresponding expression value not to be + instantiated." + ::= { expExpressionEntry 8 } + +expExpressionEntryStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The control that allows creation and deletion of entries." + ::= { expExpressionEntry 9 } + +-- +-- Expression Error Table +-- + +expErrorTable OBJECT-TYPE + SYNTAX SEQUENCE OF ExpErrorEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of expression errors." + ::= { expDefine 2 } + +expErrorEntry OBJECT-TYPE + SYNTAX ExpErrorEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about errors in processing an expression. + + Entries appear in this table only when there is a matching + expExpressionEntry and then only when there has been an + error for that expression as reflected by the error codes + defined for expErrorCode." + INDEX { expExpressionOwner, expExpressionName } + ::= { expErrorTable 1 } + +ExpErrorEntry ::= SEQUENCE { + expErrorTime TimeStamp, + expErrorIndex Integer32, + expErrorCode INTEGER, + expErrorInstance OBJECT IDENTIFIER +} + +expErrorTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime the last time an error caused a + failure to evaluate this expression." + ::= { expErrorEntry 1 } + +expErrorIndex OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The one-dimensioned character array index into + expExpression for where the error occurred. The value + zero indicates irrelevance." + ::= { expErrorEntry 2 } + +expErrorCode OBJECT-TYPE + SYNTAX INTEGER { + invalidSyntax(1), + undefinedObjectIndex(2), + unrecognizedOperator(3), + unrecognizedFunction(4), + invalidOperandType(5), + unmatchedParenthesis(6), + tooManyWildcardValues(7), + recursion(8), + deltaTooShort(9), + resourceUnavailable(10), + divideByZero(11) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The error that occurred. In the following explanations the + expected timing of the error is in parentheses. 'S' means + the error occurs on a Set request. 'E' means the error + + occurs on the attempt to evaluate the expression either due to + Get from expValueTable or in ongoing delta processing. + + invalidSyntax the value sent for expExpression is not + valid Expression MIB expression syntax + (S) + undefinedObjectIndex an object reference ($n) in + expExpression does not have a matching + instance in expObjectTable (E) + unrecognizedOperator the value sent for expExpression held an + unrecognized operator (S) + unrecognizedFunction the value sent for expExpression held an + unrecognized function name (S) + invalidOperandType an operand in expExpression is not the + right type for the associated operator + or result (SE) + unmatchedParenthesis the value sent for expExpression is not + correctly parenthesized (S) + tooManyWildcardValues evaluating the expression exceeded the + limit set by + expResourceDeltaWildcardInstanceMaximum + (E) + recursion through some chain of embedded + expressions the expression invokes itself + (E) + deltaTooShort the delta for the next evaluation passed + before the system could evaluate the + present sample (E) + resourceUnavailable some resource, typically dynamic memory, + was unavailable (SE) + divideByZero an attempt to divide by zero occurred + (E) + + For the errors that occur when the attempt is made to set + expExpression Set request fails with the SNMP error code + 'wrongValue'. Such failures refer to the most recent failure to + Set expExpression, not to the present value of expExpression + which must be either unset or syntactically correct. + + Errors that occur during evaluation for a Get* operation return + the SNMP error code 'genErr' except for 'tooManyWildcardValues' + and 'resourceUnavailable' which return the SNMP error code + 'resourceUnavailable'." + ::= { expErrorEntry 3 } + +expErrorInstance OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The expValueInstance being evaluated when the error + occurred. A zero-length indicates irrelevance." + ::= { expErrorEntry 4 } + +-- +-- Object Table +-- + +expObjectTable OBJECT-TYPE + SYNTAX SEQUENCE OF ExpObjectEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of object definitions for each expExpression. + + Wildcarding instance IDs: + + It is legal to omit all or part of the instance portion for + some or all of the objects in an expression. (See the + DESCRIPTION of expObjectID for details. However, note that + if more than one object in the same expression is wildcarded + in this way, they all must be objects where that portion of + the instance is the same. In other words, all objects may be + in the same SEQUENCE or in different SEQUENCEs but with the + same semantic index value (e.g., a value of ifIndex) + for the wildcarded portion." + ::= { expDefine 3 } + +expObjectEntry OBJECT-TYPE + SYNTAX ExpObjectEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about an object. An application uses + expObjectEntryStatus to create entries in this table while + in the process of defining an expression. + + Values of read-create objects in this table may be + changed at any time." + INDEX { expExpressionOwner, expExpressionName, expObjectIndex } + ::= { expObjectTable 1 } + +ExpObjectEntry ::= SEQUENCE { + expObjectIndex Unsigned32, + expObjectID OBJECT IDENTIFIER, + expObjectIDWildcard TruthValue, + expObjectSampleType INTEGER, + expObjectDeltaDiscontinuityID OBJECT IDENTIFIER, + expObjectDiscontinuityIDWildcard TruthValue, + expObjectDiscontinuityIDType INTEGER, + expObjectConditional OBJECT IDENTIFIER, + expObjectConditionalWildcard TruthValue, + expObjectEntryStatus RowStatus +} + +expObjectIndex OBJECT-TYPE + SYNTAX Unsigned32 (1..4294967295) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Within an expression, a unique, numeric identification for an + object. Prefixed with a dollar sign ('$') this is used to + reference the object in the corresponding expExpression." + ::= { expObjectEntry 1 } + +expObjectID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The OBJECT IDENTIFIER (OID) of this object. The OID may be + fully qualified, meaning it includes a complete instance + identifier part (e.g., ifInOctets.1 or sysUpTime.0), or it + may not be fully qualified, meaning it may lack all or part + of the instance identifier. If the expObjectID is not fully + qualified, then expObjectWildcard must be set to true(1). + The value of the expression will be multiple + values, as if done for a GetNext sweep of the object. + + An object here may itself be the result of an expression but + recursion is not allowed. + + NOTE: The simplest implementations of this MIB may not allow + wildcards." + ::= { expObjectEntry 2 } + +expObjectIDWildcard OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A true value indicates the expObjecID of this row is a wildcard + object. False indicates that expObjectID is fully instanced. + If all expObjectWildcard values for a given expression are FALSE, + expExpressionPrefix will reflect a scalar object (i.e. will + be 0.0). + + NOTE: The simplest implementations of this MIB may not allow + wildcards." + DEFVAL { false } + ::= { expObjectEntry 3 } + +expObjectSampleType OBJECT-TYPE + SYNTAX INTEGER { absoluteValue(1), deltaValue(2), + changedValue(3) } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The method of sampling the selected variable. + + An 'absoluteValue' is simply the present value of the object. + + A 'deltaValue' is the present value minus the previous value, + which was sampled expExpressionDeltaInterval seconds ago. + This is intended primarily for use with SNMP counters, which are + meaningless as an 'absoluteValue', but may be used with any + integer-based value. + + A 'changedValue' is a boolean for whether the present value is + different from the previous value. It is applicable to any data + type and results in an Unsigned32 with value 1 if the object's + value is changed and 0 if not. In all other respects it is as a + 'deltaValue' and all statements and operation regarding delta + values apply to changed values. + + When an expression contains both delta and absolute values + the absolute values are obtained at the end of the delta + period." + DEFVAL { absoluteValue } + ::= { expObjectEntry 4 } + +sysUpTimeInstance OBJECT IDENTIFIER ::= { sysUpTime 0 } + +expObjectDeltaDiscontinuityID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The OBJECT IDENTIFIER (OID) of a TimeTicks, TimeStamp, or + DateAndTime object that indicates a discontinuity in the value + at expObjectID. + + This object is instantiated only if expObjectSampleType is + 'deltaValue' or 'changedValue'. + + The OID may be for a leaf object (e.g. sysUpTime.0) or may + be wildcarded to match expObjectID. + + This object supports normal checking for a discontinuity in a + counter. Note that if this object does not point to sysUpTime + discontinuity checking must still check sysUpTime for an overall + discontinuity. + + If the object identified is not accessible no discontinuity + check will be made." + DEFVAL { sysUpTimeInstance } + ::= { expObjectEntry 5 } + +expObjectDiscontinuityIDWildcard OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A true value indicates the expObjectDeltaDiscontinuityID of + this row is a wildcard object. False indicates that + expObjectDeltaDiscontinuityID is fully instanced. + + This object is instantiated only if expObjectSampleType is + 'deltaValue' or 'changedValue'. + + NOTE: The simplest implementations of this MIB may not allow + wildcards." + DEFVAL { false } + ::= { expObjectEntry 6 } + +expObjectDiscontinuityIDType OBJECT-TYPE + SYNTAX INTEGER { timeTicks(1), timeStamp(2), dateAndTime(3) } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value 'timeTicks' indicates the expObjectDeltaDiscontinuityID + of this row is of syntax TimeTicks. The value 'timeStamp' indicates + syntax TimeStamp. The value 'dateAndTime indicates syntax + DateAndTime. + + This object is instantiated only if expObjectSampleType is + 'deltaValue' or 'changedValue'." + DEFVAL { timeTicks } + ::= { expObjectEntry 7 } + +expObjectConditional OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The OBJECT IDENTIFIER (OID) of an object that overrides + whether the instance of expObjectID is to be considered + usable. If the value of the object at expObjectConditional + is 0 or not instantiated, the object at expObjectID is + treated as if it is not instantiated. In other words, + expObjectConditional is a filter that controls whether or + not to use the value at expObjectID. + + The OID may be for a leaf object (e.g. sysObjectID.0) or may be + wildcarded to match expObjectID. If expObject is wildcarded and + expObjectID in the same row is not, the wild portion of + expObjectConditional must match the wildcarding of the rest of + the expression. If no object in the expression is wildcarded + but expObjectConditional is, use the lexically first instance + (if any) of expObjectConditional. + + If the value of expObjectConditional is 0.0 operation is + as if the value pointed to by expObjectConditional is a + non-zero (true) value. + + Note that expObjectConditional can not trivially use an object + of syntax TruthValue, since the underlying value is not 0 or 1." + DEFVAL { zeroDotZero } + ::= { expObjectEntry 8 } + + expObjectConditionalWildcard OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A true value indicates the expObjectConditional of this row is + a wildcard object. False indicates that expObjectConditional is + fully instanced. + + NOTE: The simplest implementations of this MIB may not allow + wildcards." + DEFVAL { false } + ::= { expObjectEntry 9 } + +expObjectEntryStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The control that allows creation/deletion of entries. + + Objects in this table may be changed while + expObjectEntryStatus is in any state." + ::= { expObjectEntry 10 } + +-- +-- Expression Value Table +-- + +expValueTable OBJECT-TYPE + SYNTAX SEQUENCE OF ExpValueEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of values from evaluated expressions." + ::= { expValue 1 } + +expValueEntry OBJECT-TYPE + SYNTAX ExpValueEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A single value from an evaluated expression. For a given + instance, only one 'Val' object in the conceptual row will be + instantiated, that is, the one with the appropriate type for + the value. For values that contain no objects of + expObjectSampleType 'deltaValue' or 'changedValue', reading a + value from the table causes the evaluation of the expression + for that value. For those that contain a 'deltaValue' or + 'changedValue' the value read is as of the last sampling + interval. + + If in the attempt to evaluate the expression one or more + of the necessary objects is not available, the corresponding + entry in this table is effectively not instantiated. + + To maintain security of MIB information, when creating a new + row in this table, the managed system must record the security + credentials of the requester. These security credentials are + the parameters necessary as inputs to isAccessAllowed from + [RFC2571]. When obtaining the objects that make up the + expression, the system must (conceptually) use isAccessAllowed to + ensure that it does not violate security. + + The evaluation of that expression takes place under the + + security credentials of the creator of its expExpressionEntry. + + To maintain security of MIB information, expression evaluation must + take place using security credentials for the implied Gets of the + objects in the expression as inputs (conceptually) to + isAccessAllowed from the Architecture for Describing SNMP + Management Frameworks. These are the security credentials of the + creator of the corresponding expExpressionEntry." + INDEX { expExpressionOwner, expExpressionName, + IMPLIED expValueInstance } + ::= { expValueTable 1 } + +ExpValueEntry ::= SEQUENCE { + expValueInstance OBJECT IDENTIFIER, + expValueCounter32Val Counter32, + expValueUnsigned32Val Unsigned32, + expValueTimeTicksVal TimeTicks, + expValueInteger32Val Integer32, + expValueIpAddressVal IpAddress, + expValueOctetStringVal OCTET STRING, + expValueOidVal OBJECT IDENTIFIER, + expValueCounter64Val Counter64 +} + +expValueInstance OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The final instance portion of a value's OID according to + the wildcarding in instances of expObjectID for the + expression. The prefix of this OID fragment is 0.0, + leading to the following behavior. + + If there is no wildcarding, the value is 0.0.0. In other + words, there is one value which standing alone would have + been a scalar with a 0 at the end of its OID. + + If there is wildcarding, the value is 0.0 followed by + a value that the wildcard can take, thus defining one value + instance for each real, possible value of the wildcard. + So, for example, if the wildcard worked out to be an ifIndex, + there is an expValueInstance for each applicable ifIndex." + ::= { expValueEntry 1 } + +expValueCounter32Val OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when expExpressionValueType is 'counter32'." + ::= { expValueEntry 2 } + +expValueUnsigned32Val OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when expExpressionValueType is 'unsigned32'." + ::= { expValueEntry 3 } + +expValueTimeTicksVal OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when expExpressionValueType is 'timeTicks'." + ::= { expValueEntry 4 } + +expValueInteger32Val OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when expExpressionValueType is 'integer32'." + ::= { expValueEntry 5 } + +expValueIpAddressVal OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when expExpressionValueType is 'ipAddress'." + ::= { expValueEntry 6 } + +expValueOctetStringVal OBJECT-TYPE + SYNTAX OCTET STRING (SIZE (0..65536)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when expExpressionValueType is 'octetString'." + ::= { expValueEntry 7 } + +expValueOidVal OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when expExpressionValueType is 'objectId'." + ::= { expValueEntry 8 } + +expValueCounter64Val OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when expExpressionValueType is 'counter64'." + ::= { expValueEntry 9 } + +-- +-- Conformance +-- + +dismanExpressionMIBConformance OBJECT IDENTIFIER ::= + { dismanExpressionMIB 3 } +dismanExpressionMIBCompliances OBJECT IDENTIFIER ::= + { dismanExpressionMIBConformance 1 } +dismanExpressionMIBGroups OBJECT IDENTIFIER ::= + { dismanExpressionMIBConformance 2 } + +-- Compliance + +dismanExpressionMIBCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for entities which implement + the Expression MIB." + MODULE -- this module + MANDATORY-GROUPS { + dismanExpressionResourceGroup, + dismanExpressionDefinitionGroup, + dismanExpressionValueGroup + } + + OBJECT expResourceDeltaMinimum + SYNTAX Integer32 (-1 | 60..600) + DESCRIPTION + "Implementation need not allow deltas or it may + implement them and restrict them to higher values." + + OBJECT expObjectSampleType + WRITE-SYNTAX INTEGER { absoluteValue(1) } + DESCRIPTION + "Implementation may disallow deltas calculation or + + change detection." + + OBJECT expObjectIDWildcard + WRITE-SYNTAX INTEGER { false(2) } + DESCRIPTION + "Implementation may allow wildcards." + + OBJECT expObjectDiscontinuityIDWildcard + WRITE-SYNTAX INTEGER { false(2) } + DESCRIPTION + "Implementation need not allow wildcards." + + OBJECT expObjectConditionalWildcard + WRITE-SYNTAX INTEGER { false(2) } + DESCRIPTION + "Implementation need not allow deltas wildcards." + ::= { dismanExpressionMIBCompliances 1 } + +-- Units of Conformance + +dismanExpressionResourceGroup OBJECT-GROUP + OBJECTS { + expResourceDeltaMinimum, + expResourceDeltaWildcardInstanceMaximum, + expResourceDeltaWildcardInstances, + expResourceDeltaWildcardInstancesHigh, + expResourceDeltaWildcardInstanceResourceLacks + } + STATUS current + DESCRIPTION + "Expression definition resource management." + ::= { dismanExpressionMIBGroups 1 } + +dismanExpressionDefinitionGroup OBJECT-GROUP + OBJECTS { + expExpression, + expExpressionValueType, + expExpressionComment, + expExpressionDeltaInterval, + expExpressionPrefix, + expExpressionErrors, + expExpressionEntryStatus, + expErrorTime, + expErrorIndex, + expErrorCode, + expErrorInstance, + expObjectID, + expObjectIDWildcard, + expObjectSampleType, + expObjectDeltaDiscontinuityID, + expObjectDiscontinuityIDWildcard, + expObjectDiscontinuityIDType, + expObjectConditional, + expObjectConditionalWildcard, + expObjectEntryStatus + } + STATUS current + DESCRIPTION + "Expression definition." + ::= { dismanExpressionMIBGroups 2 } + +dismanExpressionValueGroup OBJECT-GROUP + OBJECTS { + expValueCounter32Val, + expValueUnsigned32Val, + expValueTimeTicksVal, + expValueInteger32Val, + expValueIpAddressVal, + expValueOctetStringVal, + expValueOidVal, + expValueCounter64Val + } + STATUS current + DESCRIPTION + "Expression value." + ::= { dismanExpressionMIBGroups 3 } + +END diff --git a/mibs/DISMAN-NSLOOKUP-MIB.txt b/mibs/DISMAN-NSLOOKUP-MIB.txt new file mode 100644 index 0000000..b12ca53 --- /dev/null +++ b/mibs/DISMAN-NSLOOKUP-MIB.txt @@ -0,0 +1,509 @@ +DISMAN-NSLOOKUP-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, + Unsigned32, mib-2, Integer32 + FROM SNMPv2-SMI -- RFC2578 + RowStatus + FROM SNMPv2-TC -- RFC2579 + MODULE-COMPLIANCE, OBJECT-GROUP + FROM SNMPv2-CONF -- RFC2580 + SnmpAdminString + FROM SNMP-FRAMEWORK-MIB -- RFC3411 + InetAddressType, InetAddress + FROM INET-ADDRESS-MIB; -- RFC4001 + + lookupMIB MODULE-IDENTITY + LAST-UPDATED "200606130000Z" -- 13 June 2006 + ORGANIZATION "IETF Distributed Management Working Group" + CONTACT-INFO + "Juergen Quittek + + NEC Europe Ltd. + Network Laboratories + Kurfuersten-Anlage 36 + 69115 Heidelberg + Germany + + Phone: +49 6221 4342-115 + Email: quittek@netlab.nec.de" + DESCRIPTION + "The Lookup MIB (DISMAN-NSLOOKUP-MIB) enables determination + of either the name(s) corresponding to a host address or of + the address(es) associated with a host name at a remote + host. + + Copyright (C) The Internet Society (2006). This version of + this MIB module is part of RFC 4560; see the RFC itself for + full legal notices." + + -- Revision history + + REVISION "200606130000Z" -- 13 June 2006 + DESCRIPTION + "Updated version, published as RFC 4560. + - Replaced references to RFC 2575 by RFC 3415 + - Replaced references to RFC 2571 by RFC 3411 + - Replaced references to RFC 2851 by RFC 4001 + - Added value enabled(1) to SYNTAX clause of + lookupCtlOperStatus + - Added lookupMinimumCompliance + - Defined semantics of value 0 for object + lookupPurgeTime + - Added DEFVAL { unknown } to object + lookupCtlTargetAddressType OBJECT-TYPE" + + REVISION "200009210000Z" -- 21 September 2000 + DESCRIPTION + "Initial version, published as RFC 2925." + ::= { mib-2 82 } + + -- Top level structure of the MIB + + lookupObjects OBJECT IDENTIFIER ::= { lookupMIB 1 } + lookupConformance OBJECT IDENTIFIER ::= { lookupMIB 2 } + + -- Simple Object Definitions + + lookupMaxConcurrentRequests OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "requests" + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The maximum number of concurrent active lookup requests + that are allowed within an agent implementation. A value + of 0 for this object implies that there is no limit for + the number of concurrent active requests in effect. + + The limit applies only to new requests being activated. + When a new value is set, the agent will continue processing + all the requests already active, even if their number + exceed the limit just imposed." + DEFVAL { 10 } + ::= { lookupObjects 1 } + + lookupPurgeTime OBJECT-TYPE + SYNTAX Unsigned32 (0..86400) + UNITS "seconds" + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The amount of time to wait before automatically + deleting an entry in the lookupCtlTable and any + dependent lookupResultsTable entries + after the lookup operation represented by a + lookupCtlEntry has been completed. + A lookupCtEntry is considered complete + when its lookupCtlOperStatus object has a + value of completed(3). + + A value of 0 indicates that automatic deletion + of entries is disabled." + DEFVAL { 900 } -- 15 minutes as default + ::= { lookupObjects 2 } + + -- Lookup Control Table + + lookupCtlTable OBJECT-TYPE + SYNTAX SEQUENCE OF LookupCtlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines the Lookup Control Table for providing + the capability of performing a lookup operation + for a symbolic host name or for a host address + from a remote host." + ::= { lookupObjects 3 } + + lookupCtlEntry OBJECT-TYPE + SYNTAX LookupCtlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines an entry in the lookupCtlTable. A + lookupCtlEntry is initially indexed by + lookupCtlOwnerIndex, which is a type of SnmpAdminString, + a textual convention that allows for the use of the SNMPv3 + View-Based Access Control Model (RFC 3415, VACM) + and that also allows a management application to identify + its entries. The second index element, + lookupCtlOperationName, enables the same + lookupCtlOwnerIndex entity to have multiple outstanding + requests. The value of lookupCtlTargetAddressType + determines which lookup function to perform." + INDEX { + lookupCtlOwnerIndex, + lookupCtlOperationName + } + ::= { lookupCtlTable 1 } + + LookupCtlEntry ::= + SEQUENCE { + lookupCtlOwnerIndex SnmpAdminString, + lookupCtlOperationName SnmpAdminString, + lookupCtlTargetAddressType InetAddressType, + lookupCtlTargetAddress InetAddress, + lookupCtlOperStatus INTEGER, + lookupCtlTime Unsigned32, + lookupCtlRc Integer32, + lookupCtlRowStatus RowStatus + } + + lookupCtlOwnerIndex OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "To facilitate the provisioning of access control by a + security administrator using the View-Based Access + Control Model (RFC 2575, VACM) for tables in which + multiple users may need to create or + modify entries independently, the initial index is used as + an 'owner index'. Such an initial index has a syntax of + SnmpAdminString and can thus be trivially mapped to a + + securityName or groupName defined in VACM, in + accordance with a security policy. + + When used in conjunction with such a security policy all + entries in the table belonging to a particular user (or + group) will have the same value for this initial index. + For a given user's entries in a particular table, the + object identifiers for the information in these entries + will have the same subidentifiers (except for the + 'column' subidentifier) up to the end of the encoded + owner index. To configure VACM to permit access to this + portion of the table, one would create + vacmViewTreeFamilyTable entries with the value of + vacmViewTreeFamilySubtree including the owner index + portion, and vacmViewTreeFamilyMask 'wildcarding' the + column subidentifier. More elaborate configurations + are possible." + ::= { lookupCtlEntry 1 } + + lookupCtlOperationName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The name of a lookup operation. This is locally unique, + within the scope of an lookupCtlOwnerIndex." + ::= { lookupCtlEntry 2 } + + lookupCtlTargetAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the type of address for performing a + lookup operation for a symbolic host name or for a host + address from a remote host. + + Specification of dns(16) as the value for this object + means that a function such as, for example, getaddrinfo() + or gethostbyname() should be performed to return one or + more numeric addresses. Use of a value of either ipv4(1) + or ipv6(2) means that a functions such as, for example, + getnameinfo() or gethostbyaddr() should be used to return + the symbolic names associated with a host." + DEFVAL { unknown } + ::= { lookupCtlEntry 3 } + + lookupCtlTargetAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the address used for a resolver lookup at a + remote host. The corresponding lookupCtlTargetAddressType + objects determines its type, as well as the function + that can be requested. + + A value for this object MUST be set prior to + transitioning its corresponding lookupCtlEntry to + active(1) via lookupCtlRowStatus." + ::= { lookupCtlEntry 4 } + + lookupCtlOperStatus OBJECT-TYPE + SYNTAX INTEGER { + enabled(1), -- operation is active + notStarted(2), -- operation has not started + completed(3) -- operation is done + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Reflects the operational state of an lookupCtlEntry: + + enabled(1) - Operation is active. + notStarted(2) - Operation has not been enabled. + completed(3) - Operation has been completed. + + An operation is automatically enabled(1) when its + lookupCtlRowStatus object is transitioned to active(1) + status. Until this occurs, lookupCtlOperStatus MUST + report a value of notStarted(2). After the lookup + operation is completed (success or failure), the value + for lookupCtlOperStatus MUST be transitioned to + completed(3)." + ::= { lookupCtlEntry 5 } + + lookupCtlTime OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Reports the number of milliseconds that a lookup + operation required to be completed at a remote host. + Completed means operation failure as well as + + success." + ::= { lookupCtlEntry 6 } + + lookupCtlRc OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The system-specific return code from a lookup + operation. All implementations MUST return a value + of 0 for this object when the remote lookup + operation succeeds. A non-zero value for this + objects indicates failure. It is recommended that + implementations return the error codes that are + generated by the lookup function used." + ::= { lookupCtlEntry 7 } + + lookupCtlRowStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object allows entries to be created and deleted + in the lookupCtlTable. + + A remote lookup operation is started when an + entry in this table is created via an SNMP set + request and the entry is activated. This + occurs by setting the value of this object + to CreateAndGo(4) during row creation or + by setting this object to active(1) after + the row is created. + + A value MUST be specified for lookupCtlTargetAddress + prior to the acceptance of a transition to active(1) state. + A remote lookup operation starts when its entry + first becomes active(1). Transitions in and + out of active(1) state have no effect on the + operational behavior of a remote lookup + operation, with the exception that deletion of + an entry in this table by setting its RowStatus + object to destroy(6) will stop an active + remote lookup operation. + + The operational state of a remote lookup operation + can be determined by examination of its + lookupCtlOperStatus object." + REFERENCE + "See definition of RowStatus in RFC 2579, + 'Textual Conventions for SMIv2.'" + ::= { lookupCtlEntry 8 } + +-- Lookup Results Table + + lookupResultsTable OBJECT-TYPE + SYNTAX SEQUENCE OF LookupResultsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines the Lookup Results Table for providing + the capability of determining the results of a + operation at a remote host. + + One or more entries are added to the + lookupResultsTable when a lookup operation, + as reflected by an lookupCtlEntry, is completed + successfully. All entries related to a + successful lookup operation MUST be added + to the lookupResultsTable at the same time + that the associating lookupCtlOperStatus + object is transitioned to completed(2). + + The number of entries added depends on the + results determined for a particular lookup + operation. All entries associated with an + lookupCtlEntry are removed when the + lookupCtlEntry is deleted. + + A remote host can be multi-homed and have more than one IP + address associated with it (returned by lookup function), + or it can have more than one symbolic name (returned + by lookup function). + + A function such as, for example, getnameinfo() or + gethostbyaddr() is called with a host address as its + parameter and is used primarily to determine a symbolic + name to associate with the host address. Entries in the + lookupResultsTable MUST be made for each host name + returned. If the function identifies an 'official host + name,' then this symbolic name MUST be assigned a + lookupResultsIndex of 1. + + A function such as, for example, getaddrinfo() or + gethostbyname() is called with a symbolic host name and is + used primarily to retrieve a host address. The entries + + MUST be stored in the order that they are retrieved from + the lookup function. lookupResultsIndex 1 MUST be + assigned to the first entry." + ::= { lookupObjects 4 } + + lookupResultsEntry OBJECT-TYPE + SYNTAX LookupResultsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines an entry in the lookupResultsTable. The + first two index elements identify the + lookupCtlEntry that a lookupResultsEntry belongs + to. The third index element selects a single + lookup operation result." + INDEX { + lookupCtlOwnerIndex, + lookupCtlOperationName, + lookupResultsIndex + } + ::= { lookupResultsTable 1 } + + LookupResultsEntry ::= + SEQUENCE { + lookupResultsIndex Unsigned32, + lookupResultsAddressType InetAddressType, + lookupResultsAddress InetAddress + } + + lookupResultsIndex OBJECT-TYPE + SYNTAX Unsigned32 (1..'ffffffff'h) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Entries in the lookupResultsTable are created when + the result of a lookup operation is determined. + + Entries MUST be stored in the lookupResultsTable in + the order that they are retrieved. Values assigned + to lookupResultsIndex MUST start at 1 and increase + consecutively." + ::= { lookupResultsEntry 1 } + + lookupResultsAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Indicates the type of result of a remote lookup + operation. A value of unknown(0) implies either that + the operation hasn't been started or that + it has failed." + ::= { lookupResultsEntry 2 } + + lookupResultsAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Reflects a result for a remote lookup operation + as per the value of lookupResultsAddressType. + + The address type (InetAddressType) that relates to + this object is specified by the corresponding value + of lookupResultsAddress." + ::= { lookupResultsEntry 3 } + + -- Conformance information + -- Compliance statements + + lookupCompliances OBJECT IDENTIFIER ::= { lookupConformance 1 } + lookupGroups OBJECT IDENTIFIER ::= { lookupConformance 2 } + + -- Compliance statements + + lookupCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP entities that + fully implement the DISMAN-NSLOOKUP-MIB." + MODULE -- this module + MANDATORY-GROUPS { lookupGroup } + + OBJECT lookupMaxConcurrentRequests + MIN-ACCESS read-only + DESCRIPTION + "The agent is not required to support set + operations to this object." + + OBJECT lookupPurgeTime + MIN-ACCESS read-only + DESCRIPTION + "The agent is not required to support a set + operation to this object." + ::= { lookupCompliances 1 } + + lookupMinimumCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The minimum compliance statement for SNMP entities + that implement the minimal subset of the + DISMAN-NSLOOKUP-MIB. Implementors might choose this + subset for small devices with limited resources." + MODULE -- this module + MANDATORY-GROUPS { lookupGroup } + + OBJECT lookupMaxConcurrentRequests + MIN-ACCESS read-only + DESCRIPTION + "The agent is not required to support set + operations to this object." + + OBJECT lookupPurgeTime + MIN-ACCESS read-only + DESCRIPTION + "The agent is not required to support a set + operation to this object." + + OBJECT lookupCtlRowStatus + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If write access is + not supported, then at least one entry in the + lookupCtlTable MUST be established already when the SNMP + agent starts offering access to the NSLOOKUP-MIB module. + If, in such a case, only a single entry is offered, then + it is RECOMMENDED that this entry use strings with a + length of 0 for both of its two index objects." + ::= { lookupCompliances 2 } + + -- MIB groupings + + lookupGroup OBJECT-GROUP + OBJECTS { + lookupMaxConcurrentRequests, + lookupPurgeTime, + lookupCtlOperStatus, + lookupCtlTargetAddressType, + lookupCtlTargetAddress, + lookupCtlTime, + lookupCtlRc, + lookupCtlRowStatus, + lookupResultsAddressType, + lookupResultsAddress + } + STATUS current + DESCRIPTION + "The group of objects that constitute the remote + Lookup operation." + ::= { lookupGroups 1 } + +END diff --git a/mibs/DISMAN-PING-MIB.txt b/mibs/DISMAN-PING-MIB.txt new file mode 100644 index 0000000..645ff8c --- /dev/null +++ b/mibs/DISMAN-PING-MIB.txt @@ -0,0 +1,1561 @@ +DISMAN-PING-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, Integer32, + Unsigned32, Gauge32, mib-2, + NOTIFICATION-TYPE, OBJECT-IDENTITY + FROM SNMPv2-SMI -- RFC2578 + TEXTUAL-CONVENTION, RowStatus, + StorageType, DateAndTime, TruthValue + FROM SNMPv2-TC -- RFC2579 + MODULE-COMPLIANCE, OBJECT-GROUP, + NOTIFICATION-GROUP + FROM SNMPv2-CONF -- RFC2580 + InterfaceIndexOrZero -- RFC2863 + FROM IF-MIB + SnmpAdminString + FROM SNMP-FRAMEWORK-MIB -- RFC3411 + InetAddressType, InetAddress + FROM INET-ADDRESS-MIB; -- RFC4001 + + pingMIB MODULE-IDENTITY + LAST-UPDATED "200606130000Z" -- 13 June 2006 + ORGANIZATION "IETF Distributed Management Working Group" + CONTACT-INFO + "Juergen Quittek + + NEC Europe Ltd. + Network Laboratories + Kurfuersten-Anlage 36 + 69115 Heidelberg + Germany + + Phone: +49 6221 4342-115 + + Email: quittek@netlab.nec.de" + DESCRIPTION + "The Ping MIB (DISMAN-PING-MIB) provides the capability of + controlling the use of the ping function at a remote + host. + + Copyright (C) The Internet Society (2006). This version of + this MIB module is part of RFC 4560; see the RFC itself for + full legal notices." + + -- Revision history + + REVISION "200606130000Z" -- 13 June 2006 + DESCRIPTION + "Updated version, published as RFC 4560. + - Correctly considered IPv6 in DESCRIPTION + clause of pingCtlDataSize + - Replaced references to RFC 2575 by RFC 3415 + - Replaced references to RFC 2571 by RFC 3411 + - Replaced references to RFC 2851 by RFC 4001 + - Added DEFVAL { {} } to definition of + pingCtlTrapGeneration + - Changed DEFVAL of object pingCtlDescr from + DEFVAL { '00'H } to DEFVAL { ''H } + - Changed DEFVAL of object pingCtlSourceAddressType + from DEFVAL { ipv4 } to DEFVAL { unknown } + - Extended DESCRIPTION clause of pingResultsTable + describing re-initialization of entries + - Changed SYNTAX of pingResultsProbeResponses and + pingResultsSentProbes from Unsigned32 to Gauge32 + - Changed status of pingCompliance to deprecated + - Added pingFullCompliance and pingMinimumCompliance + - Changed status of pingGroup and pingTimeStampGroup + to deprecated + - Added pingMinimumGroup, pingCtlRowStatusGroup, + and pingHistoryGroup" + + REVISION "200009210000Z" -- 21 September 2000 + DESCRIPTION + "Initial version, published as RFC 2925." + ::= { mib-2 80 } + + -- Textual Conventions + + OperationResponseStatus ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Used to report the result of an operation: + + responseReceived(1) - Operation is completed successfully. + unknown(2) - Operation failed due to unknown error. + internalError(3) - An implementation detected an error + in its own processing that caused an operation + to fail. + requestTimedOut(4) - Operation failed to receive a + valid reply within the time limit imposed on it. + unknownDestinationAddress(5) - Invalid destination + address. + noRouteToTarget(6) - Could not find a route to target. + interfaceInactiveToTarget(7) - The interface to be + used in sending a probe is inactive, and an + alternate route does not exist. + arpFailure(8) - Unable to resolve a target address to a + media-specific address. + maxConcurrentLimitReached(9) - The maximum number of + concurrent active operations would have been exceeded + if the corresponding operation was allowed. + unableToResolveDnsName(10) - The DNS name specified was + unable to be mapped to an IP address. + invalidHostAddress(11) - The IP address for a host + has been determined to be invalid. Examples of this + are broadcast or multicast addresses." + SYNTAX INTEGER { + responseReceived(1), + unknown(2), + internalError(3), + requestTimedOut(4), + unknownDestinationAddress(5), + noRouteToTarget(6), + interfaceInactiveToTarget(7), + arpFailure(8), + maxConcurrentLimitReached(9), + unableToResolveDnsName(10), + invalidHostAddress(11) + } + + -- Top level structure of the MIB + + pingNotifications OBJECT IDENTIFIER ::= { pingMIB 0 } + pingObjects OBJECT IDENTIFIER ::= { pingMIB 1 } + pingConformance OBJECT IDENTIFIER ::= { pingMIB 2 } + + -- The registration node (point) for ping implementation types + + pingImplementationTypeDomains OBJECT IDENTIFIER ::= { pingMIB 3 } + + pingIcmpEcho OBJECT-IDENTITY + STATUS current + DESCRIPTION + "Indicates that an implementation is using the Internet + Control Message Protocol (ICMP) 'ECHO' facility." + ::= { pingImplementationTypeDomains 1 } + + pingUdpEcho OBJECT-IDENTITY + STATUS current + DESCRIPTION + "Indicates that an implementation is using the UDP echo + port (7)." + REFERENCE + "RFC 862, 'Echo Protocol'." + ::= { pingImplementationTypeDomains 2 } + + pingSnmpQuery OBJECT-IDENTITY + STATUS current + DESCRIPTION + "Indicates that an implementation is using an SNMP query + to calculate a round trip time." + ::= { pingImplementationTypeDomains 3 } + + pingTcpConnectionAttempt OBJECT-IDENTITY + STATUS current + DESCRIPTION + "Indicates that an implementation is attempting to + connect to a TCP port in order to calculate a round + trip time." + ::= { pingImplementationTypeDomains 4 } + + -- Simple Object Definitions + + pingMaxConcurrentRequests OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "requests" + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The maximum number of concurrent active ping requests + that are allowed within an agent implementation. A value + of 0 for this object implies that there is no limit for + the number of concurrent active requests in effect. + + The limit applies only to new requests being activated. + When a new value is set, the agent will continue processing + all the requests already active, even if their number + exceeds the limit just imposed." + DEFVAL { 10 } + ::= { pingObjects 1 } + + -- Ping Control Table + + pingCtlTable OBJECT-TYPE + SYNTAX SEQUENCE OF PingCtlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines the ping Control Table for providing, via SNMP, + the capability of performing ping operations at + a remote host. The results of these operations are + stored in the pingResultsTable and the + pingProbeHistoryTable." + ::= { pingObjects 2 } + + pingCtlEntry OBJECT-TYPE + SYNTAX PingCtlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines an entry in the pingCtlTable. The first index + element, pingCtlOwnerIndex, is of type SnmpAdminString, + a textual convention that allows for use of the SNMPv3 + View-Based Access Control Model (RFC 3415, VACM) + and that allows a management application to identify its + entries. The second index, pingCtlTestName (also an + SnmpAdminString), enables the same management + application to have multiple outstanding requests." + INDEX { + pingCtlOwnerIndex, + pingCtlTestName + } + ::= { pingCtlTable 1 } + + PingCtlEntry ::= + SEQUENCE { + pingCtlOwnerIndex SnmpAdminString, + pingCtlTestName SnmpAdminString, + pingCtlTargetAddressType InetAddressType, + pingCtlTargetAddress InetAddress, + pingCtlDataSize Unsigned32, + pingCtlTimeOut Unsigned32, + pingCtlProbeCount Unsigned32, + pingCtlAdminStatus INTEGER, + pingCtlDataFill OCTET STRING, + pingCtlFrequency Unsigned32, + pingCtlMaxRows Unsigned32, + pingCtlStorageType StorageType, + pingCtlTrapGeneration BITS, + pingCtlTrapProbeFailureFilter Unsigned32, + pingCtlTrapTestFailureFilter Unsigned32, + pingCtlType OBJECT IDENTIFIER, + pingCtlDescr SnmpAdminString, + pingCtlSourceAddressType InetAddressType, + pingCtlSourceAddress InetAddress, + pingCtlIfIndex InterfaceIndexOrZero, + pingCtlByPassRouteTable TruthValue, + pingCtlDSField Unsigned32, + pingCtlRowStatus RowStatus + } + + pingCtlOwnerIndex OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "To facilitate the provisioning of access control by a + security administrator using the View-Based Access + Control Model (RFC 2575, VACM) for tables in which + multiple users may need to create or + modify entries independently, the initial index is used + as an 'owner index'. Such an initial index has a syntax + of SnmpAdminString and can thus be trivially mapped to a + securityName or groupName defined in VACM, in + accordance with a security policy. + + When used in conjunction with such a security policy, all + entries in the table belonging to a particular user (or + group) will have the same value for this initial index. + For a given user's entries in a particular table, the + object identifiers for the information in these entries + will have the same subidentifiers (except for the 'column' + subidentifier) up to the end of the encoded owner index. + To configure VACM to permit access to this portion of the + table, one would create vacmViewTreeFamilyTable entries + with the value of vacmViewTreeFamilySubtree including + the owner index portion, and vacmViewTreeFamilyMask + 'wildcarding' the column subidentifier. More elaborate + configurations are possible." + ::= { pingCtlEntry 1 } + + pingCtlTestName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The name of the ping test. This is locally unique, within + the scope of a pingCtlOwnerIndex." + ::= { pingCtlEntry 2 } + + pingCtlTargetAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the type of host address to be used at a remote + host for performing a ping operation." + DEFVAL { unknown } + ::= { pingCtlEntry 3 } + + pingCtlTargetAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the host address to be used at a remote host for + performing a ping operation. The host address type is + determined by the value of the corresponding + pingCtlTargetAddressType. + + A value for this object MUST be set prior to transitioning + its corresponding pingCtlEntry to active(1) via + pingCtlRowStatus." + DEFVAL { ''H } + ::= { pingCtlEntry 4 } + + pingCtlDataSize OBJECT-TYPE + SYNTAX Unsigned32 (0..65507) + UNITS "octets" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the size of the data portion to be + transmitted in a ping operation, in octets. Whether this + value can be applied depends on the selected + implementation method for performing a ping operation, + indicated by pingCtlType in the same conceptual row. + If the method used allows applying the value contained + + in this object, then it MUST be applied. If the specified + size is not appropriate for the chosen ping method, the + implementation SHOULD use whatever size (appropriate to + the method) is closest to the specified size. + + The maximum value for this object was computed by + subtracting the smallest possible IP header size of + 20 octets (IPv4 header with no options) and the UDP + header size of 8 octets from the maximum IP packet size. + An IP packet has a maximum size of 65535 octets + (excluding IPv6 Jumbograms)." + DEFVAL { 0 } + ::= { pingCtlEntry 5 } + + pingCtlTimeOut OBJECT-TYPE + SYNTAX Unsigned32 (1..60) + UNITS "seconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the time-out value, in seconds, for a + remote ping operation." + DEFVAL { 3 } + ::= { pingCtlEntry 6 } + + pingCtlProbeCount OBJECT-TYPE + SYNTAX Unsigned32 (1..15) + UNITS "probes" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the number of times to perform a ping + operation at a remote host as part of a single ping test." + DEFVAL { 1 } + ::= { pingCtlEntry 7 } + + pingCtlAdminStatus OBJECT-TYPE + SYNTAX INTEGER { + enabled(1), -- test should be started + disabled(2) -- test should be stopped + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Reflects the desired state that a pingCtlEntry should be + in: + + enabled(1) - Attempt to activate the test as defined by + this pingCtlEntry. + disabled(2) - Deactivate the test as defined by this + pingCtlEntry. + + Refer to the corresponding pingResultsOperStatus to + determine the operational state of the test defined by + this entry." + DEFVAL { disabled } + ::= { pingCtlEntry 8 } + + pingCtlDataFill OBJECT-TYPE + SYNTAX OCTET STRING (SIZE(0..1024)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The content of this object is used together with the + corresponding pingCtlDataSize value to determine how to + fill the data portion of a probe packet. The option of + selecting a data fill pattern can be useful when links + are compressed or have data pattern sensitivities. The + contents of pingCtlDataFill should be repeated in a ping + packet when the size of the data portion of the ping + packet is greater than the size of pingCtlDataFill." + DEFVAL { '00'H } + ::= { pingCtlEntry 9 } + + pingCtlFrequency OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "seconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The number of seconds to wait before repeating a ping test + as defined by the value of the various objects in the + corresponding row. + + A single ping test consists of a series of ping probes. + The number of probes is determined by the value of the + corresponding pingCtlProbeCount object. After a single + test is completed the number of seconds as defined by the + value of pingCtlFrequency MUST elapse before the + next ping test is started. + + A value of 0 for this object implies that the test + as defined by the corresponding entry will not be + repeated." + DEFVAL { 0 } + ::= { pingCtlEntry 10 } + + pingCtlMaxRows OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "rows" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The maximum number of corresponding entries allowed + in the pingProbeHistoryTable. An implementation of this + MIB will remove the oldest corresponding entry in the + pingProbeHistoryTable to allow the addition of an + new entry once the number of corresponding rows in the + pingProbeHistoryTable reaches this value. + + Old entries are not removed when a new test is + started. Entries are added to the pingProbeHistoryTable + until pingCtlMaxRows is reached before entries begin to + be removed. + + A value of 0 for this object disables creation of + pingProbeHistoryTable entries." + DEFVAL { 50 } + ::= { pingCtlEntry 11 } + + pingCtlStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The storage type for this conceptual row. + Conceptual rows having the value 'permanent' need not + allow write-access to any columnar objects in the row." + DEFVAL { nonVolatile } + ::= { pingCtlEntry 12 } + + pingCtlTrapGeneration OBJECT-TYPE + SYNTAX BITS { + probeFailure(0), + testFailure(1), + testCompletion(2) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object determines when and whether + to generate a notification for this entry: + + probeFailure(0) - Generate a pingProbeFailed + notification subject to the value of + pingCtlTrapProbeFailureFilter. The object + pingCtlTrapProbeFailureFilter can be used + to specify the number of consecutive probe + failures that are required before a + pingProbeFailed notification can be generated. + testFailure(1) - Generate a pingTestFailed + notification. In this instance the object + pingCtlTrapTestFailureFilter can be used to + determine the number of probe failures that + signal when a test fails. + testCompletion(2) - Generate a pingTestCompleted + notification. + + By default, no bits are set, indicating that + none of the above options is selected." + DEFVAL { {} } -- no bits set. + ::= { pingCtlEntry 13 } + + pingCtlTrapProbeFailureFilter OBJECT-TYPE + SYNTAX Unsigned32 (0..15) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object is used to determine when + to generate a pingProbeFailed NOTIFICATION. + + Setting BIT probeFailure(0) of object + pingCtlTrapGeneration to '1' implies that a + pingProbeFailed NOTIFICATION is generated only when + + a number of consecutive ping probes equal to the + value of pingCtlTrapProbeFailureFilter fail within + a given ping test. After triggering the notification, + the probe failure counter is reset to zero." + DEFVAL { 1 } + ::= { pingCtlEntry 14 } + + pingCtlTrapTestFailureFilter OBJECT-TYPE + SYNTAX Unsigned32 (0..15) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object is used to determine when + to generate a pingTestFailed NOTIFICATION. + + Setting BIT testFailure(1) of object + + pingCtlTrapGeneration to '1' implies that a + pingTestFailed NOTIFICATION is generated only when + a number of consecutive ping tests equal to the + value of pingCtlTrapProbeFailureFilter fail. + After triggering the notification, the test failure + counter is reset to zero." + DEFVAL { 1 } + ::= { pingCtlEntry 15 } + + pingCtlType OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object is used either to report or + to select the implementation method to be used for + calculating a ping response time. The value of this + object MAY be selected from pingImplementationTypeDomains. + + Additional implementation types SHOULD be allocated as + required by implementers of the DISMAN-PING-MIB under + their enterprise-specific registration point and not + beneath pingImplementationTypeDomains." + DEFVAL { pingIcmpEcho } + ::= { pingCtlEntry 16 } + + pingCtlDescr OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The purpose of this object is to provide a + descriptive name of the remote ping test." + DEFVAL { ''H } + ::= { pingCtlEntry 17 } + + pingCtlSourceAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the type of the source address, + pingCtlSourceAddress, to be used at a remote host + when a ping operation is performed." + DEFVAL { unknown } + ::= { pingCtlEntry 18 } + + pingCtlSourceAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Use the specified IP address (which must be given in + numeric form, not as a hostname) as the source address + in outgoing probe packets. On hosts with more than one + IP address, this option can be used to select the address + to be used. If the IP address is not one of this + machine's interface addresses, an error is returned and + nothing is sent. A zero-length octet string value for + this object disables source address specification. + + The address type (InetAddressType) that relates to + this object is specified by the corresponding value + of pingCtlSourceAddressType." + DEFVAL { ''H } + ::= { pingCtlEntry 19 } + + pingCtlIfIndex OBJECT-TYPE + SYNTAX InterfaceIndexOrZero + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Setting this object to an interface's ifIndex prior + to starting a remote ping operation directs + the ping probes to be transmitted over the + specified interface. A value of zero for this object + means that this option is not enabled." + DEFVAL { 0 } + ::= { pingCtlEntry 20 } + + pingCtlByPassRouteTable OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The purpose of this object is to enable optional + bypassing the route table. If enabled, the remote + host will bypass the normal routing tables and send + directly to a host on an attached network. If the + host is not on a directly attached network, an + error is returned. This option can be used to perform + the ping operation to a local host through an + interface that has no route defined (e.g., after the + interface was dropped by the routing daemon at the host)." + DEFVAL { false } + ::= { pingCtlEntry 21 } + + pingCtlDSField OBJECT-TYPE + SYNTAX Unsigned32 (0..255) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the value to store in the Type of Service + (TOS) octet in the IPv4 header or in the Traffic + Class octet in the IPv6 header, respectively, of the + IP packet used to encapsulate the ping probe. + + The octet to be set in the IP header contains the + Differentiated Services (DS) Field in the six most + significant bits. + + This option can be used to determine what effect an + explicit DS Field setting has on a ping response. + Not all values are legal or meaningful. A value of 0 + means that the function represented by this option is + not supported. DS Field usage is often not supported + by IP implementations, and not all values are supported. + Refer to RFC 2474 and RFC 3260 for guidance on usage of + this field." + REFERENCE + "Refer to RFC 1812 for the definition of the IPv4 TOS + octet and to RFC 2460 for the definition of the IPv6 + Traffic Class octet. Refer to RFC 2474 and RFC 3260 + for the definition of the Differentiated Services Field." + DEFVAL { 0 } + ::= { pingCtlEntry 22 } + + pingCtlRowStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object allows entries to be created and deleted + in the pingCtlTable. Deletion of an entry in this + table results in the deletion of all corresponding (same + pingCtlOwnerIndex and pingCtlTestName index values) + pingResultsTable and pingProbeHistoryTable entries. + + A value MUST be specified for pingCtlTargetAddress + prior to acceptance of a transition to active(1) state. + + When a value for pingCtlTargetAddress is set, + the value of object pingCtlRowStatus changes + from notReady(3) to notInService(2). + + Activation of a remote ping operation is controlled + via pingCtlAdminStatus, not by changing + this object's value to active(1). + + Transitions in and out of active(1) state are not + allowed while an entry's pingResultsOperStatus is + active(1), with the exception that deletion of + an entry in this table by setting its RowStatus + object to destroy(6) will stop an active + ping operation. + + The operational state of a ping operation + can be determined by examination of its + pingResultsOperStatus object." + REFERENCE + "See definition of RowStatus in RFC 2579, 'Textual + Conventions for SMIv2.'" + ::= { pingCtlEntry 23 } + +-- Ping Results Table + + pingResultsTable OBJECT-TYPE + SYNTAX SEQUENCE OF PingResultsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines the Ping Results Table for providing + the capability of performing ping operations at + a remote host. The results of these operations are + stored in the pingResultsTable and the pingProbeHistoryTable. + + An entry is added to the pingResultsTable when an + pingCtlEntry is started by successful transition + of its pingCtlAdminStatus object to enabled(1). + + If the object pingCtlAdminStatus already has the value + enabled(1), and if the corresponding pingResultsOperStatus + object has the value completed(3), then successfully writing + enabled(1) to object pingCtlAdminStatus re-initializes the + already existing entry in the pingResultsTable. The values + of objects in the re-initialized entry are the same as the + values of objects in a new entry would be. + + An entry is removed from the pingResultsTable when + its corresponding pingCtlEntry is deleted." + ::= { pingObjects 3 } + + pingResultsEntry OBJECT-TYPE + SYNTAX PingResultsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines an entry in the pingResultsTable. The + pingResultsTable has the same indexing as the + pingCtlTable so that a pingResultsEntry + corresponds to the pingCtlEntry that caused it to + be created." + INDEX { + pingCtlOwnerIndex, + pingCtlTestName + } + ::= { pingResultsTable 1 } + + PingResultsEntry ::= + SEQUENCE { + pingResultsOperStatus INTEGER, + pingResultsIpTargetAddressType InetAddressType, + pingResultsIpTargetAddress InetAddress, + pingResultsMinRtt Unsigned32, + pingResultsMaxRtt Unsigned32, + pingResultsAverageRtt Unsigned32, + pingResultsProbeResponses Gauge32, + pingResultsSentProbes Gauge32, + pingResultsRttSumOfSquares Unsigned32, + pingResultsLastGoodProbe DateAndTime + } + + pingResultsOperStatus OBJECT-TYPE + SYNTAX INTEGER { + enabled(1), -- test is in progress + disabled(2), -- test has stopped + completed(3) -- test is completed + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Reflects the operational state of a pingCtlEntry: + + enabled(1) - Test is active. + disabled(2) - Test has stopped. + completed(3) - Test is completed." + ::= { pingResultsEntry 1 } + + pingResultsIpTargetAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object indicates the type of address stored + in the corresponding pingResultsIpTargetAddress + object." + DEFVAL { unknown } + ::= { pingResultsEntry 2 } + + pingResultsIpTargetAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object reports the IP address associated + with a pingCtlTargetAddress value when the destination + address is specified as a DNS name. The value of + this object should be a zero-length octet string + when a DNS name is not specified or when a + specified DNS name fails to resolve. + + The address type (InetAddressType) that relates to + this object is specified by the corresponding value + of pingResultsIpTargetAddressType." + DEFVAL { ''H } + ::= { pingResultsEntry 3 } + + pingResultsMinRtt OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The minimum ping round-trip-time (RTT) received. A value + of 0 for this object implies that no RTT has been received." + ::= { pingResultsEntry 4 } + + pingResultsMaxRtt OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The maximum ping round-trip-time (RTT) received. A value + of 0 for this object implies that no RTT has been received." + ::= { pingResultsEntry 5 } + + pingResultsAverageRtt OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current average ping round-trip-time (RTT)." + ::= { pingResultsEntry 6 } + + pingResultsProbeResponses OBJECT-TYPE + SYNTAX Gauge32 + UNITS "responses" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Number of responses received for the corresponding + pingCtlEntry and pingResultsEntry. The value of this object + MUST be reported as 0 when no probe responses have been + received." + ::= { pingResultsEntry 7 } + + pingResultsSentProbes OBJECT-TYPE + SYNTAX Gauge32 + UNITS "probes" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of this object reflects the number of probes sent + for the corresponding pingCtlEntry and pingResultsEntry. + The value of this object MUST be reported as 0 when no probes + have been sent." + ::= { pingResultsEntry 8 } + + pingResultsRttSumOfSquares OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object contains the sum of the squares for all ping + responses received. Its purpose is to enable standard + deviation calculation. The value of this object MUST + be reported as 0 when no ping responses have been + received." + ::= { pingResultsEntry 9 } + + pingResultsLastGoodProbe OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Date and time when the last response was received for + a probe." + ::= { pingResultsEntry 10 } + + -- Ping Probe History Table + + pingProbeHistoryTable OBJECT-TYPE + SYNTAX SEQUENCE OF PingProbeHistoryEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines a table for storing the results of ping + operations. The number of entries in this table is + limited per entry in the pingCtlTable by the value + of the corresponding pingCtlMaxRows object. + + An entry in this table is created when the result of + a ping probe is determined. The initial 2 instance + identifier index values identify the pingCtlEntry + that a probe result (pingProbeHistoryEntry) belongs + to. An entry is removed from this table when + its corresponding pingCtlEntry is deleted. + + An implementation of this MIB will remove the oldest + entry in the pingProbeHistoryTable of the + corresponding entry in the pingCtlTable to allow + the addition of an new entry once the number of rows + in the pingProbeHistoryTable reaches the value + specified by pingCtlMaxRows for the corresponding + entry in the pingCtlTable." + ::= { pingObjects 4 } + + pingProbeHistoryEntry OBJECT-TYPE + SYNTAX PingProbeHistoryEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines an entry in the pingProbeHistoryTable. + The first two index elements identify the + pingCtlEntry that a pingProbeHistoryEntry belongs + to. The third index element selects a single + probe result." + INDEX { + + pingCtlOwnerIndex, + pingCtlTestName, + pingProbeHistoryIndex + } + ::= { pingProbeHistoryTable 1 } + + PingProbeHistoryEntry ::= + SEQUENCE { + pingProbeHistoryIndex Unsigned32, + pingProbeHistoryResponse Unsigned32, + pingProbeHistoryStatus OperationResponseStatus, + pingProbeHistoryLastRC Integer32, + pingProbeHistoryTime DateAndTime + } + + pingProbeHistoryIndex OBJECT-TYPE + SYNTAX Unsigned32 (1..'ffffffff'h) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry in this table is created when the result of + a ping probe is determined. The initial 2 instance + identifier index values identify the pingCtlEntry + that a probe result (pingProbeHistoryEntry) belongs + to. + + An implementation MUST start assigning + pingProbeHistoryIndex values at 1 and wrap after + exceeding the maximum possible value as defined by + the limit of this object ('ffffffff'h)." + ::= { pingProbeHistoryEntry 1 } + + pingProbeHistoryResponse OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The amount of time measured in milliseconds from when + a probe was sent to when its response was received or + when it timed out. The value of this object is reported + as 0 when it is not possible to transmit a probe." + ::= { pingProbeHistoryEntry 2 } + + pingProbeHistoryStatus OBJECT-TYPE + SYNTAX OperationResponseStatus + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The result of a particular probe done by a remote host." + ::= { pingProbeHistoryEntry 3 } + + pingProbeHistoryLastRC OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The last implementation-method-specific reply code received. + If the ICMP Echo capability is being used, then a successful + probe ends when an ICMP response is received that contains + the code ICMP_ECHOREPLY(0). The ICMP codes are maintained + by IANA. Standardized ICMP codes are listed at + http://www.iana.org/assignments/icmp-parameters. + The ICMPv6 codes are listed at + http://www.iana.org/assignments/icmpv6-parameters." + ::= { pingProbeHistoryEntry 4 } + + pingProbeHistoryTime OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Timestamp for when this probe result was determined." + ::= { pingProbeHistoryEntry 5 } + + -- Notification Definition section + + pingProbeFailed NOTIFICATION-TYPE + OBJECTS { + pingCtlTargetAddressType, + pingCtlTargetAddress, + pingResultsOperStatus, + pingResultsIpTargetAddressType, + pingResultsIpTargetAddress, + pingResultsMinRtt, + pingResultsMaxRtt, + pingResultsAverageRtt, + pingResultsProbeResponses, + pingResultsSentProbes, + pingResultsRttSumOfSquares, + pingResultsLastGoodProbe + } + STATUS current + DESCRIPTION + "Generated when a probe failure is detected, when the + + corresponding pingCtlTrapGeneration object is set to + probeFailure(0), subject to the value of + pingCtlTrapProbeFailureFilter. The object + pingCtlTrapProbeFailureFilter can be used to specify the + number of consecutive probe failures that are required + before this notification can be generated." + ::= { pingNotifications 1 } + + pingTestFailed NOTIFICATION-TYPE + OBJECTS { + pingCtlTargetAddressType, + pingCtlTargetAddress, + pingResultsOperStatus, + pingResultsIpTargetAddressType, + pingResultsIpTargetAddress, + pingResultsMinRtt, + pingResultsMaxRtt, + pingResultsAverageRtt, + pingResultsProbeResponses, + pingResultsSentProbes, + pingResultsRttSumOfSquares, + pingResultsLastGoodProbe + } + STATUS current + DESCRIPTION + "Generated when a ping test is determined to have failed, + when the corresponding pingCtlTrapGeneration object is + set to testFailure(1). In this instance, + pingCtlTrapTestFailureFilter should specify the number of + probes in a test required to have failed in order to + consider the test failed." + ::= { pingNotifications 2 } + + pingTestCompleted NOTIFICATION-TYPE + OBJECTS { + pingCtlTargetAddressType, + pingCtlTargetAddress, + pingResultsOperStatus, + pingResultsIpTargetAddressType, + pingResultsIpTargetAddress, + pingResultsMinRtt, + pingResultsMaxRtt, + pingResultsAverageRtt, + pingResultsProbeResponses, + pingResultsSentProbes, + pingResultsRttSumOfSquares, + pingResultsLastGoodProbe + + } + STATUS current + DESCRIPTION + "Generated at the completion of a ping test when the + corresponding pingCtlTrapGeneration object has the + testCompletion(2) bit set." + ::= { pingNotifications 3 } + + -- Conformance information + + -- Compliance statements + + pingCompliances OBJECT IDENTIFIER ::= { pingConformance 1 } + pingGroups OBJECT IDENTIFIER ::= { pingConformance 2 } + + -- Compliance statements + + pingFullCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP entities that + fully implement the DISMAN-PING-MIB." + MODULE -- this module + MANDATORY-GROUPS { + pingMinimumGroup, + pingCtlRowStatusGroup, + pingHistoryGroup, + pingNotificationsGroup + } + + OBJECT pingMaxConcurrentRequests + MIN-ACCESS read-only + DESCRIPTION + "The agent is not required to support set + operations to this object." + + OBJECT pingCtlStorageType + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT pingCtlType + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. In addition, the only + value that MUST be supported by an implementation is + pingIcmpEcho." + + OBJECT pingCtlSourceAddressType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + MIN-ACCESS read-only + DESCRIPTION + "Write access to this object is not required by + implementations that are not capable of binding the + send socket with a source address. An implementation + is only required to support IPv4 and IPv6 addresses." + + OBJECT pingCtlSourceAddress + SYNTAX InetAddress (SIZE(0|4|16)) + MIN-ACCESS read-only + DESCRIPTION + "Write access to this object is not required by + implementations that are not capable of binding the + send socket with a source address. An implementation + is only required to support IPv4 and IPv6 addresses." + + OBJECT pingCtlIfIndex + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If write access is + not supported, return a 0 as the value of this object. + A value of 0 means that the function represented by + this option is not supported." + + OBJECT pingCtlByPassRouteTable + MIN-ACCESS read-only + DESCRIPTION + "Write access to this object is not required by + implementations that are not capable of its + implementation. The function represented by this + object is implementable if the setsockopt + SOL_SOCKET SO_DONTROUTE option is supported." + + OBJECT pingCtlDSField + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If write access is + not supported, return a 0 as the value of this object. + A value of 0 means that the function represented by + this option is not supported." + + OBJECT pingResultsIpTargetAddressType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + DESCRIPTION + "An implementation is only required to + + support IPv4 and IPv6 addresses." + + OBJECT pingResultsIpTargetAddress + SYNTAX InetAddress (SIZE(0|4|16)) + DESCRIPTION + "An implementation is only required to + support IPv4 and globally unique IPv6 addresses." + + OBJECT pingResultsLastGoodProbe + DESCRIPTION + "This object is mandatory for implementations that have + access to a system clock and that are capable of setting + the values for DateAndTime objects. It is RECOMMENDED + that when this object is not supported its values + be reported as '0000000000000000'H." + + OBJECT pingProbeHistoryTime + DESCRIPTION + "This object is mandatory for implementations that have + access to a system clock and that are capable of setting + the values for DateAndTime objects. It is RECOMMENDED + that when this object is not supported its values + be reported as '0000000000000000'H." + ::= { pingCompliances 2 } + + pingMinimumCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The minimum compliance statement for SNMP entities + that implement the minimal subset of the + DISMAN-PING-MIB. Implementors might choose this + subset for small devices with limited resources." + MODULE -- this module + MANDATORY-GROUPS { pingMinimumGroup } + + GROUP pingCtlRowStatusGroup + DESCRIPTION + "A compliant implementation does not have to implement + the pingCtlRowStatusGroup." + + GROUP pingHistoryGroup + DESCRIPTION + "A compliant implementation does not have to implement + the pingHistoryGroup." + + GROUP pingNotificationsGroup + DESCRIPTION + "A compliant implementation does not have to implement + + the pingNotificationsGroup." + + OBJECT pingMaxConcurrentRequests + MIN-ACCESS read-only + DESCRIPTION + "The agent is not required to support set + operations to this object." + + OBJECT pingCtlDataFill + MIN-ACCESS read-only + DESCRIPTION + "The agent is not required to support set + operations to this object." + + OBJECT pingCtlFrequency + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If write access is + not supported, return a 0 as the value of this object. + A value of 0 means that the function represented by + this option is not supported." + + OBJECT pingCtlMaxRows + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If the + pingHistoryGroup is not implemented, then write + access to this object MUST be disabled, and the object + MUST return a value of 0 when retrieved." + + OBJECT pingCtlStorageType + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT pingCtlTrapGeneration + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If the + pingNotificationsGroup is not implemented, then write + access to this object MUST be disabled, and the object + MUST return a value with no bit set when retrieved. + No bit set indicates that not notification is + generated." + + OBJECT pingCtlTrapProbeFailureFilter + MIN-ACCESS read-only + DESCRIPTION + "If write access to pingCtlTrapGeneration is not + supported, then write access to this object must also + not be supported. In this case, return 0 as the value + of this object." + + OBJECT pingCtlTrapTestFailureFilter + MIN-ACCESS read-only + DESCRIPTION + "If write access to pingCtlTrapGeneration is not + supported, then write access to this object must also + not be supported. In this case, return 0 as the value + of this object." + + OBJECT pingCtlType + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. In addition, the only + value that MUST be supported by an implementation is + pingIcmpEcho." + + OBJECT pingCtlDescr + MIN-ACCESS read-only + DESCRIPTION + "The agent is not required to support set + operations to this object." + + OBJECT pingCtlSourceAddressType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + MIN-ACCESS read-only + DESCRIPTION + "Write access to this object is not required by + implementations that are not capable of binding the + send socket with a source address. An implementation + is only required to support IPv4 and IPv6 addresses." + + OBJECT pingCtlSourceAddress + SYNTAX InetAddress (SIZE(0|4|16)) + MIN-ACCESS read-only + DESCRIPTION + "Write access to this object is not required by + implementations that are not capable of binding the + send socket with a source address. An implementation + is only required to support IPv4 and IPv6 addresses." + + OBJECT pingCtlIfIndex + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If write access is + + not supported, return a 0 as the value of this object. + A value of 0 means that the function represented by + this option is not supported." + + OBJECT pingCtlByPassRouteTable + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If write access is + not supported, return false(2) as the value of this + object. A value of false(2) means that the function + represented by this option is not supported." + + OBJECT pingCtlDSField + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If write access is + not supported, return a 0 as the value of this object. + A value of 0 means that the function represented by + this option is not supported." + + OBJECT pingResultsIpTargetAddressType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + DESCRIPTION + "An implementation is only required to + support IPv4 and IPv6 addresses." + + OBJECT pingResultsIpTargetAddress + SYNTAX InetAddress (SIZE(0|4|16)) + DESCRIPTION + "An implementation is only required to + support IPv4 and globally unique IPv6 addresses." + + OBJECT pingResultsLastGoodProbe + DESCRIPTION + "This object is mandatory for implementations that have + access to a system clock and that are capable of setting + the values for DateAndTime objects. It is RECOMMENDED + that when this object is not supported its values + be reported as '0000000000000000'H." + + OBJECT pingProbeHistoryTime + DESCRIPTION + "If the pingHistoryGroup is implemented, then this + object is mandatory for implementations that have + access to a system clock and that are capable of setting + the values for DateAndTime objects. It is RECOMMENDED + that when this object is not supported its values + + be reported as '0000000000000000'H." + ::= { pingCompliances 3 } + + pingCompliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION + "The compliance statement for the DISMAN-PING-MIB. This + compliance statement has been deprecated because the + group pingGroup and the pingTimeStampGroup have been + split and deprecated. The pingFullCompliance statement + is semantically identical to the deprecated + pingCompliance statement." + + MODULE -- this module + MANDATORY-GROUPS { + pingGroup, + pingNotificationsGroup + } + GROUP pingTimeStampGroup + DESCRIPTION + "This group is mandatory for implementations that have + access to a system clock and that are capable of setting + the values for DateAndTime objects. It is RECOMMENDED + that when this group is not supported the values + for the objects in this group be reported as + '0000000000000000'H." + + OBJECT pingMaxConcurrentRequests + MIN-ACCESS read-only + DESCRIPTION + "The agent is not required to support set + operations to this object." + + OBJECT pingCtlStorageType + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. It is also allowed + that implementations support only the volatile + StorageType enumeration." + + OBJECT pingCtlType + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. In addition, the only + value that MUST be supported by an implementation is + pingIcmpEcho." + + OBJECT pingCtlByPassRouteTable + MIN-ACCESS read-only + DESCRIPTION + "This object is not required by implementations that + are not capable of its implementation. The function + represented by this object is implementable if the + setsockopt SOL_SOCKET SO_DONTROUTE option is + supported." + + OBJECT pingCtlSourceAddressType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + MIN-ACCESS read-only + DESCRIPTION + "This object is not required by implementations that + are not capable of binding the send socket with a + source address. An implementation is only required to + support IPv4 and IPv6 addresses." + + OBJECT pingCtlSourceAddress + SYNTAX InetAddress (SIZE(0|4|16)) + MIN-ACCESS read-only + DESCRIPTION + "This object is not required by implementations that + are not capable of binding the send socket with a + source address. An implementation is only required to + support IPv4 and globally unique IPv6 addresses." + + OBJECT pingCtlIfIndex + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. When write access is + not supported, return a 0 as the value of this object. + A value of 0 means that the function represented by + this option is not supported." + + OBJECT pingCtlDSField + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. When write access is + not supported, return a 0 as the value of this object. + A value of 0 means that the function represented by + this option is not supported." + + OBJECT pingResultsIpTargetAddressType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + DESCRIPTION + "An implementation is only required to + support IPv4 and IPv6 addresses." + + OBJECT pingResultsIpTargetAddress + SYNTAX InetAddress (SIZE(0|4|16)) + DESCRIPTION + "An implementation is only required to + support IPv4 and globally unique IPv6 addresses." + ::= { pingCompliances 1 } + + -- MIB groupings + + pingMinimumGroup OBJECT-GROUP + OBJECTS { + pingMaxConcurrentRequests, + pingCtlTargetAddressType, + pingCtlTargetAddress, + pingCtlDataSize, + pingCtlTimeOut, + pingCtlProbeCount, + pingCtlAdminStatus, + pingCtlDataFill, + pingCtlFrequency, + pingCtlMaxRows, + pingCtlStorageType, + pingCtlTrapGeneration, + pingCtlTrapProbeFailureFilter, + pingCtlTrapTestFailureFilter, + pingCtlType, + pingCtlDescr, + pingCtlByPassRouteTable, + pingCtlSourceAddressType, + pingCtlSourceAddress, + pingCtlIfIndex, + pingCtlDSField, + pingResultsOperStatus, + pingResultsIpTargetAddressType, + pingResultsIpTargetAddress, + pingResultsMinRtt, + pingResultsMaxRtt, + pingResultsAverageRtt, + pingResultsProbeResponses, + pingResultsSentProbes, + pingResultsRttSumOfSquares, + pingResultsLastGoodProbe + } + STATUS current + DESCRIPTION + "The group of objects that constitute the remote ping + capability." + ::= { pingGroups 4 } + + pingCtlRowStatusGroup OBJECT-GROUP + OBJECTS { + pingCtlRowStatus + } + STATUS current + DESCRIPTION + "The RowStatus object of the pingCtlTable." + ::= { pingGroups 5 } + + pingHistoryGroup OBJECT-GROUP + OBJECTS { + pingProbeHistoryResponse, + pingProbeHistoryStatus, + pingProbeHistoryLastRC, + pingProbeHistoryTime + } + STATUS current + DESCRIPTION + "The group of objects that constitute the history + capability." + ::= { pingGroups 6 } + + pingNotificationsGroup NOTIFICATION-GROUP + NOTIFICATIONS { + pingProbeFailed, + pingTestFailed, + pingTestCompleted + } + STATUS current + DESCRIPTION + "The notification that are required to be supported by + implementations of this MIB." + ::= { pingGroups 3 } + + pingGroup OBJECT-GROUP + OBJECTS { + pingMaxConcurrentRequests, + pingCtlTargetAddressType, + pingCtlTargetAddress, + pingCtlDataSize, + pingCtlTimeOut, + pingCtlProbeCount, + pingCtlAdminStatus, + pingCtlDataFill, + pingCtlFrequency, + pingCtlMaxRows, + pingCtlStorageType, + pingCtlTrapGeneration, + pingCtlTrapProbeFailureFilter, + pingCtlTrapTestFailureFilter, + pingCtlType, + pingCtlDescr, + pingCtlByPassRouteTable, + pingCtlSourceAddressType, + pingCtlSourceAddress, + pingCtlIfIndex, + pingCtlDSField, + pingCtlRowStatus, + pingResultsOperStatus, + pingResultsIpTargetAddressType, + pingResultsIpTargetAddress, + pingResultsMinRtt, + pingResultsMaxRtt, + pingResultsAverageRtt, + pingResultsProbeResponses, + pingResultsSentProbes, + pingResultsRttSumOfSquares, + pingProbeHistoryResponse, + pingProbeHistoryStatus, + pingProbeHistoryLastRC + } + STATUS deprecated + DESCRIPTION + "The group of objects that constitute the remote ping + capability." + ::= { pingGroups 1 } + + pingTimeStampGroup OBJECT-GROUP + + OBJECTS { + pingResultsLastGoodProbe, + pingProbeHistoryTime + } + STATUS deprecated + DESCRIPTION + "The group of DateAndTime objects." + ::= { pingGroups 2 } + +END diff --git a/mibs/DISMAN-SCHEDULE-MIB.txt b/mibs/DISMAN-SCHEDULE-MIB.txt new file mode 100644 index 0000000..239595e --- /dev/null +++ b/mibs/DISMAN-SCHEDULE-MIB.txt @@ -0,0 +1,699 @@ +DISMAN-SCHEDULE-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE, + Integer32, Unsigned32, Counter32, mib-2, zeroDotZero + FROM SNMPv2-SMI + + TEXTUAL-CONVENTION, + DateAndTime, RowStatus, StorageType, VariablePointer + FROM SNMPv2-TC + + MODULE-COMPLIANCE, OBJECT-GROUP, NOTIFICATION-GROUP + FROM SNMPv2-CONF + + SnmpAdminString + FROM SNMP-FRAMEWORK-MIB; + +schedMIB MODULE-IDENTITY + LAST-UPDATED "200201070000Z" + ORGANIZATION "IETF Distributed Management Working Group" + CONTACT-INFO + "WG EMail: disman@dorothy.bmc.com + Subscribe: disman-request@dorothy.bmc.com + + Chair: Randy Presuhn + BMC Software, Inc. + Postal: Office 1-3141 + 2141 North First Street + San Jose, California 95131 + USA + EMail: rpresuhn@bmc.com + Phone: +1 408 546-1006 + + Editor: David B. Levi + Nortel Networks + Postal: 4401 Great America Parkway + Santa Clara, CA 95052-8185 + USA + EMail: dlevi@nortelnetworks.com + Phone: +1 865 686 0432 + + Editor: Juergen Schoenwaelder + TU Braunschweig + Postal: Bueltenweg 74/75 + 38106 Braunschweig + Germany + EMail: schoenw@ibr.cs.tu-bs.de + Phone: +49 531 391-3283" + DESCRIPTION + "This MIB module defines a MIB which provides mechanisms to + schedule SNMP set operations periodically or at specific + points in time." + REVISION "200201070000Z" + DESCRIPTION + "Revised version, published as RFC 3231. + + This revision introduces a new object type called + schedTriggers. Created new conformance and compliance + statements that take care of the new schedTriggers object. + + Several clarifications have been added to remove ambiguities + that were discovered and reported by implementors." + REVISION "199811171800Z" + DESCRIPTION + "Initial version, published as RFC 2591." + ::= { mib-2 63 } + +-- +-- The various groups defined within this MIB definition: +-- + +schedObjects OBJECT IDENTIFIER ::= { schedMIB 1 } +schedNotifications OBJECT IDENTIFIER ::= { schedMIB 2 } +schedConformance OBJECT IDENTIFIER ::= { schedMIB 3 } + +-- +-- Textual Conventions: +-- + +SnmpPduErrorStatus ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "This TC enumerates the SNMPv1 and SNMPv2 PDU error status + codes as defined in RFC 1157 and RFC 1905. It also adds a + pseudo error status code `noResponse' which indicates a + timeout condition." + SYNTAX INTEGER { + noResponse(-1), + noError(0), + tooBig(1), + noSuchName(2), + badValue(3), + readOnly(4), + genErr(5), + noAccess(6), + wrongType(7), + wrongLength(8), + wrongEncoding(9), + wrongValue(10), + noCreation(11), + inconsistentValue(12), + resourceUnavailable(13), + commitFailed(14), + undoFailed(15), + authorizationError(16), + notWritable(17), + inconsistentName(18) + } + +-- +-- Some scalars which provide information about the local time zone. +-- + +schedLocalTime OBJECT-TYPE + SYNTAX DateAndTime (SIZE (11)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The local time used by the scheduler. Schedules which + refer to calendar time will use the local time indicated + by this object. An implementation MUST return all 11 bytes + of the DateAndTime textual-convention so that a manager + may retrieve the offset from GMT time." + ::= { schedObjects 1 } + +-- +-- The schedule table which controls the scheduler. +-- + +schedTable OBJECT-TYPE + SYNTAX SEQUENCE OF SchedEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table defines scheduled actions triggered by + SNMP set operations." + ::= { schedObjects 2 } + +schedEntry OBJECT-TYPE + SYNTAX SchedEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry describing a particular scheduled action. + + Unless noted otherwise, writable objects of this row + can be modified independent of the current value of + schedRowStatus, schedAdminStatus and schedOperStatus. + In particular, it is legal to modify schedInterval + and the objects in the schedCalendarGroup when + schedRowStatus is active and schedAdminStatus and + schedOperStatus are both enabled." + INDEX { schedOwner, schedName } + ::= { schedTable 1 } + +SchedEntry ::= SEQUENCE { + schedOwner SnmpAdminString, + schedName SnmpAdminString, + schedDescr SnmpAdminString, + schedInterval Unsigned32, + schedWeekDay BITS, + schedMonth BITS, + schedDay BITS, + schedHour BITS, + schedMinute BITS, + schedContextName SnmpAdminString, + schedVariable VariablePointer, + schedValue Integer32, + schedType INTEGER, + schedAdminStatus INTEGER, + schedOperStatus INTEGER, + schedFailures Counter32, + schedLastFailure SnmpPduErrorStatus, + schedLastFailed DateAndTime, + schedStorageType StorageType, + schedRowStatus RowStatus, + schedTriggers Counter32 +} + +schedOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The owner of this scheduling entry. The exact semantics of + this string are subject to the security policy defined by + + the security administrator." + ::= { schedEntry 1 } + +schedName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(1..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The locally-unique, administratively assigned name for this + scheduling entry. This object allows a schedOwner to have + multiple entries in the schedTable." + ::= { schedEntry 2 } + +schedDescr OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The human readable description of the purpose of this + scheduling entry." + DEFVAL { "" } + ::= { schedEntry 3 } + +schedInterval OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "seconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The number of seconds between two action invocations of + a periodic scheduler. Implementations must guarantee + that action invocations will not occur before at least + schedInterval seconds have passed. + + The scheduler must ignore all periodic schedules that + have a schedInterval value of 0. A periodic schedule + with a scheduling interval of 0 seconds will therefore + never invoke an action. + + Implementations may be forced to delay invocations in the + face of local constraints. A scheduled management function + should therefore not rely on the accuracy provided by the + scheduler implementation. + + Note that implementations which maintain a list of pending + activations must re-calculate them when this object is + changed." + DEFVAL { 0 } + ::= { schedEntry 4 } + +schedWeekDay OBJECT-TYPE + SYNTAX BITS { + sunday(0), + monday(1), + tuesday(2), + wednesday(3), + thursday(4), + friday(5), + saturday(6) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The set of weekdays on which the scheduled action should + take place. Setting multiple bits will include several + weekdays in the set of possible weekdays for this schedule. + Setting all bits will cause the scheduler to ignore the + weekday. + + Note that implementations which maintain a list of pending + activations must re-calculate them when this object is + changed." + DEFVAL { {} } + ::= { schedEntry 5 } + +schedMonth OBJECT-TYPE + SYNTAX BITS { + january(0), + february(1), + march(2), + april(3), + may(4), + june(5), + july(6), + august(7), + september(8), + october(9), + november(10), + december(11) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The set of months during which the scheduled action should + take place. Setting multiple bits will include several + months in the set of possible months for this schedule. + + Setting all bits will cause the scheduler to ignore the + month. + + Note that implementations which maintain a list of pending + activations must re-calculate them when this object is + changed." + DEFVAL { {} } + ::= { schedEntry 6 } + +schedDay OBJECT-TYPE + SYNTAX BITS { + d1(0), d2(1), d3(2), d4(3), d5(4), + d6(5), d7(6), d8(7), d9(8), d10(9), + d11(10), d12(11), d13(12), d14(13), d15(14), + d16(15), d17(16), d18(17), d19(18), d20(19), + d21(20), d22(21), d23(22), d24(23), d25(24), + d26(25), d27(26), d28(27), d29(28), d30(29), + d31(30), + r1(31), r2(32), r3(33), r4(34), r5(35), + r6(36), r7(37), r8(38), r9(39), r10(40), + r11(41), r12(42), r13(43), r14(44), r15(45), + r16(46), r17(47), r18(48), r19(49), r20(50), + r21(51), r22(52), r23(53), r24(54), r25(55), + r26(56), r27(57), r28(58), r29(59), r30(60), + r31(61) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The set of days in a month on which a scheduled action + should take place. There are two sets of bits one can + use to define the day within a month: + + Enumerations starting with the letter 'd' indicate a + day in a month relative to the first day of a month. + The first day of the month can therefore be specified + by setting the bit d1(0) and d31(30) means the last + day of a month with 31 days. + + Enumerations starting with the letter 'r' indicate a + day in a month in reverse order, relative to the last + day of a month. The last day in the month can therefore + be specified by setting the bit r1(31) and r31(61) means + the first day of a month with 31 days. + + Setting multiple bits will include several days in the set + of possible days for this schedule. Setting all bits will + cause the scheduler to ignore the day within a month. + + Setting all bits starting with the letter 'd' or the + letter 'r' will also cause the scheduler to ignore the + day within a month. + + Note that implementations which maintain a list of pending + activations must re-calculate them when this object is + changed." + DEFVAL { {} } + ::= { schedEntry 7 } + +schedHour OBJECT-TYPE + SYNTAX BITS { + h0(0), h1(1), h2(2), h3(3), h4(4), + h5(5), h6(6), h7(7), h8(8), h9(9), + h10(10), h11(11), h12(12), h13(13), h14(14), + h15(15), h16(16), h17(17), h18(18), h19(19), + h20(20), h21(21), h22(22), h23(23) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The set of hours within a day during which the scheduled + action should take place. + + Note that implementations which maintain a list of pending + activations must re-calculate them when this object is + changed." + DEFVAL { {} } + ::= { schedEntry 8 } + +schedMinute OBJECT-TYPE + SYNTAX BITS { + m0(0), m1(1), m2(2), m3(3), m4(4), + m5(5), m6(6), m7(7), m8(8), m9(9), + m10(10), m11(11), m12(12), m13(13), m14(14), + m15(15), m16(16), m17(17), m18(18), m19(19), + m20(20), m21(21), m22(22), m23(23), m24(24), + m25(25), m26(26), m27(27), m28(28), m29(29), + m30(30), m31(31), m32(32), m33(33), m34(34), + m35(35), m36(36), m37(37), m38(38), m39(39), + m40(40), m41(41), m42(42), m43(43), m44(44), + m45(45), m46(46), m47(47), m48(48), m49(49), + m50(50), m51(51), m52(52), m53(53), m54(54), + m55(55), m56(56), m57(57), m58(58), m59(59) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The set of minutes within an hour when the scheduled action + should take place. + + Note that implementations which maintain a list of pending + activations must re-calculate them when this object is + changed." + DEFVAL { {} } + ::= { schedEntry 9 } + +schedContextName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The context which contains the local MIB variable pointed + to by schedVariable." + DEFVAL { "" } + ::= { schedEntry 10 } + +schedVariable OBJECT-TYPE + SYNTAX VariablePointer + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "An object identifier pointing to a local MIB variable + which resolves to an ASN.1 primitive type of INTEGER." + DEFVAL { zeroDotZero } + ::= { schedEntry 11 } + +schedValue OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value which is written to the MIB object pointed to by + schedVariable when the scheduler invokes an action. The + implementation shall enforce the use of access control + rules when performing the set operation on schedVariable. + This is accomplished by calling the isAccessAllowed abstract + service interface as defined in RFC 2571. + + Note that an implementation may choose to issue an SNMP Set + message to the SNMP engine and leave the access control + decision to the normal message processing procedure." + DEFVAL { 0 } + ::= { schedEntry 12 } + +schedType OBJECT-TYPE + SYNTAX INTEGER { + periodic(1), + calendar(2), + oneshot(3) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The type of this schedule. The value periodic(1) indicates + that this entry specifies a periodic schedule. A periodic + schedule is defined by the value of schedInterval. The + values of schedWeekDay, schedMonth, schedDay, schedHour + and schedMinute are ignored. + + The value calendar(2) indicates that this entry describes a + calendar schedule. A calendar schedule is defined by the + values of schedWeekDay, schedMonth, schedDay, schedHour and + schedMinute. The value of schedInterval is ignored. A + calendar schedule will trigger on all local times that + satisfy the bits set in schedWeekDay, schedMonth, schedDay, + schedHour and schedMinute. + + The value oneshot(3) indicates that this entry describes a + one-shot schedule. A one-shot schedule is similar to a + calendar schedule with the additional feature that it + disables itself by changing in the `finished' + schedOperStatus once the schedule triggers an action. + + Note that implementations which maintain a list of pending + activations must re-calculate them when this object is + changed." + DEFVAL { periodic } + ::= { schedEntry 13 } + +schedAdminStatus OBJECT-TYPE + SYNTAX INTEGER { + enabled(1), + disabled(2) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The desired state of the schedule." + DEFVAL { disabled } + ::= { schedEntry 14 } + +schedOperStatus OBJECT-TYPE + SYNTAX INTEGER { + + enabled(1), + disabled(2), + finished(3) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current operational state of this schedule. The state + enabled(1) indicates this entry is active and that the + scheduler will invoke actions at appropriate times. The + disabled(2) state indicates that this entry is currently + inactive and ignored by the scheduler. The finished(3) + state indicates that the schedule has ended. Schedules + in the finished(3) state are ignored by the scheduler. + A one-shot schedule enters the finished(3) state when it + deactivates itself. + + Note that the operational state must not be enabled(1) + when the schedRowStatus is not active." + ::= { schedEntry 15 } + +schedFailures OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This variable counts the number of failures while invoking + the scheduled action. This counter at most increments once + for a triggered action." + ::= { schedEntry 16 } + +schedLastFailure OBJECT-TYPE + SYNTAX SnmpPduErrorStatus + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The most recent error that occurred during the invocation of + a scheduled action. The value noError(0) is returned + if no errors have occurred yet." + DEFVAL { noError } + ::= { schedEntry 17 } + +schedLastFailed OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The date and time when the most recent failure occurred. + + The value '0000000000000000'H is returned if no failure + occurred since the last re-initialization of the scheduler." + DEFVAL { '0000000000000000'H } + ::= { schedEntry 18 } + +schedStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object defines whether this scheduled action is kept + in volatile storage and lost upon reboot or if this row is + backed up by non-volatile or permanent storage. + + Conceptual rows having the value `permanent' must allow + write access to the columnar objects schedDescr, + schedInterval, schedContextName, schedVariable, schedValue, + and schedAdminStatus. If an implementation supports the + schedCalendarGroup, write access must be also allowed to + the columnar objects schedWeekDay, schedMonth, schedDay, + schedHour, schedMinute." + DEFVAL { volatile } + ::= { schedEntry 19 } + +schedRowStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this scheduled action. A control that allows + entries to be added and removed from this table. + + Note that the operational state must change to enabled + when the administrative state is enabled and the row + status changes to active(1). + + Attempts to destroy(6) a row or to set a row + notInService(2) while the operational state is enabled + result in inconsistentValue errors. + + The value of this object has no effect on whether other + objects in this conceptual row can be modified." + ::= { schedEntry 20 } + +schedTriggers OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This variable counts the number of attempts (either + successful or failed) to invoke the scheduled action." + ::= { schedEntry 21 } + +-- +-- Notifications that are emitted to indicate failures. The +-- definition of schedTraps makes notification registrations +-- reversible (see STD 58, RFC 2578). +-- + +schedTraps OBJECT IDENTIFIER ::= { schedNotifications 0 } + +schedActionFailure NOTIFICATION-TYPE + OBJECTS { schedLastFailure, schedLastFailed } + STATUS current + DESCRIPTION + "This notification is generated whenever the invocation of a + scheduled action fails." + ::= { schedTraps 1 } + +-- conformance information + +schedCompliances OBJECT IDENTIFIER ::= { schedConformance 1 } +schedGroups OBJECT IDENTIFIER ::= { schedConformance 2 } + +-- compliance statements + +schedCompliance2 MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP entities which implement + the scheduling MIB." + MODULE -- this module + MANDATORY-GROUPS { + schedGroup2, schedNotificationsGroup + } + GROUP schedCalendarGroup + DESCRIPTION + "The schedCalendarGroup is mandatory only for those + implementations that support calendar based schedules." + OBJECT schedType + DESCRIPTION + "The values calendar(2) or oneshot(3) are not valid for + implementations that do not implement the + schedCalendarGroup. Such an implementation must return + inconsistentValue error responses for attempts to set + schedAdminStatus to calendar(2) or oneshot(3)." + ::= { schedCompliances 2 } + +schedGroup2 OBJECT-GROUP + OBJECTS { + schedDescr, schedInterval, schedContextName, + schedVariable, schedValue, schedType, + schedAdminStatus, schedOperStatus, schedFailures, + schedLastFailure, schedLastFailed, schedStorageType, + schedRowStatus, schedTriggers + } + STATUS current + DESCRIPTION + "A collection of objects providing scheduling capabilities." + ::= { schedGroups 4 } + +schedCalendarGroup OBJECT-GROUP + OBJECTS { + schedLocalTime, schedWeekDay, schedMonth, + schedDay, schedHour, schedMinute + } + STATUS current + DESCRIPTION + "A collection of objects providing calendar based schedules." + ::= { schedGroups 2 } + +schedNotificationsGroup NOTIFICATION-GROUP + NOTIFICATIONS { + schedActionFailure + } + STATUS current + DESCRIPTION + "The notifications emitted by the scheduler." + ::= { schedGroups 3 } + +-- +-- Deprecated compliance and conformance group definitions +-- from RFC 2591. +-- + +schedCompliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION + "The compliance statement for SNMP entities which implement + the scheduling MIB." + MODULE -- this module + MANDATORY-GROUPS { + schedGroup, schedNotificationsGroup + } + + GROUP schedCalendarGroup + DESCRIPTION + "The schedCalendarGroup is mandatory only for those + implementations that support calendar based schedules." + OBJECT schedType + DESCRIPTION + "The values calendar(2) or oneshot(3) are not valid for + implementations that do not implement the + schedCalendarGroup. Such an implementation must return + inconsistentValue error responses for attempts to set + schedAdminStatus to calendar(2) or oneshot(3)." + ::= { schedCompliances 1 } + +schedGroup OBJECT-GROUP + OBJECTS { + schedDescr, schedInterval, schedContextName, + schedVariable, schedValue, schedType, + schedAdminStatus, schedOperStatus, schedFailures, + schedLastFailure, schedLastFailed, schedStorageType, + schedRowStatus + } + STATUS deprecated + DESCRIPTION + "A collection of objects providing scheduling capabilities." + ::= { schedGroups 1 } + +END diff --git a/mibs/DISMAN-SCRIPT-MIB.txt b/mibs/DISMAN-SCRIPT-MIB.txt new file mode 100644 index 0000000..834f304 --- /dev/null +++ b/mibs/DISMAN-SCRIPT-MIB.txt @@ -0,0 +1,1764 @@ +DISMAN-SCRIPT-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE, + Integer32, Unsigned32, mib-2 + FROM SNMPv2-SMI + + RowStatus, TimeInterval, DateAndTime, StorageType, DisplayString + FROM SNMPv2-TC + + MODULE-COMPLIANCE, OBJECT-GROUP, NOTIFICATION-GROUP + FROM SNMPv2-CONF + + SnmpAdminString + FROM SNMP-FRAMEWORK-MIB; + +scriptMIB MODULE-IDENTITY + LAST-UPDATED "200108210000Z" + ORGANIZATION "IETF Distributed Management Working Group" + CONTACT-INFO + "WG EMail: disman@dorothy.bmc.com + Subscribe: disman-request@dorothy.bmc.com + + Chair: Randy Presuhn + BMC Software, Inc. + + Postal: Office 1-3141 + 2141 North First Street + San Jose, California 95131 + USA + EMail: rpresuhn@bmc.com + Phone: +1 408 546-1006 + + Editor: David B. Levi + Nortel Networks + Postal: 4401 Great America Parkway + Santa Clara, CA 95052-8185 + USA + EMail: dlevi@nortelnetworks.com + Phone: +1 423 686 0432 + + Editor: Juergen Schoenwaelder + TU Braunschweig + Postal: Bueltenweg 74/75 + 38106 Braunschweig + Germany + EMail: schoenw@ibr.cs.tu-bs.de + Phone: +49 531 391-3283" + DESCRIPTION + "This MIB module defines a set of objects that allow to + delegate management scripts to distributed managers." + REVISION "200108210000Z" + DESCRIPTION + "Revised version, published as RFC 3165. + + This revision introduces several new objects: smScriptError, + smScriptLastChange, smLaunchError, smLaunchLastChange, + smLaunchRowExpireTime, smRunResultTime, and smRunErrorTime. + + The following existing objects were updated: the maximum + value of smRunLifeTime now disables the timer, an + autostart value was added to the smLaunchAdminStatus + object, and a new expired state was added to the + smLaunchOperStatus object. + + A new smScriptException notification has been added to + support runtime error notifications. + + Created new conformance and compliance statements that + take care of the new objects and notifications. + + Clarifications have been added in several places to remove + ambiguities or contradictions that were discovered and + reported by implementors." + + REVISION "199902221800Z" + DESCRIPTION + "Initial version, published as RFC 2592." + ::= { mib-2 64 } + +-- +-- The groups defined within this MIB module: +-- + +smObjects OBJECT IDENTIFIER ::= { scriptMIB 1 } +smNotifications OBJECT IDENTIFIER ::= { scriptMIB 2 } +smConformance OBJECT IDENTIFIER ::= { scriptMIB 3 } + +-- +-- Script language and language extensions. +-- +-- This group defines tables which list the languages and the +-- language extensions supported by a Script MIB implementation. +-- Languages are uniquely identified by object identifier values. +-- + +smLangTable OBJECT-TYPE + SYNTAX SEQUENCE OF SmLangEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table lists supported script languages." + ::= { smObjects 1 } + +smLangEntry OBJECT-TYPE + SYNTAX SmLangEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry describing a particular language." + INDEX { smLangIndex } + ::= { smLangTable 1 } + +SmLangEntry ::= SEQUENCE { + smLangIndex Integer32, + smLangLanguage OBJECT IDENTIFIER, + smLangVersion SnmpAdminString, + smLangVendor OBJECT IDENTIFIER, + smLangRevision SnmpAdminString, + smLangDescr SnmpAdminString +} + +smLangIndex OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The locally arbitrary, but unique identifier associated + with this language entry. + + The value is expected to remain constant at least from one + re-initialization of the entity's network management system + to the next re-initialization. + + Note that the data type and the range of this object must + be consistent with the definition of smScriptLanguage." + ::= { smLangEntry 1 } + +smLangLanguage OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The globally unique identification of the language." + ::= { smLangEntry 2 } + +smLangVersion OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The version number of the language. The zero-length string + shall be used if the language does not have a version + number. + + It is suggested that the version number consist of one or + more decimal numbers separated by dots, where the first + number is called the major version number." + ::= { smLangEntry 3 } + +smLangVendor OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An object identifier which identifies the vendor who + provides the implementation of the language. This object + identifier SHALL point to the object identifier directly + below the enterprise object identifier {1 3 6 1 4 1} + allocated for the vendor. The value must be the object + identifier {0 0} if the vendor is not known." + ::= { smLangEntry 4 } + +smLangRevision OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The version number of the language implementation. + The value of this object must be an empty string if + version number of the implementation is unknown. + + It is suggested that the value consist of one or more + decimal numbers separated by dots, where the first + number is called the major version number." + ::= { smLangEntry 5 } + +smLangDescr OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A textual description of the language." + ::= { smLangEntry 6 } + +smExtsnTable OBJECT-TYPE + SYNTAX SEQUENCE OF SmExtsnEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table lists supported language extensions." + ::= { smObjects 2 } + +smExtsnEntry OBJECT-TYPE + SYNTAX SmExtsnEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry describing a particular language extension." + INDEX { smLangIndex, smExtsnIndex } + ::= { smExtsnTable 1 } + +SmExtsnEntry ::= SEQUENCE { + smExtsnIndex Integer32, + smExtsnExtension OBJECT IDENTIFIER, + smExtsnVersion SnmpAdminString, + smExtsnVendor OBJECT IDENTIFIER, + smExtsnRevision SnmpAdminString, + smExtsnDescr SnmpAdminString +} + +smExtsnIndex OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The locally arbitrary, but unique identifier associated + with this language extension entry. + + The value is expected to remain constant at least from one + re-initialization of the entity's network management system + to the next re-initialization." + ::= { smExtsnEntry 1} + +smExtsnExtension OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The globally unique identification of the language + extension." + ::= { smExtsnEntry 2 } + +smExtsnVersion OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The version number of the language extension. + It is suggested that the version number consist of one or + more decimal numbers separated by dots, where the first + number is called the major version number." + ::= { smExtsnEntry 3 } + +smExtsnVendor OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An object identifier which identifies the vendor who + provides the implementation of the extension. The + object identifier value should point to the OID node + directly below the enterprise OID {1 3 6 1 4 1} + allocated for the vendor. The value must by the object + identifier {0 0} if the vendor is not known." + ::= { smExtsnEntry 4 } + +smExtsnRevision OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The version number of the extension implementation. + The value of this object must be an empty string if + version number of the implementation is unknown. + + It is suggested that the value consist of one or more + decimal numbers separated by dots, where the first + number is called the major version number." + ::= { smExtsnEntry 5 } + +smExtsnDescr OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A textual description of the language extension." + ::= { smExtsnEntry 6 } + +-- +-- Scripts known by the Script MIB implementation. +-- +-- This group defines a table which lists all known scripts. +-- Scripts can be added and removed through manipulation of the +-- smScriptTable. +-- + +smScriptObjects OBJECT IDENTIFIER ::= { smObjects 3 } + +smScriptTable OBJECT-TYPE + SYNTAX SEQUENCE OF SmScriptEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table lists and describes locally known scripts." + ::= { smScriptObjects 1 } + +smScriptEntry OBJECT-TYPE + SYNTAX SmScriptEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry describing a particular script. Every script that + is stored in non-volatile memory is required to appear in + this script table." + INDEX { smScriptOwner, smScriptName } + ::= { smScriptTable 1 } + +SmScriptEntry ::= SEQUENCE { + smScriptOwner SnmpAdminString, + smScriptName SnmpAdminString, + smScriptDescr SnmpAdminString, + smScriptLanguage Integer32, + smScriptSource DisplayString, + smScriptAdminStatus INTEGER, + smScriptOperStatus INTEGER, + smScriptStorageType StorageType, + smScriptRowStatus RowStatus, + smScriptError SnmpAdminString, + smScriptLastChange DateAndTime +} + +smScriptOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The manager who owns this row in the smScriptTable." + ::= { smScriptEntry 1 } + +smScriptName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (1..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The locally-unique, administratively assigned name for this + script. This object allows an smScriptOwner to have multiple + entries in the smScriptTable. + + This value of this object may be used to derive the name + (e.g. a file name) which is used by the Script MIB + implementation to access the script in non-volatile + storage. The details of this mapping are implementation + specific. However, the mapping needs to ensure that scripts + created by different owners with the same script name do not + map to the same name in non-volatile storage." + ::= { smScriptEntry 2 } + +smScriptDescr OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A description of the purpose of the script." + ::= { smScriptEntry 3 } + +smScriptLanguage OBJECT-TYPE + SYNTAX Integer32 (0..2147483647) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object type identifies an entry in the + smLangTable which is used to execute this script. + The special value 0 may be used by hard-wired scripts + that can not be modified and that are executed by + internal functions. + + Set requests to change this object are invalid if the + value of smScriptOperStatus is `enabled' or `compiling' + and will result in an inconsistentValue error. + + Note that the data type and the range of this object must + be consistent with the definition of smLangIndex." + ::= { smScriptEntry 4 } + +smScriptSource OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object either contains a reference to the script + source or an empty string. A reference must be given + in the form of a Uniform Resource Locator (URL) as + defined in RFC 2396. The allowed character sets and the + encoding rules defined in RFC 2396 section 2 apply. + + When the smScriptAdminStatus object is set to `enabled', + the Script MIB implementation will `pull' the script + source from the URL contained in this object if the URL + is not empty. + + An empty URL indicates that the script source is loaded + from local storage. The script is read from the smCodeTable + if the value of smScriptStorageType is volatile. Otherwise, + the script is read from non-volatile storage. + + Note: This document does not mandate implementation of any + specific URL scheme. An attempt to load a script from a + nonsupported URL scheme will cause the smScriptOperStatus + to report an `unknownProtocol' error. + + Set requests to change this object are invalid if the + value of smScriptOperStatus is `enabled', `editing', + `retrieving' or `compiling' and will result in an + inconsistentValue error." + DEFVAL { ''H } + ::= { smScriptEntry 5 } + +smScriptAdminStatus OBJECT-TYPE + SYNTAX INTEGER { + enabled(1), + disabled(2), + editing(3) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object indicates the desired status of + the script. See the definition of smScriptOperStatus for + a description of the values. + + When the smScriptAdminStatus object is set to `enabled' and + the smScriptOperStatus is `disabled' or one of the error + states, the Script MIB implementation will `pull' the script + source from the URL contained in the smScriptSource object + if the URL is not empty." + DEFVAL { disabled } + ::= { smScriptEntry 6 } + +smScriptOperStatus OBJECT-TYPE + SYNTAX INTEGER { + enabled(1), + disabled(2), + editing(3), + retrieving(4), + compiling(5), + noSuchScript(6), + accessDenied(7), + wrongLanguage(8), + wrongVersion(9), + compilationFailed(10), + noResourcesLeft(11), + unknownProtocol(12), + protocolFailure(13), + genericError(14) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The actual status of the script in the runtime system. The + value of this object is only meaningful when the value of + the smScriptRowStatus object is `active'. + + The smScriptOperStatus object may have the following values: + + - `enabled' indicates that the script is available and can + be started by a launch table entry. + + - `disabled' indicates that the script can not be used. + + - `editing' indicates that the script can be modified in the + smCodeTable. + + - `retrieving' indicates that the script is currently being + loaded from non-volatile storage or a remote system. + + - `compiling' indicates that the script is currently being + compiled by the runtime system. + + - `noSuchScript' indicates that the script does not exist + at the smScriptSource. + + - `accessDenied' indicates that the script can not be loaded + from the smScriptSource due to a lack of permissions. + + - `wrongLanguage' indicates that the script can not be + loaded from the smScriptSource because of a language + mismatch. + + - `wrongVersion' indicates that the script can not be loaded + from the smScriptSource because of a language version + mismatch. + + - `compilationFailed' indicates that the compilation failed. + + - `noResourcesLeft' indicates that the runtime system does + not have enough resources to load the script. + + - `unknownProtocol' indicates that the script could not be + loaded from the smScriptSource because the requested + protocol is not supported. + + - `protocolFailure' indicates that the script could not be + loaded from the smScriptSource because of a protocol + failure. + + - `genericError' indicates that the script could not be + + loaded due to an error condition not listed above. + + The `retrieving' and `compiling' states are transient states + which will either lead to one of the error states or the + `enabled' state. The `disabled' and `editing' states are + administrative states which are only reached by explicit + management operations. + + All launch table entries that refer to this script table + entry shall have an smLaunchOperStatus value of `disabled' + when the value of this object is not `enabled'." + DEFVAL { disabled } + ::= { smScriptEntry 7 } + +smScriptStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object defines whether this row and the script + controlled by this row are kept in volatile storage and + lost upon reboot or if this row is backed up by + non-volatile or permanent storage. + + The storage type of this row always complies with the value + of this entry if the value of the corresponding RowStatus + object is `active'. + + However, the storage type of the script controlled by this + row may be different, if the value of this entry is + `non-volatile'. The script controlled by this row is written + into local non-volatile storage if the following condition + becomes true: + + (a) the URL contained in the smScriptSource object is empty + and + (b) the smScriptStorageType is `nonVolatile' + and + (c) the smScriptOperStatus is `enabled' + + Setting this object to `volatile' removes a script from + non-volatile storage if the script controlled by this row + has been in non-volatile storage before. Attempts to set + this object to permanent will always fail with an + inconsistentValue error. + + The value of smScriptStorageType is only meaningful if the + value of the corresponding RowStatus object is `active'. + + If smScriptStorageType has the value permanent(4), then all + objects whose MAX-ACCESS value is read-create must be + writable, with the exception of the smScriptStorageType and + smScriptRowStatus objects, which shall be read-only." + DEFVAL { volatile } + ::= { smScriptEntry 8 } + +smScriptRowStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A control that allows entries to be added and removed from + this table. + + Changing the smScriptRowStatus from `active' to + `notInService' will remove the associated script from the + runtime system. + + Deleting conceptual rows from this table may affect the + deletion of other resources associated with this row. For + example, a script stored in non-volatile storage may be + removed from non-volatile storage. + + An entry may not exist in the `active' state unless all + required objects in the entry have appropriate values. Rows + that are not complete or not in service are not known by the + script runtime system. + + Attempts to `destroy' a row or to set a row `notInService' + while the smScriptOperStatus is `enabled' will result in an + inconsistentValue error. + + Attempts to `destroy' a row or to set a row `notInService' + where the value of the smScriptStorageType object is + `permanent' or `readOnly' will result in an + inconsistentValue error. + + The value of this object has no effect on whether other + objects in this conceptual row can be modified." + ::= { smScriptEntry 9 } + +smScriptError OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object contains a descriptive error message if the + + transition into the operational status `enabled' failed. + Implementations must reset the error message to a + zero-length string when a new attempt to change the + script status to `enabled' is started." + DEFVAL { ''H } + ::= { smScriptEntry 10 } + +smScriptLastChange OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The date and time when this script table entry was last + modified. The value '0000000000000000'H is returned if + the script table entry has not yet been modified. + + Note that the resetting of smScriptError is not considered + a change of the script table entry." + DEFVAL { '0000000000000000'H } + ::= { smScriptEntry 11 } + +-- +-- Access to script code via SNMP +-- +-- The smCodeTable allows script code to be read and modified +-- via SNMP. +-- + +smCodeTable OBJECT-TYPE + SYNTAX SEQUENCE OF SmCodeEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table contains the script code for scripts that are + written via SNMP write operations." + ::= { smScriptObjects 2 } + +smCodeEntry OBJECT-TYPE + SYNTAX SmCodeEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry describing a particular fragment of a script." + INDEX { smScriptOwner, smScriptName, smCodeIndex } + ::= { smCodeTable 1 } + +SmCodeEntry ::= SEQUENCE { + smCodeIndex Unsigned32, + smCodeText OCTET STRING, + smCodeRowStatus RowStatus +} + +smCodeIndex OBJECT-TYPE + SYNTAX Unsigned32 (1..4294967295) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The index value identifying this code fragment." + ::= { smCodeEntry 1 } + +smCodeText OBJECT-TYPE + SYNTAX OCTET STRING (SIZE (1..1024)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The code that makes up a fragment of a script. The format + of this code fragment depends on the script language which + is identified by the associated smScriptLanguage object." + ::= { smCodeEntry 2 } + +smCodeRowStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A control that allows entries to be added and removed from + this table. + + The value of this object has no effect on whether other + objects in this conceptual row can be modified." + ::= { smCodeEntry 3 } + +-- +-- Script execution. +-- +-- This group defines tables which allow script execution to be +-- initiated, suspended, resumed, and terminated. It also provides +-- a mechanism for keeping a history of recent script executions +-- and their results. +-- + +smRunObjects OBJECT IDENTIFIER ::= { smObjects 4 } + +smLaunchTable OBJECT-TYPE + SYNTAX SEQUENCE OF SmLaunchEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table lists and describes scripts that are ready + to be executed together with their parameters." + ::= { smRunObjects 1 } + +smLaunchEntry OBJECT-TYPE + SYNTAX SmLaunchEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry describing a particular executable script." + INDEX { smLaunchOwner, smLaunchName } + ::= { smLaunchTable 1 } + +SmLaunchEntry ::= SEQUENCE { + smLaunchOwner SnmpAdminString, + smLaunchName SnmpAdminString, + smLaunchScriptOwner SnmpAdminString, + smLaunchScriptName SnmpAdminString, + smLaunchArgument OCTET STRING, + smLaunchMaxRunning Unsigned32, + smLaunchMaxCompleted Unsigned32, + smLaunchLifeTime TimeInterval, + smLaunchExpireTime TimeInterval, + smLaunchStart Integer32, + smLaunchControl INTEGER, + smLaunchAdminStatus INTEGER, + smLaunchOperStatus INTEGER, + smLaunchRunIndexNext Integer32, + smLaunchStorageType StorageType, + smLaunchRowStatus RowStatus, + smLaunchError SnmpAdminString, + smLaunchLastChange DateAndTime, + smLaunchRowExpireTime TimeInterval +} + +smLaunchOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The manager who owns this row in the smLaunchTable. Every + instance of a running script started from a particular entry + in the smLaunchTable (i.e. entries in the smRunTable) will + be owned by the same smLaunchOwner used to index the entry + in the smLaunchTable. This owner is not necessarily the same + as the owner of the script itself (smLaunchScriptOwner)." + ::= { smLaunchEntry 1 } + +smLaunchName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (1..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The locally-unique, administratively assigned name for this + launch table entry. This object allows an smLaunchOwner to + have multiple entries in the smLaunchTable. The smLaunchName + is an arbitrary name that must be different from any other + smLaunchTable entries with the same smLaunchOwner but can be + the same as other entries in the smLaunchTable with + different smLaunchOwner values. Note that the value of + smLaunchName is not related in any way to the name of the + script being launched." + ::= { smLaunchEntry 2 } + +smLaunchScriptOwner OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object in combination with the value of + smLaunchScriptName identifies the script that can be + launched from this smLaunchTable entry. Attempts to write + this object will fail with an inconsistentValue error if + the value of smLaunchOperStatus is `enabled'." + ::= { smLaunchEntry 3 } + +smLaunchScriptName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE (0..32)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object in combination with the value of + the smLaunchScriptOwner identifies the script that can be + launched from this smLaunchTable entry. The zero-length + string may be used to point to a non-existing script. + + Attempts to write this object will fail with an + inconsistentValue error if the value of smLaunchOperStatus + is `enabled'." + DEFVAL { ''H } + ::= { smLaunchEntry 4 } + +smLaunchArgument OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The argument supplied to the script. When a script is + invoked, the value of this object is used to initialize + the smRunArgument object." + DEFVAL { ''H } + ::= { smLaunchEntry 5 } + +smLaunchMaxRunning OBJECT-TYPE + SYNTAX Unsigned32 (1..4294967295) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The maximum number of concurrently running scripts that may + be invoked from this entry in the smLaunchTable. Lowering + the current value of this object does not affect any scripts + that are already executing." + DEFVAL { 1 } + ::= { smLaunchEntry 6 } + +smLaunchMaxCompleted OBJECT-TYPE + SYNTAX Unsigned32 (1..4294967295) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The maximum number of finished scripts invoked from this + entry in the smLaunchTable allowed to be retained in the + smRunTable. Whenever the value of this object is changed + and whenever a script terminates, entries in the smRunTable + are deleted if necessary until the number of completed + scripts is smaller than the value of this object. Scripts + whose smRunEndTime value indicates the oldest completion + time are deleted first." + DEFVAL { 1 } + ::= { smLaunchEntry 7 } + +smLaunchLifeTime OBJECT-TYPE + SYNTAX TimeInterval + UNITS "centi-seconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The default maximum amount of time a script launched + from this entry may run. The value of this object is used + to initialize the smRunLifeTime object when a script is + launched. Changing the value of an smLaunchLifeTime + instance does not affect scripts previously launched from + + this entry." + DEFVAL { 360000 } + ::= { smLaunchEntry 8 } + +smLaunchExpireTime OBJECT-TYPE + SYNTAX TimeInterval + UNITS "centi-seconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The default maximum amount of time information about a + script launched from this entry is kept in the smRunTable + after the script has completed execution. The value of + this object is used to initialize the smRunExpireTime + object when a script is launched. Changing the value of an + smLaunchExpireTime instance does not affect scripts + previously launched from this entry." + DEFVAL { 360000 } + ::= { smLaunchEntry 9 } + +smLaunchStart OBJECT-TYPE + SYNTAX Integer32 (0..2147483647) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object is used to start the execution of scripts. + When retrieved, the value will be the value of smRunIndex + for the last script that started execution by manipulating + this object. The value will be zero if no script started + execution yet. + + A script is started by setting this object to an unused + smRunIndex value. A new row in the smRunTable will be + created which is indexed by the value supplied by the + set-request in addition to the value of smLaunchOwner and + smLaunchName. An unused value can be obtained by reading + the smLaunchRunIndexNext object. + + Setting this object to the special value 0 will start + the script with a self-generated smRunIndex value. The + consequence is that the script invoker has no reliable + way to determine the smRunIndex value for this script + invocation and that the invoker has therefore no way + to obtain the results from this script invocation. The + special value 0 is however useful for scheduled script + invocations. + + If this object is set, the following checks must be + + performed: + + 1) The value of the smLaunchOperStatus object in this + entry of the smLaunchTable must be `enabled'. + 2) The values of smLaunchScriptOwner and + smLaunchScriptName of this row must identify an + existing entry in the smScriptTable. + 3) The value of smScriptOperStatus of this entry must + be `enabled'. + 4) The principal performing the set operation must have + read access to the script. This must be checked by + calling the isAccessAllowed abstract service interface + defined in RFC 2271 on the row in the smScriptTable + identified by smLaunchScriptOwner and smLaunchScriptName. + The isAccessAllowed abstract service interface must be + called on all columnar objects in the smScriptTable with + a MAX-ACCESS value different than `not-accessible'. The + test fails as soon as a call indicates that access is + not allowed. + 5) If the value provided by the set operation is not 0, + a check must be made that the value is currently not + in use. Otherwise, if the value provided by the set + operation is 0, a suitable unused value must be + generated. + 6) The number of currently executing scripts invoked + from this smLaunchTable entry must be less than + smLaunchMaxRunning. + + Attempts to start a script will fail with an + inconsistentValue error if one of the checks described + above fails. + + Otherwise, if all checks have been passed, a new entry + in the smRunTable will be created indexed by smLaunchOwner, + smLaunchName and the new value for smRunIndex. The value + of smLaunchArgument will be copied into smRunArgument, + the value of smLaunchLifeTime will be copied to + smRunLifeTime, and the value of smLaunchExpireTime + will be copied to smRunExpireTime. + + The smRunStartTime will be set to the current time and + the smRunState will be set to `initializing' before the + script execution is initiated in the appropriate runtime + system. + + Note that the data type and the range of this object must + be consistent with the smRunIndex object. Since this + object might be written from the scheduling MIB, the + + data type Integer32 rather than Unsigned32 is used." + DEFVAL { 0 } + ::= { smLaunchEntry 10 } + +smLaunchControl OBJECT-TYPE + SYNTAX INTEGER { + abort(1), + suspend(2), + resume(3), + nop(4) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object is used to request a state change for all + running scripts in the smRunTable that were started from + this row in the smLaunchTable. + + Setting this object to abort(1), suspend(2) or resume(3) + will set the smRunControl object of all applicable rows + in the smRunTable to abort(1), suspend(2) or resume(3) + respectively. The phrase `applicable rows' means the set of + rows which were created from this entry in the smLaunchTable + and whose value of smRunState allows the corresponding + state change as described in the definition of the + smRunControl object. Setting this object to nop(4) has no + effect. + + Attempts to set this object lead to an inconsistentValue + error only if all implicated sets on all the applicable + rows lead to inconsistentValue errors. It is not allowed + to return an inconsistentValue error if at least one state + change on one of the applicable rows was successful." + DEFVAL { nop } + ::= { smLaunchEntry 11 } + +smLaunchAdminStatus OBJECT-TYPE + SYNTAX INTEGER { + enabled(1), + disabled(2), + autostart(3) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object indicates the desired status of + this launch table entry. The values enabled(1) and + autostart(3) both indicate that the launch table entry + + should transition into the operational enabled(1) state as + soon as the associated script table entry is enabled(1). + + The value autostart(3) further indicates that the script + is started automatically by conceptually writing the + value 0 into the associated smLaunchStart object during + the transition from the `disabled' into the `enabled' + operational state. This is useful for scripts that are + to be launched on system start-up." + DEFVAL { disabled } + ::= { smLaunchEntry 12 } + +smLaunchOperStatus OBJECT-TYPE + SYNTAX INTEGER { + enabled(1), + disabled(2), + expired(3) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of this object indicates the actual status of + this launch table entry. The smLaunchOperStatus object + may have the following values: + + - `enabled' indicates that the launch table entry is + available and can be used to start scripts. + + - `disabled' indicates that the launch table entry can + not be used to start scripts. + + - `expired' indicates that the launch table entry can + not be used to start scripts and will disappear as + soon as all smRunTable entries associated with this + launch table entry have disappeared. + + The value `enabled' requires that the smLaunchRowStatus + object is active. The value `disabled' requires that there + are no entries in the smRunTable associated with this + smLaunchTable entry." + DEFVAL { disabled } + ::= { smLaunchEntry 13 } + +smLaunchRunIndexNext OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This variable is used for creating rows in the smRunTable. + The value of this variable is a currently unused value + for smRunIndex, which can be written into the smLaunchStart + object associated with this row to launch a script. + + The value returned when reading this variable must be unique + for the smLaunchOwner and smLaunchName associated with this + row. Subsequent attempts to read this variable must return + different values. + + This variable will return the special value 0 if no new rows + can be created. + + Note that the data type and the range of this object must be + consistent with the definition of smRunIndex." + ::= { smLaunchEntry 14 } + +smLaunchStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object defines if this row is kept in volatile storage + and lost upon reboot or if this row is backed up by stable + storage. + + The value of smLaunchStorageType is only meaningful if the + value of the corresponding RowStatus object is active. + + If smLaunchStorageType has the value permanent(4), then all + objects whose MAX-ACCESS value is read-create must be + writable, with the exception of the smLaunchStorageType and + smLaunchRowStatus objects, which shall be read-only." + DEFVAL { volatile } + ::= { smLaunchEntry 15 } + +smLaunchRowStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A control that allows entries to be added and removed from + this table. + + Attempts to `destroy' a row or to set a row `notInService' + while the smLaunchOperStatus is `enabled' will result in + an inconsistentValue error. + + Attempts to `destroy' a row or to set a row `notInService' + where the value of the smLaunchStorageType object is + `permanent' or `readOnly' will result in an + inconsistentValue error. + + The value of this object has no effect on whether other + objects in this conceptual row can be modified." + ::= { smLaunchEntry 16 } + +smLaunchError OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object contains a descriptive error message if an + attempt to launch a script fails. Implementations must reset + the error message to a zero-length string when a new attempt + to launch a script is started." + DEFVAL { ''H } + ::= { smLaunchEntry 17 } + +smLaunchLastChange OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The date and time when this launch table entry was last + modified. The value '0000000000000000'H is returned if + the launch table entry has not yet been modified. + + Note that a change of smLaunchStart, smLaunchControl, + smLaunchRunIndexNext, smLaunchRowExpireTime, or the + resetting of smLaunchError is not considered a change + of this launch table entry." + DEFVAL { '0000000000000000'H } + ::= { smLaunchEntry 18 } + +smLaunchRowExpireTime OBJECT-TYPE + SYNTAX TimeInterval + UNITS "centi-seconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object specifies how long this row remains + in the `enabled' or `disabled' operational state. The value + reported by this object ticks backwards. When the value + reaches 0, it stops ticking backward and the row is + deleted if there are no smRunTable entries associated with + + this smLaunchTable entry. Otherwise, the smLaunchOperStatus + changes to `expired' and the row deletion is deferred + until there are no smRunTable entries associated with this + smLaunchTable entry. + + The smLaunchRowExpireTime will not tick backwards if it is + set to its maximum value (2147483647). In other words, + setting this object to its maximum value turns the timer + off. + + The value of this object may be set in order to increase + or reduce the remaining time that the launch table entry + may be used. Setting the value to 0 will cause an immediate + row deletion or transition into the `expired' operational + state. + + It is not possible to set this object while the operational + status is `expired'. Attempts to modify this object while + the operational status is `expired' leads to an + inconsistentValue error. + + Note that the timer ticks backwards independent of the + operational state of the launch table entry." + DEFVAL { 2147483647 } + ::= { smLaunchEntry 19 } + +smRunTable OBJECT-TYPE + SYNTAX SEQUENCE OF SmRunEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table lists and describes scripts that are currently + running or have been running in the past." + ::= { smRunObjects 2 } + +smRunEntry OBJECT-TYPE + SYNTAX SmRunEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry describing a particular running or finished + script." + INDEX { smLaunchOwner, smLaunchName, smRunIndex } + ::= { smRunTable 1 } + +SmRunEntry ::= SEQUENCE { + smRunIndex Integer32, + smRunArgument OCTET STRING, + smRunStartTime DateAndTime, + smRunEndTime DateAndTime, + smRunLifeTime TimeInterval, + smRunExpireTime TimeInterval, + smRunExitCode INTEGER, + smRunResult OCTET STRING, + smRunControl INTEGER, + smRunState INTEGER, + smRunError SnmpAdminString, + smRunResultTime DateAndTime, + smRunErrorTime DateAndTime +} + +smRunIndex OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The locally arbitrary, but unique identifier associated + with this running or finished script. This value must be + unique for all rows in the smRunTable with the same + smLaunchOwner and smLaunchName. + + Note that the data type and the range of this object must + be consistent with the definition of smLaunchRunIndexNext + and smLaunchStart." + ::= { smRunEntry 1 } + +smRunArgument OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The argument supplied to the script when it started." + DEFVAL { ''H } + ::= { smRunEntry 2 } + +smRunStartTime OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The date and time when the execution started. The value + '0000000000000000'H is returned if the script has not + started yet." + DEFVAL { '0000000000000000'H } + ::= { smRunEntry 3 } + +smRunEndTime OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The date and time when the execution terminated. The value + '0000000000000000'H is returned if the script has not + terminated yet." + DEFVAL { '0000000000000000'H } + ::= { smRunEntry 4 } + +smRunLifeTime OBJECT-TYPE + SYNTAX TimeInterval + UNITS "centi-seconds" + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "This object specifies how long the script can execute. + This object returns the remaining time that the script + may run. The object is initialized with the value of the + associated smLaunchLifeTime object and ticks backwards. + The script is aborted immediately when the value reaches 0. + + The value of this object may be set in order to increase or + reduce the remaining time that the script may run. Setting + this value to 0 will abort script execution immediately, + and, if the value of smRunExpireTime is also 0, will remove + this entry from the smRunTable once it has terminated. + + If smRunLifeTime is set to its maximum value (2147483647), + either by a set operation or by its initialization from the + smLaunchLifeTime object, then it will not tick backwards. + A running script with a maximum smRunLifeTime value will + thus never be terminated with a `lifeTimeExceeded' exit + code. + + The value of smRunLifeTime reflects the real-time execution + time as seen by the outside world. The value of this object + will always be 0 for a script that finished execution, that + is smRunState has the value `terminated'. + + The value of smRunLifeTime does not change while a script + is suspended, that is smRunState has the value `suspended'. + Note that this does not affect set operations. It is legal + to modify smRunLifeTime via set operations while a script + is suspended." + ::= { smRunEntry 5 } + +smRunExpireTime OBJECT-TYPE + SYNTAX TimeInterval + UNITS "centi-seconds" + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The value of this object specifies how long this row can + exist in the smRunTable after the script has terminated. + This object returns the remaining time that the row may + exist before it is aged out. The object is initialized with + the value of the associated smLaunchExpireTime object and + ticks backwards. The entry in the smRunTable is destroyed + when the value reaches 0 and the smRunState has the value + `terminated'. + + The value of this object may be set in order to increase or + reduce the remaining time that the row may exist. Setting + the value to 0 will destroy this entry as soon as the + smRunState has the value `terminated'." + ::= { smRunEntry 6 } + +smRunExitCode OBJECT-TYPE + SYNTAX INTEGER { + noError(1), + halted(2), + lifeTimeExceeded(3), + noResourcesLeft(4), + languageError(5), + runtimeError(6), + invalidArgument(7), + securityViolation(8), + genericError(9) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of this object indicates the reason why a + script finished execution. The smRunExitCode code may have + one of the following values: + + - `noError', which indicates that the script completed + successfully without errors; + + - `halted', which indicates that the script was halted + by a request from an authorized manager; + + - `lifeTimeExceeded', which indicates that the script + exited because a time limit was exceeded; + + - `noResourcesLeft', which indicates that the script + exited because it ran out of resources (e.g. memory); + + - `languageError', which indicates that the script exited + because of a language error (e.g. a syntax error in an + interpreted language); + + - `runtimeError', which indicates that the script exited + due to a runtime error (e.g. a division by zero); + + - `invalidArgument', which indicates that the script could + not be run because of invalid script arguments; + + - `securityViolation', which indicates that the script + exited due to a security violation; + + - `genericError', which indicates that the script exited + for an unspecified reason. + + If the script has not yet begun running, or is currently + running, the value will be `noError'." + DEFVAL { noError } + ::= { smRunEntry 7 } + +smRunResult OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The result value produced by the running script. Note that + the result may change while the script is executing." + DEFVAL { ''H } + ::= { smRunEntry 8 } + +smRunControl OBJECT-TYPE + SYNTAX INTEGER { + abort(1), + suspend(2), + resume(3), + nop(4) + } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The value of this object indicates the desired status of the + script execution defined by this row. + + Setting this object to `abort' will abort execution if the + + value of smRunState is `initializing', `executing', + `suspending', `suspended' or `resuming'. Setting this object + to `abort' when the value of smRunState is `aborting' or + `terminated', or if the implementation can determine that + the attempt to abort the execution would fail, will result + in an inconsistentValue error. + + Setting this object to `suspend' will suspend execution + if the value of smRunState is `executing'. Setting this + object to `suspend' will cause an inconsistentValue error + if the value of smRunState is not `executing' or if the + implementation can determine that the attempt to suspend + the execution would fail. + + Setting this object to `resume' will resume execution + if the value of smRunState is `suspending' or + `suspended'. Setting this object to `resume' will cause an + inconsistentValue error if the value of smRunState is + not `suspended' or if the implementation can determine + that the attempt to resume the execution would fail. + + Setting this object to nop(4) has no effect." + DEFVAL { nop } + ::= { smRunEntry 9 } + +smRunState OBJECT-TYPE + SYNTAX INTEGER { + initializing(1), + executing(2), + suspending(3), + suspended(4), + resuming(5), + aborting(6), + terminated(7) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of this object indicates the script's execution + state. If the script has been invoked but has not yet + begun execution, the value will be `initializing'. If the + script is running, the value will be `executing'. + + A running script which received a request to suspend + execution first transitions into a temporary `suspending' + state. The temporary `suspending' state changes to + `suspended' when the script has actually been suspended. The + temporary `suspending' state changes back to `executing' if + + the attempt to suspend the running script fails. + + A suspended script which received a request to resume + execution first transitions into a temporary `resuming' + state. The temporary `resuming' state changes to `running' + when the script has actually been resumed. The temporary + `resuming' state changes back to `suspended' if the attempt + to resume the suspended script fails. + + A script which received a request to abort execution but + which is still running first transitions into a temporary + `aborting' state. + + A script which has finished its execution is `terminated'." + ::= { smRunEntry 10 } + +smRunError OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object contains a descriptive error message if the + script startup or execution raised an abnormal condition. + An implementation must store a descriptive error message + in this object if the script exits with the smRunExitCode + `genericError'." + DEFVAL { ''H } + ::= { smRunEntry 11 } + +smRunResultTime OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The date and time when the smRunResult was last updated. + The value '0000000000000000'H is returned if smRunResult + has not yet been updated after the creation of this + smRunTable entry." + DEFVAL { '0000000000000000'H } + ::= { smRunEntry 12 } + +smRunErrorTime OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The date and time when the smRunError was last updated. + The value '0000000000000000'H is returned if smRunError + + has not yet been updated after the creation of this + smRunTable entry." + DEFVAL { '0000000000000000'H } + ::= { smRunEntry 13 } + +-- +-- Notifications. The definition of smTraps makes notification +-- registrations reversible (see STD 58, RFC 2578). +-- + +smTraps OBJECT IDENTIFIER ::= { smNotifications 0 } + +smScriptAbort NOTIFICATION-TYPE + OBJECTS { smRunExitCode, smRunEndTime, smRunError } + STATUS current + DESCRIPTION + "This notification is generated whenever a running script + terminates with an smRunExitCode unequal to `noError'." + ::= { smTraps 1 } + +smScriptResult NOTIFICATION-TYPE + OBJECTS { smRunResult } + STATUS current + DESCRIPTION + "This notification can be used by scripts to notify other + management applications about results produced by the + script. + + This notification is not automatically generated by the + Script MIB implementation. It is the responsibility of + the executing script to emit this notification where it + is appropriate to do so." + ::= { smTraps 2 } + +smScriptException NOTIFICATION-TYPE + OBJECTS { smRunError } + STATUS current + DESCRIPTION + "This notification can be used by scripts to notify other + management applications about script errors. + + This notification is not automatically generated by the + Script MIB implementation. It is the responsibility of + the executing script or the runtime system to emit this + notification where it is appropriate to do so." + ::= { smTraps 3 } + +-- conformance information + +smCompliances OBJECT IDENTIFIER ::= { smConformance 1 } +smGroups OBJECT IDENTIFIER ::= { smConformance 2 } + +-- compliance statements + +smCompliance2 MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP entities which implement + the Script MIB." + MODULE -- this module + MANDATORY-GROUPS { + smLanguageGroup, smScriptGroup2, smLaunchGroup2, + smRunGroup2, smNotificationsGroup2 + } + GROUP smCodeGroup + DESCRIPTION + "The smCodeGroup is mandatory only for those implementations + that support the downloading of scripts via SNMP." + OBJECT smScriptSource + MIN-ACCESS read-only + DESCRIPTION + "The smScriptSource object is read-only for implementations + that are not able to download script code from a URL." + OBJECT smCodeText + DESCRIPTION + "A compliant implementation need only support write access to + the smCodeText object only during row creation." + OBJECT smLaunchArgument + DESCRIPTION + "A compliant implementation has to support a minimum size + for smLaunchArgument of 255 octets." + OBJECT smRunArgument + DESCRIPTION + "A compliant implementation has to support a minimum size + for smRunArgument of 255 octets." + OBJECT smRunResult + DESCRIPTION + "A compliant implementation has to support a minimum size + for smRunResult of 255 octets." + OBJECT smRunState + DESCRIPTION + "A compliant implementation does not have to support script + suspension and the smRunState `suspended'. Such an + implementation will change into the `suspending' state + when the smRunControl is set to `suspend' and remain in this + state until smRunControl is set to `resume' or the script + terminates." + ::= { smCompliances 2 } + +smLanguageGroup OBJECT-GROUP + OBJECTS { + smLangLanguage, smLangVersion, + smLangVendor, smLangRevision, + smLangDescr, smExtsnExtension, + smExtsnVersion, smExtsnVendor, + smExtsnRevision, smExtsnDescr + } + STATUS current + DESCRIPTION + "A collection of objects providing information about the + capabilities of the scripting engine." + ::= { smGroups 1 } + +smScriptGroup2 OBJECT-GROUP + OBJECTS { + smScriptDescr, smScriptLanguage, + smScriptSource, smScriptAdminStatus, + smScriptOperStatus, smScriptStorageType, + smScriptRowStatus, smScriptError, + smScriptLastChange + } + STATUS current + DESCRIPTION + "A collection of objects providing information about + installed scripts." + ::= { smGroups 7 } + +smCodeGroup OBJECT-GROUP + OBJECTS { + smCodeText, smCodeRowStatus + } + STATUS current + DESCRIPTION + "A collection of objects used to download or modify scripts + by using SNMP set requests." + ::= { smGroups 3 } + +smLaunchGroup2 OBJECT-GROUP + OBJECTS { + smLaunchScriptOwner, smLaunchScriptName, + smLaunchArgument, smLaunchMaxRunning, + smLaunchMaxCompleted, smLaunchLifeTime, + smLaunchExpireTime, smLaunchStart, + smLaunchControl, smLaunchAdminStatus, + smLaunchOperStatus, smLaunchRunIndexNext, + smLaunchStorageType, smLaunchRowStatus, + smLaunchError, smLaunchLastChange, + smLaunchRowExpireTime + } + STATUS current + DESCRIPTION + "A collection of objects providing information about scripts + that can be launched." + ::= { smGroups 8 } + +smRunGroup2 OBJECT-GROUP + OBJECTS { + smRunArgument, smRunStartTime, + smRunEndTime, smRunLifeTime, + smRunExpireTime, smRunExitCode, + smRunResult, smRunState, + smRunControl, smRunError, + smRunResultTime, smRunErrorTime + } + STATUS current + DESCRIPTION + "A collection of objects providing information about running + scripts." + ::= { smGroups 9 } + +smNotificationsGroup2 NOTIFICATION-GROUP + NOTIFICATIONS { + smScriptAbort, + smScriptResult, + smScriptException + } + STATUS current + DESCRIPTION + "The notifications emitted by the Script MIB." + ::= { smGroups 10 } + +-- +-- Deprecated compliance and conformance group definitions +-- from RFC 2592. +-- + +smCompliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION + "The compliance statement for SNMP entities which implement + the Script MIB." + MODULE -- this module + MANDATORY-GROUPS { + + smLanguageGroup, smScriptGroup, smLaunchGroup, smRunGroup + } + GROUP smCodeGroup + DESCRIPTION + "The smCodeGroup is mandatory only for those implementations + that support the downloading of scripts via SNMP." + OBJECT smScriptSource + MIN-ACCESS read-only + DESCRIPTION + "The smScriptSource object is read-only for implementations + that are not able to download script code from a URL." + OBJECT smCodeText + DESCRIPTION + "A compliant implementation need only support write access + to the smCodeText object during row creation." + OBJECT smLaunchArgument + DESCRIPTION + "A compliant implementation has to support a minimum size + for smLaunchArgument of 255 octets." + OBJECT smRunArgument + DESCRIPTION + "A compliant implementation has to support a minimum size + for smRunArgument of 255 octets." + OBJECT smRunResult + DESCRIPTION + "A compliant implementation has to support a minimum size + for smRunResult of 255 octets." + OBJECT smRunState + DESCRIPTION + "A compliant implementation does not have to support script + suspension and the smRunState `suspended'. Such an + implementation will change into the `suspending' state + when the smRunControl is set to `suspend' and remain in this + state until smRunControl is set to `resume' or the script + terminates." + ::= { smCompliances 1 } + +smScriptGroup OBJECT-GROUP + OBJECTS { + smScriptDescr, smScriptLanguage, + smScriptSource, smScriptAdminStatus, + smScriptOperStatus, smScriptStorageType, + smScriptRowStatus + } + STATUS deprecated + DESCRIPTION + "A collection of objects providing information about + installed scripts." + ::= { smGroups 2 } + +smLaunchGroup OBJECT-GROUP + OBJECTS { + smLaunchScriptOwner, smLaunchScriptName, + smLaunchArgument, smLaunchMaxRunning, + smLaunchMaxCompleted, smLaunchLifeTime, + smLaunchExpireTime, smLaunchStart, + smLaunchControl, smLaunchAdminStatus, + smLaunchOperStatus, smLaunchRunIndexNext, + smLaunchStorageType, smLaunchRowStatus + } + STATUS deprecated + DESCRIPTION + "A collection of objects providing information about scripts + that can be launched." + ::= { smGroups 4 } + +smRunGroup OBJECT-GROUP + OBJECTS { + smRunArgument, smRunStartTime, + smRunEndTime, smRunLifeTime, + smRunExpireTime, smRunExitCode, + smRunResult, smRunState, + smRunControl, smRunError + } + STATUS deprecated + DESCRIPTION + "A collection of objects providing information about running + scripts." + ::= { smGroups 5 } + +smNotificationsGroup NOTIFICATION-GROUP + NOTIFICATIONS { + smScriptAbort, + smScriptResult + } + STATUS deprecated + DESCRIPTION + "The notifications emitted by the Script MIB." + ::= { smGroups 6 } + +END diff --git a/mibs/DISMAN-TRACEROUTE-MIB.txt b/mibs/DISMAN-TRACEROUTE-MIB.txt new file mode 100644 index 0000000..d207b24 --- /dev/null +++ b/mibs/DISMAN-TRACEROUTE-MIB.txt @@ -0,0 +1,1850 @@ +DISMAN-TRACEROUTE-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, Integer32, + Gauge32, Unsigned32, mib-2, + NOTIFICATION-TYPE, + OBJECT-IDENTITY + FROM SNMPv2-SMI -- RFC2578 + RowStatus, StorageType, + TruthValue, DateAndTime + FROM SNMPv2-TC -- RFC2579 + MODULE-COMPLIANCE, OBJECT-GROUP, + NOTIFICATION-GROUP + FROM SNMPv2-CONF -- RFC2580 + SnmpAdminString + FROM SNMP-FRAMEWORK-MIB -- RFC3411 + InterfaceIndexOrZero -- RFC2863 + FROM IF-MIB + InetAddressType, InetAddress + FROM INET-ADDRESS-MIB -- RFC4001 + OperationResponseStatus + FROM DISMAN-PING-MIB; -- RFC4560 + + traceRouteMIB MODULE-IDENTITY + LAST-UPDATED "200606130000Z" -- 13 June 2006 + ORGANIZATION "IETF Distributed Management Working Group" + CONTACT-INFO + "Juergen Quittek + + NEC Europe Ltd. + Network Laboratories + Kurfuersten-Anlage 36 + 69115 Heidelberg + Germany + + Phone: +49 6221 4342-115 + Email: quittek@netlab.nec.de" + DESCRIPTION + "The Traceroute MIB (DISMAN-TRACEROUTE-MIB) provides + access to the traceroute capability at a remote host. + + Copyright (C) The Internet Society (2006). This version of + this MIB module is part of RFC 4560; see the RFC itself for + full legal notices." + + -- Revision history + + REVISION "200606130000Z" -- 13 June 2006 + DESCRIPTION + "Updated version, published as RFC 4560. + - Correctly considered IPv6 in DESCRIPTION clause of + object traceRouteCtlDataSize + - Replaced references to RFC 2575 by RFC 3415 + - Replaced references to RFC 2571 by RFC 3411 + - Replaced references to RFC 2851 by RFC 4001 + - Clarified DESCRIPTION clause of object + traceRouteResultsLastGoodPath + - Changed range of object traceRouteCtlInitialTtl + from (0..255) to (1..255) + - Extended DESCRIPTION clause of traceRouteResultsTable + describing re-initialization of entries + - Changed SYNTAX of traceRouteResultsTestAttempts and + traceRouteResultsTestSuccesses from Unsigned32 to + Gauge32 + - Changed status of traceRouteCompliance to deprecated + - Added traceRouteFullCompliance and + traceRouteMinimumCompliance + - Changed status of traceRouteGroup and + traceRouteTimeStampGroup to deprecated + - Added traceRouteMinimumGroup, + traceRouteCtlRowStatusGroup, and + traceRouteHistoryGroup + - Changed DEFVAL of object + traceRouteCtlTargetAddressType from { ipv4 } + to { unknown } + - Changed DEFVAL of object traceRouteCtlDescr + from { '00'H } to { ''H } + - Added DEFVAL for object traceRouteCtlTrapGeneration + of DEFVAL { { } }" + REVISION "200009210000Z" -- 21 September 2000 + DESCRIPTION + "Initial version, published as RFC 2925." + ::= { mib-2 81 } + + -- Top level structure of the MIB + + traceRouteNotifications OBJECT IDENTIFIER ::= { traceRouteMIB 0 } + traceRouteObjects OBJECT IDENTIFIER ::= { traceRouteMIB 1 } + traceRouteConformance OBJECT IDENTIFIER ::= { traceRouteMIB 2 } + + -- The registration node (point) for traceroute implementation types + + traceRouteImplementationTypeDomains OBJECT IDENTIFIER + ::= { traceRouteMIB 3 } + + traceRouteUsingUdpProbes OBJECT-IDENTITY + STATUS current + DESCRIPTION + "Indicates that an implementation is using UDP probes to + perform the traceroute operation." + ::= { traceRouteImplementationTypeDomains 1 } + + -- Simple Object Definitions + + traceRouteMaxConcurrentRequests OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "requests" + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The maximum number of concurrent active traceroute requests + that are allowed within an agent implementation. A value + of 0 for this object implies that there is no limit for + the number of concurrent active requests in effect. + + The limit applies only to new requests being activated. + When a new value is set, the agent will continue processing + all the requests already active, even if their number + exceeds the limit just imposed." + DEFVAL { 10 } + ::= { traceRouteObjects 1 } + + -- Traceroute Control Table + + traceRouteCtlTable OBJECT-TYPE + SYNTAX SEQUENCE OF TraceRouteCtlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines the Remote Operations Traceroute Control Table for + providing the capability of invoking traceroute from a remote + host. The results of traceroute operations can be stored in + the traceRouteResultsTable, traceRouteProbeHistoryTable, and + the traceRouteHopsTable." + ::= { traceRouteObjects 2 } + + traceRouteCtlEntry OBJECT-TYPE + SYNTAX TraceRouteCtlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines an entry in the traceRouteCtlTable. The first + index element, traceRouteCtlOwnerIndex, is of type + SnmpAdminString, a textual convention that allows for + use of the SNMPv3 View-Based Access Control Model + (RFC 3415, VACM) and that allows a management + application to identify its entries. The second index, + traceRouteCtlTestName (also an SnmpAdminString), + enables the same management application to have + multiple requests outstanding." + INDEX { + traceRouteCtlOwnerIndex, + traceRouteCtlTestName + } + ::= { traceRouteCtlTable 1 } + + TraceRouteCtlEntry ::= + SEQUENCE { + traceRouteCtlOwnerIndex SnmpAdminString, + traceRouteCtlTestName SnmpAdminString, + traceRouteCtlTargetAddressType InetAddressType, + traceRouteCtlTargetAddress InetAddress, + traceRouteCtlByPassRouteTable TruthValue, + traceRouteCtlDataSize Unsigned32, + traceRouteCtlTimeOut Unsigned32, + traceRouteCtlProbesPerHop Unsigned32, + traceRouteCtlPort Unsigned32, + traceRouteCtlMaxTtl Unsigned32, + traceRouteCtlDSField Unsigned32, + traceRouteCtlSourceAddressType InetAddressType, + traceRouteCtlSourceAddress InetAddress, + traceRouteCtlIfIndex InterfaceIndexOrZero, + traceRouteCtlMiscOptions SnmpAdminString, + traceRouteCtlMaxFailures Unsigned32, + traceRouteCtlDontFragment TruthValue, + traceRouteCtlInitialTtl Unsigned32, + traceRouteCtlFrequency Unsigned32, + traceRouteCtlStorageType StorageType, + traceRouteCtlAdminStatus INTEGER, + traceRouteCtlDescr SnmpAdminString, + traceRouteCtlMaxRows Unsigned32, + traceRouteCtlTrapGeneration BITS, + traceRouteCtlCreateHopsEntries TruthValue, + traceRouteCtlType OBJECT IDENTIFIER, + traceRouteCtlRowStatus RowStatus + } + + traceRouteCtlOwnerIndex OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "To facilitate the provisioning of access control by a + security administrator using the View-Based Access + Control Model (RFC 3415, VACM) for tables in which + multiple users may need to create or + modify entries independently, the initial index is used as + an 'owner index'. Such an initial index has a syntax of + SnmpAdminString and can thus be trivially mapped to a + securityName or groupName defined in VACM, in + accordance with a security policy. + + When used in conjunction with such a security policy, + all entries in the table belonging to a particular user + (or group) will have the same value for this initial + index. For a given user's entries in a particular + table, the object identifiers for the information in + these entries will have the same subidentifiers (except + for the 'column' subidentifier) up to the end of the + encoded owner index. To configure VACM to permit access + to this portion of the table, one would create + vacmViewTreeFamilyTable entries with the value of + vacmViewTreeFamilySubtree including the owner index + portion, and vacmViewTreeFamilyMask 'wildcarding' the + column subidentifier. More elaborate configurations + are possible." + ::= { traceRouteCtlEntry 1 } + + traceRouteCtlTestName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The name of a traceroute test. This is locally unique, + within the scope of a traceRouteCtlOwnerIndex." + ::= { traceRouteCtlEntry 2 } + + traceRouteCtlTargetAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the type of host address to be used on the + traceroute request at the remote host." + DEFVAL { unknown } + ::= { traceRouteCtlEntry 3 } + + traceRouteCtlTargetAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the host address used on the + traceroute request at the remote host. The + host address type can be determined by + examining the value of the corresponding + traceRouteCtlTargetAddressType. + + A value for this object MUST be set prior to + transitioning its corresponding traceRouteCtlEntry to + active(1) via traceRouteCtlRowStatus." + ::= { traceRouteCtlEntry 4 } + + traceRouteCtlByPassRouteTable OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The purpose of this object is to enable optional + bypassing the route table. If enabled, the remote + host will bypass the normal routing tables and send + directly to a host on an attached network. If the + host is not on a directly attached network, an + error is returned. This option can be used to perform + the traceroute operation to a local host through an + interface that has no route defined (e.g., after the + interface was dropped by the routing daemon at the host)." + DEFVAL { false } + ::= { traceRouteCtlEntry 5 } + + traceRouteCtlDataSize OBJECT-TYPE + SYNTAX Unsigned32 (0..65507) + UNITS "octets" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the size of the data portion of a traceroute + request, in octets. If the RECOMMENDED traceroute method + (UDP datagrams as probes) is used, then the value + contained in this object MUST be applied. If another + traceroute method is used for which the specified size + is not appropriate, then the implementation SHOULD use + whatever size (appropriate to the method) is closest to + the specified size. + + The maximum value for this object was computed by + subtracting the smallest possible IP header size of + 20 octets (IPv4 header with no options) and the UDP + header size of 8 octets from the maximum IP packet size. + An IP packet has a maximum size of 65535 octets + (excluding IPv6 Jumbograms)." + DEFVAL { 0 } + ::= { traceRouteCtlEntry 6 } + + traceRouteCtlTimeOut OBJECT-TYPE + SYNTAX Unsigned32 (1..60) + UNITS "seconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the time-out value, in seconds, for + a traceroute request." + DEFVAL { 3 } + ::= { traceRouteCtlEntry 7 } + + traceRouteCtlProbesPerHop OBJECT-TYPE + SYNTAX Unsigned32 (1..10) + UNITS "probes" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the number of times to reissue a traceroute + request with the same time-to-live (TTL) value." + DEFVAL { 3 } + ::= { traceRouteCtlEntry 8 } + + traceRouteCtlPort OBJECT-TYPE + SYNTAX Unsigned32 (1..65535) + UNITS "UDP Port" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the (initial) UDP port to send the traceroute + request to. A port needs to be specified that is not in + use at the destination (target) host. The default + value for this object is the IANA assigned port, + 33434, for the traceroute function." + DEFVAL { 33434 } + ::= { traceRouteCtlEntry 9 } + + traceRouteCtlMaxTtl OBJECT-TYPE + SYNTAX Unsigned32 (1..255) + UNITS "time-to-live value" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the maximum time-to-live value." + DEFVAL { 30 } + ::= { traceRouteCtlEntry 10 } + + traceRouteCtlDSField OBJECT-TYPE + SYNTAX Unsigned32 (0..255) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the value to store in the Type of Service + (TOS) octet in the IPv4 header or in the Traffic + Class octet in the IPv6 header, respectively, of the + IP packet used to encapsulate the traceroute probe. + + The octet to be set in the IP header contains the + Differentiated Services (DS) Field in the six most + significant bits. + + This option can be used to determine what effect an + explicit DS Field setting has on a traceroute response. + Not all values are legal or meaningful. A value of 0 + means that the function represented by this option is + not supported. DS Field usage is often not supported + by IP implementations, and not all values are supported. + Refer to RFC 2474 and RFC 3260 for guidance on usage of + this field." + REFERENCE + "Refer to RFC 1812 for the definition of the IPv4 TOS + octet and to RFC 2460 for the definition of the IPv6 + Traffic Class octet. Refer to RFC 2474 and RFC 3260 + for the definition of the Differentiated Services Field." + DEFVAL { 0 } + ::= { traceRouteCtlEntry 11 } + + traceRouteCtlSourceAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Specifies the type of the source address, + traceRouteCtlSourceAddress, to be used at a remote host + when a traceroute operation is performed." + DEFVAL { unknown } + ::= { traceRouteCtlEntry 12 } + + traceRouteCtlSourceAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Use the specified IP address (which must be given as an + IP number, not a hostname) as the source address in + outgoing probe packets. On hosts with more than one IP + address, this option can be used to select the address + to be used. If the IP address is not one of this + machine's interface addresses, an error is returned, and + nothing is sent. A zero-length octet string value for + this object disables source address specification. + The address type (InetAddressType) that relates to + this object is specified by the corresponding value + of traceRouteCtlSourceAddressType." + DEFVAL { ''H } + ::= { traceRouteCtlEntry 13 } + + traceRouteCtlIfIndex OBJECT-TYPE + SYNTAX InterfaceIndexOrZero + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Setting this object to an interface's ifIndex prior + to starting a remote traceroute operation directs + the traceroute probes to be transmitted over the + specified interface. A value of zero for this object + implies that this option is not enabled." + DEFVAL { 0 } + ::= { traceRouteCtlEntry 14 } + + traceRouteCtlMiscOptions OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Enables an application to specify implementation-dependent + options." + DEFVAL { ''H } + ::= { traceRouteCtlEntry 15 } + + traceRouteCtlMaxFailures OBJECT-TYPE + SYNTAX Unsigned32 (0..255) + UNITS "timeouts" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object indicates the maximum number + of consecutive timeouts allowed before a remote traceroute + request is terminated. A value of either 255 (maximum + hop count/possible TTL value) or 0 indicates that the + function of terminating a remote traceroute request when a + specific number of consecutive timeouts are detected is + disabled." + DEFVAL { 5 } + ::= { traceRouteCtlEntry 16 } + + traceRouteCtlDontFragment OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object enables setting of the don't fragment flag (DF) + in the IP header for a probe. Use of this object enables + a manual PATH MTU test is performed." + DEFVAL { false } + ::= { traceRouteCtlEntry 17 } + + traceRouteCtlInitialTtl OBJECT-TYPE + SYNTAX Unsigned32 (1..255) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object specifies the initial TTL value to + use. This enables bypassing the initial (often well known) + portion of a path." + DEFVAL { 1 } + ::= { traceRouteCtlEntry 18 } + + traceRouteCtlFrequency OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "seconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The number of seconds to wait before repeating a + traceroute test, as defined by the value of the + various objects in the corresponding row. + + After a single test is completed the number of seconds + as defined by the value of traceRouteCtlFrequency MUST + elapse before the next traceroute test is started. + + A value of 0 for this object implies that the test + as defined by the corresponding entry will not be + + repeated." + DEFVAL { 0 } + ::= { traceRouteCtlEntry 19 } + + traceRouteCtlStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The storage type for this conceptual row. + Conceptual rows having the value 'permanent' need not + allow write-access to any columnar objects in the row." + DEFVAL { nonVolatile } + ::= { traceRouteCtlEntry 20 } + + traceRouteCtlAdminStatus OBJECT-TYPE + SYNTAX INTEGER { + + enabled(1), -- operation should be started + disabled(2) -- operation should be stopped + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Reflects the desired state that an traceRouteCtlEntry + should be in: + + enabled(1) - Attempt to activate the test as defined by + this traceRouteCtlEntry. + disabled(2) - Deactivate the test as defined by this + traceRouteCtlEntry. + + Refer to the corresponding traceRouteResultsOperStatus to + determine the operational state of the test defined by + this entry." + DEFVAL { disabled } + ::= { traceRouteCtlEntry 21 } + + traceRouteCtlDescr OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The purpose of this object is to provide a + descriptive name of the remote traceroute + test." + DEFVAL { ''H } + ::= { traceRouteCtlEntry 22 } + + traceRouteCtlMaxRows OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "rows" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The maximum number of corresponding entries allowed + in the traceRouteProbeHistoryTable. An implementation + of this MIB will remove the oldest corresponding entry + in the traceRouteProbeHistoryTable to allow the + addition of an new entry once the number of + corresponding rows in the traceRouteProbeHistoryTable + reaches this value. + + Old entries are not removed when a new test is + started. Entries are added to the + traceRouteProbeHistoryTable until traceRouteCtlMaxRows + is reached before entries begin to be removed. + A value of 0 for this object disables creation of + traceRouteProbeHistoryTable entries." + DEFVAL { 50 } + ::= { traceRouteCtlEntry 23 } + + traceRouteCtlTrapGeneration OBJECT-TYPE + SYNTAX BITS { + pathChange(0), + testFailure(1), + testCompletion(2) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object determines when and whether to + generate a notification for this entry: + + pathChange(0) - Generate a traceRoutePathChange + notification when the current path varies from a + previously determined path. + testFailure(1) - Generate a traceRouteTestFailed + notification when the full path to a target + can't be determined. + testCompletion(2) - Generate a traceRouteTestCompleted + notification when the path to a target has been + determined. + + The value of this object defaults to an empty set, + indicating that none of the above options has been + selected." + DEFVAL { { } } + ::= { traceRouteCtlEntry 24 } + + traceRouteCtlCreateHopsEntries OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The current path for a traceroute test is kept in the + traceRouteHopsTable on a per-hop basis when the value of + this object is true(1)." + DEFVAL { false } + ::= { traceRouteCtlEntry 25 } + + traceRouteCtlType OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object is used either to report or to + select the implementation method to be used for + performing a traceroute operation. The value of this + object may be selected from + traceRouteImplementationTypeDomains. + + Additional implementation types should be allocated as + required by implementers of the DISMAN-TRACEROUTE-MIB + under their enterprise specific registration point, + not beneath traceRouteImplementationTypeDomains." + DEFVAL { traceRouteUsingUdpProbes } + ::= { traceRouteCtlEntry 26 } + + traceRouteCtlRowStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object allows entries to be created and deleted + in the traceRouteCtlTable. Deletion of an entry in + this table results in a deletion of all corresponding (same + traceRouteCtlOwnerIndex and traceRouteCtlTestName + index values) traceRouteResultsTable, + traceRouteProbeHistoryTable, and traceRouteHopsTable + entries. + + A value MUST be specified for traceRouteCtlTargetAddress + prior to acceptance of a transition to active(1) state. + + When a value for pingCtlTargetAddress is set, + the value of object pingCtlRowStatus changes + from notReady(3) to notInService(2). + + Activation of a remote traceroute operation is + controlled via traceRouteCtlAdminStatus, and not + by transitioning of this object's value to active(1). + + Transitions in and out of active(1) state are not + allowed while an entry's traceRouteResultsOperStatus + is active(1), with the exception that deletion of + an entry in this table by setting its RowStatus + object to destroy(6) will stop an active + traceroute operation. + + The operational state of an traceroute operation + can be determined by examination of the corresponding + traceRouteResultsOperStatus object." + REFERENCE + "See definition of RowStatus in RFC 2579, 'Textual + Conventions for SMIv2.'" + ::= { traceRouteCtlEntry 27 } + + -- Traceroute Results Table + + traceRouteResultsTable OBJECT-TYPE + SYNTAX SEQUENCE OF TraceRouteResultsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines the Remote Operations Traceroute Results Table for + keeping track of the status of a traceRouteCtlEntry. + + An entry is added to the traceRouteResultsTable when an + traceRouteCtlEntry is started by successful transition + of its traceRouteCtlAdminStatus object to enabled(1). + + If the object traceRouteCtlAdminStatus already has the value + enabled(1), and if the corresponding + traceRouteResultsOperStatus object has the value + completed(3), then successfully writing enabled(1) to the + object traceRouteCtlAdminStatus re-initializes the already + existing entry in the traceRouteResultsTable. The values of + objects in the re-initialized entry are the same as + the values of objects in a new entry would be. + + An entry is removed from the traceRouteResultsTable when + + its corresponding traceRouteCtlEntry is deleted." + ::= { traceRouteObjects 3 } + + traceRouteResultsEntry OBJECT-TYPE + SYNTAX TraceRouteResultsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines an entry in the traceRouteResultsTable. The + traceRouteResultsTable has the same indexing as the + traceRouteCtlTable so that a traceRouteResultsEntry + corresponds to the traceRouteCtlEntry that caused it to + be created." + INDEX { + traceRouteCtlOwnerIndex, + traceRouteCtlTestName + } + ::= { traceRouteResultsTable 1 } + + TraceRouteResultsEntry ::= + SEQUENCE { + traceRouteResultsOperStatus INTEGER, + traceRouteResultsCurHopCount Gauge32, + traceRouteResultsCurProbeCount Gauge32, + traceRouteResultsIpTgtAddrType InetAddressType, + traceRouteResultsIpTgtAddr InetAddress, + traceRouteResultsTestAttempts Gauge32, + traceRouteResultsTestSuccesses Gauge32, + traceRouteResultsLastGoodPath DateAndTime + } + + traceRouteResultsOperStatus OBJECT-TYPE + SYNTAX INTEGER { + enabled(1), -- test is in progress + disabled(2), -- test has stopped + completed(3) -- test is completed + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Reflects the operational state of an traceRouteCtlEntry: + + enabled(1) - Test is active. + disabled(2) - Test has stopped. + completed(3) - Test is completed." + ::= { traceRouteResultsEntry 1 } + + traceRouteResultsCurHopCount OBJECT-TYPE + SYNTAX Gauge32 + UNITS "hops" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Reflects the current TTL value (from 1 to + 255) for a remote traceroute operation. + Maximum TTL value is determined by + traceRouteCtlMaxTtl." + ::= { traceRouteResultsEntry 2 } + + traceRouteResultsCurProbeCount OBJECT-TYPE + SYNTAX Gauge32 + UNITS "probes" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Reflects the current probe count (1..10) for + a remote traceroute operation. The maximum + probe count is determined by + traceRouteCtlProbesPerHop." + ::= { traceRouteResultsEntry 3 } + + traceRouteResultsIpTgtAddrType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object indicates the type of address stored + in the corresponding traceRouteResultsIpTgtAddr + object." + ::= { traceRouteResultsEntry 4 } + + traceRouteResultsIpTgtAddr OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object reports the IP address associated + with a traceRouteCtlTargetAddress value when the + destination address is specified as a DNS name. + The value of this object should be a zero-length + octet string when a DNS name is not specified or + when a specified DNS name fails to resolve." + ::= { traceRouteResultsEntry 5 } + + traceRouteResultsTestAttempts OBJECT-TYPE + SYNTAX Gauge32 + UNITS "tests" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current number of attempts to determine a path + to a target. The value of this object MUST be started + at 0." + ::= { traceRouteResultsEntry 6 } + + traceRouteResultsTestSuccesses OBJECT-TYPE + SYNTAX Gauge32 + UNITS "tests" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current number of attempts to determine a path + to a target that have succeeded. The value of this + object MUST be reported as 0 when no attempts have + succeeded." + ::= { traceRouteResultsEntry 7 } + + traceRouteResultsLastGoodPath OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The date and time when the last complete path + was determined. A path is complete if responses + were received or timeout occurred for each hop on + the path; i.e., for each TTL value from the value + of the corresponding traceRouteCtlInitialTtl object + up to the end of the path or (if no reply from the + target IP address was received) up to the value of + the corresponding traceRouteCtlMaxTtl object." + ::= { traceRouteResultsEntry 8 } + + -- Trace Route Probe History Table + + traceRouteProbeHistoryTable OBJECT-TYPE + SYNTAX SEQUENCE OF TraceRouteProbeHistoryEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines the Remote Operations Traceroute Results Table + for storing the results of a traceroute operation. + + An implementation of this MIB will remove the oldest + + entry in the traceRouteProbeHistoryTable of the + corresponding entry in the traceRouteCtlTable to allow + the addition of a new entry once the number of rows in + the traceRouteProbeHistoryTable reaches the value specified + by traceRouteCtlMaxRows for the corresponding entry in the + traceRouteCtlTable." + ::= { traceRouteObjects 4 } + + traceRouteProbeHistoryEntry OBJECT-TYPE + SYNTAX TraceRouteProbeHistoryEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines a table for storing the results of a traceroute + operation. Entries in this table are limited by + the value of the corresponding traceRouteCtlMaxRows + object. + + The first two index elements identify the + traceRouteCtlEntry that a traceRouteProbeHistoryEntry + belongs to. The third index element selects a single + traceroute operation result. The fourth and fifth indexes + select the hop and the probe for a particular + traceroute operation." + INDEX { + traceRouteCtlOwnerIndex, + traceRouteCtlTestName, + traceRouteProbeHistoryIndex, + traceRouteProbeHistoryHopIndex, + traceRouteProbeHistoryProbeIndex + + } + ::= { traceRouteProbeHistoryTable 1 } + + TraceRouteProbeHistoryEntry ::= + SEQUENCE { + traceRouteProbeHistoryIndex Unsigned32, + traceRouteProbeHistoryHopIndex Unsigned32, + traceRouteProbeHistoryProbeIndex Unsigned32, + traceRouteProbeHistoryHAddrType InetAddressType, + traceRouteProbeHistoryHAddr InetAddress, + traceRouteProbeHistoryResponse Unsigned32, + traceRouteProbeHistoryStatus OperationResponseStatus, + traceRouteProbeHistoryLastRC Integer32, + traceRouteProbeHistoryTime DateAndTime + } + + traceRouteProbeHistoryIndex OBJECT-TYPE + SYNTAX Unsigned32 (1..'ffffffff'h) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry in this table is created when the result of + a traceroute probe is determined. The initial 2 instance + identifier index values identify the traceRouteCtlEntry + that a probe result (traceRouteProbeHistoryEntry) belongs + to. An entry is removed from this table when + its corresponding traceRouteCtlEntry is deleted. + + An implementation MUST start assigning + traceRouteProbeHistoryIndex values at 1 and wrap after + exceeding the maximum possible value, as defined by the + limit of this object ('ffffffff'h)." + ::= { traceRouteProbeHistoryEntry 1 } + + traceRouteProbeHistoryHopIndex OBJECT-TYPE + SYNTAX Unsigned32 (1..255) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Indicates which hop in a traceroute path the probe's + results are for. The value of this object is initially + determined by the value of traceRouteCtlInitialTtl." + ::= { traceRouteProbeHistoryEntry 2 } + + traceRouteProbeHistoryProbeIndex OBJECT-TYPE + SYNTAX Unsigned32 (1..10) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Indicates the index of a probe for a particular + hop in a traceroute path. The number of probes per + hop is determined by the value of the corresponding + traceRouteCtlProbesPerHop object." + ::= { traceRouteProbeHistoryEntry 3 } + + traceRouteProbeHistoryHAddrType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This objects indicates the type of address stored + in the corresponding traceRouteProbeHistoryHAddr + object." + ::= { traceRouteProbeHistoryEntry 4 } + + traceRouteProbeHistoryHAddr OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The address of a hop in a traceroute path. This object + is not allowed to be a DNS name. The value of the + corresponding object, traceRouteProbeHistoryHAddrType, + indicates this object's IP address type." + ::= { traceRouteProbeHistoryEntry 5 } + + traceRouteProbeHistoryResponse OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The amount of time measured in milliseconds from when + a probe was sent to when its response was received or + when it timed out. The value of this object is reported + as 0 when it is not possible to transmit a probe." + ::= { traceRouteProbeHistoryEntry 6 } + + traceRouteProbeHistoryStatus OBJECT-TYPE + SYNTAX OperationResponseStatus + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The result of a traceroute operation made by a remote + host for a particular probe." + ::= { traceRouteProbeHistoryEntry 7 } + + traceRouteProbeHistoryLastRC OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The last implementation-method-specific reply code received. + + Traceroute is usually implemented by transmitting a series of + probe packets with increasing time-to-live values. A probe + packet is a UDP datagram encapsulated into an IP packet. + Each hop in a path to the target (destination) host rejects + the probe packets (probe's TTL too small, ICMP reply) until + either the maximum TTL is exceeded or the target host is + received." + ::= { traceRouteProbeHistoryEntry 8 } + + traceRouteProbeHistoryTime OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Timestamp for when this probe's results were determined." + ::= { traceRouteProbeHistoryEntry 9 } + + -- Traceroute Hop Results Table + + traceRouteHopsTable OBJECT-TYPE + SYNTAX SEQUENCE OF TraceRouteHopsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines the Remote Operations Traceroute Hop Table for + keeping track of the results of traceroute tests on a + per-hop basis." + ::= { traceRouteObjects 5 } + + traceRouteHopsEntry OBJECT-TYPE + SYNTAX TraceRouteHopsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Defines an entry in the traceRouteHopsTable. + The first two index elements identify the + traceRouteCtlEntry that a traceRouteHopsEntry + belongs to. The third index element, + traceRouteHopsHopIndex, selects a + hop in a traceroute path." + INDEX { + traceRouteCtlOwnerIndex, + traceRouteCtlTestName, + traceRouteHopsHopIndex + } + ::= { traceRouteHopsTable 1 } + + TraceRouteHopsEntry ::= + SEQUENCE { + traceRouteHopsHopIndex Unsigned32, + traceRouteHopsIpTgtAddressType InetAddressType, + traceRouteHopsIpTgtAddress InetAddress, + traceRouteHopsMinRtt Unsigned32, + traceRouteHopsMaxRtt Unsigned32, + traceRouteHopsAverageRtt Unsigned32, + traceRouteHopsRttSumOfSquares Unsigned32, + traceRouteHopsSentProbes Unsigned32, + traceRouteHopsProbeResponses Unsigned32, + traceRouteHopsLastGoodProbe DateAndTime + } + + traceRouteHopsHopIndex OBJECT-TYPE + SYNTAX Unsigned32 (1..'ffffffff'h) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Specifies the hop index for a traceroute hop. Values + for this object with respect to the same + traceRouteCtlOwnerIndex and traceRouteCtlTestName + MUST start at 1 and be given increasing values for + subsequent hops. The value of traceRouteHopsHopIndex is not + necessarily the number of the hop on the traced path. + + The traceRouteHopsTable keeps the current traceroute + path per traceRouteCtlEntry if enabled by + setting the corresponding traceRouteCtlCreateHopsEntries + to true(1). + + All hops (traceRouteHopsTable entries) in a traceroute + path MUST be updated at the same time when a traceroute + operation is completed. Care needs to be applied when a path + either changes or can't be determined. The initial portion + of the path, up to the first hop change, MUST retain the + same traceRouteHopsHopIndex values. The remaining portion + of the path SHOULD be assigned new traceRouteHopsHopIndex + values." + ::= { traceRouteHopsEntry 1 } + + traceRouteHopsIpTgtAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object indicates the type of address stored + in the corresponding traceRouteHopsIpTgtAddress + object." + ::= { traceRouteHopsEntry 2 } + + traceRouteHopsIpTgtAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object reports the IP address associated with + + the hop. A value for this object should be reported + as a numeric IP address, not as a DNS name. + + The address type (InetAddressType) that relates to + this object is specified by the corresponding value + of pingCtlSourceAddressType." + ::= { traceRouteHopsEntry 3 } + + traceRouteHopsMinRtt OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The minimum traceroute round-trip-time (RTT) received for + this hop. A value of 0 for this object implies that no + RTT has been received." + ::= { traceRouteHopsEntry 4 } + + traceRouteHopsMaxRtt OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The maximum traceroute round-trip-time (RTT) received for + this hop. A value of 0 for this object implies that no + RTT has been received." + ::= { traceRouteHopsEntry 5 } + + traceRouteHopsAverageRtt OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current average traceroute round-trip-time (RTT) for + this hop." + ::= { traceRouteHopsEntry 6 } + + traceRouteHopsRttSumOfSquares OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object contains the sum of the squares of all + round-trip-times received for this hop. Its purpose is + to enable standard deviation calculation." + ::= { traceRouteHopsEntry 7 } + + traceRouteHopsSentProbes OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of this object reflects the number of probes sent + for this hop during this traceroute test. The value of this + object should start at 0." + ::= { traceRouteHopsEntry 8 } + + traceRouteHopsProbeResponses OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Number of responses received for this hop during this + traceroute test. This value of this object should start + at 0." + ::= { traceRouteHopsEntry 9 } + + traceRouteHopsLastGoodProbe OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Date and time at which the last response was received for a + probe for this hop during this traceroute test." + ::= { traceRouteHopsEntry 10 } + + -- Notification Definition section + + traceRoutePathChange NOTIFICATION-TYPE + OBJECTS { + traceRouteCtlTargetAddressType, + traceRouteCtlTargetAddress, + traceRouteResultsIpTgtAddrType, + traceRouteResultsIpTgtAddr + } + STATUS current + DESCRIPTION + "The path to a target has changed." + ::= { traceRouteNotifications 1 } + + traceRouteTestFailed NOTIFICATION-TYPE + OBJECTS { + traceRouteCtlTargetAddressType, + traceRouteCtlTargetAddress, + traceRouteResultsIpTgtAddrType, + traceRouteResultsIpTgtAddr + + } + STATUS current + DESCRIPTION + "Could not determine the path to a target." + ::= { traceRouteNotifications 2 } + + traceRouteTestCompleted NOTIFICATION-TYPE + OBJECTS { + traceRouteCtlTargetAddressType, + traceRouteCtlTargetAddress, + traceRouteResultsIpTgtAddrType, + traceRouteResultsIpTgtAddr + } + STATUS current + DESCRIPTION + "The path to a target has just been determined." + ::= { traceRouteNotifications 3 } + + -- Conformance information + -- Compliance statements + + traceRouteCompliances OBJECT IDENTIFIER + ::= { traceRouteConformance 1 } + traceRouteGroups OBJECT IDENTIFIER + ::= { traceRouteConformance 2 } + + -- Compliance statements + + traceRouteFullCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP entities that + fully implement the DISMAN-TRACEROUTE-MIB." + MODULE -- this module + MANDATORY-GROUPS { + traceRouteMinimumGroup, + traceRouteCtlRowStatusGroup, + traceRouteHistoryGroup + } + + GROUP traceRouteHopsTableGroup + DESCRIPTION + "This group lists the objects that make up a + traceRouteHopsEntry. Support of the traceRouteHopsTable + is optional." + + GROUP traceRouteNotificationsGroup + DESCRIPTION + "This group defines a collection of optional + notifications." + + OBJECT traceRouteMaxConcurrentRequests + MIN-ACCESS read-only + DESCRIPTION + "The agent is not required to support SET + operations to this object." + + OBJECT traceRouteCtlByPassRouteTable + MIN-ACCESS read-only + DESCRIPTION + "Write access to this object is not required by + implementations that are not capable of its + implementation. The function represented by this + object is implementable if the setsockopt + SOL_SOCKET SO_DONTROUTE option is supported." + + OBJECT traceRouteCtlDSField + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If write access is + not supported, return a 0 as the value of this object. + A value of 0 implies that the function represented by + this option is not supported." + + OBJECT traceRouteCtlSourceAddressType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + MIN-ACCESS read-only + DESCRIPTION + "Write access to this object is not required by + implementations that are not capable of binding the + send socket with a source address. An implementation + is only required to support IPv4 and IPv6 addresses." + + OBJECT traceRouteCtlSourceAddress + SYNTAX InetAddress (SIZE(0|4|16)) + MIN-ACCESS read-only + DESCRIPTION + "Write access to this object is not required by + implementations that are not capable of binding the + send socket with a source address. An implementation + is only required to support IPv4 and IPv6 addresses." + + OBJECT traceRouteCtlIfIndex + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If write access is + + not supported, return a 0 as the value of this object. + A value of 0 implies that the function represented by + this option is not supported." + + OBJECT traceRouteCtlMiscOptions + MIN-ACCESS read-only + DESCRIPTION + "Support of this object is optional. If not + supporting, do not allow write access and return a + zero-length octet string as the value of the object." + + OBJECT traceRouteCtlStorageType + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. It is also allowed + that implementations support only the volatile(2) + StorageType enumeration." + + OBJECT traceRouteCtlType + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. In addition, the only + value that is RECOMMENDED to be supported by an + implementation is traceRouteUsingUdpProbes." + + OBJECT traceRouteResultsIpTgtAddrType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + + OBJECT traceRouteResultsIpTgtAddr + SYNTAX InetAddress (SIZE(0|4|16)) + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + + OBJECT traceRouteResultsLastGoodPath + DESCRIPTION + "If the traceRouteHopsTableGroup is implemented, then + this object is mandatory for implementations that have + access to a system clock and that are capable of setting + the values for DateAndTime objects. It is RECOMMENDED + that when this object is not supported its values + be reported as '0000000000000000'H." + + OBJECT traceRouteProbeHistoryHAddrType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + + OBJECT traceRouteProbeHistoryHAddr + SYNTAX InetAddress (SIZE(0|4|16)) + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + + OBJECT traceRouteProbeHistoryTime + DESCRIPTION + "This object is mandatory for implementations that have + access to a system clock and that are capable of setting + the values for DateAndTime objects. It is RECOMMENDED + that when this object is not supported its values + be reported as '0000000000000000'H." + + OBJECT traceRouteHopsIpTgtAddressType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + + OBJECT traceRouteHopsIpTgtAddress + SYNTAX InetAddress (SIZE(0|4|16)) + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + + OBJECT traceRouteHopsLastGoodProbe + DESCRIPTION + "This object is mandatory for implementations that have + access to a system clock and that are capable of setting + the values for DateAndTime objects. It is RECOMMENDED + that when this object is not supported its values + be reported as '0000000000000000'H." + ::= { traceRouteCompliances 2 } + + traceRouteMinimumCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The minimum compliance statement for SNMP entities + which implement the minimal subset of the + DISMAN-TRACEROUTE-MIB. Implementors might choose this + subset for small devices with limited resources." + MODULE -- this module + + MANDATORY-GROUPS { traceRouteMinimumGroup } + + GROUP traceRouteCtlRowStatusGroup + DESCRIPTION + "A compliant implementation does not have to implement + the traceRouteCtlRowStatusGroup." + + GROUP traceRouteHistoryGroup + DESCRIPTION + "A compliant implementation does not have to implement + the traceRouteHistoryGroup." + + GROUP traceRouteHopsTableGroup + DESCRIPTION + "This group lists the objects that make up a + traceRouteHopsEntry. Support of the traceRouteHopsTable + is optional." + + GROUP traceRouteNotificationsGroup + DESCRIPTION + "This group defines a collection of optional + notifications." + + OBJECT traceRouteMaxConcurrentRequests + MIN-ACCESS read-only + DESCRIPTION + "The agent is not required to support SET + operations to this object." + + OBJECT traceRouteCtlByPassRouteTable + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If write access is + not supported, return a false(2) as the value of this + object. A value of false(2) means that the function + represented by this option is not supported." + + OBJECT traceRouteCtlDSField + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If write access is + not supported, return a 0 as the value of this object. + A value of 0 implies that the function represented by + this option is not supported." + + OBJECT traceRouteCtlSourceAddressType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + MIN-ACCESS read-only + DESCRIPTION + "Write access to this object is not required by + implementations that are not capable of binding the + send socket with a source address. An implementation + is only required to support IPv4 and IPv6 addresses." + + OBJECT traceRouteCtlSourceAddress + SYNTAX InetAddress (SIZE(0|4|16)) + MIN-ACCESS read-only + DESCRIPTION + "Write access to this object is not required by + implementations that are not capable of binding the + send socket with a source address. An implementation + is only required to support IPv4 and IPv6 addresses." + + OBJECT traceRouteCtlIfIndex + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If write access is + not supported, return a 0 as the value of this object. + A value of 0 implies that the function represented by + this option is not supported." + + OBJECT traceRouteCtlMiscOptions + MIN-ACCESS read-only + DESCRIPTION + "Support of this object is optional. If not + supporting, do not allow write access, and return a + zero-length octet string as the value of the object." + + OBJECT traceRouteCtlDontFragment + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If write access is + not supported, return a false(2) as the value of this + object. A value of false(2) means that the function + represented by this option is not supported." + + OBJECT traceRouteCtlInitialTtl + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If write access is + not supported, return a 1 as the value of this object." + + OBJECT traceRouteCtlFrequency + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If write access is + not supported, return a 0 as the value of this object. + A value of 0 implies that the function represented by + this option is not supported." + + OBJECT traceRouteCtlStorageType + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. It is also allowed + that implementations support only the volatile(2) + StorageType enumeration." + + OBJECT traceRouteCtlDescr + MIN-ACCESS read-only + DESCRIPTION + "The agent is not required to support set + operations to this object." + + OBJECT traceRouteCtlMaxRows + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If the + traceRouteHistoryGroup is not implemented, then write + access to this object MUST be disabled, and the object + MUST return a value of 0 when retrieved." + + OBJECT traceRouteCtlTrapGeneration + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If the + traceRouteNotificationsGroup is not implemented, then + write access to this object MUST be disabled, and the + object MUST return a value with no bit set when + retrieved. No bit set indicates that no notification + is generated." + + OBJECT traceRouteCtlCreateHopsEntries + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. If the + traceRouteHopsTableGroup is not implemented, then + write access to this object MUST be disabled, and the + object MUST return a value of false(2) when retrieved." + + OBJECT traceRouteCtlType + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. In addition, the only + + value that is RECOMMENDED to be supported by an + implementation is traceRouteUsingUdpProbes." + + OBJECT traceRouteResultsIpTgtAddrType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + + OBJECT traceRouteResultsIpTgtAddr + SYNTAX InetAddress (SIZE(0|4|16)) + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + + OBJECT traceRouteResultsLastGoodPath + DESCRIPTION + "This object is mandatory for implementations that have + access to a system clock and that are capable of setting + the values for DateAndTime objects. It is RECOMMENDED + that when this object is not supported its values + be reported as '0000000000000000'H." + + OBJECT traceRouteProbeHistoryHAddrType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + + OBJECT traceRouteProbeHistoryHAddr + SYNTAX InetAddress (SIZE(0|4|16)) + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + + OBJECT traceRouteProbeHistoryTime + DESCRIPTION + "If the traceRouteHistoryGroup is implemented, then + this object is mandatory for implementations that have + access to a system clock and that are capable of setting + the values for DateAndTime objects. It is RECOMMENDED + that when this object is not supported its values + be reported as '0000000000000000'H." + + OBJECT traceRouteHopsIpTgtAddressType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + DESCRIPTION + "An implementation should only support IPv4 and + + globally unique IPv6 address values for this object." + + OBJECT traceRouteHopsIpTgtAddress + SYNTAX InetAddress (SIZE(0|4|16)) + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + + OBJECT traceRouteHopsLastGoodProbe + DESCRIPTION + "If the traceRouteHopsTableGroup is implemented, then + this object is mandatory for implementations that have + access to a system clock and that are capable of setting + the values for DateAndTime objects. It is RECOMMENDED + that when this object is not supported its values + be reported as '0000000000000000'H." + ::= { traceRouteCompliances 3 } + + traceRouteCompliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION + "The compliance statement for the DISMAN-TRACEROUTE-MIB. + This compliance statement has been deprecated because + the traceRouteGroup and the traceRouteTimeStampGroup + have been split and deprecated. The + traceRouteFullCompliance is semantically identical to the + deprecated traceRouteCompliance statement." + MODULE -- this module + MANDATORY-GROUPS { + traceRouteGroup + } + GROUP traceRouteTimeStampGroup + DESCRIPTION + "This group is mandatory for implementations that have + access to a system clock and that are capable of setting + the values for DateAndTime objects." + + GROUP traceRouteNotificationsGroup + DESCRIPTION + "This group defines a collection of optional + notifications." + + GROUP traceRouteHopsTableGroup + DESCRIPTION + "This group lists the objects that make up a + traceRouteHopsEntry. Support of the traceRouteHopsTable + is optional." + + OBJECT traceRouteMaxConcurrentRequests + MIN-ACCESS read-only + DESCRIPTION + "The agent is not required to support SET + operations to this object." + + OBJECT traceRouteCtlByPassRouteTable + MIN-ACCESS read-only + DESCRIPTION + "This object is not required by implementations that + are not capable of its implementation. The function + represented by this object is implementable if the + setsockopt SOL_SOCKET SO_DONTROUTE option is + supported." + + OBJECT traceRouteCtlSourceAddressType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + MIN-ACCESS read-only + DESCRIPTION + "This object is not required by implementations that + are not capable of binding the send socket with a + source address. An implementation is only required to + support IPv4 and IPv6 addresses." + + OBJECT traceRouteCtlSourceAddress + SYNTAX InetAddress (SIZE(0|4|16)) + MIN-ACCESS read-only + DESCRIPTION + "This object is not required by implementations that + are not capable of binding the send socket with a + source address. An implementation is only required to + support IPv4 and globally unique IPv6 addresses." + + OBJECT traceRouteCtlIfIndex + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. When write access is + not supported, return a 0 as the value of this object. + A value of 0 implies that the function represented by + this option is not supported." + + OBJECT traceRouteCtlMiscOptions + MIN-ACCESS read-only + DESCRIPTION + "Support of this object is optional. When not + supporting, do not allow write access, and return a + zero-length octet string as the value of the object." + + OBJECT traceRouteCtlStorageType + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. It is also allowed + that implementations support only the volatile + StorageType enumeration." + + OBJECT traceRouteCtlDSField + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. When write access is + not supported, return a 0 as the value of this object. + A value of 0 implies that the function represented by + this option is not supported." + + OBJECT traceRouteCtlType + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. In addition, the only + value that is RECOMMENDED to be supported by an + implementation is traceRouteUsingUdpProbes." + + OBJECT traceRouteResultsIpTgtAddrType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + + OBJECT traceRouteResultsIpTgtAddr + SYNTAX InetAddress (SIZE(0|4|16)) + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + + OBJECT traceRouteProbeHistoryHAddrType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + + OBJECT traceRouteProbeHistoryHAddr + SYNTAX InetAddress (SIZE(0|4|16)) + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + + OBJECT traceRouteHopsIpTgtAddressType + SYNTAX InetAddressType { unknown(0), ipv4(1), ipv6(2) } + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + + OBJECT traceRouteHopsIpTgtAddress + SYNTAX InetAddress (SIZE(0|4|16)) + DESCRIPTION + "An implementation should only support IPv4 and + globally unique IPv6 address values for this object." + ::= { traceRouteCompliances 1 } + + -- MIB groupings + + traceRouteMinimumGroup OBJECT-GROUP + OBJECTS { + traceRouteMaxConcurrentRequests, + traceRouteCtlTargetAddressType, + traceRouteCtlTargetAddress, + traceRouteCtlByPassRouteTable, + traceRouteCtlDataSize, + traceRouteCtlTimeOut, + traceRouteCtlProbesPerHop, + traceRouteCtlPort, + traceRouteCtlMaxTtl, + traceRouteCtlDSField, + traceRouteCtlSourceAddressType, + traceRouteCtlSourceAddress, + traceRouteCtlIfIndex, + traceRouteCtlMiscOptions, + traceRouteCtlMaxFailures, + traceRouteCtlDontFragment, + traceRouteCtlInitialTtl, + traceRouteCtlFrequency, + traceRouteCtlStorageType, + traceRouteCtlAdminStatus, + traceRouteCtlMaxRows, + traceRouteCtlTrapGeneration, + traceRouteCtlDescr, + traceRouteCtlCreateHopsEntries, + traceRouteCtlType, + traceRouteResultsOperStatus, + traceRouteResultsCurHopCount, + traceRouteResultsCurProbeCount, + traceRouteResultsIpTgtAddrType, + traceRouteResultsIpTgtAddr, + traceRouteResultsTestAttempts, + traceRouteResultsTestSuccesses, + traceRouteResultsLastGoodPath + + } + STATUS current + DESCRIPTION + "The group of objects that constitute the remote traceroute + operation." + ::= { traceRouteGroups 5 } + + traceRouteCtlRowStatusGroup OBJECT-GROUP + OBJECTS { + traceRouteCtlRowStatus + } + STATUS current + DESCRIPTION + "The RowStatus object of the traceRouteCtlTable." + ::= { traceRouteGroups 6 } + + traceRouteHistoryGroup OBJECT-GROUP + OBJECTS { + traceRouteProbeHistoryHAddrType, + traceRouteProbeHistoryHAddr, + traceRouteProbeHistoryResponse, + traceRouteProbeHistoryStatus, + traceRouteProbeHistoryLastRC, + traceRouteProbeHistoryTime + } + STATUS current + DESCRIPTION + "The group of objects that constitute the history + capability." + ::= { traceRouteGroups 7 } + + traceRouteNotificationsGroup NOTIFICATION-GROUP + NOTIFICATIONS { + traceRoutePathChange, + traceRouteTestFailed, + traceRouteTestCompleted + } + STATUS current + DESCRIPTION + "The notifications that are required to be supported by + implementations of this MIB." + ::= { traceRouteGroups 3 } + + traceRouteHopsTableGroup OBJECT-GROUP + OBJECTS { + traceRouteHopsIpTgtAddressType, + traceRouteHopsIpTgtAddress, + traceRouteHopsMinRtt, + traceRouteHopsMaxRtt, + traceRouteHopsAverageRtt, + traceRouteHopsRttSumOfSquares, + traceRouteHopsSentProbes, + traceRouteHopsProbeResponses, + traceRouteHopsLastGoodProbe + } + STATUS current + DESCRIPTION + "The group of objects that constitute the + traceRouteHopsTable." + ::= { traceRouteGroups 4 } + + traceRouteGroup OBJECT-GROUP + OBJECTS { + traceRouteMaxConcurrentRequests, + traceRouteCtlTargetAddressType, + traceRouteCtlTargetAddress, + traceRouteCtlByPassRouteTable, + traceRouteCtlDataSize, + traceRouteCtlTimeOut, + traceRouteCtlProbesPerHop, + traceRouteCtlPort, + traceRouteCtlMaxTtl, + traceRouteCtlDSField, + traceRouteCtlSourceAddressType, + traceRouteCtlSourceAddress, + traceRouteCtlIfIndex, + traceRouteCtlMiscOptions, + traceRouteCtlMaxFailures, + traceRouteCtlDontFragment, + traceRouteCtlInitialTtl, + traceRouteCtlFrequency, + traceRouteCtlStorageType, + traceRouteCtlAdminStatus, + traceRouteCtlMaxRows, + traceRouteCtlTrapGeneration, + traceRouteCtlDescr, + traceRouteCtlCreateHopsEntries, + traceRouteCtlType, + traceRouteCtlRowStatus, + traceRouteResultsOperStatus, + traceRouteResultsCurHopCount, + traceRouteResultsCurProbeCount, + traceRouteResultsIpTgtAddrType, + traceRouteResultsIpTgtAddr, + traceRouteResultsTestAttempts, + traceRouteResultsTestSuccesses, + traceRouteProbeHistoryHAddrType, + traceRouteProbeHistoryHAddr, + traceRouteProbeHistoryResponse, + traceRouteProbeHistoryStatus, + traceRouteProbeHistoryLastRC + } + STATUS deprecated + DESCRIPTION + "The group of objects that constitute the remote traceroute + operation." + ::= { traceRouteGroups 1 } + + traceRouteTimeStampGroup OBJECT-GROUP + OBJECTS { + traceRouteResultsLastGoodPath, + traceRouteProbeHistoryTime + } + STATUS deprecated + DESCRIPTION + "The group of DateAndTime objects." + ::= { traceRouteGroups 2 } + +END diff --git a/mibs/EtherLike-MIB.txt b/mibs/EtherLike-MIB.txt new file mode 100644 index 0000000..dcec7ce --- /dev/null +++ b/mibs/EtherLike-MIB.txt @@ -0,0 +1,1862 @@ +EtherLike-MIB DEFINITIONS ::= BEGIN + + IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, OBJECT-IDENTITY, + Integer32, Counter32, Counter64, mib-2, transmission + FROM SNMPv2-SMI + MODULE-COMPLIANCE, OBJECT-GROUP + FROM SNMPv2-CONF + TruthValue + FROM SNMPv2-TC + ifIndex, InterfaceIndex + FROM IF-MIB; + + etherMIB MODULE-IDENTITY + LAST-UPDATED "200309190000Z" -- September 19, 2003 + ORGANIZATION "IETF Ethernet Interfaces and Hub MIB + Working Group" + CONTACT-INFO + "WG E-mail: hubmib@ietf.org + To subscribe: hubmib-request@ietf.org + + Chair: Dan Romascanu + Postal: Avaya Inc. + Atidum Technology Park, Bldg. 3 + Tel Aviv 61131 + Israel + Tel: +972 3 645 8414 + E-mail: dromasca@avaya.com + + Editor: John Flick + Postal: Hewlett-Packard Company + 8000 Foothills Blvd. M/S 5557 + Roseville, CA 95747-5557 + USA + Tel: +1 916 785 4018 + Fax: +1 916 785 1199 + E-mail: johnf@rose.hp.com" + DESCRIPTION "The MIB module to describe generic objects for + ethernet-like network interfaces. + + The following reference is used throughout this + MIB module: + + [IEEE 802.3 Std] refers to: + IEEE Std 802.3, 2002 Edition: 'IEEE Standard + + for Information technology - + Telecommunications and information exchange + between systems - Local and metropolitan + area networks - Specific requirements - + Part 3: Carrier sense multiple access with + collision detection (CSMA/CD) access method + and physical layer specifications', as + amended by IEEE Std 802.3ae-2002: + 'Amendment: Media Access Control (MAC) + Parameters, Physical Layer, and Management + Parameters for 10 Gb/s Operation', August, + 2002. + + Of particular interest is Clause 30, '10 Mb/s, + 100 Mb/s, 1000 Mb/s, and 10 Gb/s Management'. + + Copyright (C) The Internet Society (2003). This + version of this MIB module is part of RFC 3635; + see the RFC itself for full legal notices." + + REVISION "200309190000Z" -- September 19, 2003 + DESCRIPTION "Updated to include support for 10 Gb/sec + interfaces. This resulted in the following + revisions: + + - Updated dot3StatsAlignmentErrors and + dot3StatsSymbolErrors DESCRIPTIONs to + reflect behaviour at 10 Gb/s + - Added dot3StatsRateControlAbility and + dot3RateControlStatus for management + of the Rate Control function in 10 Gb/s + WAN applications + - Added 64-bit versions of all counters + that are used on high-speed ethernet + interfaces + - Added object groups to contain the new + objects + - Deprecated etherStatsBaseGroup and + split into etherStatsBaseGroup2 and + etherStatsHalfDuplexGroup, so that + interfaces which can only operate at + full-duplex do not need to implement + half-duplex-only statistics + - Deprecated dot3Compliance and replaced + it with dot3Compliance2, which includes + the compliance information for the new + object groups + + In addition, the dot3Tests and dot3Errors + object identities have been deprecated, + since there is no longer a standard method + for using them. + + This version published as RFC 3635." + + REVISION "199908240400Z" -- August 24, 1999 + DESCRIPTION "Updated to include support for 1000 Mb/sec + interfaces and full-duplex interfaces. + This version published as RFC 2665." + + REVISION "199806032150Z" -- June 3, 1998 + DESCRIPTION "Updated to include support for 100 Mb/sec + interfaces. + This version published as RFC 2358." + + REVISION "199402030400Z" -- February 3, 1994 + DESCRIPTION "Initial version, published as RFC 1650." + ::= { mib-2 35 } + + etherMIBObjects OBJECT IDENTIFIER ::= { etherMIB 1 } + + dot3 OBJECT IDENTIFIER ::= { transmission 7 } + + -- the Ethernet-like Statistics group + + dot3StatsTable OBJECT-TYPE + SYNTAX SEQUENCE OF Dot3StatsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "Statistics for a collection of ethernet-like + interfaces attached to a particular system. + There will be one row in this table for each + ethernet-like interface in the system." + ::= { dot3 2 } + + dot3StatsEntry OBJECT-TYPE + SYNTAX Dot3StatsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "Statistics for a particular interface to an + ethernet-like medium." + INDEX { dot3StatsIndex } + ::= { dot3StatsTable 1 } + + Dot3StatsEntry ::= + SEQUENCE { + + dot3StatsIndex InterfaceIndex, + dot3StatsAlignmentErrors Counter32, + dot3StatsFCSErrors Counter32, + dot3StatsSingleCollisionFrames Counter32, + dot3StatsMultipleCollisionFrames Counter32, + dot3StatsSQETestErrors Counter32, + dot3StatsDeferredTransmissions Counter32, + dot3StatsLateCollisions Counter32, + dot3StatsExcessiveCollisions Counter32, + dot3StatsInternalMacTransmitErrors Counter32, + dot3StatsCarrierSenseErrors Counter32, + dot3StatsFrameTooLongs Counter32, + dot3StatsInternalMacReceiveErrors Counter32, + dot3StatsEtherChipSet OBJECT IDENTIFIER, + dot3StatsSymbolErrors Counter32, + dot3StatsDuplexStatus INTEGER, + dot3StatsRateControlAbility TruthValue, + dot3StatsRateControlStatus INTEGER + } + + dot3StatsIndex OBJECT-TYPE + SYNTAX InterfaceIndex + MAX-ACCESS read-only -- read-only since originally an + -- SMIv1 index + STATUS current + DESCRIPTION "An index value that uniquely identifies an + interface to an ethernet-like medium. The + interface identified by a particular value of + this index is the same interface as identified + by the same value of ifIndex." + REFERENCE "RFC 2863, ifIndex" + ::= { dot3StatsEntry 1 } + + dot3StatsAlignmentErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of frames received on a particular + interface that are not an integral number of + octets in length and do not pass the FCS check. + + The count represented by an instance of this + object is incremented when the alignmentError + status is returned by the MAC service to the + LLC (or other MAC user). Received frames for + which multiple error conditions pertain are, + according to the conventions of IEEE 802.3 + Layer Management, counted exclusively according + + to the error status presented to the LLC. + + This counter does not increment for group + encoding schemes greater than 4 bits per group. + + For interfaces operating at 10 Gb/s, this + counter can roll over in less than 5 minutes if + it is incrementing at its maximum rate. Since + that amount of time could be less than a + management station's poll cycle time, in order + to avoid a loss of information, a management + station is advised to poll the + dot3HCStatsAlignmentErrors object for 10 Gb/s + or faster interfaces. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.7, + aAlignmentErrors" + ::= { dot3StatsEntry 2 } + + dot3StatsFCSErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of frames received on a particular + interface that are an integral number of octets + in length but do not pass the FCS check. This + count does not include frames received with + frame-too-long or frame-too-short error. + + The count represented by an instance of this + object is incremented when the frameCheckError + status is returned by the MAC service to the + LLC (or other MAC user). Received frames for + which multiple error conditions pertain are, + according to the conventions of IEEE 802.3 + Layer Management, counted exclusively according + to the error status presented to the LLC. + + Note: Coding errors detected by the physical + layer for speeds above 10 Mb/s will cause the + frame to fail the FCS check. + + For interfaces operating at 10 Gb/s, this + counter can roll over in less than 5 minutes if + + it is incrementing at its maximum rate. Since + that amount of time could be less than a + management station's poll cycle time, in order + to avoid a loss of information, a management + station is advised to poll the + dot3HCStatsFCSErrors object for 10 Gb/s or + faster interfaces. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.6, + aFrameCheckSequenceErrors." + ::= { dot3StatsEntry 3 } + + dot3StatsSingleCollisionFrames OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of frames that are involved in a single + collision, and are subsequently transmitted + successfully. + + A frame that is counted by an instance of this + object is also counted by the corresponding + instance of either the ifOutUcastPkts, + ifOutMulticastPkts, or ifOutBroadcastPkts, + and is not counted by the corresponding + instance of the dot3StatsMultipleCollisionFrames + object. + + This counter does not increment when the + interface is operating in full-duplex mode. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.3, + aSingleCollisionFrames." + ::= { dot3StatsEntry 4 } + + dot3StatsMultipleCollisionFrames OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of frames that are involved in more + + than one collision and are subsequently + transmitted successfully. + + A frame that is counted by an instance of this + object is also counted by the corresponding + instance of either the ifOutUcastPkts, + ifOutMulticastPkts, or ifOutBroadcastPkts, + and is not counted by the corresponding + instance of the dot3StatsSingleCollisionFrames + object. + + This counter does not increment when the + interface is operating in full-duplex mode. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.4, + aMultipleCollisionFrames." + ::= { dot3StatsEntry 5 } + + dot3StatsSQETestErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of times that the SQE TEST ERROR + is received on a particular interface. The + SQE TEST ERROR is set in accordance with the + rules for verification of the SQE detection + mechanism in the PLS Carrier Sense Function as + described in IEEE Std. 802.3, 2000 Edition, + section 7.2.4.6. + + This counter does not increment on interfaces + operating at speeds greater than 10 Mb/s, or on + interfaces operating in full-duplex mode. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 7.2.4.6, also 30.3.2.1.4, + aSQETestErrors." + ::= { dot3StatsEntry 6 } + + dot3StatsDeferredTransmissions OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of frames for which the first + transmission attempt on a particular interface + is delayed because the medium is busy. + + The count represented by an instance of this + object does not include frames involved in + collisions. + + This counter does not increment when the + interface is operating in full-duplex mode. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.9, + aFramesWithDeferredXmissions." + ::= { dot3StatsEntry 7 } + + dot3StatsLateCollisions OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "The number of times that a collision is + detected on a particular interface later than + one slotTime into the transmission of a packet. + + A (late) collision included in a count + represented by an instance of this object is + also considered as a (generic) collision for + purposes of other collision-related + statistics. + + This counter does not increment when the + interface is operating in full-duplex mode. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.10, + aLateCollisions." + ::= { dot3StatsEntry 8 } + + dot3StatsExcessiveCollisions OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of frames for which transmission on a + particular interface fails due to excessive + collisions. + + This counter does not increment when the + interface is operating in full-duplex mode. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.11, + aFramesAbortedDueToXSColls." + ::= { dot3StatsEntry 9 } + + dot3StatsInternalMacTransmitErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of frames for which transmission on a + particular interface fails due to an internal + MAC sublayer transmit error. A frame is only + counted by an instance of this object if it is + not counted by the corresponding instance of + either the dot3StatsLateCollisions object, the + dot3StatsExcessiveCollisions object, or the + dot3StatsCarrierSenseErrors object. + + The precise meaning of the count represented by + an instance of this object is implementation- + specific. In particular, an instance of this + object may represent a count of transmission + errors on a particular interface that are not + otherwise counted. + + For interfaces operating at 10 Gb/s, this + counter can roll over in less than 5 minutes if + it is incrementing at its maximum rate. Since + that amount of time could be less than a + management station's poll cycle time, in order + to avoid a loss of information, a management + station is advised to poll the + dot3HCStatsInternalMacTransmitErrors object for + 10 Gb/s or faster interfaces. + + Discontinuities in the value of this counter can + + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.12, + aFramesLostDueToIntMACXmitError." + ::= { dot3StatsEntry 10 } + + dot3StatsCarrierSenseErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "The number of times that the carrier sense + condition was lost or never asserted when + attempting to transmit a frame on a particular + interface. + + The count represented by an instance of this + object is incremented at most once per + transmission attempt, even if the carrier sense + condition fluctuates during a transmission + attempt. + + This counter does not increment when the + interface is operating in full-duplex mode. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.13, + aCarrierSenseErrors." + ::= { dot3StatsEntry 11 } + + -- { dot3StatsEntry 12 } is not assigned + + dot3StatsFrameTooLongs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of frames received on a particular + interface that exceed the maximum permitted + frame size. + + The count represented by an instance of this + object is incremented when the frameTooLong + status is returned by the MAC service to the + LLC (or other MAC user). Received frames for + which multiple error conditions pertain are, + according to the conventions of IEEE 802.3 + Layer Management, counted exclusively according + to the error status presented to the LLC. + + For interfaces operating at 10 Gb/s, this + counter can roll over in less than 80 minutes if + it is incrementing at its maximum rate. Since + that amount of time could be less than a + management station's poll cycle time, in order + to avoid a loss of information, a management + station is advised to poll the + dot3HCStatsFrameTooLongs object for 10 Gb/s + or faster interfaces. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.25, + aFrameTooLongErrors." + ::= { dot3StatsEntry 13 } + + -- { dot3StatsEntry 14 } is not assigned + + -- { dot3StatsEntry 15 } is not assigned + + dot3StatsInternalMacReceiveErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of frames for which reception on a + particular interface fails due to an internal + MAC sublayer receive error. A frame is only + counted by an instance of this object if it is + not counted by the corresponding instance of + either the dot3StatsFrameTooLongs object, the + dot3StatsAlignmentErrors object, or the + dot3StatsFCSErrors object. + + The precise meaning of the count represented by + an instance of this object is implementation- + specific. In particular, an instance of this + object may represent a count of receive errors + on a particular interface that are not + otherwise counted. + + For interfaces operating at 10 Gb/s, this + counter can roll over in less than 5 minutes if + + it is incrementing at its maximum rate. Since + that amount of time could be less than a + management station's poll cycle time, in order + to avoid a loss of information, a management + station is advised to poll the + dot3HCStatsInternalMacReceiveErrors object for + 10 Gb/s or faster interfaces. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.15, + aFramesLostDueToIntMACRcvError." + ::= { dot3StatsEntry 16 } + + dot3StatsEtherChipSet OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION "******** THIS OBJECT IS DEPRECATED ******** + + This object contains an OBJECT IDENTIFIER + which identifies the chipset used to + realize the interface. Ethernet-like + interfaces are typically built out of + several different chips. The MIB implementor + is presented with a decision of which chip + to identify via this object. The implementor + should identify the chip which is usually + called the Medium Access Control chip. + If no such chip is easily identifiable, + the implementor should identify the chip + which actually gathers the transmit + and receive statistics and error + indications. This would allow a + manager station to correlate the + statistics and the chip generating + them, giving it the ability to take + into account any known anomalies + in the chip. + + This object has been deprecated. Implementation + feedback indicates that it is of limited use for + debugging network problems in the field, and + the administrative overhead involved in + maintaining a registry of chipset OIDs is not + justified." + ::= { dot3StatsEntry 17 } + + dot3StatsSymbolErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "For an interface operating at 100 Mb/s, the + number of times there was an invalid data symbol + when a valid carrier was present. + + For an interface operating in half-duplex mode + at 1000 Mb/s, the number of times the receiving + media is non-idle (a carrier event) for a period + of time equal to or greater than slotTime, and + during which there was at least one occurrence + of an event that causes the PHY to indicate + 'Data reception error' or 'carrier extend error' + on the GMII. + + For an interface operating in full-duplex mode + at 1000 Mb/s, the number of times the receiving + media is non-idle (a carrier event) for a period + of time equal to or greater than minFrameSize, + and during which there was at least one + occurrence of an event that causes the PHY to + indicate 'Data reception error' on the GMII. + + For an interface operating at 10 Gb/s, the + number of times the receiving media is non-idle + (a carrier event) for a period of time equal to + or greater than minFrameSize, and during which + there was at least one occurrence of an event + that causes the PHY to indicate 'Receive Error' + on the XGMII. + + The count represented by an instance of this + object is incremented at most once per carrier + event, even if multiple symbol errors occur + during the carrier event. This count does + not increment if a collision is present. + + This counter does not increment when the + interface is operating at 10 Mb/s. + + For interfaces operating at 10 Gb/s, this + counter can roll over in less than 5 minutes if + it is incrementing at its maximum rate. Since + that amount of time could be less than a + + management station's poll cycle time, in order + to avoid a loss of information, a management + station is advised to poll the + dot3HCStatsSymbolErrors object for 10 Gb/s + or faster interfaces. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.2.1.5, + aSymbolErrorDuringCarrier." + ::= { dot3StatsEntry 18 } + + dot3StatsDuplexStatus OBJECT-TYPE + SYNTAX INTEGER { + unknown(1), + halfDuplex(2), + fullDuplex(3) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION "The current mode of operation of the MAC + entity. 'unknown' indicates that the current + duplex mode could not be determined. + + Management control of the duplex mode is + accomplished through the MAU MIB. When + an interface does not support autonegotiation, + or when autonegotiation is not enabled, the + duplex mode is controlled using + ifMauDefaultType. When autonegotiation is + supported and enabled, duplex mode is controlled + using ifMauAutoNegAdvertisedBits. In either + case, the currently operating duplex mode is + reflected both in this object and in ifMauType. + + Note that this object provides redundant + information with ifMauType. Normally, redundant + objects are discouraged. However, in this + instance, it allows a management application to + determine the duplex status of an interface + without having to know every possible value of + ifMauType. This was felt to be sufficiently + valuable to justify the redundancy." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.32, + aDuplexStatus." + ::= { dot3StatsEntry 19 } + + dot3StatsRateControlAbility OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION "'true' for interfaces operating at speeds above + 1000 Mb/s that support Rate Control through + lowering the average data rate of the MAC + sublayer, with frame granularity, and 'false' + otherwise." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.33, + aRateControlAbility." + ::= { dot3StatsEntry 20 } + + dot3StatsRateControlStatus OBJECT-TYPE + SYNTAX INTEGER { + rateControlOff(1), + rateControlOn(2), + unknown(3) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION "The current Rate Control mode of operation of + the MAC sublayer of this interface." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.34, + aRateControlStatus." + ::= { dot3StatsEntry 21 } + + -- the Ethernet-like Collision Statistics group + + -- Implementation of this group is optional; it is appropriate + -- for all systems which have the necessary metering + + dot3CollTable OBJECT-TYPE + SYNTAX SEQUENCE OF Dot3CollEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "A collection of collision histograms for a + particular set of interfaces." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.30, + aCollisionFrames." + ::= { dot3 5 } + + dot3CollEntry OBJECT-TYPE + SYNTAX Dot3CollEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "A cell in the histogram of per-frame + collisions for a particular interface. An + + instance of this object represents the + frequency of individual MAC frames for which + the transmission (successful or otherwise) on a + particular interface is accompanied by a + particular number of media collisions." + INDEX { ifIndex, dot3CollCount } + ::= { dot3CollTable 1 } + + Dot3CollEntry ::= + SEQUENCE { + dot3CollCount Integer32, + dot3CollFrequencies Counter32 + } + + -- { dot3CollEntry 1 } is no longer in use + + dot3CollCount OBJECT-TYPE + SYNTAX Integer32 (1..16) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "The number of per-frame media collisions for + which a particular collision histogram cell + represents the frequency on a particular + interface." + ::= { dot3CollEntry 2 } + + dot3CollFrequencies OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of individual MAC frames for which the + transmission (successful or otherwise) on a + particular interface occurs after the + frame has experienced exactly the number + of collisions in the associated + dot3CollCount object. + + For example, a frame which is transmitted + on interface 77 after experiencing + exactly 4 collisions would be indicated + by incrementing only dot3CollFrequencies.77.4. + No other instance of dot3CollFrequencies would + be incremented in this example. + + This counter does not increment when the + interface is operating in full-duplex mode. + + Discontinuities in the value of this counter can + + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + ::= { dot3CollEntry 3 } + + dot3ControlTable OBJECT-TYPE + SYNTAX SEQUENCE OF Dot3ControlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "A table of descriptive and status information + about the MAC Control sublayer on the + ethernet-like interfaces attached to a + particular system. There will be one row in + this table for each ethernet-like interface in + the system which implements the MAC Control + sublayer. If some, but not all, of the + ethernet-like interfaces in the system implement + the MAC Control sublayer, there will be fewer + rows in this table than in the dot3StatsTable." + ::= { dot3 9 } + + dot3ControlEntry OBJECT-TYPE + SYNTAX Dot3ControlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "An entry in the table, containing information + about the MAC Control sublayer on a single + ethernet-like interface." + INDEX { dot3StatsIndex } + ::= { dot3ControlTable 1 } + + Dot3ControlEntry ::= + SEQUENCE { + dot3ControlFunctionsSupported BITS, + dot3ControlInUnknownOpcodes Counter32, + dot3HCControlInUnknownOpcodes Counter64 + } + + dot3ControlFunctionsSupported OBJECT-TYPE + SYNTAX BITS { + pause(0) -- 802.3 flow control + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A list of the possible MAC Control functions + implemented for this interface." + REFERENCE "[IEEE 802.3 Std.], 30.3.3.2, + aMACControlFunctionsSupported." + ::= { dot3ControlEntry 1 } + + dot3ControlInUnknownOpcodes OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of MAC Control frames received on this + interface that contain an opcode that is not + supported by this device. + + For interfaces operating at 10 Gb/s, this + counter can roll over in less than 5 minutes if + it is incrementing at its maximum rate. Since + that amount of time could be less than a + management station's poll cycle time, in order + to avoid a loss of information, a management + station is advised to poll the + dot3HCControlInUnknownOpcodes object for 10 Gb/s + or faster interfaces. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.3.5, + aUnsupportedOpcodesReceived" + ::= { dot3ControlEntry 2 } + + dot3HCControlInUnknownOpcodes OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of MAC Control frames received on this + interface that contain an opcode that is not + supported by this device. + + This counter is a 64 bit version of + dot3ControlInUnknownOpcodes. It should be used + on interfaces operating at 10 Gb/s or faster. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.3.5, + aUnsupportedOpcodesReceived" + ::= { dot3ControlEntry 3 } + + dot3PauseTable OBJECT-TYPE + SYNTAX SEQUENCE OF Dot3PauseEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "A table of descriptive and status information + about the MAC Control PAUSE function on the + ethernet-like interfaces attached to a + particular system. There will be one row in + this table for each ethernet-like interface in + the system which supports the MAC Control PAUSE + function (i.e., the 'pause' bit in the + corresponding instance of + dot3ControlFunctionsSupported is set). If some, + but not all, of the ethernet-like interfaces in + the system implement the MAC Control PAUSE + function (for example, if some interfaces only + support half-duplex), there will be fewer rows + in this table than in the dot3StatsTable." + ::= { dot3 10 } + + dot3PauseEntry OBJECT-TYPE + SYNTAX Dot3PauseEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "An entry in the table, containing information + about the MAC Control PAUSE function on a single + ethernet-like interface." + INDEX { dot3StatsIndex } + ::= { dot3PauseTable 1 } + + Dot3PauseEntry ::= + + SEQUENCE { + dot3PauseAdminMode INTEGER, + dot3PauseOperMode INTEGER, + dot3InPauseFrames Counter32, + dot3OutPauseFrames Counter32, + dot3HCInPauseFrames Counter64, + dot3HCOutPauseFrames Counter64 + } + + dot3PauseAdminMode OBJECT-TYPE + SYNTAX INTEGER { + disabled(1), + enabledXmit(2), + enabledRcv(3), + enabledXmitAndRcv(4) + } + MAX-ACCESS read-write + STATUS current + DESCRIPTION "This object is used to configure the default + administrative PAUSE mode for this interface. + + This object represents the + administratively-configured PAUSE mode for this + interface. If auto-negotiation is not enabled + or is not implemented for the active MAU + attached to this interface, the value of this + object determines the operational PAUSE mode + of the interface whenever it is operating in + full-duplex mode. In this case, a set to this + object will force the interface into the + specified mode. + + If auto-negotiation is implemented and enabled + for the MAU attached to this interface, the + PAUSE mode for this interface is determined by + auto-negotiation, and the value of this object + denotes the mode to which the interface will + automatically revert if/when auto-negotiation is + later disabled. Note that when auto-negotiation + is running, administrative control of the PAUSE + mode may be accomplished using the + ifMauAutoNegCapAdvertisedBits object in the + MAU-MIB. + + Note that the value of this object is ignored + when the interface is not operating in + full-duplex mode. + + An attempt to set this object to + 'enabledXmit(2)' or 'enabledRcv(3)' will fail + on interfaces that do not support operation + at greater than 100 Mb/s." + ::= { dot3PauseEntry 1 } + + dot3PauseOperMode OBJECT-TYPE + SYNTAX INTEGER { + disabled(1), + enabledXmit(2), + enabledRcv(3), + enabledXmitAndRcv(4) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION "This object reflects the PAUSE mode currently + + in use on this interface, as determined by + either (1) the result of the auto-negotiation + function or (2) if auto-negotiation is not + enabled or is not implemented for the active MAU + attached to this interface, by the value of + dot3PauseAdminMode. Interfaces operating at + 100 Mb/s or less will never return + 'enabledXmit(2)' or 'enabledRcv(3)'. Interfaces + operating in half-duplex mode will always return + 'disabled(1)'. Interfaces on which + auto-negotiation is enabled but not yet + completed should return the value + 'disabled(1)'." + ::= { dot3PauseEntry 2 } + + dot3InPauseFrames OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of MAC Control frames received on this + interface with an opcode indicating the PAUSE + operation. + + This counter does not increment when the + interface is operating in half-duplex mode. + + For interfaces operating at 10 Gb/s, this + counter can roll over in less than 5 minutes if + it is incrementing at its maximum rate. Since + that amount of time could be less than a + management station's poll cycle time, in order + to avoid a loss of information, a management + station is advised to poll the + dot3HCInPauseFrames object for 10 Gb/s or + faster interfaces. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.4.3, + aPAUSEMACCtrlFramesReceived." + ::= { dot3PauseEntry 3 } + + dot3OutPauseFrames OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of MAC Control frames transmitted on + this interface with an opcode indicating the + PAUSE operation. + + This counter does not increment when the + interface is operating in half-duplex mode. + + For interfaces operating at 10 Gb/s, this + counter can roll over in less than 5 minutes if + it is incrementing at its maximum rate. Since + that amount of time could be less than a + management station's poll cycle time, in order + to avoid a loss of information, a management + station is advised to poll the + dot3HCOutPauseFrames object for 10 Gb/s or + faster interfaces. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.4.2, + aPAUSEMACCtrlFramesTransmitted." + ::= { dot3PauseEntry 4 } + + dot3HCInPauseFrames OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of MAC Control frames received on this + interface with an opcode indicating the PAUSE + operation. + + This counter does not increment when the + interface is operating in half-duplex mode. + + This counter is a 64 bit version of + dot3InPauseFrames. It should be used on + interfaces operating at 10 Gb/s or faster. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.4.3, + aPAUSEMACCtrlFramesReceived." + ::= { dot3PauseEntry 5 } + + dot3HCOutPauseFrames OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of MAC Control frames transmitted on + this interface with an opcode indicating the + PAUSE operation. + + This counter does not increment when the + interface is operating in half-duplex mode. + + This counter is a 64 bit version of + dot3OutPauseFrames. It should be used on + interfaces operating at 10 Gb/s or faster. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.4.2, + aPAUSEMACCtrlFramesTransmitted." + ::= { dot3PauseEntry 6 } + + dot3HCStatsTable OBJECT-TYPE + SYNTAX SEQUENCE OF Dot3HCStatsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "A table containing 64-bit versions of error + counters from the dot3StatsTable. The 32-bit + versions of these counters may roll over quite + quickly on higher speed ethernet interfaces. + The counters that have 64-bit versions in this + table are the counters that apply to full-duplex + interfaces, since 10 Gb/s and faster + ethernet-like interfaces do not support + half-duplex, and very few 1000 Mb/s + ethernet-like interfaces support half-duplex. + + Entries in this table are recommended for + interfaces capable of operating at 1000 Mb/s or + faster, and are required for interfaces capable + of operating at 10 Gb/s or faster. Lower speed + ethernet-like interfaces do not need entries in + this table, in which case there may be fewer + entries in this table than in the + dot3StatsTable. However, implementations + containing interfaces with a mix of speeds may + choose to implement entries in this table for + + all ethernet-like interfaces." + ::= { dot3 11 } + + dot3HCStatsEntry OBJECT-TYPE + SYNTAX Dot3HCStatsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "An entry containing 64-bit statistics for a + single ethernet-like interface." + INDEX { dot3StatsIndex } + ::= { dot3HCStatsTable 1 } + + Dot3HCStatsEntry ::= + SEQUENCE { + dot3HCStatsAlignmentErrors Counter64, + dot3HCStatsFCSErrors Counter64, + dot3HCStatsInternalMacTransmitErrors Counter64, + dot3HCStatsFrameTooLongs Counter64, + dot3HCStatsInternalMacReceiveErrors Counter64, + dot3HCStatsSymbolErrors Counter64 + } + + dot3HCStatsAlignmentErrors OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of frames received on a particular + interface that are not an integral number of + octets in length and do not pass the FCS check. + + The count represented by an instance of this + object is incremented when the alignmentError + status is returned by the MAC service to the + LLC (or other MAC user). Received frames for + which multiple error conditions pertain are, + according to the conventions of IEEE 802.3 + Layer Management, counted exclusively according + to the error status presented to the LLC. + + This counter does not increment for group + encoding schemes greater than 4 bits per group. + + This counter is a 64 bit version of + dot3StatsAlignmentErrors. It should be used + on interfaces operating at 10 Gb/s or faster. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.7, + aAlignmentErrors" + ::= { dot3HCStatsEntry 1 } + + dot3HCStatsFCSErrors OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of frames received on a particular + interface that are an integral number of octets + in length but do not pass the FCS check. This + count does not include frames received with + frame-too-long or frame-too-short error. + + The count represented by an instance of this + object is incremented when the frameCheckError + status is returned by the MAC service to the + LLC (or other MAC user). Received frames for + which multiple error conditions pertain are, + according to the conventions of IEEE 802.3 + Layer Management, counted exclusively according + to the error status presented to the LLC. + + Note: Coding errors detected by the physical + layer for speeds above 10 Mb/s will cause the + frame to fail the FCS check. + + This counter is a 64 bit version of + dot3StatsFCSErrors. It should be used on + interfaces operating at 10 Gb/s or faster. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.6, + aFrameCheckSequenceErrors." + ::= { dot3HCStatsEntry 2 } + + dot3HCStatsInternalMacTransmitErrors OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of frames for which transmission on a + particular interface fails due to an internal + MAC sublayer transmit error. A frame is only + + counted by an instance of this object if it is + not counted by the corresponding instance of + either the dot3StatsLateCollisions object, the + dot3StatsExcessiveCollisions object, or the + dot3StatsCarrierSenseErrors object. + + The precise meaning of the count represented by + an instance of this object is implementation- + specific. In particular, an instance of this + object may represent a count of transmission + errors on a particular interface that are not + otherwise counted. + + This counter is a 64 bit version of + dot3StatsInternalMacTransmitErrors. It should + be used on interfaces operating at 10 Gb/s or + faster. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.12, + aFramesLostDueToIntMACXmitError." + ::= { dot3HCStatsEntry 3 } + + dot3HCStatsFrameTooLongs OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of frames received on a particular + interface that exceed the maximum permitted + frame size. + + The count represented by an instance of this + object is incremented when the frameTooLong + status is returned by the MAC service to the + LLC (or other MAC user). Received frames for + which multiple error conditions pertain are, + according to the conventions of IEEE 802.3 + Layer Management, counted exclusively according + to the error status presented to the LLC. + + This counter is a 64 bit version of + dot3StatsFrameTooLongs. It should be used on + interfaces operating at 10 Gb/s or faster. + + Discontinuities in the value of this counter can + + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.25, + aFrameTooLongErrors." + ::= { dot3HCStatsEntry 4 } + + dot3HCStatsInternalMacReceiveErrors OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A count of frames for which reception on a + particular interface fails due to an internal + MAC sublayer receive error. A frame is only + counted by an instance of this object if it is + not counted by the corresponding instance of + either the dot3StatsFrameTooLongs object, the + dot3StatsAlignmentErrors object, or the + dot3StatsFCSErrors object. + + The precise meaning of the count represented by + an instance of this object is implementation- + specific. In particular, an instance of this + object may represent a count of receive errors + on a particular interface that are not + otherwise counted. + + This counter is a 64 bit version of + dot3StatsInternalMacReceiveErrors. It should be + used on interfaces operating at 10 Gb/s or + faster. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.1.1.15, + aFramesLostDueToIntMACRcvError." + ::= { dot3HCStatsEntry 5 } + + dot3HCStatsSymbolErrors OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "For an interface operating at 100 Mb/s, the + number of times there was an invalid data symbol + when a valid carrier was present. + + For an interface operating in half-duplex mode + at 1000 Mb/s, the number of times the receiving + media is non-idle (a carrier event) for a period + of time equal to or greater than slotTime, and + during which there was at least one occurrence + of an event that causes the PHY to indicate + 'Data reception error' or 'carrier extend error' + on the GMII. + + For an interface operating in full-duplex mode + at 1000 Mb/s, the number of times the receiving + media is non-idle (a carrier event) for a period + of time equal to or greater than minFrameSize, + and during which there was at least one + occurrence of an event that causes the PHY to + indicate 'Data reception error' on the GMII. + + For an interface operating at 10 Gb/s, the + number of times the receiving media is non-idle + (a carrier event) for a period of time equal to + or greater than minFrameSize, and during which + there was at least one occurrence of an event + that causes the PHY to indicate 'Receive Error' + on the XGMII. + + The count represented by an instance of this + object is incremented at most once per carrier + event, even if multiple symbol errors occur + during the carrier event. This count does + not increment if a collision is present. + + This counter is a 64 bit version of + dot3StatsSymbolErrors. It should be used on + interfaces operating at 10 Gb/s or faster. + + Discontinuities in the value of this counter can + occur at re-initialization of the management + system, and at other times as indicated by the + value of ifCounterDiscontinuityTime." + REFERENCE "[IEEE 802.3 Std.], 30.3.2.1.5, + aSymbolErrorDuringCarrier." + ::= { dot3HCStatsEntry 6 } + + -- 802.3 Tests + + dot3Tests OBJECT IDENTIFIER ::= { dot3 6 } + + dot3Errors OBJECT IDENTIFIER ::= { dot3 7 } + + -- TDR Test + + dot3TestTdr OBJECT-IDENTITY + STATUS deprecated + DESCRIPTION "******** THIS IDENTITY IS DEPRECATED ******* + + The Time-Domain Reflectometry (TDR) test is + specific to ethernet-like interfaces of type + 10Base5 and 10Base2. The TDR value may be + useful in determining the approximate distance + to a cable fault. It is advisable to repeat + this test to check for a consistent resulting + TDR value, to verify that there is a fault. + + A TDR test returns as its result the time + interval, measured in 10 MHz ticks or 100 nsec + units, between the start of TDR test + transmission and the subsequent detection of a + collision or deassertion of carrier. On + successful completion of a TDR test, the result + is stored as the value of an appropriate + instance of an appropriate vendor specific MIB + object, and the OBJECT IDENTIFIER of that + instance is stored in the appropriate instance + of the appropriate test result code object + (thereby indicating where the result has been + stored). + + This object identity has been deprecated, since + the ifTestTable in the IF-MIB was deprecated, + and there is no longer a standard mechanism for + initiating an interface test. This left no + standard way of using this object identity." + ::= { dot3Tests 1 } + + -- Loopback Test + + dot3TestLoopBack OBJECT-IDENTITY + STATUS deprecated + DESCRIPTION "******** THIS IDENTITY IS DEPRECATED ******* + + This test configures the MAC chip and executes + an internal loopback test of memory, data paths, + and the MAC chip logic. This loopback test can + only be executed if the interface is offline. + Once the test has completed, the MAC chip should + + be reinitialized for network operation, but it + should remain offline. + + If an error occurs during a test, the + appropriate test result object will be set + to indicate a failure. The two OBJECT + IDENTIFIER values dot3ErrorInitError and + dot3ErrorLoopbackError may be used to provided + more information as values for an appropriate + test result code object. + + This object identity has been deprecated, since + the ifTestTable in the IF-MIB was deprecated, + and there is no longer a standard mechanism for + initiating an interface test. This left no + standard way of using this object identity." + ::= { dot3Tests 2 } + + dot3ErrorInitError OBJECT-IDENTITY + STATUS deprecated + DESCRIPTION "******** THIS IDENTITY IS DEPRECATED ******* + + Couldn't initialize MAC chip for test. + + This object identity has been deprecated, since + the ifTestTable in the IF-MIB was deprecated, + and there is no longer a standard mechanism for + initiating an interface test. This left no + standard way of using this object identity." + ::= { dot3Errors 1 } + + dot3ErrorLoopbackError OBJECT-IDENTITY + STATUS deprecated + DESCRIPTION "******** THIS IDENTITY IS DEPRECATED ******* + + Expected data not received (or not received + correctly) in loopback test. + + This object identity has been deprecated, since + the ifTestTable in the IF-MIB was deprecated, + and there is no longer a standard mechanism for + initiating an interface test. This left no + standard way of using this object identity." + ::= { dot3Errors 2 } + + -- { dot3 8 }, the dot3ChipSets tree, is defined in [RFC2666] + + -- conformance information + + etherConformance OBJECT IDENTIFIER ::= { etherMIB 2 } + + etherGroups OBJECT IDENTIFIER ::= { etherConformance 1 } + etherCompliances OBJECT IDENTIFIER ::= { etherConformance 2 } + + -- compliance statements + + etherCompliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION "******** THIS COMPLIANCE IS DEPRECATED ******** + + The compliance statement for managed network + entities which have ethernet-like network + interfaces. + + This compliance is deprecated and replaced by + dot3Compliance." + + MODULE -- this module + MANDATORY-GROUPS { etherStatsGroup } + + GROUP etherCollisionTableGroup + DESCRIPTION "This group is optional. It is appropriate + for all systems which have the necessary + metering. Implementation in such systems is + highly recommended." + ::= { etherCompliances 1 } + + ether100MbsCompliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION "******** THIS COMPLIANCE IS DEPRECATED ******** + + The compliance statement for managed network + entities which have 100 Mb/sec ethernet-like + network interfaces. + + This compliance is deprecated and replaced by + dot3Compliance." + + MODULE -- this module + MANDATORY-GROUPS { etherStats100MbsGroup } + + GROUP etherCollisionTableGroup + DESCRIPTION "This group is optional. It is appropriate + for all systems which have the necessary + metering. Implementation in such systems is + highly recommended." + ::= { etherCompliances 2 } + + dot3Compliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION "******** THIS COMPLIANCE IS DEPRECATED ******** + + The compliance statement for managed network + entities which have ethernet-like network + interfaces. + + This compliance is deprecated and replaced by + dot3Compliance2." + + MODULE -- this module + MANDATORY-GROUPS { etherStatsBaseGroup } + + GROUP etherDuplexGroup + DESCRIPTION "This group is mandatory for all + ethernet-like network interfaces which are + capable of operating in full-duplex mode. + It is highly recommended for all + ethernet-like network interfaces." + + GROUP etherStatsLowSpeedGroup + DESCRIPTION "This group is mandatory for all + ethernet-like network interfaces which are + capable of operating at 10 Mb/s or slower in + half-duplex mode." + + GROUP etherStatsHighSpeedGroup + DESCRIPTION "This group is mandatory for all + ethernet-like network interfaces which are + capable of operating at 100 Mb/s or faster." + + GROUP etherControlGroup + DESCRIPTION "This group is mandatory for all + ethernet-like network interfaces that + support the MAC Control sublayer." + + GROUP etherControlPauseGroup + DESCRIPTION "This group is mandatory for all + ethernet-like network interfaces that + support the MAC Control PAUSE function." + + GROUP etherCollisionTableGroup + DESCRIPTION "This group is optional. It is appropriate + for all ethernet-like network interfaces + which are capable of operating in + half-duplex mode and have the necessary + metering. Implementation in systems with + + such interfaces is highly recommended." + ::= { etherCompliances 3 } + + dot3Compliance2 MODULE-COMPLIANCE + STATUS current + DESCRIPTION "The compliance statement for managed network + entities which have ethernet-like network + interfaces. + + Note that compliance with this MIB module + requires compliance with the ifCompliance3 + MODULE-COMPLIANCE statement of the IF-MIB + (RFC2863). In addition, compliance with this + MIB module requires compliance with the + mauModIfCompl3 MODULE-COMPLIANCE statement of + the MAU-MIB (RFC3636)." + + MODULE -- this module + MANDATORY-GROUPS { etherStatsBaseGroup2 } + + GROUP etherDuplexGroup + DESCRIPTION "This group is mandatory for all + ethernet-like network interfaces which are + capable of operating in full-duplex mode. + It is highly recommended for all + ethernet-like network interfaces." + + GROUP etherRateControlGroup + DESCRIPTION "This group is mandatory for all + ethernet-like network interfaces which are + capable of operating at speeds faster than + 1000 Mb/s. It is highly recommended for all + ethernet-like network interfaces." + + GROUP etherStatsLowSpeedGroup + DESCRIPTION "This group is mandatory for all + ethernet-like network interfaces which are + capable of operating at 10 Mb/s or slower in + half-duplex mode." + + GROUP etherStatsHighSpeedGroup + DESCRIPTION "This group is mandatory for all + ethernet-like network interfaces which are + capable of operating at 100 Mb/s or faster." + + GROUP etherStatsHalfDuplexGroup + DESCRIPTION "This group is mandatory for all + ethernet-like network interfaces which are + + capable of operating in half-duplex mode." + + GROUP etherHCStatsGroup + DESCRIPTION "This group is mandatory for all + ethernet-like network interfaces which are + capable of operating at 10 Gb/s or faster. + It is recommended for all ethernet-like + network interfaces which are capable of + operating at 1000 Mb/s or faster." + + GROUP etherControlGroup + DESCRIPTION "This group is mandatory for all + ethernet-like network interfaces that + support the MAC Control sublayer." + + GROUP etherHCControlGroup + DESCRIPTION "This group is mandatory for all + ethernet-like network interfaces that + support the MAC Control sublayer and are + capable of operating at 10 Gb/s or faster." + + GROUP etherControlPauseGroup + DESCRIPTION "This group is mandatory for all + ethernet-like network interfaces that + support the MAC Control PAUSE function." + + GROUP etherHCControlPauseGroup + DESCRIPTION "This group is mandatory for all + ethernet-like network interfaces that + support the MAC Control PAUSE function and + are capable of operating at 10 Gb/s or + faster." + + GROUP etherCollisionTableGroup + DESCRIPTION "This group is optional. It is appropriate + for all ethernet-like network interfaces + which are capable of operating in + half-duplex mode and have the necessary + metering. Implementation in systems with + such interfaces is highly recommended." + ::= { etherCompliances 4 } + + -- units of conformance + + etherStatsGroup OBJECT-GROUP + OBJECTS { dot3StatsIndex, + dot3StatsAlignmentErrors, + dot3StatsFCSErrors, + dot3StatsSingleCollisionFrames, + dot3StatsMultipleCollisionFrames, + dot3StatsSQETestErrors, + dot3StatsDeferredTransmissions, + dot3StatsLateCollisions, + dot3StatsExcessiveCollisions, + dot3StatsInternalMacTransmitErrors, + dot3StatsCarrierSenseErrors, + dot3StatsFrameTooLongs, + dot3StatsInternalMacReceiveErrors, + dot3StatsEtherChipSet + } + STATUS deprecated + DESCRIPTION "********* THIS GROUP IS DEPRECATED ********** + + A collection of objects providing information + applicable to all ethernet-like network + interfaces. + + This object group has been deprecated and + replaced by etherStatsBaseGroup and + etherStatsLowSpeedGroup." + ::= { etherGroups 1 } + + etherCollisionTableGroup OBJECT-GROUP + OBJECTS { dot3CollFrequencies + } + STATUS current + DESCRIPTION "A collection of objects providing a histogram + of packets successfully transmitted after + experiencing exactly N collisions." + ::= { etherGroups 2 } + + etherStats100MbsGroup OBJECT-GROUP + OBJECTS { dot3StatsIndex, + dot3StatsAlignmentErrors, + dot3StatsFCSErrors, + dot3StatsSingleCollisionFrames, + dot3StatsMultipleCollisionFrames, + dot3StatsDeferredTransmissions, + dot3StatsLateCollisions, + dot3StatsExcessiveCollisions, + dot3StatsInternalMacTransmitErrors, + dot3StatsCarrierSenseErrors, + dot3StatsFrameTooLongs, + dot3StatsInternalMacReceiveErrors, + dot3StatsEtherChipSet, + dot3StatsSymbolErrors + + } + STATUS deprecated + DESCRIPTION "********* THIS GROUP IS DEPRECATED ********** + + A collection of objects providing information + applicable to 100 Mb/sec ethernet-like network + interfaces. + + This object group has been deprecated and + replaced by etherStatsBaseGroup and + etherStatsHighSpeedGroup." + ::= { etherGroups 3 } + + etherStatsBaseGroup OBJECT-GROUP + OBJECTS { dot3StatsIndex, + dot3StatsAlignmentErrors, + dot3StatsFCSErrors, + dot3StatsSingleCollisionFrames, + dot3StatsMultipleCollisionFrames, + dot3StatsDeferredTransmissions, + dot3StatsLateCollisions, + dot3StatsExcessiveCollisions, + dot3StatsInternalMacTransmitErrors, + dot3StatsCarrierSenseErrors, + dot3StatsFrameTooLongs, + dot3StatsInternalMacReceiveErrors + } + STATUS deprecated + DESCRIPTION "********* THIS GROUP IS DEPRECATED ********** + + A collection of objects providing information + applicable to all ethernet-like network + interfaces. + + This object group has been deprecated and + replaced by etherStatsBaseGroup2 and + etherStatsHalfDuplexGroup, to separate + objects which must be implemented by all + ethernet-like network interfaces from + objects that need only be implemented on + ethernet-like network interfaces that are + capable of half-duplex operation." + ::= { etherGroups 4 } + + etherStatsLowSpeedGroup OBJECT-GROUP + OBJECTS { dot3StatsSQETestErrors } + STATUS current + DESCRIPTION "A collection of objects providing information + + applicable to ethernet-like network interfaces + capable of operating at 10 Mb/s or slower in + half-duplex mode." + ::= { etherGroups 5 } + + etherStatsHighSpeedGroup OBJECT-GROUP + OBJECTS { dot3StatsSymbolErrors } + STATUS current + DESCRIPTION "A collection of objects providing information + applicable to ethernet-like network interfaces + capable of operating at 100 Mb/s or faster." + ::= { etherGroups 6 } + + etherDuplexGroup OBJECT-GROUP + OBJECTS { dot3StatsDuplexStatus } + STATUS current + DESCRIPTION "A collection of objects providing information + about the duplex mode of an ethernet-like + network interface." + ::= { etherGroups 7 } + + etherControlGroup OBJECT-GROUP + OBJECTS { dot3ControlFunctionsSupported, + dot3ControlInUnknownOpcodes + } + STATUS current + DESCRIPTION "A collection of objects providing information + about the MAC Control sublayer on ethernet-like + network interfaces." + ::= { etherGroups 8 } + + etherControlPauseGroup OBJECT-GROUP + OBJECTS { dot3PauseAdminMode, + dot3PauseOperMode, + dot3InPauseFrames, + dot3OutPauseFrames + } + STATUS current + DESCRIPTION "A collection of objects providing information + about and control of the MAC Control PAUSE + function on ethernet-like network interfaces." + ::= { etherGroups 9 } + + etherStatsBaseGroup2 OBJECT-GROUP + OBJECTS { dot3StatsIndex, + dot3StatsAlignmentErrors, + dot3StatsFCSErrors, + dot3StatsInternalMacTransmitErrors, + dot3StatsFrameTooLongs, + dot3StatsInternalMacReceiveErrors + } + STATUS current + DESCRIPTION "A collection of objects providing information + applicable to all ethernet-like network + interfaces." + ::= { etherGroups 10 } + + etherStatsHalfDuplexGroup OBJECT-GROUP + OBJECTS { dot3StatsSingleCollisionFrames, + dot3StatsMultipleCollisionFrames, + dot3StatsDeferredTransmissions, + dot3StatsLateCollisions, + dot3StatsExcessiveCollisions, + dot3StatsCarrierSenseErrors + } + STATUS current + DESCRIPTION "A collection of objects providing information + applicable only to half-duplex ethernet-like + network interfaces." + ::= { etherGroups 11 } + + etherHCStatsGroup OBJECT-GROUP + OBJECTS { dot3HCStatsAlignmentErrors, + dot3HCStatsFCSErrors, + dot3HCStatsInternalMacTransmitErrors, + dot3HCStatsFrameTooLongs, + dot3HCStatsInternalMacReceiveErrors, + dot3HCStatsSymbolErrors + } + STATUS current + DESCRIPTION "A collection of objects providing high-capacity + statistics applicable to higher-speed + ethernet-like network interfaces." + ::= { etherGroups 12 } + + etherHCControlGroup OBJECT-GROUP + OBJECTS { dot3HCControlInUnknownOpcodes } + STATUS current + DESCRIPTION "A collection of objects providing high-capacity + statistics for the MAC Control sublayer on + higher-speed ethernet-like network interfaces." + ::= { etherGroups 13 } + + etherHCControlPauseGroup OBJECT-GROUP + OBJECTS { dot3HCInPauseFrames, + dot3HCOutPauseFrames + + } + STATUS current + DESCRIPTION "A collection of objects providing high-capacity + statistics for the MAC Control PAUSE function on + higher-speed ethernet-like network interfaces." + ::= { etherGroups 14 } + + etherRateControlGroup OBJECT-GROUP + OBJECTS { dot3StatsRateControlAbility, + dot3StatsRateControlStatus + } + STATUS current + DESCRIPTION "A collection of objects providing information + about the Rate Control function on ethernet-like + interfaces." + ::= { etherGroups 15 } + +END diff --git a/mibs/HCNUM-TC.txt b/mibs/HCNUM-TC.txt new file mode 100644 index 0000000..4be3d54 --- /dev/null +++ b/mibs/HCNUM-TC.txt @@ -0,0 +1,118 @@ +HCNUM-TC DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, mib-2, Counter64 + FROM SNMPv2-SMI + TEXTUAL-CONVENTION + FROM SNMPv2-TC; + +hcnumTC MODULE-IDENTITY + LAST-UPDATED "200006080000Z" + + ORGANIZATION "IETF OPS Area" + CONTACT-INFO + " E-mail: mibs@ops.ietf.org + Subscribe: majordomo@psg.com + with msg body: subscribe mibs + + Andy Bierman + Cisco Systems Inc. + 170 West Tasman Drive + San Jose, CA 95134 USA + +1 408-527-3711 + abierman@cisco.com + + Keith McCloghrie + Cisco Systems Inc. + 170 West Tasman Drive + San Jose, CA 95134 USA + +1 408-526-5260 + kzm@cisco.com + + Randy Presuhn + BMC Software, Inc. + Office 1-3141 + 2141 North First Street + San Jose, California 95131 USA + +1 408 546-1006 + rpresuhn@bmc.com" + DESCRIPTION + "A MIB module containing textual conventions + for high capacity data types. This module + addresses an immediate need for data types not directly + supported in the SMIv2. This short-term solution + is meant to be deprecated as a long-term solution + is deployed." + REVISION "200006080000Z" + DESCRIPTION + "Initial Version of the High Capacity Numbers + MIB module, published as RFC 2856." + ::= { mib-2 78 } + +CounterBasedGauge64 ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The CounterBasedGauge64 type represents a non-negative + integer, which may increase or decrease, but shall never + exceed a maximum value, nor fall below a minimum value. The + maximum value can not be greater than 2^64-1 + (18446744073709551615 decimal), and the minimum value can + + not be smaller than 0. The value of a CounterBasedGauge64 + has its maximum value whenever the information being modeled + is greater than or equal to its maximum value, and has its + minimum value whenever the information being modeled is + smaller than or equal to its minimum value. If the + information being modeled subsequently decreases below + (increases above) the maximum (minimum) value, the + CounterBasedGauge64 also decreases (increases). + + Note that this TC is not strictly supported in SMIv2, + because the 'always increasing' and 'counter wrap' semantics + associated with the Counter64 base type are not preserved. + It is possible that management applications which rely + solely upon the (Counter64) ASN.1 tag to determine object + semantics will mistakenly operate upon objects of this type + as they would for Counter64 objects. + + This textual convention represents a limited and short-term + solution, and may be deprecated as a long term solution is + defined and deployed to replace it." + SYNTAX Counter64 + +ZeroBasedCounter64 ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "This TC describes an object which counts events with the + following semantics: objects of this type will be set to + zero(0) on creation and will thereafter count appropriate + events, wrapping back to zero(0) when the value 2^64 is + reached. + + Provided that an application discovers the new object within + the minimum time to wrap it can use the initial value as a + delta since it last polled the table of which this object is + part. It is important for a management station to be aware + of this minimum time and the actual time between polls, and + to discard data if the actual time is too long or there is + no defined minimum time. + + Typically this TC is used in tables where the INDEX space is + constantly changing and/or the TimeFilter mechanism is in + use. + + Note that this textual convention does not retain all the + semantics of the Counter64 base type. Specifically, a + Counter64 has an arbitrary initial value, but objects + defined with this TC are required to start at the value + + zero. This behavior is not likely to have any adverse + effects on management applications which are expecting + Counter64 semantics. + + This textual convention represents a limited and short-term + solution, and may be deprecated as a long term solution is + defined and deployed to replace it." + SYNTAX Counter64 + +END diff --git a/mibs/HOST-RESOURCES-MIB.txt b/mibs/HOST-RESOURCES-MIB.txt new file mode 100644 index 0000000..373b9b3 --- /dev/null +++ b/mibs/HOST-RESOURCES-MIB.txt @@ -0,0 +1,1540 @@ +HOST-RESOURCES-MIB DEFINITIONS ::= BEGIN + +IMPORTS +MODULE-IDENTITY, OBJECT-TYPE, mib-2, +Integer32, Counter32, Gauge32, TimeTicks FROM SNMPv2-SMI + +TEXTUAL-CONVENTION, DisplayString, +TruthValue, DateAndTime, AutonomousType FROM SNMPv2-TC + +MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF + +InterfaceIndexOrZero FROM IF-MIB; + +hostResourcesMibModule MODULE-IDENTITY + LAST-UPDATED "200003060000Z" -- 6 March 2000 + ORGANIZATION "IETF Host Resources MIB Working Group" + CONTACT-INFO + "Steve Waldbusser + Postal: Lucent Technologies, Inc. + 1213 Innsbruck Dr. + Sunnyvale, CA 94089 + USA + Phone: 650-318-1251 + Fax: 650-318-1633 + Email: waldbusser@lucent.com + + In addition, the Host Resources MIB mailing list is + dedicated to discussion of this MIB. To join the + mailing list, send a request message to + hostmib-request@andrew.cmu.edu. The mailing list + address is hostmib@andrew.cmu.edu." + DESCRIPTION + "This MIB is for use in managing host systems. The term + `host' is construed to mean any computer that communicates + with other similar computers attached to the internet and + that is directly used by one or more human beings. Although + this MIB does not necessarily apply to devices whose primary + function is communications services (e.g., terminal servers, + routers, bridges, monitoring equipment), such relevance is + not explicitly precluded. This MIB instruments attributes + common to all internet hosts including, for example, both + personal computers and systems that run variants of Unix." + + REVISION "200003060000Z" -- 6 March 2000 + DESCRIPTION + "Clarifications and bug fixes based on implementation + experience. This revision was also reformatted in the SMIv2 + format. The revisions made were: + + New RFC document standards: + Added Copyright notice, updated introduction to SNMP + Framework, updated references section, added reference to + RFC 2119, and added a meaningful Security Considerations + section. + + New IANA considerations section for registration of new types + + Conversion to new SMIv2 syntax for the following types and + macros: + Counter32, Integer32, Gauge32, MODULE-IDENTITY, + OBJECT-TYPE, TEXTUAL-CONVENTION, OBJECT-IDENTITY, + MODULE-COMPLIANCE, OBJECT-GROUP + + Used new Textual Conventions: + TruthValue, DateAndTime, AutonomousType, + InterfaceIndexOrZero + + Fixed typo in hrPrinterStatus. + + Added missing error bits to hrPrinterDetectedErrorState and + clarified confusion resulting from suggested mappings to + hrPrinterStatus. + + Clarified that size of objects of type + InternationalDisplayString is number of octets, not number + of encoded symbols. + + Clarified the use of the following objects based on + implementation experience: + hrSystemInitialLoadDevice, hrSystemInitialLoadParameters, + hrMemorySize, hrStorageSize, hrStorageAllocationFailures, + hrDeviceErrors, hrProcessorLoad, hrNetworkIfIndex, + hrDiskStorageCapacity, hrSWRunStatus, hrSWRunPerfCPU, + and hrSWInstalledDate. + + Clarified implementation technique for hrSWInstalledTable. + + Used new AUGMENTS clause for hrSWRunPerfTable. + + Added Internationalization Considerations section. + +This revision published as RFC2790." + + REVISION "9910202200Z" -- 20 October, 1999 + DESCRIPTION + "The original version of this MIB, published as + RFC1514." + ::= { hrMIBAdminInfo 1 } + +host OBJECT IDENTIFIER ::= { mib-2 25 } + +hrSystem OBJECT IDENTIFIER ::= { host 1 } +hrStorage OBJECT IDENTIFIER ::= { host 2 } +hrDevice OBJECT IDENTIFIER ::= { host 3 } +hrSWRun OBJECT IDENTIFIER ::= { host 4 } +hrSWRunPerf OBJECT IDENTIFIER ::= { host 5 } +hrSWInstalled OBJECT IDENTIFIER ::= { host 6 } +hrMIBAdminInfo OBJECT IDENTIFIER ::= { host 7 } + +-- textual conventions + +KBytes ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Storage size, expressed in units of 1024 bytes." + SYNTAX Integer32 (0..2147483647) + +ProductID ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "This textual convention is intended to identify the + + manufacturer, model, and version of a specific + hardware or software product. It is suggested that + these OBJECT IDENTIFIERs are allocated such that all + products from a particular manufacturer are registered + under a subtree distinct to that manufacturer. In + addition, all versions of a product should be + registered under a subtree distinct to that product. + With this strategy, a management station may uniquely + determine the manufacturer and/or model of a product + whose productID is unknown to the management station. + Objects of this type may be useful for inventory + purposes or for automatically detecting + incompatibilities or version mismatches between + various hardware and software components on a system. + + For example, the product ID for the ACME 4860 66MHz + clock doubled processor might be: + enterprises.acme.acmeProcessors.a4860DX2.MHz66 + + A software product might be registered as: + enterprises.acme.acmeOperatingSystems.acmeDOS.six(6).one(1) + " + SYNTAX OBJECT IDENTIFIER + +-- unknownProduct will be used for any unknown ProductID +-- unknownProduct OBJECT IDENTIFIER ::= { 0 0 } + +InternationalDisplayString ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "This data type is used to model textual information + in some character set. A network management station + should use a local algorithm to determine which + character set is in use and how it should be + displayed. Note that this character set may be + encoded with more than one octet per symbol, but will + most often be NVT ASCII. When a size clause is + specified for an object of this type, the size refers + to the length in octets, not the number of symbols." + SYNTAX OCTET STRING + +-- The Host Resources System Group + +hrSystemUptime OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The amount of time since this host was last + initialized. Note that this is different from + sysUpTime in the SNMPv2-MIB [RFC1907] because + sysUpTime is the uptime of the network management + portion of the system." + ::= { hrSystem 1 } + +hrSystemDate OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The host's notion of the local date and time of day." + ::= { hrSystem 2 } + +hrSystemInitialLoadDevice OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The index of the hrDeviceEntry for the device from + which this host is configured to load its initial + operating system configuration (i.e., which operating + system code and/or boot parameters). + + Note that writing to this object just changes the + configuration that will be used the next time the + operating system is loaded and does not actually cause + the reload to occur." + ::= { hrSystem 3 } + +hrSystemInitialLoadParameters OBJECT-TYPE + SYNTAX InternationalDisplayString (SIZE (0..128)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "This object contains the parameters (e.g. a pathname + and parameter) supplied to the load device when + requesting the initial operating system configuration + from that device. + + Note that writing to this object just changes the + configuration that will be used the next time the + operating system is loaded and does not actually cause + the reload to occur." + ::= { hrSystem 4 } + +hrSystemNumUsers OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of user sessions for which this host is + storing state information. A session is a collection + of processes requiring a single act of user + authentication and possibly subject to collective job + control." + ::= { hrSystem 5 } + +hrSystemProcesses OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of process contexts currently loaded or + running on this system." + ::= { hrSystem 6 } + +hrSystemMaxProcesses OBJECT-TYPE + SYNTAX Integer32 (0..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The maximum number of process contexts this system + can support. If there is no fixed maximum, the value + should be zero. On systems that have a fixed maximum, + this object can help diagnose failures that occur when + this maximum is reached." + ::= { hrSystem 7 } + +-- The Host Resources Storage Group + +-- Registration point for storage types, for use with hrStorageType. +-- These are defined in the HOST-RESOURCES-TYPES module. +hrStorageTypes OBJECT IDENTIFIER ::= { hrStorage 1 } + +hrMemorySize OBJECT-TYPE + SYNTAX KBytes + UNITS "KBytes" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The amount of physical read-write main memory, + typically RAM, contained by the host." + ::= { hrStorage 2 } + +hrStorageTable OBJECT-TYPE + SYNTAX SEQUENCE OF HrStorageEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The (conceptual) table of logical storage areas on + the host. + + An entry shall be placed in the storage table for each + logical area of storage that is allocated and has + fixed resource limits. The amount of storage + represented in an entity is the amount actually usable + by the requesting entity, and excludes loss due to + formatting or file system reference information. + + These entries are associated with logical storage + areas, as might be seen by an application, rather than + physical storage entities which are typically seen by + an operating system. Storage such as tapes and + floppies without file systems on them are typically + not allocated in chunks by the operating system to + requesting applications, and therefore shouldn't + appear in this table. Examples of valid storage for + this table include disk partitions, file systems, ram + (for some architectures this is further segmented into + regular memory, extended memory, and so on), backing + store for virtual memory (`swap space'). + + This table is intended to be a useful diagnostic for + `out of memory' and `out of buffers' types of + failures. In addition, it can be a useful performance + monitoring tool for tracking memory, disk, or buffer + usage." + ::= { hrStorage 3 } + +hrStorageEntry OBJECT-TYPE + SYNTAX HrStorageEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A (conceptual) entry for one logical storage area on + the host. As an example, an instance of the + hrStorageType object might be named hrStorageType.3" + INDEX { hrStorageIndex } + ::= { hrStorageTable 1 } + +HrStorageEntry ::= SEQUENCE { + hrStorageIndex Integer32, + hrStorageType AutonomousType, + hrStorageDescr DisplayString, + hrStorageAllocationUnits Integer32, + hrStorageSize Integer32, + hrStorageUsed Integer32, + hrStorageAllocationFailures Counter32 + } + +hrStorageIndex OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A unique value for each logical storage area + contained by the host." + ::= { hrStorageEntry 1 } + +hrStorageType OBJECT-TYPE + SYNTAX AutonomousType + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The type of storage represented by this entry." + ::= { hrStorageEntry 2 } + +hrStorageDescr OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A description of the type and instance of the storage + described by this entry." + ::= { hrStorageEntry 3 } + +hrStorageAllocationUnits OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + UNITS "Bytes" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The size, in bytes, of the data objects allocated + from this pool. If this entry is monitoring sectors, + blocks, buffers, or packets, for example, this number + will commonly be greater than one. Otherwise this + number will typically be one." + ::= { hrStorageEntry 4 } + +hrStorageSize OBJECT-TYPE + SYNTAX Integer32 (0..2147483647) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The size of the storage represented by this entry, in + units of hrStorageAllocationUnits. This object is + writable to allow remote configuration of the size of + the storage area in those cases where such an + operation makes sense and is possible on the + underlying system. For example, the amount of main + memory allocated to a buffer pool might be modified or + the amount of disk space allocated to virtual memory + might be modified." + ::= { hrStorageEntry 5 } + +hrStorageUsed OBJECT-TYPE + SYNTAX Integer32 (0..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The amount of the storage represented by this entry + that is allocated, in units of + hrStorageAllocationUnits." + ::= { hrStorageEntry 6 } + +hrStorageAllocationFailures OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of requests for storage represented by + this entry that could not be honored due to not enough + storage. It should be noted that as this object has a + SYNTAX of Counter32, that it does not have a defined + initial value. However, it is recommended that this + object be initialized to zero, even though management + stations must not depend on such an initialization." + ::= { hrStorageEntry 7 } + +-- The Host Resources Device Group +-- +-- The device group is useful for identifying and diagnosing the +-- devices on a system. The hrDeviceTable contains common +-- information for any type of device. In addition, some devices +-- have device-specific tables for more detailed information. More +-- such tables may be defined in the future for other device types. + +-- Registration point for device types, for use with hrDeviceType. + +-- These are defined in the HOST-RESOURCES-TYPES module. +hrDeviceTypes OBJECT IDENTIFIER ::= { hrDevice 1 } + +hrDeviceTable OBJECT-TYPE + SYNTAX SEQUENCE OF HrDeviceEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The (conceptual) table of devices contained by the + host." + ::= { hrDevice 2 } + +hrDeviceEntry OBJECT-TYPE + SYNTAX HrDeviceEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A (conceptual) entry for one device contained by the + host. As an example, an instance of the hrDeviceType + object might be named hrDeviceType.3" + INDEX { hrDeviceIndex } + ::= { hrDeviceTable 1 } + +HrDeviceEntry ::= SEQUENCE { + hrDeviceIndex Integer32, + hrDeviceType AutonomousType, + hrDeviceDescr DisplayString, + hrDeviceID ProductID, + hrDeviceStatus INTEGER, + hrDeviceErrors Counter32 + } + +hrDeviceIndex OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A unique value for each device contained by the host. + The value for each device must remain constant at + least from one re-initialization of the agent to the + next re-initialization." + ::= { hrDeviceEntry 1 } + +hrDeviceType OBJECT-TYPE + SYNTAX AutonomousType + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An indication of the type of device. + + If this value is + `hrDeviceProcessor { hrDeviceTypes 3 }' then an entry + exists in the hrProcessorTable which corresponds to + this device. + + If this value is + `hrDeviceNetwork { hrDeviceTypes 4 }', then an entry + exists in the hrNetworkTable which corresponds to this + device. + + If this value is + `hrDevicePrinter { hrDeviceTypes 5 }', then an entry + exists in the hrPrinterTable which corresponds to this + device. + + If this value is + `hrDeviceDiskStorage { hrDeviceTypes 6 }', then an + entry exists in the hrDiskStorageTable which + corresponds to this device." + ::= { hrDeviceEntry 2 } + +hrDeviceDescr OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..64)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A textual description of this device, including the + device's manufacturer and revision, and optionally, + its serial number." + ::= { hrDeviceEntry 3 } + +hrDeviceID OBJECT-TYPE + SYNTAX ProductID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The product ID for this device." + ::= { hrDeviceEntry 4 } + +hrDeviceStatus OBJECT-TYPE + SYNTAX INTEGER { + unknown(1), + running(2), + warning(3), + testing(4), + down(5) + + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current operational state of the device described + by this row of the table. A value unknown(1) + indicates that the current state of the device is + unknown. running(2) indicates that the device is up + and running and that no unusual error conditions are + known. The warning(3) state indicates that agent has + been informed of an unusual error condition by the + operational software (e.g., a disk device driver) but + that the device is still 'operational'. An example + would be a high number of soft errors on a disk. A + value of testing(4), indicates that the device is not + available for use because it is in the testing state. + The state of down(5) is used only when the agent has + been informed that the device is not available for any + use." + ::= { hrDeviceEntry 5 } + +hrDeviceErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of errors detected on this device. It + should be noted that as this object has a SYNTAX of + Counter32, that it does not have a defined initial + value. However, it is recommended that this object be + initialized to zero, even though management stations + must not depend on such an initialization." + ::= { hrDeviceEntry 6 } + +hrProcessorTable OBJECT-TYPE + SYNTAX SEQUENCE OF HrProcessorEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The (conceptual) table of processors contained by the + host. + + Note that this table is potentially sparse: a + (conceptual) entry exists only if the correspondent + value of the hrDeviceType object is + `hrDeviceProcessor'." + ::= { hrDevice 3 } + +hrProcessorEntry OBJECT-TYPE + SYNTAX HrProcessorEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A (conceptual) entry for one processor contained by + the host. The hrDeviceIndex in the index represents + the entry in the hrDeviceTable that corresponds to the + hrProcessorEntry. + + As an example of how objects in this table are named, + an instance of the hrProcessorFrwID object might be + named hrProcessorFrwID.3" + INDEX { hrDeviceIndex } + ::= { hrProcessorTable 1 } + +HrProcessorEntry ::= SEQUENCE { + hrProcessorFrwID ProductID, + hrProcessorLoad Integer32 + } + +hrProcessorFrwID OBJECT-TYPE + SYNTAX ProductID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The product ID of the firmware associated with the + processor." + ::= { hrProcessorEntry 1 } + +hrProcessorLoad OBJECT-TYPE + SYNTAX Integer32 (0..100) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The average, over the last minute, of the percentage + of time that this processor was not idle. + Implementations may approximate this one minute + smoothing period if necessary." + ::= { hrProcessorEntry 2 } + +hrNetworkTable OBJECT-TYPE + SYNTAX SEQUENCE OF HrNetworkEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The (conceptual) table of network devices contained + by the host. + + Note that this table is potentially sparse: a + (conceptual) entry exists only if the correspondent + value of the hrDeviceType object is + `hrDeviceNetwork'." + ::= { hrDevice 4 } + +hrNetworkEntry OBJECT-TYPE + SYNTAX HrNetworkEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A (conceptual) entry for one network device contained + by the host. The hrDeviceIndex in the index + represents the entry in the hrDeviceTable that + corresponds to the hrNetworkEntry. + + As an example of how objects in this table are named, + an instance of the hrNetworkIfIndex object might be + named hrNetworkIfIndex.3" + INDEX { hrDeviceIndex } + ::= { hrNetworkTable 1 } + +HrNetworkEntry ::= SEQUENCE { + hrNetworkIfIndex InterfaceIndexOrZero + } + +hrNetworkIfIndex OBJECT-TYPE + SYNTAX InterfaceIndexOrZero + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of ifIndex which corresponds to this + network device. If this device is not represented in + the ifTable, then this value shall be zero." + ::= { hrNetworkEntry 1 } + +hrPrinterTable OBJECT-TYPE + SYNTAX SEQUENCE OF HrPrinterEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The (conceptual) table of printers local to the host. + + Note that this table is potentially sparse: a + (conceptual) entry exists only if the correspondent + value of the hrDeviceType object is + `hrDevicePrinter'." + ::= { hrDevice 5 } + +hrPrinterEntry OBJECT-TYPE + SYNTAX HrPrinterEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A (conceptual) entry for one printer local to the + host. The hrDeviceIndex in the index represents the + entry in the hrDeviceTable that corresponds to the + hrPrinterEntry. + + As an example of how objects in this table are named, + an instance of the hrPrinterStatus object might be + named hrPrinterStatus.3" + INDEX { hrDeviceIndex } + ::= { hrPrinterTable 1 } + +HrPrinterEntry ::= SEQUENCE { + hrPrinterStatus INTEGER, + hrPrinterDetectedErrorState OCTET STRING + } + +hrPrinterStatus OBJECT-TYPE + SYNTAX INTEGER { + other(1), + unknown(2), + idle(3), + printing(4), + warmup(5) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current status of this printer device." + ::= { hrPrinterEntry 1 } + +hrPrinterDetectedErrorState OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object represents any error conditions detected + by the printer. The error conditions are encoded as + bits in an octet string, with the following + definitions: + + Condition Bit # + + lowPaper 0 + + noPaper 1 + lowToner 2 + noToner 3 + doorOpen 4 + jammed 5 + offline 6 + serviceRequested 7 + inputTrayMissing 8 + outputTrayMissing 9 + markerSupplyMissing 10 + outputNearFull 11 + outputFull 12 + inputTrayEmpty 13 + overduePreventMaint 14 + + Bits are numbered starting with the most significant + bit of the first byte being bit 0, the least + significant bit of the first byte being bit 7, the + most significant bit of the second byte being bit 8, + and so on. A one bit encodes that the condition was + detected, while a zero bit encodes that the condition + was not detected. + + This object is useful for alerting an operator to + specific warning or error conditions that may occur, + especially those requiring human intervention." + ::= { hrPrinterEntry 2 } + +hrDiskStorageTable OBJECT-TYPE + SYNTAX SEQUENCE OF HrDiskStorageEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The (conceptual) table of long-term storage devices + contained by the host. In particular, disk devices + accessed remotely over a network are not included + here. + + Note that this table is potentially sparse: a + (conceptual) entry exists only if the correspondent + value of the hrDeviceType object is + `hrDeviceDiskStorage'." + ::= { hrDevice 6 } + +hrDiskStorageEntry OBJECT-TYPE + SYNTAX HrDiskStorageEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A (conceptual) entry for one long-term storage device + contained by the host. The hrDeviceIndex in the index + represents the entry in the hrDeviceTable that + corresponds to the hrDiskStorageEntry. As an example, + an instance of the hrDiskStorageCapacity object might + be named hrDiskStorageCapacity.3" + INDEX { hrDeviceIndex } + ::= { hrDiskStorageTable 1 } + +HrDiskStorageEntry ::= SEQUENCE { + hrDiskStorageAccess INTEGER, + hrDiskStorageMedia INTEGER, + hrDiskStorageRemoveble TruthValue, + hrDiskStorageCapacity KBytes + } + +hrDiskStorageAccess OBJECT-TYPE + SYNTAX INTEGER { + readWrite(1), + readOnly(2) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An indication if this long-term storage device is + readable and writable or only readable. This should + reflect the media type, any write-protect mechanism, + and any device configuration that affects the entire + device." + ::= { hrDiskStorageEntry 1 } + +hrDiskStorageMedia OBJECT-TYPE + SYNTAX INTEGER { + other(1), + unknown(2), + hardDisk(3), + floppyDisk(4), + opticalDiskROM(5), + opticalDiskWORM(6), -- Write Once Read Many + opticalDiskRW(7), + ramDisk(8) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An indication of the type of media used in this long- + term storage device." + ::= { hrDiskStorageEntry 2 } + +hrDiskStorageRemoveble OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Denotes whether or not the disk media may be removed + from the drive." + ::= { hrDiskStorageEntry 3 } + +hrDiskStorageCapacity OBJECT-TYPE + SYNTAX KBytes + UNITS "KBytes" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total size for this long-term storage device. If + the media is removable and is currently removed, this + value should be zero." + ::= { hrDiskStorageEntry 4 } + +hrPartitionTable OBJECT-TYPE + SYNTAX SEQUENCE OF HrPartitionEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The (conceptual) table of partitions for long-term + storage devices contained by the host. In particular, + partitions accessed remotely over a network are not + included here." + ::= { hrDevice 7 } + +hrPartitionEntry OBJECT-TYPE + SYNTAX HrPartitionEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A (conceptual) entry for one partition. The + hrDeviceIndex in the index represents the entry in the + hrDeviceTable that corresponds to the + hrPartitionEntry. + + As an example of how objects in this table are named, + an instance of the hrPartitionSize object might be + named hrPartitionSize.3.1" + INDEX { hrDeviceIndex, hrPartitionIndex } + ::= { hrPartitionTable 1 } + +HrPartitionEntry ::= SEQUENCE { + hrPartitionIndex Integer32, + hrPartitionLabel InternationalDisplayString, + hrPartitionID OCTET STRING, + hrPartitionSize KBytes, + hrPartitionFSIndex Integer32 + } + +hrPartitionIndex OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A unique value for each partition on this long-term + storage device. The value for each long-term storage + device must remain constant at least from one re- + initialization of the agent to the next re- + initialization." + ::= { hrPartitionEntry 1 } + +hrPartitionLabel OBJECT-TYPE + SYNTAX InternationalDisplayString (SIZE (0..128)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A textual description of this partition." + ::= { hrPartitionEntry 2 } + +hrPartitionID OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A descriptor which uniquely represents this partition + to the responsible operating system. On some systems, + this might take on a binary representation." + ::= { hrPartitionEntry 3 } + +hrPartitionSize OBJECT-TYPE + SYNTAX KBytes + UNITS "KBytes" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The size of this partition." + ::= { hrPartitionEntry 4 } + +hrPartitionFSIndex OBJECT-TYPE + SYNTAX Integer32 (0..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The index of the file system mounted on this + partition. If no file system is mounted on this + partition, then this value shall be zero. Note that + multiple partitions may point to one file system, + denoting that that file system resides on those + partitions. Multiple file systems may not reside on + one partition." + ::= { hrPartitionEntry 5 } + +-- The File System Table + +-- Registration point for popular File System types, +-- for use with hrFSType. These are defined in the +-- HOST-RESOURCES-TYPES module. +hrFSTypes OBJECT IDENTIFIER ::= { hrDevice 9 } + +hrFSTable OBJECT-TYPE + SYNTAX SEQUENCE OF HrFSEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The (conceptual) table of file systems local to this + host or remotely mounted from a file server. File + systems that are in only one user's environment on a + multi-user system will not be included in this table." + ::= { hrDevice 8 } + +hrFSEntry OBJECT-TYPE + SYNTAX HrFSEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A (conceptual) entry for one file system local to + this host or remotely mounted from a file server. + File systems that are in only one user's environment + on a multi-user system will not be included in this + table. + + As an example of how objects in this table are named, + an instance of the hrFSMountPoint object might be + named hrFSMountPoint.3" + INDEX { hrFSIndex } + ::= { hrFSTable 1 } + +HrFSEntry ::= SEQUENCE { + hrFSIndex Integer32, + hrFSMountPoint InternationalDisplayString, + hrFSRemoteMountPoint InternationalDisplayString, + hrFSType AutonomousType, + hrFSAccess INTEGER, + hrFSBootable TruthValue, + hrFSStorageIndex Integer32, + hrFSLastFullBackupDate DateAndTime, + hrFSLastPartialBackupDate DateAndTime + } + +hrFSIndex OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A unique value for each file system local to this + host. The value for each file system must remain + constant at least from one re-initialization of the + agent to the next re-initialization." + ::= { hrFSEntry 1 } + +hrFSMountPoint OBJECT-TYPE + SYNTAX InternationalDisplayString (SIZE(0..128)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The path name of the root of this file system." + ::= { hrFSEntry 2 } + +hrFSRemoteMountPoint OBJECT-TYPE + SYNTAX InternationalDisplayString (SIZE(0..128)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A description of the name and/or address of the + server that this file system is mounted from. This + may also include parameters such as the mount point on + the remote file system. If this is not a remote file + system, this string should have a length of zero." + ::= { hrFSEntry 3 } + +hrFSType OBJECT-TYPE + SYNTAX AutonomousType + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of this object identifies the type of this + file system." + ::= { hrFSEntry 4 } + +hrFSAccess OBJECT-TYPE + SYNTAX INTEGER { + readWrite(1), + readOnly(2) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An indication if this file system is logically + configured by the operating system to be readable and + writable or only readable. This does not represent + any local access-control policy, except one that is + applied to the file system as a whole." + ::= { hrFSEntry 5 } + +hrFSBootable OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A flag indicating whether this file system is + bootable." + ::= { hrFSEntry 6 } + +hrFSStorageIndex OBJECT-TYPE + SYNTAX Integer32 (0..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The index of the hrStorageEntry that represents + information about this file system. If there is no + such information available, then this value shall be + zero. The relevant storage entry will be useful in + tracking the percent usage of this file system and + diagnosing errors that may occur when it runs out of + space." + ::= { hrFSEntry 7 } + +hrFSLastFullBackupDate OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The last date at which this complete file system was + + copied to another storage device for backup. This + information is useful for ensuring that backups are + being performed regularly. + + If this information is not known, then this variable + shall have the value corresponding to January 1, year + 0000, 00:00:00.0, which is encoded as + (hex)'00 00 01 01 00 00 00 00'." + ::= { hrFSEntry 8 } + +hrFSLastPartialBackupDate OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The last date at which a portion of this file system + was copied to another storage device for backup. This + information is useful for ensuring that backups are + being performed regularly. + + If this information is not known, then this variable + shall have the value corresponding to January 1, year + 0000, 00:00:00.0, which is encoded as + (hex)'00 00 01 01 00 00 00 00'." + ::= { hrFSEntry 9 } + +-- The Host Resources Running Software Group +-- +-- The hrSWRunTable contains an entry for each distinct piece of +-- software that is running or loaded into physical or virtual +-- memory in preparation for running. This includes the host's +-- operating system, device drivers, and applications. + +hrSWOSIndex OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of the hrSWRunIndex for the hrSWRunEntry + that represents the primary operating system running + on this host. This object is useful for quickly and + uniquely identifying that primary operating system." + ::= { hrSWRun 1 } + +hrSWRunTable OBJECT-TYPE + SYNTAX SEQUENCE OF HrSWRunEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The (conceptual) table of software running on the + host." + ::= { hrSWRun 2 } + +hrSWRunEntry OBJECT-TYPE + SYNTAX HrSWRunEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A (conceptual) entry for one piece of software + running on the host Note that because the installed + software table only contains information for software + stored locally on this host, not every piece of + running software will be found in the installed + software table. This is true of software that was + loaded and run from a non-local source, such as a + network-mounted file system. + + As an example of how objects in this table are named, + an instance of the hrSWRunName object might be named + hrSWRunName.1287" + INDEX { hrSWRunIndex } + ::= { hrSWRunTable 1 } + +HrSWRunEntry ::= SEQUENCE { + hrSWRunIndex Integer32, + hrSWRunName InternationalDisplayString, + hrSWRunID ProductID, + hrSWRunPath InternationalDisplayString, + hrSWRunParameters InternationalDisplayString, + hrSWRunType INTEGER, + hrSWRunStatus INTEGER + } + +hrSWRunIndex OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A unique value for each piece of software running on + the host. Wherever possible, this should be the + system's native, unique identification number." + ::= { hrSWRunEntry 1 } + +hrSWRunName OBJECT-TYPE + SYNTAX InternationalDisplayString (SIZE (0..64)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A textual description of this running piece of + software, including the manufacturer, revision, and + the name by which it is commonly known. If this + software was installed locally, this should be the + same string as used in the corresponding + hrSWInstalledName." + ::= { hrSWRunEntry 2 } + +hrSWRunID OBJECT-TYPE + SYNTAX ProductID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The product ID of this running piece of software." + ::= { hrSWRunEntry 3 } + +hrSWRunPath OBJECT-TYPE + SYNTAX InternationalDisplayString (SIZE(0..128)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A description of the location on long-term storage + (e.g. a disk drive) from which this software was + loaded." + ::= { hrSWRunEntry 4 } + +hrSWRunParameters OBJECT-TYPE + SYNTAX InternationalDisplayString (SIZE(0..128)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A description of the parameters supplied to this + software when it was initially loaded." + ::= { hrSWRunEntry 5 } + +hrSWRunType OBJECT-TYPE + SYNTAX INTEGER { + unknown(1), + operatingSystem(2), + deviceDriver(3), + application(4) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The type of this software." + ::= { hrSWRunEntry 6 } + +hrSWRunStatus OBJECT-TYPE + SYNTAX INTEGER { + running(1), + runnable(2), -- waiting for resource + -- (i.e., CPU, memory, IO) + notRunnable(3), -- loaded but waiting for event + invalid(4) -- not loaded + } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The status of this running piece of software. + Setting this value to invalid(4) shall cause this + software to stop running and to be unloaded. Sets to + other values are not valid." + ::= { hrSWRunEntry 7 } + +-- The Host Resources Running Software Performance Group +-- +-- The hrSWRunPerfTable contains an entry corresponding to +-- each entry in the hrSWRunTable. + +hrSWRunPerfTable OBJECT-TYPE + SYNTAX SEQUENCE OF HrSWRunPerfEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The (conceptual) table of running software + performance metrics." + ::= { hrSWRunPerf 1 } + +hrSWRunPerfEntry OBJECT-TYPE + SYNTAX HrSWRunPerfEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A (conceptual) entry containing software performance + metrics. As an example, an instance of the + hrSWRunPerfCPU object might be named + hrSWRunPerfCPU.1287" + AUGMENTS { hrSWRunEntry } -- This table augments information in + -- the hrSWRunTable. + ::= { hrSWRunPerfTable 1 } + +HrSWRunPerfEntry ::= SEQUENCE { + hrSWRunPerfCPU Integer32, + hrSWRunPerfMem KBytes +} + +hrSWRunPerfCPU OBJECT-TYPE + SYNTAX Integer32 (0..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of centi-seconds of the total system's CPU + resources consumed by this process. Note that on a + multi-processor system, this value may increment by + more than one centi-second in one centi-second of real + (wall clock) time." + ::= { hrSWRunPerfEntry 1 } + +hrSWRunPerfMem OBJECT-TYPE + SYNTAX KBytes + UNITS "KBytes" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total amount of real system memory allocated to + this process." + ::= { hrSWRunPerfEntry 2 } + +-- The Host Resources Installed Software Group +-- +-- The hrSWInstalledTable contains an entry for each piece +-- of software installed in long-term storage (e.g. a disk +-- drive) locally on this host. Note that this does not +-- include software loadable remotely from a network +-- server. +-- +-- Different implementations may track software in varying +-- ways. For example, while some implementations may track +-- executable files as distinct pieces of software, other +-- implementations may use other strategies such as keeping +-- track of software "packages" (e.g., related groups of files) +-- or keeping track of system or application "patches". +-- +-- This table is useful for identifying and inventorying +-- software on a host and for diagnosing incompatibility +-- and version mismatch problems between various pieces +-- of hardware and software. + +hrSWInstalledLastChange OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime when an entry in the + hrSWInstalledTable was last added, renamed, or + deleted. Because this table is likely to contain many + entries, polling of this object allows a management + station to determine when re-downloading of the table + might be useful." + ::= { hrSWInstalled 1 } + +hrSWInstalledLastUpdateTime OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime when the hrSWInstalledTable + was last completely updated. Because caching of this + data will be a popular implementation strategy, + retrieval of this object allows a management station + to obtain a guarantee that no data in this table is + older than the indicated time." + ::= { hrSWInstalled 2 } + +hrSWInstalledTable OBJECT-TYPE + SYNTAX SEQUENCE OF HrSWInstalledEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The (conceptual) table of software installed on this + host." + ::= { hrSWInstalled 3 } + +hrSWInstalledEntry OBJECT-TYPE + SYNTAX HrSWInstalledEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A (conceptual) entry for a piece of software + installed on this host. + + As an example of how objects in this table are named, + an instance of the hrSWInstalledName object might be + named hrSWInstalledName.96" + INDEX { hrSWInstalledIndex } + ::= { hrSWInstalledTable 1 } + +HrSWInstalledEntry ::= SEQUENCE { + hrSWInstalledIndex Integer32, + hrSWInstalledName InternationalDisplayString, + hrSWInstalledID ProductID, + hrSWInstalledType INTEGER, + hrSWInstalledDate DateAndTime +} + +hrSWInstalledIndex OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A unique value for each piece of software installed + on the host. This value shall be in the range from 1 + to the number of pieces of software installed on the + host." + ::= { hrSWInstalledEntry 1 } + +hrSWInstalledName OBJECT-TYPE + SYNTAX InternationalDisplayString (SIZE (0..64)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A textual description of this installed piece of + software, including the manufacturer, revision, the + name by which it is commonly known, and optionally, + its serial number." + ::= { hrSWInstalledEntry 2 } + +hrSWInstalledID OBJECT-TYPE + SYNTAX ProductID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The product ID of this installed piece of software." + ::= { hrSWInstalledEntry 3 } + +hrSWInstalledType OBJECT-TYPE + SYNTAX INTEGER { + unknown(1), + operatingSystem(2), + deviceDriver(3), + application(4) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The type of this software." + ::= { hrSWInstalledEntry 4 } + +hrSWInstalledDate OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The last-modification date of this application as it + would appear in a directory listing. + + If this information is not known, then this variable + shall have the value corresponding to January 1, year + 0000, 00:00:00.0, which is encoded as + (hex)'00 00 01 01 00 00 00 00'." + ::= { hrSWInstalledEntry 5 } + +-- Conformance information + +hrMIBCompliances OBJECT IDENTIFIER ::= { hrMIBAdminInfo 2 } +hrMIBGroups OBJECT IDENTIFIER ::= { hrMIBAdminInfo 3 } + +-- Compliance Statements +hrMIBCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The requirements for conformance to the Host Resources MIB." + MODULE -- this module + MANDATORY-GROUPS { hrSystemGroup, hrStorageGroup, + hrDeviceGroup } + + OBJECT hrSystemDate + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT hrSystemInitialLoadDevice + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT hrSystemInitialLoadParameters + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT hrStorageSize + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT hrFSLastFullBackupDate + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT hrFSLastPartialBackupDate + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + GROUP hrSWRunGroup + DESCRIPTION + "The Running Software Group. Implementation + of this group is mandatory only when the + hrSWRunPerfGroup is implemented." + + OBJECT hrSWRunStatus + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + GROUP hrSWRunPerfGroup + DESCRIPTION + "The Running Software Performance Group. + Implementation of this group is at the discretion + of the implementor." + + GROUP hrSWInstalledGroup + DESCRIPTION + "The Installed Software Group. + Implementation of this group is at the discretion + of the implementor." + ::= { hrMIBCompliances 1 } + + hrSystemGroup OBJECT-GROUP + OBJECTS { + hrSystemUptime, hrSystemDate, + hrSystemInitialLoadDevice, + hrSystemInitialLoadParameters, + hrSystemNumUsers, hrSystemProcesses, + hrSystemMaxProcesses + } + STATUS current + DESCRIPTION + "The Host Resources System Group." + ::= { hrMIBGroups 1 } + + hrStorageGroup OBJECT-GROUP + OBJECTS { + hrMemorySize, hrStorageIndex, hrStorageType, + hrStorageDescr, hrStorageAllocationUnits, + hrStorageSize, hrStorageUsed, + hrStorageAllocationFailures + } + STATUS current + DESCRIPTION + "The Host Resources Storage Group." + ::= { hrMIBGroups 2 } + + hrDeviceGroup OBJECT-GROUP + OBJECTS { + hrDeviceIndex, hrDeviceType, hrDeviceDescr, + hrDeviceID, hrDeviceStatus, hrDeviceErrors, + hrProcessorFrwID, hrProcessorLoad, + hrNetworkIfIndex, hrPrinterStatus, + hrPrinterDetectedErrorState, + hrDiskStorageAccess, hrDiskStorageMedia, + hrDiskStorageRemoveble, hrDiskStorageCapacity, + hrPartitionIndex, hrPartitionLabel, + hrPartitionID, hrPartitionSize, + hrPartitionFSIndex, hrFSIndex, hrFSMountPoint, + hrFSRemoteMountPoint, hrFSType, hrFSAccess, + hrFSBootable, hrFSStorageIndex, + hrFSLastFullBackupDate, + hrFSLastPartialBackupDate + } + STATUS current + DESCRIPTION + "The Host Resources Device Group." + ::= { hrMIBGroups 3 } + + hrSWRunGroup OBJECT-GROUP + OBJECTS { + hrSWOSIndex, hrSWRunIndex, hrSWRunName, + hrSWRunID, hrSWRunPath, hrSWRunParameters, + hrSWRunType, hrSWRunStatus + } + STATUS current + DESCRIPTION + "The Host Resources Running Software Group." + ::= { hrMIBGroups 4 } + + hrSWRunPerfGroup OBJECT-GROUP + OBJECTS { hrSWRunPerfCPU, hrSWRunPerfMem } + STATUS current + DESCRIPTION + "The Host Resources Running Software + Performance Group." + ::= { hrMIBGroups 5 } + + hrSWInstalledGroup OBJECT-GROUP + OBJECTS { + hrSWInstalledLastChange, + hrSWInstalledLastUpdateTime, + hrSWInstalledIndex, hrSWInstalledName, + hrSWInstalledID, hrSWInstalledType, + hrSWInstalledDate + } + STATUS current + DESCRIPTION + "The Host Resources Installed Software Group." + ::= { hrMIBGroups 6 } + +END diff --git a/mibs/HOST-RESOURCES-TYPES.txt b/mibs/HOST-RESOURCES-TYPES.txt new file mode 100644 index 0000000..d25bb40 --- /dev/null +++ b/mibs/HOST-RESOURCES-TYPES.txt @@ -0,0 +1,389 @@ +HOST-RESOURCES-TYPES DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-IDENTITY FROM SNMPv2-SMI + hrMIBAdminInfo, hrStorage, hrDevice FROM HOST-RESOURCES-MIB; + +hostResourcesTypesModule MODULE-IDENTITY + LAST-UPDATED "200003060000Z" -- 6 March, 2000 + ORGANIZATION "IETF Host Resources MIB Working Group" + CONTACT-INFO + "Steve Waldbusser + Postal: Lucent Technologies, Inc. + 1213 Innsbruck Dr. + Sunnyvale, CA 94089 + USA + Phone: 650-318-1251 + Fax: 650-318-1633 + Email: waldbusser@ins.com + + In addition, the Host Resources MIB mailing list is dedicated + to discussion of this MIB. To join the mailing list, send a + request message to hostmib-request@andrew.cmu.edu. The mailing + list address is hostmib@andrew.cmu.edu." + DESCRIPTION + "This MIB module registers type definitions for + storage types, device types, and file system types. + + After the initial revision, this module will be + maintained by IANA." + REVISION "200003060000Z" -- 6 March 2000 + DESCRIPTION + "The original version of this module, published as RFC + 2790." + ::= { hrMIBAdminInfo 4 } + +-- Registrations for some storage types, for use with hrStorageType +hrStorageTypes OBJECT IDENTIFIER ::= { hrStorage 1 } + +hrStorageOther OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The storage type identifier used when no other defined + type is appropriate." + ::= { hrStorageTypes 1 } + +hrStorageRam OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The storage type identifier used for RAM." + ::= { hrStorageTypes 2 } + +hrStorageVirtualMemory OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The storage type identifier used for virtual memory, + temporary storage of swapped or paged memory." + ::= { hrStorageTypes 3 } + +hrStorageFixedDisk OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The storage type identifier used for non-removable + rigid rotating magnetic storage devices." + ::= { hrStorageTypes 4 } + +hrStorageRemovableDisk OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The storage type identifier used for removable rigid + rotating magnetic storage devices." + ::= { hrStorageTypes 5 } + +hrStorageFloppyDisk OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The storage type identifier used for non-rigid rotating + magnetic storage devices." + ::= { hrStorageTypes 6 } + +hrStorageCompactDisc OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The storage type identifier used for read-only rotating + optical storage devices." + ::= { hrStorageTypes 7 } + +hrStorageRamDisk OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The storage type identifier used for a file system that + is stored in RAM." + ::= { hrStorageTypes 8 } + +hrStorageFlashMemory OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The storage type identifier used for flash memory." + ::= { hrStorageTypes 9 } + +hrStorageNetworkDisk OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The storage type identifier used for a + networked file system." + ::= { hrStorageTypes 10 } + +-- Registrations for some device types, for use with hrDeviceType +hrDeviceTypes OBJECT IDENTIFIER ::= { hrDevice 1 } + +hrDeviceOther OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used when no other defined + type is appropriate." + ::= { hrDeviceTypes 1 } + +hrDeviceUnknown OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used when the device type is + unknown." + ::= { hrDeviceTypes 2 } + +hrDeviceProcessor OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used for a CPU." + ::= { hrDeviceTypes 3 } + +hrDeviceNetwork OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used for a network interface." + ::= { hrDeviceTypes 4 } + +hrDevicePrinter OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used for a printer." + ::= { hrDeviceTypes 5 } + +hrDeviceDiskStorage OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used for a disk drive." + ::= { hrDeviceTypes 6 } + +hrDeviceVideo OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used for a video device." + ::= { hrDeviceTypes 10 } + +hrDeviceAudio OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used for an audio device." + ::= { hrDeviceTypes 11 } + +hrDeviceCoprocessor OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used for a co-processor." + ::= { hrDeviceTypes 12 } + +hrDeviceKeyboard OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used for a keyboard device." + ::= { hrDeviceTypes 13 } + +hrDeviceModem OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used for a modem." + ::= { hrDeviceTypes 14 } + +hrDeviceParallelPort OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used for a parallel port." + ::= { hrDeviceTypes 15 } + +hrDevicePointing OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used for a pointing device + (e.g., a mouse)." + ::= { hrDeviceTypes 16 } + +hrDeviceSerialPort OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used for a serial port." + ::= { hrDeviceTypes 17 } + +hrDeviceTape OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used for a tape storage device." + ::= { hrDeviceTypes 18 } + +hrDeviceClock OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used for a clock device." + ::= { hrDeviceTypes 19 } + +hrDeviceVolatileMemory OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used for a volatile memory + storage device." + ::= { hrDeviceTypes 20 } + +hrDeviceNonVolatileMemory OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The device type identifier used for a non-volatile memory + + storage device." + ::= { hrDeviceTypes 21 } + +-- Registrations for some popular File System types, +-- for use with hrFSType. +hrFSTypes OBJECT IDENTIFIER ::= { hrDevice 9 } + +hrFSOther OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used when no other + defined type is appropriate." + ::= { hrFSTypes 1 } + +hrFSUnknown OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used when the type of + file system is unknown." + ::= { hrFSTypes 2 } + +hrFSBerkeleyFFS OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + Berkeley Fast File System." + ::= { hrFSTypes 3 } + +hrFSSys5FS OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + System V File System." + ::= { hrFSTypes 4 } + +hrFSFat OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for + DOS's FAT file system." + ::= { hrFSTypes 5 } + +hrFSHPFS OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for OS/2's + High Performance File System." + ::= { hrFSTypes 6 } + +hrFSHFS OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + Macintosh Hierarchical File System." + ::= { hrFSTypes 7 } + +hrFSMFS OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + Macintosh File System." + ::= { hrFSTypes 8 } + +hrFSNTFS OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + Windows NT File System." + ::= { hrFSTypes 9 } + +hrFSVNode OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + VNode File System." + ::= { hrFSTypes 10 } + +hrFSJournaled OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + Journaled File System." + ::= { hrFSTypes 11 } + +hrFSiso9660 OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + ISO 9660 File System for CD's." + ::= { hrFSTypes 12 } + +hrFSRockRidge OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + RockRidge File System for CD's." + ::= { hrFSTypes 13 } + +hrFSNFS OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + NFS File System." + ::= { hrFSTypes 14 } + +hrFSNetware OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + Netware File System." + ::= { hrFSTypes 15 } + +hrFSAFS OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + Andrew File System." + ::= { hrFSTypes 16 } + +hrFSDFS OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + OSF DCE Distributed File System." + ::= { hrFSTypes 17 } + +hrFSAppleshare OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + AppleShare File System." + ::= { hrFSTypes 18 } + +hrFSRFS OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + RFS File System." + ::= { hrFSTypes 19 } + +hrFSDGCFS OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + Data General DGCFS." + ::= { hrFSTypes 20 } + +hrFSBFS OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + SVR4 Boot File System." + ::= { hrFSTypes 21 } + +hrFSFAT32 OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + Windows FAT32 File System." + ::= { hrFSTypes 22 } + +hrFSLinuxExt2 OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The file system type identifier used for the + Linux EXT2 File System." + ::= { hrFSTypes 23 } + +END diff --git a/mibs/IANA-ADDRESS-FAMILY-NUMBERS-MIB.txt b/mibs/IANA-ADDRESS-FAMILY-NUMBERS-MIB.txt new file mode 100644 index 0000000..7995fc4 --- /dev/null +++ b/mibs/IANA-ADDRESS-FAMILY-NUMBERS-MIB.txt @@ -0,0 +1,166 @@ + IANA-ADDRESS-FAMILY-NUMBERS-MIB DEFINITIONS ::= BEGIN + + IMPORTS + MODULE-IDENTITY, + mib-2 FROM SNMPv2-SMI + TEXTUAL-CONVENTION FROM SNMPv2-TC; + + ianaAddressFamilyNumbers MODULE-IDENTITY + LAST-UPDATED "201309250000Z" -- September 25, 2013 + ORGANIZATION "IANA" + CONTACT-INFO + "Postal: Internet Assigned Numbers Authority + Internet Corporation for Assigned Names + and Numbers + 12025 Waterfront Drive, Suite 300 + Los Angeles, CA 90094-2536 + USA + + Tel: +1 310-301-5800 + E-Mail: iana&iana.org" + DESCRIPTION + "The MIB module defines the AddressFamilyNumbers + textual convention." + + -- revision history + + REVISION "201309250000Z" -- September 25, 2013 + DESCRIPTION "Fixed labels for 16389-16390." + + REVISION "201307160000Z" -- July 16, 2013 + DESCRIPTION "Fixed labels for 16389-16390." + + REVISION "201306260000Z" -- June 26, 2013 + DESCRIPTION "Added assignments 26-28." + + REVISION "201306180000Z" -- June 18, 2013 + DESCRIPTION "Added assignments 16384-16390. Assignment + 25 added in 2007 revision." + + REVISION "200203140000Z" -- March 14, 2002 + DESCRIPTION "AddressFamilyNumbers assignment 22 to + fibreChannelWWPN. AddressFamilyNumbers + assignment 23 to fibreChannelWWNN. + AddressFamilyNumers assignment 24 to gwid." + + REVISION "200009080000Z" -- September 8, 2000 + DESCRIPTION "AddressFamilyNumbers assignment 19 to xtpOverIpv4. + AddressFamilyNumbers assignment 20 to xtpOverIpv6. + AddressFamilyNumbers assignment 21 to xtpNativeModeXTP." + + REVISION "200003010000Z" -- March 1, 2000 + DESCRIPTION "AddressFamilyNumbers assignment 17 to distinguishedName. + AddressFamilyNumbers assignment 18 to asNumber." + + REVISION "200002040000Z" -- February 4, 2000 + DESCRIPTION "AddressFamilyNumbers assignment 16 to dns." + + REVISION "9908260000Z" -- August 26, 1999 + DESCRIPTION "Initial version, published as RFC 2677." + ::= { mib-2 72 } + + AddressFamilyNumbers ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The definition of this textual convention with the + addition of newly assigned values is published + periodically by the IANA, in either the Assigned + Numbers RFC, or some derivative of it specific to + Internet Network Management number assignments. + (The latest arrangements can be obtained by + contacting the IANA.) + + The enumerations are described as: + + other(0), -- none of the following + ipV4(1), -- IP Version 4 + ipV6(2), -- IP Version 6 + nsap(3), -- NSAP + hdlc(4), -- (8-bit multidrop) + bbn1822(5), + all802(6), -- (includes all 802 media + -- plus Ethernet 'canonical format') + e163(7), + e164(8), -- (SMDS, Frame Relay, ATM) + f69(9), -- (Telex) + x121(10), -- (X.25, Frame Relay) + ipx(11), -- IPX (Internet Protocol Exchange) + appleTalk(12), -- Apple Talk + decnetIV(13), -- DEC Net Phase IV + banyanVines(14), -- Banyan Vines + e164withNsap(15), + -- (E.164 with NSAP format subaddress) + dns(16), -- (Domain Name System) + distinguishedName(17), -- (Distinguished Name, per X.500) + asNumber(18), -- (16-bit quantity, per the AS number space) + xtpOverIpv4(19), -- XTP over IP version 4 + xtpOverIpv6(20), -- XTP over IP version 6 + xtpNativeModeXTP(21), -- XTP native mode XTP + fibreChannelWWPN(22), -- Fibre Channel World-Wide Port Name + fibreChannelWWNN(23), -- Fibre Channel World-Wide Node Name + gwid(24), -- Gateway Identifier + afi(25), -- AFI for L2VPN information + mplsTpSectionEndpointIdentifier(26), -- MPLS-TP Section Endpoint Identifier + mplsTpLspEndpointIdentifier(27), -- MPLS-TP LSP Endpoint Identifier + mplsTpPseudowireEndpointIdentifier(28), -- MPLS-TP Pseudowire Endpoint Identifier + eigrpCommonServiceFamily(16384), -- EIGRP Common Service Family + eigrpIpv4ServiceFamily(16385), -- EIGRP IPv4 Service Family + eigrpIpv6ServiceFamily(16386), -- EIGRP IPv6 Service Family + lispCanonicalAddressFormat(16387), -- LISP Canonical Address Format (LCAF) + bgpLs(16388), -- BGP-LS + fortyeightBitMacBitMac(16389), -- 48-bit MAC + sixtyfourBitMac(16390), -- 64-bit MAC + oui(16391), -- OUI + mac24(16392), -- MAC/24 + mac40(16393), -- MAC/40 + ipv664(16394), -- IPv6/64 + rBridgePortID(16395), -- RBridge Port ID + reserved(65535) + + Requests for new values should be made to IANA via + email (iana&iana.org)." + SYNTAX INTEGER { + other(0), + ipV4(1), + ipV6(2), + nsap(3), + hdlc(4), + bbn1822(5), + all802(6), + e163(7), + e164(8), + f69(9), + x121(10), + ipx(11), + appleTalk(12), + decnetIV(13), + banyanVines(14), + e164withNsap(15), + dns(16), + distinguishedName(17), -- (Distinguished Name, per X.500) + asNumber(18), -- (16-bit quantity, per the AS number space) + xtpOverIpv4(19), + xtpOverIpv6(20), + xtpNativeModeXTP(21), + fibreChannelWWPN(22), + fibreChannelWWNN(23), + gwid(24), + afi(25), + mplsTpSectionEndpointIdentifier(26), + mplsTpLspEndpointIdentifier(27), + mplsTpPseudowireEndpointIdentifier(28), + eigrpCommonServiceFamily(16384), + eigrpIpv4ServiceFamily(16385), + eigrpIpv6ServiceFamily(16386), + lispCanonicalAddressFormat(16387), + bgpLs(16388), + fortyeightBitMac(16389), + sixtyfourBitMac(16390), + oui(16391), + mac24(16392), + mac40(16393), + ipv664(16394), + rBridgePortID(16395), + reserved(65535) + } + END diff --git a/mibs/IANA-LANGUAGE-MIB.txt b/mibs/IANA-LANGUAGE-MIB.txt new file mode 100644 index 0000000..4b97bdd --- /dev/null +++ b/mibs/IANA-LANGUAGE-MIB.txt @@ -0,0 +1,126 @@ +IANA-LANGUAGE-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-IDENTITY, mib-2 + FROM SNMPv2-SMI; + +ianaLanguages MODULE-IDENTITY + LAST-UPDATED "201405220000Z" -- May 22, 2014 + ORGANIZATION "IANA" + CONTACT-INFO + "Internet Assigned Numbers Authority (IANA) + + Postal: ICANN + 12025 Waterfront Drive, Suite 300 + Los Angeles, CA 90094-2536 + + Tel: +1 310-301-5800 + E-Mail: iana&iana.org" + DESCRIPTION + "The MIB module registers object identifier values for + well-known programming and scripting languages. Every + language registration MUST describe the format used + when transferring scripts written in this language. + + Any additions or changes to the contents of this MIB + module require Designated Expert Review as defined in + the Guidelines for Writing IANA Considerations Section + document. The Designated Expert will be selected by + the IESG Area Director of the OPS Area. + + Note, this module does not have to register all possible + languages since languages are identified by object + identifier values. It is therefore possible to registered + languages in private OID trees. The references given below are not + normative with regard to the language version. Other + references might be better suited to describe some newer + versions of this language. The references are only + provided as `a pointer into the right direction'." + + -- Revision log, in reverse chronological order + + REVISION "201405220000Z" -- May 22, 2014 + DESCRIPTION "Updated contact info." + + REVISION "200005100000Z" -- May 10, 2000 + DESCRIPTION "Import mib-2 instead of experimental, so that + this module compiles" + + REVISION "199909090900Z" -- September 9, 1999 + DESCRIPTION "Initial version as published at time of + publication of RFC 2591." + ::= { mib-2 73 } + +ianaLangJavaByteCode OBJECT-IDENTITY + STATUS current + DESCRIPTION + "Java byte code to be processed by a Java virtual machine. + A script written in Java byte code is transferred by using + the Java archive file format (JAR)." + REFERENCE + "The Java Virtual Machine Specification. + ISBN 0-201-63452-X" + ::= { ianaLanguages 1 } + +ianaLangTcl OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The Tool Command Language (Tcl). A script written in the + Tcl language is transferred in Tcl source code format." + REFERENCE + "Tcl and the Tk Toolkit. + ISBN 0-201-63337-X" + ::= { ianaLanguages 2 } + +ianaLangPerl OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The Perl language. A script written in the Perl language + is transferred in Perl source code format." + REFERENCE + "Programming Perl. + ISBN 1-56592-149-6" + ::= { ianaLanguages 3 } + +ianaLangScheme OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The Scheme language. A script written in the Scheme + language is transferred in Scheme source code format." + REFERENCE + "The Revised^4 Report on the Algorithmic Language Scheme. + MIT Press" + ::= { ianaLanguages 4 } + +ianaLangSRSL OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The SNMP Script Language defined by SNMP Research. A + script written in the SNMP Script Language is transferred + in the SNMP Script Language source code format." + ::= { ianaLanguages 5 } + +ianaLangPSL OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The Patrol Script Language defined by BMC Software. A script + written in the Patrol Script Language is transferred in the + Patrol Script Language source code format." + REFERENCE + "PATROL Script Language Reference Manual, Version 3.0, + November 30, 1995. BMC Software, Inc. 2101 City West Blvd., + Houston, Texas 77042." + ::= { ianaLanguages 6 } + +ianaLangSMSL OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The Systems Management Scripting Language. A script written + in the SMSL language is transferred in the SMSL source code + format." + REFERENCE + "ISO/ITU Command Sequencer. + ISO 10164-21 or ITU X.753" + ::= { ianaLanguages 7 } + +END diff --git a/mibs/IANA-RTPROTO-MIB.txt b/mibs/IANA-RTPROTO-MIB.txt new file mode 100644 index 0000000..f7bc1eb --- /dev/null +++ b/mibs/IANA-RTPROTO-MIB.txt @@ -0,0 +1,95 @@ +IANA-RTPROTO-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, mib-2 FROM SNMPv2-SMI + TEXTUAL-CONVENTION FROM SNMPv2-TC; + +ianaRtProtoMIB MODULE-IDENTITY + LAST-UPDATED "201208300000Z" -- August 30, 2012 + ORGANIZATION "IANA" + CONTACT-INFO + " Internet Assigned Numbers Authority + Internet Corporation for Assigned Names and Numbers + 12025 Waterfront Drive, Suite 300 + Los Angeles, CA 90094-2536 + + Phone: +1 310 301 5800 + EMail: iana&iana.org" + DESCRIPTION + "This MIB module defines the IANAipRouteProtocol and + IANAipMRouteProtocol textual conventions for use in MIBs + which need to identify unicast or multicast routing + mechanisms. + + Any additions or changes to the contents of this MIB module + require either publication of an RFC, or Designated Expert + Review as defined in RFC 2434, Guidelines for Writing an + IANA Considerations Section in RFCs. The Designated Expert + will be selected by the IESG Area Director(s) of the Routing + Area." + + REVISION "201208300000Z" -- August 30, 2012 + DESCRIPTION "Added dhcp(19)." + + REVISION "201107220000Z" -- July 22, 2011 + DESCRIPTION "Added rpl(18) ." + + REVISION "200009260000Z" -- September 26, 2000 + DESCRIPTION "Original version, published in coordination + with RFC 2932." + ::= { mib-2 84 } + +IANAipRouteProtocol ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "A mechanism for learning routes. Inclusion of values for + routing protocols is not intended to imply that those + protocols need be supported." + SYNTAX INTEGER { + other (1), -- not specified + local (2), -- local interface + netmgmt (3), -- static route + icmp (4), -- result of ICMP Redirect + + -- the following are all dynamic + -- routing protocols + + egp (5), -- Exterior Gateway Protocol + ggp (6), -- Gateway-Gateway Protocol + hello (7), -- FuzzBall HelloSpeak + rip (8), -- Berkeley RIP or RIP-II + isIs (9), -- Dual IS-IS + esIs (10), -- ISO 9542 + ciscoIgrp (11), -- Cisco IGRP + bbnSpfIgp (12), -- BBN SPF IGP + ospf (13), -- Open Shortest Path First + bgp (14), -- Border Gateway Protocol + idpr (15), -- InterDomain Policy Routing + ciscoEigrp (16), -- Cisco EIGRP + dvmrp (17), -- DVMRP + rpl (18), -- RPL [RFC-ietf-roll-rpl-19] + dhcp (19) -- DHCP [RFC2132] + } + +IANAipMRouteProtocol ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The multicast routing protocol. Inclusion of values for + multicast routing protocols is not intended to imply that + those protocols need be supported." + SYNTAX INTEGER { + other(1), -- none of the following + local(2), -- e.g., manually configured + netmgmt(3), -- set via net.mgmt protocol + dvmrp(4), + mospf(5), + pimSparseDense(6), -- PIMv1, both DM and SM + cbt(7), + pimSparseMode(8), -- PIM-SM + pimDenseMode(9), -- PIM-DM + igmpOnly(10), + bgmp(11), + msdp(12) + } + +END diff --git a/mibs/IANAifType-MIB.txt b/mibs/IANAifType-MIB.txt new file mode 100644 index 0000000..027a153 --- /dev/null +++ b/mibs/IANAifType-MIB.txt @@ -0,0 +1,646 @@ + IANAifType-MIB DEFINITIONS ::= BEGIN + + IMPORTS + MODULE-IDENTITY, mib-2 FROM SNMPv2-SMI + TEXTUAL-CONVENTION FROM SNMPv2-TC; + + ianaifType MODULE-IDENTITY + LAST-UPDATED "201407030000Z" -- July 3, 2014 + ORGANIZATION "IANA" + CONTACT-INFO " Internet Assigned Numbers Authority + + Postal: ICANN + 12025 Waterfront Drive, Suite 300 + Los Angeles, CA 90094-2536 + + Tel: +1 310-301-5800 + E-Mail: iana&iana.org" + DESCRIPTION "This MIB module defines the IANAifType Textual + Convention, and thus the enumerated values of + the ifType object defined in MIB-II's ifTable." + + REVISION "201407030000Z" -- July 3, 2014 + DESCRIPTION "Registration of new IANAifTypes 277-278." + + REVISION "201405220000Z" -- May 22, 2014 + DESCRIPTION "Updated contact info." + + REVISION "201205170000Z" -- May 17, 2012 + DESCRIPTION "Registration of new IANAifType 272." + + REVISION "201201110000Z" -- January 11, 2012 + DESCRIPTION "Registration of new IANAifTypes 266-271." + + REVISION "201112180000Z" -- December 18, 2011 + DESCRIPTION "Registration of new IANAifTypes 263-265." + + REVISION "201110260000Z" -- October 26, 2011 + DESCRIPTION "Registration of new IANAifType 262." + + REVISION "201109070000Z" -- September 7, 2011 + DESCRIPTION "Registration of new IANAifTypes 260 and 261." + + REVISION "201107220000Z" -- July 22, 2011 + DESCRIPTION "Registration of new IANAifType 259." + + REVISION "201106030000Z" -- June 03, 2011 + DESCRIPTION "Registration of new IANAifType 258." + + REVISION "201009210000Z" -- September 21, 2010 + DESCRIPTION "Registration of new IANAifTypes 256 and 257." + + REVISION "201007210000Z" -- July 21, 2010 + DESCRIPTION "Registration of new IANAifType 255." + + REVISION "201002110000Z" -- February 11, 2010 + DESCRIPTION "Registration of new IANAifType 254." + + REVISION "201002080000Z" -- February 08, 2010 + DESCRIPTION "Registration of new IANAifTypes 252 and 253." + + REVISION "200905060000Z" -- May 06, 2009 + DESCRIPTION "Registration of new IANAifType 251." + + REVISION "200902060000Z" -- February 06, 2009 + DESCRIPTION "Registration of new IANAtunnelType 15." + + REVISION "200810090000Z" -- October 09, 2008 + DESCRIPTION "Registration of new IANAifType 250." + + REVISION "200808120000Z" -- August 12, 2008 + DESCRIPTION "Registration of new IANAifType 249." + + REVISION "200807220000Z" -- July 22, 2008 + DESCRIPTION "Registration of new IANAifTypes 247 and 248." + + REVISION "200806240000Z" -- June 24, 2008 + DESCRIPTION "Registration of new IANAifType 246." + + REVISION "200805290000Z" -- May 29, 2008 + DESCRIPTION "Registration of new IANAifType 245." + + REVISION "200709130000Z" -- September 13, 2007 + DESCRIPTION "Registration of new IANAifTypes 243 and 244." + + REVISION "200705290000Z" -- May 29, 2007 + DESCRIPTION "Changed the description for IANAifType 228." + + REVISION "200703080000Z" -- March 08, 2007 + DESCRIPTION "Registration of new IANAifType 242." + + REVISION "200701230000Z" -- January 23, 2007 + DESCRIPTION "Registration of new IANAifTypes 239, 240, and 241." + + REVISION "200610170000Z" -- October 17, 2006 + DESCRIPTION "Deprecated/Obsoleted IANAifType 230. Registration of + IANAifType 238." + + REVISION "200609250000Z" -- September 25, 2006 + DESCRIPTION "Changed the description for IANA ifType + 184 and added new IANA ifType 237." + + REVISION "200608170000Z" -- August 17, 2006 + DESCRIPTION "Changed the descriptions for IANAifTypes + 20 and 21." + + REVISION "200608110000Z" -- August 11, 2006 + DESCRIPTION "Changed the descriptions for IANAifTypes + 7, 11, 62, 69, and 117." + + REVISION "200607250000Z" -- July 25, 2006 + DESCRIPTION "Registration of new IANA ifType 236." + + REVISION "200606140000Z" -- June 14, 2006 + DESCRIPTION "Registration of new IANA ifType 235." + + REVISION "200603310000Z" -- March 31, 2006 + DESCRIPTION "Registration of new IANA ifType 234." + + REVISION "200603300000Z" -- March 30, 2006 + DESCRIPTION "Registration of new IANA ifType 233." + + REVISION "200512220000Z" -- December 22, 2005 + DESCRIPTION "Registration of new IANA ifTypes 231 and 232." + + REVISION "200510100000Z" -- October 10, 2005 + DESCRIPTION "Registration of new IANA ifType 230." + + REVISION "200509090000Z" -- September 09, 2005 + DESCRIPTION "Registration of new IANA ifType 229." + + REVISION "200505270000Z" -- May 27, 2005 + DESCRIPTION "Registration of new IANA ifType 228." + + REVISION "200503030000Z" -- March 3, 2005 + DESCRIPTION "Added the IANAtunnelType TC and deprecated + IANAifType sixToFour (215) per RFC4087." + + REVISION "200411220000Z" -- November 22, 2004 + DESCRIPTION "Registration of new IANA ifType 227 per RFC4631." + + REVISION "200406170000Z" -- June 17, 2004 + DESCRIPTION "Registration of new IANA ifType 226." + + REVISION "200405120000Z" -- May 12, 2004 + DESCRIPTION "Added description for IANAifType 6, and + changed the descriptions for IANAifTypes + 180, 181, and 182." + + REVISION "200405070000Z" -- May 7, 2004 + DESCRIPTION "Registration of new IANAifType 225." + + REVISION "200308250000Z" -- Aug 25, 2003 + DESCRIPTION "Deprecated IANAifTypes 7 and 11. Obsoleted + IANAifTypes 62, 69, and 117. ethernetCsmacd (6) + should be used instead of these values" + + REVISION "200308180000Z" -- Aug 18, 2003 + DESCRIPTION "Registration of new IANAifType + 224." + + REVISION "200308070000Z" -- Aug 7, 2003 + DESCRIPTION "Registration of new IANAifTypes + 222 and 223." + + REVISION "200303180000Z" -- Mar 18, 2003 + DESCRIPTION "Registration of new IANAifType + 221." + + REVISION "200301130000Z" -- Jan 13, 2003 + DESCRIPTION "Registration of new IANAifType + 220." + + REVISION "200210170000Z" -- Oct 17, 2002 + DESCRIPTION "Registration of new IANAifType + 219." + + REVISION "200207160000Z" -- Jul 16, 2002 + DESCRIPTION "Registration of new IANAifTypes + 217 and 218." + + REVISION "200207100000Z" -- Jul 10, 2002 + DESCRIPTION "Registration of new IANAifTypes + 215 and 216." + + REVISION "200206190000Z" -- Jun 19, 2002 + DESCRIPTION "Registration of new IANAifType + 214." + + REVISION "200201040000Z" -- Jan 4, 2002 + DESCRIPTION "Registration of new IANAifTypes + 211, 212 and 213." + + REVISION "200112200000Z" -- Dec 20, 2001 + DESCRIPTION "Registration of new IANAifTypes + 209 and 210." + + REVISION "200111150000Z" -- Nov 15, 2001 + DESCRIPTION "Registration of new IANAifTypes + 207 and 208." + + REVISION "200111060000Z" -- Nov 6, 2001 + DESCRIPTION "Registration of new IANAifType + 206." + + REVISION "200111020000Z" -- Nov 2, 2001 + DESCRIPTION "Registration of new IANAifType + 205." + + REVISION "200110160000Z" -- Oct 16, 2001 + DESCRIPTION "Registration of new IANAifTypes + 199, 200, 201, 202, 203, and 204." + + REVISION "200109190000Z" -- Sept 19, 2001 + DESCRIPTION "Registration of new IANAifType + 198." + + REVISION "200105110000Z" -- May 11, 2001 + DESCRIPTION "Registration of new IANAifType + 197." + + REVISION "200101120000Z" -- Jan 12, 2001 + DESCRIPTION "Registration of new IANAifTypes + 195 and 196." + + REVISION "200012190000Z" -- Dec 19, 2000 + DESCRIPTION "Registration of new IANAifTypes + 193 and 194." + + REVISION "200012070000Z" -- Dec 07, 2000 + DESCRIPTION "Registration of new IANAifTypes + 191 and 192." + + REVISION "200012040000Z" -- Dec 04, 2000 + DESCRIPTION "Registration of new IANAifType + 190." + + REVISION "200010170000Z" -- Oct 17, 2000 + DESCRIPTION "Registration of new IANAifTypes + 188 and 189." + + REVISION "200010020000Z" -- Oct 02, 2000 + DESCRIPTION "Registration of new IANAifType 187." + + REVISION "200009010000Z" -- Sept 01, 2000 + DESCRIPTION "Registration of new IANAifTypes + 184, 185, and 186." + + REVISION "200008240000Z" -- Aug 24, 2000 + DESCRIPTION "Registration of new IANAifType 183." + + REVISION "200008230000Z" -- Aug 23, 2000 + DESCRIPTION "Registration of new IANAifTypes + 174-182." + + REVISION "200008220000Z" -- Aug 22, 2000 + DESCRIPTION "Registration of new IANAifTypes 170, + 171, 172 and 173." + + REVISION "200004250000Z" -- Apr 25, 2000 + DESCRIPTION "Registration of new IANAifTypes 168 and 169." + + REVISION "200003060000Z" -- Mar 6, 2000 + DESCRIPTION "Fixed a missing semi-colon in the IMPORT. + Also cleaned up the REVISION log a bit. + It is not complete, but from now on it will + be maintained and kept up to date with each + change to this MIB module." + + REVISION "199910081430Z" -- Oct 08, 1999 + DESCRIPTION "Include new name assignments up to cnr(85). + This is the first version available via the WWW + at: ftp://ftp.isi.edu/mib/ianaiftype.mib" + + REVISION "199401310000Z" -- Jan 31, 1994 + DESCRIPTION "Initial version of this MIB as published in + RFC 1573." + ::= { mib-2 30 } + + IANAifType ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "This data type is used as the syntax of the ifType + object in the (updated) definition of MIB-II's + ifTable. + + The definition of this textual convention with the + addition of newly assigned values is published + periodically by the IANA, in either the Assigned + Numbers RFC, or some derivative of it specific to + Internet Network Management number assignments. (The + latest arrangements can be obtained by contacting the + IANA.) + + Requests for new values should be made to IANA via + email (iana&iana.org). + + The relationship between the assignment of ifType + values and of OIDs to particular media-specific MIBs + is solely the purview of IANA and is subject to change + without notice. Quite often, a media-specific MIB's + OID-subtree assignment within MIB-II's 'transmission' + subtree will be the same as its ifType value. + However, in some circumstances this will not be the + case, and implementors must not pre-assume any + specific relationship between ifType values and + transmission subtree OIDs." + SYNTAX INTEGER { + other(1), -- none of the following + regular1822(2), + hdh1822(3), + ddnX25(4), + rfc877x25(5), + ethernetCsmacd(6), -- for all ethernet-like interfaces, + -- regardless of speed, as per RFC3635 + iso88023Csmacd(7), -- Deprecated via RFC3635 + -- ethernetCsmacd (6) should be used instead + iso88024TokenBus(8), + iso88025TokenRing(9), + iso88026Man(10), + starLan(11), -- Deprecated via RFC3635 + -- ethernetCsmacd (6) should be used instead + proteon10Mbit(12), + proteon80Mbit(13), + hyperchannel(14), + fddi(15), + lapb(16), + sdlc(17), + ds1(18), -- DS1-MIB + e1(19), -- Obsolete see DS1-MIB + basicISDN(20), -- no longer used + -- see also RFC2127 + primaryISDN(21), -- no longer used + -- see also RFC2127 + propPointToPointSerial(22), -- proprietary serial + ppp(23), + softwareLoopback(24), + eon(25), -- CLNP over IP + ethernet3Mbit(26), + nsip(27), -- XNS over IP + slip(28), -- generic SLIP + ultra(29), -- ULTRA technologies + ds3(30), -- DS3-MIB + sip(31), -- SMDS, coffee + frameRelay(32), -- DTE only. + rs232(33), + para(34), -- parallel-port + arcnet(35), -- arcnet + arcnetPlus(36), -- arcnet plus + atm(37), -- ATM cells + miox25(38), + sonet(39), -- SONET or SDH + x25ple(40), + iso88022llc(41), + localTalk(42), + smdsDxi(43), + frameRelayService(44), -- FRNETSERV-MIB + v35(45), + hssi(46), + hippi(47), + modem(48), -- Generic modem + aal5(49), -- AAL5 over ATM + sonetPath(50), + sonetVT(51), + smdsIcip(52), -- SMDS InterCarrier Interface + propVirtual(53), -- proprietary virtual/internal + propMultiplexor(54),-- proprietary multiplexing + ieee80212(55), -- 100BaseVG + fibreChannel(56), -- Fibre Channel + hippiInterface(57), -- HIPPI interfaces + frameRelayInterconnect(58), -- Obsolete, use either + -- frameRelay(32) or + -- frameRelayService(44). + aflane8023(59), -- ATM Emulated LAN for 802.3 + aflane8025(60), -- ATM Emulated LAN for 802.5 + cctEmul(61), -- ATM Emulated circuit + fastEther(62), -- Obsoleted via RFC3635 + -- ethernetCsmacd (6) should be used instead + isdn(63), -- ISDN and X.25 + v11(64), -- CCITT V.11/X.21 + v36(65), -- CCITT V.36 + g703at64k(66), -- CCITT G703 at 64Kbps + g703at2mb(67), -- Obsolete see DS1-MIB + qllc(68), -- SNA QLLC + fastEtherFX(69), -- Obsoleted via RFC3635 + -- ethernetCsmacd (6) should be used instead + channel(70), -- channel + ieee80211(71), -- radio spread spectrum + ibm370parChan(72), -- IBM System 360/370 OEMI Channel + escon(73), -- IBM Enterprise Systems Connection + dlsw(74), -- Data Link Switching + isdns(75), -- ISDN S/T interface + isdnu(76), -- ISDN U interface + lapd(77), -- Link Access Protocol D + ipSwitch(78), -- IP Switching Objects + rsrb(79), -- Remote Source Route Bridging + atmLogical(80), -- ATM Logical Port + ds0(81), -- Digital Signal Level 0 + ds0Bundle(82), -- group of ds0s on the same ds1 + bsc(83), -- Bisynchronous Protocol + async(84), -- Asynchronous Protocol + cnr(85), -- Combat Net Radio + iso88025Dtr(86), -- ISO 802.5r DTR + eplrs(87), -- Ext Pos Loc Report Sys + arap(88), -- Appletalk Remote Access Protocol + propCnls(89), -- Proprietary Connectionless Protocol + hostPad(90), -- CCITT-ITU X.29 PAD Protocol + termPad(91), -- CCITT-ITU X.3 PAD Facility + frameRelayMPI(92), -- Multiproto Interconnect over FR + x213(93), -- CCITT-ITU X213 + adsl(94), -- Asymmetric Digital Subscriber Loop + radsl(95), -- Rate-Adapt. Digital Subscriber Loop + sdsl(96), -- Symmetric Digital Subscriber Loop + vdsl(97), -- Very H-Speed Digital Subscrib. Loop + iso88025CRFPInt(98), -- ISO 802.5 CRFP + myrinet(99), -- Myricom Myrinet + voiceEM(100), -- voice recEive and transMit + voiceFXO(101), -- voice Foreign Exchange Office + voiceFXS(102), -- voice Foreign Exchange Station + voiceEncap(103), -- voice encapsulation + voiceOverIp(104), -- voice over IP encapsulation + atmDxi(105), -- ATM DXI + atmFuni(106), -- ATM FUNI + atmIma (107), -- ATM IMA + pppMultilinkBundle(108), -- PPP Multilink Bundle + ipOverCdlc (109), -- IBM ipOverCdlc + ipOverClaw (110), -- IBM Common Link Access to Workstn + stackToStack (111), -- IBM stackToStack + virtualIpAddress (112), -- IBM VIPA + mpc (113), -- IBM multi-protocol channel support + ipOverAtm (114), -- IBM ipOverAtm + iso88025Fiber (115), -- ISO 802.5j Fiber Token Ring + tdlc (116), -- IBM twinaxial data link control + gigabitEthernet (117), -- Obsoleted via RFC3635 + -- ethernetCsmacd (6) should be used instead + hdlc (118), -- HDLC + lapf (119), -- LAP F + v37 (120), -- V.37 + x25mlp (121), -- Multi-Link Protocol + x25huntGroup (122), -- X25 Hunt Group + transpHdlc (123), -- Transp HDLC + interleave (124), -- Interleave channel + fast (125), -- Fast channel + ip (126), -- IP (for APPN HPR in IP networks) + docsCableMaclayer (127), -- CATV Mac Layer + docsCableDownstream (128), -- CATV Downstream interface + docsCableUpstream (129), -- CATV Upstream interface + a12MppSwitch (130), -- Avalon Parallel Processor + tunnel (131), -- Encapsulation interface + coffee (132), -- coffee pot + ces (133), -- Circuit Emulation Service + atmSubInterface (134), -- ATM Sub Interface + l2vlan (135), -- Layer 2 Virtual LAN using 802.1Q + l3ipvlan (136), -- Layer 3 Virtual LAN using IP + l3ipxvlan (137), -- Layer 3 Virtual LAN using IPX + digitalPowerline (138), -- IP over Power Lines + mediaMailOverIp (139), -- Multimedia Mail over IP + dtm (140), -- Dynamic syncronous Transfer Mode + dcn (141), -- Data Communications Network + ipForward (142), -- IP Forwarding Interface + msdsl (143), -- Multi-rate Symmetric DSL + ieee1394 (144), -- IEEE1394 High Performance Serial Bus + if-gsn (145), -- HIPPI-6400 + dvbRccMacLayer (146), -- DVB-RCC MAC Layer + dvbRccDownstream (147), -- DVB-RCC Downstream Channel + dvbRccUpstream (148), -- DVB-RCC Upstream Channel + atmVirtual (149), -- ATM Virtual Interface + mplsTunnel (150), -- MPLS Tunnel Virtual Interface + srp (151), -- Spatial Reuse Protocol + voiceOverAtm (152), -- Voice Over ATM + voiceOverFrameRelay (153), -- Voice Over Frame Relay + idsl (154), -- Digital Subscriber Loop over ISDN + compositeLink (155), -- Avici Composite Link Interface + ss7SigLink (156), -- SS7 Signaling Link + propWirelessP2P (157), -- Prop. P2P wireless interface + frForward (158), -- Frame Forward Interface + rfc1483 (159), -- Multiprotocol over ATM AAL5 + usb (160), -- USB Interface + ieee8023adLag (161), -- IEEE 802.3ad Link Aggregate + bgppolicyaccounting (162), -- BGP Policy Accounting + frf16MfrBundle (163), -- FRF .16 Multilink Frame Relay + h323Gatekeeper (164), -- H323 Gatekeeper + h323Proxy (165), -- H323 Voice and Video Proxy + mpls (166), -- MPLS + mfSigLink (167), -- Multi-frequency signaling link + hdsl2 (168), -- High Bit-Rate DSL - 2nd generation + shdsl (169), -- Multirate HDSL2 + ds1FDL (170), -- Facility Data Link 4Kbps on a DS1 + pos (171), -- Packet over SONET/SDH Interface + dvbAsiIn (172), -- DVB-ASI Input + dvbAsiOut (173), -- DVB-ASI Output + plc (174), -- Power Line Communtications + nfas (175), -- Non Facility Associated Signaling + tr008 (176), -- TR008 + gr303RDT (177), -- Remote Digital Terminal + gr303IDT (178), -- Integrated Digital Terminal + isup (179), -- ISUP + propDocsWirelessMaclayer (180), -- Cisco proprietary Maclayer + propDocsWirelessDownstream (181), -- Cisco proprietary Downstream + propDocsWirelessUpstream (182), -- Cisco proprietary Upstream + hiperlan2 (183), -- HIPERLAN Type 2 Radio Interface + propBWAp2Mp (184), -- PropBroadbandWirelessAccesspt2multipt + -- use of this iftype for IEEE 802.16 WMAN + -- interfaces as per IEEE Std 802.16f is + -- deprecated and ifType 237 should be used instead. + sonetOverheadChannel (185), -- SONET Overhead Channel + digitalWrapperOverheadChannel (186), -- Digital Wrapper + aal2 (187), -- ATM adaptation layer 2 + radioMAC (188), -- MAC layer over radio links + atmRadio (189), -- ATM over radio links + imt (190), -- Inter Machine Trunks + mvl (191), -- Multiple Virtual Lines DSL + reachDSL (192), -- Long Reach DSL + frDlciEndPt (193), -- Frame Relay DLCI End Point + atmVciEndPt (194), -- ATM VCI End Point + opticalChannel (195), -- Optical Channel + opticalTransport (196), -- Optical Transport + propAtm (197), -- Proprietary ATM + voiceOverCable (198), -- Voice Over Cable Interface + infiniband (199), -- Infiniband + teLink (200), -- TE Link + q2931 (201), -- Q.2931 + virtualTg (202), -- Virtual Trunk Group + sipTg (203), -- SIP Trunk Group + sipSig (204), -- SIP Signaling + docsCableUpstreamChannel (205), -- CATV Upstream Channel + econet (206), -- Acorn Econet + pon155 (207), -- FSAN 155Mb Symetrical PON interface + pon622 (208), -- FSAN622Mb Symetrical PON interface + bridge (209), -- Transparent bridge interface + linegroup (210), -- Interface common to multiple lines + voiceEMFGD (211), -- voice E&M Feature Group D + voiceFGDEANA (212), -- voice FGD Exchange Access North American + voiceDID (213), -- voice Direct Inward Dialing + mpegTransport (214), -- MPEG transport interface + sixToFour (215), -- 6to4 interface (DEPRECATED) + gtp (216), -- GTP (GPRS Tunneling Protocol) + pdnEtherLoop1 (217), -- Paradyne EtherLoop 1 + pdnEtherLoop2 (218), -- Paradyne EtherLoop 2 + opticalChannelGroup (219), -- Optical Channel Group + homepna (220), -- HomePNA ITU-T G.989 + gfp (221), -- Generic Framing Procedure (GFP) + ciscoISLvlan (222), -- Layer 2 Virtual LAN using Cisco ISL + actelisMetaLOOP (223), -- Acteleis proprietary MetaLOOP High Speed Link + fcipLink (224), -- FCIP Link + rpr (225), -- Resilient Packet Ring Interface Type + qam (226), -- RF Qam Interface + lmp (227), -- Link Management Protocol + cblVectaStar (228), -- Cambridge Broadband Networks Limited VectaStar + docsCableMCmtsDownstream (229), -- CATV Modular CMTS Downstream Interface + adsl2 (230), -- Asymmetric Digital Subscriber Loop Version 2 + -- (DEPRECATED/OBSOLETED - please use adsl2plus 238 instead) + macSecControlledIF (231), -- MACSecControlled + macSecUncontrolledIF (232), -- MACSecUncontrolled + aviciOpticalEther (233), -- Avici Optical Ethernet Aggregate + atmbond (234), -- atmbond + voiceFGDOS (235), -- voice FGD Operator Services + mocaVersion1 (236), -- MultiMedia over Coax Alliance (MoCA) Interface + -- as documented in information provided privately to IANA + ieee80216WMAN (237), -- IEEE 802.16 WMAN interface + adsl2plus (238), -- Asymmetric Digital Subscriber Loop Version 2, + -- Version 2 Plus and all variants + dvbRcsMacLayer (239), -- DVB-RCS MAC Layer + dvbTdm (240), -- DVB Satellite TDM + dvbRcsTdma (241), -- DVB-RCS TDMA + x86Laps (242), -- LAPS based on ITU-T X.86/Y.1323 + wwanPP (243), -- 3GPP WWAN + wwanPP2 (244), -- 3GPP2 WWAN + voiceEBS (245), -- voice P-phone EBS physical interface + ifPwType (246), -- Pseudowire interface type + ilan (247), -- Internal LAN on a bridge per IEEE 802.1ap + pip (248), -- Provider Instance Port on a bridge per IEEE 802.1ah PBB + aluELP (249), -- Alcatel-Lucent Ethernet Link Protection + gpon (250), -- Gigabit-capable passive optical networks (G-PON) as per ITU-T G.948 + vdsl2 (251), -- Very high speed digital subscriber line Version 2 (as per ITU-T Recommendation G.993.2) + capwapDot11Profile (252), -- WLAN Profile Interface + capwapDot11Bss (253), -- WLAN BSS Interface + capwapWtpVirtualRadio (254), -- WTP Virtual Radio Interface + bits (255), -- bitsport + docsCableUpstreamRfPort (256), -- DOCSIS CATV Upstream RF Port + cableDownstreamRfPort (257), -- CATV downstream RF port + vmwareVirtualNic (258), -- VMware Virtual Network Interface + ieee802154 (259), -- IEEE 802.15.4 WPAN interface + otnOdu (260), -- OTN Optical Data Unit + otnOtu (261), -- OTN Optical channel Transport Unit + ifVfiType (262), -- VPLS Forwarding Instance Interface Type + g9981 (263), -- G.998.1 bonded interface + g9982 (264), -- G.998.2 bonded interface + g9983 (265), -- G.998.3 bonded interface + aluEpon (266), -- Ethernet Passive Optical Networks (E-PON) + aluEponOnu (267), -- EPON Optical Network Unit + aluEponPhysicalUni (268), -- EPON physical User to Network interface + aluEponLogicalLink (269), -- The emulation of a point-to-point link over the EPON layer + aluGponOnu (270), -- GPON Optical Network Unit + aluGponPhysicalUni (271), -- GPON physical User to Network interface + vmwareNicTeam (272), -- VMware NIC Team + docsOfdmDownstream (277), -- CATV Downstream OFDM interface + docsOfdmaUpstream (278) -- CATV Upstream OFDMA interface + } + +IANAtunnelType ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The encapsulation method used by a tunnel. The value + direct indicates that a packet is encapsulated + directly within a normal IP header, with no + intermediate header, and unicast to the remote tunnel + endpoint (e.g., an RFC 2003 IP-in-IP tunnel, or an RFC + 1933 IPv6-in-IPv4 tunnel). The value minimal indicates + that a Minimal Forwarding Header (RFC 2004) is + inserted between the outer header and the payload + packet. The value UDP indicates that the payload + packet is encapsulated within a normal UDP packet + (e.g., RFC 1234). + + The values sixToFour, sixOverFour, and isatap + indicates that an IPv6 packet is encapsulated directly + within an IPv4 header, with no intermediate header, + and unicast to the destination determined by the 6to4, + 6over4, or ISATAP protocol. + + The remaining protocol-specific values indicate that a + header of the protocol of that name is inserted + between the outer header and the payload header. + + The assignment policy for IANAtunnelType values is + identical to the policy for assigning IANAifType + values." + SYNTAX INTEGER { + other(1), -- none of the following + direct(2), -- no intermediate header + gre(3), -- GRE encapsulation + minimal(4), -- Minimal encapsulation + l2tp(5), -- L2TP encapsulation + pptp(6), -- PPTP encapsulation + l2f(7), -- L2F encapsulation + udp(8), -- UDP encapsulation + atmp(9), -- ATMP encapsulation + msdp(10), -- MSDP encapsulation + sixToFour(11), -- 6to4 encapsulation + sixOverFour(12), -- 6over4 encapsulation + isatap(13), -- ISATAP encapsulation + teredo(14), -- Teredo encapsulation + ipHttps(15) -- IPHTTPS + } + + END diff --git a/mibs/IF-INVERTED-STACK-MIB.txt b/mibs/IF-INVERTED-STACK-MIB.txt new file mode 100644 index 0000000..eb8797b --- /dev/null +++ b/mibs/IF-INVERTED-STACK-MIB.txt @@ -0,0 +1,149 @@ +IF-INVERTED-STACK-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, mib-2 FROM SNMPv2-SMI + RowStatus FROM SNMPv2-TC + MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF + ifStackGroup2, + ifStackHigherLayer, ifStackLowerLayer FROM IF-MIB; + +ifInvertedStackMIB MODULE-IDENTITY + LAST-UPDATED "200006140000Z" + ORGANIZATION "IETF Interfaces MIB Working Group" + CONTACT-INFO + " Keith McCloghrie + Cisco Systems, Inc. + 170 West Tasman Drive + San Jose, CA 95134-1706 + US + + 408-526-5260 + kzm@cisco.com" + DESCRIPTION + "The MIB module which provides the Inverted Stack Table for + interface sub-layers." + REVISION "200006140000Z" + DESCRIPTION + "Initial revision, published as RFC 2864" + ::= { mib-2 77 } + +ifInvMIBObjects OBJECT IDENTIFIER ::= { ifInvertedStackMIB 1 } + +-- +-- The Inverted Interface Stack Group +-- + +ifInvStackTable OBJECT-TYPE + SYNTAX SEQUENCE OF IfInvStackEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table containing information on the relationships between + + the multiple sub-layers of network interfaces. In + particular, it contains information on which sub-layers run + 'underneath' which other sub-layers, where each sub-layer + corresponds to a conceptual row in the ifTable. For + example, when the sub-layer with ifIndex value x runs + underneath the sub-layer with ifIndex value y, then this + table contains: + + ifInvStackStatus.x.y=active + + For each ifIndex value, z, which identifies an active + interface, there are always at least two instantiated rows + in this table associated with z. For one of these rows, z + is the value of ifStackHigherLayer; for the other, z is the + value of ifStackLowerLayer. (If z is not involved in + multiplexing, then these are the only two rows associated + with z.) + + For example, two rows exist even for an interface which has + no others stacked on top or below it: + + ifInvStackStatus.z.0=active + ifInvStackStatus.0.z=active + + This table contains exactly the same number of rows as the + ifStackTable, but the rows appear in a different order." + REFERENCE + "ifStackTable of RFC 2863" + ::= { ifInvMIBObjects 1 } + +ifInvStackEntry OBJECT-TYPE + SYNTAX IfInvStackEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information on a particular relationship between two sub- + layers, specifying that one sub-layer runs underneath the + other sub-layer. Each sub-layer corresponds to a conceptual + row in the ifTable." + INDEX { ifStackLowerLayer, ifStackHigherLayer } + ::= { ifInvStackTable 1 } + +IfInvStackEntry ::= + SEQUENCE { + ifInvStackStatus RowStatus + } + +ifInvStackStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The status of the relationship between two sub-layers. + + An instance of this object exists for each instance of the + ifStackStatus object, and vice versa. For example, if the + variable ifStackStatus.H.L exists, then the variable + ifInvStackStatus.L.H must also exist, and vice versa. In + addition, the two variables always have the same value. + + However, unlike ifStackStatus, the ifInvStackStatus object + is NOT write-able. A network management application wishing + to change a relationship between sub-layers H and L cannot + do so by modifying the value of ifInvStackStatus.L.H, but + must instead modify the value of ifStackStatus.H.L. After + the ifStackTable is modified, the change will be reflected + in this table." + ::= { ifInvStackEntry 1 } + +-- conformance information + +ifInvConformance OBJECT IDENTIFIER ::= { ifInvMIBObjects 2 } + +ifInvGroups OBJECT IDENTIFIER ::= { ifInvConformance 1 } +ifInvCompliances OBJECT IDENTIFIER ::= { ifInvConformance 2 } + +-- compliance statements + +ifInvCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP entities which provide + inverted information on the layering of network interfaces." + + MODULE -- this module + MANDATORY-GROUPS { ifInvStackGroup } + + OBJECT ifInvStackStatus + SYNTAX INTEGER { active(1) } + DESCRIPTION + "Support is only required for 'active'." + + MODULE IF-MIB + MANDATORY-GROUPS { ifStackGroup2 } + ::= { ifInvCompliances 1 } + +-- units of conformance + +ifInvStackGroup OBJECT-GROUP + OBJECTS { ifInvStackStatus } + STATUS current + DESCRIPTION + "A collection of objects providing inverted information on + the layering of MIB-II interfaces." + ::= { ifInvGroups 1 } + +END diff --git a/mibs/IF-MIB.txt b/mibs/IF-MIB.txt new file mode 100644 index 0000000..7704f0c --- /dev/null +++ b/mibs/IF-MIB.txt @@ -0,0 +1,1814 @@ +IF-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, Counter32, Gauge32, Counter64, + Integer32, TimeTicks, mib-2, + NOTIFICATION-TYPE FROM SNMPv2-SMI + TEXTUAL-CONVENTION, DisplayString, + PhysAddress, TruthValue, RowStatus, + TimeStamp, AutonomousType, TestAndIncr FROM SNMPv2-TC + MODULE-COMPLIANCE, OBJECT-GROUP, + NOTIFICATION-GROUP FROM SNMPv2-CONF + snmpTraps FROM SNMPv2-MIB + IANAifType FROM IANAifType-MIB; + +ifMIB MODULE-IDENTITY + LAST-UPDATED "200006140000Z" + ORGANIZATION "IETF Interfaces MIB Working Group" + CONTACT-INFO + " Keith McCloghrie + Cisco Systems, Inc. + 170 West Tasman Drive + San Jose, CA 95134-1706 + US + + 408-526-5260 + kzm@cisco.com" + DESCRIPTION + "The MIB module to describe generic objects for network + interface sub-layers. This MIB is an updated version of + MIB-II's ifTable, and incorporates the extensions defined in + RFC 1229." + + REVISION "200006140000Z" + DESCRIPTION + "Clarifications agreed upon by the Interfaces MIB WG, and + published as RFC 2863." + REVISION "199602282155Z" + DESCRIPTION + "Revisions made by the Interfaces MIB WG, and published in + RFC 2233." + REVISION "199311082155Z" + DESCRIPTION + "Initial revision, published as part of RFC 1573." + ::= { mib-2 31 } + +ifMIBObjects OBJECT IDENTIFIER ::= { ifMIB 1 } + +interfaces OBJECT IDENTIFIER ::= { mib-2 2 } + +-- +-- Textual Conventions +-- + +-- OwnerString has the same semantics as used in RFC 1271 + +OwnerString ::= TEXTUAL-CONVENTION + DISPLAY-HINT "255a" + STATUS deprecated + DESCRIPTION + "This data type is used to model an administratively + assigned name of the owner of a resource. This information + is taken from the NVT ASCII character set. It is suggested + that this name contain one or more of the following: ASCII + form of the manager station's transport address, management + station name (e.g., domain name), network management + personnel's name, location, or phone number. In some cases + the agent itself will be the owner of an entry. In these + cases, this string shall be set to a string starting with + 'agent'." + SYNTAX OCTET STRING (SIZE(0..255)) + +-- InterfaceIndex contains the semantics of ifIndex and should be used +-- for any objects defined in other MIB modules that need these semantics. + +InterfaceIndex ::= TEXTUAL-CONVENTION + DISPLAY-HINT "d" + STATUS current + DESCRIPTION + "A unique value, greater than zero, for each interface or + interface sub-layer in the managed system. It is + recommended that values are assigned contiguously starting + from 1. The value for each interface sub-layer must remain + constant at least from one re-initialization of the entity's + network management system to the next re-initialization." + SYNTAX Integer32 (1..2147483647) + +InterfaceIndexOrZero ::= TEXTUAL-CONVENTION + DISPLAY-HINT "d" + STATUS current + DESCRIPTION + "This textual convention is an extension of the + InterfaceIndex convention. The latter defines a greater + than zero value used to identify an interface or interface + sub-layer in the managed system. This extension permits the + additional value of zero. the value zero is object-specific + and must therefore be defined as part of the description of + any object which uses this syntax. Examples of the usage of + zero might include situations where interface was unknown, + or when none or all interfaces need to be referenced." + SYNTAX Integer32 (0..2147483647) + +ifNumber OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of network interfaces (regardless of their + current state) present on this system." + ::= { interfaces 1 } + +ifTableLastChange OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time of the last creation or + deletion of an entry in the ifTable. If the number of + entries has been unchanged since the last re-initialization + of the local network management subsystem, then this object + contains a zero value." + ::= { ifMIBObjects 5 } + +-- the Interfaces table + +-- The Interfaces table contains information on the entity's + +-- interfaces. Each sub-layer below the internetwork-layer +-- of a network interface is considered to be an interface. + +ifTable OBJECT-TYPE + SYNTAX SEQUENCE OF IfEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of interface entries. The number of entries is + given by the value of ifNumber." + ::= { interfaces 2 } + +ifEntry OBJECT-TYPE + SYNTAX IfEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry containing management information applicable to a + particular interface." + INDEX { ifIndex } + ::= { ifTable 1 } + +IfEntry ::= + SEQUENCE { + ifIndex InterfaceIndex, + ifDescr DisplayString, + ifType IANAifType, + ifMtu Integer32, + ifSpeed Gauge32, + ifPhysAddress PhysAddress, + ifAdminStatus INTEGER, + ifOperStatus INTEGER, + ifLastChange TimeTicks, + ifInOctets Counter32, + ifInUcastPkts Counter32, + ifInNUcastPkts Counter32, -- deprecated + ifInDiscards Counter32, + ifInErrors Counter32, + ifInUnknownProtos Counter32, + ifOutOctets Counter32, + ifOutUcastPkts Counter32, + ifOutNUcastPkts Counter32, -- deprecated + ifOutDiscards Counter32, + ifOutErrors Counter32, + ifOutQLen Gauge32, -- deprecated + ifSpecific OBJECT IDENTIFIER -- deprecated + } + +ifIndex OBJECT-TYPE + SYNTAX InterfaceIndex + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A unique value, greater than zero, for each interface. It + is recommended that values are assigned contiguously + starting from 1. The value for each interface sub-layer + must remain constant at least from one re-initialization of + the entity's network management system to the next re- + initialization." + ::= { ifEntry 1 } + +ifDescr OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A textual string containing information about the + interface. This string should include the name of the + manufacturer, the product name and the version of the + interface hardware/software." + ::= { ifEntry 2 } + +ifType OBJECT-TYPE + SYNTAX IANAifType + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The type of interface. Additional values for ifType are + assigned by the Internet Assigned Numbers Authority (IANA), + through updating the syntax of the IANAifType textual + convention." + ::= { ifEntry 3 } + +ifMtu OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The size of the largest packet which can be sent/received + on the interface, specified in octets. For interfaces that + are used for transmitting network datagrams, this is the + size of the largest network datagram that can be sent on the + interface." + ::= { ifEntry 4 } + +ifSpeed OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An estimate of the interface's current bandwidth in bits + per second. For interfaces which do not vary in bandwidth + or for those where no accurate estimation can be made, this + object should contain the nominal bandwidth. If the + bandwidth of the interface is greater than the maximum value + reportable by this object then this object should report its + maximum value (4,294,967,295) and ifHighSpeed must be used + to report the interace's speed. For a sub-layer which has + no concept of bandwidth, this object should be zero." + ::= { ifEntry 5 } + +ifPhysAddress OBJECT-TYPE + SYNTAX PhysAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The interface's address at its protocol sub-layer. For + example, for an 802.x interface, this object normally + contains a MAC address. The interface's media-specific MIB + must define the bit and byte ordering and the format of the + value of this object. For interfaces which do not have such + an address (e.g., a serial line), this object should contain + an octet string of zero length." + ::= { ifEntry 6 } + +ifAdminStatus OBJECT-TYPE + SYNTAX INTEGER { + up(1), -- ready to pass packets + down(2), + testing(3) -- in some test mode + } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The desired state of the interface. The testing(3) state + indicates that no operational packets can be passed. When a + managed system initializes, all interfaces start with + ifAdminStatus in the down(2) state. As a result of either + explicit management action or per configuration information + retained by the managed system, ifAdminStatus is then + changed to either the up(1) or testing(3) states (or remains + in the down(2) state)." + ::= { ifEntry 7 } + +ifOperStatus OBJECT-TYPE + SYNTAX INTEGER { + up(1), -- ready to pass packets + down(2), + testing(3), -- in some test mode + unknown(4), -- status can not be determined + -- for some reason. + dormant(5), + notPresent(6), -- some component is missing + lowerLayerDown(7) -- down due to state of + -- lower-layer interface(s) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current operational state of the interface. The + testing(3) state indicates that no operational packets can + be passed. If ifAdminStatus is down(2) then ifOperStatus + should be down(2). If ifAdminStatus is changed to up(1) + then ifOperStatus should change to up(1) if the interface is + ready to transmit and receive network traffic; it should + change to dormant(5) if the interface is waiting for + external actions (such as a serial line waiting for an + incoming connection); it should remain in the down(2) state + if and only if there is a fault that prevents it from going + to the up(1) state; it should remain in the notPresent(6) + state if the interface has missing (typically, hardware) + components." + ::= { ifEntry 8 } + +ifLastChange OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time the interface entered + its current operational state. If the current state was + entered prior to the last re-initialization of the local + network management subsystem, then this object contains a + zero value." + ::= { ifEntry 9 } + +ifInOctets OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets received on the interface, + including framing characters. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifEntry 10 } + +ifInUcastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, which were not addressed to a multicast + or broadcast address at this sub-layer. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifEntry 11 } + +ifInNUcastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, which were addressed to a multicast or + broadcast address at this sub-layer. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime. + + This object is deprecated in favour of ifInMulticastPkts and + ifInBroadcastPkts." + ::= { ifEntry 12 } + +ifInDiscards OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of inbound packets which were chosen to be + discarded even though no errors had been detected to prevent + + their being deliverable to a higher-layer protocol. One + possible reason for discarding such a packet could be to + free up buffer space. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifEntry 13 } + +ifInErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "For packet-oriented interfaces, the number of inbound + packets that contained errors preventing them from being + deliverable to a higher-layer protocol. For character- + oriented or fixed-length interfaces, the number of inbound + transmission units that contained errors preventing them + from being deliverable to a higher-layer protocol. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifEntry 14 } + +ifInUnknownProtos OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "For packet-oriented interfaces, the number of packets + received via the interface which were discarded because of + an unknown or unsupported protocol. For character-oriented + or fixed-length interfaces that support protocol + multiplexing the number of transmission units received via + the interface which were discarded because of an unknown or + unsupported protocol. For any interface that does not + support protocol multiplexing, this counter will always be + 0. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifEntry 15 } + +ifOutOctets OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets transmitted out of the + interface, including framing characters. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifEntry 16 } + +ifOutUcastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets that higher-level protocols + requested be transmitted, and which were not addressed to a + multicast or broadcast address at this sub-layer, including + those that were discarded or not sent. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifEntry 17 } + +ifOutNUcastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The total number of packets that higher-level protocols + requested be transmitted, and which were addressed to a + multicast or broadcast address at this sub-layer, including + those that were discarded or not sent. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime. + + This object is deprecated in favour of ifOutMulticastPkts + and ifOutBroadcastPkts." + ::= { ifEntry 18 } + +ifOutDiscards OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of outbound packets which were chosen to be + discarded even though no errors had been detected to prevent + their being transmitted. One possible reason for discarding + such a packet could be to free up buffer space. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifEntry 19 } + +ifOutErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "For packet-oriented interfaces, the number of outbound + packets that could not be transmitted because of errors. + For character-oriented or fixed-length interfaces, the + number of outbound transmission units that could not be + transmitted because of errors. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifEntry 20 } + +ifOutQLen OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The length of the output packet queue (in packets)." + ::= { ifEntry 21 } + +ifSpecific OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "A reference to MIB definitions specific to the particular + media being used to realize the interface. It is + + recommended that this value point to an instance of a MIB + object in the media-specific MIB, i.e., that this object + have the semantics associated with the InstancePointer + textual convention defined in RFC 2579. In fact, it is + recommended that the media-specific MIB specify what value + ifSpecific should/can take for values of ifType. If no MIB + definitions specific to the particular media are available, + the value should be set to the OBJECT IDENTIFIER { 0 0 }." + ::= { ifEntry 22 } + +-- +-- Extension to the interface table +-- +-- This table replaces the ifExtnsTable table. +-- + +ifXTable OBJECT-TYPE + SYNTAX SEQUENCE OF IfXEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of interface entries. The number of entries is + given by the value of ifNumber. This table contains + additional objects for the interface table." + ::= { ifMIBObjects 1 } + +ifXEntry OBJECT-TYPE + SYNTAX IfXEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry containing additional management information + applicable to a particular interface." + AUGMENTS { ifEntry } + ::= { ifXTable 1 } + +IfXEntry ::= + SEQUENCE { + ifName DisplayString, + ifInMulticastPkts Counter32, + ifInBroadcastPkts Counter32, + ifOutMulticastPkts Counter32, + ifOutBroadcastPkts Counter32, + ifHCInOctets Counter64, + ifHCInUcastPkts Counter64, + ifHCInMulticastPkts Counter64, + ifHCInBroadcastPkts Counter64, + ifHCOutOctets Counter64, + ifHCOutUcastPkts Counter64, + ifHCOutMulticastPkts Counter64, + ifHCOutBroadcastPkts Counter64, + ifLinkUpDownTrapEnable INTEGER, + ifHighSpeed Gauge32, + ifPromiscuousMode TruthValue, + ifConnectorPresent TruthValue, + ifAlias DisplayString, + ifCounterDiscontinuityTime TimeStamp + } + +ifName OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The textual name of the interface. The value of this + object should be the name of the interface as assigned by + the local device and should be suitable for use in commands + entered at the device's `console'. This might be a text + name, such as `le0' or a simple port number, such as `1', + depending on the interface naming syntax of the device. If + several entries in the ifTable together represent a single + interface as named by the device, then each will have the + same value of ifName. Note that for an agent which responds + to SNMP queries concerning an interface on some other + (proxied) device, then the value of ifName for such an + interface is the proxied device's local name for it. + + If there is no local name, or this object is otherwise not + applicable, then this object contains a zero-length string." + ::= { ifXEntry 1 } + +ifInMulticastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, which were addressed to a multicast + address at this sub-layer. For a MAC layer protocol, this + includes both Group and Functional addresses. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifXEntry 2 } + +ifInBroadcastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, which were addressed to a broadcast + address at this sub-layer. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifXEntry 3 } + +ifOutMulticastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets that higher-level protocols + requested be transmitted, and which were addressed to a + multicast address at this sub-layer, including those that + were discarded or not sent. For a MAC layer protocol, this + includes both Group and Functional addresses. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifXEntry 4 } + +ifOutBroadcastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets that higher-level protocols + requested be transmitted, and which were addressed to a + broadcast address at this sub-layer, including those that + were discarded or not sent. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifXEntry 5 } + +-- +-- High Capacity Counter objects. These objects are all +-- 64 bit versions of the "basic" ifTable counters. These +-- objects all have the same basic semantics as their 32-bit +-- counterparts, however, their syntax has been extended +-- to 64 bits. +-- + +ifHCInOctets OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets received on the interface, + including framing characters. This object is a 64-bit + version of ifInOctets. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifXEntry 6 } + +ifHCInUcastPkts OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, which were not addressed to a multicast + or broadcast address at this sub-layer. This object is a + 64-bit version of ifInUcastPkts. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifXEntry 7 } + +ifHCInMulticastPkts OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, which were addressed to a multicast + address at this sub-layer. For a MAC layer protocol, this + includes both Group and Functional addresses. This object + is a 64-bit version of ifInMulticastPkts. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifXEntry 8 } + +ifHCInBroadcastPkts OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, which were addressed to a broadcast + address at this sub-layer. This object is a 64-bit version + of ifInBroadcastPkts. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifXEntry 9 } + +ifHCOutOctets OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets transmitted out of the + interface, including framing characters. This object is a + 64-bit version of ifOutOctets. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifXEntry 10 } + +ifHCOutUcastPkts OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets that higher-level protocols + requested be transmitted, and which were not addressed to a + multicast or broadcast address at this sub-layer, including + those that were discarded or not sent. This object is a + 64-bit version of ifOutUcastPkts. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifXEntry 11 } + +ifHCOutMulticastPkts OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets that higher-level protocols + requested be transmitted, and which were addressed to a + multicast address at this sub-layer, including those that + were discarded or not sent. For a MAC layer protocol, this + includes both Group and Functional addresses. This object + is a 64-bit version of ifOutMulticastPkts. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifXEntry 12 } + +ifHCOutBroadcastPkts OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets that higher-level protocols + requested be transmitted, and which were addressed to a + broadcast address at this sub-layer, including those that + were discarded or not sent. This object is a 64-bit version + of ifOutBroadcastPkts. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ifCounterDiscontinuityTime." + ::= { ifXEntry 13 } + +ifLinkUpDownTrapEnable OBJECT-TYPE + SYNTAX INTEGER { enabled(1), disabled(2) } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "Indicates whether linkUp/linkDown traps should be generated + for this interface. + + By default, this object should have the value enabled(1) for + interfaces which do not operate on 'top' of any other + interface (as defined in the ifStackTable), and disabled(2) + otherwise." + ::= { ifXEntry 14 } + +ifHighSpeed OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An estimate of the interface's current bandwidth in units + of 1,000,000 bits per second. If this object reports a + value of `n' then the speed of the interface is somewhere in + the range of `n-500,000' to `n+499,999'. For interfaces + which do not vary in bandwidth or for those where no + accurate estimation can be made, this object should contain + the nominal bandwidth. For a sub-layer which has no concept + of bandwidth, this object should be zero." + ::= { ifXEntry 15 } + +ifPromiscuousMode OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "This object has a value of false(2) if this interface only + accepts packets/frames that are addressed to this station. + This object has a value of true(1) when the station accepts + all packets/frames transmitted on the media. The value + true(1) is only legal on certain types of media. If legal, + setting this object to a value of true(1) may require the + interface to be reset before becoming effective. + + The value of ifPromiscuousMode does not affect the reception + of broadcast and multicast packets/frames by the interface." + ::= { ifXEntry 16 } + +ifConnectorPresent OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object has the value 'true(1)' if the interface + sublayer has a physical connector and the value 'false(2)' + otherwise." + ::= { ifXEntry 17 } + +ifAlias OBJECT-TYPE + SYNTAX DisplayString (SIZE(0..64)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "This object is an 'alias' name for the interface as + specified by a network manager, and provides a non-volatile + 'handle' for the interface. + + On the first instantiation of an interface, the value of + ifAlias associated with that interface is the zero-length + string. As and when a value is written into an instance of + ifAlias through a network management set operation, then the + agent must retain the supplied value in the ifAlias instance + associated with the same interface for as long as that + interface remains instantiated, including across all re- + initializations/reboots of the network management system, + including those which result in a change of the interface's + ifIndex value. + + An example of the value which a network manager might store + in this object for a WAN interface is the (Telco's) circuit + number/identifier of the interface. + + Some agents may support write-access only for interfaces + having particular values of ifType. An agent which supports + write access to this object is required to keep the value in + non-volatile storage, but it may limit the length of new + values depending on how much storage is already occupied by + the current values for other interfaces." + ::= { ifXEntry 18 } + +ifCounterDiscontinuityTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime on the most recent occasion at which + any one or more of this interface's counters suffered a + discontinuity. The relevant counters are the specific + instances associated with this interface of any Counter32 or + + Counter64 object contained in the ifTable or ifXTable. If + no such discontinuities have occurred since the last re- + initialization of the local management subsystem, then this + object contains a zero value." + ::= { ifXEntry 19 } + +-- The Interface Stack Group +-- +-- Implementation of this group is optional, but strongly recommended +-- for all systems +-- + +ifStackTable OBJECT-TYPE + SYNTAX SEQUENCE OF IfStackEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table containing information on the relationships + between the multiple sub-layers of network interfaces. In + particular, it contains information on which sub-layers run + 'on top of' which other sub-layers, where each sub-layer + corresponds to a conceptual row in the ifTable. For + example, when the sub-layer with ifIndex value x runs over + the sub-layer with ifIndex value y, then this table + contains: + + ifStackStatus.x.y=active + + For each ifIndex value, I, which identifies an active + interface, there are always at least two instantiated rows + in this table associated with I. For one of these rows, I + is the value of ifStackHigherLayer; for the other, I is the + value of ifStackLowerLayer. (If I is not involved in + multiplexing, then these are the only two rows associated + with I.) + + For example, two rows exist even for an interface which has + no others stacked on top or below it: + + ifStackStatus.0.x=active + ifStackStatus.x.0=active " + ::= { ifMIBObjects 2 } + +ifStackEntry OBJECT-TYPE + SYNTAX IfStackEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information on a particular relationship between two sub- + layers, specifying that one sub-layer runs on 'top' of the + other sub-layer. Each sub-layer corresponds to a conceptual + row in the ifTable." + INDEX { ifStackHigherLayer, ifStackLowerLayer } + ::= { ifStackTable 1 } + +IfStackEntry ::= + SEQUENCE { + ifStackHigherLayer InterfaceIndexOrZero, + ifStackLowerLayer InterfaceIndexOrZero, + ifStackStatus RowStatus + } + +ifStackHigherLayer OBJECT-TYPE + SYNTAX InterfaceIndexOrZero + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The value of ifIndex corresponding to the higher sub-layer + of the relationship, i.e., the sub-layer which runs on 'top' + of the sub-layer identified by the corresponding instance of + ifStackLowerLayer. If there is no higher sub-layer (below + the internetwork layer), then this object has the value 0." + ::= { ifStackEntry 1 } + +ifStackLowerLayer OBJECT-TYPE + SYNTAX InterfaceIndexOrZero + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The value of ifIndex corresponding to the lower sub-layer + of the relationship, i.e., the sub-layer which runs 'below' + the sub-layer identified by the corresponding instance of + ifStackHigherLayer. If there is no lower sub-layer, then + this object has the value 0." + ::= { ifStackEntry 2 } + +ifStackStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of the relationship between two sub-layers. + + Changing the value of this object from 'active' to + 'notInService' or 'destroy' will likely have consequences up + and down the interface stack. Thus, write access to this + object is likely to be inappropriate for some types of + interfaces, and many implementations will choose not to + support write-access for any type of interface." + ::= { ifStackEntry 3 } + +ifStackLastChange OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time of the last change of + the (whole) interface stack. A change of the interface + stack is defined to be any creation, deletion, or change in + value of any instance of ifStackStatus. If the interface + stack has been unchanged since the last re-initialization of + the local network management subsystem, then this object + contains a zero value." + ::= { ifMIBObjects 6 } + +-- Generic Receive Address Table +-- +-- This group of objects is mandatory for all types of +-- interfaces which can receive packets/frames addressed to +-- more than one address. +-- +-- This table replaces the ifExtnsRcvAddr table. The main +-- difference is that this table makes use of the RowStatus +-- textual convention, while ifExtnsRcvAddr did not. + +ifRcvAddressTable OBJECT-TYPE + SYNTAX SEQUENCE OF IfRcvAddressEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table contains an entry for each address (broadcast, + multicast, or uni-cast) for which the system will receive + packets/frames on a particular interface, except as follows: + + - for an interface operating in promiscuous mode, entries + are only required for those addresses for which the system + would receive frames were it not operating in promiscuous + mode. + + - for 802.5 functional addresses, only one entry is + required, for the address which has the functional address + bit ANDed with the bit mask of all functional addresses for + which the interface will accept frames. + + A system is normally able to use any unicast address which + corresponds to an entry in this table as a source address." + ::= { ifMIBObjects 4 } + +ifRcvAddressEntry OBJECT-TYPE + SYNTAX IfRcvAddressEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of objects identifying an address for which the + system will accept packets/frames on the particular + interface identified by the index value ifIndex." + INDEX { ifIndex, ifRcvAddressAddress } + ::= { ifRcvAddressTable 1 } + +IfRcvAddressEntry ::= + SEQUENCE { + ifRcvAddressAddress PhysAddress, + ifRcvAddressStatus RowStatus, + ifRcvAddressType INTEGER + } + +ifRcvAddressAddress OBJECT-TYPE + SYNTAX PhysAddress + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An address for which the system will accept packets/frames + on this entry's interface." + ::= { ifRcvAddressEntry 1 } + +ifRcvAddressStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object is used to create and delete rows in the + ifRcvAddressTable." + ::= { ifRcvAddressEntry 2 } + +ifRcvAddressType OBJECT-TYPE + SYNTAX INTEGER { + + other(1), + volatile(2), + nonVolatile(3) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object has the value nonVolatile(3) for those entries + in the table which are valid and will not be deleted by the + next restart of the managed system. Entries having the + value volatile(2) are valid and exist, but have not been + saved, so that will not exist after the next restart of the + managed system. Entries having the value other(1) are valid + and exist but are not classified as to whether they will + continue to exist after the next restart." + DEFVAL { volatile } + ::= { ifRcvAddressEntry 3 } + +-- definition of interface-related traps. + +linkDown NOTIFICATION-TYPE + OBJECTS { ifIndex, ifAdminStatus, ifOperStatus } + STATUS current + DESCRIPTION + "A linkDown trap signifies that the SNMP entity, acting in + an agent role, has detected that the ifOperStatus object for + one of its communication links is about to enter the down + state from some other state (but not from the notPresent + state). This other state is indicated by the included value + of ifOperStatus." + ::= { snmpTraps 3 } + +linkUp NOTIFICATION-TYPE + OBJECTS { ifIndex, ifAdminStatus, ifOperStatus } + STATUS current + DESCRIPTION + "A linkUp trap signifies that the SNMP entity, acting in an + agent role, has detected that the ifOperStatus object for + one of its communication links left the down state and + transitioned into some other state (but not into the + notPresent state). This other state is indicated by the + included value of ifOperStatus." + ::= { snmpTraps 4 } + +-- conformance information + +ifConformance OBJECT IDENTIFIER ::= { ifMIB 2 } + +ifGroups OBJECT IDENTIFIER ::= { ifConformance 1 } +ifCompliances OBJECT IDENTIFIER ::= { ifConformance 2 } + +-- compliance statements + +ifCompliance3 MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP entities which have + network interfaces." + + MODULE -- this module + MANDATORY-GROUPS { ifGeneralInformationGroup, + linkUpDownNotificationsGroup } + +-- The groups: +-- ifFixedLengthGroup +-- ifHCFixedLengthGroup +-- ifPacketGroup +-- ifHCPacketGroup +-- ifVHCPacketGroup +-- are mutually exclusive; at most one of these groups is implemented +-- for a particular interface. When any of these groups is implemented +-- for a particular interface, then ifCounterDiscontinuityGroup must +-- also be implemented for that interface. + + GROUP ifFixedLengthGroup + DESCRIPTION + "This group is mandatory for those network interfaces which + are character-oriented or transmit data in fixed-length + transmission units, and for which the value of the + corresponding instance of ifSpeed is less than or equal to + 20,000,000 bits/second." + + GROUP ifHCFixedLengthGroup + DESCRIPTION + "This group is mandatory for those network interfaces which + are character-oriented or transmit data in fixed-length + transmission units, and for which the value of the + corresponding instance of ifSpeed is greater than 20,000,000 + bits/second." + + GROUP ifPacketGroup + DESCRIPTION + "This group is mandatory for those network interfaces which + are packet-oriented, and for which the value of the + corresponding instance of ifSpeed is less than or equal to + 20,000,000 bits/second." + + GROUP ifHCPacketGroup + DESCRIPTION + "This group is mandatory only for those network interfaces + which are packet-oriented and for which the value of the + corresponding instance of ifSpeed is greater than 20,000,000 + bits/second but less than or equal to 650,000,000 + bits/second." + + GROUP ifVHCPacketGroup + DESCRIPTION + "This group is mandatory only for those network interfaces + which are packet-oriented and for which the value of the + corresponding instance of ifSpeed is greater than + 650,000,000 bits/second." + + GROUP ifCounterDiscontinuityGroup + DESCRIPTION + "This group is mandatory for those network interfaces that + are required to maintain counters (i.e., those for which one + of the ifFixedLengthGroup, ifHCFixedLengthGroup, + ifPacketGroup, ifHCPacketGroup, or ifVHCPacketGroup is + mandatory)." + + GROUP ifRcvAddressGroup + DESCRIPTION + "The applicability of this group MUST be defined by the + media-specific MIBs. Media-specific MIBs must define the + exact meaning, use, and semantics of the addresses in this + group." + + OBJECT ifLinkUpDownTrapEnable + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT ifPromiscuousMode + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT ifAdminStatus + SYNTAX INTEGER { up(1), down(2) } + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required, nor is support for the value + testing(3)." + + OBJECT ifAlias + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + ::= { ifCompliances 3 } + +-- units of conformance + +ifGeneralInformationGroup OBJECT-GROUP + OBJECTS { ifIndex, ifDescr, ifType, ifSpeed, ifPhysAddress, + ifAdminStatus, ifOperStatus, ifLastChange, + ifLinkUpDownTrapEnable, ifConnectorPresent, + ifHighSpeed, ifName, ifNumber, ifAlias, + ifTableLastChange } + STATUS current + DESCRIPTION + "A collection of objects providing information applicable to + all network interfaces." + ::= { ifGroups 10 } + +-- the following five groups are mutually exclusive; at most +-- one of these groups is implemented for any interface + +ifFixedLengthGroup OBJECT-GROUP + OBJECTS { ifInOctets, ifOutOctets, ifInUnknownProtos, + ifInErrors, ifOutErrors } + STATUS current + DESCRIPTION + "A collection of objects providing information specific to + non-high speed (non-high speed interfaces transmit and + receive at speeds less than or equal to 20,000,000 + bits/second) character-oriented or fixed-length-transmission + network interfaces." + ::= { ifGroups 2 } + +ifHCFixedLengthGroup OBJECT-GROUP + OBJECTS { ifHCInOctets, ifHCOutOctets, + ifInOctets, ifOutOctets, ifInUnknownProtos, + ifInErrors, ifOutErrors } + STATUS current + DESCRIPTION + "A collection of objects providing information specific to + high speed (greater than 20,000,000 bits/second) character- + oriented or fixed-length-transmission network interfaces." + ::= { ifGroups 3 } + +ifPacketGroup OBJECT-GROUP + OBJECTS { ifInOctets, ifOutOctets, ifInUnknownProtos, + ifInErrors, ifOutErrors, + ifMtu, ifInUcastPkts, ifInMulticastPkts, + ifInBroadcastPkts, ifInDiscards, + ifOutUcastPkts, ifOutMulticastPkts, + ifOutBroadcastPkts, ifOutDiscards, + ifPromiscuousMode } + STATUS current + DESCRIPTION + "A collection of objects providing information specific to + non-high speed (non-high speed interfaces transmit and + receive at speeds less than or equal to 20,000,000 + bits/second) packet-oriented network interfaces." + ::= { ifGroups 4 } + +ifHCPacketGroup OBJECT-GROUP + OBJECTS { ifHCInOctets, ifHCOutOctets, + ifInOctets, ifOutOctets, ifInUnknownProtos, + ifInErrors, ifOutErrors, + ifMtu, ifInUcastPkts, ifInMulticastPkts, + ifInBroadcastPkts, ifInDiscards, + ifOutUcastPkts, ifOutMulticastPkts, + ifOutBroadcastPkts, ifOutDiscards, + ifPromiscuousMode } + STATUS current + DESCRIPTION + "A collection of objects providing information specific to + high speed (greater than 20,000,000 bits/second but less + than or equal to 650,000,000 bits/second) packet-oriented + network interfaces." + ::= { ifGroups 5 } + +ifVHCPacketGroup OBJECT-GROUP + OBJECTS { ifHCInUcastPkts, ifHCInMulticastPkts, + ifHCInBroadcastPkts, ifHCOutUcastPkts, + ifHCOutMulticastPkts, ifHCOutBroadcastPkts, + ifHCInOctets, ifHCOutOctets, + ifInOctets, ifOutOctets, ifInUnknownProtos, + ifInErrors, ifOutErrors, + ifMtu, ifInUcastPkts, ifInMulticastPkts, + ifInBroadcastPkts, ifInDiscards, + ifOutUcastPkts, ifOutMulticastPkts, + ifOutBroadcastPkts, ifOutDiscards, + ifPromiscuousMode } + STATUS current + DESCRIPTION + "A collection of objects providing information specific to + higher speed (greater than 650,000,000 bits/second) packet- + oriented network interfaces." + ::= { ifGroups 6 } + +ifRcvAddressGroup OBJECT-GROUP + OBJECTS { ifRcvAddressStatus, ifRcvAddressType } + STATUS current + DESCRIPTION + "A collection of objects providing information on the + multiple addresses which an interface receives." + ::= { ifGroups 7 } + +ifStackGroup2 OBJECT-GROUP + OBJECTS { ifStackStatus, ifStackLastChange } + STATUS current + DESCRIPTION + "A collection of objects providing information on the + layering of MIB-II interfaces." + ::= { ifGroups 11 } + +ifCounterDiscontinuityGroup OBJECT-GROUP + OBJECTS { ifCounterDiscontinuityTime } + STATUS current + DESCRIPTION + "A collection of objects providing information specific to + interface counter discontinuities." + ::= { ifGroups 13 } + +linkUpDownNotificationsGroup NOTIFICATION-GROUP + NOTIFICATIONS { linkUp, linkDown } + STATUS current + DESCRIPTION + "The notifications which indicate specific changes in the + value of ifOperStatus." + ::= { ifGroups 14 } + +-- Deprecated Definitions - Objects + +-- +-- The Interface Test Table +-- +-- This group of objects is optional. However, a media-specific + +-- MIB may make implementation of this group mandatory. +-- +-- This table replaces the ifExtnsTestTable +-- + +ifTestTable OBJECT-TYPE + SYNTAX SEQUENCE OF IfTestEntry + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "This table contains one entry per interface. It defines + objects which allow a network manager to instruct an agent + to test an interface for various faults. Tests for an + interface are defined in the media-specific MIB for that + interface. After invoking a test, the object ifTestResult + can be read to determine the outcome. If an agent can not + perform the test, ifTestResult is set to so indicate. The + object ifTestCode can be used to provide further test- + specific or interface-specific (or even enterprise-specific) + information concerning the outcome of the test. Only one + test can be in progress on each interface at any one time. + If one test is in progress when another test is invoked, the + second test is rejected. Some agents may reject a test when + a prior test is active on another interface. + + Before starting a test, a manager-station must first obtain + 'ownership' of the entry in the ifTestTable for the + interface to be tested. This is accomplished with the + ifTestId and ifTestStatus objects as follows: + + try_again: + get (ifTestId, ifTestStatus) + while (ifTestStatus != notInUse) + /* + * Loop while a test is running or some other + * manager is configuring a test. + */ + short delay + get (ifTestId, ifTestStatus) + } + + /* + * Is not being used right now -- let's compete + * to see who gets it. + */ + lock_value = ifTestId + + if ( set(ifTestId = lock_value, ifTestStatus = inUse, + ifTestOwner = 'my-IP-address') == FAILURE) + /* + * Another manager got the ifTestEntry -- go + * try again + */ + goto try_again; + + /* + * I have the lock + */ + set up any test parameters. + + /* + * This starts the test + */ + set(ifTestType = test_to_run); + + wait for test completion by polling ifTestResult + + when test completes, agent sets ifTestResult + agent also sets ifTestStatus = 'notInUse' + + retrieve any additional test results, and ifTestId + + if (ifTestId == lock_value+1) results are valid + + A manager station first retrieves the value of the + appropriate ifTestId and ifTestStatus objects, periodically + repeating the retrieval if necessary, until the value of + ifTestStatus is 'notInUse'. The manager station then tries + to set the same ifTestId object to the value it just + retrieved, the same ifTestStatus object to 'inUse', and the + corresponding ifTestOwner object to a value indicating + itself. If the set operation succeeds then the manager has + obtained ownership of the ifTestEntry, and the value of the + ifTestId object is incremented by the agent (per the + semantics of TestAndIncr). Failure of the set operation + indicates that some other manager has obtained ownership of + the ifTestEntry. + + Once ownership is obtained, any test parameters can be + setup, and then the test is initiated by setting ifTestType. + On completion of the test, the agent sets ifTestStatus to + 'notInUse'. Once this occurs, the manager can retrieve the + results. In the (rare) event that the invocation of tests + by two network managers were to overlap, then there would be + a possibility that the first test's results might be + overwritten by the second test's results prior to the first + + results being read. This unlikely circumstance can be + detected by a network manager retrieving ifTestId at the + same time as retrieving the test results, and ensuring that + the results are for the desired request. + + If ifTestType is not set within an abnormally long period of + time after ownership is obtained, the agent should time-out + the manager, and reset the value of the ifTestStatus object + back to 'notInUse'. It is suggested that this time-out + period be 5 minutes. + + In general, a management station must not retransmit a + request to invoke a test for which it does not receive a + response; instead, it properly inspects an agent's MIB to + determine if the invocation was successful. Only if the + invocation was unsuccessful, is the invocation request + retransmitted. + + Some tests may require the interface to be taken off-line in + order to execute them, or may even require the agent to + reboot after completion of the test. In these + circumstances, communication with the management station + invoking the test may be lost until after completion of the + test. An agent is not required to support such tests. + However, if such tests are supported, then the agent should + make every effort to transmit a response to the request + which invoked the test prior to losing communication. When + the agent is restored to normal service, the results of the + test are properly made available in the appropriate objects. + Note that this requires that the ifIndex value assigned to + an interface must be unchanged even if the test causes a + reboot. An agent must reject any test for which it cannot, + perhaps due to resource constraints, make available at least + the minimum amount of information after that test + completes." + ::= { ifMIBObjects 3 } + +ifTestEntry OBJECT-TYPE + SYNTAX IfTestEntry + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "An entry containing objects for invoking tests on an + interface." + AUGMENTS { ifEntry } + ::= { ifTestTable 1 } + +IfTestEntry ::= + + SEQUENCE { + ifTestId TestAndIncr, + ifTestStatus INTEGER, + ifTestType AutonomousType, + ifTestResult INTEGER, + ifTestCode OBJECT IDENTIFIER, + ifTestOwner OwnerString + } + +ifTestId OBJECT-TYPE + SYNTAX TestAndIncr + MAX-ACCESS read-write + STATUS deprecated + DESCRIPTION + "This object identifies the current invocation of the + interface's test." + ::= { ifTestEntry 1 } + +ifTestStatus OBJECT-TYPE + SYNTAX INTEGER { notInUse(1), inUse(2) } + MAX-ACCESS read-write + STATUS deprecated + DESCRIPTION + "This object indicates whether or not some manager currently + has the necessary 'ownership' required to invoke a test on + this interface. A write to this object is only successful + when it changes its value from 'notInUse(1)' to 'inUse(2)'. + After completion of a test, the agent resets the value back + to 'notInUse(1)'." + ::= { ifTestEntry 2 } + +ifTestType OBJECT-TYPE + SYNTAX AutonomousType + MAX-ACCESS read-write + STATUS deprecated + DESCRIPTION + "A control variable used to start and stop operator- + initiated interface tests. Most OBJECT IDENTIFIER values + assigned to tests are defined elsewhere, in association with + specific types of interface. However, this document assigns + a value for a full-duplex loopback test, and defines the + special meanings of the subject identifier: + + noTest OBJECT IDENTIFIER ::= { 0 0 } + + When the value noTest is written to this object, no action + is taken unless a test is in progress, in which case the + test is aborted. Writing any other value to this object is + + only valid when no test is currently in progress, in which + case the indicated test is initiated. + + When read, this object always returns the most recent value + that ifTestType was set to. If it has not been set since + the last initialization of the network management subsystem + on the agent, a value of noTest is returned." + ::= { ifTestEntry 3 } + +ifTestResult OBJECT-TYPE + SYNTAX INTEGER { + none(1), -- no test yet requested + success(2), + inProgress(3), + notSupported(4), + unAbleToRun(5), -- due to state of system + aborted(6), + failed(7) + } + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "This object contains the result of the most recently + requested test, or the value none(1) if no tests have been + requested since the last reset. Note that this facility + provides no provision for saving the results of one test + when starting another, as could be required if used by + multiple managers concurrently." + ::= { ifTestEntry 4 } + +ifTestCode OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "This object contains a code which contains more specific + information on the test result, for example an error-code + after a failed test. Error codes and other values this + object may take are specific to the type of interface and/or + test. The value may have the semantics of either the + AutonomousType or InstancePointer textual conventions as + defined in RFC 2579. The identifier: + + testCodeUnknown OBJECT IDENTIFIER ::= { 0 0 } + + is defined for use if no additional result code is + available." + ::= { ifTestEntry 5 } + +ifTestOwner OBJECT-TYPE + SYNTAX OwnerString + MAX-ACCESS read-write + STATUS deprecated + DESCRIPTION + "The entity which currently has the 'ownership' required to + invoke a test on this interface." + ::= { ifTestEntry 6 } + +-- Deprecated Definitions - Groups + +ifGeneralGroup OBJECT-GROUP + OBJECTS { ifDescr, ifType, ifSpeed, ifPhysAddress, + ifAdminStatus, ifOperStatus, ifLastChange, + ifLinkUpDownTrapEnable, ifConnectorPresent, + ifHighSpeed, ifName } + STATUS deprecated + DESCRIPTION + "A collection of objects deprecated in favour of + ifGeneralInformationGroup." + ::= { ifGroups 1 } + +ifTestGroup OBJECT-GROUP + OBJECTS { ifTestId, ifTestStatus, ifTestType, + ifTestResult, ifTestCode, ifTestOwner } + STATUS deprecated + DESCRIPTION + "A collection of objects providing the ability to invoke + tests on an interface." + ::= { ifGroups 8 } + +ifStackGroup OBJECT-GROUP + OBJECTS { ifStackStatus } + STATUS deprecated + DESCRIPTION + "The previous collection of objects providing information on + the layering of MIB-II interfaces." + ::= { ifGroups 9 } + +ifOldObjectsGroup OBJECT-GROUP + OBJECTS { ifInNUcastPkts, ifOutNUcastPkts, + ifOutQLen, ifSpecific } + STATUS deprecated + DESCRIPTION + "The collection of objects deprecated from the original MIB- + II interfaces group." + ::= { ifGroups 12 } + +-- Deprecated Definitions - Compliance + +ifCompliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION + "A compliance statement defined in a previous version of + this MIB module, for SNMP entities which have network + interfaces." + + MODULE -- this module + MANDATORY-GROUPS { ifGeneralGroup, ifStackGroup } + + GROUP ifFixedLengthGroup + DESCRIPTION + "This group is mandatory for all network interfaces which + are character-oriented or transmit data in fixed-length + transmission units." + + GROUP ifHCFixedLengthGroup + DESCRIPTION + "This group is mandatory only for those network interfaces + which are character-oriented or transmit data in fixed- + length transmission units, and for which the value of the + corresponding instance of ifSpeed is greater than 20,000,000 + bits/second." + + GROUP ifPacketGroup + DESCRIPTION + "This group is mandatory for all network interfaces which + are packet-oriented." + + GROUP ifHCPacketGroup + DESCRIPTION + "This group is mandatory only for those network interfaces + which are packet-oriented and for which the value of the + corresponding instance of ifSpeed is greater than + 650,000,000 bits/second." + + GROUP ifTestGroup + DESCRIPTION + "This group is optional. Media-specific MIBs which require + interface tests are strongly encouraged to use this group + for invoking tests and reporting results. A medium specific + MIB which has mandatory tests may make implementation of + + this group mandatory." + + GROUP ifRcvAddressGroup + DESCRIPTION + "The applicability of this group MUST be defined by the + media-specific MIBs. Media-specific MIBs must define the + exact meaning, use, and semantics of the addresses in this + group." + + OBJECT ifLinkUpDownTrapEnable + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT ifPromiscuousMode + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT ifStackStatus + SYNTAX INTEGER { active(1) } -- subset of RowStatus + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required, and only one of the six + enumerated values for the RowStatus textual convention need + be supported, specifically: active(1)." + + OBJECT ifAdminStatus + SYNTAX INTEGER { up(1), down(2) } + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required, nor is support for the value + testing(3)." + ::= { ifCompliances 1 } + +ifCompliance2 MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION + "A compliance statement defined in a previous version of + this MIB module, for SNMP entities which have network + interfaces." + + MODULE -- this module + MANDATORY-GROUPS { ifGeneralInformationGroup, ifStackGroup2, + ifCounterDiscontinuityGroup } + + GROUP ifFixedLengthGroup + DESCRIPTION + "This group is mandatory for all network interfaces which + are character-oriented or transmit data in fixed-length + transmission units." + + GROUP ifHCFixedLengthGroup + DESCRIPTION + "This group is mandatory only for those network interfaces + which are character-oriented or transmit data in fixed- + length transmission units, and for which the value of the + corresponding instance of ifSpeed is greater than 20,000,000 + bits/second." + + GROUP ifPacketGroup + DESCRIPTION + "This group is mandatory for all network interfaces which + are packet-oriented." + + GROUP ifHCPacketGroup + DESCRIPTION + "This group is mandatory only for those network interfaces + which are packet-oriented and for which the value of the + corresponding instance of ifSpeed is greater than + 650,000,000 bits/second." + + GROUP ifRcvAddressGroup + DESCRIPTION + "The applicability of this group MUST be defined by the + media-specific MIBs. Media-specific MIBs must define the + exact meaning, use, and semantics of the addresses in this + group." + + OBJECT ifLinkUpDownTrapEnable + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT ifPromiscuousMode + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT ifStackStatus + SYNTAX INTEGER { active(1) } -- subset of RowStatus + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required, and only one of the six + enumerated values for the RowStatus textual convention need + be supported, specifically: active(1)." + + OBJECT ifAdminStatus + SYNTAX INTEGER { up(1), down(2) } + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required, nor is support for the value + testing(3)." + + OBJECT ifAlias + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + ::= { ifCompliances 2 } + +END diff --git a/mibs/INET-ADDRESS-MIB.txt b/mibs/INET-ADDRESS-MIB.txt new file mode 100644 index 0000000..a778cba --- /dev/null +++ b/mibs/INET-ADDRESS-MIB.txt @@ -0,0 +1,402 @@ +INET-ADDRESS-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, mib-2, Unsigned32 FROM SNMPv2-SMI + TEXTUAL-CONVENTION FROM SNMPv2-TC; + +inetAddressMIB MODULE-IDENTITY + LAST-UPDATED "200502040000Z" + ORGANIZATION + "IETF Operations and Management Area" + CONTACT-INFO + "Juergen Schoenwaelder (Editor) + International University Bremen + P.O. Box 750 561 + 28725 Bremen, Germany + + Phone: +49 421 200-3587 + EMail: j.schoenwaelder@iu-bremen.de + + Send comments to <ietfmibs@ops.ietf.org>." + DESCRIPTION + "This MIB module defines textual conventions for + representing Internet addresses. An Internet + address can be an IPv4 address, an IPv6 address, + or a DNS domain name. This module also defines + textual conventions for Internet port numbers, + autonomous system numbers, and the length of an + Internet address prefix. + + Copyright (C) The Internet Society (2005). This version + of this MIB module is part of RFC 4001, see the RFC + itself for full legal notices." + REVISION "200502040000Z" + DESCRIPTION + "Third version, published as RFC 4001. This revision + introduces the InetZoneIndex, InetScopeType, and + InetVersion textual conventions." + REVISION "200205090000Z" + DESCRIPTION + "Second version, published as RFC 3291. This + revision contains several clarifications and + introduces several new textual conventions: + InetAddressPrefixLength, InetPortNumber, + InetAutonomousSystemNumber, InetAddressIPv4z, + and InetAddressIPv6z." + REVISION "200006080000Z" + DESCRIPTION + "Initial version, published as RFC 2851." + ::= { mib-2 76 } + +InetAddressType ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "A value that represents a type of Internet address. + + unknown(0) An unknown address type. This value MUST + be used if the value of the corresponding + InetAddress object is a zero-length string. + It may also be used to indicate an IP address + that is not in one of the formats defined + below. + + ipv4(1) An IPv4 address as defined by the + InetAddressIPv4 textual convention. + + ipv6(2) An IPv6 address as defined by the + InetAddressIPv6 textual convention. + + ipv4z(3) A non-global IPv4 address including a zone + index as defined by the InetAddressIPv4z + textual convention. + + ipv6z(4) A non-global IPv6 address including a zone + index as defined by the InetAddressIPv6z + textual convention. + + dns(16) A DNS domain name as defined by the + InetAddressDNS textual convention. + + Each definition of a concrete InetAddressType value must be + accompanied by a definition of a textual convention for use + with that InetAddressType. + + To support future extensions, the InetAddressType textual + convention SHOULD NOT be sub-typed in object type definitions. + It MAY be sub-typed in compliance statements in order to + require only a subset of these address types for a compliant + implementation. + + Implementations must ensure that InetAddressType objects + and any dependent objects (e.g., InetAddress objects) are + consistent. An inconsistentValue error must be generated + if an attempt to change an InetAddressType object would, + for example, lead to an undefined InetAddress value. In + + particular, InetAddressType/InetAddress pairs must be + changed together if the address type changes (e.g., from + ipv6(2) to ipv4(1))." + SYNTAX INTEGER { + unknown(0), + ipv4(1), + ipv6(2), + ipv4z(3), + ipv6z(4), + dns(16) + } + +InetAddress ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Denotes a generic Internet address. + + An InetAddress value is always interpreted within the context + of an InetAddressType value. Every usage of the InetAddress + textual convention is required to specify the InetAddressType + object that provides the context. It is suggested that the + InetAddressType object be logically registered before the + object(s) that use the InetAddress textual convention, if + they appear in the same logical row. + + The value of an InetAddress object must always be + consistent with the value of the associated InetAddressType + object. Attempts to set an InetAddress object to a value + inconsistent with the associated InetAddressType + must fail with an inconsistentValue error. + + When this textual convention is used as the syntax of an + index object, there may be issues with the limit of 128 + sub-identifiers specified in SMIv2, STD 58. In this case, + the object definition MUST include a 'SIZE' clause to + limit the number of potential instance sub-identifiers; + otherwise the applicable constraints MUST be stated in + the appropriate conceptual row DESCRIPTION clauses, or + in the surrounding documentation if there is no single + DESCRIPTION clause that is appropriate." + SYNTAX OCTET STRING (SIZE (0..255)) + +InetAddressIPv4 ::= TEXTUAL-CONVENTION + DISPLAY-HINT "1d.1d.1d.1d" + STATUS current + DESCRIPTION + "Represents an IPv4 network address: + + Octets Contents Encoding + 1-4 IPv4 address network-byte order + + The corresponding InetAddressType value is ipv4(1). + + This textual convention SHOULD NOT be used directly in object + definitions, as it restricts addresses to a specific format. + However, if it is used, it MAY be used either on its own or in + conjunction with InetAddressType, as a pair." + SYNTAX OCTET STRING (SIZE (4)) + +InetAddressIPv6 ::= TEXTUAL-CONVENTION + DISPLAY-HINT "2x:2x:2x:2x:2x:2x:2x:2x" + STATUS current + DESCRIPTION + "Represents an IPv6 network address: + + Octets Contents Encoding + 1-16 IPv6 address network-byte order + + The corresponding InetAddressType value is ipv6(2). + + This textual convention SHOULD NOT be used directly in object + definitions, as it restricts addresses to a specific format. + However, if it is used, it MAY be used either on its own or in + conjunction with InetAddressType, as a pair." + SYNTAX OCTET STRING (SIZE (16)) + +InetAddressIPv4z ::= TEXTUAL-CONVENTION + DISPLAY-HINT "1d.1d.1d.1d%4d" + STATUS current + DESCRIPTION + "Represents a non-global IPv4 network address, together + with its zone index: + + Octets Contents Encoding + 1-4 IPv4 address network-byte order + 5-8 zone index network-byte order + + The corresponding InetAddressType value is ipv4z(3). + + The zone index (bytes 5-8) is used to disambiguate identical + address values on nodes that have interfaces attached to + different zones of the same scope. The zone index may contain + the special value 0, which refers to the default zone for each + scope. + + This textual convention SHOULD NOT be used directly in object + + definitions, as it restricts addresses to a specific format. + However, if it is used, it MAY be used either on its own or in + conjunction with InetAddressType, as a pair." + SYNTAX OCTET STRING (SIZE (8)) + +InetAddressIPv6z ::= TEXTUAL-CONVENTION + DISPLAY-HINT "2x:2x:2x:2x:2x:2x:2x:2x%4d" + STATUS current + DESCRIPTION + "Represents a non-global IPv6 network address, together + with its zone index: + + Octets Contents Encoding + 1-16 IPv6 address network-byte order + 17-20 zone index network-byte order + + The corresponding InetAddressType value is ipv6z(4). + + The zone index (bytes 17-20) is used to disambiguate + identical address values on nodes that have interfaces + attached to different zones of the same scope. The zone index + may contain the special value 0, which refers to the default + zone for each scope. + + This textual convention SHOULD NOT be used directly in object + definitions, as it restricts addresses to a specific format. + However, if it is used, it MAY be used either on its own or in + conjunction with InetAddressType, as a pair." + SYNTAX OCTET STRING (SIZE (20)) + +InetAddressDNS ::= TEXTUAL-CONVENTION + DISPLAY-HINT "255a" + STATUS current + DESCRIPTION + "Represents a DNS domain name. The name SHOULD be fully + qualified whenever possible. + + The corresponding InetAddressType is dns(16). + + The DESCRIPTION clause of InetAddress objects that may have + InetAddressDNS values MUST fully describe how (and when) + these names are to be resolved to IP addresses. + + The resolution of an InetAddressDNS value may require to + query multiple DNS records (e.g., A for IPv4 and AAAA for + IPv6). The order of the resolution process and which DNS + record takes precedence depends on the configuration of the + resolver. + + This textual convention SHOULD NOT be used directly in object + definitions, as it restricts addresses to a specific format. + However, if it is used, it MAY be used either on its own or in + conjunction with InetAddressType, as a pair." + SYNTAX OCTET STRING (SIZE (1..255)) + +InetAddressPrefixLength ::= TEXTUAL-CONVENTION + DISPLAY-HINT "d" + STATUS current + DESCRIPTION + "Denotes the length of a generic Internet network address + prefix. A value of n corresponds to an IP address mask + that has n contiguous 1-bits from the most significant + bit (MSB), with all other bits set to 0. + + An InetAddressPrefixLength value is always interpreted within + the context of an InetAddressType value. Every usage of the + InetAddressPrefixLength textual convention is required to + specify the InetAddressType object that provides the + context. It is suggested that the InetAddressType object be + logically registered before the object(s) that use the + InetAddressPrefixLength textual convention, if they appear + in the same logical row. + + InetAddressPrefixLength values larger than + the maximum length of an IP address for a specific + InetAddressType are treated as the maximum significant + value applicable for the InetAddressType. The maximum + significant value is 32 for the InetAddressType + 'ipv4(1)' and 'ipv4z(3)' and 128 for the InetAddressType + 'ipv6(2)' and 'ipv6z(4)'. The maximum significant value + for the InetAddressType 'dns(16)' is 0. + + The value zero is object-specific and must be defined as + part of the description of any object that uses this + syntax. Examples of the usage of zero might include + situations where the Internet network address prefix + is unknown or does not apply. + + The upper bound of the prefix length has been chosen to + be consistent with the maximum size of an InetAddress." + SYNTAX Unsigned32 (0..2040) + +InetPortNumber ::= TEXTUAL-CONVENTION + DISPLAY-HINT "d" + STATUS current + DESCRIPTION + "Represents a 16 bit port number of an Internet transport + + layer protocol. Port numbers are assigned by IANA. A + current list of all assignments is available from + <http://www.iana.org/>. + + The value zero is object-specific and must be defined as + part of the description of any object that uses this + syntax. Examples of the usage of zero might include + situations where a port number is unknown, or when the + value zero is used as a wildcard in a filter." + REFERENCE "STD 6 (RFC 768), STD 7 (RFC 793) and RFC 2960" + SYNTAX Unsigned32 (0..65535) + +InetAutonomousSystemNumber ::= TEXTUAL-CONVENTION + DISPLAY-HINT "d" + STATUS current + DESCRIPTION + "Represents an autonomous system number that identifies an + Autonomous System (AS). An AS is a set of routers under a + single technical administration, using an interior gateway + protocol and common metrics to route packets within the AS, + and using an exterior gateway protocol to route packets to + other ASes'. IANA maintains the AS number space and has + delegated large parts to the regional registries. + + Autonomous system numbers are currently limited to 16 bits + (0..65535). There is, however, work in progress to enlarge the + autonomous system number space to 32 bits. Therefore, this + textual convention uses an Unsigned32 value without a + range restriction in order to support a larger autonomous + system number space." + REFERENCE "RFC 1771, RFC 1930" + SYNTAX Unsigned32 + +InetScopeType ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Represents a scope type. This textual convention can be used + in cases where a MIB has to represent different scope types + and there is no context information, such as an InetAddress + object, that implicitly defines the scope type. + + Note that not all possible values have been assigned yet, but + they may be assigned in future revisions of this specification. + Applications should therefore be able to deal with values + not yet assigned." + REFERENCE "RFC 3513" + SYNTAX INTEGER { + -- reserved(0), + interfaceLocal(1), + linkLocal(2), + subnetLocal(3), + adminLocal(4), + siteLocal(5), -- site-local unicast addresses + -- have been deprecated by RFC 3879 + -- unassigned(6), + -- unassigned(7), + organizationLocal(8), + -- unassigned(9), + -- unassigned(10), + -- unassigned(11), + -- unassigned(12), + -- unassigned(13), + global(14) + -- reserved(15) + } + +InetZoneIndex ::= TEXTUAL-CONVENTION + DISPLAY-HINT "d" + STATUS current + DESCRIPTION + "A zone index identifies an instance of a zone of a + specific scope. + + The zone index MUST disambiguate identical address + values. For link-local addresses, the zone index will + typically be the interface index (ifIndex as defined in the + IF-MIB) of the interface on which the address is configured. + + The zone index may contain the special value 0, which refers + to the default zone. The default zone may be used in cases + where the valid zone index is not known (e.g., when a + management application has to write a link-local IPv6 + address without knowing the interface index value). The + default zone SHOULD NOT be used as an easy way out in + cases where the zone index for a non-global IPv6 address + is known." + REFERENCE "RFC4007" + SYNTAX Unsigned32 + +InetVersion ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "A value representing a version of the IP protocol. + + unknown(0) An unknown or unspecified version of the IP + protocol. + + ipv4(1) The IPv4 protocol as defined in RFC 791 (STD 5). + + ipv6(2) The IPv6 protocol as defined in RFC 2460. + + Note that this textual convention SHOULD NOT be used to + distinguish different address types associated with IP + protocols. The InetAddressType has been designed for this + purpose." + REFERENCE "RFC 791, RFC 2460" + SYNTAX INTEGER { + unknown(0), + ipv4(1), + ipv6(2) + } +END diff --git a/mibs/IP-FORWARD-MIB.txt b/mibs/IP-FORWARD-MIB.txt new file mode 100644 index 0000000..347b5e0 --- /dev/null +++ b/mibs/IP-FORWARD-MIB.txt @@ -0,0 +1,1277 @@ +IP-FORWARD-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, + IpAddress, Integer32, Gauge32, + Counter32 FROM SNMPv2-SMI + RowStatus FROM SNMPv2-TC + + MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF + InterfaceIndexOrZero FROM IF-MIB + ip FROM IP-MIB + IANAipRouteProtocol FROM IANA-RTPROTO-MIB + InetAddress, InetAddressType, + InetAddressPrefixLength, + InetAutonomousSystemNumber FROM INET-ADDRESS-MIB; + +ipForward MODULE-IDENTITY + LAST-UPDATED "200602010000Z" + ORGANIZATION + "IETF IPv6 Working Group + http://www.ietf.org/html.charters/ipv6-charter.html" + CONTACT-INFO + "Editor: + Brian Haberman + Johns Hopkins University - Applied Physics Laboratory + Mailstop 17-S442 + 11100 Johns Hopkins Road + Laurel MD, 20723-6099 USA + + Phone: +1-443-778-1319 + Email: brian@innovationslab.net + + Send comments to <ipv6@ietf.org>" + DESCRIPTION + "The MIB module for the management of CIDR multipath IP + Routes. + + Copyright (C) The Internet Society (2006). This version + of this MIB module is a part of RFC 4292; see the RFC + itself for full legal notices." + + REVISION "200602010000Z" + DESCRIPTION + "IPv4/v6 version-independent revision. Minimal changes + were made to the original RFC 2096 MIB to allow easy + upgrade of existing IPv4 implementations to the + version-independent MIB. These changes include: + + Adding inetCidrRouteDiscards as a replacement for the + deprecated ipRoutingDiscards and ipv6DiscardedRoutes + objects. + + Adding a new conformance statement to support the + implementation of the IP Forwarding MIB in a + read-only mode. + + The inetCidrRouteTable replaces the IPv4-specific + ipCidrRouteTable, its related objects, and related + conformance statements. + + Published as RFC 4292." + + REVISION "199609190000Z" + DESCRIPTION + "Revised to support CIDR routes. + Published as RFC 2096." + + REVISION "199207022156Z" + DESCRIPTION + "Initial version, published as RFC 1354." + ::= { ip 24 } + +inetCidrRouteNumber OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of current inetCidrRouteTable entries that + are not invalid." +::= { ipForward 6 } + +inetCidrRouteDiscards OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of valid route entries discarded from the + inetCidrRouteTable. Discarded route entries do not + appear in the inetCidrRouteTable. One possible reason + for discarding an entry would be to free-up buffer space + for other route table entries." + ::= { ipForward 8 } + +-- Inet CIDR Route Table + +-- The Inet CIDR Route Table deprecates and replaces the +-- ipCidrRoute Table currently in the IP Forwarding Table MIB. +-- It adds IP protocol independence. + +inetCidrRouteTable OBJECT-TYPE + SYNTAX SEQUENCE OF InetCidrRouteEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This entity's IP Routing table." + REFERENCE + "RFC 1213 Section 6.6, The IP Group" + ::= { ipForward 7 } + +inetCidrRouteEntry OBJECT-TYPE + SYNTAX InetCidrRouteEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A particular route to a particular destination, under a + particular policy (as reflected in the + inetCidrRoutePolicy object). + + Dynamically created rows will survive an agent reboot. + + Implementers need to be aware that if the total number + of elements (octets or sub-identifiers) in + inetCidrRouteDest, inetCidrRoutePolicy, and + inetCidrRouteNextHop exceeds 111, then OIDs of column + instances in this table will have more than 128 sub- + identifiers and cannot be accessed using SNMPv1, + SNMPv2c, or SNMPv3." + INDEX { + inetCidrRouteDestType, + inetCidrRouteDest, + inetCidrRoutePfxLen, + inetCidrRoutePolicy, + inetCidrRouteNextHopType, + inetCidrRouteNextHop + } + ::= { inetCidrRouteTable 1 } + +InetCidrRouteEntry ::= SEQUENCE { + inetCidrRouteDestType InetAddressType, + inetCidrRouteDest InetAddress, + inetCidrRoutePfxLen InetAddressPrefixLength, + inetCidrRoutePolicy OBJECT IDENTIFIER, + inetCidrRouteNextHopType InetAddressType, + inetCidrRouteNextHop InetAddress, + inetCidrRouteIfIndex InterfaceIndexOrZero, + inetCidrRouteType INTEGER, + inetCidrRouteProto IANAipRouteProtocol, + inetCidrRouteAge Gauge32, + inetCidrRouteNextHopAS InetAutonomousSystemNumber, + inetCidrRouteMetric1 Integer32, + inetCidrRouteMetric2 Integer32, + inetCidrRouteMetric3 Integer32, + inetCidrRouteMetric4 Integer32, + inetCidrRouteMetric5 Integer32, + inetCidrRouteStatus RowStatus + } + +inetCidrRouteDestType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The type of the inetCidrRouteDest address, as defined + in the InetAddress MIB. + + Only those address types that may appear in an actual + routing table are allowed as values of this object." + REFERENCE "RFC 4001" + ::= { inetCidrRouteEntry 1 } + +inetCidrRouteDest OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The destination IP address of this route. + + The type of this address is determined by the value of + the inetCidrRouteDestType object. + + The values for the index objects inetCidrRouteDest and + inetCidrRoutePfxLen must be consistent. When the value + of inetCidrRouteDest (excluding the zone index, if one + is present) is x, then the bitwise logical-AND + of x with the value of the mask formed from the + corresponding index object inetCidrRoutePfxLen MUST be + equal to x. If not, then the index pair is not + consistent and an inconsistentName error must be + returned on SET or CREATE requests." + ::= { inetCidrRouteEntry 2 } + +inetCidrRoutePfxLen OBJECT-TYPE + SYNTAX InetAddressPrefixLength + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Indicates the number of leading one bits that form the + mask to be logical-ANDed with the destination address + before being compared to the value in the + + inetCidrRouteDest field. + + The values for the index objects inetCidrRouteDest and + inetCidrRoutePfxLen must be consistent. When the value + of inetCidrRouteDest (excluding the zone index, if one + is present) is x, then the bitwise logical-AND + of x with the value of the mask formed from the + corresponding index object inetCidrRoutePfxLen MUST be + equal to x. If not, then the index pair is not + consistent and an inconsistentName error must be + returned on SET or CREATE requests." + ::= { inetCidrRouteEntry 3 } + +inetCidrRoutePolicy OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This object is an opaque object without any defined + semantics. Its purpose is to serve as an additional + index that may delineate between multiple entries to + the same destination. The value { 0 0 } shall be used + as the default value for this object." + ::= { inetCidrRouteEntry 4 } + +inetCidrRouteNextHopType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The type of the inetCidrRouteNextHop address, as + defined in the InetAddress MIB. + + Value should be set to unknown(0) for non-remote + routes. + + Only those address types that may appear in an actual + routing table are allowed as values of this object." + REFERENCE "RFC 4001" + ::= { inetCidrRouteEntry 5 } + +inetCidrRouteNextHop OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "On remote routes, the address of the next system en + + route. For non-remote routes, a zero length string. + + The type of this address is determined by the value of + the inetCidrRouteNextHopType object." + ::= { inetCidrRouteEntry 6 } + +inetCidrRouteIfIndex OBJECT-TYPE + SYNTAX InterfaceIndexOrZero + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The ifIndex value that identifies the local interface + through which the next hop of this route should be + reached. A value of 0 is valid and represents the + scenario where no interface is specified." + ::= { inetCidrRouteEntry 7 } + +inetCidrRouteType OBJECT-TYPE + SYNTAX INTEGER { + other (1), -- not specified by this MIB + reject (2), -- route that discards traffic and + -- returns ICMP notification + local (3), -- local interface + remote (4), -- remote destination + blackhole(5) -- route that discards traffic + -- silently + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The type of route. Note that local(3) refers to a + route for which the next hop is the final destination; + remote(4) refers to a route for which the next hop is + not the final destination. + + Routes that do not result in traffic forwarding or + rejection should not be displayed, even if the + implementation keeps them stored internally. + + reject(2) refers to a route that, if matched, discards + the message as unreachable and returns a notification + (e.g., ICMP error) to the message sender. This is used + in some protocols as a means of correctly aggregating + routes. + + blackhole(5) refers to a route that, if matched, + discards the message silently." + ::= { inetCidrRouteEntry 8 } + +inetCidrRouteProto OBJECT-TYPE + SYNTAX IANAipRouteProtocol + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The routing mechanism via which this route was learned. + Inclusion of values for gateway routing protocols is + not intended to imply that hosts should support those + protocols." + ::= { inetCidrRouteEntry 9 } + +inetCidrRouteAge OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of seconds since this route was last updated + or otherwise determined to be correct. Note that no + semantics of 'too old' can be implied, except through + knowledge of the routing protocol by which the route + was learned." + ::= { inetCidrRouteEntry 10 } + +inetCidrRouteNextHopAS OBJECT-TYPE + SYNTAX InetAutonomousSystemNumber + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The Autonomous System Number of the Next Hop. The + semantics of this object are determined by the routing- + protocol specified in the route's inetCidrRouteProto + value. When this object is unknown or not relevant, its + value should be set to zero." + DEFVAL { 0 } + ::= { inetCidrRouteEntry 11 } + +inetCidrRouteMetric1 OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The primary routing metric for this route. The + semantics of this metric are determined by the routing- + protocol specified in the route's inetCidrRouteProto + value. If this metric is not used, its value should be + set to -1." + DEFVAL { -1 } + ::= { inetCidrRouteEntry 12 } + +inetCidrRouteMetric2 OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the routing- + protocol specified in the route's inetCidrRouteProto + value. If this metric is not used, its value should be + set to -1." + DEFVAL { -1 } + ::= { inetCidrRouteEntry 13 } + +inetCidrRouteMetric3 OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the routing- + protocol specified in the route's inetCidrRouteProto + value. If this metric is not used, its value should be + set to -1." + DEFVAL { -1 } + ::= { inetCidrRouteEntry 14 } + +inetCidrRouteMetric4 OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the routing- + protocol specified in the route's inetCidrRouteProto + value. If this metric is not used, its value should be + set to -1." + DEFVAL { -1 } + ::= { inetCidrRouteEntry 15 } + +inetCidrRouteMetric5 OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the routing- + + protocol specified in the route's inetCidrRouteProto + value. If this metric is not used, its value should be + set to -1." + DEFVAL { -1 } + ::= { inetCidrRouteEntry 16 } + +inetCidrRouteStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The row status variable, used according to row + installation and removal conventions. + + A row entry cannot be modified when the status is + marked as active(1)." + ::= { inetCidrRouteEntry 17 } + +-- Conformance information + +ipForwardConformance + OBJECT IDENTIFIER ::= { ipForward 5 } + +ipForwardGroups + OBJECT IDENTIFIER ::= { ipForwardConformance 1 } + +ipForwardCompliances + OBJECT IDENTIFIER ::= { ipForwardConformance 2 } + +-- Compliance statements + +ipForwardFullCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "When this MIB is implemented for read-create, the + implementation can claim full compliance. + + There are a number of INDEX objects that cannot be + represented in the form of OBJECT clauses in SMIv2, + but for which there are compliance requirements, + expressed in OBJECT clause form in this description: + + -- OBJECT inetCidrRouteDestType + -- SYNTAX InetAddressType (ipv4(1), ipv6(2), + -- ipv4z(3), ipv6z(4)) + -- DESCRIPTION + -- This MIB requires support for global and + -- non-global ipv4 and ipv6 addresses. + + -- + -- OBJECT inetCidrRouteDest + -- SYNTAX InetAddress (SIZE (4 | 8 | 16 | 20)) + -- DESCRIPTION + -- This MIB requires support for global and + -- non-global IPv4 and IPv6 addresses. + -- + -- OBJECT inetCidrRouteNextHopType + -- SYNTAX InetAddressType (unknown(0), ipv4(1), + -- ipv6(2), ipv4z(3) + -- ipv6z(4)) + -- DESCRIPTION + -- This MIB requires support for global and + -- non-global ipv4 and ipv6 addresses. + -- + -- OBJECT inetCidrRouteNextHop + -- SYNTAX InetAddress (SIZE (0 | 4 | 8 | 16 | 20)) + -- DESCRIPTION + -- This MIB requires support for global and + -- non-global IPv4 and IPv6 addresses. + " + + MODULE -- this module + MANDATORY-GROUPS { inetForwardCidrRouteGroup } + + OBJECT inetCidrRouteStatus + SYNTAX RowStatus { active(1), notInService (2) } + WRITE-SYNTAX RowStatus { active(1), notInService (2), + createAndGo(4), destroy(6) } + DESCRIPTION "Support for createAndWait is not required." + ::= { ipForwardCompliances 3 } + +ipForwardReadOnlyCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "When this MIB is implemented without support for read- + create (i.e., in read-only mode), the implementation can + claim read-only compliance." + MODULE -- this module + MANDATORY-GROUPS { inetForwardCidrRouteGroup } + + OBJECT inetCidrRouteIfIndex + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT inetCidrRouteType + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT inetCidrRouteNextHopAS + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT inetCidrRouteMetric1 + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT inetCidrRouteMetric2 + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT inetCidrRouteMetric3 + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT inetCidrRouteMetric4 + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT inetCidrRouteMetric5 + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT inetCidrRouteStatus + SYNTAX RowStatus { active(1) } + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + ::= { ipForwardCompliances 4 } + +-- units of conformance + +inetForwardCidrRouteGroup OBJECT-GROUP + OBJECTS { inetCidrRouteDiscards, + inetCidrRouteIfIndex, inetCidrRouteType, + inetCidrRouteProto, inetCidrRouteAge, + inetCidrRouteNextHopAS, inetCidrRouteMetric1, + inetCidrRouteMetric2, inetCidrRouteMetric3, + inetCidrRouteMetric4, inetCidrRouteMetric5, + inetCidrRouteStatus, inetCidrRouteNumber + } + STATUS current + DESCRIPTION + "The IP version-independent CIDR Route Table." + ::= { ipForwardGroups 4 } + +-- Deprecated Objects + +ipCidrRouteNumber OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of current ipCidrRouteTable entries that are + not invalid. This object is deprecated in favor of + inetCidrRouteNumber and the inetCidrRouteTable." + ::= { ipForward 3 } + +-- IP CIDR Route Table + +-- The IP CIDR Route Table obsoletes and replaces the ipRoute +-- Table current in MIB-I and MIB-II and the IP Forwarding Table. +-- It adds knowledge of the autonomous system of the next hop, +-- multiple next hops, policy routing, and Classless +-- Inter-Domain Routing. + +ipCidrRouteTable OBJECT-TYPE + SYNTAX SEQUENCE OF IpCidrRouteEntry + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "This entity's IP Routing table. This table has been + deprecated in favor of the IP version neutral + inetCidrRouteTable." + REFERENCE + "RFC 1213 Section 6.6, The IP Group" + ::= { ipForward 4 } + +ipCidrRouteEntry OBJECT-TYPE + SYNTAX IpCidrRouteEntry + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "A particular route to a particular destination, under a + + particular policy." + INDEX { + ipCidrRouteDest, + ipCidrRouteMask, + ipCidrRouteTos, + ipCidrRouteNextHop + } + ::= { ipCidrRouteTable 1 } + +IpCidrRouteEntry ::= SEQUENCE { + ipCidrRouteDest IpAddress, + ipCidrRouteMask IpAddress, + ipCidrRouteTos Integer32, + ipCidrRouteNextHop IpAddress, + ipCidrRouteIfIndex Integer32, + ipCidrRouteType INTEGER, + ipCidrRouteProto INTEGER, + ipCidrRouteAge Integer32, + ipCidrRouteInfo OBJECT IDENTIFIER, + ipCidrRouteNextHopAS Integer32, + ipCidrRouteMetric1 Integer32, + ipCidrRouteMetric2 Integer32, + ipCidrRouteMetric3 Integer32, + ipCidrRouteMetric4 Integer32, + ipCidrRouteMetric5 Integer32, + ipCidrRouteStatus RowStatus + } + +ipCidrRouteDest OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The destination IP address of this route. + + This object may not take a Multicast (Class D) address + value. + + Any assignment (implicit or otherwise) of an instance + of this object to a value x must be rejected if the + bitwise logical-AND of x with the value of the + corresponding instance of the ipCidrRouteMask object is + not equal to x." + ::= { ipCidrRouteEntry 1 } + +ipCidrRouteMask OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "Indicate the mask to be logical-ANDed with the + destination address before being compared to the value + in the ipCidrRouteDest field. For those systems that + do not support arbitrary subnet masks, an agent + constructs the value of the ipCidrRouteMask by + reference to the IP Address Class. + + Any assignment (implicit or otherwise) of an instance + of this object to a value x must be rejected if the + bitwise logical-AND of x with the value of the + corresponding instance of the ipCidrRouteDest object is + not equal to ipCidrRouteDest." + ::= { ipCidrRouteEntry 2 } + +-- The following convention is included for specification +-- of TOS Field contents. At this time, the Host Requirements +-- and the Router Requirements documents disagree on the width +-- of the TOS field. This mapping describes the Router +-- Requirements mapping, and leaves room to widen the TOS field +-- without impact to fielded systems. + +ipCidrRouteTos OBJECT-TYPE + SYNTAX Integer32 (0..2147483647) + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The policy specifier is the IP TOS Field. The encoding + of IP TOS is as specified by the following convention. + Zero indicates the default path if no more specific + policy applies. + + +-----+-----+-----+-----+-----+-----+-----+-----+ + | | | | + | PRECEDENCE | TYPE OF SERVICE | 0 | + | | | | + +-----+-----+-----+-----+-----+-----+-----+-----+ + + IP TOS IP TOS + Field Policy Field Policy + Contents Code Contents Code + 0 0 0 0 ==> 0 0 0 0 1 ==> 2 + 0 0 1 0 ==> 4 0 0 1 1 ==> 6 + 0 1 0 0 ==> 8 0 1 0 1 ==> 10 + 0 1 1 0 ==> 12 0 1 1 1 ==> 14 + 1 0 0 0 ==> 16 1 0 0 1 ==> 18 + 1 0 1 0 ==> 20 1 0 1 1 ==> 22 + + 1 1 0 0 ==> 24 1 1 0 1 ==> 26 + 1 1 1 0 ==> 28 1 1 1 1 ==> 30" + ::= { ipCidrRouteEntry 3 } + +ipCidrRouteNextHop OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "On remote routes, the address of the next system en + route; Otherwise, 0.0.0.0." + ::= { ipCidrRouteEntry 4 } + +ipCidrRouteIfIndex OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS deprecated + DESCRIPTION + "The ifIndex value that identifies the local interface + through which the next hop of this route should be + reached." + DEFVAL { 0 } + ::= { ipCidrRouteEntry 5 } + +ipCidrRouteType OBJECT-TYPE + SYNTAX INTEGER { + other (1), -- not specified by this MIB + reject (2), -- route that discards traffic + local (3), -- local interface + remote (4) -- remote destination + } + MAX-ACCESS read-create + STATUS deprecated + DESCRIPTION + "The type of route. Note that local(3) refers to a + route for which the next hop is the final destination; + remote(4) refers to a route for which the next hop is + not the final destination. + + Routes that do not result in traffic forwarding or + rejection should not be displayed, even if the + implementation keeps them stored internally. + + reject (2) refers to a route that, if matched, + discards the message as unreachable. This is used in + some protocols as a means of correctly aggregating + routes." + ::= { ipCidrRouteEntry 6 } + +ipCidrRouteProto OBJECT-TYPE + SYNTAX INTEGER { + other (1), -- not specified + local (2), -- local interface + netmgmt (3), -- static route + icmp (4), -- result of ICMP Redirect + + -- the following are all dynamic + -- routing protocols + egp (5), -- Exterior Gateway Protocol + ggp (6), -- Gateway-Gateway Protocol + hello (7), -- FuzzBall HelloSpeak + rip (8), -- Berkeley RIP or RIP-II + isIs (9), -- Dual IS-IS + esIs (10), -- ISO 9542 + ciscoIgrp (11), -- Cisco IGRP + bbnSpfIgp (12), -- BBN SPF IGP + ospf (13), -- Open Shortest Path First + bgp (14), -- Border Gateway Protocol + idpr (15), -- InterDomain Policy Routing + ciscoEigrp (16) -- Cisco EIGRP + } + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The routing mechanism via which this route was learned. + Inclusion of values for gateway routing protocols is + not intended to imply that hosts should support those + protocols." + ::= { ipCidrRouteEntry 7 } + +ipCidrRouteAge OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of seconds since this route was last updated + or otherwise determined to be correct. Note that no + semantics of `too old' can be implied, except through + knowledge of the routing protocol by which the route + was learned." + DEFVAL { 0 } + ::= { ipCidrRouteEntry 8 } + +ipCidrRouteInfo OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-create + STATUS deprecated + DESCRIPTION + "A reference to MIB definitions specific to the + particular routing protocol that is responsible for + this route, as determined by the value specified in the + route's ipCidrRouteProto value. If this information is + not present, its value should be set to the OBJECT + IDENTIFIER { 0 0 }, which is a syntactically valid + object identifier, and any implementation conforming to + ASN.1 and the Basic Encoding Rules must be able to + generate and recognize this value." + ::= { ipCidrRouteEntry 9 } + +ipCidrRouteNextHopAS OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS deprecated + DESCRIPTION + "The Autonomous System Number of the Next Hop. The + semantics of this object are determined by the routing- + protocol specified in the route's ipCidrRouteProto + value. When this object is unknown or not relevant, its + value should be set to zero." + DEFVAL { 0 } + ::= { ipCidrRouteEntry 10 } + +ipCidrRouteMetric1 OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS deprecated + DESCRIPTION + "The primary routing metric for this route. The + semantics of this metric are determined by the routing- + protocol specified in the route's ipCidrRouteProto + value. If this metric is not used, its value should be + set to -1." + DEFVAL { -1 } + ::= { ipCidrRouteEntry 11 } + +ipCidrRouteMetric2 OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS deprecated + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the routing- + protocol specified in the route's ipCidrRouteProto + value. If this metric is not used, its value should be + + set to -1." + DEFVAL { -1 } + ::= { ipCidrRouteEntry 12 } + +ipCidrRouteMetric3 OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS deprecated + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the routing- + protocol specified in the route's ipCidrRouteProto + value. If this metric is not used, its value should be + set to -1." + DEFVAL { -1 } + ::= { ipCidrRouteEntry 13 } + +ipCidrRouteMetric4 OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS deprecated + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the routing- + protocol specified in the route's ipCidrRouteProto + value. If this metric is not used, its value should be + set to -1." + DEFVAL { -1 } + ::= { ipCidrRouteEntry 14 } + +ipCidrRouteMetric5 OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS deprecated + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the routing- + protocol specified in the route's ipCidrRouteProto + value. If this metric is not used, its value should be + set to -1." + DEFVAL { -1 } + ::= { ipCidrRouteEntry 15 } + +ipCidrRouteStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS deprecated + DESCRIPTION + "The row status variable, used according to row + installation and removal conventions." + ::= { ipCidrRouteEntry 16 } + +-- compliance statements + +ipForwardCompliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION + "The compliance statement for SNMPv2 entities that + implement the ipForward MIB. + + This compliance statement has been deprecated and + replaced with ipForwardFullCompliance and + ipForwardReadOnlyCompliance." + + MODULE -- this module + MANDATORY-GROUPS { ipForwardCidrRouteGroup } + ::= { ipForwardCompliances 1 } + +-- units of conformance + +ipForwardCidrRouteGroup OBJECT-GROUP + OBJECTS { ipCidrRouteNumber, + ipCidrRouteDest, ipCidrRouteMask, ipCidrRouteTos, + ipCidrRouteNextHop, ipCidrRouteIfIndex, + ipCidrRouteType, ipCidrRouteProto, ipCidrRouteAge, + ipCidrRouteInfo,ipCidrRouteNextHopAS, + ipCidrRouteMetric1, ipCidrRouteMetric2, + ipCidrRouteMetric3, ipCidrRouteMetric4, + ipCidrRouteMetric5, ipCidrRouteStatus + } + STATUS deprecated + DESCRIPTION + "The CIDR Route Table. + + This group has been deprecated and replaced with + inetForwardCidrRouteGroup." + ::= { ipForwardGroups 3 } + +-- Obsoleted Definitions - Objects + +ipForwardNumber OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The number of current ipForwardTable entries that are + not invalid." + ::= { ipForward 1 } + +-- IP Forwarding Table + +-- The IP Forwarding Table obsoletes and replaces the ipRoute +-- Table current in MIB-I and MIB-II. It adds knowledge of +-- the autonomous system of the next hop, multiple next hop +-- support, and policy routing support. + +ipForwardTable OBJECT-TYPE + SYNTAX SEQUENCE OF IpForwardEntry + MAX-ACCESS not-accessible + STATUS obsolete + DESCRIPTION + "This entity's IP Routing table." + REFERENCE + "RFC 1213 Section 6.6, The IP Group" + ::= { ipForward 2 } + +ipForwardEntry OBJECT-TYPE + SYNTAX IpForwardEntry + MAX-ACCESS not-accessible + STATUS obsolete + DESCRIPTION + "A particular route to a particular destination, under a + particular policy." + INDEX { + ipForwardDest, + ipForwardProto, + ipForwardPolicy, + ipForwardNextHop + } + ::= { ipForwardTable 1 } + +IpForwardEntry ::= SEQUENCE { + ipForwardDest IpAddress, + ipForwardMask IpAddress, + ipForwardPolicy Integer32, + ipForwardNextHop IpAddress, + ipForwardIfIndex Integer32, + ipForwardType INTEGER, + ipForwardProto INTEGER, + ipForwardAge Integer32, + ipForwardInfo OBJECT IDENTIFIER, + ipForwardNextHopAS Integer32, + ipForwardMetric1 Integer32, + ipForwardMetric2 Integer32, + ipForwardMetric3 Integer32, + ipForwardMetric4 Integer32, + ipForwardMetric5 Integer32 + } + +ipForwardDest OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The destination IP address of this route. An entry + with a value of 0.0.0.0 is considered a default route. + + This object may not take a Multicast (Class D) address + value. + + Any assignment (implicit or otherwise) of an instance + of this object to a value x must be rejected if the + bitwise logical-AND of x with the value of the + corresponding instance of the ipForwardMask object is + not equal to x." + ::= { ipForwardEntry 1 } + +ipForwardMask OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-create + STATUS obsolete + DESCRIPTION + "Indicate the mask to be logical-ANDed with the + destination address before being compared to the value + in the ipForwardDest field. For those systems that do + not support arbitrary subnet masks, an agent constructs + the value of the ipForwardMask by reference to the IP + Address Class. + + Any assignment (implicit or otherwise) of an instance + of this object to a value x must be rejected if the + bitwise logical-AND of x with the value of the + corresponding instance of the ipForwardDest object is + not equal to ipForwardDest." + DEFVAL { '00000000'H } -- 0.0.0.0 + ::= { ipForwardEntry 2 } + +-- The following convention is included for specification +-- of TOS Field contents. At this time, the Host Requirements +-- and the Router Requirements documents disagree on the width +-- of the TOS field. This mapping describes the Router + +-- Requirements mapping, and leaves room to widen the TOS field +-- without impact to fielded systems. + +ipForwardPolicy OBJECT-TYPE + SYNTAX Integer32 (0..2147483647) + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The general set of conditions that would cause + the selection of one multipath route (set of + next hops for a given destination) is referred + to as 'policy'. + + Unless the mechanism indicated by ipForwardProto + specifies otherwise, the policy specifier is + the IP TOS Field. The encoding of IP TOS is as + specified by the following convention. Zero + indicates the default path if no more specific + policy applies. + + +-----+-----+-----+-----+-----+-----+-----+-----+ + | | | | + | PRECEDENCE | TYPE OF SERVICE | 0 | + | | | | + +-----+-----+-----+-----+-----+-----+-----+-----+ + + IP TOS IP TOS + Field Policy Field Policy + Contents Code Contents Code + 0 0 0 0 ==> 0 0 0 0 1 ==> 2 + 0 0 1 0 ==> 4 0 0 1 1 ==> 6 + 0 1 0 0 ==> 8 0 1 0 1 ==> 10 + 0 1 1 0 ==> 12 0 1 1 1 ==> 14 + 1 0 0 0 ==> 16 1 0 0 1 ==> 18 + 1 0 1 0 ==> 20 1 0 1 1 ==> 22 + 1 1 0 0 ==> 24 1 1 0 1 ==> 26 + 1 1 1 0 ==> 28 1 1 1 1 ==> 30 + + Protocols defining 'policy' otherwise must either + define a set of values that are valid for + this object or must implement an integer-instanced + policy table for which this object's + value acts as an index." + ::= { ipForwardEntry 3 } + +ipForwardNextHop OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "On remote routes, the address of the next system en + route; otherwise, 0.0.0.0." + ::= { ipForwardEntry 4 } + +ipForwardIfIndex OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS obsolete + DESCRIPTION + "The ifIndex value that identifies the local interface + through which the next hop of this route should be + reached." + DEFVAL { 0 } + ::= { ipForwardEntry 5 } + +ipForwardType OBJECT-TYPE + SYNTAX INTEGER { + other (1), -- not specified by this MIB + invalid (2), -- logically deleted + local (3), -- local interface + remote (4) -- remote destination + } + MAX-ACCESS read-create + STATUS obsolete + DESCRIPTION + "The type of route. Note that local(3) refers to a + route for which the next hop is the final destination; + remote(4) refers to a route for which the next hop is + not the final destination. + + Setting this object to the value invalid(2) has the + effect of invalidating the corresponding entry in the + ipForwardTable object. That is, it effectively + disassociates the destination identified with said + entry from the route identified with said entry. It is + an implementation-specific matter as to whether the + agent removes an invalidated entry from the table. + Accordingly, management stations must be prepared to + receive tabular information from agents that + corresponds to entries not currently in use. Proper + interpretation of such entries requires examination of + the relevant ipForwardType object." + DEFVAL { invalid } + ::= { ipForwardEntry 6 } + +ipForwardProto OBJECT-TYPE + SYNTAX INTEGER { + other (1), -- not specified + local (2), -- local interface + netmgmt (3), -- static route + icmp (4), -- result of ICMP Redirect + + -- the following are all dynamic + -- routing protocols + egp (5), -- Exterior Gateway Protocol + ggp (6), -- Gateway-Gateway Protocol + hello (7), -- FuzzBall HelloSpeak + rip (8), -- Berkeley RIP or RIP-II + is-is (9), -- Dual IS-IS + es-is (10), -- ISO 9542 + ciscoIgrp (11), -- Cisco IGRP + bbnSpfIgp (12), -- BBN SPF IGP + ospf (13), -- Open Shortest Path First + bgp (14), -- Border Gateway Protocol + idpr (15) -- InterDomain Policy Routing + } + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The routing mechanism via which this route was learned. + Inclusion of values for gateway routing protocols is + not intended to imply that hosts should support those + protocols." + ::= { ipForwardEntry 7 } + +ipForwardAge OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The number of seconds since this route was last updated + or otherwise determined to be correct. Note that no + semantics of `too old' can be implied except through + knowledge of the routing protocol by which the route + was learned." + DEFVAL { 0 } + ::= { ipForwardEntry 8 } + +ipForwardInfo OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-create + STATUS obsolete + DESCRIPTION + "A reference to MIB definitions specific to the + particular routing protocol that is responsible for + this route, as determined by the value specified in the + route's ipForwardProto value. If this information is + not present, its value should be set to the OBJECT + IDENTIFIER { 0 0 }, which is a syntactically valid + object identifier, and any implementation conforming to + ASN.1 and the Basic Encoding Rules must be able to + generate and recognize this value." + ::= { ipForwardEntry 9 } + +ipForwardNextHopAS OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS obsolete + DESCRIPTION + "The Autonomous System Number of the Next Hop. When + this is unknown or not relevant to the protocol + indicated by ipForwardProto, zero." + DEFVAL { 0 } + ::= { ipForwardEntry 10 } + +ipForwardMetric1 OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS obsolete + DESCRIPTION + "The primary routing metric for this route. The + semantics of this metric are determined by the routing- + protocol specified in the route's ipForwardProto value. + If this metric is not used, its value should be set to + -1." + DEFVAL { -1 } + ::= { ipForwardEntry 11 } + +ipForwardMetric2 OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS obsolete + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the routing- + protocol specified in the route's ipForwardProto value. + If this metric is not used, its value should be set to + -1." + DEFVAL { -1 } + ::= { ipForwardEntry 12 } + +ipForwardMetric3 OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS obsolete + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the routing- + protocol specified in the route's ipForwardProto value. + If this metric is not used, its value should be set to + -1." + DEFVAL { -1 } + ::= { ipForwardEntry 13 } + +ipForwardMetric4 OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS obsolete + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the routing- + protocol specified in the route's ipForwardProto value. + If this metric is not used, its value should be set to + -1." + DEFVAL { -1 } + ::= { ipForwardEntry 14 } + +ipForwardMetric5 OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS obsolete + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the routing- + protocol specified in the route's ipForwardProto value. + If this metric is not used, its value should be set to + -1." + DEFVAL { -1 } + ::= { ipForwardEntry 15 } + +-- Obsoleted Definitions - Groups +-- compliance statements + +ipForwardOldCompliance MODULE-COMPLIANCE + STATUS obsolete + DESCRIPTION + "The compliance statement for SNMP entities that + implement the ipForward MIB." + + MODULE -- this module + MANDATORY-GROUPS { ipForwardMultiPathGroup } + ::= { ipForwardCompliances 2 } + +ipForwardMultiPathGroup OBJECT-GROUP + OBJECTS { ipForwardNumber, + ipForwardDest, ipForwardMask, ipForwardPolicy, + ipForwardNextHop, ipForwardIfIndex, ipForwardType, + ipForwardProto, ipForwardAge, ipForwardInfo, + ipForwardNextHopAS, + ipForwardMetric1, ipForwardMetric2, ipForwardMetric3, + ipForwardMetric4, ipForwardMetric5 + } + STATUS obsolete + DESCRIPTION + "IP Multipath Route Table." + ::= { ipForwardGroups 2 } + +END diff --git a/mibs/IP-MIB.txt b/mibs/IP-MIB.txt new file mode 100644 index 0000000..fe2db5f --- /dev/null +++ b/mibs/IP-MIB.txt @@ -0,0 +1,4993 @@ +IP-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, + Integer32, Counter32, IpAddress, + mib-2, Unsigned32, Counter64, + zeroDotZero FROM SNMPv2-SMI + PhysAddress, TruthValue, + TimeStamp, RowPointer, + TEXTUAL-CONVENTION, TestAndIncr, + RowStatus, StorageType FROM SNMPv2-TC + MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF + InetAddress, InetAddressType, + InetAddressPrefixLength, + InetVersion, InetZoneIndex FROM INET-ADDRESS-MIB + InterfaceIndex FROM IF-MIB; + +ipMIB MODULE-IDENTITY + LAST-UPDATED "200602020000Z" + ORGANIZATION "IETF IPv6 MIB Revision Team" + CONTACT-INFO + "Editor: + + Shawn A. Routhier + Interworking Labs + 108 Whispering Pines Dr. Suite 235 + Scotts Valley, CA 95066 + USA + EMail: <sar@iwl.com>" + DESCRIPTION + "The MIB module for managing IP and ICMP implementations, but + excluding their management of IP routes. + + Copyright (C) The Internet Society (2006). This version of + this MIB module is part of RFC 4293; see the RFC itself for + full legal notices." + + REVISION "200602020000Z" + DESCRIPTION + "The IP version neutral revision with added IPv6 objects for + ND, default routers, and router advertisements. As well as + being the successor to RFC 2011, this MIB is also the + successor to RFCs 2465 and 2466. Published as RFC 4293." + + REVISION "199411010000Z" + DESCRIPTION + "A separate MIB module (IP-MIB) for IP and ICMP management + objects. Published as RFC 2011." + + REVISION "199103310000Z" + DESCRIPTION + "The initial revision of this MIB module was part of MIB-II, + which was published as RFC 1213." + ::= { mib-2 48} + +-- +-- The textual conventions we define and use in this MIB. +-- + +IpAddressOriginTC ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The origin of the address. + + manual(2) indicates that the address was manually configured + to a specified address, e.g., by user configuration. + + dhcp(4) indicates an address that was assigned to this + system by a DHCP server. + + linklayer(5) indicates an address created by IPv6 stateless + + auto-configuration. + + random(6) indicates an address chosen by the system at + random, e.g., an IPv4 address within 169.254/16, or an RFC + 3041 privacy address." + SYNTAX INTEGER { + other(1), + manual(2), + dhcp(4), + linklayer(5), + random(6) + } + +IpAddressStatusTC ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The status of an address. Most of the states correspond to + states from the IPv6 Stateless Address Autoconfiguration + protocol. + + The preferred(1) state indicates that this is a valid + address that can appear as the destination or source address + of a packet. + + The deprecated(2) state indicates that this is a valid but + deprecated address that should no longer be used as a source + address in new communications, but packets addressed to such + an address are processed as expected. + + The invalid(3) state indicates that this isn't a valid + address and it shouldn't appear as the destination or source + address of a packet. + + The inaccessible(4) state indicates that the address is not + accessible because the interface to which this address is + assigned is not operational. + + The unknown(5) state indicates that the status cannot be + determined for some reason. + + The tentative(6) state indicates that the uniqueness of the + address on the link is being verified. Addresses in this + state should not be used for general communication and + should only be used to determine the uniqueness of the + address. + + The duplicate(7) state indicates the address has been + determined to be non-unique on the link and so must not be + + used. + + The optimistic(8) state indicates the address is available + for use, subject to restrictions, while its uniqueness on + a link is being verified. + + In the absence of other information, an IPv4 address is + always preferred(1)." + REFERENCE "RFC 2462" + SYNTAX INTEGER { + preferred(1), + deprecated(2), + invalid(3), + inaccessible(4), + unknown(5), + tentative(6), + duplicate(7), + optimistic(8) + } + +IpAddressPrefixOriginTC ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The origin of this prefix. + + manual(2) indicates a prefix that was manually configured. + + wellknown(3) indicates a well-known prefix, e.g., 169.254/16 + for IPv4 auto-configuration or fe80::/10 for IPv6 link-local + addresses. Well known prefixes may be assigned by IANA, + the address registries, or by specification in a standards + track RFC. + + dhcp(4) indicates a prefix that was assigned by a DHCP + server. + + routeradv(5) indicates a prefix learned from a router + advertisement. + + Note: while IpAddressOriginTC and IpAddressPrefixOriginTC + are similar, they are not identical. The first defines how + an address was created, while the second defines how a + prefix was found." + SYNTAX INTEGER { + other(1), + manual(2), + wellknown(3), + dhcp(4), + routeradv(5) + } + +Ipv6AddressIfIdentifierTC ::= TEXTUAL-CONVENTION + DISPLAY-HINT "2x:" + STATUS current + DESCRIPTION + "This data type is used to model IPv6 address + interface identifiers. This is a binary string + of up to 8 octets in network byte-order." + SYNTAX OCTET STRING (SIZE (0..8)) + +-- +-- the IP general group +-- some objects that affect all of IPv4 +-- + +ip OBJECT IDENTIFIER ::= { mib-2 4 } + +ipForwarding OBJECT-TYPE + SYNTAX INTEGER { + forwarding(1), -- acting as a router + notForwarding(2) -- NOT acting as a router + } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The indication of whether this entity is acting as an IPv4 + router in respect to the forwarding of datagrams received + by, but not addressed to, this entity. IPv4 routers forward + datagrams. IPv4 hosts do not (except those source-routed + via the host). + + When this object is written, the entity should save the + change to non-volatile storage and restore the object from + non-volatile storage upon re-initialization of the system. + Note: a stronger requirement is not used because this object + was previously defined." + ::= { ip 1 } + +ipDefaultTTL OBJECT-TYPE + SYNTAX Integer32 (1..255) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The default value inserted into the Time-To-Live field of + the IPv4 header of datagrams originated at this entity, + whenever a TTL value is not supplied by the transport layer + + protocol. + + When this object is written, the entity should save the + change to non-volatile storage and restore the object from + non-volatile storage upon re-initialization of the system. + Note: a stronger requirement is not used because this object + was previously defined." + ::= { ip 2 } + +ipReasmTimeout OBJECT-TYPE + SYNTAX Integer32 + UNITS "seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The maximum number of seconds that received fragments are + held while they are awaiting reassembly at this entity." + ::= { ip 13 } + +-- +-- the IPv6 general group +-- Some objects that affect all of IPv6 +-- + +ipv6IpForwarding OBJECT-TYPE + SYNTAX INTEGER { + forwarding(1), -- acting as a router + notForwarding(2) -- NOT acting as a router + } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The indication of whether this entity is acting as an IPv6 + router on any interface in respect to the forwarding of + datagrams received by, but not addressed to, this entity. + IPv6 routers forward datagrams. IPv6 hosts do not (except + those source-routed via the host). + + When this object is written, the entity SHOULD save the + change to non-volatile storage and restore the object from + non-volatile storage upon re-initialization of the system." + ::= { ip 25 } + +ipv6IpDefaultHopLimit OBJECT-TYPE + SYNTAX Integer32 (0..255) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The default value inserted into the Hop Limit field of the + IPv6 header of datagrams originated at this entity whenever + a Hop Limit value is not supplied by the transport layer + protocol. + + When this object is written, the entity SHOULD save the + change to non-volatile storage and restore the object from + non-volatile storage upon re-initialization of the system." + REFERENCE "RFC 2461 Section 6.3.2" + ::= { ip 26 } + +-- +-- IPv4 Interface Table +-- + +ipv4InterfaceTableLastChange OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime on the most recent occasion at which + a row in the ipv4InterfaceTable was added or deleted, or + when an ipv4InterfaceReasmMaxSize or an + ipv4InterfaceEnableStatus object was modified. + + If new objects are added to the ipv4InterfaceTable that + require the ipv4InterfaceTableLastChange to be updated when + they are modified, they must specify that requirement in + their description clause." + ::= { ip 27 } + +ipv4InterfaceTable OBJECT-TYPE + SYNTAX SEQUENCE OF Ipv4InterfaceEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table containing per-interface IPv4-specific + information." + ::= { ip 28 } + +ipv4InterfaceEntry OBJECT-TYPE + SYNTAX Ipv4InterfaceEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry containing IPv4-specific information for a specific + interface." + INDEX { ipv4InterfaceIfIndex } + ::= { ipv4InterfaceTable 1 } + +Ipv4InterfaceEntry ::= SEQUENCE { + ipv4InterfaceIfIndex InterfaceIndex, + ipv4InterfaceReasmMaxSize Integer32, + ipv4InterfaceEnableStatus INTEGER, + ipv4InterfaceRetransmitTime Unsigned32 + } + +ipv4InterfaceIfIndex OBJECT-TYPE + SYNTAX InterfaceIndex + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The index value that uniquely identifies the interface to + which this entry is applicable. The interface identified by + a particular value of this index is the same interface as + identified by the same value of the IF-MIB's ifIndex." + ::= { ipv4InterfaceEntry 1 } + +ipv4InterfaceReasmMaxSize OBJECT-TYPE + SYNTAX Integer32 (0..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The size of the largest IPv4 datagram that this entity can + re-assemble from incoming IPv4 fragmented datagrams received + on this interface." + ::= { ipv4InterfaceEntry 2 } + +ipv4InterfaceEnableStatus OBJECT-TYPE + SYNTAX INTEGER { + up(1), + down(2) + } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The indication of whether IPv4 is enabled (up) or disabled + (down) on this interface. This object does not affect the + state of the interface itself, only its connection to an + IPv4 stack. The IF-MIB should be used to control the state + of the interface." + ::= { ipv4InterfaceEntry 3 } + +ipv4InterfaceRetransmitTime OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The time between retransmissions of ARP requests to a + neighbor when resolving the address or when probing the + reachability of a neighbor." + REFERENCE "RFC 1122" + DEFVAL { 1000 } + ::= { ipv4InterfaceEntry 4 } + +-- +-- v6 interface table +-- + +ipv6InterfaceTableLastChange OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime on the most recent occasion at which + a row in the ipv6InterfaceTable was added or deleted or when + an ipv6InterfaceReasmMaxSize, ipv6InterfaceIdentifier, + ipv6InterfaceEnableStatus, ipv6InterfaceReachableTime, + ipv6InterfaceRetransmitTime, or ipv6InterfaceForwarding + object was modified. + + If new objects are added to the ipv6InterfaceTable that + require the ipv6InterfaceTableLastChange to be updated when + they are modified, they must specify that requirement in + their description clause." + ::= { ip 29 } + +ipv6InterfaceTable OBJECT-TYPE + SYNTAX SEQUENCE OF Ipv6InterfaceEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table containing per-interface IPv6-specific + information." + ::= { ip 30 } + +ipv6InterfaceEntry OBJECT-TYPE + SYNTAX Ipv6InterfaceEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry containing IPv6-specific information for a given + interface." + INDEX { ipv6InterfaceIfIndex } + ::= { ipv6InterfaceTable 1 } + +Ipv6InterfaceEntry ::= SEQUENCE { + ipv6InterfaceIfIndex InterfaceIndex, + ipv6InterfaceReasmMaxSize Unsigned32, + ipv6InterfaceIdentifier Ipv6AddressIfIdentifierTC, + ipv6InterfaceEnableStatus INTEGER, + ipv6InterfaceReachableTime Unsigned32, + ipv6InterfaceRetransmitTime Unsigned32, + ipv6InterfaceForwarding INTEGER + } + +ipv6InterfaceIfIndex OBJECT-TYPE + SYNTAX InterfaceIndex + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The index value that uniquely identifies the interface to + which this entry is applicable. The interface identified by + a particular value of this index is the same interface as + identified by the same value of the IF-MIB's ifIndex." + ::= { ipv6InterfaceEntry 1 } + +ipv6InterfaceReasmMaxSize OBJECT-TYPE + SYNTAX Unsigned32 (1500..65535) + UNITS "octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The size of the largest IPv6 datagram that this entity can + re-assemble from incoming IPv6 fragmented datagrams received + on this interface." + ::= { ipv6InterfaceEntry 2 } + +ipv6InterfaceIdentifier OBJECT-TYPE + SYNTAX Ipv6AddressIfIdentifierTC + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Interface Identifier for this interface. The Interface + Identifier is combined with an address prefix to form an + interface address. + + By default, the Interface Identifier is auto-configured + according to the rules of the link type to which this + interface is attached. + + A zero length identifier may be used where appropriate. One + possible example is a loopback interface." + ::= { ipv6InterfaceEntry 3 } + +-- This object ID is reserved as it was used in earlier versions of +-- the MIB module. In theory, OIDs are not assigned until the +-- specification is released as an RFC; however, as some companies +-- may have shipped code based on earlier versions of the MIB, it +-- seems best to reserve this OID. This OID had been +-- ipv6InterfacePhysicalAddress. +-- ::= { ipv6InterfaceEntry 4} + +ipv6InterfaceEnableStatus OBJECT-TYPE + SYNTAX INTEGER { + up(1), + down(2) + } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The indication of whether IPv6 is enabled (up) or disabled + (down) on this interface. This object does not affect the + state of the interface itself, only its connection to an + IPv6 stack. The IF-MIB should be used to control the state + of the interface. + + When this object is written, the entity SHOULD save the + change to non-volatile storage and restore the object from + non-volatile storage upon re-initialization of the system." + ::= { ipv6InterfaceEntry 5 } + +ipv6InterfaceReachableTime OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The time a neighbor is considered reachable after receiving + a reachability confirmation." + REFERENCE "RFC 2461, Section 6.3.2" + ::= { ipv6InterfaceEntry 6 } + +ipv6InterfaceRetransmitTime OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The time between retransmissions of Neighbor Solicitation + messages to a neighbor when resolving the address or when + probing the reachability of a neighbor." + REFERENCE "RFC 2461, Section 6.3.2" + ::= { ipv6InterfaceEntry 7 } + +ipv6InterfaceForwarding OBJECT-TYPE + SYNTAX INTEGER { + forwarding(1), -- acting as a router + notForwarding(2) -- NOT acting as a router + } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The indication of whether this entity is acting as an IPv6 + router on this interface with respect to the forwarding of + datagrams received by, but not addressed to, this entity. + IPv6 routers forward datagrams. IPv6 hosts do not (except + those source-routed via the host). + + This object is constrained by ipv6IpForwarding and is + ignored if ipv6IpForwarding is set to notForwarding. Those + systems that do not provide per-interface control of the + forwarding function should set this object to forwarding for + all interfaces and allow the ipv6IpForwarding object to + control the forwarding capability. + + When this object is written, the entity SHOULD save the + change to non-volatile storage and restore the object from + non-volatile storage upon re-initialization of the system." + ::= { ipv6InterfaceEntry 8 } + +-- +-- Per-Interface or System-Wide IP statistics. +-- +-- The following two tables, ipSystemStatsTable and ipIfStatsTable, +-- are intended to provide the same counters at different granularities. +-- The ipSystemStatsTable provides system wide counters aggregating +-- the traffic counters for all interfaces for a given address type. +-- The ipIfStatsTable provides the same counters but for specific +-- interfaces rather than as an aggregate. +-- +-- Note well: If a system provides both system-wide and interface- +-- specific values, the system-wide value may not be equal to the sum +-- of the interface-specific values across all interfaces due to e.g., +-- dynamic interface creation/deletion. +-- +-- Note well: Both of these tables contain some items that are + +-- represented by two objects, representing the value in either 32 +-- or 64 bits. For those objects, the 32-bit value MUST be the low +-- order 32 bits of the 64-bit value. Also note that the 32-bit +-- counters must be included when the 64-bit counters are included. + +ipTrafficStats OBJECT IDENTIFIER ::= { ip 31 } + +ipSystemStatsTable OBJECT-TYPE + SYNTAX SEQUENCE OF IpSystemStatsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table containing system wide, IP version specific + traffic statistics. This table and the ipIfStatsTable + contain similar objects whose difference is in their + granularity. Where this table contains system wide traffic + statistics, the ipIfStatsTable contains the same statistics + but counted on a per-interface basis." + ::= { ipTrafficStats 1 } + +ipSystemStatsEntry OBJECT-TYPE + SYNTAX IpSystemStatsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A statistics entry containing system-wide objects for a + particular IP version." + INDEX { ipSystemStatsIPVersion } + ::= { ipSystemStatsTable 1 } + +IpSystemStatsEntry ::= SEQUENCE { + ipSystemStatsIPVersion InetVersion, + ipSystemStatsInReceives Counter32, + ipSystemStatsHCInReceives Counter64, + ipSystemStatsInOctets Counter32, + ipSystemStatsHCInOctets Counter64, + ipSystemStatsInHdrErrors Counter32, + ipSystemStatsInNoRoutes Counter32, + ipSystemStatsInAddrErrors Counter32, + ipSystemStatsInUnknownProtos Counter32, + ipSystemStatsInTruncatedPkts Counter32, + ipSystemStatsInForwDatagrams Counter32, + ipSystemStatsHCInForwDatagrams Counter64, + ipSystemStatsReasmReqds Counter32, + ipSystemStatsReasmOKs Counter32, + ipSystemStatsReasmFails Counter32, + ipSystemStatsInDiscards Counter32, + ipSystemStatsInDelivers Counter32, + ipSystemStatsHCInDelivers Counter64, + ipSystemStatsOutRequests Counter32, + ipSystemStatsHCOutRequests Counter64, + ipSystemStatsOutNoRoutes Counter32, + ipSystemStatsOutForwDatagrams Counter32, + ipSystemStatsHCOutForwDatagrams Counter64, + ipSystemStatsOutDiscards Counter32, + ipSystemStatsOutFragReqds Counter32, + ipSystemStatsOutFragOKs Counter32, + ipSystemStatsOutFragFails Counter32, + ipSystemStatsOutFragCreates Counter32, + ipSystemStatsOutTransmits Counter32, + ipSystemStatsHCOutTransmits Counter64, + ipSystemStatsOutOctets Counter32, + ipSystemStatsHCOutOctets Counter64, + ipSystemStatsInMcastPkts Counter32, + ipSystemStatsHCInMcastPkts Counter64, + ipSystemStatsInMcastOctets Counter32, + ipSystemStatsHCInMcastOctets Counter64, + ipSystemStatsOutMcastPkts Counter32, + ipSystemStatsHCOutMcastPkts Counter64, + ipSystemStatsOutMcastOctets Counter32, + ipSystemStatsHCOutMcastOctets Counter64, + ipSystemStatsInBcastPkts Counter32, + ipSystemStatsHCInBcastPkts Counter64, + ipSystemStatsOutBcastPkts Counter32, + ipSystemStatsHCOutBcastPkts Counter64, + ipSystemStatsDiscontinuityTime TimeStamp, + ipSystemStatsRefreshRate Unsigned32 + } + +ipSystemStatsIPVersion OBJECT-TYPE + SYNTAX InetVersion + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The IP version of this row." + ::= { ipSystemStatsEntry 1 } + +-- This object ID is reserved to allow the IDs for this table's objects +-- to align with the objects in the ipIfStatsTable. +-- ::= { ipSystemStatsEntry 2 } + +ipSystemStatsInReceives OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of input IP datagrams received, including + those received in error. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 3 } + +ipSystemStatsHCInReceives OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of input IP datagrams received, including + those received in error. This object counts the same + datagrams as ipSystemStatsInReceives, but allows for larger + values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 4 } + +ipSystemStatsInOctets OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets received in input IP datagrams, + including those received in error. Octets from datagrams + counted in ipSystemStatsInReceives MUST be counted here. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 5 } + +ipSystemStatsHCInOctets OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets received in input IP datagrams, + including those received in error. This object counts the + same octets as ipSystemStatsInOctets, but allows for larger + + values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 6 } + +ipSystemStatsInHdrErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input IP datagrams discarded due to errors in + their IP headers, including version number mismatch, other + format errors, hop count exceeded, errors discovered in + processing their IP options, etc. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 7 } + +ipSystemStatsInNoRoutes OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input IP datagrams discarded because no route + could be found to transmit them to their destination. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 8 } + +ipSystemStatsInAddrErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input IP datagrams discarded because the IP + address in their IP header's destination field was not a + valid address to be received at this entity. This count + includes invalid addresses (e.g., ::0). For entities + that are not IP routers and therefore do not forward + + datagrams, this counter includes datagrams discarded + because the destination address was not a local address. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 9 } + +ipSystemStatsInUnknownProtos OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of locally-addressed IP datagrams received + successfully but discarded because of an unknown or + unsupported protocol. + + When tracking interface statistics, the counter of the + interface to which these datagrams were addressed is + incremented. This interface might not be the same as the + input interface for some of the datagrams. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 10 } + +ipSystemStatsInTruncatedPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input IP datagrams discarded because the + datagram frame didn't carry enough data. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 11 } + +ipSystemStatsInForwDatagrams OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input datagrams for which this entity was not + their final IP destination and for which this entity + attempted to find a route to forward them to that final + destination. In entities that do not act as IP routers, + this counter will include only those datagrams that were + Source-Routed via this entity, and the Source-Route + processing was successful. + + When tracking interface statistics, the counter of the + incoming interface is incremented for each datagram. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 12 } + +ipSystemStatsHCInForwDatagrams OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input datagrams for which this entity was not + their final IP destination and for which this entity + attempted to find a route to forward them to that final + destination. This object counts the same packets as + ipSystemStatsInForwDatagrams, but allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 13 } + +ipSystemStatsReasmReqds OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP fragments received that needed to be + reassembled at this interface. + + When tracking interface statistics, the counter of the + interface to which these fragments were addressed is + incremented. This interface might not be the same as the + input interface for some of the fragments. + + Discontinuities in the value of this counter can occur at + + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 14 } + +ipSystemStatsReasmOKs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP datagrams successfully reassembled. + + When tracking interface statistics, the counter of the + interface to which these datagrams were addressed is + incremented. This interface might not be the same as the + input interface for some of the datagrams. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 15 } + +ipSystemStatsReasmFails OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of failures detected by the IP re-assembly + algorithm (for whatever reason: timed out, errors, etc.). + Note that this is not necessarily a count of discarded IP + fragments since some algorithms (notably the algorithm in + RFC 815) can lose track of the number of fragments by + combining them as they are received. + + When tracking interface statistics, the counter of the + interface to which these fragments were addressed is + incremented. This interface might not be the same as the + input interface for some of the fragments. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 16 } + +ipSystemStatsInDiscards OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input IP datagrams for which no problems were + encountered to prevent their continued processing, but + were discarded (e.g., for lack of buffer space). Note that + this counter does not include any datagrams discarded while + awaiting re-assembly. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 17 } + +ipSystemStatsInDelivers OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of datagrams successfully delivered to IP + user-protocols (including ICMP). + + When tracking interface statistics, the counter of the + interface to which these datagrams were addressed is + incremented. This interface might not be the same as the + input interface for some of the datagrams. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 18 } + +ipSystemStatsHCInDelivers OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of datagrams successfully delivered to IP + user-protocols (including ICMP). This object counts the + same packets as ipSystemStatsInDelivers, but allows for + larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 19 } + +ipSystemStatsOutRequests OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of IP datagrams that local IP user- + protocols (including ICMP) supplied to IP in requests for + transmission. Note that this counter does not include any + datagrams counted in ipSystemStatsOutForwDatagrams. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 20 } + +ipSystemStatsHCOutRequests OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of IP datagrams that local IP user- + protocols (including ICMP) supplied to IP in requests for + transmission. This object counts the same packets as + ipSystemStatsOutRequests, but allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 21 } + +ipSystemStatsOutNoRoutes OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of locally generated IP datagrams discarded + because no route could be found to transmit them to their + destination. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 22 } + +ipSystemStatsOutForwDatagrams OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of datagrams for which this entity was not their + final IP destination and for which it was successful in + finding a path to their final destination. In entities + that do not act as IP routers, this counter will include + only those datagrams that were Source-Routed via this + entity, and the Source-Route processing was successful. + + When tracking interface statistics, the counter of the + outgoing interface is incremented for a successfully + forwarded datagram. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 23 } + +ipSystemStatsHCOutForwDatagrams OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of datagrams for which this entity was not their + final IP destination and for which it was successful in + finding a path to their final destination. This object + counts the same packets as ipSystemStatsOutForwDatagrams, + but allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 24 } + +ipSystemStatsOutDiscards OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of output IP datagrams for which no problem was + encountered to prevent their transmission to their + destination, but were discarded (e.g., for lack of + buffer space). Note that this counter would include + + datagrams counted in ipSystemStatsOutForwDatagrams if any + such datagrams met this (discretionary) discard criterion. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 25 } + +ipSystemStatsOutFragReqds OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP datagrams that would require fragmentation + in order to be transmitted. + + When tracking interface statistics, the counter of the + outgoing interface is incremented for a successfully + fragmented datagram. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 26 } + +ipSystemStatsOutFragOKs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP datagrams that have been successfully + fragmented. + + When tracking interface statistics, the counter of the + outgoing interface is incremented for a successfully + fragmented datagram. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 27 } + +ipSystemStatsOutFragFails OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP datagrams that have been discarded because + they needed to be fragmented but could not be. This + includes IPv4 packets that have the DF bit set and IPv6 + packets that are being forwarded and exceed the outgoing + link MTU. + + When tracking interface statistics, the counter of the + outgoing interface is incremented for an unsuccessfully + fragmented datagram. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 28 } + +ipSystemStatsOutFragCreates OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of output datagram fragments that have been + generated as a result of IP fragmentation. + + When tracking interface statistics, the counter of the + outgoing interface is incremented for a successfully + fragmented datagram. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 29 } + +ipSystemStatsOutTransmits OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of IP datagrams that this entity supplied + to the lower layers for transmission. This includes + datagrams generated locally and those forwarded by this + entity. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 30 } + +ipSystemStatsHCOutTransmits OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of IP datagrams that this entity supplied + to the lower layers for transmission. This object counts + the same datagrams as ipSystemStatsOutTransmits, but allows + for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 31 } + +ipSystemStatsOutOctets OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets in IP datagrams delivered to the + lower layers for transmission. Octets from datagrams + counted in ipSystemStatsOutTransmits MUST be counted here. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 32 } + +ipSystemStatsHCOutOctets OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets in IP datagrams delivered to the + lower layers for transmission. This objects counts the same + octets as ipSystemStatsOutOctets, but allows for larger + values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 33 } + +ipSystemStatsInMcastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP multicast datagrams received. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 34 } + +ipSystemStatsHCInMcastPkts OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP multicast datagrams received. This object + counts the same datagrams as ipSystemStatsInMcastPkts but + allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 35 } + +ipSystemStatsInMcastOctets OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets received in IP multicast + datagrams. Octets from datagrams counted in + ipSystemStatsInMcastPkts MUST be counted here. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 36 } + +ipSystemStatsHCInMcastOctets OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets received in IP multicast + datagrams. This object counts the same octets as + ipSystemStatsInMcastOctets, but allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 37 } + +ipSystemStatsOutMcastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP multicast datagrams transmitted. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 38 } + +ipSystemStatsHCOutMcastPkts OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP multicast datagrams transmitted. This + object counts the same datagrams as + ipSystemStatsOutMcastPkts, but allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 39 } + +ipSystemStatsOutMcastOctets OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets transmitted in IP multicast + datagrams. Octets from datagrams counted in + + ipSystemStatsOutMcastPkts MUST be counted here. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 40 } + +ipSystemStatsHCOutMcastOctets OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets transmitted in IP multicast + datagrams. This object counts the same octets as + ipSystemStatsOutMcastOctets, but allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 41 } + +ipSystemStatsInBcastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP broadcast datagrams received. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 42 } + +ipSystemStatsHCInBcastPkts OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP broadcast datagrams received. This object + counts the same datagrams as ipSystemStatsInBcastPkts but + allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 43 } + +ipSystemStatsOutBcastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP broadcast datagrams transmitted. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 44 } + +ipSystemStatsHCOutBcastPkts OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP broadcast datagrams transmitted. This + object counts the same datagrams as + ipSystemStatsOutBcastPkts, but allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipSystemStatsDiscontinuityTime." + ::= { ipSystemStatsEntry 45 } + +ipSystemStatsDiscontinuityTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime on the most recent occasion at which + any one or more of this entry's counters suffered a + discontinuity. + + If no such discontinuities have occurred since the last re- + initialization of the local management subsystem, then this + object contains a zero value." + ::= { ipSystemStatsEntry 46 } + +ipSystemStatsRefreshRate OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milli-seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The minimum reasonable polling interval for this entry. + This object provides an indication of the minimum amount of + time required to update the counters in this entry." + ::= { ipSystemStatsEntry 47 } + +ipIfStatsTableLastChange OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime on the most recent occasion at which + a row in the ipIfStatsTable was added or deleted. + + If new objects are added to the ipIfStatsTable that require + the ipIfStatsTableLastChange to be updated when they are + modified, they must specify that requirement in their + description clause." + ::= { ipTrafficStats 2 } + +ipIfStatsTable OBJECT-TYPE + SYNTAX SEQUENCE OF IpIfStatsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table containing per-interface traffic statistics. This + table and the ipSystemStatsTable contain similar objects + whose difference is in their granularity. Where this table + contains per-interface statistics, the ipSystemStatsTable + contains the same statistics, but counted on a system wide + basis." + ::= { ipTrafficStats 3 } + +ipIfStatsEntry OBJECT-TYPE + SYNTAX IpIfStatsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An interface statistics entry containing objects for a + particular interface and version of IP." + INDEX { ipIfStatsIPVersion, ipIfStatsIfIndex } + ::= { ipIfStatsTable 1 } + +IpIfStatsEntry ::= SEQUENCE { + ipIfStatsIPVersion InetVersion, + ipIfStatsIfIndex InterfaceIndex, + ipIfStatsInReceives Counter32, + ipIfStatsHCInReceives Counter64, + ipIfStatsInOctets Counter32, + ipIfStatsHCInOctets Counter64, + ipIfStatsInHdrErrors Counter32, + ipIfStatsInNoRoutes Counter32, + ipIfStatsInAddrErrors Counter32, + ipIfStatsInUnknownProtos Counter32, + ipIfStatsInTruncatedPkts Counter32, + ipIfStatsInForwDatagrams Counter32, + ipIfStatsHCInForwDatagrams Counter64, + ipIfStatsReasmReqds Counter32, + ipIfStatsReasmOKs Counter32, + ipIfStatsReasmFails Counter32, + ipIfStatsInDiscards Counter32, + ipIfStatsInDelivers Counter32, + ipIfStatsHCInDelivers Counter64, + ipIfStatsOutRequests Counter32, + ipIfStatsHCOutRequests Counter64, + ipIfStatsOutForwDatagrams Counter32, + ipIfStatsHCOutForwDatagrams Counter64, + ipIfStatsOutDiscards Counter32, + ipIfStatsOutFragReqds Counter32, + ipIfStatsOutFragOKs Counter32, + ipIfStatsOutFragFails Counter32, + ipIfStatsOutFragCreates Counter32, + ipIfStatsOutTransmits Counter32, + ipIfStatsHCOutTransmits Counter64, + ipIfStatsOutOctets Counter32, + ipIfStatsHCOutOctets Counter64, + ipIfStatsInMcastPkts Counter32, + ipIfStatsHCInMcastPkts Counter64, + ipIfStatsInMcastOctets Counter32, + ipIfStatsHCInMcastOctets Counter64, + ipIfStatsOutMcastPkts Counter32, + ipIfStatsHCOutMcastPkts Counter64, + ipIfStatsOutMcastOctets Counter32, + ipIfStatsHCOutMcastOctets Counter64, + ipIfStatsInBcastPkts Counter32, + ipIfStatsHCInBcastPkts Counter64, + ipIfStatsOutBcastPkts Counter32, + ipIfStatsHCOutBcastPkts Counter64, + ipIfStatsDiscontinuityTime TimeStamp, + ipIfStatsRefreshRate Unsigned32 + } + +ipIfStatsIPVersion OBJECT-TYPE + SYNTAX InetVersion + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The IP version of this row." + ::= { ipIfStatsEntry 1 } + +ipIfStatsIfIndex OBJECT-TYPE + SYNTAX InterfaceIndex + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The index value that uniquely identifies the interface to + which this entry is applicable. The interface identified by + a particular value of this index is the same interface as + identified by the same value of the IF-MIB's ifIndex." + ::= { ipIfStatsEntry 2 } + +ipIfStatsInReceives OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of input IP datagrams received, including + those received in error. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 3 } + +ipIfStatsHCInReceives OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of input IP datagrams received, including + those received in error. This object counts the same + datagrams as ipIfStatsInReceives, but allows for larger + values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 4 } + +ipIfStatsInOctets OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets received in input IP datagrams, + including those received in error. Octets from datagrams + counted in ipIfStatsInReceives MUST be counted here. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 5 } + +ipIfStatsHCInOctets OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets received in input IP datagrams, + including those received in error. This object counts the + same octets as ipIfStatsInOctets, but allows for larger + values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 6 } + +ipIfStatsInHdrErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input IP datagrams discarded due to errors in + their IP headers, including version number mismatch, other + format errors, hop count exceeded, errors discovered in + processing their IP options, etc. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 7 } + +ipIfStatsInNoRoutes OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input IP datagrams discarded because no route + could be found to transmit them to their destination. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 8 } + +ipIfStatsInAddrErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input IP datagrams discarded because the IP + address in their IP header's destination field was not a + valid address to be received at this entity. This count + includes invalid addresses (e.g., ::0). For entities that + are not IP routers and therefore do not forward datagrams, + this counter includes datagrams discarded because the + destination address was not a local address. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 9 } + +ipIfStatsInUnknownProtos OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of locally-addressed IP datagrams received + successfully but discarded because of an unknown or + unsupported protocol. + + When tracking interface statistics, the counter of the + interface to which these datagrams were addressed is + incremented. This interface might not be the same as the + input interface for some of the datagrams. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 10 } + +ipIfStatsInTruncatedPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input IP datagrams discarded because the + datagram frame didn't carry enough data. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 11 } + +ipIfStatsInForwDatagrams OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input datagrams for which this entity was not + their final IP destination and for which this entity + attempted to find a route to forward them to that final + destination. In entities that do not act as IP routers, + this counter will include only those datagrams that were + Source-Routed via this entity, and the Source-Route + processing was successful. + + When tracking interface statistics, the counter of the + incoming interface is incremented for each datagram. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 12 } + +ipIfStatsHCInForwDatagrams OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input datagrams for which this entity was not + their final IP destination and for which this entity + attempted to find a route to forward them to that final + destination. This object counts the same packets as + + ipIfStatsInForwDatagrams, but allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 13 } + +ipIfStatsReasmReqds OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP fragments received that needed to be + reassembled at this interface. + + When tracking interface statistics, the counter of the + interface to which these fragments were addressed is + incremented. This interface might not be the same as the + input interface for some of the fragments. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 14 } + +ipIfStatsReasmOKs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP datagrams successfully reassembled. + + When tracking interface statistics, the counter of the + interface to which these datagrams were addressed is + incremented. This interface might not be the same as the + input interface for some of the datagrams. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 15 } + +ipIfStatsReasmFails OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of failures detected by the IP re-assembly + algorithm (for whatever reason: timed out, errors, etc.). + Note that this is not necessarily a count of discarded IP + fragments since some algorithms (notably the algorithm in + RFC 815) can lose track of the number of fragments by + combining them as they are received. + + When tracking interface statistics, the counter of the + interface to which these fragments were addressed is + incremented. This interface might not be the same as the + input interface for some of the fragments. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 16 } + +ipIfStatsInDiscards OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input IP datagrams for which no problems were + encountered to prevent their continued processing, but + were discarded (e.g., for lack of buffer space). Note that + this counter does not include any datagrams discarded while + awaiting re-assembly. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 17 } + +ipIfStatsInDelivers OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of datagrams successfully delivered to IP + user-protocols (including ICMP). + + When tracking interface statistics, the counter of the + interface to which these datagrams were addressed is + incremented. This interface might not be the same as the + + input interface for some of the datagrams. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 18 } + +ipIfStatsHCInDelivers OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of datagrams successfully delivered to IP + user-protocols (including ICMP). This object counts the + same packets as ipIfStatsInDelivers, but allows for larger + values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 19 } + +ipIfStatsOutRequests OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of IP datagrams that local IP user- + protocols (including ICMP) supplied to IP in requests for + transmission. Note that this counter does not include any + datagrams counted in ipIfStatsOutForwDatagrams. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 20 } + +ipIfStatsHCOutRequests OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of IP datagrams that local IP user- + protocols (including ICMP) supplied to IP in requests for + transmission. This object counts the same packets as + + ipIfStatsOutRequests, but allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 21 } + +-- This object ID is reserved to allow the IDs for this table's objects +-- to align with the objects in the ipSystemStatsTable. +-- ::= {ipIfStatsEntry 22} + +ipIfStatsOutForwDatagrams OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of datagrams for which this entity was not their + final IP destination and for which it was successful in + finding a path to their final destination. In entities + that do not act as IP routers, this counter will include + only those datagrams that were Source-Routed via this + entity, and the Source-Route processing was successful. + + When tracking interface statistics, the counter of the + outgoing interface is incremented for a successfully + forwarded datagram. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 23 } + +ipIfStatsHCOutForwDatagrams OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of datagrams for which this entity was not their + final IP destination and for which it was successful in + finding a path to their final destination. This object + counts the same packets as ipIfStatsOutForwDatagrams, but + allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 24 } + +ipIfStatsOutDiscards OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of output IP datagrams for which no problem was + encountered to prevent their transmission to their + destination, but were discarded (e.g., for lack of + buffer space). Note that this counter would include + datagrams counted in ipIfStatsOutForwDatagrams if any such + datagrams met this (discretionary) discard criterion. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 25 } + +ipIfStatsOutFragReqds OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP datagrams that would require fragmentation + in order to be transmitted. + + When tracking interface statistics, the counter of the + outgoing interface is incremented for a successfully + fragmented datagram. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 26 } + +ipIfStatsOutFragOKs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP datagrams that have been successfully + fragmented. + + When tracking interface statistics, the counter of the + + outgoing interface is incremented for a successfully + fragmented datagram. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 27 } + +ipIfStatsOutFragFails OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP datagrams that have been discarded because + they needed to be fragmented but could not be. This + includes IPv4 packets that have the DF bit set and IPv6 + packets that are being forwarded and exceed the outgoing + link MTU. + + When tracking interface statistics, the counter of the + outgoing interface is incremented for an unsuccessfully + fragmented datagram. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 28 } + +ipIfStatsOutFragCreates OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of output datagram fragments that have been + generated as a result of IP fragmentation. + + When tracking interface statistics, the counter of the + outgoing interface is incremented for a successfully + fragmented datagram. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 29 } + +ipIfStatsOutTransmits OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of IP datagrams that this entity supplied + to the lower layers for transmission. This includes + datagrams generated locally and those forwarded by this + entity. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 30 } + +ipIfStatsHCOutTransmits OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of IP datagrams that this entity supplied + to the lower layers for transmission. This object counts + the same datagrams as ipIfStatsOutTransmits, but allows for + larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 31 } + +ipIfStatsOutOctets OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets in IP datagrams delivered to the + lower layers for transmission. Octets from datagrams + counted in ipIfStatsOutTransmits MUST be counted here. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 32 } + +ipIfStatsHCOutOctets OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets in IP datagrams delivered to the + lower layers for transmission. This objects counts the same + octets as ipIfStatsOutOctets, but allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 33 } + +ipIfStatsInMcastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP multicast datagrams received. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 34 } + +ipIfStatsHCInMcastPkts OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP multicast datagrams received. This object + counts the same datagrams as ipIfStatsInMcastPkts, but + allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 35 } + +ipIfStatsInMcastOctets OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets received in IP multicast + + datagrams. Octets from datagrams counted in + ipIfStatsInMcastPkts MUST be counted here. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 36 } + +ipIfStatsHCInMcastOctets OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets received in IP multicast + datagrams. This object counts the same octets as + ipIfStatsInMcastOctets, but allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 37 } + +ipIfStatsOutMcastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP multicast datagrams transmitted. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 38 } + +ipIfStatsHCOutMcastPkts OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP multicast datagrams transmitted. This + object counts the same datagrams as ipIfStatsOutMcastPkts, + but allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 39 } + +ipIfStatsOutMcastOctets OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets transmitted in IP multicast + datagrams. Octets from datagrams counted in + ipIfStatsOutMcastPkts MUST be counted here. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 40 } + +ipIfStatsHCOutMcastOctets OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets transmitted in IP multicast + datagrams. This object counts the same octets as + ipIfStatsOutMcastOctets, but allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 41 } + +ipIfStatsInBcastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP broadcast datagrams received. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 42 } + +ipIfStatsHCInBcastPkts OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP broadcast datagrams received. This object + counts the same datagrams as ipIfStatsInBcastPkts, but + allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 43 } + +ipIfStatsOutBcastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP broadcast datagrams transmitted. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 44 } + +ipIfStatsHCOutBcastPkts OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IP broadcast datagrams transmitted. This + object counts the same datagrams as ipIfStatsOutBcastPkts, + but allows for larger values. + + Discontinuities in the value of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of + ipIfStatsDiscontinuityTime." + ::= { ipIfStatsEntry 45 } + +ipIfStatsDiscontinuityTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime on the most recent occasion at which + + any one or more of this entry's counters suffered a + discontinuity. + + If no such discontinuities have occurred since the last re- + initialization of the local management subsystem, then this + object contains a zero value." + ::= { ipIfStatsEntry 46 } + +ipIfStatsRefreshRate OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milli-seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The minimum reasonable polling interval for this entry. + This object provides an indication of the minimum amount of + time required to update the counters in this entry." + ::= { ipIfStatsEntry 47 } + +-- +-- Internet Address Prefix table +-- + +ipAddressPrefixTable OBJECT-TYPE + SYNTAX SEQUENCE OF IpAddressPrefixEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table allows the user to determine the source of an IP + address or set of IP addresses, and allows other tables to + share the information via pointer rather than by copying. + + For example, when the node configures both a unicast and + anycast address for a prefix, the ipAddressPrefix objects + for those addresses will point to a single row in this + table. + + This table primarily provides support for IPv6 prefixes, and + several of the objects are less meaningful for IPv4. The + table continues to allow IPv4 addresses to allow future + flexibility. In order to promote a common configuration, + this document includes suggestions for default values for + IPv4 prefixes. Each of these values may be overridden if an + object is meaningful to the node. + + All prefixes used by this entity should be included in this + table independent of how the entity learned the prefix. + (This table isn't limited to prefixes learned from router + + advertisements.)" + ::= { ip 32 } + +ipAddressPrefixEntry OBJECT-TYPE + SYNTAX IpAddressPrefixEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry in the ipAddressPrefixTable." + INDEX { ipAddressPrefixIfIndex, ipAddressPrefixType, + ipAddressPrefixPrefix, ipAddressPrefixLength } + ::= { ipAddressPrefixTable 1 } + +IpAddressPrefixEntry ::= SEQUENCE { + ipAddressPrefixIfIndex InterfaceIndex, + ipAddressPrefixType InetAddressType, + ipAddressPrefixPrefix InetAddress, + ipAddressPrefixLength InetAddressPrefixLength, + ipAddressPrefixOrigin IpAddressPrefixOriginTC, + ipAddressPrefixOnLinkFlag TruthValue, + ipAddressPrefixAutonomousFlag TruthValue, + ipAddressPrefixAdvPreferredLifetime Unsigned32, + ipAddressPrefixAdvValidLifetime Unsigned32 + } + +ipAddressPrefixIfIndex OBJECT-TYPE + SYNTAX InterfaceIndex + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The index value that uniquely identifies the interface on + which this prefix is configured. The interface identified + by a particular value of this index is the same interface as + identified by the same value of the IF-MIB's ifIndex." + ::= { ipAddressPrefixEntry 1 } + +ipAddressPrefixType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The address type of ipAddressPrefix." + ::= { ipAddressPrefixEntry 2 } + +ipAddressPrefixPrefix OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The address prefix. The address type of this object is + specified in ipAddressPrefixType. The length of this object + is the standard length for objects of that type (4 or 16 + bytes). Any bits after ipAddressPrefixLength must be zero. + + Implementors need to be aware that, if the size of + ipAddressPrefixPrefix exceeds 114 octets, then OIDS of + instances of columns in this row will have more than 128 + sub-identifiers and cannot be accessed using SNMPv1, + SNMPv2c, or SNMPv3." + ::= { ipAddressPrefixEntry 3 } + +ipAddressPrefixLength OBJECT-TYPE + SYNTAX InetAddressPrefixLength + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The prefix length associated with this prefix. + + The value 0 has no special meaning for this object. It + simply refers to address '::/0'." + ::= { ipAddressPrefixEntry 4 } + +ipAddressPrefixOrigin OBJECT-TYPE + SYNTAX IpAddressPrefixOriginTC + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The origin of this prefix." + ::= { ipAddressPrefixEntry 5 } + +ipAddressPrefixOnLinkFlag OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object has the value 'true(1)', if this prefix can be + used for on-link determination; otherwise, the value is + 'false(2)'. + + The default for IPv4 prefixes is 'true(1)'." + REFERENCE "For IPv6 RFC 2461, especially sections 2 and 4.6.2 and + RFC 2462" + ::= { ipAddressPrefixEntry 6 } + +ipAddressPrefixAutonomousFlag OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Autonomous address configuration flag. When true(1), + indicates that this prefix can be used for autonomous + address configuration (i.e., can be used to form a local + interface address). If false(2), it is not used to auto- + configure a local interface address. + + The default for IPv4 prefixes is 'false(2)'." + REFERENCE "For IPv6 RFC 2461, especially sections 2 and 4.6.2 and + RFC 2462" + ::= { ipAddressPrefixEntry 7 } + +ipAddressPrefixAdvPreferredLifetime OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The remaining length of time, in seconds, that this prefix + will continue to be preferred, i.e., time until deprecation. + + A value of 4,294,967,295 represents infinity. + + The address generated from a deprecated prefix should no + longer be used as a source address in new communications, + but packets received on such an interface are processed as + expected. + + The default for IPv4 prefixes is 4,294,967,295 (infinity)." + REFERENCE "For IPv6 RFC 2461, especially sections 2 and 4.6.2 and + RFC 2462" + ::= { ipAddressPrefixEntry 8 } + +ipAddressPrefixAdvValidLifetime OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The remaining length of time, in seconds, that this prefix + will continue to be valid, i.e., time until invalidation. A + value of 4,294,967,295 represents infinity. + + The address generated from an invalidated prefix should not + appear as the destination or source address of a packet. + + The default for IPv4 prefixes is 4,294,967,295 (infinity)." + REFERENCE "For IPv6 RFC 2461, especially sections 2 and 4.6.2 and + RFC 2462" + ::= { ipAddressPrefixEntry 9 } + +-- +-- Internet Address Table +-- + +ipAddressSpinLock OBJECT-TYPE + SYNTAX TestAndIncr + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "An advisory lock used to allow cooperating SNMP managers to + coordinate their use of the set operation in creating or + modifying rows within this table. + + In order to use this lock to coordinate the use of set + operations, managers should first retrieve + ipAddressTableSpinLock. They should then determine the + appropriate row to create or modify. Finally, they should + issue the appropriate set command, including the retrieved + value of ipAddressSpinLock. If another manager has altered + the table in the meantime, then the value of + ipAddressSpinLock will have changed, and the creation will + fail as it will be specifying an incorrect value for + ipAddressSpinLock. It is suggested, but not required, that + the ipAddressSpinLock be the first var bind for each set of + objects representing a 'row' in a PDU." + ::= { ip 33 } + +ipAddressTable OBJECT-TYPE + SYNTAX SEQUENCE OF IpAddressEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table contains addressing information relevant to the + entity's interfaces. + + This table does not contain multicast address information. + Tables for such information should be contained in multicast + specific MIBs, such as RFC 3019. + + While this table is writable, the user will note that + several objects, such as ipAddressOrigin, are not. The + intention in allowing a user to write to this table is to + allow them to add or remove any entry that isn't + + permanent. The user should be allowed to modify objects + and entries when that would not cause inconsistencies + within the table. Allowing write access to objects, such + as ipAddressOrigin, could allow a user to insert an entry + and then label it incorrectly. + + Note well: When including IPv6 link-local addresses in this + table, the entry must use an InetAddressType of 'ipv6z' in + order to differentiate between the possible interfaces." + ::= { ip 34 } + +ipAddressEntry OBJECT-TYPE + SYNTAX IpAddressEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An address mapping for a particular interface." + INDEX { ipAddressAddrType, ipAddressAddr } + ::= { ipAddressTable 1 } + +IpAddressEntry ::= SEQUENCE { + ipAddressAddrType InetAddressType, + ipAddressAddr InetAddress, + ipAddressIfIndex InterfaceIndex, + ipAddressType INTEGER, + ipAddressPrefix RowPointer, + ipAddressOrigin IpAddressOriginTC, + ipAddressStatus IpAddressStatusTC, + ipAddressCreated TimeStamp, + ipAddressLastChanged TimeStamp, + ipAddressRowStatus RowStatus, + ipAddressStorageType StorageType + } + +ipAddressAddrType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The address type of ipAddressAddr." + ::= { ipAddressEntry 1 } + +ipAddressAddr OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The IP address to which this entry's addressing information + + pertains. The address type of this object is specified in + ipAddressAddrType. + + Implementors need to be aware that if the size of + ipAddressAddr exceeds 116 octets, then OIDS of instances of + columns in this row will have more than 128 sub-identifiers + and cannot be accessed using SNMPv1, SNMPv2c, or SNMPv3." + ::= { ipAddressEntry 2 } + +ipAddressIfIndex OBJECT-TYPE + SYNTAX InterfaceIndex + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The index value that uniquely identifies the interface to + which this entry is applicable. The interface identified by + a particular value of this index is the same interface as + identified by the same value of the IF-MIB's ifIndex." + ::= { ipAddressEntry 3 } + +ipAddressType OBJECT-TYPE + SYNTAX INTEGER { + unicast(1), + anycast(2), + broadcast(3) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The type of address. broadcast(3) is not a valid value for + IPv6 addresses (RFC 3513)." + DEFVAL { unicast } + ::= { ipAddressEntry 4 } + +ipAddressPrefix OBJECT-TYPE + SYNTAX RowPointer + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A pointer to the row in the prefix table to which this + address belongs. May be { 0 0 } if there is no such row." + DEFVAL { zeroDotZero } + ::= { ipAddressEntry 5 } + +ipAddressOrigin OBJECT-TYPE + SYNTAX IpAddressOriginTC + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The origin of the address." + ::= { ipAddressEntry 6 } + +ipAddressStatus OBJECT-TYPE + SYNTAX IpAddressStatusTC + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of the address, describing if the address can be + used for communication. + + In the absence of other information, an IPv4 address is + always preferred(1)." + DEFVAL { preferred } + ::= { ipAddressEntry 7 } + +ipAddressCreated OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time this entry was created. + If this entry was created prior to the last re- + initialization of the local network management subsystem, + then this object contains a zero value." + ::= { ipAddressEntry 8 } + +ipAddressLastChanged OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time this entry was last + updated. If this entry was updated prior to the last re- + initialization of the local network management subsystem, + then this object contains a zero value." + ::= { ipAddressEntry 9 } + +ipAddressRowStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this conceptual row. + + The RowStatus TC requires that this DESCRIPTION clause + states under which circumstances other objects in this row + + can be modified. The value of this object has no effect on + whether other objects in this conceptual row can be + modified. + + A conceptual row can not be made active until the + ipAddressIfIndex has been set to a valid index." + ::= { ipAddressEntry 10 } + +ipAddressStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The storage type for this conceptual row. If this object + has a value of 'permanent', then no other objects are + required to be able to be modified." + DEFVAL { volatile } + ::= { ipAddressEntry 11 } + +-- +-- the Internet Address Translation table +-- + +ipNetToPhysicalTable OBJECT-TYPE + SYNTAX SEQUENCE OF IpNetToPhysicalEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The IP Address Translation table used for mapping from IP + addresses to physical addresses. + + The Address Translation tables contain the IP address to + 'physical' address equivalences. Some interfaces do not use + translation tables for determining address equivalences + (e.g., DDN-X.25 has an algorithmic method); if all + interfaces are of this type, then the Address Translation + table is empty, i.e., has zero entries. + + While many protocols may be used to populate this table, ARP + and Neighbor Discovery are the most likely + options." + REFERENCE "RFC 826 and RFC 2461" + ::= { ip 35 } + +ipNetToPhysicalEntry OBJECT-TYPE + SYNTAX IpNetToPhysicalEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Each entry contains one IP address to `physical' address + equivalence." + INDEX { ipNetToPhysicalIfIndex, + ipNetToPhysicalNetAddressType, + ipNetToPhysicalNetAddress } + ::= { ipNetToPhysicalTable 1 } + +IpNetToPhysicalEntry ::= SEQUENCE { + ipNetToPhysicalIfIndex InterfaceIndex, + ipNetToPhysicalNetAddressType InetAddressType, + ipNetToPhysicalNetAddress InetAddress, + ipNetToPhysicalPhysAddress PhysAddress, + ipNetToPhysicalLastUpdated TimeStamp, + ipNetToPhysicalType INTEGER, + ipNetToPhysicalState INTEGER, + ipNetToPhysicalRowStatus RowStatus + } + +ipNetToPhysicalIfIndex OBJECT-TYPE + SYNTAX InterfaceIndex + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The index value that uniquely identifies the interface to + which this entry is applicable. The interface identified by + a particular value of this index is the same interface as + identified by the same value of the IF-MIB's ifIndex." + ::= { ipNetToPhysicalEntry 1 } + +ipNetToPhysicalNetAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The type of ipNetToPhysicalNetAddress." + ::= { ipNetToPhysicalEntry 2 } + +ipNetToPhysicalNetAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The IP Address corresponding to the media-dependent + `physical' address. The address type of this object is + specified in ipNetToPhysicalAddressType. + + Implementors need to be aware that if the size of + + ipNetToPhysicalNetAddress exceeds 115 octets, then OIDS of + instances of columns in this row will have more than 128 + sub-identifiers and cannot be accessed using SNMPv1, + SNMPv2c, or SNMPv3." + ::= { ipNetToPhysicalEntry 3 } + +ipNetToPhysicalPhysAddress OBJECT-TYPE + SYNTAX PhysAddress (SIZE(0..65535)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The media-dependent `physical' address. + + As the entries in this table are typically not persistent + when this object is written the entity SHOULD NOT save the + change to non-volatile storage." + ::= { ipNetToPhysicalEntry 4 } + +ipNetToPhysicalLastUpdated OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time this entry was last + updated. If this entry was updated prior to the last re- + initialization of the local network management subsystem, + then this object contains a zero value." + ::= { ipNetToPhysicalEntry 5 } + +ipNetToPhysicalType OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- none of the following + invalid(2), -- an invalidated mapping + dynamic(3), + static(4), + local(5) -- local interface + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The type of mapping. + + Setting this object to the value invalid(2) has the effect + of invalidating the corresponding entry in the + ipNetToPhysicalTable. That is, it effectively dis- + associates the interface identified with said entry from the + mapping identified with said entry. It is an + implementation-specific matter as to whether the agent + + removes an invalidated entry from the table. Accordingly, + management stations must be prepared to receive tabular + information from agents that corresponds to entries not + currently in use. Proper interpretation of such entries + requires examination of the relevant ipNetToPhysicalType + object. + + The 'dynamic(3)' type indicates that the IP address to + physical addresses mapping has been dynamically resolved + using e.g., IPv4 ARP or the IPv6 Neighbor Discovery + protocol. + + The 'static(4)' type indicates that the mapping has been + statically configured. Both of these refer to entries that + provide mappings for other entities addresses. + + The 'local(5)' type indicates that the mapping is provided + for an entity's own interface address. + + As the entries in this table are typically not persistent + when this object is written the entity SHOULD NOT save the + change to non-volatile storage." + DEFVAL { static } + ::= { ipNetToPhysicalEntry 6 } + +ipNetToPhysicalState OBJECT-TYPE + SYNTAX INTEGER { + reachable(1), -- confirmed reachability + + stale(2), -- unconfirmed reachability + + delay(3), -- waiting for reachability + -- confirmation before entering + -- the probe state + + probe(4), -- actively probing + + invalid(5), -- an invalidated mapping + + unknown(6), -- state can not be determined + -- for some reason. + + incomplete(7) -- address resolution is being + -- performed. + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Neighbor Unreachability Detection state for the + interface when the address mapping in this entry is used. + If Neighbor Unreachability Detection is not in use (e.g. for + IPv4), this object is always unknown(6)." + REFERENCE "RFC 2461" + ::= { ipNetToPhysicalEntry 7 } + +ipNetToPhysicalRowStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this conceptual row. + + The RowStatus TC requires that this DESCRIPTION clause + states under which circumstances other objects in this row + can be modified. The value of this object has no effect on + whether other objects in this conceptual row can be + modified. + + A conceptual row can not be made active until the + ipNetToPhysicalPhysAddress object has been set. + + Note that if the ipNetToPhysicalType is set to 'invalid', + the managed node may delete the entry independent of the + state of this object." + ::= { ipNetToPhysicalEntry 8 } + +-- +-- The IPv6 Scope Zone Index Table. +-- + +ipv6ScopeZoneIndexTable OBJECT-TYPE + SYNTAX SEQUENCE OF Ipv6ScopeZoneIndexEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table used to describe IPv6 unicast and multicast scope + zones. + + For those objects that have names rather than numbers, the + names were chosen to coincide with the names used in the + IPv6 address architecture document. " + REFERENCE "Section 2.7 of RFC 4291" + ::= { ip 36 } + +ipv6ScopeZoneIndexEntry OBJECT-TYPE + SYNTAX Ipv6ScopeZoneIndexEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Each entry contains the list of scope identifiers on a given + interface." + INDEX { ipv6ScopeZoneIndexIfIndex } + ::= { ipv6ScopeZoneIndexTable 1 } + +Ipv6ScopeZoneIndexEntry ::= SEQUENCE { + ipv6ScopeZoneIndexIfIndex InterfaceIndex, + ipv6ScopeZoneIndexLinkLocal InetZoneIndex, + ipv6ScopeZoneIndex3 InetZoneIndex, + ipv6ScopeZoneIndexAdminLocal InetZoneIndex, + ipv6ScopeZoneIndexSiteLocal InetZoneIndex, + ipv6ScopeZoneIndex6 InetZoneIndex, + ipv6ScopeZoneIndex7 InetZoneIndex, + ipv6ScopeZoneIndexOrganizationLocal InetZoneIndex, + ipv6ScopeZoneIndex9 InetZoneIndex, + ipv6ScopeZoneIndexA InetZoneIndex, + ipv6ScopeZoneIndexB InetZoneIndex, + ipv6ScopeZoneIndexC InetZoneIndex, + ipv6ScopeZoneIndexD InetZoneIndex + } + +ipv6ScopeZoneIndexIfIndex OBJECT-TYPE + SYNTAX InterfaceIndex + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The index value that uniquely identifies the interface to + which these scopes belong. The interface identified by a + particular value of this index is the same interface as + identified by the same value of the IF-MIB's ifIndex." + ::= { ipv6ScopeZoneIndexEntry 1 } + +ipv6ScopeZoneIndexLinkLocal OBJECT-TYPE + SYNTAX InetZoneIndex + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The zone index for the link-local scope on this interface." + ::= { ipv6ScopeZoneIndexEntry 2 } + +ipv6ScopeZoneIndex3 OBJECT-TYPE + SYNTAX InetZoneIndex + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The zone index for scope 3 on this interface." + ::= { ipv6ScopeZoneIndexEntry 3 } + +ipv6ScopeZoneIndexAdminLocal OBJECT-TYPE + SYNTAX InetZoneIndex + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The zone index for the admin-local scope on this interface." + ::= { ipv6ScopeZoneIndexEntry 4 } + +ipv6ScopeZoneIndexSiteLocal OBJECT-TYPE + SYNTAX InetZoneIndex + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The zone index for the site-local scope on this interface." + ::= { ipv6ScopeZoneIndexEntry 5 } + +ipv6ScopeZoneIndex6 OBJECT-TYPE + SYNTAX InetZoneIndex + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The zone index for scope 6 on this interface." + ::= { ipv6ScopeZoneIndexEntry 6 } + +ipv6ScopeZoneIndex7 OBJECT-TYPE + SYNTAX InetZoneIndex + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The zone index for scope 7 on this interface." + ::= { ipv6ScopeZoneIndexEntry 7 } + +ipv6ScopeZoneIndexOrganizationLocal OBJECT-TYPE + SYNTAX InetZoneIndex + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The zone index for the organization-local scope on this + interface." + ::= { ipv6ScopeZoneIndexEntry 8 } + +ipv6ScopeZoneIndex9 OBJECT-TYPE + SYNTAX InetZoneIndex + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The zone index for scope 9 on this interface." + ::= { ipv6ScopeZoneIndexEntry 9 } + +ipv6ScopeZoneIndexA OBJECT-TYPE + SYNTAX InetZoneIndex + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The zone index for scope A on this interface." + ::= { ipv6ScopeZoneIndexEntry 10 } + +ipv6ScopeZoneIndexB OBJECT-TYPE + SYNTAX InetZoneIndex + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The zone index for scope B on this interface." + ::= { ipv6ScopeZoneIndexEntry 11 } + +ipv6ScopeZoneIndexC OBJECT-TYPE + SYNTAX InetZoneIndex + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The zone index for scope C on this interface." + ::= { ipv6ScopeZoneIndexEntry 12 } + +ipv6ScopeZoneIndexD OBJECT-TYPE + SYNTAX InetZoneIndex + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The zone index for scope D on this interface." + ::= { ipv6ScopeZoneIndexEntry 13 } + +-- +-- The Default Router Table +-- This table simply lists the default routers; for more information +-- about routing tables, see the routing MIBs +-- + +ipDefaultRouterTable OBJECT-TYPE + SYNTAX SEQUENCE OF IpDefaultRouterEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table used to describe the default routers known to this + + entity." + ::= { ip 37 } + +ipDefaultRouterEntry OBJECT-TYPE + SYNTAX IpDefaultRouterEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Each entry contains information about a default router known + to this entity." + INDEX {ipDefaultRouterAddressType, ipDefaultRouterAddress, + ipDefaultRouterIfIndex} + ::= { ipDefaultRouterTable 1 } + +IpDefaultRouterEntry ::= SEQUENCE { + ipDefaultRouterAddressType InetAddressType, + ipDefaultRouterAddress InetAddress, + ipDefaultRouterIfIndex InterfaceIndex, + ipDefaultRouterLifetime Unsigned32, + ipDefaultRouterPreference INTEGER + } + +ipDefaultRouterAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The address type for this row." + ::= { ipDefaultRouterEntry 1 } + +ipDefaultRouterAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The IP address of the default router represented by this + row. The address type of this object is specified in + ipDefaultRouterAddressType. + + Implementers need to be aware that if the size of + ipDefaultRouterAddress exceeds 115 octets, then OIDS of + instances of columns in this row will have more than 128 + sub-identifiers and cannot be accessed using SNMPv1, + SNMPv2c, or SNMPv3." + ::= { ipDefaultRouterEntry 2 } + +ipDefaultRouterIfIndex OBJECT-TYPE + SYNTAX InterfaceIndex + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The index value that uniquely identifies the interface by + which the router can be reached. The interface identified + by a particular value of this index is the same interface as + identified by the same value of the IF-MIB's ifIndex." + ::= { ipDefaultRouterEntry 3 } + +ipDefaultRouterLifetime OBJECT-TYPE + SYNTAX Unsigned32 (0..65535) + UNITS "seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The remaining length of time, in seconds, that this router + will continue to be useful as a default router. A value of + zero indicates that it is no longer useful as a default + router. It is left to the implementer of the MIB as to + whether a router with a lifetime of zero is removed from the + list. + + For IPv6, this value should be extracted from the router + advertisement messages." + REFERENCE "For IPv6 RFC 2462 sections 4.2 and 6.3.4" + ::= { ipDefaultRouterEntry 4 } + +ipDefaultRouterPreference OBJECT-TYPE + SYNTAX INTEGER { + reserved (-2), + low (-1), + medium (0), + high (1) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An indication of preference given to this router as a + default router as described in he Default Router + Preferences document. Treating the value as a + 2 bit signed integer allows for simple arithmetic + comparisons. + + For IPv4 routers or IPv6 routers that are not using the + updated router advertisement format, this object is set to + medium (0)." + REFERENCE "RFC 4291, section 2.1" + ::= { ipDefaultRouterEntry 5 } + +-- +-- Configuration information for constructing router advertisements +-- + +ipv6RouterAdvertSpinLock OBJECT-TYPE + SYNTAX TestAndIncr + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "An advisory lock used to allow cooperating SNMP managers to + coordinate their use of the set operation in creating or + modifying rows within this table. + + In order to use this lock to coordinate the use of set + operations, managers should first retrieve + ipv6RouterAdvertSpinLock. They should then determine the + appropriate row to create or modify. Finally, they should + issue the appropriate set command including the retrieved + value of ipv6RouterAdvertSpinLock. If another manager has + altered the table in the meantime, then the value of + ipv6RouterAdvertSpinLock will have changed and the creation + will fail as it will be specifying an incorrect value for + ipv6RouterAdvertSpinLock. It is suggested, but not + required, that the ipv6RouterAdvertSpinLock be the first var + bind for each set of objects representing a 'row' in a PDU." + ::= { ip 38 } + +ipv6RouterAdvertTable OBJECT-TYPE + SYNTAX SEQUENCE OF Ipv6RouterAdvertEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table containing information used to construct router + advertisements." + ::= { ip 39 } + +ipv6RouterAdvertEntry OBJECT-TYPE + SYNTAX Ipv6RouterAdvertEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry containing information used to construct router + advertisements. + + Information in this table is persistent, and when this + object is written, the entity SHOULD save the change to + non-volatile storage." + INDEX { ipv6RouterAdvertIfIndex } + ::= { ipv6RouterAdvertTable 1 } + +Ipv6RouterAdvertEntry ::= SEQUENCE { + ipv6RouterAdvertIfIndex InterfaceIndex, + ipv6RouterAdvertSendAdverts TruthValue, + ipv6RouterAdvertMaxInterval Unsigned32, + ipv6RouterAdvertMinInterval Unsigned32, + ipv6RouterAdvertManagedFlag TruthValue, + ipv6RouterAdvertOtherConfigFlag TruthValue, + ipv6RouterAdvertLinkMTU Unsigned32, + ipv6RouterAdvertReachableTime Unsigned32, + ipv6RouterAdvertRetransmitTime Unsigned32, + ipv6RouterAdvertCurHopLimit Unsigned32, + ipv6RouterAdvertDefaultLifetime Unsigned32, + ipv6RouterAdvertRowStatus RowStatus + } + +ipv6RouterAdvertIfIndex OBJECT-TYPE + SYNTAX InterfaceIndex + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The index value that uniquely identifies the interface on + which router advertisements constructed with this + information will be transmitted. The interface identified + by a particular value of this index is the same interface as + identified by the same value of the IF-MIB's ifIndex." + ::= { ipv6RouterAdvertEntry 1 } + +ipv6RouterAdvertSendAdverts OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A flag indicating whether the router sends periodic + router advertisements and responds to router solicitations + on this interface." + REFERENCE "RFC 2461 Section 6.2.1" + DEFVAL { false } + ::= { ipv6RouterAdvertEntry 2 } + +ipv6RouterAdvertMaxInterval OBJECT-TYPE + SYNTAX Unsigned32 (4..1800) + UNITS "seconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The maximum time allowed between sending unsolicited router + + advertisements from this interface." + REFERENCE "RFC 2461 Section 6.2.1" + DEFVAL { 600 } + ::= { ipv6RouterAdvertEntry 3 } + +ipv6RouterAdvertMinInterval OBJECT-TYPE + SYNTAX Unsigned32 (3..1350) + UNITS "seconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The minimum time allowed between sending unsolicited router + advertisements from this interface. + + The default is 0.33 * ipv6RouterAdvertMaxInterval, however, + in the case of a low value for ipv6RouterAdvertMaxInterval, + the minimum value for this object is restricted to 3." + REFERENCE "RFC 2461 Section 6.2.1" + ::= { ipv6RouterAdvertEntry 4 } + +ipv6RouterAdvertManagedFlag OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The true/false value to be placed into the 'managed address + configuration' flag field in router advertisements sent from + this interface." + REFERENCE "RFC 2461 Section 6.2.1" + DEFVAL { false } + ::= { ipv6RouterAdvertEntry 5 } + +ipv6RouterAdvertOtherConfigFlag OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The true/false value to be placed into the 'other stateful + configuration' flag field in router advertisements sent from + this interface." + REFERENCE "RFC 2461 Section 6.2.1" + DEFVAL { false } + ::= { ipv6RouterAdvertEntry 6 } + +ipv6RouterAdvertLinkMTU OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value to be placed in MTU options sent by the router on + this interface. + + A value of zero indicates that no MTU options are sent." + REFERENCE "RFC 2461 Section 6.2.1" + DEFVAL { 0 } + ::= { ipv6RouterAdvertEntry 7 } + +ipv6RouterAdvertReachableTime OBJECT-TYPE + SYNTAX Unsigned32 (0..3600000) + UNITS "milliseconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value to be placed in the reachable time field in router + advertisement messages sent from this interface. + + A value of zero in the router advertisement indicates that + the advertisement isn't specifying a value for reachable + time." + REFERENCE "RFC 2461 Section 6.2.1" + DEFVAL { 0 } + ::= { ipv6RouterAdvertEntry 8 } + +ipv6RouterAdvertRetransmitTime OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value to be placed in the retransmit timer field in + router advertisements sent from this interface. + + A value of zero in the router advertisement indicates that + the advertisement isn't specifying a value for retrans + time." + REFERENCE "RFC 2461 Section 6.2.1" + DEFVAL { 0 } + ::= { ipv6RouterAdvertEntry 9 } + +ipv6RouterAdvertCurHopLimit OBJECT-TYPE + SYNTAX Unsigned32 (0..255) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The default value to be placed in the current hop limit + field in router advertisements sent from this interface. + + The value should be set to the current diameter of the + Internet. + + A value of zero in the router advertisement indicates that + the advertisement isn't specifying a value for curHopLimit. + + The default should be set to the value specified in the IANA + web pages (www.iana.org) at the time of implementation." + REFERENCE "RFC 2461 Section 6.2.1" + ::= { ipv6RouterAdvertEntry 10 } + +ipv6RouterAdvertDefaultLifetime OBJECT-TYPE + SYNTAX Unsigned32 (0|4..9000) + UNITS "seconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value to be placed in the router lifetime field of + router advertisements sent from this interface. This value + MUST be either 0 or between ipv6RouterAdvertMaxInterval and + 9000 seconds. + + A value of zero indicates that the router is not to be used + as a default router. + + The default is 3 * ipv6RouterAdvertMaxInterval." + REFERENCE "RFC 2461 Section 6.2.1" + ::= { ipv6RouterAdvertEntry 11 } + +ipv6RouterAdvertRowStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this conceptual row. + + As all objects in this conceptual row have default values, a + row can be created and made active by setting this object + appropriately. + + The RowStatus TC requires that this DESCRIPTION clause + states under which circumstances other objects in this row + can be modified. The value of this object has no effect on + whether other objects in this conceptual row can be + modified." + ::= { ipv6RouterAdvertEntry 12 } + +-- + +-- ICMP section +-- + +icmp OBJECT IDENTIFIER ::= { mib-2 5 } + +-- +-- ICMP non-message-specific counters +-- + +-- These object IDs are reserved, as they were used in earlier +-- versions of the MIB module. In theory, OIDs are not assigned +-- until the specification is released as an RFC; however, as some +-- companies may have shipped code based on earlier versions of +-- the MIB, it seems best to reserve these OIDs. +-- ::= { icmp 27 } +-- ::= { icmp 28 } + +icmpStatsTable OBJECT-TYPE + SYNTAX SEQUENCE OF IcmpStatsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table of generic system-wide ICMP counters." + ::= { icmp 29 } + +icmpStatsEntry OBJECT-TYPE + SYNTAX IcmpStatsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A conceptual row in the icmpStatsTable." + INDEX { icmpStatsIPVersion } + ::= { icmpStatsTable 1 } + +IcmpStatsEntry ::= SEQUENCE { + icmpStatsIPVersion InetVersion, + icmpStatsInMsgs Counter32, + icmpStatsInErrors Counter32, + icmpStatsOutMsgs Counter32, + icmpStatsOutErrors Counter32 + } + +icmpStatsIPVersion OBJECT-TYPE + SYNTAX InetVersion + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The IP version of the statistics." + ::= { icmpStatsEntry 1 } + +icmpStatsInMsgs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of ICMP messages that the entity received. + Note that this counter includes all those counted by + icmpStatsInErrors." + ::= { icmpStatsEntry 2 } + +icmpStatsInErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP messages that the entity received but + determined as having ICMP-specific errors (bad ICMP + checksums, bad length, etc.)." + ::= { icmpStatsEntry 3 } + +icmpStatsOutMsgs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of ICMP messages that the entity attempted + to send. Note that this counter includes all those counted + by icmpStatsOutErrors." + ::= { icmpStatsEntry 4 } + +icmpStatsOutErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP messages that this entity did not send + due to problems discovered within ICMP, such as a lack of + buffers. This value should not include errors discovered + outside the ICMP layer, such as the inability of IP to route + the resultant datagram. In some implementations, there may + be no types of error that contribute to this counter's + value." + ::= { icmpStatsEntry 5 } + +-- +-- per-version, per-message type ICMP counters + +-- + +icmpMsgStatsTable OBJECT-TYPE + SYNTAX SEQUENCE OF IcmpMsgStatsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table of system-wide per-version, per-message type ICMP + counters." + ::= { icmp 30 } + +icmpMsgStatsEntry OBJECT-TYPE + SYNTAX IcmpMsgStatsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A conceptual row in the icmpMsgStatsTable. + + The system should track each ICMP type value, even if that + ICMP type is not supported by the system. However, a + given row need not be instantiated unless a message of that + type has been processed, i.e., the row for + icmpMsgStatsType=X MAY be instantiated before but MUST be + instantiated after the first message with Type=X is + received or transmitted. After receiving or transmitting + any succeeding messages with Type=X, the relevant counter + must be incremented." + INDEX { icmpMsgStatsIPVersion, icmpMsgStatsType } + ::= { icmpMsgStatsTable 1 } + +IcmpMsgStatsEntry ::= SEQUENCE { + icmpMsgStatsIPVersion InetVersion, + icmpMsgStatsType Integer32, + icmpMsgStatsInPkts Counter32, + icmpMsgStatsOutPkts Counter32 + } + +icmpMsgStatsIPVersion OBJECT-TYPE + SYNTAX InetVersion + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The IP version of the statistics." + ::= { icmpMsgStatsEntry 1 } + +icmpMsgStatsType OBJECT-TYPE + SYNTAX Integer32 (0..255) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The ICMP type field of the message type being counted by + this row. + + Note that ICMP message types are scoped by the address type + in use." + REFERENCE "http://www.iana.org/assignments/icmp-parameters and + http://www.iana.org/assignments/icmpv6-parameters" + ::= { icmpMsgStatsEntry 2 } + +icmpMsgStatsInPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input packets for this AF and type." + ::= { icmpMsgStatsEntry 3 } + +icmpMsgStatsOutPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of output packets for this AF and type." + ::= { icmpMsgStatsEntry 4 } +-- +-- conformance information +-- + +ipMIBConformance OBJECT IDENTIFIER ::= { ipMIB 2 } + +ipMIBCompliances OBJECT IDENTIFIER ::= { ipMIBConformance 1 } +ipMIBGroups OBJECT IDENTIFIER ::= { ipMIBConformance 2 } + +-- compliance statements +ipMIBCompliance2 MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for systems that implement IP - + either IPv4 or IPv6. + + There are a number of INDEX objects that cannot be + represented in the form of OBJECT clauses in SMIv2, but + for which we have the following compliance requirements, + expressed in OBJECT clause form in this description + clause: + + -- OBJECT ipSystemStatsIPVersion + -- SYNTAX InetVersion {ipv4(1), ipv6(2)} + -- DESCRIPTION + -- This MIB requires support for only IPv4 and IPv6 + -- versions. + -- + -- OBJECT ipIfStatsIPVersion + -- SYNTAX InetVersion {ipv4(1), ipv6(2)} + -- DESCRIPTION + -- This MIB requires support for only IPv4 and IPv6 + -- versions. + -- + -- OBJECT icmpStatsIPVersion + -- SYNTAX InetVersion {ipv4(1), ipv6(2)} + -- DESCRIPTION + -- This MIB requires support for only IPv4 and IPv6 + -- versions. + -- + -- OBJECT icmpMsgStatsIPVersion + -- SYNTAX InetVersion {ipv4(1), ipv6(2)} + -- DESCRIPTION + -- This MIB requires support for only IPv4 and IPv6 + -- versions. + -- + -- OBJECT ipAddressPrefixType + -- SYNTAX InetAddressType {ipv4(1), ipv6(2)} + -- DESCRIPTION + -- This MIB requires support for only global IPv4 and + -- IPv6 address types. + -- + -- OBJECT ipAddressPrefixPrefix + -- SYNTAX InetAddress (Size(4 | 16)) + -- DESCRIPTION + -- This MIB requires support for only global IPv4 and + -- IPv6 addresses and so the size can be either 4 or + -- 16 bytes. + -- + -- OBJECT ipAddressAddrType + -- SYNTAX InetAddressType {ipv4(1), ipv6(2), + -- ipv4z(3), ipv6z(4)} + -- DESCRIPTION + -- This MIB requires support for only global and + -- non-global IPv4 and IPv6 address types. + -- + -- OBJECT ipAddressAddr + -- SYNTAX InetAddress (Size(4 | 8 | 16 | 20)) + -- DESCRIPTION + -- This MIB requires support for only global and + + -- non-global IPv4 and IPv6 addresses and so the size + -- can be 4, 8, 16, or 20 bytes. + -- + -- OBJECT ipNetToPhysicalNetAddressType + -- SYNTAX InetAddressType {ipv4(1), ipv6(2), + -- ipv4z(3), ipv6z(4)} + -- DESCRIPTION + -- This MIB requires support for only global and + -- non-global IPv4 and IPv6 address types. + -- + -- OBJECT ipNetToPhysicalNetAddress + -- SYNTAX InetAddress (Size(4 | 8 | 16 | 20)) + -- DESCRIPTION + -- This MIB requires support for only global and + -- non-global IPv4 and IPv6 addresses and so the size + -- can be 4, 8, 16, or 20 bytes. + -- + -- OBJECT ipDefaultRouterAddressType + -- SYNTAX InetAddressType {ipv4(1), ipv6(2), + -- ipv4z(3), ipv6z(4)} + -- DESCRIPTION + -- This MIB requires support for only global and + -- non-global IPv4 and IPv6 address types. + -- + -- OBJECT ipDefaultRouterAddress + -- SYNTAX InetAddress (Size(4 | 8 | 16 | 20)) + -- DESCRIPTION + -- This MIB requires support for only global and + -- non-global IPv4 and IPv6 addresses and so the size + -- can be 4, 8, 16, or 20 bytes." + + MODULE -- this module + + MANDATORY-GROUPS { ipSystemStatsGroup, ipAddressGroup, + ipNetToPhysicalGroup, ipDefaultRouterGroup, + icmpStatsGroup } + + GROUP ipSystemStatsHCOctetGroup + DESCRIPTION + "This group is mandatory for systems that have an aggregate + bandwidth of greater than 20MB. Including this group does + not allow an entity to neglect the 32 bit versions of these + objects." + + GROUP ipSystemStatsHCPacketGroup + DESCRIPTION + "This group is mandatory for systems that have an aggregate + bandwidth of greater than 650MB. Including this group + + does not allow an entity to neglect the 32 bit versions of + these objects." + + GROUP ipIfStatsGroup + DESCRIPTION + "This group is optional for all systems." + + GROUP ipIfStatsHCOctetGroup + DESCRIPTION + "This group is mandatory for systems that include the + ipIfStatsGroup and include links with bandwidths of greater + than 20MB. Including this group does not allow an entity to + neglect the 32 bit versions of these objects." + + GROUP ipIfStatsHCPacketGroup + DESCRIPTION + "This group is mandatory for systems that include the + ipIfStatsGroup and include links with bandwidths of greater + than 650MB. Including this group does not allow an entity + to neglect the 32 bit versions of these objects." + + GROUP ipv4GeneralGroup + DESCRIPTION + "This group is mandatory for all systems supporting IPv4." + + GROUP ipv4IfGroup + DESCRIPTION + "This group is mandatory for all systems supporting IPv4." + + GROUP ipv4SystemStatsGroup + DESCRIPTION + "This group is mandatory for all systems supporting IPv4." + + GROUP ipv4SystemStatsHCPacketGroup + DESCRIPTION + "This group is mandatory for all systems supporting IPv4 and + that have an aggregate bandwidth of greater than 650MB. + Including this group does not allow an entity to neglect the + 32 bit versions of these objects." + + GROUP ipv4IfStatsGroup + DESCRIPTION + "This group is mandatory for all systems supporting IPv4 and + including the ipIfStatsGroup." + + GROUP ipv4IfStatsHCPacketGroup + DESCRIPTION + "This group is mandatory for all systems supporting IPv4 and + + including the ipIfStatsHCPacketGroup. Including this group + does not allow an entity to neglect the 32 bit versions of + these objects." + + GROUP ipv6GeneralGroup2 + DESCRIPTION + "This group is mandatory for all systems supporting IPv6." + + GROUP ipv6IfGroup + DESCRIPTION + "This group is mandatory for all systems supporting IPv6." + + GROUP ipAddressPrefixGroup + DESCRIPTION + "This group is mandatory for all systems supporting IPv6." + + GROUP ipv6ScopeGroup + DESCRIPTION + "This group is mandatory for all systems supporting IPv6." + + GROUP ipv6RouterAdvertGroup + DESCRIPTION + "This group is mandatory for all IPv6 routers." + + GROUP ipLastChangeGroup + DESCRIPTION + "This group is optional for all agents." + + OBJECT ipv6IpForwarding + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write access to this + object." + + OBJECT ipv6IpDefaultHopLimit + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write access to this + object." + + OBJECT ipv4InterfaceEnableStatus + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write access to this + object." + + OBJECT ipv6InterfaceEnableStatus + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write access to this + object." + + OBJECT ipv6InterfaceForwarding + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write access to this + object." + + OBJECT ipAddressSpinLock + MIN-ACCESS not-accessible + DESCRIPTION + "An agent is not required to provide write access to this + object. However, if an agent provides write access to any + of the other objects in the ipAddressGroup, it SHOULD + provide write access to this object as well." + + OBJECT ipAddressIfIndex + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write or create access + to this object." + + OBJECT ipAddressType + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write or create access + to this object." + + OBJECT ipAddressStatus + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write or create access + to this object." + + OBJECT ipAddressRowStatus + SYNTAX RowStatus { active(1) } + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write or create access + to this object." + + OBJECT ipAddressStorageType + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write or create access + to this object. + + If an agent allows this object to be written or created, it + is not required to allow this object to be set to readOnly, + permanent, or nonVolatile." + + OBJECT ipNetToPhysicalPhysAddress + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write or create access + to this object." + + OBJECT ipNetToPhysicalType + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write or create access + to this object." + + OBJECT ipv6RouterAdvertSpinLock + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write access to this + object. However, if an agent provides write access to + any of the other objects in the ipv6RouterAdvertGroup, it + SHOULD provide write access to this object as well." + + OBJECT ipv6RouterAdvertSendAdverts + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write access to this + object." + + OBJECT ipv6RouterAdvertMaxInterval + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write access to this + object." + + OBJECT ipv6RouterAdvertMinInterval + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write access to this + object." + + OBJECT ipv6RouterAdvertManagedFlag + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write access to this + object." + + OBJECT ipv6RouterAdvertOtherConfigFlag + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write access to this + object." + + OBJECT ipv6RouterAdvertLinkMTU + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write access to this + object." + + OBJECT ipv6RouterAdvertReachableTime + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write access to this + object." + + OBJECT ipv6RouterAdvertRetransmitTime + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write access to this + object." + + OBJECT ipv6RouterAdvertCurHopLimit + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write access to this + object." + + OBJECT ipv6RouterAdvertDefaultLifetime + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write access to this + object." + + OBJECT ipv6RouterAdvertRowStatus + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write or create access + to this object." + ::= { ipMIBCompliances 2 } + +-- units of conformance + +ipv4GeneralGroup OBJECT-GROUP + OBJECTS { ipForwarding, ipDefaultTTL, ipReasmTimeout } + STATUS current + DESCRIPTION + "The group of IPv4-specific objects for basic management of + IPv4 entities." + ::= { ipMIBGroups 3 } + +ipv4IfGroup OBJECT-GROUP + OBJECTS { ipv4InterfaceReasmMaxSize, ipv4InterfaceEnableStatus, + ipv4InterfaceRetransmitTime } + STATUS current + DESCRIPTION + "The group of IPv4-specific objects for basic management of + IPv4 interfaces." + ::= { ipMIBGroups 4 } + +ipv6GeneralGroup2 OBJECT-GROUP + OBJECTS { ipv6IpForwarding, ipv6IpDefaultHopLimit } + STATUS current + DESCRIPTION + "The IPv6 group of objects providing for basic management of + IPv6 entities." + ::= { ipMIBGroups 5 } + +ipv6IfGroup OBJECT-GROUP + OBJECTS { ipv6InterfaceReasmMaxSize, ipv6InterfaceIdentifier, + ipv6InterfaceEnableStatus, ipv6InterfaceReachableTime, + ipv6InterfaceRetransmitTime, ipv6InterfaceForwarding } + STATUS current + DESCRIPTION + "The group of IPv6-specific objects for basic management of + IPv6 interfaces." + ::= { ipMIBGroups 6 } + +ipLastChangeGroup OBJECT-GROUP + OBJECTS { ipv4InterfaceTableLastChange, + ipv6InterfaceTableLastChange, + ipIfStatsTableLastChange } + STATUS current + DESCRIPTION + "The last change objects associated with this MIB. These + objects are optional for all agents. They SHOULD be + implemented on agents where it is possible to determine the + proper values. Where it is not possible to determine the + proper values, for example when the tables are split amongst + several sub-agents using AgentX, the agent MUST NOT + implement these objects to return an incorrect or static + value." + ::= { ipMIBGroups 7 } + +ipSystemStatsGroup OBJECT-GROUP + OBJECTS { ipSystemStatsInReceives, + ipSystemStatsInOctets, + ipSystemStatsInHdrErrors, + ipSystemStatsInNoRoutes, + ipSystemStatsInAddrErrors, + ipSystemStatsInUnknownProtos, + ipSystemStatsInTruncatedPkts, + ipSystemStatsInForwDatagrams, + ipSystemStatsReasmReqds, + ipSystemStatsReasmOKs, + ipSystemStatsReasmFails, + ipSystemStatsInDiscards, + ipSystemStatsInDelivers, + ipSystemStatsOutRequests, + ipSystemStatsOutNoRoutes, + ipSystemStatsOutForwDatagrams, + ipSystemStatsOutDiscards, + ipSystemStatsOutFragReqds, + ipSystemStatsOutFragOKs, + ipSystemStatsOutFragFails, + ipSystemStatsOutFragCreates, + ipSystemStatsOutTransmits, + ipSystemStatsOutOctets, + ipSystemStatsInMcastPkts, + ipSystemStatsInMcastOctets, + ipSystemStatsOutMcastPkts, + ipSystemStatsOutMcastOctets, + ipSystemStatsDiscontinuityTime, + ipSystemStatsRefreshRate } + STATUS current + DESCRIPTION + "IP system wide statistics." + ::= { ipMIBGroups 8 } + +ipv4SystemStatsGroup OBJECT-GROUP + OBJECTS { ipSystemStatsInBcastPkts, ipSystemStatsOutBcastPkts } + STATUS current + DESCRIPTION + "IPv4 only system wide statistics." + ::= { ipMIBGroups 9 } + +ipSystemStatsHCOctetGroup OBJECT-GROUP + OBJECTS { ipSystemStatsHCInOctets, + ipSystemStatsHCOutOctets, + ipSystemStatsHCInMcastOctets, + ipSystemStatsHCOutMcastOctets +} + STATUS current + DESCRIPTION + "IP system wide statistics for systems that may overflow the + standard octet counters within 1 hour." + ::= { ipMIBGroups 10 } + +ipSystemStatsHCPacketGroup OBJECT-GROUP + OBJECTS { ipSystemStatsHCInReceives, + ipSystemStatsHCInForwDatagrams, + ipSystemStatsHCInDelivers, + ipSystemStatsHCOutRequests, + ipSystemStatsHCOutForwDatagrams, + ipSystemStatsHCOutTransmits, + ipSystemStatsHCInMcastPkts, + ipSystemStatsHCOutMcastPkts +} + STATUS current + DESCRIPTION + "IP system wide statistics for systems that may overflow the + standard packet counters within 1 hour." + ::= { ipMIBGroups 11 } + +ipv4SystemStatsHCPacketGroup OBJECT-GROUP + OBJECTS { ipSystemStatsHCInBcastPkts, + ipSystemStatsHCOutBcastPkts } + STATUS current + DESCRIPTION + "IPv4 only system wide statistics for systems that may + overflow the standard packet counters within 1 hour." + ::= { ipMIBGroups 12 } + +ipIfStatsGroup OBJECT-GROUP + OBJECTS { ipIfStatsInReceives, ipIfStatsInOctets, + ipIfStatsInHdrErrors, ipIfStatsInNoRoutes, + ipIfStatsInAddrErrors, ipIfStatsInUnknownProtos, + ipIfStatsInTruncatedPkts, ipIfStatsInForwDatagrams, + ipIfStatsReasmReqds, ipIfStatsReasmOKs, + ipIfStatsReasmFails, ipIfStatsInDiscards, + ipIfStatsInDelivers, ipIfStatsOutRequests, + ipIfStatsOutForwDatagrams, ipIfStatsOutDiscards, + ipIfStatsOutFragReqds, ipIfStatsOutFragOKs, + ipIfStatsOutFragFails, ipIfStatsOutFragCreates, + ipIfStatsOutTransmits, ipIfStatsOutOctets, + ipIfStatsInMcastPkts, ipIfStatsInMcastOctets, + ipIfStatsOutMcastPkts, ipIfStatsOutMcastOctets, + ipIfStatsDiscontinuityTime, ipIfStatsRefreshRate } + STATUS current + DESCRIPTION + "IP per-interface statistics." + ::= { ipMIBGroups 13 } + +ipv4IfStatsGroup OBJECT-GROUP + OBJECTS { ipIfStatsInBcastPkts, ipIfStatsOutBcastPkts } + STATUS current + DESCRIPTION + "IPv4 only per-interface statistics." + ::= { ipMIBGroups 14 } + +ipIfStatsHCOctetGroup OBJECT-GROUP + OBJECTS { ipIfStatsHCInOctets, ipIfStatsHCOutOctets, + ipIfStatsHCInMcastOctets, ipIfStatsHCOutMcastOctets } + STATUS current + DESCRIPTION + "IP per-interfaces statistics for systems that include + interfaces that may overflow the standard octet + counters within 1 hour." + ::= { ipMIBGroups 15 } + +ipIfStatsHCPacketGroup OBJECT-GROUP + OBJECTS { ipIfStatsHCInReceives, ipIfStatsHCInForwDatagrams, + ipIfStatsHCInDelivers, ipIfStatsHCOutRequests, + ipIfStatsHCOutForwDatagrams, ipIfStatsHCOutTransmits, + ipIfStatsHCInMcastPkts, ipIfStatsHCOutMcastPkts } + STATUS current + DESCRIPTION + "IP per-interfaces statistics for systems that include + interfaces that may overflow the standard packet counters + within 1 hour." + ::= { ipMIBGroups 16 } + +ipv4IfStatsHCPacketGroup OBJECT-GROUP + OBJECTS { ipIfStatsHCInBcastPkts, ipIfStatsHCOutBcastPkts } + STATUS current + DESCRIPTION + "IPv4 only per-interface statistics for systems that include + interfaces that may overflow the standard packet counters + within 1 hour." + ::= { ipMIBGroups 17 } + +ipAddressPrefixGroup OBJECT-GROUP + OBJECTS { ipAddressPrefixOrigin, + ipAddressPrefixOnLinkFlag, + ipAddressPrefixAutonomousFlag, + ipAddressPrefixAdvPreferredLifetime, + ipAddressPrefixAdvValidLifetime } + STATUS current + DESCRIPTION + "The group of objects for providing information about address + prefixes used by this node." + ::= { ipMIBGroups 18 } + +ipAddressGroup OBJECT-GROUP + OBJECTS { ipAddressSpinLock, ipAddressIfIndex, + ipAddressType, ipAddressPrefix, + ipAddressOrigin, ipAddressStatus, + ipAddressCreated, ipAddressLastChanged, + ipAddressRowStatus, ipAddressStorageType } + STATUS current + DESCRIPTION + "The group of objects for providing information about the + addresses relevant to this entity's interfaces." + ::= { ipMIBGroups 19 } + +ipNetToPhysicalGroup OBJECT-GROUP + OBJECTS { ipNetToPhysicalPhysAddress, ipNetToPhysicalLastUpdated, + ipNetToPhysicalType, ipNetToPhysicalState, + ipNetToPhysicalRowStatus } + STATUS current + DESCRIPTION + "The group of objects for providing information about the + mappings of network address to physical address known to + this node." + ::= { ipMIBGroups 20 } + +ipv6ScopeGroup OBJECT-GROUP + OBJECTS { ipv6ScopeZoneIndexLinkLocal, + ipv6ScopeZoneIndex3, + ipv6ScopeZoneIndexAdminLocal, + ipv6ScopeZoneIndexSiteLocal, + ipv6ScopeZoneIndex6, + ipv6ScopeZoneIndex7, + ipv6ScopeZoneIndexOrganizationLocal, + ipv6ScopeZoneIndex9, + ipv6ScopeZoneIndexA, + ipv6ScopeZoneIndexB, + ipv6ScopeZoneIndexC, + ipv6ScopeZoneIndexD } + STATUS current + DESCRIPTION + "The group of objects for managing IPv6 scope zones." + ::= { ipMIBGroups 21 } + +ipDefaultRouterGroup OBJECT-GROUP + OBJECTS { ipDefaultRouterLifetime, ipDefaultRouterPreference } + STATUS current + DESCRIPTION + "The group of objects for providing information about default + routers known to this node." + ::= { ipMIBGroups 22 } + +ipv6RouterAdvertGroup OBJECT-GROUP + OBJECTS { ipv6RouterAdvertSpinLock, + ipv6RouterAdvertSendAdverts, + ipv6RouterAdvertMaxInterval, + ipv6RouterAdvertMinInterval, + ipv6RouterAdvertManagedFlag, + ipv6RouterAdvertOtherConfigFlag, + ipv6RouterAdvertLinkMTU, + ipv6RouterAdvertReachableTime, + ipv6RouterAdvertRetransmitTime, + ipv6RouterAdvertCurHopLimit, + ipv6RouterAdvertDefaultLifetime, + ipv6RouterAdvertRowStatus +} + STATUS current + DESCRIPTION + "The group of objects for controlling information advertised + by IPv6 routers." + ::= { ipMIBGroups 23 } + +icmpStatsGroup OBJECT-GROUP + OBJECTS {icmpStatsInMsgs, icmpStatsInErrors, + icmpStatsOutMsgs, icmpStatsOutErrors, + icmpMsgStatsInPkts, icmpMsgStatsOutPkts } + STATUS current + DESCRIPTION + "The group of objects providing ICMP statistics." + ::= { ipMIBGroups 24 } + +-- +-- Deprecated objects +-- + +ipInReceives OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The total number of input datagrams received from + interfaces, including those received in error. + + This object has been deprecated, as a new IP version-neutral + + table has been added. It is loosely replaced by + ipSystemStatsInRecieves." + ::= { ip 3 } + +ipInHdrErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of input datagrams discarded due to errors in + their IPv4 headers, including bad checksums, version number + mismatch, other format errors, time-to-live exceeded, errors + discovered in processing their IPv4 options, etc. + + This object has been deprecated as a new IP version-neutral + table has been added. It is loosely replaced by + ipSystemStatsInHdrErrors." + ::= { ip 4 } + +ipInAddrErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of input datagrams discarded because the IPv4 + address in their IPv4 header's destination field was not a + valid address to be received at this entity. This count + includes invalid addresses (e.g., 0.0.0.0) and addresses of + unsupported Classes (e.g., Class E). For entities which are + not IPv4 routers, and therefore do not forward datagrams, + this counter includes datagrams discarded because the + destination address was not a local address. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by + ipSystemStatsInAddrErrors." + ::= { ip 5 } + +ipForwDatagrams OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of input datagrams for which this entity was not + their final IPv4 destination, as a result of which an + attempt was made to find a route to forward them to that + final destination. In entities which do not act as IPv4 + routers, this counter will include only those packets which + + were Source-Routed via this entity, and the Source-Route + option processing was successful. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by + ipSystemStatsInForwDatagrams." + ::= { ip 6 } + +ipInUnknownProtos OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of locally-addressed datagrams received + successfully but discarded because of an unknown or + unsupported protocol. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by + ipSystemStatsInUnknownProtos." + ::= { ip 7 } + +ipInDiscards OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of input IPv4 datagrams for which no problems + were encountered to prevent their continued processing, but + which were discarded (e.g., for lack of buffer space). Note + that this counter does not include any datagrams discarded + while awaiting re-assembly. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by + ipSystemStatsInDiscards." + ::= { ip 8 } + +ipInDelivers OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The total number of input datagrams successfully delivered + to IPv4 user-protocols (including ICMP). + + This object has been deprecated as a new IP version neutral + table has been added. It is loosely replaced by + + ipSystemStatsIndelivers." + ::= { ip 9 } + +ipOutRequests OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The total number of IPv4 datagrams which local IPv4 user + protocols (including ICMP) supplied to IPv4 in requests for + transmission. Note that this counter does not include any + datagrams counted in ipForwDatagrams. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by + ipSystemStatsOutRequests." + ::= { ip 10 } + +ipOutDiscards OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of output IPv4 datagrams for which no problem was + encountered to prevent their transmission to their + destination, but which were discarded (e.g., for lack of + buffer space). Note that this counter would include + datagrams counted in ipForwDatagrams if any such packets met + this (discretionary) discard criterion. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by + ipSystemStatsOutDiscards." + ::= { ip 11 } + +ipOutNoRoutes OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of IPv4 datagrams discarded because no route + could be found to transmit them to their destination. Note + that this counter includes any packets counted in + ipForwDatagrams which meet this `no-route' criterion. Note + that this includes any datagrams which a host cannot route + because all of its default routers are down. + + This object has been deprecated, as a new IP version-neutral + + table has been added. It is loosely replaced by + ipSystemStatsOutNoRoutes." + ::= { ip 12 } + +ipReasmReqds OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of IPv4 fragments received which needed to be + reassembled at this entity. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by + ipSystemStatsReasmReqds." + ::= { ip 14 } + +ipReasmOKs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of IPv4 datagrams successfully re-assembled. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by + ipSystemStatsReasmOKs." + ::= { ip 15 } + +ipReasmFails OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of failures detected by the IPv4 re-assembly + algorithm (for whatever reason: timed out, errors, etc). + Note that this is not necessarily a count of discarded IPv4 + fragments since some algorithms (notably the algorithm in + RFC 815) can lose track of the number of fragments by + combining them as they are received. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by + ipSystemStatsReasmFails." + ::= { ip 16 } + +ipFragOKs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of IPv4 datagrams that have been successfully + fragmented at this entity. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by + ipSystemStatsOutFragOKs." + ::= { ip 17 } + +ipFragFails OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of IPv4 datagrams that have been discarded + because they needed to be fragmented at this entity but + could not be, e.g., because their Don't Fragment flag was + set. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by + ipSystemStatsOutFragFails." + ::= { ip 18 } + +ipFragCreates OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of IPv4 datagram fragments that have been + generated as a result of fragmentation at this entity. + + This object has been deprecated as a new IP version neutral + table has been added. It is loosely replaced by + ipSystemStatsOutFragCreates." + ::= { ip 19 } + +ipRoutingDiscards OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of routing entries which were chosen to be + discarded even though they are valid. One possible reason + for discarding such an entry could be to free-up buffer + space for other routing entries. + + This object was defined in pre-IPv6 versions of the IP MIB. + It was implicitly IPv4 only, but the original specifications + did not indicate this protocol restriction. In order to + clarify the specifications, this object has been deprecated + and a similar, but more thoroughly clarified, object has + been added to the IP-FORWARD-MIB." + ::= { ip 23 } + +-- the deprecated IPv4 address table + +ipAddrTable OBJECT-TYPE + SYNTAX SEQUENCE OF IpAddrEntry + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "The table of addressing information relevant to this + entity's IPv4 addresses. + + This table has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by the + ipAddressTable although several objects that weren't deemed + useful weren't carried forward while another + (ipAdEntReasmMaxSize) was moved to the ipv4InterfaceTable." + ::= { ip 20 } + +ipAddrEntry OBJECT-TYPE + SYNTAX IpAddrEntry + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "The addressing information for one of this entity's IPv4 + addresses." + INDEX { ipAdEntAddr } + ::= { ipAddrTable 1 } + +IpAddrEntry ::= SEQUENCE { + ipAdEntAddr IpAddress, + ipAdEntIfIndex INTEGER, + ipAdEntNetMask IpAddress, + ipAdEntBcastAddr INTEGER, + ipAdEntReasmMaxSize INTEGER + } + +ipAdEntAddr OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The IPv4 address to which this entry's addressing + information pertains." + ::= { ipAddrEntry 1 } + +ipAdEntIfIndex OBJECT-TYPE + SYNTAX INTEGER (1..2147483647) + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The index value which uniquely identifies the interface to + which this entry is applicable. The interface identified by + a particular value of this index is the same interface as + identified by the same value of the IF-MIB's ifIndex." + ::= { ipAddrEntry 2 } + +ipAdEntNetMask OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The subnet mask associated with the IPv4 address of this + entry. The value of the mask is an IPv4 address with all + the network bits set to 1 and all the hosts bits set to 0." + ::= { ipAddrEntry 3 } + +ipAdEntBcastAddr OBJECT-TYPE + SYNTAX INTEGER (0..1) + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The value of the least-significant bit in the IPv4 broadcast + address used for sending datagrams on the (logical) + interface associated with the IPv4 address of this entry. + For example, when the Internet standard all-ones broadcast + address is used, the value will be 1. This value applies to + both the subnet and network broadcast addresses used by the + entity on this (logical) interface." + ::= { ipAddrEntry 4 } + +ipAdEntReasmMaxSize OBJECT-TYPE + SYNTAX INTEGER (0..65535) + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The size of the largest IPv4 datagram which this entity can + re-assemble from incoming IPv4 fragmented datagrams received + on this interface." + ::= { ipAddrEntry 5 } + +-- the deprecated IPv4 Address Translation table + +-- The Address Translation tables contain the IpAddress to +-- "physical" address equivalences. Some interfaces do not +-- use translation tables for determining address +-- equivalences (e.g., DDN-X.25 has an algorithmic method); +-- if all interfaces are of this type, then the Address +-- Translation table is empty, i.e., has zero entries. + +ipNetToMediaTable OBJECT-TYPE + SYNTAX SEQUENCE OF IpNetToMediaEntry + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "The IPv4 Address Translation table used for mapping from + IPv4 addresses to physical addresses. + + This table has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by the + ipNetToPhysicalTable." + ::= { ip 22 } + +ipNetToMediaEntry OBJECT-TYPE + SYNTAX IpNetToMediaEntry + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "Each entry contains one IpAddress to `physical' address + equivalence." + INDEX { ipNetToMediaIfIndex, + ipNetToMediaNetAddress } + ::= { ipNetToMediaTable 1 } + +IpNetToMediaEntry ::= SEQUENCE { + ipNetToMediaIfIndex INTEGER, + ipNetToMediaPhysAddress PhysAddress, + ipNetToMediaNetAddress IpAddress, + ipNetToMediaType INTEGER + } + +ipNetToMediaIfIndex OBJECT-TYPE + SYNTAX INTEGER (1..2147483647) + MAX-ACCESS read-create + STATUS deprecated + DESCRIPTION + "The interface on which this entry's equivalence is + effective. The interface identified by a particular value + of this index is the same interface as identified by the + + same value of the IF-MIB's ifIndex. + + This object predates the rule limiting index objects to a + max access value of 'not-accessible' and so continues to use + a value of 'read-create'." + ::= { ipNetToMediaEntry 1 } + +ipNetToMediaPhysAddress OBJECT-TYPE + SYNTAX PhysAddress (SIZE(0..65535)) + MAX-ACCESS read-create + STATUS deprecated + DESCRIPTION + "The media-dependent `physical' address. This object should + return 0 when this entry is in the 'incomplete' state. + + As the entries in this table are typically not persistent + when this object is written the entity should not save the + change to non-volatile storage. Note: a stronger + requirement is not used because this object was previously + defined." + ::= { ipNetToMediaEntry 2 } + +ipNetToMediaNetAddress OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-create + STATUS deprecated + DESCRIPTION + "The IpAddress corresponding to the media-dependent + `physical' address. + + This object predates the rule limiting index objects to a + max access value of 'not-accessible' and so continues to use + a value of 'read-create'." + ::= { ipNetToMediaEntry 3 } + +ipNetToMediaType OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- none of the following + invalid(2), -- an invalidated mapping + dynamic(3), + static(4) + } + MAX-ACCESS read-create + STATUS deprecated + DESCRIPTION + "The type of mapping. + + Setting this object to the value invalid(2) has the effect + + of invalidating the corresponding entry in the + ipNetToMediaTable. That is, it effectively dis-associates + the interface identified with said entry from the mapping + identified with said entry. It is an implementation- + specific matter as to whether the agent removes an + invalidated entry from the table. Accordingly, management + stations must be prepared to receive tabular information + from agents that corresponds to entries not currently in + use. Proper interpretation of such entries requires + examination of the relevant ipNetToMediaType object. + + As the entries in this table are typically not persistent + when this object is written the entity should not save the + change to non-volatile storage. Note: a stronger + requirement is not used because this object was previously + defined." + ::= { ipNetToMediaEntry 4 } + +-- the deprecated ICMP group + +icmpInMsgs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The total number of ICMP messages which the entity received. + Note that this counter includes all those counted by + icmpInErrors. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by + icmpStatsInMsgs." + ::= { icmp 1 } + +icmpInErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP messages which the entity received but + determined as having ICMP-specific errors (bad ICMP + checksums, bad length, etc.). + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by + icmpStatsInErrors." + ::= { icmp 2 } + +icmpInDestUnreachs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Destination Unreachable messages + received. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 3 } + +icmpInTimeExcds OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Time Exceeded messages received. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 4 } + +icmpInParmProbs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Parameter Problem messages received. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 5 } + +icmpInSrcQuenchs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Source Quench messages received. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 6 } + +icmpInRedirects OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Redirect messages received. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 7 } + +icmpInEchos OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Echo (request) messages received. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 8 } + +icmpInEchoReps OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Echo Reply messages received. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 9 } + +icmpInTimestamps OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Timestamp (request) messages received. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 10 } + +icmpInTimestampReps OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Timestamp Reply messages received. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 11 } + +icmpInAddrMasks OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Address Mask Request messages received. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 12 } + +icmpInAddrMaskReps OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Address Mask Reply messages received. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 13 } + +icmpOutMsgs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The total number of ICMP messages which this entity + attempted to send. Note that this counter includes all + those counted by icmpOutErrors. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by + icmpStatsOutMsgs." + ::= { icmp 14 } + +icmpOutErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP messages which this entity did not send + due to problems discovered within ICMP, such as a lack of + buffers. This value should not include errors discovered + outside the ICMP layer, such as the inability of IP to route + the resultant datagram. In some implementations, there may + be no types of error which contribute to this counter's + value. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by + icmpStatsOutErrors." + ::= { icmp 15 } + +icmpOutDestUnreachs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Destination Unreachable messages sent. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 16 } + +icmpOutTimeExcds OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Time Exceeded messages sent. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 17 } + +icmpOutParmProbs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Parameter Problem messages sent. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 18 } + +icmpOutSrcQuenchs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Source Quench messages sent. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 19 } + +icmpOutRedirects OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Redirect messages sent. For a host, this + object will always be zero, since hosts do not send + redirects. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 20 } + +icmpOutEchos OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Echo (request) messages sent. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 21 } + +icmpOutEchoReps OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Echo Reply messages sent. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 22 } + +icmpOutTimestamps OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Timestamp (request) messages sent. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 23 } + +icmpOutTimestampReps OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Timestamp Reply messages sent. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 24 } + +icmpOutAddrMasks OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Address Mask Request messages sent. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 25 } + +icmpOutAddrMaskReps OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The number of ICMP Address Mask Reply messages sent. + + This object has been deprecated, as a new IP version-neutral + table has been added. It is loosely replaced by a column in + the icmpMsgStatsTable." + ::= { icmp 26 } + +-- deprecated conformance information +-- deprecated compliance statements + +ipMIBCompliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION + "The compliance statement for systems that implement only + IPv4. For version-independence, this compliance statement + is deprecated in favor of ipMIBCompliance2." + MODULE -- this module + MANDATORY-GROUPS { ipGroup, + icmpGroup } + ::= { ipMIBCompliances 1 } + +-- deprecated units of conformance + +ipGroup OBJECT-GROUP + OBJECTS { ipForwarding, ipDefaultTTL, + ipInReceives, ipInHdrErrors, + ipInAddrErrors, ipForwDatagrams, + ipInUnknownProtos, ipInDiscards, + ipInDelivers, ipOutRequests, + ipOutDiscards, ipOutNoRoutes, + ipReasmTimeout, ipReasmReqds, + ipReasmOKs, ipReasmFails, + ipFragOKs, ipFragFails, + ipFragCreates, ipAdEntAddr, + ipAdEntIfIndex, ipAdEntNetMask, + ipAdEntBcastAddr, ipAdEntReasmMaxSize, + ipNetToMediaIfIndex, ipNetToMediaPhysAddress, + ipNetToMediaNetAddress, ipNetToMediaType, + ipRoutingDiscards +} + STATUS deprecated + DESCRIPTION + "The ip group of objects providing for basic management of IP + entities, exclusive of the management of IP routes. + + As part of the version independence, this group has been + deprecated. " + ::= { ipMIBGroups 1 } + +icmpGroup OBJECT-GROUP + OBJECTS { icmpInMsgs, icmpInErrors, + icmpInDestUnreachs, icmpInTimeExcds, + icmpInParmProbs, icmpInSrcQuenchs, + icmpInRedirects, icmpInEchos, + icmpInEchoReps, icmpInTimestamps, + icmpInTimestampReps, icmpInAddrMasks, + icmpInAddrMaskReps, icmpOutMsgs, + icmpOutErrors, icmpOutDestUnreachs, + icmpOutTimeExcds, icmpOutParmProbs, + icmpOutSrcQuenchs, icmpOutRedirects, + icmpOutEchos, icmpOutEchoReps, + icmpOutTimestamps, icmpOutTimestampReps, + icmpOutAddrMasks, icmpOutAddrMaskReps } + STATUS deprecated + DESCRIPTION + "The icmp group of objects providing ICMP statistics. + + As part of the version independence, this group has been + deprecated. " + ::= { ipMIBGroups 2 } + +END diff --git a/mibs/IPV6-FLOW-LABEL-MIB.txt b/mibs/IPV6-FLOW-LABEL-MIB.txt new file mode 100644 index 0000000..6fb3659 --- /dev/null +++ b/mibs/IPV6-FLOW-LABEL-MIB.txt @@ -0,0 +1,58 @@ +IPV6-FLOW-LABEL-MIB DEFINITIONS ::= BEGIN + +IMPORTS + + MODULE-IDENTITY, mib-2, Integer32 FROM SNMPv2-SMI + TEXTUAL-CONVENTION FROM SNMPv2-TC; + +ipv6FlowLabelMIB MODULE-IDENTITY + + LAST-UPDATED "200308280000Z" -- 28 August 2003 + ORGANIZATION "IETF Operations and Management Area" + CONTACT-INFO "Bert Wijnen (Editor) + Lucent Technologies + Schagen 33 + 3461 GL Linschoten + Netherlands + + Phone: +31 348-407-775 + EMail: bwijnen@lucent.com + + Send comments to <mibs@ops.ietf.org>. + " + DESCRIPTION "This MIB module provides commonly used textual + conventions for IPv6 Flow Labels. + + Copyright (C) The Internet Society (2003). This + version of this MIB module is part of RFC 3595, + see the RFC itself for full legal notices. + " + -- Revision History + + REVISION "200308280000Z" -- 28 August 2003 + DESCRIPTION "Initial version, published as RFC 3595." + ::= { mib-2 103 } + +IPv6FlowLabel ::= TEXTUAL-CONVENTION + DISPLAY-HINT "d" + STATUS current + DESCRIPTION "The flow identifier or Flow Label in an IPv6 + packet header that may be used to discriminate + traffic flows. + " + REFERENCE "Internet Protocol, Version 6 (IPv6) specification, + section 6. RFC 2460. + " + SYNTAX Integer32 (0..1048575) + +IPv6FlowLabelOrAny ::= TEXTUAL-CONVENTION + DISPLAY-HINT "d" + STATUS current + DESCRIPTION "The flow identifier or Flow Label in an IPv6 + packet header that may be used to discriminate + traffic flows. The value of -1 is used to + indicate a wildcard, i.e. any value. + " + SYNTAX Integer32 (-1 | 0..1048575) + +END diff --git a/mibs/IPV6-ICMP-MIB.txt b/mibs/IPV6-ICMP-MIB.txt new file mode 100644 index 0000000..bb66da5 --- /dev/null +++ b/mibs/IPV6-ICMP-MIB.txt @@ -0,0 +1,529 @@ + IPV6-ICMP-MIB DEFINITIONS ::= BEGIN + + IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, + Counter32, mib-2 FROM SNMPv2-SMI + MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF + ipv6IfEntry FROM IPV6-MIB; + + ipv6IcmpMIB MODULE-IDENTITY + LAST-UPDATED "9801082155Z" + ORGANIZATION "IETF IPv6 Working Group" + CONTACT-INFO + " Dimitry Haskin + + Postal: Bay Networks, Inc. + 660 Techology Park Drive. + Billerica, MA 01821 + US + + Tel: +1-978-916-8124 + E-mail: dhaskin@baynetworks.com + + Steve Onishi + + Postal: Bay Networks, Inc. + 3 Federal Street + Billerica, MA 01821 + US + + Tel: +1-978-916-3816 + E-mail: sonishi@baynetworks.com" + DESCRIPTION + "The MIB module for entities implementing + the ICMPv6." + ::= { mib-2 56 } + + -- the ICMPv6 group + + ipv6IcmpMIBObjects OBJECT IDENTIFIER ::= { ipv6IcmpMIB 1 } + + -- Per-interface ICMPv6 statistics table + + ipv6IfIcmpTable OBJECT-TYPE + SYNTAX SEQUENCE OF Ipv6IfIcmpEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "IPv6 ICMP statistics. This table contains statistics + of ICMPv6 messages that are received and sourced by + the entity." + ::= { ipv6IcmpMIBObjects 1 } + + ipv6IfIcmpEntry OBJECT-TYPE + SYNTAX Ipv6IfIcmpEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An ICMPv6 statistics entry containing + objects at a particular IPv6 interface. + + Note that a receiving interface is + the interface to which a given ICMPv6 message + is addressed which may not be necessarily + the input interface for the message. + + Similarly, the sending interface is + the interface that sources a given + ICMP message which is usually but not + necessarily the output interface for the message." + AUGMENTS { ipv6IfEntry } + ::= { ipv6IfIcmpTable 1 } + + Ipv6IfIcmpEntry ::= SEQUENCE { + ipv6IfIcmpInMsgs + Counter32 , + ipv6IfIcmpInErrors + Counter32 , + ipv6IfIcmpInDestUnreachs + Counter32 , + ipv6IfIcmpInAdminProhibs + Counter32 , + ipv6IfIcmpInTimeExcds + Counter32 , + ipv6IfIcmpInParmProblems + Counter32 , + ipv6IfIcmpInPktTooBigs + Counter32 , + ipv6IfIcmpInEchos + Counter32 , + ipv6IfIcmpInEchoReplies + Counter32 , + ipv6IfIcmpInRouterSolicits + Counter32 , + ipv6IfIcmpInRouterAdvertisements + Counter32 , + ipv6IfIcmpInNeighborSolicits + Counter32 , + ipv6IfIcmpInNeighborAdvertisements + Counter32 , + ipv6IfIcmpInRedirects + Counter32 , + ipv6IfIcmpInGroupMembQueries + Counter32 , + ipv6IfIcmpInGroupMembResponses + Counter32 , + ipv6IfIcmpInGroupMembReductions + Counter32 , + ipv6IfIcmpOutMsgs + Counter32 , + ipv6IfIcmpOutErrors + Counter32 , + ipv6IfIcmpOutDestUnreachs + Counter32 , + ipv6IfIcmpOutAdminProhibs + Counter32 , + ipv6IfIcmpOutTimeExcds + Counter32 , + ipv6IfIcmpOutParmProblems + Counter32 , + ipv6IfIcmpOutPktTooBigs + Counter32 , + ipv6IfIcmpOutEchos + Counter32 , + ipv6IfIcmpOutEchoReplies + Counter32 , + ipv6IfIcmpOutRouterSolicits + Counter32 , + ipv6IfIcmpOutRouterAdvertisements + Counter32 , + ipv6IfIcmpOutNeighborSolicits + Counter32 , + ipv6IfIcmpOutNeighborAdvertisements + Counter32 , + ipv6IfIcmpOutRedirects + Counter32 , + ipv6IfIcmpOutGroupMembQueries + Counter32 , + ipv6IfIcmpOutGroupMembResponses + Counter32 , + ipv6IfIcmpOutGroupMembReductions + Counter32 + + } + + ipv6IfIcmpInMsgs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of ICMP messages received + by the interface which includes all those + counted by ipv6IfIcmpInErrors. Note that this + interface is the interface to which the + ICMP messages were addressed which may not be + necessarily the input interface for the messages." + ::= { ipv6IfIcmpEntry 1 } + + ipv6IfIcmpInErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP messages which the interface + received but determined as having ICMP-specific + errors (bad ICMP checksums, bad length, etc.)." + ::= { ipv6IfIcmpEntry 2 } + + ipv6IfIcmpInDestUnreachs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Destination Unreachable + messages received by the interface." + ::= { ipv6IfIcmpEntry 3 } + + ipv6IfIcmpInAdminProhibs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP destination + unreachable/communication administratively + prohibited messages received by the interface." + ::= { ipv6IfIcmpEntry 4 } + + ipv6IfIcmpInTimeExcds OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Time Exceeded messages + received by the interface." + ::= { ipv6IfIcmpEntry 5 } + + ipv6IfIcmpInParmProblems OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Parameter Problem messages + received by the interface." + ::= { ipv6IfIcmpEntry 6 } + + ipv6IfIcmpInPktTooBigs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Packet Too Big messages + received by the interface." + ::= { ipv6IfIcmpEntry 7 } + + ipv6IfIcmpInEchos OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Echo (request) messages + received by the interface." + ::= { ipv6IfIcmpEntry 8 } + + ipv6IfIcmpInEchoReplies OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Echo Reply messages received + by the interface." + ::= { ipv6IfIcmpEntry 9 } + + ipv6IfIcmpInRouterSolicits OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Router Solicit messages + received by the interface." + ::= { ipv6IfIcmpEntry 10 } + + ipv6IfIcmpInRouterAdvertisements OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Router Advertisement messages + received by the interface." + ::= { ipv6IfIcmpEntry 11 } + + ipv6IfIcmpInNeighborSolicits OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Neighbor Solicit messages + received by the interface." + ::= { ipv6IfIcmpEntry 12 } + + ipv6IfIcmpInNeighborAdvertisements OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Neighbor Advertisement + messages received by the interface." + ::= { ipv6IfIcmpEntry 13 } + + ipv6IfIcmpInRedirects OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of Redirect messages received + by the interface." + ::= { ipv6IfIcmpEntry 14 } + + ipv6IfIcmpInGroupMembQueries OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMPv6 Group Membership Query + messages received by the interface." + ::= { ipv6IfIcmpEntry 15} + + ipv6IfIcmpInGroupMembResponses OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMPv6 Group Membership Response messages + received by the interface." + ::= { ipv6IfIcmpEntry 16} + + ipv6IfIcmpInGroupMembReductions OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMPv6 Group Membership Reduction messages + received by the interface." + ::= { ipv6IfIcmpEntry 17} + + ipv6IfIcmpOutMsgs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of ICMP messages which this + interface attempted to send. Note that this counter + includes all those counted by icmpOutErrors." + ::= { ipv6IfIcmpEntry 18 } + + ipv6IfIcmpOutErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP messages which this interface did + not send due to problems discovered within ICMP + such as a lack of buffers. This value should not + include errors discovered outside the ICMP layer + such as the inability of IPv6 to route the resultant + datagram. In some implementations there may be no + types of error which contribute to this counter's + value." + ::= { ipv6IfIcmpEntry 19 } + + ipv6IfIcmpOutDestUnreachs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Destination Unreachable + + messages sent by the interface." + ::= { ipv6IfIcmpEntry 20 } + + ipv6IfIcmpOutAdminProhibs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Number of ICMP dest unreachable/communication + administratively prohibited messages sent." + ::= { ipv6IfIcmpEntry 21 } + + ipv6IfIcmpOutTimeExcds OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Time Exceeded messages sent + by the interface." + ::= { ipv6IfIcmpEntry 22 } + + ipv6IfIcmpOutParmProblems OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Parameter Problem messages + sent by the interface." + ::= { ipv6IfIcmpEntry 23 } + + ipv6IfIcmpOutPktTooBigs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Packet Too Big messages sent + by the interface." + ::= { ipv6IfIcmpEntry 24 } + + ipv6IfIcmpOutEchos OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Echo (request) messages sent + by the interface." + ::= { ipv6IfIcmpEntry 25 } + + ipv6IfIcmpOutEchoReplies OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Echo Reply messages sent + by the interface." + ::= { ipv6IfIcmpEntry 26 } + + ipv6IfIcmpOutRouterSolicits OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Router Solicitation messages + sent by the interface." + ::= { ipv6IfIcmpEntry 27 } + + ipv6IfIcmpOutRouterAdvertisements OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Router Advertisement messages + sent by the interface." + ::= { ipv6IfIcmpEntry 28 } + + ipv6IfIcmpOutNeighborSolicits OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Neighbor Solicitation + messages sent by the interface." + ::= { ipv6IfIcmpEntry 29 } + + ipv6IfIcmpOutNeighborAdvertisements OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMP Neighbor Advertisement + messages sent by the interface." + ::= { ipv6IfIcmpEntry 30 } + + ipv6IfIcmpOutRedirects OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of Redirect messages sent. For + a host, this object will always be zero, + since hosts do not send redirects." + ::= { ipv6IfIcmpEntry 31 } + + ipv6IfIcmpOutGroupMembQueries OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMPv6 Group Membership Query + messages sent." + ::= { ipv6IfIcmpEntry 32} + + ipv6IfIcmpOutGroupMembResponses OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMPv6 Group Membership Response + messages sent." + ::= { ipv6IfIcmpEntry 33} + + ipv6IfIcmpOutGroupMembReductions OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of ICMPv6 Group Membership Reduction + messages sent." + ::= { ipv6IfIcmpEntry 34} + +-- conformance information + +ipv6IcmpConformance OBJECT IDENTIFIER ::= { ipv6IcmpMIB 2 } + +ipv6IcmpCompliances + OBJECT IDENTIFIER ::= { ipv6IcmpConformance 1 } +ipv6IcmpGroups + OBJECT IDENTIFIER ::= { ipv6IcmpConformance 2 } + +-- compliance statements + +ipv6IcmpCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMPv2 entities which + implement ICMPv6." + MODULE -- this module + MANDATORY-GROUPS { ipv6IcmpGroup } + ::= { ipv6IcmpCompliances 1 } + +ipv6IcmpGroup OBJECT-GROUP + OBJECTS { + ipv6IfIcmpInMsgs, + ipv6IfIcmpInErrors, + ipv6IfIcmpInDestUnreachs, + ipv6IfIcmpInAdminProhibs, + ipv6IfIcmpInTimeExcds, + ipv6IfIcmpInParmProblems, + ipv6IfIcmpInPktTooBigs, + ipv6IfIcmpInEchos, + ipv6IfIcmpInEchoReplies, + ipv6IfIcmpInRouterSolicits, + ipv6IfIcmpInRouterAdvertisements, + ipv6IfIcmpInNeighborSolicits, + ipv6IfIcmpInNeighborAdvertisements, + ipv6IfIcmpInRedirects, + ipv6IfIcmpInGroupMembQueries, + ipv6IfIcmpInGroupMembResponses, + ipv6IfIcmpInGroupMembReductions, + ipv6IfIcmpOutMsgs, + ipv6IfIcmpOutErrors, + ipv6IfIcmpOutDestUnreachs, + ipv6IfIcmpOutAdminProhibs, + ipv6IfIcmpOutTimeExcds, + ipv6IfIcmpOutParmProblems, + ipv6IfIcmpOutPktTooBigs, + ipv6IfIcmpOutEchos, + ipv6IfIcmpOutEchoReplies, + ipv6IfIcmpOutRouterSolicits, + ipv6IfIcmpOutRouterAdvertisements, + ipv6IfIcmpOutNeighborSolicits, + ipv6IfIcmpOutNeighborAdvertisements, + ipv6IfIcmpOutRedirects, + ipv6IfIcmpOutGroupMembQueries, + ipv6IfIcmpOutGroupMembResponses, + ipv6IfIcmpOutGroupMembReductions + } + STATUS current + DESCRIPTION + "The ICMPv6 group of objects providing information + specific to ICMPv6." + ::= { ipv6IcmpGroups 1 } + + END diff --git a/mibs/IPV6-MIB.txt b/mibs/IPV6-MIB.txt new file mode 100644 index 0000000..6957af2 --- /dev/null +++ b/mibs/IPV6-MIB.txt @@ -0,0 +1,1443 @@ + IPV6-MIB DEFINITIONS ::= BEGIN + + IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE, + mib-2, Counter32, Unsigned32, Integer32, + Gauge32 FROM SNMPv2-SMI + DisplayString, PhysAddress, TruthValue, TimeStamp, + VariablePointer, RowPointer FROM SNMPv2-TC + MODULE-COMPLIANCE, OBJECT-GROUP, + NOTIFICATION-GROUP FROM SNMPv2-CONF + Ipv6IfIndex, Ipv6Address, Ipv6AddressPrefix, + Ipv6AddressIfIdentifier, + Ipv6IfIndexOrZero FROM IPV6-TC; + + ipv6MIB MODULE-IDENTITY + LAST-UPDATED "9802052155Z" + ORGANIZATION "IETF IPv6 Working Group" + CONTACT-INFO + " Dimitry Haskin + + Postal: Bay Networks, Inc. + 660 Techology Park Drive. + Billerica, MA 01821 + + US + + Tel: +1-978-916-8124 + E-mail: dhaskin@baynetworks.com + + Steve Onishi + + Postal: Bay Networks, Inc. + 3 Federal Street + Billerica, MA 01821 + US + + Tel: +1-978-916-3816 + E-mail: sonishi@baynetworks.com" + DESCRIPTION + "The MIB module for entities implementing the IPv6 + protocol." + ::= { mib-2 55 } + + -- the IPv6 general group + + ipv6MIBObjects OBJECT IDENTIFIER ::= { ipv6MIB 1 } + + ipv6Forwarding OBJECT-TYPE + SYNTAX INTEGER { + forwarding(1), -- acting as a router + + -- NOT acting as + notForwarding(2) -- a router + } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The indication of whether this entity is acting + as an IPv6 router in respect to the forwarding of + datagrams received by, but not addressed to, this + entity. IPv6 routers forward datagrams. IPv6 + hosts do not (except those source-routed via the + host). + + Note that for some managed nodes, this object may + take on only a subset of the values possible. + Accordingly, it is appropriate for an agent to + return a `wrongValue' response if a management + station attempts to change this object to an + inappropriate value." + ::= { ipv6MIBObjects 1 } + + ipv6DefaultHopLimit OBJECT-TYPE + SYNTAX INTEGER(0..255) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The default value inserted into the Hop Limit + field of the IPv6 header of datagrams originated + at this entity, whenever a Hop Limit value is not + supplied by the transport layer protocol." + DEFVAL { 64 } + ::= { ipv6MIBObjects 2 } + +ipv6Interfaces OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IPv6 interfaces (regardless of + their current state) present on this system." + ::= { ipv6MIBObjects 3 } + +ipv6IfTableLastChange OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time of the last + insertion or removal of an entry in the + ipv6IfTable. If the number of entries has been + unchanged since the last re-initialization of + the local network management subsystem, then this + object contains a zero value." + ::= { ipv6MIBObjects 4 } + +-- the IPv6 Interfaces table + +ipv6IfTable OBJECT-TYPE + SYNTAX SEQUENCE OF Ipv6IfEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The IPv6 Interfaces table contains information + on the entity's internetwork-layer interfaces. + An IPv6 interface constitutes a logical network + layer attachment to the layer immediately below + + IPv6 including internet layer 'tunnels', such as + tunnels over IPv4 or IPv6 itself." + ::= { ipv6MIBObjects 5 } + + ipv6IfEntry OBJECT-TYPE + SYNTAX Ipv6IfEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An interface entry containing objects + about a particular IPv6 interface." + INDEX { ipv6IfIndex } + ::= { ipv6IfTable 1 } + + Ipv6IfEntry ::= SEQUENCE { + ipv6IfIndex Ipv6IfIndex, + ipv6IfDescr DisplayString, + ipv6IfLowerLayer VariablePointer, + ipv6IfEffectiveMtu Unsigned32, + ipv6IfReasmMaxSize Unsigned32, + ipv6IfIdentifier Ipv6AddressIfIdentifier, + ipv6IfIdentifierLength INTEGER, + ipv6IfPhysicalAddress PhysAddress, + ipv6IfAdminStatus INTEGER, + ipv6IfOperStatus INTEGER, + ipv6IfLastChange TimeStamp + } + + ipv6IfIndex OBJECT-TYPE + SYNTAX Ipv6IfIndex + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A unique non-zero value identifying + the particular IPv6 interface." + ::= { ipv6IfEntry 1 } + + ipv6IfDescr OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "A textual string containing information about the + interface. This string may be set by the network + management system." + ::= { ipv6IfEntry 2 } + + ipv6IfLowerLayer OBJECT-TYPE + SYNTAX VariablePointer + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object identifies the protocol layer over + which this network interface operates. If this + network interface operates over the data-link + layer, then the value of this object refers to an + instance of ifIndex [6]. If this network interface + operates over an IPv4 interface, the value of this + object refers to an instance of ipAdEntAddr [3]. + + If this network interface operates over another + IPv6 interface, the value of this object refers to + an instance of ipv6IfIndex. If this network + interface is not currently operating over an active + protocol layer, then the value of this object + should be set to the OBJECT ID { 0 0 }." + ::= { ipv6IfEntry 3 } + + ipv6IfEffectiveMtu OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The size of the largest IPv6 packet which can be + sent/received on the interface, specified in + octets." + ::= { ipv6IfEntry 4 } + + ipv6IfReasmMaxSize OBJECT-TYPE + SYNTAX Unsigned32 (0..65535) + UNITS "octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The size of the largest IPv6 datagram which this + entity can re-assemble from incoming IPv6 fragmented + datagrams received on this interface." + ::= { ipv6IfEntry 5 } + + ipv6IfIdentifier OBJECT-TYPE + SYNTAX Ipv6AddressIfIdentifier + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The Interface Identifier for this interface that + + is (at least) unique on the link this interface is + attached to. The Interface Identifier is combined + with an address prefix to form an interface address. + + By default, the Interface Identifier is autoconfigured + according to the rules of the link type this + interface is attached to." + ::= { ipv6IfEntry 6 } + + ipv6IfIdentifierLength OBJECT-TYPE + SYNTAX INTEGER (0..64) + UNITS "bits" + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The length of the Interface Identifier in bits." + ::= { ipv6IfEntry 7 } + + ipv6IfPhysicalAddress OBJECT-TYPE + SYNTAX PhysAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The interface's physical address. For example, for + an IPv6 interface attached to an 802.x link, this + object normally contains a MAC address. Note that + in some cases this address may differ from the + address of the interface's protocol sub-layer. The + interface's media-specific MIB must define the bit + and byte ordering and the format of the value of + this object. For interfaces which do not have such + an address (e.g., a serial line), this object should + contain an octet string of zero length." + ::= { ipv6IfEntry 8 } + +ipv6IfAdminStatus OBJECT-TYPE + SYNTAX INTEGER { + up(1), -- ready to pass packets + down(2) + } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The desired state of the interface. When a managed + system initializes, all IPv6 interfaces start with + ipv6IfAdminStatus in the down(2) state. As a result + of either explicit management action or per + configuration information retained by the managed + + system, ipv6IfAdminStatus is then changed to + the up(1) state (or remains in the down(2) state)." + ::= { ipv6IfEntry 9 } + +ipv6IfOperStatus OBJECT-TYPE + SYNTAX INTEGER { + up(1), -- ready to pass packets + + down(2), + noIfIdentifier(3), -- no interface identifier + + -- status can not be + -- determined for some + unknown(4), -- reason + + -- some component is + notPresent(5) -- missing + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current operational state of the interface. + The noIfIdentifier(3) state indicates that no valid + Interface Identifier is assigned to the interface. + This state usually indicates that the link-local + interface address failed Duplicate Address Detection. + If ipv6IfAdminStatus is down(2) then ipv6IfOperStatus + should be down(2). If ipv6IfAdminStatus is changed + to up(1) then ipv6IfOperStatus should change to up(1) + if the interface is ready to transmit and receive + network traffic; it should remain in the down(2) or + noIfIdentifier(3) state if and only if there is a + fault that prevents it from going to the up(1) state; + it should remain in the notPresent(5) state if + the interface has missing (typically, lower layer) + components." + ::= { ipv6IfEntry 10 } + +ipv6IfLastChange OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time the interface + entered its current operational state. If the + current state was entered prior to the last + re-initialization of the local network management + + subsystem, then this object contains a zero + value." + ::= { ipv6IfEntry 11 } + + -- IPv6 Interface Statistics table + + ipv6IfStatsTable OBJECT-TYPE + SYNTAX SEQUENCE OF Ipv6IfStatsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "IPv6 interface traffic statistics." + ::= { ipv6MIBObjects 6 } + + ipv6IfStatsEntry OBJECT-TYPE + SYNTAX Ipv6IfStatsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An interface statistics entry containing objects + at a particular IPv6 interface." + AUGMENTS { ipv6IfEntry } + ::= { ipv6IfStatsTable 1 } + + Ipv6IfStatsEntry ::= SEQUENCE { + ipv6IfStatsInReceives + Counter32, + ipv6IfStatsInHdrErrors + Counter32, + ipv6IfStatsInTooBigErrors + Counter32, + ipv6IfStatsInNoRoutes + Counter32, + ipv6IfStatsInAddrErrors + Counter32, + ipv6IfStatsInUnknownProtos + Counter32, + ipv6IfStatsInTruncatedPkts + Counter32, + ipv6IfStatsInDiscards + Counter32, + ipv6IfStatsInDelivers + Counter32, + ipv6IfStatsOutForwDatagrams + Counter32, + ipv6IfStatsOutRequests + Counter32, + ipv6IfStatsOutDiscards + + Counter32, + ipv6IfStatsOutFragOKs + Counter32, + ipv6IfStatsOutFragFails + Counter32, + ipv6IfStatsOutFragCreates + Counter32, + ipv6IfStatsReasmReqds + Counter32, + ipv6IfStatsReasmOKs + Counter32, + ipv6IfStatsReasmFails + Counter32, + ipv6IfStatsInMcastPkts + Counter32, + ipv6IfStatsOutMcastPkts + Counter32 + } + + ipv6IfStatsInReceives OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of input datagrams received by + the interface, including those received in error." + ::= { ipv6IfStatsEntry 1 } + + ipv6IfStatsInHdrErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input datagrams discarded due to + errors in their IPv6 headers, including version + number mismatch, other format errors, hop count + exceeded, errors discovered in processing their + IPv6 options, etc." + ::= { ipv6IfStatsEntry 2 } + + ipv6IfStatsInTooBigErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input datagrams that could not be + forwarded because their size exceeded the link MTU + of outgoing interface." + ::= { ipv6IfStatsEntry 3 } + + ipv6IfStatsInNoRoutes OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input datagrams discarded because no + route could be found to transmit them to their + destination." + ::= { ipv6IfStatsEntry 4 } + + ipv6IfStatsInAddrErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input datagrams discarded because + the IPv6 address in their IPv6 header's destination + field was not a valid address to be received at + this entity. This count includes invalid + addresses (e.g., ::0) and unsupported addresses + (e.g., addresses with unallocated prefixes). For + entities which are not IPv6 routers and therefore + do not forward datagrams, this counter includes + datagrams discarded because the destination address + was not a local address." + ::= { ipv6IfStatsEntry 5 } + + ipv6IfStatsInUnknownProtos OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of locally-addressed datagrams + received successfully but discarded because of an + unknown or unsupported protocol. This counter is + incremented at the interface to which these + datagrams were addressed which might not be + necessarily the input interface for some of + the datagrams." + ::= { ipv6IfStatsEntry 6 } + + ipv6IfStatsInTruncatedPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input datagrams discarded because + datagram frame didn't carry enough data." + ::= { ipv6IfStatsEntry 7 } + + ipv6IfStatsInDiscards OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of input IPv6 datagrams for which no + problems were encountered to prevent their + continued processing, but which were discarded + (e.g., for lack of buffer space). Note that this + counter does not include any datagrams discarded + while awaiting re-assembly." + ::= { ipv6IfStatsEntry 8 } + + ipv6IfStatsInDelivers OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of datagrams successfully + delivered to IPv6 user-protocols (including ICMP). + This counter is incremented at the interface to + which these datagrams were addressed which might + not be necessarily the input interface for some of + the datagrams." + ::= { ipv6IfStatsEntry 9 } + + ipv6IfStatsOutForwDatagrams OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of output datagrams which this + entity received and forwarded to their final + destinations. In entities which do not act + as IPv6 routers, this counter will include + only those packets which were Source-Routed + via this entity, and the Source-Route + processing was successful. Note that for + a successfully forwarded datagram the counter + of the outgoing interface is incremented." + ::= { ipv6IfStatsEntry 10 } + + ipv6IfStatsOutRequests OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of IPv6 datagrams which local IPv6 + user-protocols (including ICMP) supplied to IPv6 in + requests for transmission. Note that this counter + does not include any datagrams counted in + ipv6IfStatsOutForwDatagrams." + ::= { ipv6IfStatsEntry 11 } + + ipv6IfStatsOutDiscards OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of output IPv6 datagrams for which no + problem was encountered to prevent their + transmission to their destination, but which were + discarded (e.g., for lack of buffer space). Note + that this counter would include datagrams counted + in ipv6IfStatsOutForwDatagrams if any such packets + met this (discretionary) discard criterion." + ::= { ipv6IfStatsEntry 12 } + + ipv6IfStatsOutFragOKs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IPv6 datagrams that have been + successfully fragmented at this output interface." + ::= { ipv6IfStatsEntry 13 } + + ipv6IfStatsOutFragFails OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IPv6 datagrams that have been + discarded because they needed to be fragmented + at this output interface but could not be." + ::= { ipv6IfStatsEntry 14 } + + ipv6IfStatsOutFragCreates OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of output datagram fragments that have + been generated as a result of fragmentation at + this output interface." + ::= { ipv6IfStatsEntry 15 } + + ipv6IfStatsReasmReqds OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IPv6 fragments received which needed + to be reassembled at this interface. Note that this + counter is incremented at the interface to which + these fragments were addressed which might not + be necessarily the input interface for some of + the fragments." + ::= { ipv6IfStatsEntry 16 } + + ipv6IfStatsReasmOKs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of IPv6 datagrams successfully + reassembled. Note that this counter is incremented + at the interface to which these datagrams were + addressed which might not be necessarily the input + interface for some of the fragments." + ::= { ipv6IfStatsEntry 17 } + + ipv6IfStatsReasmFails OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of failures detected by the IPv6 re- + assembly algorithm (for whatever reason: timed + out, errors, etc.). Note that this is not + necessarily a count of discarded IPv6 fragments + since some algorithms (notably the algorithm in + RFC 815) can lose track of the number of fragments + by combining them as they are received. + This counter is incremented at the interface to which + these fragments were addressed which might not be + necessarily the input interface for some of the + fragments." + ::= { ipv6IfStatsEntry 18 } + + ipv6IfStatsInMcastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of multicast packets received + by the interface" + ::= { ipv6IfStatsEntry 19 } + + ipv6IfStatsOutMcastPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of multicast packets transmitted + by the interface" + ::= { ipv6IfStatsEntry 20 } + + -- Address Prefix table + + -- The IPv6 Address Prefix table contains information on + -- the entity's IPv6 Address Prefixes that are associated + -- with IPv6 interfaces. + + ipv6AddrPrefixTable OBJECT-TYPE + SYNTAX SEQUENCE OF Ipv6AddrPrefixEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The list of IPv6 address prefixes of + IPv6 interfaces." + ::= { ipv6MIBObjects 7 } + + ipv6AddrPrefixEntry OBJECT-TYPE + SYNTAX Ipv6AddrPrefixEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An interface entry containing objects of + a particular IPv6 address prefix." + INDEX { ipv6IfIndex, + ipv6AddrPrefix, + ipv6AddrPrefixLength } + ::= { ipv6AddrPrefixTable 1 } + + Ipv6AddrPrefixEntry ::= SEQUENCE { + + ipv6AddrPrefix Ipv6AddressPrefix, + ipv6AddrPrefixLength INTEGER (0..128), + ipv6AddrPrefixOnLinkFlag TruthValue, + ipv6AddrPrefixAutonomousFlag TruthValue, + ipv6AddrPrefixAdvPreferredLifetime Unsigned32, + ipv6AddrPrefixAdvValidLifetime Unsigned32 + } + + ipv6AddrPrefix OBJECT-TYPE + SYNTAX Ipv6AddressPrefix + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The prefix associated with the this interface." + ::= { ipv6AddrPrefixEntry 1 } + + ipv6AddrPrefixLength OBJECT-TYPE + SYNTAX INTEGER (0..128) + UNITS "bits" + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The length of the prefix (in bits)." + ::= { ipv6AddrPrefixEntry 2 } + + ipv6AddrPrefixOnLinkFlag OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object has the value 'true(1)', if this + prefix can be used for on-link determination + and the value 'false(2)' otherwise." + ::= { ipv6AddrPrefixEntry 3 } + + ipv6AddrPrefixAutonomousFlag OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Autonomous address configuration flag. When + true(1), indicates that this prefix can be used + for autonomous address configuration (i.e. can + be used to form a local interface address). + If false(2), it is not used to autoconfigure + a local interface address." + ::= { ipv6AddrPrefixEntry 4 } + + ipv6AddrPrefixAdvPreferredLifetime OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "It is the length of time in seconds that this + prefix will remain preferred, i.e. time until + deprecation. A value of 4,294,967,295 represents + infinity. + + The address generated from a deprecated prefix + should no longer be used as a source address in + new communications, but packets received on such + an interface are processed as expected." + ::= { ipv6AddrPrefixEntry 5 } + + ipv6AddrPrefixAdvValidLifetime OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "It is the length of time in seconds that this + prefix will remain valid, i.e. time until + invalidation. A value of 4,294,967,295 represents + infinity. + + The address generated from an invalidated prefix + should not appear as the destination or source + address of a packet." + ::= { ipv6AddrPrefixEntry 6 } + + -- the IPv6 Address table + + -- The IPv6 address table contains this node's IPv6 + -- addressing information. + + ipv6AddrTable OBJECT-TYPE + SYNTAX SEQUENCE OF Ipv6AddrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table of addressing information relevant to + this node's interface addresses." + ::= { ipv6MIBObjects 8 } + + ipv6AddrEntry OBJECT-TYPE + SYNTAX Ipv6AddrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The addressing information for one of this + node's interface addresses." + INDEX { ipv6IfIndex, ipv6AddrAddress } + ::= { ipv6AddrTable 1 } + + Ipv6AddrEntry ::= + SEQUENCE { + ipv6AddrAddress Ipv6Address, + ipv6AddrPfxLength INTEGER, + ipv6AddrType INTEGER, + ipv6AddrAnycastFlag TruthValue, + ipv6AddrStatus INTEGER + } + + ipv6AddrAddress OBJECT-TYPE + SYNTAX Ipv6Address + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The IPv6 address to which this entry's addressing + information pertains." + ::= { ipv6AddrEntry 1 } + + ipv6AddrPfxLength OBJECT-TYPE + SYNTAX INTEGER(0..128) + UNITS "bits" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The length of the prefix (in bits) associated with + the IPv6 address of this entry." + ::= { ipv6AddrEntry 2 } + + ipv6AddrType OBJECT-TYPE + SYNTAX INTEGER { + -- address has been formed + -- using stateless + stateless(1), -- autoconfiguration + + -- address has been acquired + -- by stateful means + -- (e.g. DHCPv6, manual + stateful(2), -- configuration) + + -- type can not be determined + unknown(3) -- for some reason. + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The type of address. Note that 'stateless(1)' + refers to an address that was statelessly + autoconfigured; 'stateful(2)' refers to a address + which was acquired by via a stateful protocol + (e.g. DHCPv6, manual configuration)." + ::= { ipv6AddrEntry 3 } + + ipv6AddrAnycastFlag OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object has the value 'true(1)', if this + address is an anycast address and the value + 'false(2)' otherwise." + ::= { ipv6AddrEntry 4 } + + ipv6AddrStatus OBJECT-TYPE + SYNTAX INTEGER { + preferred(1), + deprecated(2), + invalid(3), + inaccessible(4), + unknown(5) -- status can not be determined + -- for some reason. + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Address status. The preferred(1) state indicates + that this is a valid address that can appear as + the destination or source address of a packet. + The deprecated(2) state indicates that this is + a valid but deprecated address that should no longer + be used as a source address in new communications, + but packets addressed to such an address are + processed as expected. The invalid(3) state indicates + that this is not valid address which should not + + appear as the destination or source address of + a packet. The inaccessible(4) state indicates that + the address is not accessible because the interface + to which this address is assigned is not operational." + ::= { ipv6AddrEntry 5 } + + -- IPv6 Routing objects + + ipv6RouteNumber OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of current ipv6RouteTable entries. + This is primarily to avoid having to read + the table in order to determine this number." + ::= { ipv6MIBObjects 9 } + + ipv6DiscardedRoutes OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of routing entries which were chosen + to be discarded even though they are valid. One + possible reason for discarding such an entry could + be to free-up buffer space for other routing + entries." + ::= { ipv6MIBObjects 10 } + + -- IPv6 Routing table + + ipv6RouteTable OBJECT-TYPE + SYNTAX SEQUENCE OF Ipv6RouteEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "IPv6 Routing table. This table contains + an entry for each valid IPv6 unicast route + that can be used for packet forwarding + determination." + ::= { ipv6MIBObjects 11 } + + ipv6RouteEntry OBJECT-TYPE + SYNTAX Ipv6RouteEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A routing entry." + INDEX { ipv6RouteDest, + ipv6RoutePfxLength, + ipv6RouteIndex } + ::= { ipv6RouteTable 1 } + + Ipv6RouteEntry ::= SEQUENCE { + ipv6RouteDest Ipv6Address, + ipv6RoutePfxLength INTEGER, + ipv6RouteIndex Unsigned32, + ipv6RouteIfIndex Ipv6IfIndexOrZero, + ipv6RouteNextHop Ipv6Address, + ipv6RouteType INTEGER, + ipv6RouteProtocol INTEGER, + ipv6RoutePolicy Integer32, + ipv6RouteAge Unsigned32, + ipv6RouteNextHopRDI Unsigned32, + ipv6RouteMetric Unsigned32, + ipv6RouteWeight Unsigned32, + ipv6RouteInfo RowPointer, + ipv6RouteValid TruthValue + } + + ipv6RouteDest OBJECT-TYPE + SYNTAX Ipv6Address + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The destination IPv6 address of this route. + This object may not take a Multicast address + value." + ::= { ipv6RouteEntry 1 } + + ipv6RoutePfxLength OBJECT-TYPE + SYNTAX INTEGER(0..128) + UNITS "bits" + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Indicates the prefix length of the destination + address." + ::= { ipv6RouteEntry 2 } + + ipv6RouteIndex OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The value which uniquely identifies the route + among the routes to the same network layer + destination. The way this value is chosen is + implementation specific but it must be unique for + ipv6RouteDest/ipv6RoutePfxLength pair and remain + constant for the life of the route." + ::= { ipv6RouteEntry 3 } + + ipv6RouteIfIndex OBJECT-TYPE + SYNTAX Ipv6IfIndexOrZero + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The index value which uniquely identifies the local + interface through which the next hop of this + route should be reached. The interface identified + by a particular value of this index is the same + interface as identified by the same value of + ipv6IfIndex. For routes of the discard type this + value can be zero." + ::= { ipv6RouteEntry 4 } + + ipv6RouteNextHop OBJECT-TYPE + SYNTAX Ipv6Address + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "On remote routes, the address of the next + system en route; otherwise, ::0 + ('00000000000000000000000000000000'H in ASN.1 + string representation)." + ::= { ipv6RouteEntry 5 } + + ipv6RouteType OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- none of the following + + -- an route indicating that + -- packets to destinations + -- matching this route are + discard(2), -- to be discarded + + -- route to directly + local(3), -- connected (sub-)network + + -- route to a remote + + remote(4) -- destination + + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The type of route. Note that 'local(3)' refers + to a route for which the next hop is the final + destination; 'remote(4)' refers to a route for + which the next hop is not the final + destination; 'discard(2)' refers to a route + indicating that packets to destinations matching + this route are to be discarded (sometimes called + black-hole route)." + ::= { ipv6RouteEntry 6 } + + ipv6RouteProtocol OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- none of the following + + -- non-protocol information, + -- e.g., manually configured + local(2), -- entries + + netmgmt(3), -- static route + + -- obtained via Neighbor + -- Discovery protocol, + ndisc(4), -- e.g., result of Redirect + + -- the following are all + -- dynamic routing protocols + rip(5), -- RIPng + ospf(6), -- Open Shortest Path First + bgp(7), -- Border Gateway Protocol + idrp(8), -- InterDomain Routing Protocol + igrp(9) -- InterGateway Routing Protocol + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The routing mechanism via which this route was + learned." + ::= { ipv6RouteEntry 7 } + + ipv6RoutePolicy OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The general set of conditions that would cause the + selection of one multipath route (set of next hops + for a given destination) is referred to as 'policy'. + Unless the mechanism indicated by ipv6RouteProtocol + specified otherwise, the policy specifier is the + 8-bit Traffic Class field of the IPv6 packet header + that is zero extended at the left to a 32-bit value. + + Protocols defining 'policy' otherwise must either + define a set of values which are valid for + this object or must implement an integer- + instanced policy table for which this object's + value acts as an index." + ::= { ipv6RouteEntry 8 } + + ipv6RouteAge OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of seconds since this route was last + updated or otherwise determined to be correct. + Note that no semantics of `too old' can be implied + except through knowledge of the routing protocol + by which the route was learned." + ::= { ipv6RouteEntry 9 } + + ipv6RouteNextHopRDI OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Routing Domain ID of the Next Hop. + The semantics of this object are determined by + the routing-protocol specified in the route's + ipv6RouteProtocol value. When this object is + unknown or not relevant its value should be set + to zero." + ::= { ipv6RouteEntry 10 } + + ipv6RouteMetric OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The routing metric for this route. The + semantics of this metric are determined by the + routing protocol specified in the route's + ipv6RouteProtocol value. When this is unknown + or not relevant to the protocol indicated by + ipv6RouteProtocol, the object value should be + set to its maximum value (4,294,967,295)." + ::= { ipv6RouteEntry 11 } + + ipv6RouteWeight OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The system internal weight value for this route. + The semantics of this value are determined by + the implementation specific rules. Generally, + within routes with the same ipv6RoutePolicy value, + the lower the weight value the more preferred is + the route." + ::= { ipv6RouteEntry 12 } + + ipv6RouteInfo OBJECT-TYPE + SYNTAX RowPointer + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A reference to MIB definitions specific to the + particular routing protocol which is responsible + for this route, as determined by the value + specified in the route's ipv6RouteProto value. + If this information is not present, its value + should be set to the OBJECT ID { 0 0 }, + which is a syntactically valid object identifier, + and any implementation conforming to ASN.1 + and the Basic Encoding Rules must be able to + generate and recognize this value." + ::= { ipv6RouteEntry 13 } + + ipv6RouteValid OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "Setting this object to the value 'false(2)' has + the effect of invalidating the corresponding entry + in the ipv6RouteTable object. That is, it + effectively disassociates the destination + + identified with said entry from the route + identified with said entry. It is an + implementation-specific matter as to whether the + agent removes an invalidated entry from the table. + Accordingly, management stations must be prepared + to receive tabular information from agents that + corresponds to entries not currently in use. + Proper interpretation of such entries requires + examination of the relevant ipv6RouteValid + object." + DEFVAL { true } + ::= { ipv6RouteEntry 14 } + + -- IPv6 Address Translation table + + ipv6NetToMediaTable OBJECT-TYPE + SYNTAX SEQUENCE OF Ipv6NetToMediaEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The IPv6 Address Translation table used for + mapping from IPv6 addresses to physical addresses. + + The IPv6 address translation table contain the + Ipv6Address to `physical' address equivalencies. + Some interfaces do not use translation tables + for determining address equivalencies; if all + interfaces are of this type, then the Address + Translation table is empty, i.e., has zero + entries." + ::= { ipv6MIBObjects 12 } + + ipv6NetToMediaEntry OBJECT-TYPE + SYNTAX Ipv6NetToMediaEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Each entry contains one IPv6 address to `physical' + address equivalence." + INDEX { ipv6IfIndex, + ipv6NetToMediaNetAddress } + ::= { ipv6NetToMediaTable 1 } + + Ipv6NetToMediaEntry ::= SEQUENCE { + ipv6NetToMediaNetAddress + Ipv6Address, + ipv6NetToMediaPhysAddress + + PhysAddress, + ipv6NetToMediaType + INTEGER, + ipv6IfNetToMediaState + INTEGER, + ipv6IfNetToMediaLastUpdated + TimeStamp, + ipv6NetToMediaValid + TruthValue + } + + ipv6NetToMediaNetAddress OBJECT-TYPE + SYNTAX Ipv6Address + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The IPv6 Address corresponding to + the media-dependent `physical' address." + ::= { ipv6NetToMediaEntry 1 } + + ipv6NetToMediaPhysAddress OBJECT-TYPE + SYNTAX PhysAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The media-dependent `physical' address." + ::= { ipv6NetToMediaEntry 2 } + + ipv6NetToMediaType OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- none of the following + dynamic(2), -- dynamically resolved + static(3), -- statically configured + local(4) -- local interface + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The type of the mapping. The 'dynamic(2)' type + indicates that the IPv6 address to physical + addresses mapping has been dynamically + resolved using the IPv6 Neighbor Discovery + protocol. The static(3)' types indicates that + the mapping has been statically configured. + The local(4) indicates that the mapping is + provided for an entity's own interface address." + ::= { ipv6NetToMediaEntry 3 } + +ipv6IfNetToMediaState OBJECT-TYPE + SYNTAX INTEGER { + reachable(1), -- confirmed reachability + + stale(2), -- unconfirmed reachability + + delay(3), -- waiting for reachability + -- confirmation before entering + -- the probe state + + probe(4), -- actively probing + + invalid(5), -- an invalidated mapping + + unknown(6) -- state can not be determined + -- for some reason. + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Neighbor Unreachability Detection [8] state + for the interface when the address mapping in + this entry is used." + ::= { ipv6NetToMediaEntry 4 } + +ipv6IfNetToMediaLastUpdated OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time this entry + was last updated. If this entry was updated prior + to the last re-initialization of the local network + management subsystem, then this object contains + a zero value." + ::= { ipv6NetToMediaEntry 5 } + + ipv6NetToMediaValid OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "Setting this object to the value 'false(2)' has + the effect of invalidating the corresponding entry + in the ipv6NetToMediaTable. That is, it effectively + disassociates the interface identified with said + entry from the mapping identified with said entry. + It is an implementation-specific matter as to + + whether the agent removes an invalidated entry + from the table. Accordingly, management stations + must be prepared to receive tabular information + from agents that corresponds to entries not + currently in use. Proper interpretation of such + entries requires examination of the relevant + ipv6NetToMediaValid object." + DEFVAL { true } + ::= { ipv6NetToMediaEntry 6 } + +-- definition of IPv6-related notifications. +-- Note that we need ipv6NotificationPrefix with the 0 +-- sub-identifier to make this MIB to translate to +-- an SNMPv1 format in a reversible way. For example +-- it is needed for proxies that convert SNMPv1 traps +-- to SNMPv2 notifications without MIB knowledge. + +ipv6Notifications OBJECT IDENTIFIER + ::= { ipv6MIB 2 } +ipv6NotificationPrefix OBJECT IDENTIFIER + ::= { ipv6Notifications 0 } + +ipv6IfStateChange NOTIFICATION-TYPE + OBJECTS { + ipv6IfDescr, + ipv6IfOperStatus -- the new state of the If. + } + STATUS current + DESCRIPTION + "An ipv6IfStateChange notification signifies + that there has been a change in the state of + an ipv6 interface. This notification should + be generated when the interface's operational + status transitions to or from the up(1) state." + ::= { ipv6NotificationPrefix 1 } + +-- conformance information + +ipv6Conformance OBJECT IDENTIFIER ::= { ipv6MIB 3 } + +ipv6Compliances OBJECT IDENTIFIER ::= { ipv6Conformance 1 } +ipv6Groups OBJECT IDENTIFIER ::= { ipv6Conformance 2 } + +-- compliance statements + +ipv6Compliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMPv2 entities which + implement ipv6 MIB." + MODULE -- this module + MANDATORY-GROUPS { ipv6GeneralGroup, + ipv6NotificationGroup } + OBJECT ipv6Forwarding + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write + access to this object" + OBJECT ipv6DefaultHopLimit + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write + access to this object" + OBJECT ipv6IfDescr + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write + access to this object" + OBJECT ipv6IfIdentifier + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write + access to this object" + OBJECT ipv6IfIdentifierLength + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write + access to this object" + + OBJECT ipv6IfAdminStatus + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write + access to this object" + OBJECT ipv6RouteValid + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write + access to this object" + OBJECT ipv6NetToMediaValid + MIN-ACCESS read-only + DESCRIPTION + "An agent is not required to provide write + + access to this object" + ::= { ipv6Compliances 1 } + +ipv6GeneralGroup OBJECT-GROUP + OBJECTS { ipv6Forwarding, + ipv6DefaultHopLimit, + ipv6Interfaces, + ipv6IfTableLastChange, + ipv6IfDescr, + ipv6IfLowerLayer, + ipv6IfEffectiveMtu, + ipv6IfReasmMaxSize, + ipv6IfIdentifier, + ipv6IfIdentifierLength, + ipv6IfPhysicalAddress, + ipv6IfAdminStatus, + ipv6IfOperStatus, + ipv6IfLastChange, + ipv6IfStatsInReceives, + ipv6IfStatsInHdrErrors, + ipv6IfStatsInTooBigErrors, + ipv6IfStatsInNoRoutes, + ipv6IfStatsInAddrErrors, + ipv6IfStatsInUnknownProtos, + ipv6IfStatsInTruncatedPkts, + ipv6IfStatsInDiscards, + ipv6IfStatsInDelivers, + ipv6IfStatsOutForwDatagrams, + ipv6IfStatsOutRequests, + ipv6IfStatsOutDiscards, + ipv6IfStatsOutFragOKs, + ipv6IfStatsOutFragFails, + ipv6IfStatsOutFragCreates, + ipv6IfStatsReasmReqds, + ipv6IfStatsReasmOKs, + ipv6IfStatsReasmFails, + ipv6IfStatsInMcastPkts, + ipv6IfStatsOutMcastPkts, + ipv6AddrPrefixOnLinkFlag, + ipv6AddrPrefixAutonomousFlag, + ipv6AddrPrefixAdvPreferredLifetime, + ipv6AddrPrefixAdvValidLifetime, + ipv6AddrPfxLength, + ipv6AddrType, + ipv6AddrAnycastFlag, + ipv6AddrStatus, + ipv6RouteNumber, + ipv6DiscardedRoutes, + ipv6RouteIfIndex, + ipv6RouteNextHop, + ipv6RouteType, + ipv6RouteProtocol, + ipv6RoutePolicy, + ipv6RouteAge, + ipv6RouteNextHopRDI, + ipv6RouteMetric, + ipv6RouteWeight, + ipv6RouteInfo, + ipv6RouteValid, + ipv6NetToMediaPhysAddress, + ipv6NetToMediaType, + ipv6IfNetToMediaState, + ipv6IfNetToMediaLastUpdated, + ipv6NetToMediaValid } + STATUS current + DESCRIPTION + "The IPv6 group of objects providing for basic + management of IPv6 entities." + ::= { ipv6Groups 1 } + +ipv6NotificationGroup NOTIFICATION-GROUP + NOTIFICATIONS { ipv6IfStateChange } + STATUS current + DESCRIPTION + "The notification that an IPv6 entity is required + to implement." + ::= { ipv6Groups 2 } + + END diff --git a/mibs/IPV6-TC.txt b/mibs/IPV6-TC.txt new file mode 100644 index 0000000..05e1e7d --- /dev/null +++ b/mibs/IPV6-TC.txt @@ -0,0 +1,67 @@ +IPV6-TC DEFINITIONS ::= BEGIN + +IMPORTS + Integer32 FROM SNMPv2-SMI + TEXTUAL-CONVENTION FROM SNMPv2-TC; + +-- definition of textual conventions +Ipv6Address ::= TEXTUAL-CONVENTION + DISPLAY-HINT "2x:" + STATUS current + DESCRIPTION + "This data type is used to model IPv6 addresses. + This is a binary string of 16 octets in network + byte-order." + SYNTAX OCTET STRING (SIZE (16)) + +Ipv6AddressPrefix ::= TEXTUAL-CONVENTION + DISPLAY-HINT "2x:" + STATUS current + DESCRIPTION + "This data type is used to model IPv6 address + prefixes. This is a binary string of up to 16 + octets in network byte-order." + SYNTAX OCTET STRING (SIZE (0..16)) + +Ipv6AddressIfIdentifier ::= TEXTUAL-CONVENTION + DISPLAY-HINT "2x:" + STATUS current + DESCRIPTION + "This data type is used to model IPv6 address + interface identifiers. This is a binary string + of up to 8 octets in network byte-order." + SYNTAX OCTET STRING (SIZE (0..8)) + +Ipv6IfIndex ::= TEXTUAL-CONVENTION + DISPLAY-HINT "d" + STATUS current + DESCRIPTION + "A unique value, greater than zero for each + internetwork-layer interface in the managed + system. It is recommended that values are assigned + contiguously starting from 1. The value for each + internetwork-layer interface must remain constant + at least from one re-initialization of the entity's + network management system to the next + + re-initialization." + SYNTAX Integer32 (1..2147483647) + +Ipv6IfIndexOrZero ::= TEXTUAL-CONVENTION + DISPLAY-HINT "d" + STATUS current + DESCRIPTION + "This textual convention is an extension of the + Ipv6IfIndex convention. The latter defines + a greater than zero value used to identify an IPv6 + interface in the managed system. This extension + permits the additional value of zero. The value + zero is object-specific and must therefore be + defined as part of the description of any object + which uses this syntax. Examples of the usage of + zero might include situations where interface was + unknown, or when none or all interfaces need to be + referenced." + SYNTAX Integer32 (0..2147483647) + +END diff --git a/mibs/IPV6-TCP-MIB.txt b/mibs/IPV6-TCP-MIB.txt new file mode 100644 index 0000000..a2fb857 --- /dev/null +++ b/mibs/IPV6-TCP-MIB.txt @@ -0,0 +1,211 @@ +IPV6-TCP-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF + MODULE-IDENTITY, OBJECT-TYPE, + mib-2, experimental FROM SNMPv2-SMI + Ipv6Address, Ipv6IfIndexOrZero FROM IPV6-TC; + +ipv6TcpMIB MODULE-IDENTITY + LAST-UPDATED "9801290000Z" + ORGANIZATION "IETF IPv6 MIB Working Group" + CONTACT-INFO + " Mike Daniele + + Postal: Compaq Computer Corporation + 110 Spitbrook Rd + Nashua, NH 03062. + US + + Phone: +1 603 884 1423 + Email: daniele@zk3.dec.com" + DESCRIPTION + "The MIB module for entities implementing TCP over IPv6." + ::= { experimental 86 } + +-- objects specific to TCP for IPv6 + +tcp OBJECT IDENTIFIER ::= { mib-2 6 } + +-- the TCP over IPv6 Connection table + +-- This connection table contains information about this +-- entity's existing TCP connections between IPv6 endpoints. +-- Only connections between IPv6 addresses are contained in +-- this table. This entity's connections between IPv4 +-- endpoints are contained in tcpConnTable. + +ipv6TcpConnTable OBJECT-TYPE + SYNTAX SEQUENCE OF Ipv6TcpConnEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table containing TCP connection-specific information, + for only those connections whose endpoints are IPv6 addresses." + ::= { tcp 16 } + +ipv6TcpConnEntry OBJECT-TYPE + SYNTAX Ipv6TcpConnEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A conceptual row of the ipv6TcpConnTable containing + information about a particular current TCP connection. + Each row of this table is transient, in that it ceases to + exist when (or soon after) the connection makes the transition + to the CLOSED state. + + Note that conceptual rows in this table require an additional + index object compared to tcpConnTable, since IPv6 addresses + are not guaranteed to be unique on the managed node." + INDEX { ipv6TcpConnLocalAddress, + ipv6TcpConnLocalPort, + ipv6TcpConnRemAddress, + ipv6TcpConnRemPort, + ipv6TcpConnIfIndex } + ::= { ipv6TcpConnTable 1 } + +Ipv6TcpConnEntry ::= + SEQUENCE { ipv6TcpConnLocalAddress Ipv6Address, + ipv6TcpConnLocalPort INTEGER (0..65535), + ipv6TcpConnRemAddress Ipv6Address, + ipv6TcpConnRemPort INTEGER (0..65535), + ipv6TcpConnIfIndex Ipv6IfIndexOrZero, + ipv6TcpConnState INTEGER } + +ipv6TcpConnLocalAddress OBJECT-TYPE + SYNTAX Ipv6Address + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The local IPv6 address for this TCP connection. In + the case of a connection in the listen state which + is willing to accept connections for any IPv6 + address associated with the managed node, the value + ::0 is used." + ::= { ipv6TcpConnEntry 1 } + +ipv6TcpConnLocalPort OBJECT-TYPE + SYNTAX INTEGER (0..65535) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The local port number for this TCP connection." + ::= { ipv6TcpConnEntry 2 } + +ipv6TcpConnRemAddress OBJECT-TYPE + SYNTAX Ipv6Address + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The remote IPv6 address for this TCP connection." + ::= { ipv6TcpConnEntry 3 } + +ipv6TcpConnRemPort OBJECT-TYPE + SYNTAX INTEGER (0..65535) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The remote port number for this TCP connection." + ::= { ipv6TcpConnEntry 4 } + +ipv6TcpConnIfIndex OBJECT-TYPE + SYNTAX Ipv6IfIndexOrZero + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An index object used to disambiguate conceptual rows in + the table, since the connection 4-tuple may not be unique. + + If the connection's remote address (ipv6TcpConnRemAddress) + is a link-local address and the connection's local address + + (ipv6TcpConnLocalAddress) is not a link-local address, this + object identifies a local interface on the same link as + the connection's remote link-local address. + + Otherwise, this object identifies the local interface that + is associated with the ipv6TcpConnLocalAddress for this + TCP connection. If such a local interface cannot be determined, + this object should take on the value 0. (A possible example + of this would be if the value of ipv6TcpConnLocalAddress is ::0.) + + The interface identified by a particular non-0 value of this + index is the same interface as identified by the same value + of ipv6IfIndex. + + The value of this object must remain constant during the life + of the TCP connection." + ::= { ipv6TcpConnEntry 5 } + +ipv6TcpConnState OBJECT-TYPE + SYNTAX INTEGER { + closed(1), + listen(2), + synSent(3), + synReceived(4), + established(5), + finWait1(6), + finWait2(7), + closeWait(8), + lastAck(9), + closing(10), + timeWait(11), + deleteTCB(12) } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The state of this TCP connection. + + The only value which may be set by a management station is + deleteTCB(12). Accordingly, it is appropriate for an agent + to return an error response (`badValue' for SNMPv1, 'wrongValue' + for SNMPv2) if a management station attempts to set this + object to any other value. + + If a management station sets this object to the value + deleteTCB(12), then this has the effect of deleting the TCB + (as defined in RFC 793) of the corresponding connection on + the managed node, resulting in immediate termination of the + connection. + + As an implementation-specific option, a RST segment may be + sent from the managed node to the other TCP endpoint (note + however that RST segments are not sent reliably)." + ::= { ipv6TcpConnEntry 6 } + +-- +-- conformance information +-- + +ipv6TcpConformance OBJECT IDENTIFIER ::= { ipv6TcpMIB 2 } + +ipv6TcpCompliances OBJECT IDENTIFIER ::= { ipv6TcpConformance 1 } +ipv6TcpGroups OBJECT IDENTIFIER ::= { ipv6TcpConformance 2 } + +-- compliance statements + +ipv6TcpCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMPv2 entities which + implement TCP over IPv6." + MODULE -- this module + MANDATORY-GROUPS { ipv6TcpGroup } + ::= { ipv6TcpCompliances 1 } + +ipv6TcpGroup OBJECT-GROUP + OBJECTS { -- these are defined in this module + -- ipv6TcpConnLocalAddress (not-accessible) + -- ipv6TcpConnLocalPort (not-accessible) + -- ipv6TcpConnRemAddress (not-accessible) + -- ipv6TcpConnRemPort (not-accessible) + -- ipv6TcpConnIfIndex (not-accessible) + ipv6TcpConnState } + STATUS current + DESCRIPTION + "The group of objects providing management of + TCP over IPv6." + ::= { ipv6TcpGroups 1 } + +END diff --git a/mibs/IPV6-UDP-MIB.txt b/mibs/IPV6-UDP-MIB.txt new file mode 100644 index 0000000..6c929eb --- /dev/null +++ b/mibs/IPV6-UDP-MIB.txt @@ -0,0 +1,141 @@ +IPV6-UDP-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF + MODULE-IDENTITY, OBJECT-TYPE, + mib-2, experimental FROM SNMPv2-SMI + Ipv6Address, Ipv6IfIndexOrZero FROM IPV6-TC; + +ipv6UdpMIB MODULE-IDENTITY + LAST-UPDATED "9801290000Z" + ORGANIZATION "IETF IPv6 MIB Working Group" + CONTACT-INFO + " Mike Daniele + + Postal: Compaq Computer Corporation + 110 Spitbrook Rd + Nashua, NH 03062. + US + + Phone: +1 603 884 1423 + Email: daniele@zk3.dec.com" + DESCRIPTION + "The MIB module for entities implementing UDP over IPv6." + ::= { experimental 87 } + +-- objects specific to UDP for IPv6 + +udp OBJECT IDENTIFIER ::= { mib-2 7 } + +-- the UDP over IPv6 Listener table + +-- This table contains information about this entity's +-- UDP/IPv6 endpoints. Only endpoints utilizing IPv6 addresses +-- are contained in this table. This entity's UDP/IPv4 endpoints +-- are contained in udpTable. + +ipv6UdpTable OBJECT-TYPE + SYNTAX SEQUENCE OF Ipv6UdpEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table containing UDP listener information for + UDP/IPv6 endpoints." + ::= { udp 6 } + +ipv6UdpEntry OBJECT-TYPE + SYNTAX Ipv6UdpEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a particular current UDP listener. + + Note that conceptual rows in this table require an + additional index object compared to udpTable, since + IPv6 addresses are not guaranteed to be unique on the + managed node." + INDEX { ipv6UdpLocalAddress, + ipv6UdpLocalPort, + ipv6UdpIfIndex } + ::= { ipv6UdpTable 1 } + +Ipv6UdpEntry ::= SEQUENCE { + ipv6UdpLocalAddress Ipv6Address, + ipv6UdpLocalPort INTEGER (0..65535), + ipv6UdpIfIndex Ipv6IfIndexOrZero } + +ipv6UdpLocalAddress OBJECT-TYPE + SYNTAX Ipv6Address + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The local IPv6 address for this UDP listener. + In the case of a UDP listener which is willing + to accept datagrams for any IPv6 address + associated with the managed node, the value ::0 + is used." + ::= { ipv6UdpEntry 1 } + +ipv6UdpLocalPort OBJECT-TYPE + SYNTAX INTEGER (0..65535) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The local port number for this UDP listener." + ::= { ipv6UdpEntry 2 } + +ipv6UdpIfIndex OBJECT-TYPE + SYNTAX Ipv6IfIndexOrZero + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index object used to disambiguate conceptual rows in + the table, since the ipv6UdpLocalAddress/ipv6UdpLocalPort + pair may not be unique. + + This object identifies the local interface that is + associated with ipv6UdpLocalAddress for this UDP listener. + If such a local interface cannot be determined, this object + should take on the value 0. (A possible example of this + would be if the value of ipv6UdpLocalAddress is ::0.) + + The interface identified by a particular non-0 value of + this index is the same interface as identified by the same + value of ipv6IfIndex. + + The value of this object must remain constant during + the life of this UDP endpoint." + ::= { ipv6UdpEntry 3 } + +-- +-- conformance information +-- + +ipv6UdpConformance OBJECT IDENTIFIER ::= { ipv6UdpMIB 2 } + +ipv6UdpCompliances OBJECT IDENTIFIER ::= { ipv6UdpConformance 1 } +ipv6UdpGroups OBJECT IDENTIFIER ::= { ipv6UdpConformance 2 } + +-- compliance statements + +ipv6UdpCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMPv2 entities which + implement UDP over IPv6." + MODULE -- this module + MANDATORY-GROUPS { ipv6UdpGroup } + ::= { ipv6UdpCompliances 1 } + +ipv6UdpGroup OBJECT-GROUP + OBJECTS { -- these are defined in this module + -- ipv6UdpLocalAddress (not-accessible) + -- ipv6UdpLocalPort (not-accessible) + ipv6UdpIfIndex } + STATUS current + DESCRIPTION + "The group of objects providing management of + UDP over IPv6." + ::= { ipv6UdpGroups 1 } + +END diff --git a/mibs/MTA-MIB.txt b/mibs/MTA-MIB.txt new file mode 100644 index 0000000..29618ad --- /dev/null +++ b/mibs/MTA-MIB.txt @@ -0,0 +1,1226 @@ +MTA-MIB DEFINITIONS ::= BEGIN + +IMPORTS + OBJECT-TYPE, Counter32, Gauge32, MODULE-IDENTITY, mib-2 + FROM SNMPv2-SMI + TimeInterval + FROM SNMPv2-TC + MODULE-COMPLIANCE, OBJECT-GROUP + FROM SNMPv2-CONF + SnmpAdminString + FROM SNMP-FRAMEWORK-MIB + applIndex, URLString + FROM NETWORK-SERVICES-MIB; + +mta MODULE-IDENTITY + LAST-UPDATED "200003030000Z" + ORGANIZATION "IETF Mail and Directory Management Working Group" + CONTACT-INFO + " Ned Freed + + Postal: Innosoft International, Inc. + 1050 Lakes Drive + West Covina, CA 91790 + US + + Tel: +1 626 919 3600 + Fax: +1 626 919 3614 + + E-Mail: ned.freed@innosoft.com" + DESCRIPTION + "The MIB module describing Message Transfer Agents (MTAs)" + REVISION "200003030000Z" + DESCRIPTION + "This revision, published in RFC 2789, changes a number of + DisplayStrings to SnmpAdminStrings. Note that this change + + is not strictly supported by SMIv2. However, the alternative + of deprecating the old objects and defining new objects + would have a more adverse impact on backward compatibility + and interoperability, given the particular semantics of + these objects. The defining reference for distinguished + names has also been updated from RFC 1779 to RFC 2253." + REVISION "199905120000Z" + DESCRIPTION + "This revision fixes a number of technical problems found in + previous versions: The conformance groups for different + versions of this MIB have been corrected, the recommendation + that an empty string be returned if the last operation was + successful has been removed from + mtaGroupInboundRejectionReason and + mtaGroupOutboundConnectFailureReason as it conflicts + with the stated purpose of these variables, and the + required mtaStatusCode entry has been added to + MtaGroupErrorEntry. It should be noted that this last + change in no way affects the bits on the wire." + REVISION "199708170000Z" + DESCRIPTION + "This revision, published in RFC 2249, adds the + mtaGroupDescription and mtaGroupURL fields, conversion + operation counters, a group hierarchy description mechanism, + counters for specific errors, oldest message IDs, per-MTA + and per-group loop counters, and a new table for tracking + any errors an MTA encounters." + REVISION "199311280000Z" + DESCRIPTION + "The original version of this MIB was published in RFC 1566" + ::= {mib-2 28} + +mtaTable OBJECT-TYPE + SYNTAX SEQUENCE OF MtaEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table holding information specific to an MTA." + ::= {mta 1} + +mtaEntry OBJECT-TYPE + SYNTAX MtaEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The entry associated with each MTA." + INDEX {applIndex} + ::= {mtaTable 1} + +MtaEntry ::= SEQUENCE { + mtaReceivedMessages + Counter32, + mtaStoredMessages + Gauge32, + mtaTransmittedMessages + Counter32, + mtaReceivedVolume + Counter32, + mtaStoredVolume + Gauge32, + mtaTransmittedVolume + Counter32, + mtaReceivedRecipients + Counter32, + mtaStoredRecipients + Gauge32, + mtaTransmittedRecipients + Counter32, + mtaSuccessfulConvertedMessages + Counter32, + mtaFailedConvertedMessages + Counter32, + mtaLoopsDetected + Counter32 +} + +mtaReceivedMessages OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of messages received since MTA initialization. + This includes messages transmitted to this MTA from other + MTAs as well as messages that have been submitted to the + MTA directly by end-users or applications." + ::= {mtaEntry 1} + +mtaStoredMessages OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of messages currently stored in the MTA. + This includes messages that are awaiting transmission to + some other MTA or are waiting for delivery to an end-user + or application." + ::= {mtaEntry 2} + +mtaTransmittedMessages OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of messages transmitted since MTA initialization. + This includes messages that were transmitted to some other + MTA or are waiting for delivery to an end-user or + application." + ::= {mtaEntry 3} + +mtaReceivedVolume OBJECT-TYPE + SYNTAX Counter32 + UNITS "K-octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total volume of messages received since MTA + initialization, measured in kilo-octets. This volume should + include all transferred data that is logically above the mail + transport protocol level. For example, an SMTP-based MTA + should use the number of kilo-octets in the message header + and body, while an X.400-based MTA should use the number of + kilo-octets of P2 data. This includes messages transmitted + to this MTA from other MTAs as well as messages that have + been submitted to the MTA directly by end-users or + applications." + ::= {mtaEntry 4} + +mtaStoredVolume OBJECT-TYPE + SYNTAX Gauge32 + UNITS "K-octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total volume of messages currently stored in the MTA, + measured in kilo-octets. This volume should include all + stored data that is logically above the mail transport + protocol level. For example, an SMTP-based MTA should + use the number of kilo-octets in the message header and + body, while an X.400-based MTA would use the number of + kilo-octets of P2 data. This includes messages that are + awaiting transmission to some other MTA or are waiting + for delivery to an end-user or application." + ::= {mtaEntry 5} + +mtaTransmittedVolume OBJECT-TYPE + SYNTAX Counter32 + UNITS "K-octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total volume of messages transmitted since MTA + initialization, measured in kilo-octets. This volume should + include all transferred data that is logically above the mail + transport protocol level. For example, an SMTP-based MTA + should use the number of kilo-octets in the message header + and body, while an X.400-based MTA should use the number of + kilo-octets of P2 data. This includes messages that were + transmitted to some other MTA or are waiting for delivery + to an end-user or application." + ::= {mtaEntry 6} + +mtaReceivedRecipients OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of recipients specified in all messages + received since MTA initialization. Recipients this MTA + has no responsibility for, i.e. inactive envelope + recipients or ones referred to in message headers, + should not be counted even if information about such + recipients is available. This includes messages + transmitted to this MTA from other MTAs as well as + messages that have been submitted to the MTA directly + by end-users or applications." + ::= {mtaEntry 7} + +mtaStoredRecipients OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of recipients specified in all messages + currently stored in the MTA. Recipients this MTA has no + responsibility for, i.e. inactive envelope recipients or + ones referred to in message headers, should not be + counted. This includes messages that are awaiting + transmission to some other MTA or are waiting for + delivery to an end-user or application." + ::= {mtaEntry 8} + +mtaTransmittedRecipients OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of recipients specified in all messages + transmitted since MTA initialization. Recipients this + MTA had no responsibility for, i.e. inactive envelope + recipients or ones referred to in message headers, + should not be counted. This includes messages that were + transmitted to some other MTA or are waiting for + delivery to an end-user or application." + ::= {mtaEntry 9} + +mtaSuccessfulConvertedMessages OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of messages that have been successfully + converted from one form to another since MTA + initialization." + ::= {mtaEntry 10} + +mtaFailedConvertedMessages OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of messages for which an unsuccessful + attempt was made to convert them from one form to + another since MTA initialization." + ::= {mtaEntry 11} + +mtaLoopsDetected OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A message loop is defined as a situation where the MTA + decides that a given message will never be delivered to + one or more recipients and instead will continue to + loop endlessly through one or more MTAs. This variable + counts the number of times the MTA has detected such a + situation since MTA initialization. Note that the + mechanism MTAs use to detect loops (e.g., trace field + counting, count of references to this MTA in a trace + field, examination of DNS or other directory information, + etc.), the level at which loops are detected (e.g., per + message, per recipient, per directory entry, etc.), and + the handling of a loop once it is detected (e.g., looping + + messages are held, looping messages are bounced or sent + to the postmaster, messages that the MTA knows will loop + won't be accepted, etc.) vary widely from one MTA to the + next and cannot be inferred from this variable." + ::= {mtaEntry 12} + +-- MTAs typically group inbound reception, queue storage, and +-- outbound transmission in some way, rather than accounting for +-- such operations only across the MTA as a whole. In the most +-- extreme case separate information will be maintained for each +-- different entity that receives messages and for each entity +-- the MTA stores messages for and delivers messages to. Other +-- MTAs may elect to treat all reception equally, all queue +-- storage equally, all deliveries equally, or some combination +-- of this. Overlapped groupings are also possible, where an MTA +-- decomposes its traffic in different ways for different +-- purposes. + +-- In any case, a grouping abstraction is an extremely useful for +-- breaking down the activities of an MTA. For purposes of +-- labelling this will be called a "group" in this MIB. + +-- Each group contains all the variables needed to monitor all +-- aspects of an MTA's operation. However, the fact that all +-- groups contain all possible variables does not imply that all +-- groups must use all possible variables. For example, a single +-- group might be used to monitor only one kind of event (inbound +-- processing, outbound processing, or storage). In this sort of +-- configuration any counters that are unused as a result of a +-- given MTA's use of the group construct must be inaccessible; +-- e.g., returning either a noSuchName error (for an SNMPv1 get), +-- or a noSuchInstance exception (for an SNMPv2 get). + +-- Groups can be created at any time after MTA initialization. Once +-- a group is created it should not be deleted or its mtaGroupIndex +-- changed unless the MTA is reinitialized. + +-- Groups are not necessarily mutually exclusive. A given event may +-- be recorded by more than one group, a message may be seen as +-- stored by more than one group, and so on. Groups should be all +-- inclusive, however: if groups are implemented all aspects of an +-- MTA's operation should be registered in at least one group. +-- This freedom lets implementors use different sets of groups to +-- provide different "views" of an MTA. + +-- The possibility of overlap between groups means that summing +-- variables across groups may not produce values equal to those in +-- the mtaTable. mtaTable should always provide accurate information + +-- about the MTA as a whole. + +-- The term "channel" is often used in MTA implementations; channels +-- are usually, but not always, equivalent to a group. However, +-- this MIB does not use the term "channel" because there is no +-- requirement that an MTA supporting this MIB has to map its +-- "channel" abstraction one-to-one onto the MIB's group abstraction. + +-- An MTA may create a group or group of groups at any time. Once +-- created, however, an MTA cannot delete an entry for a group from +-- the group table. Deletion is only allowed when the MTA is +-- reinitialized, and is not required even then. This restriction +-- is imposed so that monitoring agents can rely on group +-- assignments being consistent across multiple query operations. + +-- Groups may be laid out so as to form a hierarchical arrangement, +-- with some groups acting as subgroups for other groups. +-- Alternately, disjoint groups of groups may be used to provide +-- different sorts of "snapshots" of MTA operation. The +-- mtaGroupHierarchy variable provides an indication of how each +-- group fits into the overall arrangement being used. + +-- Note that SNMP also defines and uses term "group". MTA groups are +-- NOT the same as SNMP groups. + +mtaGroupTable OBJECT-TYPE + SYNTAX SEQUENCE OF MtaGroupEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table holding information specific to each MTA group." + ::= {mta 2} + +mtaGroupEntry OBJECT-TYPE + SYNTAX MtaGroupEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The entry associated with each MTA group." + INDEX {applIndex, mtaGroupIndex} + ::= {mtaGroupTable 1} + +MtaGroupEntry ::= SEQUENCE { + mtaGroupIndex + INTEGER, + mtaGroupReceivedMessages + Counter32, + mtaGroupRejectedMessages + + Counter32, + mtaGroupStoredMessages + Gauge32, + mtaGroupTransmittedMessages + Counter32, + mtaGroupReceivedVolume + Counter32, + mtaGroupStoredVolume + Gauge32, + mtaGroupTransmittedVolume + Counter32, + mtaGroupReceivedRecipients + Counter32, + mtaGroupStoredRecipients + Gauge32, + mtaGroupTransmittedRecipients + Counter32, + mtaGroupOldestMessageStored + TimeInterval, + mtaGroupInboundAssociations + Gauge32, + mtaGroupOutboundAssociations + Gauge32, + mtaGroupAccumulatedInboundAssociations + Counter32, + mtaGroupAccumulatedOutboundAssociations + Counter32, + mtaGroupLastInboundActivity + TimeInterval, + mtaGroupLastOutboundActivity + TimeInterval, + mtaGroupLastOutboundAssociationAttempt + TimeInterval, + mtaGroupRejectedInboundAssociations + Counter32, + mtaGroupFailedOutboundAssociations + Counter32, + mtaGroupInboundRejectionReason + SnmpAdminString, + mtaGroupOutboundConnectFailureReason + SnmpAdminString, + mtaGroupScheduledRetry + TimeInterval, + mtaGroupMailProtocol + OBJECT IDENTIFIER, + mtaGroupName + SnmpAdminString, + mtaGroupSuccessfulConvertedMessages + + Counter32, + mtaGroupFailedConvertedMessages + Counter32, + mtaGroupDescription + SnmpAdminString, + mtaGroupURL + URLString, + mtaGroupCreationTime + TimeInterval, + mtaGroupHierarchy + INTEGER, + mtaGroupOldestMessageId + SnmpAdminString, + mtaGroupLoopsDetected + Counter32 +} + +mtaGroupIndex OBJECT-TYPE + SYNTAX INTEGER (1..2147483647) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The index associated with a group for a given MTA." + ::= {mtaGroupEntry 1} + +mtaGroupReceivedMessages OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of messages received to this group since + group creation." + ::= {mtaGroupEntry 2} + +mtaGroupRejectedMessages OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of messages rejected by this group since + group creation." + ::= {mtaGroupEntry 3} + +mtaGroupStoredMessages OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of messages currently stored in this + group's queue." + ::= {mtaGroupEntry 4} + +mtaGroupTransmittedMessages OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of messages transmitted by this group since + group creation." + ::= {mtaGroupEntry 5} + +mtaGroupReceivedVolume OBJECT-TYPE + SYNTAX Counter32 + UNITS "K-octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total volume of messages received to this group since + group creation, measured in kilo-octets. This volume + should include all transferred data that is logically above + the mail transport protocol level. For example, an + SMTP-based MTA should use the number of kilo-octets in the + message header and body, while an X.400-based MTA should use + the number of kilo-octets of P2 data." + ::= {mtaGroupEntry 6} + +mtaGroupStoredVolume OBJECT-TYPE + SYNTAX Gauge32 + UNITS "K-octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total volume of messages currently stored in this + group's queue, measured in kilo-octets. This volume should + include all stored data that is logically above the mail + transport protocol level. For example, an SMTP-based + MTA should use the number of kilo-octets in the message + header and body, while an X.400-based MTA would use the + number of kilo-octets of P2 data." + ::= {mtaGroupEntry 7} + +mtaGroupTransmittedVolume OBJECT-TYPE + SYNTAX Counter32 + UNITS "K-octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total volume of messages transmitted by this group + since group creation, measured in kilo-octets. This + volume should include all transferred data that is logically + above the mail transport protocol level. For example, an + SMTP-based MTA should use the number of kilo-octets in the + message header and body, while an X.400-based MTA should use + the number of kilo-octets of P2 data." + ::= {mtaGroupEntry 8} + +mtaGroupReceivedRecipients OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of recipients specified in all messages + received to this group since group creation. + Recipients this MTA has no responsibility for should not + be counted." + ::= {mtaGroupEntry 9} + +mtaGroupStoredRecipients OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of recipients specified in all messages + currently stored in this group's queue. Recipients this + MTA has no responsibility for should not be counted." + ::= {mtaGroupEntry 10} + +mtaGroupTransmittedRecipients OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of recipients specified in all messages + transmitted by this group since group creation. + Recipients this MTA had no responsibility for should not + be counted." + ::= {mtaGroupEntry 11} + +mtaGroupOldestMessageStored OBJECT-TYPE + SYNTAX TimeInterval + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Time since the oldest message in this group's queue was + + placed in the queue." + ::= {mtaGroupEntry 12} + +mtaGroupInboundAssociations OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of current associations to the group, where the + group is the responder." + ::= {mtaGroupEntry 13} + +mtaGroupOutboundAssociations OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of current associations to the group, where the + group is the initiator." + ::= {mtaGroupEntry 14} + +mtaGroupAccumulatedInboundAssociations OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of associations to the group since + group creation, where the MTA was the responder." + ::= {mtaGroupEntry 15} + +mtaGroupAccumulatedOutboundAssociations OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of associations from the group since + group creation, where the MTA was the initiator." + ::= {mtaGroupEntry 16} + +mtaGroupLastInboundActivity OBJECT-TYPE + SYNTAX TimeInterval + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Time since the last time that this group had an active + inbound association for purposes of message reception." + ::= {mtaGroupEntry 17} + +mtaGroupLastOutboundActivity OBJECT-TYPE + SYNTAX TimeInterval + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Time since the last time that this group had a + successful outbound association for purposes of + message delivery." + ::= {mtaGroupEntry 18} + +mtaGroupLastOutboundAssociationAttempt OBJECT-TYPE + SYNTAX TimeInterval + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Time since the last time that this group attempted + to make an outbound association for purposes of + message delivery." + ::= {mtaGroupEntry 34} + +mtaGroupRejectedInboundAssociations OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of inbound associations the group has + rejected, since group creation. Rejected associations + are not counted in the accumulated association totals." + ::= {mtaGroupEntry 19} + +mtaGroupFailedOutboundAssociations OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number associations where the group was the + initiator and association establishment has failed, + since group creation. Failed associations are + not counted in the accumulated association totals." + ::= {mtaGroupEntry 20} + +mtaGroupInboundRejectionReason OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The failure reason, if any, for the last association this + group refused to respond to. If no association attempt + + has been made since the MTA was initialized the value + should be 'never'." + ::= {mtaGroupEntry 21} + +mtaGroupOutboundConnectFailureReason OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The failure reason, if any, for the last association attempt + this group initiated. If no association attempt has been + made since the MTA was initialized the value should be + 'never'." + ::= {mtaGroupEntry 22} + +mtaGroupScheduledRetry OBJECT-TYPE + SYNTAX TimeInterval + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The amount of time until this group is next scheduled to + attempt to make an association." + ::= {mtaGroupEntry 23} + +mtaGroupMailProtocol OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An identification of the protocol being used by this group. + For an group employing OSI protocols, this will be the + Application Context. For Internet applications, OID + values of the form {applTCPProtoID port} or {applUDPProtoID + port} are used for TCP-based and UDP-based protocols, + respectively. In either case 'port' corresponds to the + primary port number being used by the protocol. The + usual IANA procedures may be used to register ports for + new protocols. applTCPProtoID and applUDPProtoID are + defined in the NETWORK-SERVICES-MIB, RFC 2788." + ::= {mtaGroupEntry 24} + +mtaGroupName OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A descriptive name for the group. If this group connects to + a single remote MTA this should be the name of that MTA. If + + this in turn is an Internet MTA this should be the domain + name. For an OSI MTA it should be the string encoded + distinguished name of the managed object using the format + defined in RFC 2253. For X.400(1984) MTAs which do not + have a Distinguished Name, the RFC 2156 syntax + 'mta in globalid' used in X400-Received: fields can be + used." + ::= {mtaGroupEntry 25} + +mtaGroupSuccessfulConvertedMessages OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of messages that have been successfully + converted from one form to another in this group + since group creation." + ::= {mtaGroupEntry 26} + +mtaGroupFailedConvertedMessages OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of messages for which an unsuccessful + attempt was made to convert them from one form to + another in this group since group creation." + ::= {mtaGroupEntry 27} + +mtaGroupDescription OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A description of the group's purpose. This information is + intended to identify the group in a status display." + ::= {mtaGroupEntry 28} + +mtaGroupURL OBJECT-TYPE + SYNTAX URLString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A URL pointing to a description of the group. This + information is intended to identify and briefly describe + the group in a status display." + ::= {mtaGroupEntry 29} + +mtaGroupCreationTime OBJECT-TYPE + SYNTAX TimeInterval + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Time since this group was first created." + ::= {mtaGroupEntry 30} + +mtaGroupHierarchy OBJECT-TYPE + SYNTAX INTEGER (-2147483648..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Describes how this group fits into the hierarchy. A + positive value is interpreted as an mtaGroupIndex + value for some other group whose variables include + those of this group (and usually others). A negative + value is interpreted as a group collection code: Groups + with common negative hierarchy values comprise one + particular breakdown of MTA activity as a whole. A + zero value means that this MIB implementation doesn't + implement hierarchy indicators and thus the overall + group hierarchy cannot be determined." + ::= {mtaGroupEntry 31} + +mtaGroupOldestMessageId OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Message ID of the oldest message in the group's queue. + Whenever possible this should be in the form of an + RFC 822 msg-id; X.400 may convert X.400 message + identifiers to this form by following the rules laid + out in RFC2156." + ::= {mtaGroupEntry 32} + +mtaGroupLoopsDetected OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A message loop is defined as a situation where the MTA + decides that a given message will never be delivered to + one or more recipients and instead will continue to + loop endlessly through one or more MTAs. This variable + counts the number of times the MTA has detected such a + situation in conjunction with something associated with + + this group since group creation. Note that the + mechanism MTAs use to detect loops (e.g., trace field + counting, count of references to this MTA in a trace + field, examination of DNS or other directory information, + etc.), the level at which loops are detected (e.g., per + message, per recipient, per directory entry, etc.), and + the handling of a loop once it is detected (e.g., looping + messages are held, looping messages are bounced or sent + to the postmaster, messages that the MTA knows will loop + won't be accepted, etc.) vary widely from one MTA to the + next and cannot be inferred from this variable." + ::= {mtaGroupEntry 33} + +-- The mtaGroupAssociationTable provides a means of correlating +-- entries in the network services association table with the +-- MTA group responsible for the association. + +mtaGroupAssociationTable OBJECT-TYPE + SYNTAX SEQUENCE OF MtaGroupAssociationEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table holding information regarding the associations + for each MTA group." + ::= {mta 3} + +mtaGroupAssociationEntry OBJECT-TYPE + SYNTAX MtaGroupAssociationEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The entry holding information regarding the associations + for each MTA group." + INDEX {applIndex, mtaGroupIndex, mtaGroupAssociationIndex} + ::= {mtaGroupAssociationTable 1} + +MtaGroupAssociationEntry ::= SEQUENCE { + mtaGroupAssociationIndex + INTEGER +} + +mtaGroupAssociationIndex OBJECT-TYPE + SYNTAX INTEGER (1..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Reference into association table to allow correlation of + this group's active associations with the association table." + ::= {mtaGroupAssociationEntry 1} + +-- The mtaGroupErrorTable gives each group a way of tallying +-- the specific errors it has encountered. The mechanism +-- defined here uses RFC 1893 status codes to identify +-- various specific errors. There are also classes for generic +-- errors of various sorts, and the entire mechanism is also +-- extensible, in that new error codes can be defined at any +-- time. + +mtaGroupErrorTable OBJECT-TYPE + SYNTAX SEQUENCE OF MtaGroupErrorEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table holding information regarding accumulated errors + for each MTA group." + ::= {mta 5} + +mtaGroupErrorEntry OBJECT-TYPE + SYNTAX MtaGroupErrorEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The entry holding information regarding accumulated + errors for each MTA group." + INDEX {applIndex, mtaGroupIndex, mtaStatusCode} + ::= {mtaGroupErrorTable 1} + +MtaGroupErrorEntry ::= SEQUENCE { + mtaStatusCode + INTEGER (4000000..5999999), + mtaGroupInboundErrorCount + Counter32, + mtaGroupInternalErrorCount + Counter32, + mtaGroupOutboundErrorCount + Counter32 +} + +mtaGroupInboundErrorCount OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Count of the number of errors of a given type that have + been accumulated in association with a particular group + while processing incoming messages. In the case of SMTP + + these will typically be errors reporting by an SMTP + server to the remote client; in the case of X.400 + these will typically be errors encountered while + processing an incoming message." + ::= {mtaGroupErrorEntry 1} + +mtaGroupInternalErrorCount OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Count of the number of errors of a given type that have + been accumulated in association with a particular group + during internal MTA processing." + ::= {mtaGroupErrorEntry 2} + +mtaGroupOutboundErrorCount OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Count of the number of errors of a given type that have + been accumulated in association with a particular group's + outbound connection activities. In the case of an SMTP + client these will typically be errors reported while + attempting to contact or while communicating with the + remote SMTP server. In the case of X.400 these will + typically be errors encountered while constructing + or attempting to deliver an outgoing message." + ::= {mtaGroupErrorEntry 3} + +mtaStatusCode OBJECT-TYPE + SYNTAX INTEGER (4000000..5999999) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An index capable of representing an Enhanced Mail System + Status Code. Enhanced Mail System Status Codes are + defined in RFC 1893. These codes have the form + + class.subject.detail + + Here 'class' is either 2, 4, or 5 and both 'subject' and + 'detail' are integers in the range 0..999. Given a status + code the corresponding index value is defined to be + ((class * 1000) + subject) * 1000 + detail. Both SMTP + error response codes and X.400 reason and diagnostic codes + can be mapped into these codes, resulting in a namespace + + capable of describing most error conditions a mail system + encounters in a generic yet detailed way." + ::= {mtaGroupErrorEntry 4} + +-- Conformance information + +mtaConformance OBJECT IDENTIFIER ::= {mta 4} + +mtaGroups OBJECT IDENTIFIER ::= {mtaConformance 1} +mtaCompliances OBJECT IDENTIFIER ::= {mtaConformance 2} + +-- Compliance statements + +mtaCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for RFC 1566 implementations + which support the Mail Monitoring MIB for basic + monitoring of MTAs." + MODULE -- this module + MANDATORY-GROUPS {mtaRFC1566Group} + ::= {mtaCompliances 1} + +mtaAssocCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for RFC 1566 implementations + which support the Mail Monitoring MIB for monitoring + of MTAs and their associations." + MODULE -- this module + MANDATORY-GROUPS {mtaRFC1566Group, mtaRFC1566AssocGroup} + ::= {mtaCompliances 2} + +mtaRFC2249Compliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for RFC 2249 implementations + which support the Mail Monitoring MIB for basic + monitoring of MTAs." + MODULE -- this module + MANDATORY-GROUPS {mtaRFC2249Group} + ::= {mtaCompliances 5} + +mtaRFC2249AssocCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for RFC 2249 implementations + + which support the Mail Monitoring MIB for monitoring of + MTAs and their associations." + MODULE -- this module + MANDATORY-GROUPS {mtaRFC2249Group, mtaRFC2249AssocGroup} + ::= {mtaCompliances 6} + +mtaRFC2249ErrorCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for RFC 2249 implementations + which support the Mail Monitoring MIB for monitoring of + MTAs and detailed errors." + MODULE -- this module + MANDATORY-GROUPS {mtaRFC2249Group, mtaRFC2249ErrorGroup} + ::= {mtaCompliances 7} + +mtaRFC2249FullCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for RFC 2249 implementations + which support the full Mail Monitoring MIB for + monitoring of MTAs, associations, and detailed errors." + MODULE -- this module + MANDATORY-GROUPS {mtaRFC2249Group, mtaRFC2249AssocGroup, + mtaRFC2249ErrorGroup} + ::= {mtaCompliances 8} + +mtaRFC2789Compliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for RFC 2789 implementations + which support the Mail Monitoring MIB for basic + monitoring of MTAs." + MODULE -- this module + MANDATORY-GROUPS {mtaRFC2789Group} + ::= {mtaCompliances 9} + +mtaRFC2789AssocCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for RFC 2789 implementations + which support the Mail Monitoring MIB for monitoring of + MTAs and their associations." + MODULE -- this module + MANDATORY-GROUPS {mtaRFC2789Group, mtaRFC2789AssocGroup} + ::= {mtaCompliances 10} + +mtaRFC2789ErrorCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for RFC 2789 implementations + which support the Mail Monitoring MIB for monitoring of + MTAs and detailed errors." + MODULE -- this module + MANDATORY-GROUPS {mtaRFC2789Group, mtaRFC2789ErrorGroup} + ::= {mtaCompliances 11} + +mtaRFC2789FullCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for RFC 2789 implementations + which support the full Mail Monitoring MIB for + monitoring of MTAs, associations, and detailed errors." + MODULE -- this module + MANDATORY-GROUPS {mtaRFC2789Group, mtaRFC2789AssocGroup, + mtaRFC2789ErrorGroup} + ::= {mtaCompliances 12} + +-- Units of conformance + +mtaRFC1566Group OBJECT-GROUP + OBJECTS { + mtaReceivedMessages, mtaStoredMessages, + mtaTransmittedMessages, mtaReceivedVolume, mtaStoredVolume, + mtaTransmittedVolume, mtaReceivedRecipients, + mtaStoredRecipients, mtaTransmittedRecipients, + mtaGroupReceivedMessages, mtaGroupRejectedMessages, + mtaGroupStoredMessages, mtaGroupTransmittedMessages, + mtaGroupReceivedVolume, mtaGroupStoredVolume, + mtaGroupTransmittedVolume, mtaGroupReceivedRecipients, + mtaGroupStoredRecipients, mtaGroupTransmittedRecipients, + mtaGroupOldestMessageStored, mtaGroupInboundAssociations, + mtaGroupOutboundAssociations, + mtaGroupAccumulatedInboundAssociations, + mtaGroupAccumulatedOutboundAssociations, + mtaGroupLastInboundActivity, mtaGroupLastOutboundActivity, + mtaGroupRejectedInboundAssociations, + mtaGroupFailedOutboundAssociations, + mtaGroupInboundRejectionReason, + mtaGroupOutboundConnectFailureReason, + mtaGroupScheduledRetry, mtaGroupMailProtocol, mtaGroupName} + STATUS current + DESCRIPTION + "A collection of objects providing basic monitoring of MTAs. + This is the original set of such objects defined in RFC + 1566." + ::= {mtaGroups 10} + +mtaRFC1566AssocGroup OBJECT-GROUP + OBJECTS { + mtaGroupAssociationIndex} + STATUS current + DESCRIPTION + "A collection of objects providing monitoring of MTA + associations. This is the original set of such objects + defined in RFC 1566." + ::= {mtaGroups 11} + +mtaRFC2249Group OBJECT-GROUP + OBJECTS { + mtaReceivedMessages, mtaStoredMessages, + mtaTransmittedMessages, mtaReceivedVolume, mtaStoredVolume, + mtaTransmittedVolume, mtaReceivedRecipients, + mtaStoredRecipients, mtaTransmittedRecipients, + mtaSuccessfulConvertedMessages, mtaFailedConvertedMessages, + mtaGroupReceivedMessages, mtaGroupRejectedMessages, + mtaGroupStoredMessages, mtaGroupTransmittedMessages, + mtaGroupReceivedVolume, mtaGroupStoredVolume, + mtaGroupTransmittedVolume, mtaGroupReceivedRecipients, + mtaGroupStoredRecipients, mtaGroupTransmittedRecipients, + mtaGroupOldestMessageStored, mtaGroupInboundAssociations, + mtaGroupOutboundAssociations, mtaLoopsDetected, + mtaGroupAccumulatedInboundAssociations, + mtaGroupAccumulatedOutboundAssociations, + mtaGroupLastInboundActivity, mtaGroupLastOutboundActivity, + mtaGroupLastOutboundAssociationAttempt, + mtaGroupRejectedInboundAssociations, + mtaGroupFailedOutboundAssociations, + mtaGroupInboundRejectionReason, + mtaGroupOutboundConnectFailureReason, + mtaGroupScheduledRetry, mtaGroupMailProtocol, mtaGroupName, + mtaGroupSuccessfulConvertedMessages, + mtaGroupFailedConvertedMessages, mtaGroupDescription, + mtaGroupURL, mtaGroupCreationTime, mtaGroupHierarchy, + mtaGroupOldestMessageId, mtaGroupLoopsDetected} + STATUS current + DESCRIPTION + "A collection of objects providing basic monitoring of MTAs. + This group was originally defined in RFC 2249." + ::= {mtaGroups 4} + +mtaRFC2249AssocGroup OBJECT-GROUP + OBJECTS { + mtaGroupAssociationIndex} + STATUS current + DESCRIPTION + "A collection of objects providing monitoring of MTA + associations. This group was originally defined in RFC + 2249." + ::= {mtaGroups 5} + +mtaRFC2249ErrorGroup OBJECT-GROUP + OBJECTS { + mtaGroupInboundErrorCount, mtaGroupInternalErrorCount, + mtaGroupOutboundErrorCount} + STATUS current + DESCRIPTION + "A collection of objects providing monitoring of + detailed MTA errors. This group was originally defined + in RFC 2249." + ::= {mtaGroups 6} + +mtaRFC2789Group OBJECT-GROUP + OBJECTS { + mtaReceivedMessages, mtaStoredMessages, + mtaTransmittedMessages, mtaReceivedVolume, mtaStoredVolume, + mtaTransmittedVolume, mtaReceivedRecipients, + mtaStoredRecipients, mtaTransmittedRecipients, + mtaSuccessfulConvertedMessages, mtaFailedConvertedMessages, + mtaGroupReceivedMessages, mtaGroupRejectedMessages, + mtaGroupStoredMessages, mtaGroupTransmittedMessages, + mtaGroupReceivedVolume, mtaGroupStoredVolume, + mtaGroupTransmittedVolume, mtaGroupReceivedRecipients, + mtaGroupStoredRecipients, mtaGroupTransmittedRecipients, + mtaGroupOldestMessageStored, mtaGroupInboundAssociations, + mtaGroupOutboundAssociations, mtaLoopsDetected, + mtaGroupAccumulatedInboundAssociations, + mtaGroupAccumulatedOutboundAssociations, + mtaGroupLastInboundActivity, mtaGroupLastOutboundActivity, + mtaGroupLastOutboundAssociationAttempt, + mtaGroupRejectedInboundAssociations, + mtaGroupFailedOutboundAssociations, + mtaGroupInboundRejectionReason, + mtaGroupOutboundConnectFailureReason, + mtaGroupScheduledRetry, mtaGroupMailProtocol, mtaGroupName, + mtaGroupSuccessfulConvertedMessages, + mtaGroupFailedConvertedMessages, mtaGroupDescription, + mtaGroupURL, mtaGroupCreationTime, mtaGroupHierarchy, + mtaGroupOldestMessageId, mtaGroupLoopsDetected} + STATUS current + DESCRIPTION + "A collection of objects providing basic monitoring of MTAs. + + This is the appropriate group for RFC 2789." + ::= {mtaGroups 7} + +mtaRFC2789AssocGroup OBJECT-GROUP + OBJECTS { + mtaGroupAssociationIndex} + STATUS current + DESCRIPTION + "A collection of objects providing monitoring of MTA + associations. This is the appropriate group for RFC + 2789 association monitoring." + ::= {mtaGroups 8} + +mtaRFC2789ErrorGroup OBJECT-GROUP + OBJECTS { + mtaGroupInboundErrorCount, mtaGroupInternalErrorCount, + mtaGroupOutboundErrorCount} + STATUS current + DESCRIPTION + "A collection of objects providing monitoring of + detailed MTA errors. This is the appropriate group + for RFC 2789 error monitoring." + ::= {mtaGroups 9} + +END diff --git a/mibs/NETWORK-SERVICES-MIB.txt b/mibs/NETWORK-SERVICES-MIB.txt new file mode 100644 index 0000000..0f2cabe --- /dev/null +++ b/mibs/NETWORK-SERVICES-MIB.txt @@ -0,0 +1,626 @@ +NETWORK-SERVICES-MIB DEFINITIONS ::= BEGIN + +IMPORTS + OBJECT-TYPE, Counter32, Gauge32, MODULE-IDENTITY, mib-2 + FROM SNMPv2-SMI + TimeStamp, TEXTUAL-CONVENTION + FROM SNMPv2-TC + MODULE-COMPLIANCE, OBJECT-GROUP + FROM SNMPv2-CONF + SnmpAdminString + FROM SNMP-FRAMEWORK-MIB; + +application MODULE-IDENTITY + LAST-UPDATED "200003030000Z" + ORGANIZATION "IETF Mail and Directory Management Working Group" + + CONTACT-INFO + " Ned Freed + + Postal: Innosoft International, Inc. + 1050 Lakes Drive + West Covina, CA 91790 + US + + Tel: +1 626 919 3600 + Fax: +1 626 919 3614 + + E-Mail: ned.freed@innosoft.com" + DESCRIPTION + "The MIB module describing network service applications" + REVISION "200003030000Z" + DESCRIPTION + "This revision, published in RFC 2788, changes a number of + DisplayStrings to SnmpAdminStrings. Note that this change + is not strictly supported by SMIv2. However, the alternative + of deprecating the old objects and defining new objects + would have a more adverse impact on backward compatibility + and interoperability, given the particular semantics of + these objects. The defining reference for distinguished + names has also been updated from RFC 1779 to RFC 2253." + REVISION "199905120000Z" + DESCRIPTION + "This revision fixes a few small technical problems found + in previous versions, mostly in regards to the conformance + groups for different versions of this MIB. No changes have + been made to the objects this MIB defines since RFC 2248." + REVISION "199708170000Z" + DESCRIPTION + "This revision, published in RFC 2248, adds the + applDescription and applURL objects, adds the quiescing + state to the applOperStatus object and renames the MIB + from the APPLICATION-MIB to the NETWORK-SERVICE-MIB." + REVISION "199311280000Z" + DESCRIPTION + "The original version of this MIB was published in RFC 1565" + ::= {mib-2 27} + +-- Textual conventions + +-- DistinguishedName is used to refer to objects in the +-- directory. + +DistinguishedName ::= TEXTUAL-CONVENTION + DISPLAY-HINT "255a" + STATUS current + DESCRIPTION + "A Distinguished Name represented in accordance with + RFC 2253, presented in the UTF-8 charset defined in + RFC 2279." + SYNTAX OCTET STRING (SIZE (0..255)) + +-- Uniform Resource Locators are stored in URLStrings. + +URLString ::= TEXTUAL-CONVENTION + DISPLAY-HINT "255a" + STATUS current + DESCRIPTION + "A Uniform Resource Locator represented in accordance + with RFCs 1738 and 2368, presented in the NVT ASCII + charset defined in RFC 854." + SYNTAX OCTET STRING (SIZE (0..255)) + +-- The basic applTable contains a list of the application +-- entities. + +applTable OBJECT-TYPE + SYNTAX SEQUENCE OF ApplEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table holding objects which apply to all different + kinds of applications providing network services. + Each network service application capable of being + monitored should have a single entry in this table." + ::= {application 1} + +applEntry OBJECT-TYPE + SYNTAX ApplEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry associated with a single network service + application." + INDEX {applIndex} + ::= {applTable 1} + +ApplEntry ::= SEQUENCE { + applIndex + INTEGER, + applName + SnmpAdminString, + applDirectoryName + + DistinguishedName, + applVersion + SnmpAdminString, + applUptime + TimeStamp, + applOperStatus + INTEGER, + applLastChange + TimeStamp, + applInboundAssociations + Gauge32, + applOutboundAssociations + Gauge32, + applAccumulatedInboundAssociations + Counter32, + applAccumulatedOutboundAssociations + Counter32, + applLastInboundActivity + TimeStamp, + applLastOutboundActivity + TimeStamp, + applRejectedInboundAssociations + Counter32, + applFailedOutboundAssociations + Counter32, + applDescription + SnmpAdminString, + applURL + URLString +} + +applIndex OBJECT-TYPE + SYNTAX INTEGER (1..2147483647) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An index to uniquely identify the network service + application. This attribute is the index used for + lexicographic ordering of the table." + ::= {applEntry 1} + +applName OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The name the network service application chooses to be + known by." + ::= {applEntry 2} + +applDirectoryName OBJECT-TYPE + SYNTAX DistinguishedName + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Distinguished Name of the directory entry where + static information about this application is stored. + An empty string indicates that no information about + the application is available in the directory." + ::= {applEntry 3} + +applVersion OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The version of network service application software. + This field is usually defined by the vendor of the + network service application software." + ::= {applEntry 4} +applUptime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time the network service + application was last initialized. If the application was + last initialized prior to the last initialization of the + network management subsystem, then this object contains + a zero value." + ::= {applEntry 5} + +applOperStatus OBJECT-TYPE + SYNTAX INTEGER { + up(1), + down(2), + halted(3), + congested(4), + restarting(5), + quiescing(6) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Indicates the operational status of the network service + application. 'down' indicates that the network service is + + not available. 'up' indicates that the network service + is operational and available. 'halted' indicates that the + service is operational but not available. 'congested' + indicates that the service is operational but no additional + inbound associations can be accommodated. 'restarting' + indicates that the service is currently unavailable but is + in the process of restarting and will be available soon. + 'quiescing' indicates that service is currently operational + but is in the process of shutting down. Additional inbound + associations may be rejected by applications in the + 'quiescing' state." + ::= {applEntry 6} + +applLastChange OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time the network service + application entered its current operational state. If + the current state was entered prior to the last + initialization of the local network management subsystem, + then this object contains a zero value." + ::= {applEntry 7} + +applInboundAssociations OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of current associations to the network service + application, where it is the responder. An inbound + association occurs when another application successfully + connects to this one." + ::= {applEntry 8} + +applOutboundAssociations OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of current associations to the network service + application, where it is the initiator. An outbound + association occurs when this application successfully + connects to another one." + ::= {applEntry 9} + +applAccumulatedInboundAssociations OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of associations to the application entity + since application initialization, where it was the responder." + ::= {applEntry 10} + +applAccumulatedOutboundAssociations OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of associations to the application entity + since application initialization, where it was the initiator." + ::= {applEntry 11} + +applLastInboundActivity OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time this application last + had an inbound association. If the last association + occurred prior to the last initialization of the network + subsystem, then this object contains a zero value." + ::= {applEntry 12} + +applLastOutboundActivity OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time this application last + had an outbound association. If the last association + occurred prior to the last initialization of the network + subsystem, then this object contains a zero value." + ::= {applEntry 13} + +applRejectedInboundAssociations OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of inbound associations the application + entity has rejected, since application initialization. + Rejected associations are not counted in the accumulated + association totals. Note that this only counts + + associations the application entity has rejected itself; + it does not count rejections that occur at lower layers + of the network. Thus, this counter may not reflect the + true number of failed inbound associations." + ::= {applEntry 14} + +applFailedOutboundAssociations OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number associations where the application entity + is initiator and association establishment has failed, + since application initialization. Failed associations are + not counted in the accumulated association totals." + ::= {applEntry 15} + +applDescription OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A text description of the application. This information + is intended to identify and briefly describe the + application in a status display." + ::= {applEntry 16} + +applURL OBJECT-TYPE + SYNTAX URLString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A URL pointing to a description of the application. + This information is intended to identify and describe + the application in a status display." + ::= {applEntry 17} + +-- The assocTable augments the information in the applTable +-- with information about associations. Note that two levels +-- of compliance are specified below, depending on whether +-- association monitoring is mandated. + +assocTable OBJECT-TYPE + SYNTAX SEQUENCE OF AssocEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table holding a set of all active application + + associations." + ::= {application 2} + +assocEntry OBJECT-TYPE + SYNTAX AssocEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry associated with an association for a network + service application." + INDEX {applIndex, assocIndex} + ::= {assocTable 1} + +AssocEntry ::= SEQUENCE { + assocIndex + INTEGER, + assocRemoteApplication + SnmpAdminString, + assocApplicationProtocol + OBJECT IDENTIFIER, + assocApplicationType + INTEGER, + assocDuration + TimeStamp +} + +assocIndex OBJECT-TYPE + SYNTAX INTEGER (1..2147483647) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An index to uniquely identify each association for a network + service application. This attribute is the index that is + used for lexicographic ordering of the table. Note that the + table is also indexed by the applIndex." + ::= {assocEntry 1} + +assocRemoteApplication OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The name of the system running remote network service + application. For an IP-based application this should be + either a domain name or IP address. For an OSI application + it should be the string encoded distinguished name of the + managed object. For X.400(1984) MTAs which do not have a + Distinguished Name, the RFC 2156 syntax 'mta in + + globalid' used in X400-Received: fields can be used. Note, + however, that not all connections an MTA makes are + necessarily to another MTA." + ::= {assocEntry 2} + +assocApplicationProtocol OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An identification of the protocol being used for the + application. For an OSI Application, this will be the + Application Context. For Internet applications, OID + values of the form {applTCPProtoID port} or {applUDPProtoID + port} are used for TCP-based and UDP-based protocols, + respectively. In either case 'port' corresponds to the + primary port number being used by the protocol. The + usual IANA procedures may be used to register ports for + new protocols." + ::= {assocEntry 3} + +assocApplicationType OBJECT-TYPE + SYNTAX INTEGER { + uainitiator(1), + uaresponder(2), + peerinitiator(3), + peerresponder(4)} + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This indicates whether the remote application is some type of + client making use of this network service (e.g., a Mail User + Agent) or a server acting as a peer. Also indicated is whether + the remote end initiated an incoming connection to the network + service or responded to an outgoing connection made by the + local application. MTAs and messaging gateways are + considered to be peers for the purposes of this variable." + ::= {assocEntry 4} + +assocDuration OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time this association was + started. If this association started prior to the last + initialization of the network subsystem, then this + object contains a zero value." + ::= {assocEntry 5} + +-- Conformance information + +applConformance OBJECT IDENTIFIER ::= {application 3} + +applGroups OBJECT IDENTIFIER ::= {applConformance 1} +applCompliances OBJECT IDENTIFIER ::= {applConformance 2} + +-- Compliance statements + +applCompliance MODULE-COMPLIANCE + STATUS obsolete + DESCRIPTION + "The compliance statement for RFC 1565 implementations + which support the Network Services Monitoring MIB + for basic monitoring of network service applications. + This is the basic compliance statement for RFC 1565." + MODULE + MANDATORY-GROUPS {applRFC1565Group} + ::= {applCompliances 1} + +assocCompliance MODULE-COMPLIANCE + STATUS obsolete + DESCRIPTION + "The compliance statement for RFC 1565 implementations + which support the Network Services Monitoring MIB + for basic monitoring of network service applications + and their associations." + MODULE + MANDATORY-GROUPS {applRFC1565Group, assocRFC1565Group} + ::= {applCompliances 2} + +applRFC2248Compliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION + "The compliance statement for RFC 2248 implementations + which support the Network Services Monitoring MIB + for basic monitoring of network service applications." + MODULE + MANDATORY-GROUPS {applRFC2248Group} + ::= {applCompliances 3} + +assocRFC2248Compliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION + "The compliance statement for RFC 2248 implementations + + which support the Network Services Monitoring MIB for + basic monitoring of network service applications and + their associations." + MODULE + MANDATORY-GROUPS {applRFC2248Group, assocRFC2248Group} + ::= {applCompliances 4} + +applRFC2788Compliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for RFC 2788 implementations + which support the Network Services Monitoring MIB + for basic monitoring of network service applications." + MODULE + MANDATORY-GROUPS {applRFC2788Group} + ::= {applCompliances 5} + +assocRFC2788Compliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for RFC 2788 implementations + which support the Network Services Monitoring MIB for + basic monitoring of network service applications and + their associations." + MODULE + MANDATORY-GROUPS {applRFC2788Group, assocRFC2788Group} + ::= {applCompliances 6} + +-- Units of conformance + +applRFC1565Group OBJECT-GROUP + OBJECTS { + applName, applVersion, applUptime, applOperStatus, + applLastChange, applInboundAssociations, + applOutboundAssociations, applAccumulatedInboundAssociations, + applAccumulatedOutboundAssociations, applLastInboundActivity, + applLastOutboundActivity, applRejectedInboundAssociations, + applFailedOutboundAssociations} + STATUS obsolete + DESCRIPTION + "A collection of objects providing basic monitoring of + network service applications. This is the original set + of such objects defined in RFC 1565." + ::= {applGroups 7} + +assocRFC1565Group OBJECT-GROUP + OBJECTS { + + assocRemoteApplication, assocApplicationProtocol, + assocApplicationType, assocDuration} + STATUS obsolete + DESCRIPTION + "A collection of objects providing basic monitoring of + network service applications' associations. This is the + original set of such objects defined in RFC 1565." + ::= {applGroups 2} + +applRFC2248Group OBJECT-GROUP + OBJECTS { + applName, applVersion, applUptime, applOperStatus, + applLastChange, applInboundAssociations, + applOutboundAssociations, applAccumulatedInboundAssociations, + applAccumulatedOutboundAssociations, applLastInboundActivity, + applLastOutboundActivity, applRejectedInboundAssociations, + applFailedOutboundAssociations, applDescription, applURL} + STATUS deprecated + DESCRIPTION + "A collection of objects providing basic monitoring of + network service applications. This group was originally + defined in RFC 2248; note that applDirectoryName is + missing." + ::= {applGroups 3} + +assocRFC2248Group OBJECT-GROUP + OBJECTS { + assocRemoteApplication, assocApplicationProtocol, + assocApplicationType, assocDuration} + STATUS deprecated + DESCRIPTION + "A collection of objects providing basic monitoring of + network service applications' associations. This group + was originally defined by RFC 2248." + ::= {applGroups 4} + +applRFC2788Group OBJECT-GROUP + OBJECTS { + applName, applDirectoryName, applVersion, applUptime, + applOperStatus, applLastChange, applInboundAssociations, + applOutboundAssociations, applAccumulatedInboundAssociations, + applAccumulatedOutboundAssociations, applLastInboundActivity, + applLastOutboundActivity, applRejectedInboundAssociations, + applFailedOutboundAssociations, applDescription, applURL} + STATUS current + DESCRIPTION + "A collection of objects providing basic monitoring of + network service applications. This is the appropriate + + group for RFC 2788 -- it adds the applDirectoryName object + missing in RFC 2248." + ::= {applGroups 5} + +assocRFC2788Group OBJECT-GROUP + OBJECTS { + assocRemoteApplication, assocApplicationProtocol, + assocApplicationType, assocDuration} + STATUS current + DESCRIPTION + "A collection of objects providing basic monitoring of + network service applications' associations. This is + the appropriate group for RFC 2788." + ::= {applGroups 6} + +-- OIDs of the form {applTCPProtoID port} are intended to be used +-- for TCP-based protocols that don't have OIDs assigned by other +-- means. {applUDPProtoID port} serves the same purpose for +-- UDP-based protocols. In either case 'port' corresponds to +-- the primary port number being used by the protocol. For example, +-- assuming no other OID is assigned for SMTP, an OID of +-- {applTCPProtoID 25} could be used, since SMTP is a TCP-based +-- protocol that uses port 25 as its primary port. + +applTCPProtoID OBJECT IDENTIFIER ::= {application 4} +applUDPProtoID OBJECT IDENTIFIER ::= {application 5} + +END diff --git a/mibs/NOTIFICATION-LOG-MIB.txt b/mibs/NOTIFICATION-LOG-MIB.txt new file mode 100644 index 0000000..c7da934 --- /dev/null +++ b/mibs/NOTIFICATION-LOG-MIB.txt @@ -0,0 +1,753 @@ +NOTIFICATION-LOG-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, + Integer32, Unsigned32, + TimeTicks, Counter32, Counter64, + IpAddress, Opaque, mib-2 FROM SNMPv2-SMI + TimeStamp, DateAndTime, + StorageType, RowStatus, + TAddress, TDomain FROM SNMPv2-TC + SnmpAdminString, SnmpEngineID FROM SNMP-FRAMEWORK-MIB + MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF; + +notificationLogMIB MODULE-IDENTITY + LAST-UPDATED "200011270000Z" -- 27 November 2000 + ORGANIZATION "IETF Distributed Management Working Group" + CONTACT-INFO "Ramanathan Kavasseri + Cisco Systems, Inc. + 170 West Tasman Drive, + San Jose CA 95134-1706. + Phone: +1 408 527 2446 + Email: ramk@cisco.com" + DESCRIPTION + "The MIB module for logging SNMP Notifications, that is, Traps + + and Informs." +-- Revision History + + REVISION "200011270000Z" -- 27 November 2000 + DESCRIPTION "This is the initial version of this MIB. + Published as RFC 3014" + ::= { mib-2 92 } + +notificationLogMIBObjects OBJECT IDENTIFIER ::= { notificationLogMIB 1 } + +nlmConfig OBJECT IDENTIFIER ::= { notificationLogMIBObjects 1 } +nlmStats OBJECT IDENTIFIER ::= { notificationLogMIBObjects 2 } +nlmLog OBJECT IDENTIFIER ::= { notificationLogMIBObjects 3 } + +-- +-- Configuration Section +-- + +nlmConfigGlobalEntryLimit OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The maximum number of notification entries that may be held + in nlmLogTable for all nlmLogNames added together. A particular + setting does not guarantee that much data can be held. + + If an application changes the limit while there are + Notifications in the log, the oldest Notifications MUST be + discarded to bring the log down to the new limit - thus the + value of nlmConfigGlobalEntryLimit MUST take precedence over + the values of nlmConfigGlobalAgeOut and nlmConfigLogEntryLimit, + even if the Notification being discarded has been present for + fewer minutes than the value of nlmConfigGlobalAgeOut, or if + the named log has fewer entries than that specified in + nlmConfigLogEntryLimit. + + A value of 0 means no limit. + + Please be aware that contention between multiple managers + trying to set this object to different values MAY affect the + reliability and completeness of data seen by each manager." + DEFVAL { 0 } + ::= { nlmConfig 1 } + +nlmConfigGlobalAgeOut OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "minutes" + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The number of minutes a Notification SHOULD be kept in a log + before it is automatically removed. + + If an application changes the value of nlmConfigGlobalAgeOut, + Notifications older than the new time MAY be discarded to meet the + new time. + + A value of 0 means no age out. + + Please be aware that contention between multiple managers + trying to set this object to different values MAY affect the + reliability and completeness of data seen by each manager." + DEFVAL { 1440 } -- 24 hours + ::= { nlmConfig 2 } + +-- +-- Basic Log Configuration Table +-- + +nlmConfigLogTable OBJECT-TYPE + SYNTAX SEQUENCE OF NlmConfigLogEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of logging control entries." + ::= { nlmConfig 3 } + +nlmConfigLogEntry OBJECT-TYPE + SYNTAX NlmConfigLogEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A logging control entry. Depending on the entry's storage type + entries may be supplied by the system or created and deleted by + applications using nlmConfigLogEntryStatus." + INDEX { nlmLogName } + ::= { nlmConfigLogTable 1 } + +NlmConfigLogEntry ::= SEQUENCE { + nlmLogName SnmpAdminString, + nlmConfigLogFilterName SnmpAdminString, + nlmConfigLogEntryLimit Unsigned32, + nlmConfigLogAdminStatus INTEGER, + nlmConfigLogOperStatus INTEGER, + nlmConfigLogStorageType StorageType, + nlmConfigLogEntryStatus RowStatus + } + +nlmLogName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The name of the log. + + An implementation may allow multiple named logs, up to some + implementation-specific limit (which may be none). A + zero-length log name is reserved for creation and deletion by + the managed system, and MUST be used as the default log name by + systems that do not support named logs." + ::= { nlmConfigLogEntry 1 } + +nlmConfigLogFilterName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A value of snmpNotifyFilterProfileName as used as an index + into the snmpNotifyFilterTable in the SNMP Notification MIB, + specifying the locally or remotely originated Notifications + to be filtered out and not logged in this log. + + A zero-length value or a name that does not identify an + existing entry in snmpNotifyFilterTable indicate no + Notifications are to be logged in this log." + DEFVAL { ''H } + ::= { nlmConfigLogEntry 2 } + +nlmConfigLogEntryLimit OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The maximum number of notification entries that can be held in + nlmLogTable for this named log. A particular setting does not + guarantee that that much data can be held. + + If an application changes the limit while there are + Notifications in the log, the oldest Notifications are discarded + to bring the log down to the new limit. + + A value of 0 indicates no limit. + + Please be aware that contention between multiple managers + trying to set this object to different values MAY affect the + reliability and completeness of data seen by each manager." + DEFVAL { 0 } + ::= { nlmConfigLogEntry 3 } + +nlmConfigLogAdminStatus OBJECT-TYPE + SYNTAX INTEGER { enabled(1), disabled(2) } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Control to enable or disable the log without otherwise + disturbing the log's entry. + + Please be aware that contention between multiple managers + trying to set this object to different values MAY affect the + reliability and completeness of data seen by each manager." + DEFVAL { enabled } + ::= { nlmConfigLogEntry 4 } + +nlmConfigLogOperStatus OBJECT-TYPE + SYNTAX INTEGER { disabled(1), operational(2), noFilter(3) } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The operational status of this log: + + disabled administratively disabled + + operational administratively enabled and working + + noFilter administratively enabled but either + nlmConfigLogFilterName is zero length + or does not name an existing entry in + snmpNotifyFilterTable" + ::= { nlmConfigLogEntry 5 } + +nlmConfigLogStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The storage type of this conceptual row." + ::= { nlmConfigLogEntry 6 } + +nlmConfigLogEntryStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Control for creating and deleting entries. Entries may be + modified while active. + + For non-null-named logs, the managed system records the security + credentials from the request that sets nlmConfigLogStatus + to 'active' and uses that identity to apply access control to + the objects in the Notification to decide if that Notification + may be logged." + ::= { nlmConfigLogEntry 7 } + +-- +-- Statistics Section +-- + +nlmStatsGlobalNotificationsLogged OBJECT-TYPE + SYNTAX Counter32 + UNITS "notifications" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of Notifications put into the nlmLogTable. This + counts a Notification once for each log entry, so a Notification + put into multiple logs is counted multiple times." + ::= { nlmStats 1 } + +nlmStatsGlobalNotificationsBumped OBJECT-TYPE + SYNTAX Counter32 + UNITS "notifications" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of log entries discarded to make room for a new entry + due to lack of resources or the value of nlmConfigGlobalEntryLimit + or nlmConfigLogEntryLimit. This does not include entries discarded + due to the value of nlmConfigGlobalAgeOut." + ::= { nlmStats 2 } + +-- +-- Log Statistics Table +-- + +nlmStatsLogTable OBJECT-TYPE + SYNTAX SEQUENCE OF NlmStatsLogEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of Notification log statistics entries." + ::= { nlmStats 3 } + +nlmStatsLogEntry OBJECT-TYPE + SYNTAX NlmStatsLogEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A Notification log statistics entry." + AUGMENTS { nlmConfigLogEntry } + ::= { nlmStatsLogTable 1 } + +NlmStatsLogEntry ::= SEQUENCE { + nlmStatsLogNotificationsLogged Counter32, + nlmStatsLogNotificationsBumped Counter32 +} + +nlmStatsLogNotificationsLogged OBJECT-TYPE + SYNTAX Counter32 + UNITS "notifications" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of Notifications put in this named log." + ::= { nlmStatsLogEntry 1 } + +nlmStatsLogNotificationsBumped OBJECT-TYPE + SYNTAX Counter32 + UNITS "notifications" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of log entries discarded from this named log to make + room for a new entry due to lack of resources or the value of + nlmConfigGlobalEntryLimit or nlmConfigLogEntryLimit. This does not + include entries discarded due to the value of + nlmConfigGlobalAgeOut." + ::= { nlmStatsLogEntry 2 } + +-- +-- Log Section +-- + +-- +-- Log Table + +-- + +nlmLogTable OBJECT-TYPE + SYNTAX SEQUENCE OF NlmLogEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of Notification log entries. + + It is an implementation-specific matter whether entries in this + table are preserved across initializations of the management + system. In general one would expect that they are not. + + Note that keeping entries across initializations of the + management system leads to some confusion with counters and + TimeStamps, since both of those are based on sysUpTime, which + resets on management initialization. In this situation, + counters apply only after the reset and nlmLogTime for entries + made before the reset MUST be set to 0." + ::= { nlmLog 1 } + +nlmLogEntry OBJECT-TYPE + SYNTAX NlmLogEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A Notification log entry. + + Entries appear in this table when Notifications occur and pass + filtering by nlmConfigLogFilterName and access control. They are + removed to make way for new entries due to lack of resources or + the values of nlmConfigGlobalEntryLimit, nlmConfigGlobalAgeOut, or + nlmConfigLogEntryLimit. + + If adding an entry would exceed nlmConfigGlobalEntryLimit or system + resources in general, the oldest entry in any log SHOULD be removed + to make room for the new one. + + If adding an entry would exceed nlmConfigLogEntryLimit the oldest + entry in that log SHOULD be removed to make room for the new one. + + Before the managed system puts a locally-generated Notification + into a non-null-named log it assures that the creator of the log + has access to the information in the Notification. If not it + does not log that Notification in that log." + INDEX { nlmLogName, nlmLogIndex } + ::= { nlmLogTable 1 } + +NlmLogEntry ::= SEQUENCE { + nlmLogIndex Unsigned32, + nlmLogTime TimeStamp, + nlmLogDateAndTime DateAndTime, + nlmLogEngineID SnmpEngineID, + nlmLogEngineTAddress TAddress, + nlmLogEngineTDomain TDomain, + nlmLogContextEngineID SnmpEngineID, + nlmLogContextName SnmpAdminString, + nlmLogNotificationID OBJECT IDENTIFIER +} + +nlmLogIndex OBJECT-TYPE + SYNTAX Unsigned32 (1..4294967295) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A monotonically increasing integer for the sole purpose of + indexing entries within the named log. When it reaches the + maximum value, an extremely unlikely event, the agent wraps the + value back to 1." + ::= { nlmLogEntry 1 } + +nlmLogTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime when the entry was placed in the log. If + the entry occurred before the most recent management system + initialization this object value MUST be set to zero." + ::= { nlmLogEntry 2 } + +nlmLogDateAndTime OBJECT-TYPE + SYNTAX DateAndTime + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The local date and time when the entry was logged, instantiated + only by systems that have date and time capability." + ::= { nlmLogEntry 3 } + +nlmLogEngineID OBJECT-TYPE + SYNTAX SnmpEngineID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The identification of the SNMP engine at which the Notification + + originated. + + If the log can contain Notifications from only one engine + or the Trap is in SNMPv1 format, this object is a zero-length + string." + ::= { nlmLogEntry 4 } + +nlmLogEngineTAddress OBJECT-TYPE + SYNTAX TAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The transport service address of the SNMP engine from which the + Notification was received, formatted according to the corresponding + value of nlmLogEngineTDomain. This is used to identify the source + of an SNMPv1 trap, since an nlmLogEngineId cannot be extracted + from the SNMPv1 trap pdu. + + This object MUST always be instantiated, even if the log + can contain Notifications from only one engine. + + Please be aware that the nlmLogEngineTAddress may not uniquely + identify the SNMP engine from which the Notification was received. + For example, if an SNMP engine uses DHCP or NAT to obtain + ip addresses, the address it uses may be shared with other + network devices, and hence will not uniquely identify the + SNMP engine." + ::= { nlmLogEntry 5 } + +nlmLogEngineTDomain OBJECT-TYPE + SYNTAX TDomain + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Indicates the kind of transport service by which a Notification + was received from an SNMP engine. nlmLogEngineTAddress contains + the transport service address of the SNMP engine from which + this Notification was received. + + Possible values for this object are presently found in the + Transport Mappings for SNMPv2 document (RFC 1906 [8])." + ::= { nlmLogEntry 6 } + +nlmLogContextEngineID OBJECT-TYPE + SYNTAX SnmpEngineID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "If the Notification was received in a protocol which has a + contextEngineID element like SNMPv3, this object has that value. + Otherwise its value is a zero-length string." + ::= { nlmLogEntry 7 } + +nlmLogContextName OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The name of the SNMP MIB context from which the Notification came. + For SNMPv1 Traps this is the community string from the Trap." + ::= { nlmLogEntry 8 } + +nlmLogNotificationID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The NOTIFICATION-TYPE object identifier of the Notification that + occurred." + ::= { nlmLogEntry 9 } + +-- +-- Log Variable Table +-- + +nlmLogVariableTable OBJECT-TYPE + SYNTAX SEQUENCE OF NlmLogVariableEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of variables to go with Notification log entries." + ::= { nlmLog 2 } + +nlmLogVariableEntry OBJECT-TYPE + SYNTAX NlmLogVariableEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A Notification log entry variable. + + Entries appear in this table when there are variables in + the varbind list of a Notification in nlmLogTable." + INDEX { nlmLogName, nlmLogIndex, nlmLogVariableIndex } + ::= { nlmLogVariableTable 1 } + +NlmLogVariableEntry ::= SEQUENCE { + + nlmLogVariableIndex Unsigned32, + nlmLogVariableID OBJECT IDENTIFIER, + nlmLogVariableValueType INTEGER, + nlmLogVariableCounter32Val Counter32, + nlmLogVariableUnsigned32Val Unsigned32, + nlmLogVariableTimeTicksVal TimeTicks, + nlmLogVariableInteger32Val Integer32, + nlmLogVariableOctetStringVal OCTET STRING, + nlmLogVariableIpAddressVal IpAddress, + nlmLogVariableOidVal OBJECT IDENTIFIER, + nlmLogVariableCounter64Val Counter64, + nlmLogVariableOpaqueVal Opaque +} + +nlmLogVariableIndex OBJECT-TYPE + SYNTAX Unsigned32 (1..4294967295) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A monotonically increasing integer, starting at 1 for a given + nlmLogIndex, for indexing variables within the logged + Notification." + ::= { nlmLogVariableEntry 1 } + +nlmLogVariableID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The variable's object identifier." + ::= { nlmLogVariableEntry 2 } + +nlmLogVariableValueType OBJECT-TYPE + SYNTAX INTEGER { counter32(1), unsigned32(2), timeTicks(3), + integer32(4), ipAddress(5), octetString(6), + objectId(7), counter64(8), opaque(9) } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The type of the value. One and only one of the value + objects that follow must be instantiated, based on this type." + ::= { nlmLogVariableEntry 3 } + +nlmLogVariableCounter32Val OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when nlmLogVariableType is 'counter32'." + ::= { nlmLogVariableEntry 4 } + +nlmLogVariableUnsigned32Val OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when nlmLogVariableType is 'unsigned32'." + ::= { nlmLogVariableEntry 5 } + +nlmLogVariableTimeTicksVal OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when nlmLogVariableType is 'timeTicks'." + ::= { nlmLogVariableEntry 6 } + +nlmLogVariableInteger32Val OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when nlmLogVariableType is 'integer32'." + ::= { nlmLogVariableEntry 7 } + +nlmLogVariableOctetStringVal OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when nlmLogVariableType is 'octetString'." + ::= { nlmLogVariableEntry 8 } + +nlmLogVariableIpAddressVal OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when nlmLogVariableType is 'ipAddress'. + Although this seems to be unfriendly for IPv6, we + have to recognize that there are a number of older + MIBs that do contain an IPv4 format address, known + as IpAddress. + + IPv6 addresses are represented using TAddress or + InetAddress, and so the underlying datatype is + + OCTET STRING, and their value would be stored in + the nlmLogVariableOctetStringVal column." + ::= { nlmLogVariableEntry 9 } + +nlmLogVariableOidVal OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when nlmLogVariableType is 'objectId'." + ::= { nlmLogVariableEntry 10 } + +nlmLogVariableCounter64Val OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when nlmLogVariableType is 'counter64'." + ::= { nlmLogVariableEntry 11 } + +nlmLogVariableOpaqueVal OBJECT-TYPE + SYNTAX Opaque + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value when nlmLogVariableType is 'opaque'." + ::= { nlmLogVariableEntry 12 } + +-- +-- Conformance +-- + +notificationLogMIBConformance OBJECT IDENTIFIER ::= + { notificationLogMIB 3 } +notificationLogMIBCompliances OBJECT IDENTIFIER ::= + { notificationLogMIBConformance 1 } +notificationLogMIBGroups OBJECT IDENTIFIER ::= + { notificationLogMIBConformance 2 } + +-- Compliance + +notificationLogMIBCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for entities which implement + the Notification Log MIB." + MODULE -- this module + + MANDATORY-GROUPS { + notificationLogConfigGroup, + notificationLogStatsGroup, + notificationLogLogGroup + } + + OBJECT nlmConfigGlobalEntryLimit + SYNTAX Unsigned32 (0..4294967295) + MIN-ACCESS read-only + DESCRIPTION + "Implementations may choose a limit and not allow it to be + changed or may enforce an upper or lower bound on the + limit." + + OBJECT nlmConfigLogEntryLimit + SYNTAX Unsigned32 (0..4294967295) + MIN-ACCESS read-only + DESCRIPTION + "Implementations may choose a limit and not allow it to be + changed or may enforce an upper or lower bound on the + limit." + + OBJECT nlmConfigLogEntryStatus + MIN-ACCESS read-only + DESCRIPTION + "Implementations may disallow the creation of named logs." + + GROUP notificationLogDateGroup + DESCRIPTION + "This group is mandatory on systems that keep wall clock + date and time and should not be implemented on systems that + do not have a wall clock date." + ::= { notificationLogMIBCompliances 1 } + +-- Units of Conformance + +notificationLogConfigGroup OBJECT-GROUP + OBJECTS { + nlmConfigGlobalEntryLimit, + nlmConfigGlobalAgeOut, + nlmConfigLogFilterName, + nlmConfigLogEntryLimit, + nlmConfigLogAdminStatus, + nlmConfigLogOperStatus, + nlmConfigLogStorageType, + nlmConfigLogEntryStatus + } + STATUS current + DESCRIPTION + "Notification log configuration management." + ::= { notificationLogMIBGroups 1 } + +notificationLogStatsGroup OBJECT-GROUP + OBJECTS { + nlmStatsGlobalNotificationsLogged, + nlmStatsGlobalNotificationsBumped, + nlmStatsLogNotificationsLogged, + nlmStatsLogNotificationsBumped + } + STATUS current + DESCRIPTION + "Notification log statistics." + ::= { notificationLogMIBGroups 2 } + +notificationLogLogGroup OBJECT-GROUP + OBJECTS { + nlmLogTime, + nlmLogEngineID, + nlmLogEngineTAddress, + nlmLogEngineTDomain, + nlmLogContextEngineID, + nlmLogContextName, + nlmLogNotificationID, + nlmLogVariableID, + nlmLogVariableValueType, + nlmLogVariableCounter32Val, + nlmLogVariableUnsigned32Val, + nlmLogVariableTimeTicksVal, + nlmLogVariableInteger32Val, + nlmLogVariableOctetStringVal, + nlmLogVariableIpAddressVal, + nlmLogVariableOidVal, + nlmLogVariableCounter64Val, + nlmLogVariableOpaqueVal + } + STATUS current + DESCRIPTION + "Notification log data." + ::= { notificationLogMIBGroups 3 } + +notificationLogDateGroup OBJECT-GROUP + OBJECTS { + nlmLogDateAndTime + } + STATUS current + DESCRIPTION + "Conditionally mandatory notification log data. + This group is mandatory on systems that keep wall + clock date and time and should not be implemented + on systems that do not have a wall clock date." + ::= { notificationLogMIBGroups 4 } + +END diff --git a/mibs/OSPF-MIB.txt b/mibs/OSPF-MIB.txt new file mode 100644 index 0000000..de7d03f --- /dev/null +++ b/mibs/OSPF-MIB.txt @@ -0,0 +1,2723 @@ +OSPF-MIB DEFINITIONS ::= BEGIN + + IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, Counter32, Gauge32, + Integer32, IpAddress + FROM SNMPv2-SMI + TEXTUAL-CONVENTION, TruthValue, RowStatus + FROM SNMPv2-TC + MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF + mib-2 FROM RFC1213-MIB; + +-- This MIB module uses the extended OBJECT-TYPE macro as +-- defined in [9]. + +ospf MODULE-IDENTITY + LAST-UPDATED "9501201225Z" -- Fri Jan 20 12:25:50 PST 1995 + ORGANIZATION "IETF OSPF Working Group" + CONTACT-INFO + " Fred Baker + Postal: Cisco Systems + 519 Lado Drive + Santa Barbara, California 93111 + Tel: +1 805 681 0115 + E-Mail: fred@cisco.com + + Rob Coltun + Postal: RainbowBridge Communications + Tel: (301) 340-9416 + E-Mail: rcoltun@rainbow-bridge.com" + DESCRIPTION + "The MIB module to describe the OSPF Version 2 + Protocol" + ::= { mib-2 14 } + +-- The Area ID, in OSPF, has the same format as an IP Address, +-- but has the function of defining a summarization point for +-- Link State Advertisements + +AreaID ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "An OSPF Area Identifier." + SYNTAX IpAddress + + +-- The Router ID, in OSPF, has the same format as an IP Address, +-- but identifies the router independent of its IP Address. + +RouterID ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "A OSPF Router Identifier." + SYNTAX IpAddress + + +-- The OSPF Metric is defined as an unsigned value in the range + +Metric ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The OSPF Internal Metric." + SYNTAX Integer32 (0..'FFFF'h) + +BigMetric ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The OSPF External Metric." + SYNTAX Integer32 (0..'FFFFFF'h) + +-- Status Values + +Status ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The status of an interface: 'enabled' indicates that + it is willing to communicate with other OSPF Routers, + while 'disabled' indicates that it is not." + SYNTAX INTEGER { enabled (1), disabled (2) } + +-- Time Durations measured in seconds + +PositiveInteger ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "A positive integer. Values in excess are precluded as + unnecessary and prone to interoperability issues." + SYNTAX Integer32 (0..'7FFFFFFF'h) + +HelloRange ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The range of intervals on which hello messages are + exchanged." + SYNTAX Integer32 (1..'FFFF'h) + +UpToMaxAge ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The values that one might find or configure for + variables bounded by the maximum age of an LSA." + SYNTAX Integer32 (0..3600) + + +-- The range of ifIndex + +InterfaceIndex ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The range of ifIndex." + SYNTAX Integer32 + + +-- Potential Priorities for the Designated Router Election + +DesignatedRouterPriority ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The values defined for the priority of a system for + becoming the designated router." + SYNTAX Integer32 (0..'FF'h) + +TOSType ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Type of Service is defined as a mapping to the IP Type of + Service Flags as defined in the IP Forwarding Table MIB + + +-----+-----+-----+-----+-----+-----+-----+-----+ + | | | | + | PRECEDENCE | TYPE OF SERVICE | 0 | + | | | | + +-----+-----+-----+-----+-----+-----+-----+-----+ + + IP TOS IP TOS + Field Policy Field Policy + + Contents Code Contents Code + 0 0 0 0 ==> 0 0 0 0 1 ==> 2 + 0 0 1 0 ==> 4 0 0 1 1 ==> 6 + 0 1 0 0 ==> 8 0 1 0 1 ==> 10 + 0 1 1 0 ==> 12 0 1 1 1 ==> 14 + 1 0 0 0 ==> 16 1 0 0 1 ==> 18 + 1 0 1 0 ==> 20 1 0 1 1 ==> 22 + 1 1 0 0 ==> 24 1 1 0 1 ==> 26 + 1 1 1 0 ==> 28 1 1 1 1 ==> 30 + + The remaining values are left for future definition." + SYNTAX Integer32 (0..30) + + +-- OSPF General Variables + +-- These parameters apply globally to the Router's +-- OSPF Process. + +ospfGeneralGroup OBJECT IDENTIFIER ::= { ospf 1 } + + + ospfRouterId OBJECT-TYPE + SYNTAX RouterID + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "A 32-bit integer uniquely identifying the + router in the Autonomous System. + + By convention, to ensure uniqueness, this + should default to the value of one of the + router's IP interface addresses." + REFERENCE + "OSPF Version 2, C.1 Global parameters" + ::= { ospfGeneralGroup 1 } + + + ospfAdminStat OBJECT-TYPE + SYNTAX Status + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The administrative status of OSPF in the + router. The value 'enabled' denotes that the + OSPF Process is active on at least one inter- + face; 'disabled' disables it on all inter- + faces." + ::= { ospfGeneralGroup 2 } + + ospfVersionNumber OBJECT-TYPE + SYNTAX INTEGER { version2 (2) } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current version number of the OSPF proto- + col is 2." + REFERENCE + "OSPF Version 2, Title" + ::= { ospfGeneralGroup 3 } + + + ospfAreaBdrRtrStatus OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A flag to note whether this router is an area + border router." + REFERENCE + "OSPF Version 2, Section 3 Splitting the AS into + Areas" + ::= { ospfGeneralGroup 4 } + + + ospfASBdrRtrStatus OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "A flag to note whether this router is config- + ured as an Autonomous System border router." + REFERENCE + "OSPF Version 2, Section 3.3 Classification of + routers" + ::= { ospfGeneralGroup 5 } + + ospfExternLsaCount OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of external (LS type 5) link-state + advertisements in the link-state database." + REFERENCE + "OSPF Version 2, Appendix A.4.5 AS external link + advertisements" + ::= { ospfGeneralGroup 6 } + + + ospfExternLsaCksumSum OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The 32-bit unsigned sum of the LS checksums of + the external link-state advertisements con- + tained in the link-state database. This sum + can be used to determine if there has been a + change in a router's link state database, and + to compare the link-state database of two + routers." + ::= { ospfGeneralGroup 7 } + + + ospfTOSSupport OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The router's support for type-of-service rout- + ing." + REFERENCE + "OSPF Version 2, Appendix F.1.2 Optional TOS + support" + ::= { ospfGeneralGroup 8 } + + ospfOriginateNewLsas OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of new link-state advertisements + that have been originated. This number is in- + cremented each time the router originates a new + LSA." + ::= { ospfGeneralGroup 9 } + + + ospfRxNewLsas OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of link-state advertisements re- + ceived determined to be new instantiations. + This number does not include newer instantia- + tions of self-originated link-state advertise- + ments." + ::= { ospfGeneralGroup 10 } + + ospfExtLsdbLimit OBJECT-TYPE + SYNTAX Integer32 (-1..'7FFFFFFF'h) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The maximum number of non-default AS- + external-LSAs entries that can be stored in the + link-state database. If the value is -1, then + there is no limit. + + When the number of non-default AS-external-LSAs + in a router's link-state database reaches + ospfExtLsdbLimit, the router enters Overflow- + State. The router never holds more than + ospfExtLsdbLimit non-default AS-external-LSAs + in its database. OspfExtLsdbLimit MUST be set + identically in all routers attached to the OSPF + backbone and/or any regular OSPF area. (i.e., + OSPF stub areas and NSSAs are excluded)." + DEFVAL { -1 } + ::= { ospfGeneralGroup 11 } + + ospfMulticastExtensions OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "A Bit Mask indicating whether the router is + forwarding IP multicast (Class D) datagrams + based on the algorithms defined in the Multi- + cast Extensions to OSPF. + + Bit 0, if set, indicates that the router can + forward IP multicast datagrams in the router's + directly attached areas (called intra-area mul- + ticast routing). + + Bit 1, if set, indicates that the router can + forward IP multicast datagrams between OSPF + areas (called inter-area multicast routing). + + Bit 2, if set, indicates that the router can + forward IP multicast datagrams between Auto- + nomous Systems (called inter-AS multicast rout- + ing). + + Only certain combinations of bit settings are + allowed, namely: 0 (no multicast forwarding is + enabled), 1 (intra-area multicasting only), 3 + (intra-area and inter-area multicasting), 5 + (intra-area and inter-AS multicasting) and 7 + (multicasting everywhere). By default, no mul- + ticast forwarding is enabled." + DEFVAL { 0 } + ::= { ospfGeneralGroup 12 } + + ospfExitOverflowInterval OBJECT-TYPE + SYNTAX PositiveInteger + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The number of seconds that, after entering + OverflowState, a router will attempt to leave + OverflowState. This allows the router to again + originate non-default AS-external-LSAs. When + set to 0, the router will not leave Overflow- + State until restarted." + DEFVAL { 0 } + ::= { ospfGeneralGroup 13 } + + + ospfDemandExtensions OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The router's support for demand routing." + REFERENCE + "OSPF Version 2, Appendix on Demand Routing" + ::= { ospfGeneralGroup 14 } + + +-- The OSPF Area Data Structure contains information +-- regarding the various areas. The interfaces and +-- virtual links are configured as part of these areas. +-- Area 0.0.0.0, by definition, is the Backbone Area + + + ospfAreaTable OBJECT-TYPE + SYNTAX SEQUENCE OF OspfAreaEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information describing the configured parame- + ters and cumulative statistics of the router's + attached areas." + REFERENCE + "OSPF Version 2, Section 6 The Area Data Struc- + ture" + ::= { ospf 2 } + + + ospfAreaEntry OBJECT-TYPE + SYNTAX OspfAreaEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information describing the configured parame- + ters and cumulative statistics of one of the + router's attached areas." + INDEX { ospfAreaId } + ::= { ospfAreaTable 1 } + +OspfAreaEntry ::= + SEQUENCE { + ospfAreaId + AreaID, + ospfAuthType + Integer32, + ospfImportAsExtern + INTEGER, + ospfSpfRuns + Counter32, + ospfAreaBdrRtrCount + Gauge32, + ospfAsBdrRtrCount + Gauge32, + ospfAreaLsaCount + Gauge32, + ospfAreaLsaCksumSum + Integer32, + ospfAreaSummary + INTEGER, + ospfAreaStatus + RowStatus + } + + ospfAreaId OBJECT-TYPE + SYNTAX AreaID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A 32-bit integer uniquely identifying an area. + Area ID 0.0.0.0 is used for the OSPF backbone." + REFERENCE + "OSPF Version 2, Appendix C.2 Area parameters" + ::= { ospfAreaEntry 1 } + + + ospfAuthType OBJECT-TYPE + SYNTAX Integer32 + -- none (0), + -- simplePassword (1) + -- md5 (2) + -- reserved for specification by IANA (> 2) + MAX-ACCESS read-create + STATUS obsolete + DESCRIPTION + "The authentication type specified for an area. + Additional authentication types may be assigned + locally on a per Area basis." + REFERENCE + "OSPF Version 2, Appendix E Authentication" + DEFVAL { 0 } -- no authentication, by default + ::= { ospfAreaEntry 2 } + + ospfImportAsExtern OBJECT-TYPE + SYNTAX INTEGER { + importExternal (1), + importNoExternal (2), + importNssa (3) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The area's support for importing AS external + link- state advertisements." + REFERENCE + "OSPF Version 2, Appendix C.2 Area parameters" + DEFVAL { importExternal } + ::= { ospfAreaEntry 3 } + + + ospfSpfRuns OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times that the intra-area route + table has been calculated using this area's + link-state database. This is typically done + using Dijkstra's algorithm." + ::= { ospfAreaEntry 4 } + + + ospfAreaBdrRtrCount OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of area border routers reach- + able within this area. This is initially zero, + and is calculated in each SPF Pass." + ::= { ospfAreaEntry 5 } + + ospfAsBdrRtrCount OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of Autonomous System border + routers reachable within this area. This is + initially zero, and is calculated in each SPF + Pass." + ::= { ospfAreaEntry 6 } + + + ospfAreaLsaCount OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of link-state advertisements + in this area's link-state database, excluding + AS External LSA's." + ::= { ospfAreaEntry 7 } + + + ospfAreaLsaCksumSum OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The 32-bit unsigned sum of the link-state ad- + vertisements' LS checksums contained in this + area's link-state database. This sum excludes + external (LS type 5) link-state advertisements. + The sum can be used to determine if there has + been a change in a router's link state data- + base, and to compare the link-state database of + two routers." + DEFVAL { 0 } + ::= { ospfAreaEntry 8 } + + ospfAreaSummary OBJECT-TYPE + SYNTAX INTEGER { + noAreaSummary (1), + sendAreaSummary (2) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The variable ospfAreaSummary controls the im- + port of summary LSAs into stub areas. It has + no effect on other areas. + + If it is noAreaSummary, the router will neither + originate nor propagate summary LSAs into the + stub area. It will rely entirely on its de- + fault route. + + If it is sendAreaSummary, the router will both + summarize and propagate summary LSAs." + DEFVAL { noAreaSummary } + ::= { ospfAreaEntry 9 } + + + ospfAreaStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This variable displays the status of the en- + try. Setting it to 'invalid' has the effect of + rendering it inoperative. The internal effect + (row removal) is implementation dependent." + ::= { ospfAreaEntry 10 } + + +-- OSPF Area Default Metric Table + +-- The OSPF Area Default Metric Table describes the metrics +-- that a default Area Border Router will advertise into a +-- Stub area. + + + ospfStubAreaTable OBJECT-TYPE + SYNTAX SEQUENCE OF OspfStubAreaEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The set of metrics that will be advertised by + a default Area Border Router into a stub area." + REFERENCE + "OSPF Version 2, Appendix C.2, Area Parameters" + ::= { ospf 3 } + + + ospfStubAreaEntry OBJECT-TYPE + SYNTAX OspfStubAreaEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The metric for a given Type of Service that + will be advertised by a default Area Border + Router into a stub area." + REFERENCE + "OSPF Version 2, Appendix C.2, Area Parameters" + INDEX { ospfStubAreaId, ospfStubTOS } + ::= { ospfStubAreaTable 1 } + +OspfStubAreaEntry ::= + SEQUENCE { + ospfStubAreaId + AreaID, + ospfStubTOS + TOSType, + ospfStubMetric + BigMetric, + ospfStubStatus + RowStatus, + ospfStubMetricType + INTEGER + } + + ospfStubAreaId OBJECT-TYPE + SYNTAX AreaID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The 32 bit identifier for the Stub Area. On + creation, this can be derived from the in- + stance." + ::= { ospfStubAreaEntry 1 } + + + ospfStubTOS OBJECT-TYPE + SYNTAX TOSType + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Type of Service associated with the + metric. On creation, this can be derived from + the instance." + ::= { ospfStubAreaEntry 2 } + + + ospfStubMetric OBJECT-TYPE + SYNTAX BigMetric + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The metric value applied at the indicated type + of service. By default, this equals the least + metric at the type of service among the inter- + faces to other areas." + ::= { ospfStubAreaEntry 3 } + + + ospfStubStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This variable displays the status of the en- + try. Setting it to 'invalid' has the effect of + rendering it inoperative. The internal effect + (row removal) is implementation dependent." + ::= { ospfStubAreaEntry 4 } + + ospfStubMetricType OBJECT-TYPE + SYNTAX INTEGER { + ospfMetric (1), -- OSPF Metric + comparableCost (2), -- external type 1 + nonComparable (3) -- external type 2 + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This variable displays the type of metric ad- + vertised as a default route." + DEFVAL { ospfMetric } + ::= { ospfStubAreaEntry 5 } + +-- OSPF Link State Database + +-- The Link State Database contains the Link State +-- Advertisements from throughout the areas that the +-- device is attached to. + + + ospfLsdbTable OBJECT-TYPE + SYNTAX SEQUENCE OF OspfLsdbEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The OSPF Process's Link State Database." + REFERENCE + "OSPF Version 2, Section 12 Link State Adver- + tisements" + ::= { ospf 4 } + + + ospfLsdbEntry OBJECT-TYPE + SYNTAX OspfLsdbEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A single Link State Advertisement." + INDEX { ospfLsdbAreaId, ospfLsdbType, + ospfLsdbLsid, ospfLsdbRouterId } + ::= { ospfLsdbTable 1 } + +OspfLsdbEntry ::= + SEQUENCE { + ospfLsdbAreaId + AreaID, + ospfLsdbType + INTEGER, + ospfLsdbLsid + IpAddress, + ospfLsdbRouterId + RouterID, + ospfLsdbSequence + Integer32, + ospfLsdbAge + Integer32, + ospfLsdbChecksum + Integer32, + ospfLsdbAdvertisement + OCTET STRING + } + ospfLsdbAreaId OBJECT-TYPE + SYNTAX AreaID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The 32 bit identifier of the Area from which + the LSA was received." + REFERENCE + "OSPF Version 2, Appendix C.2 Area parameters" + ::= { ospfLsdbEntry 1 } + +-- External Link State Advertisements are permitted +-- for backward compatibility, but should be displayed in +-- the ospfExtLsdbTable rather than here. + + ospfLsdbType OBJECT-TYPE + SYNTAX INTEGER { + routerLink (1), + networkLink (2), + summaryLink (3), + asSummaryLink (4), + asExternalLink (5), -- but see ospfExtLsdbTable + multicastLink (6), + nssaExternalLink (7) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The type of the link state advertisement. + Each link state type has a separate advertise- + ment format." + REFERENCE + "OSPF Version 2, Appendix A.4.1 The Link State + Advertisement header" + ::= { ospfLsdbEntry 2 } + + ospfLsdbLsid OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Link State ID is an LS Type Specific field + containing either a Router ID or an IP Address; + it identifies the piece of the routing domain + that is being described by the advertisement." + REFERENCE + "OSPF Version 2, Section 12.1.4 Link State ID" + ::= { ospfLsdbEntry 3 } + ospfLsdbRouterId OBJECT-TYPE + SYNTAX RouterID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The 32 bit number that uniquely identifies the + originating router in the Autonomous System." + REFERENCE + "OSPF Version 2, Appendix C.1 Global parameters" + ::= { ospfLsdbEntry 4 } + +-- Note that the OSPF Sequence Number is a 32 bit signed +-- integer. It starts with the value '80000001'h, +-- or -'7FFFFFFF'h, and increments until '7FFFFFFF'h +-- Thus, a typical sequence number will be very negative. + + ospfLsdbSequence OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The sequence number field is a signed 32-bit + integer. It is used to detect old and dupli- + cate link state advertisements. The space of + sequence numbers is linearly ordered. The + larger the sequence number the more recent the + advertisement." + REFERENCE + "OSPF Version 2, Section 12.1.6 LS sequence + number" + ::= { ospfLsdbEntry 5 } + + + ospfLsdbAge OBJECT-TYPE + SYNTAX Integer32 -- Should be 0..MaxAge + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This field is the age of the link state adver- + tisement in seconds." + REFERENCE + "OSPF Version 2, Section 12.1.1 LS age" + ::= { ospfLsdbEntry 6 } + + ospfLsdbChecksum OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This field is the checksum of the complete + contents of the advertisement, excepting the + age field. The age field is excepted so that + an advertisement's age can be incremented + without updating the checksum. The checksum + used is the same that is used for ISO connec- + tionless datagrams; it is commonly referred to + as the Fletcher checksum." + REFERENCE + "OSPF Version 2, Section 12.1.7 LS checksum" + ::= { ospfLsdbEntry 7 } + + + ospfLsdbAdvertisement OBJECT-TYPE + SYNTAX OCTET STRING (SIZE (1..65535)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The entire Link State Advertisement, including + its header." + REFERENCE + "OSPF Version 2, Section 12 Link State Adver- + tisements" + ::= { ospfLsdbEntry 8 } + + +-- Address Range Table + +-- The Address Range Table acts as an adjunct to the Area +-- Table; It describes those Address Range Summaries that +-- are configured to be propagated from an Area to reduce +-- the amount of information about it which is known beyond +-- its borders. + + ospfAreaRangeTable OBJECT-TYPE + SYNTAX SEQUENCE OF OspfAreaRangeEntry + MAX-ACCESS not-accessible + STATUS obsolete + DESCRIPTION + "A range if IP addresses specified by an IP + address/IP network mask pair. For example, + class B address range of X.X.X.X with a network + mask of 255.255.0.0 includes all IP addresses + from X.X.0.0 to X.X.255.255" + REFERENCE + "OSPF Version 2, Appendix C.2 Area parameters" + ::= { ospf 5 } + ospfAreaRangeEntry OBJECT-TYPE + SYNTAX OspfAreaRangeEntry + MAX-ACCESS not-accessible + STATUS obsolete + DESCRIPTION + "A range if IP addresses specified by an IP + address/IP network mask pair. For example, + class B address range of X.X.X.X with a network + mask of 255.255.0.0 includes all IP addresses + from X.X.0.0 to X.X.255.255" + REFERENCE + "OSPF Version 2, Appendix C.2 Area parameters" + INDEX { ospfAreaRangeAreaId, ospfAreaRangeNet } + ::= { ospfAreaRangeTable 1 } + +OspfAreaRangeEntry ::= + SEQUENCE { + ospfAreaRangeAreaId + AreaID, + ospfAreaRangeNet + IpAddress, + ospfAreaRangeMask + IpAddress, + ospfAreaRangeStatus + RowStatus, + ospfAreaRangeEffect + INTEGER + } + + ospfAreaRangeAreaId OBJECT-TYPE + SYNTAX AreaID + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The Area the Address Range is to be found + within." + REFERENCE + "OSPF Version 2, Appendix C.2 Area parameters" + ::= { ospfAreaRangeEntry 1 } + + + ospfAreaRangeNet OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The IP Address of the Net or Subnet indicated + by the range." + REFERENCE + "OSPF Version 2, Appendix C.2 Area parameters" + ::= { ospfAreaRangeEntry 2 } + + + ospfAreaRangeMask OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-create + STATUS obsolete + DESCRIPTION + "The Subnet Mask that pertains to the Net or + Subnet." + REFERENCE + "OSPF Version 2, Appendix C.2 Area parameters" + ::= { ospfAreaRangeEntry 3 } + + ospfAreaRangeStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS obsolete + DESCRIPTION + "This variable displays the status of the en- + try. Setting it to 'invalid' has the effect of + rendering it inoperative. The internal effect + (row removal) is implementation dependent." + ::= { ospfAreaRangeEntry 4 } + + + ospfAreaRangeEffect OBJECT-TYPE + SYNTAX INTEGER { + advertiseMatching (1), + doNotAdvertiseMatching (2) + } + MAX-ACCESS read-create + STATUS obsolete + DESCRIPTION + "Subnets subsumed by ranges either trigger the + advertisement of the indicated summary (adver- + tiseMatching), or result in the subnet's not + being advertised at all outside the area." + DEFVAL { advertiseMatching } + ::= { ospfAreaRangeEntry 5 } + + + +-- OSPF Host Table + +-- The Host/Metric Table indicates what hosts are directly +-- attached to the Router, and what metrics and types of +-- service should be advertised for them. + + ospfHostTable OBJECT-TYPE + SYNTAX SEQUENCE OF OspfHostEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The list of Hosts, and their metrics, that the + router will advertise as host routes." + REFERENCE + "OSPF Version 2, Appendix C.6 Host route param- + eters" + ::= { ospf 6 } + + + ospfHostEntry OBJECT-TYPE + SYNTAX OspfHostEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A metric to be advertised, for a given type of + service, when a given host is reachable." + INDEX { ospfHostIpAddress, ospfHostTOS } + ::= { ospfHostTable 1 } + +OspfHostEntry ::= + SEQUENCE { + ospfHostIpAddress + IpAddress, + ospfHostTOS + TOSType, + ospfHostMetric + Metric, + ospfHostStatus + RowStatus, + ospfHostAreaID + AreaID + } + + ospfHostIpAddress OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The IP Address of the Host." + REFERENCE + "OSPF Version 2, Appendix C.6 Host route parame- + ters" + ::= { ospfHostEntry 1 } + + + ospfHostTOS OBJECT-TYPE + SYNTAX TOSType + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Type of Service of the route being config- + ured." + REFERENCE + "OSPF Version 2, Appendix C.6 Host route parame- + ters" + ::= { ospfHostEntry 2 } + + + ospfHostMetric OBJECT-TYPE + SYNTAX Metric + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The Metric to be advertised." + REFERENCE + "OSPF Version 2, Appendix C.6 Host route parame- + ters" + ::= { ospfHostEntry 3 } + + ospfHostStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This variable displays the status of the en- + try. Setting it to 'invalid' has the effect of + rendering it inoperative. The internal effect + (row removal) is implementation dependent." + ::= { ospfHostEntry 4 } + + + ospfHostAreaID OBJECT-TYPE + SYNTAX AreaID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Area the Host Entry is to be found within. + By default, the area that a subsuming OSPF in- + terface is in, or 0.0.0.0" + REFERENCE + "OSPF Version 2, Appendix C.2 Area parameters" + ::= { ospfHostEntry 5 } + + +-- OSPF Interface Table + +-- The OSPF Interface Table augments the ipAddrTable +-- with OSPF specific information. + + ospfIfTable OBJECT-TYPE + SYNTAX SEQUENCE OF OspfIfEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The OSPF Interface Table describes the inter- + faces from the viewpoint of OSPF." + REFERENCE + "OSPF Version 2, Appendix C.3 Router interface + parameters" + ::= { ospf 7 } + + + ospfIfEntry OBJECT-TYPE + SYNTAX OspfIfEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The OSPF Interface Entry describes one inter- + face from the viewpoint of OSPF." + INDEX { ospfIfIpAddress, ospfAddressLessIf } + ::= { ospfIfTable 1 } + +OspfIfEntry ::= + SEQUENCE { + ospfIfIpAddress + IpAddress, + ospfAddressLessIf + Integer32, + ospfIfAreaId + AreaID, + ospfIfType + INTEGER, + ospfIfAdminStat + Status, + ospfIfRtrPriority + DesignatedRouterPriority, + ospfIfTransitDelay + UpToMaxAge, + ospfIfRetransInterval + UpToMaxAge, + ospfIfHelloInterval + HelloRange, + ospfIfRtrDeadInterval + PositiveInteger, + ospfIfPollInterval + PositiveInteger, + ospfIfState + INTEGER, + ospfIfDesignatedRouter + IpAddress, + ospfIfBackupDesignatedRouter + IpAddress, + ospfIfEvents + Counter32, + ospfIfAuthType + INTEGER, + ospfIfAuthKey + OCTET STRING, + ospfIfStatus + RowStatus, + ospfIfMulticastForwarding + INTEGER, + ospfIfDemand + TruthValue + } + + ospfIfIpAddress OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The IP address of this OSPF interface." + ::= { ospfIfEntry 1 } + + ospfAddressLessIf OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "For the purpose of easing the instancing of + addressed and addressless interfaces; This + variable takes the value 0 on interfaces with + IP Addresses, and the corresponding value of + ifIndex for interfaces having no IP Address." + ::= { ospfIfEntry 2 } + ospfIfAreaId OBJECT-TYPE + SYNTAX AreaID + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A 32-bit integer uniquely identifying the area + to which the interface connects. Area ID + 0.0.0.0 is used for the OSPF backbone." + DEFVAL { '00000000'H } -- 0.0.0.0 + ::= { ospfIfEntry 3 } + + ospfIfType OBJECT-TYPE + SYNTAX INTEGER { + broadcast (1), + nbma (2), + pointToPoint (3), + pointToMultipoint (5) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The OSPF interface type. + + By way of a default, this field may be intuited + from the corresponding value of ifType. Broad- + cast LANs, such as Ethernet and IEEE 802.5, + take the value 'broadcast', X.25 and similar + technologies take the value 'nbma', and links + that are definitively point to point take the + value 'pointToPoint'." + ::= { ospfIfEntry 4 } + + + ospfIfAdminStat OBJECT-TYPE + SYNTAX Status + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The OSPF interface's administrative status. + The value formed on the interface, and the in- + terface will be advertised as an internal route + to some area. The value 'disabled' denotes + that the interface is external to OSPF." + DEFVAL { enabled } + ::= { ospfIfEntry 5 } + + ospfIfRtrPriority OBJECT-TYPE + SYNTAX DesignatedRouterPriority + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The priority of this interface. Used in + multi-access networks, this field is used in + the designated router election algorithm. The + value 0 signifies that the router is not eligi- + ble to become the designated router on this + particular network. In the event of a tie in + this value, routers will use their Router ID as + a tie breaker." + DEFVAL { 1 } + ::= { ospfIfEntry 6 } + + + ospfIfTransitDelay OBJECT-TYPE + SYNTAX UpToMaxAge + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The estimated number of seconds it takes to + transmit a link state update packet over this + interface." + DEFVAL { 1 } + ::= { ospfIfEntry 7 } + + + ospfIfRetransInterval OBJECT-TYPE + SYNTAX UpToMaxAge + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The number of seconds between link-state ad- + vertisement retransmissions, for adjacencies + belonging to this interface. This value is + also used when retransmitting database descrip- + tion and link-state request packets." + DEFVAL { 5 } + ::= { ospfIfEntry 8 } + + + ospfIfHelloInterval OBJECT-TYPE + SYNTAX HelloRange + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The length of time, in seconds, between the + Hello packets that the router sends on the in- + terface. This value must be the same for all + routers attached to a common network." + DEFVAL { 10 } + ::= { ospfIfEntry 9 } + + + ospfIfRtrDeadInterval OBJECT-TYPE + SYNTAX PositiveInteger + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The number of seconds that a router's Hello + packets have not been seen before it's neigh- + bors declare the router down. This should be + some multiple of the Hello interval. This + value must be the same for all routers attached + to a common network." + DEFVAL { 40 } + ::= { ospfIfEntry 10 } + + + ospfIfPollInterval OBJECT-TYPE + SYNTAX PositiveInteger + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The larger time interval, in seconds, between + the Hello packets sent to an inactive non- + broadcast multi- access neighbor." + DEFVAL { 120 } + ::= { ospfIfEntry 11 } + + + ospfIfState OBJECT-TYPE + SYNTAX INTEGER { + down (1), + loopback (2), + waiting (3), + pointToPoint (4), + designatedRouter (5), + backupDesignatedRouter (6), + otherDesignatedRouter (7) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The OSPF Interface State." + DEFVAL { down } + ::= { ospfIfEntry 12 } + + + ospfIfDesignatedRouter OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The IP Address of the Designated Router." + DEFVAL { '00000000'H } -- 0.0.0.0 + ::= { ospfIfEntry 13 } + + + ospfIfBackupDesignatedRouter OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The IP Address of the Backup Designated + Router." + DEFVAL { '00000000'H } -- 0.0.0.0 + ::= { ospfIfEntry 14 } + + ospfIfEvents OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times this OSPF interface has + changed its state, or an error has occurred." + ::= { ospfIfEntry 15 } + + + ospfIfAuthKey OBJECT-TYPE + SYNTAX OCTET STRING (SIZE (0..256)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The Authentication Key. If the Area's Author- + ization Type is simplePassword, and the key + length is shorter than 8 octets, the agent will + left adjust and zero fill to 8 octets. + + Note that unauthenticated interfaces need no + authentication key, and simple password authen- + tication cannot use a key of more than 8 oc- + tets. Larger keys are useful only with authen- + tication mechanisms not specified in this docu- + ment. + + When read, ospfIfAuthKey always returns an Oc- + tet String of length zero." + REFERENCE + "OSPF Version 2, Section 9 The Interface Data + Structure" + DEFVAL { '0000000000000000'H } -- 0.0.0.0.0.0.0.0 + ::= { ospfIfEntry 16 } + + ospfIfStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This variable displays the status of the en- + try. Setting it to 'invalid' has the effect of + rendering it inoperative. The internal effect + (row removal) is implementation dependent." + ::= { ospfIfEntry 17 } + + + ospfIfMulticastForwarding OBJECT-TYPE + SYNTAX INTEGER { + blocked (1), -- no multicast forwarding + multicast (2), -- using multicast address + unicast (3) -- to each OSPF neighbor + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The way multicasts should forwarded on this + interface; not forwarded, forwarded as data + link multicasts, or forwarded as data link uni- + casts. Data link multicasting is not meaning- + ful on point to point and NBMA interfaces, and + setting ospfMulticastForwarding to 0 effective- + ly disables all multicast forwarding." + DEFVAL { blocked } + ::= { ospfIfEntry 18 } + + + ospfIfDemand OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Indicates whether Demand OSPF procedures (hel- + lo supression to FULL neighbors and setting the + DoNotAge flag on proogated LSAs) should be per- + formed on this interface." + DEFVAL { false } + ::= { ospfIfEntry 19 } + + + ospfIfAuthType OBJECT-TYPE + SYNTAX INTEGER (0..255) + -- none (0), + -- simplePassword (1) + -- md5 (2) + -- reserved for specification by IANA (> 2) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The authentication type specified for an in- + terface. Additional authentication types may + be assigned locally." + REFERENCE + "OSPF Version 2, Appendix E Authentication" + DEFVAL { 0 } -- no authentication, by default + ::= { ospfIfEntry 20 } + + +-- OSPF Interface Metric Table + +-- The Metric Table describes the metrics to be advertised +-- for a specified interface at the various types of service. +-- As such, this table is an adjunct of the OSPF Interface +-- Table. + +-- Types of service, as defined by RFC 791, have the ability +-- to request low delay, high bandwidth, or reliable linkage. + +-- For the purposes of this specification, the measure of +-- bandwidth + +-- Metric = 10^8 / ifSpeed + +-- is the default value. For multiple link interfaces, note +-- that ifSpeed is the sum of the individual link speeds. +-- This yields a number having the following typical values: + +-- Network Type/bit rate Metric + +-- >= 100 MBPS 1 +-- Ethernet/802.3 10 +-- E1 48 +-- T1 (ESF) 65 +-- 64 KBPS 1562 +-- 56 KBPS 1785 +-- 19.2 KBPS 5208 +-- 9.6 KBPS 10416 + +-- Routes that are not specified use the default (TOS 0) metric + + ospfIfMetricTable OBJECT-TYPE + SYNTAX SEQUENCE OF OspfIfMetricEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The TOS metrics for a non-virtual interface + identified by the interface index." + REFERENCE + "OSPF Version 2, Appendix C.3 Router interface + parameters" + ::= { ospf 8 } + + ospfIfMetricEntry OBJECT-TYPE + SYNTAX OspfIfMetricEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A particular TOS metric for a non-virtual in- + terface identified by the interface index." + REFERENCE + "OSPF Version 2, Appendix C.3 Router interface + parameters" + INDEX { ospfIfMetricIpAddress, + ospfIfMetricAddressLessIf, + ospfIfMetricTOS } + ::= { ospfIfMetricTable 1 } + +OspfIfMetricEntry ::= + SEQUENCE { + ospfIfMetricIpAddress + IpAddress, + ospfIfMetricAddressLessIf + Integer32, + ospfIfMetricTOS + TOSType, + ospfIfMetricValue + Metric, + ospfIfMetricStatus + RowStatus + } + + ospfIfMetricIpAddress OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The IP address of this OSPF interface. On row + creation, this can be derived from the in- + stance." + ::= { ospfIfMetricEntry 1 } + + ospfIfMetricAddressLessIf OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "For the purpose of easing the instancing of + addressed and addressless interfaces; This + variable takes the value 0 on interfaces with + IP Addresses, and the value of ifIndex for in- + terfaces having no IP Address. On row crea- + tion, this can be derived from the instance." + ::= { ospfIfMetricEntry 2 } + + + ospfIfMetricTOS OBJECT-TYPE + SYNTAX TOSType + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The type of service metric being referenced. + On row creation, this can be derived from the + instance." + ::= { ospfIfMetricEntry 3 } + + + ospfIfMetricValue OBJECT-TYPE + SYNTAX Metric + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The metric of using this type of service on + this interface. The default value of the TOS 0 + Metric is 10^8 / ifSpeed." + ::= { ospfIfMetricEntry 4 } + + ospfIfMetricStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This variable displays the status of the en- + try. Setting it to 'invalid' has the effect of + rendering it inoperative. The internal effect + (row removal) is implementation dependent." + ::= { ospfIfMetricEntry 5 } + + +-- OSPF Virtual Interface Table + +-- The Virtual Interface Table describes the virtual +-- links that the OSPF Process is configured to +-- carry on. + + ospfVirtIfTable OBJECT-TYPE + SYNTAX SEQUENCE OF OspfVirtIfEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about this router's virtual inter- + faces." + REFERENCE + "OSPF Version 2, Appendix C.4 Virtual link + parameters" + ::= { ospf 9 } + + + ospfVirtIfEntry OBJECT-TYPE + SYNTAX OspfVirtIfEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a single Virtual Interface." + INDEX { ospfVirtIfAreaId, ospfVirtIfNeighbor } + ::= { ospfVirtIfTable 1 } + +OspfVirtIfEntry ::= + SEQUENCE { + ospfVirtIfAreaId + AreaID, + ospfVirtIfNeighbor + RouterID, + ospfVirtIfTransitDelay + UpToMaxAge, + ospfVirtIfRetransInterval + UpToMaxAge, + ospfVirtIfHelloInterval + HelloRange, + ospfVirtIfRtrDeadInterval + PositiveInteger, + ospfVirtIfState + INTEGER, + ospfVirtIfEvents + Counter32, + ospfVirtIfAuthType + INTEGER, + ospfVirtIfAuthKey + OCTET STRING, + ospfVirtIfStatus + RowStatus + } + + ospfVirtIfAreaId OBJECT-TYPE + SYNTAX AreaID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Transit Area that the Virtual Link + traverses. By definition, this is not 0.0.0.0" + ::= { ospfVirtIfEntry 1 } + + + ospfVirtIfNeighbor OBJECT-TYPE + SYNTAX RouterID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Router ID of the Virtual Neighbor." + ::= { ospfVirtIfEntry 2 } + + + ospfVirtIfTransitDelay OBJECT-TYPE + SYNTAX UpToMaxAge + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The estimated number of seconds it takes to + transmit a link- state update packet over this + interface." + DEFVAL { 1 } + ::= { ospfVirtIfEntry 3 } + + + ospfVirtIfRetransInterval OBJECT-TYPE + SYNTAX UpToMaxAge + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The number of seconds between link-state ad- + vertisement retransmissions, for adjacencies + belonging to this interface. This value is + also used when retransmitting database descrip- + tion and link-state request packets. This + value should be well over the expected round- + trip time." + DEFVAL { 5 } + ::= { ospfVirtIfEntry 4 } + + + ospfVirtIfHelloInterval OBJECT-TYPE + SYNTAX HelloRange + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The length of time, in seconds, between the + Hello packets that the router sends on the in- + terface. This value must be the same for the + virtual neighbor." + DEFVAL { 10 } + ::= { ospfVirtIfEntry 5 } + + + ospfVirtIfRtrDeadInterval OBJECT-TYPE + SYNTAX PositiveInteger + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The number of seconds that a router's Hello + packets have not been seen before it's neigh- + bors declare the router down. This should be + some multiple of the Hello interval. This + value must be the same for the virtual neigh- + bor." + DEFVAL { 60 } + ::= { ospfVirtIfEntry 6 } + + + ospfVirtIfState OBJECT-TYPE + SYNTAX INTEGER { + down (1), -- these use the same encoding + pointToPoint (4) -- as the ospfIfTable + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "OSPF virtual interface states." + DEFVAL { down } + ::= { ospfVirtIfEntry 7 } + + + ospfVirtIfEvents OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of state changes or error events on + this Virtual Link" + ::= { ospfVirtIfEntry 8 } + + + ospfVirtIfAuthKey OBJECT-TYPE + SYNTAX OCTET STRING (SIZE(0..256)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "If Authentication Type is simplePassword, the + device will left adjust and zero fill to 8 oc- + tets. + + Note that unauthenticated interfaces need no + authentication key, and simple password authen- + tication cannot use a key of more than 8 oc- + tets. Larger keys are useful only with authen- + tication mechanisms not specified in this docu- + ment. + + When read, ospfVifAuthKey always returns a + string of length zero." + REFERENCE + "OSPF Version 2, Section 9 The Interface Data + Structure" + DEFVAL { '0000000000000000'H } -- 0.0.0.0.0.0.0.0 + ::= { ospfVirtIfEntry 9 } + + + ospfVirtIfStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This variable displays the status of the en- + try. Setting it to 'invalid' has the effect of + rendering it inoperative. The internal effect + (row removal) is implementation dependent." + ::= { ospfVirtIfEntry 10 } + + + ospfVirtIfAuthType OBJECT-TYPE + SYNTAX INTEGER (0..255) + -- none (0), + -- simplePassword (1) + -- md5 (2) + -- reserved for specification by IANA (> 2) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The authentication type specified for a virtu- + al interface. Additional authentication types + may be assigned locally." + REFERENCE + "OSPF Version 2, Appendix E Authentication" + DEFVAL { 0 } -- no authentication, by default + ::= { ospfVirtIfEntry 11 } + + +-- OSPF Neighbor Table + +-- The OSPF Neighbor Table describes all neighbors in +-- the locality of the subject router. + + ospfNbrTable OBJECT-TYPE + SYNTAX SEQUENCE OF OspfNbrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of non-virtual neighbor information." + REFERENCE + "OSPF Version 2, Section 10 The Neighbor Data + Structure" + ::= { ospf 10 } + + + ospfNbrEntry OBJECT-TYPE + SYNTAX OspfNbrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The information regarding a single neighbor." + REFERENCE + "OSPF Version 2, Section 10 The Neighbor Data + Structure" + INDEX { ospfNbrIpAddr, ospfNbrAddressLessIndex } + ::= { ospfNbrTable 1 } + +OspfNbrEntry ::= + SEQUENCE { + ospfNbrIpAddr + IpAddress, + ospfNbrAddressLessIndex + InterfaceIndex, + ospfNbrRtrId + RouterID, + ospfNbrOptions + Integer32, + ospfNbrPriority + DesignatedRouterPriority, + ospfNbrState + INTEGER, + ospfNbrEvents + Counter32, + ospfNbrLsRetransQLen + Gauge32, + ospfNbmaNbrStatus + RowStatus, + ospfNbmaNbrPermanence + INTEGER, + ospfNbrHelloSuppressed + TruthValue + } + + ospfNbrIpAddr OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The IP address this neighbor is using in its + IP Source Address. Note that, on addressless + links, this will not be 0.0.0.0, but the ad- + dress of another of the neighbor's interfaces." + ::= { ospfNbrEntry 1 } + + + ospfNbrAddressLessIndex OBJECT-TYPE + SYNTAX InterfaceIndex + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "On an interface having an IP Address, zero. + On addressless interfaces, the corresponding + value of ifIndex in the Internet Standard MIB. + On row creation, this can be derived from the + instance." + ::= { ospfNbrEntry 2 } + + + ospfNbrRtrId OBJECT-TYPE + SYNTAX RouterID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A 32-bit integer (represented as a type IpAd- + dress) uniquely identifying the neighboring + router in the Autonomous System." + DEFVAL { '00000000'H } -- 0.0.0.0 + ::= { ospfNbrEntry 3 } + + + ospfNbrOptions OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A Bit Mask corresponding to the neighbor's op- + tions field. + + Bit 0, if set, indicates that the system will + operate on Type of Service metrics other than + TOS 0. If zero, the neighbor will ignore all + metrics except the TOS 0 metric. + + Bit 1, if set, indicates that the associated + area accepts and operates on external informa- + tion; if zero, it is a stub area. + + Bit 2, if set, indicates that the system is ca- + pable of routing IP Multicast datagrams; i.e., + that it implements the Multicast Extensions to + OSPF. + + Bit 3, if set, indicates that the associated + area is an NSSA. These areas are capable of + carrying type 7 external advertisements, which + are translated into type 5 external advertise- + ments at NSSA borders." + REFERENCE + "OSPF Version 2, Section 12.1.2 Options" + DEFVAL { 0 } + ::= { ospfNbrEntry 4 } + + + ospfNbrPriority OBJECT-TYPE + SYNTAX DesignatedRouterPriority + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The priority of this neighbor in the designat- + ed router election algorithm. The value 0 sig- + nifies that the neighbor is not eligible to be- + come the designated router on this particular + network." + DEFVAL { 1 } + ::= { ospfNbrEntry 5 } + + + ospfNbrState OBJECT-TYPE + SYNTAX INTEGER { + down (1), + attempt (2), + init (3), + twoWay (4), + exchangeStart (5), + exchange (6), + loading (7), + full (8) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The State of the relationship with this Neigh- + bor." + REFERENCE + "OSPF Version 2, Section 10.1 Neighbor States" + DEFVAL { down } + ::= { ospfNbrEntry 6 } + + + ospfNbrEvents OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times this neighbor relationship + has changed state, or an error has occurred." + ::= { ospfNbrEntry 7 } + + + ospfNbrLsRetransQLen OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current length of the retransmission + queue." + ::= { ospfNbrEntry 8 } + + + ospfNbmaNbrStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This variable displays the status of the en- + try. Setting it to 'invalid' has the effect of + rendering it inoperative. The internal effect + (row removal) is implementation dependent." + ::= { ospfNbrEntry 9 } + + + ospfNbmaNbrPermanence OBJECT-TYPE + SYNTAX INTEGER { + dynamic (1), -- learned through protocol + permanent (2) -- configured address + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This variable displays the status of the en- + try. 'dynamic' and 'permanent' refer to how + the neighbor became known." + DEFVAL { permanent } + ::= { ospfNbrEntry 10 } + + + ospfNbrHelloSuppressed OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Indicates whether Hellos are being suppressed + to the neighbor" + ::= { ospfNbrEntry 11 } + + +-- OSPF Virtual Neighbor Table + +-- This table describes all virtual neighbors. +-- Since Virtual Links are configured in the +-- virtual interface table, this table is read-only. + + ospfVirtNbrTable OBJECT-TYPE + SYNTAX SEQUENCE OF OspfVirtNbrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of virtual neighbor information." + REFERENCE + "OSPF Version 2, Section 15 Virtual Links" + ::= { ospf 11 } + + + ospfVirtNbrEntry OBJECT-TYPE + SYNTAX OspfVirtNbrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Virtual neighbor information." + INDEX { ospfVirtNbrArea, ospfVirtNbrRtrId } + ::= { ospfVirtNbrTable 1 } + +OspfVirtNbrEntry ::= + SEQUENCE { + ospfVirtNbrArea + AreaID, + ospfVirtNbrRtrId + RouterID, + ospfVirtNbrIpAddr + IpAddress, + ospfVirtNbrOptions + Integer32, + ospfVirtNbrState + INTEGER, + ospfVirtNbrEvents + Counter32, + ospfVirtNbrLsRetransQLen + Gauge32, + ospfVirtNbrHelloSuppressed + TruthValue + } + + ospfVirtNbrArea OBJECT-TYPE + SYNTAX AreaID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Transit Area Identifier." + ::= { ospfVirtNbrEntry 1 } + + + ospfVirtNbrRtrId OBJECT-TYPE + SYNTAX RouterID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A 32-bit integer uniquely identifying the + neighboring router in the Autonomous System." + ::= { ospfVirtNbrEntry 2 } + + + ospfVirtNbrIpAddr OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The IP address this Virtual Neighbor is us- + ing." + ::= { ospfVirtNbrEntry 3 } + + + ospfVirtNbrOptions OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A Bit Mask corresponding to the neighbor's op- + tions field. + + Bit 1, if set, indicates that the system will + operate on Type of Service metrics other than + TOS 0. If zero, the neighbor will ignore all + metrics except the TOS 0 metric. + + Bit 2, if set, indicates that the system is + Network Multicast capable; ie, that it imple- + ments OSPF Multicast Routing." + ::= { ospfVirtNbrEntry 4 } + ospfVirtNbrState OBJECT-TYPE + SYNTAX INTEGER { + down (1), + attempt (2), + init (3), + twoWay (4), + exchangeStart (5), + exchange (6), + loading (7), + full (8) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The state of the Virtual Neighbor Relation- + ship." + ::= { ospfVirtNbrEntry 5 } + + + ospfVirtNbrEvents OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times this virtual link has + changed its state, or an error has occurred." + ::= { ospfVirtNbrEntry 6 } + + + ospfVirtNbrLsRetransQLen OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current length of the retransmission + queue." + ::= { ospfVirtNbrEntry 7 } + + + ospfVirtNbrHelloSuppressed OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Indicates whether Hellos are being suppressed + to the neighbor" + ::= { ospfVirtNbrEntry 8 } + +-- OSPF Link State Database, External + +-- The Link State Database contains the Link State +-- Advertisements from throughout the areas that the +-- device is attached to. + +-- This table is identical to the OSPF LSDB Table in +-- format, but contains only External Link State +-- Advertisements. The purpose is to allow external +-- LSAs to be displayed once for the router rather +-- than once in each non-stub area. + + ospfExtLsdbTable OBJECT-TYPE + SYNTAX SEQUENCE OF OspfExtLsdbEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The OSPF Process's Links State Database." + REFERENCE + "OSPF Version 2, Section 12 Link State Adver- + tisements" + ::= { ospf 12 } + + + ospfExtLsdbEntry OBJECT-TYPE + SYNTAX OspfExtLsdbEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A single Link State Advertisement." + INDEX { ospfExtLsdbType, ospfExtLsdbLsid, ospfExtLsdbRouterId } + ::= { ospfExtLsdbTable 1 } + +OspfExtLsdbEntry ::= + SEQUENCE { + ospfExtLsdbType + INTEGER, + ospfExtLsdbLsid + IpAddress, + ospfExtLsdbRouterId + RouterID, + ospfExtLsdbSequence + Integer32, + ospfExtLsdbAge + Integer32, + ospfExtLsdbChecksum + Integer32, + ospfExtLsdbAdvertisement + OCTET STRING + } + + ospfExtLsdbType OBJECT-TYPE + SYNTAX INTEGER { + asExternalLink (5) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The type of the link state advertisement. + Each link state type has a separate advertise- + ment format." + REFERENCE + "OSPF Version 2, Appendix A.4.1 The Link State + Advertisement header" + ::= { ospfExtLsdbEntry 1 } + + + ospfExtLsdbLsid OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Link State ID is an LS Type Specific field + containing either a Router ID or an IP Address; + it identifies the piece of the routing domain + that is being described by the advertisement." + REFERENCE + "OSPF Version 2, Section 12.1.4 Link State ID" + ::= { ospfExtLsdbEntry 2 } + + + ospfExtLsdbRouterId OBJECT-TYPE + SYNTAX RouterID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The 32 bit number that uniquely identifies the + originating router in the Autonomous System." + REFERENCE + "OSPF Version 2, Appendix C.1 Global parameters" + ::= { ospfExtLsdbEntry 3 } + +-- Note that the OSPF Sequence Number is a 32 bit signed +-- integer. It starts with the value '80000001'h, +-- or -'7FFFFFFF'h, and increments until '7FFFFFFF'h +-- Thus, a typical sequence number will be very negative. + ospfExtLsdbSequence OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The sequence number field is a signed 32-bit + integer. It is used to detect old and dupli- + cate link state advertisements. The space of + sequence numbers is linearly ordered. The + larger the sequence number the more recent the + advertisement." + REFERENCE + "OSPF Version 2, Section 12.1.6 LS sequence + number" + ::= { ospfExtLsdbEntry 4 } + + + ospfExtLsdbAge OBJECT-TYPE + SYNTAX Integer32 -- Should be 0..MaxAge + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This field is the age of the link state adver- + tisement in seconds." + REFERENCE + "OSPF Version 2, Section 12.1.1 LS age" + ::= { ospfExtLsdbEntry 5 } + + + ospfExtLsdbChecksum OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This field is the checksum of the complete + contents of the advertisement, excepting the + age field. The age field is excepted so that + an advertisement's age can be incremented + without updating the checksum. The checksum + used is the same that is used for ISO connec- + tionless datagrams; it is commonly referred to + as the Fletcher checksum." + REFERENCE + "OSPF Version 2, Section 12.1.7 LS checksum" + ::= { ospfExtLsdbEntry 6 } + + + ospfExtLsdbAdvertisement OBJECT-TYPE + SYNTAX OCTET STRING (SIZE(36)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The entire Link State Advertisement, including + its header." + REFERENCE + "OSPF Version 2, Section 12 Link State Adver- + tisements" + ::= { ospfExtLsdbEntry 7 } + + +-- OSPF Use of the CIDR Route Table + +ospfRouteGroup OBJECT IDENTIFIER ::= { ospf 13 } + +-- The IP Forwarding Table defines a number of objects for use by +-- the routing protocol to externalize its information. Most of +-- the variables (ipForwardDest, ipForwardMask, ipForwardPolicy, +-- ipForwardNextHop, ipForwardIfIndex, ipForwardType, +-- ipForwardProto, ipForwardAge, and ipForwardNextHopAS) are +-- defined there. + +-- Those that leave some discretion are defined here. + +-- ipCidrRouteProto is, of course, ospf (13). + +-- ipCidrRouteAge is the time since the route was first calculated, +-- as opposed to the time since the last SPF run. + +-- ipCidrRouteInfo is an OBJECT IDENTIFIER for use by the routing +-- protocol. The following values shall be found there depending +-- on the way the route was calculated. + +ospfIntraArea OBJECT IDENTIFIER ::= { ospfRouteGroup 1 } +ospfInterArea OBJECT IDENTIFIER ::= { ospfRouteGroup 2 } +ospfExternalType1 OBJECT IDENTIFIER ::= { ospfRouteGroup 3 } +ospfExternalType2 OBJECT IDENTIFIER ::= { ospfRouteGroup 4 } + +-- ipCidrRouteMetric1 is, by definition, the primary routing +-- metric. Therefore, it should be the metric that route +-- selection is based on. For intra-area and inter-area routes, +-- it is an OSPF metric. For External Type 1 (comparable value) +-- routes, it is an OSPF metric plus the External Metric. For +-- external Type 2 (non-comparable value) routes, it is the +-- external metric. + +-- ipCidrRouteMetric2 is, by definition, a secondary routing +-- metric. Therefore, it should be the metric that breaks a tie +-- among routes having equal metric1 values and the same +-- calculation rule. For intra-area, inter-area routes, and +-- External Type 1 (comparable value) routes, it is unused. For +-- external Type 2 (non-comparable value) routes, it is the metric +-- to the AS border router. + +-- ipCidrRouteMetric3, ipCidrRouteMetric4, and ipCidrRouteMetric5 are +-- unused. + +-- +-- The OSPF Area Aggregate Table +-- +-- This table replaces the OSPF Area Summary Table, being an +-- extension of that for CIDR routers. + + ospfAreaAggregateTable OBJECT-TYPE + SYNTAX SEQUENCE OF OspfAreaAggregateEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A range of IP addresses specified by an IP + address/IP network mask pair. For example, + class B address range of X.X.X.X with a network + mask of 255.255.0.0 includes all IP addresses + from X.X.0.0 to X.X.255.255. Note that if + ranges are configured such that one range sub- + sumes another range (e.g., 10.0.0.0 mask + 255.0.0.0 and 10.1.0.0 mask 255.255.0.0), the + most specific match is the preferred one." + REFERENCE + "OSPF Version 2, Appendix C.2 Area parameters" + ::= { ospf 14 } + + + ospfAreaAggregateEntry OBJECT-TYPE + SYNTAX OspfAreaAggregateEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A range of IP addresses specified by an IP + address/IP network mask pair. For example, + class B address range of X.X.X.X with a network + mask of 255.255.0.0 includes all IP addresses + from X.X.0.0 to X.X.255.255. Note that if + ranges are range configured such that one range + subsumes another range (e.g., 10.0.0.0 mask + 255.0.0.0 and 10.1.0.0 mask 255.255.0.0), the + most specific match is the preferred one." + REFERENCE + "OSPF Version 2, Appendix C.2 Area parameters" + INDEX { ospfAreaAggregateAreaID, ospfAreaAggregateLsdbType, + ospfAreaAggregateNet, ospfAreaAggregateMask } + ::= { ospfAreaAggregateTable 1 } + + +OspfAreaAggregateEntry ::= + SEQUENCE { + ospfAreaAggregateAreaID + AreaID, + ospfAreaAggregateLsdbType + INTEGER, + ospfAreaAggregateNet + IpAddress, + ospfAreaAggregateMask + IpAddress, + ospfAreaAggregateStatus + RowStatus, + ospfAreaAggregateEffect + INTEGER + } + + ospfAreaAggregateAreaID OBJECT-TYPE + SYNTAX AreaID + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Area the Address Aggregate is to be found + within." + REFERENCE + "OSPF Version 2, Appendix C.2 Area parameters" + ::= { ospfAreaAggregateEntry 1 } + + + ospfAreaAggregateLsdbType OBJECT-TYPE + SYNTAX INTEGER { + summaryLink (3), + nssaExternalLink (7) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The type of the Address Aggregate. This field + specifies the Lsdb type that this Address Ag- + gregate applies to." + REFERENCE + "OSPF Version 2, Appendix A.4.1 The Link State + Advertisement header" + ::= { ospfAreaAggregateEntry 2 } + + + ospfAreaAggregateNet OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The IP Address of the Net or Subnet indicated + by the range." + REFERENCE + "OSPF Version 2, Appendix C.2 Area parameters" + ::= { ospfAreaAggregateEntry 3 } + + + ospfAreaAggregateMask OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The Subnet Mask that pertains to the Net or + Subnet." + REFERENCE + "OSPF Version 2, Appendix C.2 Area parameters" + ::= { ospfAreaAggregateEntry 4 } + + + ospfAreaAggregateStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This variable displays the status of the en- + try. Setting it to 'invalid' has the effect of + rendering it inoperative. The internal effect + (row removal) is implementation dependent." + ::= { ospfAreaAggregateEntry 5 } + + + ospfAreaAggregateEffect OBJECT-TYPE + SYNTAX INTEGER { + advertiseMatching (1), + doNotAdvertiseMatching (2) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Subnets subsumed by ranges either trigger the + advertisement of the indicated aggregate (ad- + vertiseMatching), or result in the subnet's not + being advertised at all outside the area." + DEFVAL { advertiseMatching } + ::= { ospfAreaAggregateEntry 6 } + + +-- conformance information + +ospfConformance OBJECT IDENTIFIER ::= { ospf 15 } + +ospfGroups OBJECT IDENTIFIER ::= { ospfConformance 1 } +ospfCompliances OBJECT IDENTIFIER ::= { ospfConformance 2 } + +-- compliance statements + + ospfCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement " + MODULE -- this module + MANDATORY-GROUPS { + ospfBasicGroup, + ospfAreaGroup, + ospfStubAreaGroup, + ospfIfGroup, + ospfIfMetricGroup, + ospfVirtIfGroup, + ospfNbrGroup, + ospfVirtNbrGroup, + ospfAreaAggregateGroup + } + ::= { ospfCompliances 1 } + + +-- units of conformance + + ospfBasicGroup OBJECT-GROUP + OBJECTS { + ospfRouterId, + ospfAdminStat, + ospfVersionNumber, + ospfAreaBdrRtrStatus, + ospfASBdrRtrStatus, + ospfExternLsaCount, + ospfExternLsaCksumSum, + ospfTOSSupport, + ospfOriginateNewLsas, + ospfRxNewLsas, + ospfExtLsdbLimit, + ospfMulticastExtensions, + ospfExitOverflowInterval, + ospfDemandExtensions + } + STATUS current + DESCRIPTION + "These objects are required for OSPF systems." + ::= { ospfGroups 1 } + + + ospfAreaGroup OBJECT-GROUP + OBJECTS { + ospfAreaId, + ospfImportAsExtern, + ospfSpfRuns, + ospfAreaBdrRtrCount, + ospfAsBdrRtrCount, + ospfAreaLsaCount, + ospfAreaLsaCksumSum, + ospfAreaSummary, + ospfAreaStatus + } + STATUS current + DESCRIPTION + "These objects are required for OSPF systems + supporting areas." + ::= { ospfGroups 2 } + + + ospfStubAreaGroup OBJECT-GROUP + OBJECTS { + ospfStubAreaId, + ospfStubTOS, + ospfStubMetric, + ospfStubStatus, + ospfStubMetricType + } + STATUS current + DESCRIPTION + "These objects are required for OSPF systems + supporting stub areas." + ::= { ospfGroups 3 } + + + ospfLsdbGroup OBJECT-GROUP + OBJECTS { + ospfLsdbAreaId, + ospfLsdbType, + ospfLsdbLsid, + ospfLsdbRouterId, + ospfLsdbSequence, + ospfLsdbAge, + ospfLsdbChecksum, + ospfLsdbAdvertisement + } + STATUS current + DESCRIPTION + "These objects are required for OSPF systems + that display their link state database." + ::= { ospfGroups 4 } + + + ospfAreaRangeGroup OBJECT-GROUP + OBJECTS { + ospfAreaRangeAreaId, + ospfAreaRangeNet, + ospfAreaRangeMask, + ospfAreaRangeStatus, + ospfAreaRangeEffect + } + STATUS obsolete + DESCRIPTION + "These objects are required for non-CIDR OSPF + systems that support multiple areas." + ::= { ospfGroups 5 } + + + ospfHostGroup OBJECT-GROUP + OBJECTS { + ospfHostIpAddress, + ospfHostTOS, + ospfHostMetric, + ospfHostStatus, + ospfHostAreaID + } + STATUS current + DESCRIPTION + "These objects are required for OSPF systems + that support attached hosts." + ::= { ospfGroups 6 } + + + ospfIfGroup OBJECT-GROUP + OBJECTS { + ospfIfIpAddress, + ospfAddressLessIf, + ospfIfAreaId, + ospfIfType, + ospfIfAdminStat, + ospfIfRtrPriority, + ospfIfTransitDelay, + ospfIfRetransInterval, + ospfIfHelloInterval, + ospfIfRtrDeadInterval, + ospfIfPollInterval, + ospfIfState, + ospfIfDesignatedRouter, + ospfIfBackupDesignatedRouter, + ospfIfEvents, + ospfIfAuthType, + ospfIfAuthKey, + ospfIfStatus, + ospfIfMulticastForwarding, + ospfIfDemand + } + STATUS current + DESCRIPTION + "These objects are required for OSPF systems." + ::= { ospfGroups 7 } + + + ospfIfMetricGroup OBJECT-GROUP + OBJECTS { + ospfIfMetricIpAddress, + ospfIfMetricAddressLessIf, + ospfIfMetricTOS, + ospfIfMetricValue, + ospfIfMetricStatus + } + STATUS current + DESCRIPTION + "These objects are required for OSPF systems." + ::= { ospfGroups 8 } + + + ospfVirtIfGroup OBJECT-GROUP + OBJECTS { + ospfVirtIfAreaId, + ospfVirtIfNeighbor, + ospfVirtIfTransitDelay, + ospfVirtIfRetransInterval, + ospfVirtIfHelloInterval, + ospfVirtIfRtrDeadInterval, + ospfVirtIfState, + ospfVirtIfEvents, + ospfVirtIfAuthType, + ospfVirtIfAuthKey, + ospfVirtIfStatus + } + STATUS current + DESCRIPTION + "These objects are required for OSPF systems." + ::= { ospfGroups 9 } + + + ospfNbrGroup OBJECT-GROUP + OBJECTS { + ospfNbrIpAddr, + ospfNbrAddressLessIndex, + ospfNbrRtrId, + ospfNbrOptions, + ospfNbrPriority, + ospfNbrState, + ospfNbrEvents, + ospfNbrLsRetransQLen, + ospfNbmaNbrStatus, + ospfNbmaNbrPermanence, + ospfNbrHelloSuppressed + } + STATUS current + DESCRIPTION + "These objects are required for OSPF systems." + ::= { ospfGroups 10 } + + + ospfVirtNbrGroup OBJECT-GROUP + OBJECTS { + ospfVirtNbrArea, + ospfVirtNbrRtrId, + ospfVirtNbrIpAddr, + ospfVirtNbrOptions, + ospfVirtNbrState, + ospfVirtNbrEvents, + ospfVirtNbrLsRetransQLen, + ospfVirtNbrHelloSuppressed + } + STATUS current + DESCRIPTION + "These objects are required for OSPF systems." + ::= { ospfGroups 11 } + + + ospfExtLsdbGroup OBJECT-GROUP + OBJECTS { + ospfExtLsdbType, + ospfExtLsdbLsid, + ospfExtLsdbRouterId, + ospfExtLsdbSequence, + ospfExtLsdbAge, + ospfExtLsdbChecksum, + ospfExtLsdbAdvertisement + } + STATUS current + DESCRIPTION + "These objects are required for OSPF systems + that display their link state database." + ::= { ospfGroups 12 } + + + ospfAreaAggregateGroup OBJECT-GROUP + OBJECTS { + ospfAreaAggregateAreaID, + ospfAreaAggregateLsdbType, + ospfAreaAggregateNet, + ospfAreaAggregateMask, + ospfAreaAggregateStatus, + ospfAreaAggregateEffect + } + STATUS current + DESCRIPTION + "These objects are required for OSPF systems." + ::= { ospfGroups 13 } + +END diff --git a/mibs/OSPF-TRAP-MIB.txt b/mibs/OSPF-TRAP-MIB.txt new file mode 100644 index 0000000..8a3ab99 --- /dev/null +++ b/mibs/OSPF-TRAP-MIB.txt @@ -0,0 +1,443 @@ +OSPF-TRAP-MIB DEFINITIONS ::= BEGIN + + IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE, IpAddress + FROM SNMPv2-SMI + MODULE-COMPLIANCE, OBJECT-GROUP + FROM SNMPv2-CONF + ospfRouterId, ospfIfIpAddress, ospfAddressLessIf, ospfIfState, + ospfVirtIfAreaId, ospfVirtIfNeighbor, ospfVirtIfState, + ospfNbrIpAddr, ospfNbrAddressLessIndex, ospfNbrRtrId, + ospfNbrState, ospfVirtNbrArea, ospfVirtNbrRtrId, ospfVirtNbrState, + ospfLsdbType, ospfLsdbLsid, ospfLsdbRouterId, ospfLsdbAreaId, + ospfExtLsdbLimit, ospf + FROM OSPF-MIB; + + ospfTrap MODULE-IDENTITY + LAST-UPDATED "9501201225Z" -- Fri Jan 20 12:25:50 PST 1995 + ORGANIZATION "IETF OSPF Working Group" + CONTACT-INFO + " Fred Baker + Postal: Cisco Systems + 519 Lado Drive + Santa Barbara, California 93111 + Tel: +1 805 681 0115 + E-Mail: fred@cisco.com + + Rob Coltun + Postal: RainbowBridge Communications + Tel: (301) 340-9416 + E-Mail: rcoltun@rainbow-bridge.com" + DESCRIPTION + "The MIB module to describe traps for the OSPF + Version 2 Protocol." + ::= { ospf 16 } + +-- Trap Support Objects + +-- The following are support objects for the OSPF traps. + +ospfTrapControl OBJECT IDENTIFIER ::= { ospfTrap 1 } +ospfTraps OBJECT IDENTIFIER ::= { ospfTrap 2 } + + ospfSetTrap OBJECT-TYPE + SYNTAX OCTET STRING (SIZE(4)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "A four-octet string serving as a bit map for + the trap events defined by the OSPF traps. This + object is used to enable and disable specific + OSPF traps where a 1 in the bit field + represents enabled. The right-most bit (least + significant) represents trap 0." + ::= { ospfTrapControl 1 } + + + ospfConfigErrorType OBJECT-TYPE + SYNTAX INTEGER { + badVersion (1), + areaMismatch (2), + unknownNbmaNbr (3), -- Router is Dr eligible + unknownVirtualNbr (4), + authTypeMismatch(5), + authFailure (6), + netMaskMismatch (7), + helloIntervalMismatch (8), + deadIntervalMismatch (9), + optionMismatch (10) } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Potential types of configuration conflicts. + Used by the ospfConfigError and ospfConfigVir- + tError traps." + ::= { ospfTrapControl 2 } + + + ospfPacketType OBJECT-TYPE + SYNTAX INTEGER { + hello (1), + dbDescript (2), + lsReq (3), + lsUpdate (4), + lsAck (5) } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "OSPF packet types." + ::= { ospfTrapControl 3 } + + + ospfPacketSrc OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The IP address of an inbound packet that can- + not be identified by a neighbor instance." + ::= { ospfTrapControl 4 } + + +-- Traps + + + ospfIfStateChange NOTIFICATION-TYPE + OBJECTS { + ospfRouterId, -- The originator of the trap + ospfIfIpAddress, + ospfAddressLessIf, + ospfIfState -- The new state + } + STATUS current + DESCRIPTION + "An ospfIfStateChange trap signifies that there + has been a change in the state of a non-virtual + OSPF interface. This trap should be generated + when the interface state regresses (e.g., goes + from Dr to Down) or progresses to a terminal + state (i.e., Point-to-Point, DR Other, Dr, or + Backup)." + ::= { ospfTraps 16 } + + + ospfVirtIfStateChange NOTIFICATION-TYPE + OBJECTS { + ospfRouterId, -- The originator of the trap + ospfVirtIfAreaId, + ospfVirtIfNeighbor, + ospfVirtIfState -- The new state + } + STATUS current + DESCRIPTION + "An ospfIfStateChange trap signifies that there + has been a change in the state of an OSPF vir- + tual interface. + This trap should be generated when the inter- + face state regresses (e.g., goes from Point- + to-Point to Down) or progresses to a terminal + state (i.e., Point-to-Point)." + ::= { ospfTraps 1 } + + + ospfNbrStateChange NOTIFICATION-TYPE + OBJECTS { + ospfRouterId, -- The originator of the trap + ospfNbrIpAddr, + ospfNbrAddressLessIndex, + ospfNbrRtrId, + ospfNbrState -- The new state + } + STATUS current + DESCRIPTION + "An ospfNbrStateChange trap signifies that + there has been a change in the state of a non- + virtual OSPF neighbor. This trap should be + generated when the neighbor state regresses + (e.g., goes from Attempt or Full to 1-Way or + Down) or progresses to a terminal state (e.g., + 2-Way or Full). When an neighbor transitions + from or to Full on non-broadcast multi-access + and broadcast networks, the trap should be gen- + erated by the designated router. A designated + router transitioning to Down will be noted by + ospfIfStateChange." + ::= { ospfTraps 2 } + + + ospfVirtNbrStateChange NOTIFICATION-TYPE + OBJECTS { + ospfRouterId, -- The originator of the trap + ospfVirtNbrArea, + ospfVirtNbrRtrId, + ospfVirtNbrState -- The new state + } + STATUS current + DESCRIPTION + "An ospfIfStateChange trap signifies that there + has been a change in the state of an OSPF vir- + tual neighbor. This trap should be generated + when the neighbor state regresses (e.g., goes + from Attempt or Full to 1-Way or Down) or + progresses to a terminal state (e.g., Full)." + ::= { ospfTraps 3 } + ospfIfConfigError NOTIFICATION-TYPE + OBJECTS { + ospfRouterId, -- The originator of the trap + ospfIfIpAddress, + ospfAddressLessIf, + ospfPacketSrc, -- The source IP address + ospfConfigErrorType, -- Type of error + ospfPacketType + } + STATUS current + DESCRIPTION + "An ospfIfConfigError trap signifies that a + packet has been received on a non-virtual in- + terface from a router whose configuration + parameters conflict with this router's confi- + guration parameters. Note that the event op- + tionMismatch should cause a trap only if it + prevents an adjacency from forming." + ::= { ospfTraps 4 } + + + ospfVirtIfConfigError NOTIFICATION-TYPE + OBJECTS { + ospfRouterId, -- The originator of the trap + ospfVirtIfAreaId, + ospfVirtIfNeighbor, + ospfConfigErrorType, -- Type of error + ospfPacketType + } + STATUS current + DESCRIPTION + "An ospfConfigError trap signifies that a pack- + et has been received on a virtual interface + from a router whose configuration parameters + conflict with this router's configuration + parameters. Note that the event optionMismatch + should cause a trap only if it prevents an ad- + jacency from forming." + ::= { ospfTraps 5 } + + + ospfIfAuthFailure NOTIFICATION-TYPE + OBJECTS { + ospfRouterId, -- The originator of the trap + ospfIfIpAddress, + ospfAddressLessIf, + ospfPacketSrc, -- The source IP address + ospfConfigErrorType, -- authTypeMismatch or + -- authFailure + ospfPacketType + } + STATUS current + DESCRIPTION + "An ospfIfAuthFailure trap signifies that a + packet has been received on a non-virtual in- + terface from a router whose authentication key + or authentication type conflicts with this + router's authentication key or authentication + type." + ::= { ospfTraps 6 } + + + ospfVirtIfAuthFailure NOTIFICATION-TYPE + OBJECTS { + ospfRouterId, -- The originator of the trap + ospfVirtIfAreaId, + ospfVirtIfNeighbor, + ospfConfigErrorType, -- authTypeMismatch or + -- authFailure + ospfPacketType + } + STATUS current + DESCRIPTION + "An ospfVirtIfAuthFailure trap signifies that a + packet has been received on a virtual interface + from a router whose authentication key or au- + thentication type conflicts with this router's + authentication key or authentication type." + ::= { ospfTraps 7 } + + + ospfIfRxBadPacket NOTIFICATION-TYPE + OBJECTS { + ospfRouterId, -- The originator of the trap + ospfIfIpAddress, + ospfAddressLessIf, + ospfPacketSrc, -- The source IP address + ospfPacketType + } + STATUS current + DESCRIPTION + "An ospfIfRxBadPacket trap signifies that an + OSPF packet has been received on a non-virtual + interface that cannot be parsed." + ::= { ospfTraps 8 } + + ospfVirtIfRxBadPacket NOTIFICATION-TYPE + OBJECTS { + ospfRouterId, -- The originator of the trap + ospfVirtIfAreaId, + ospfVirtIfNeighbor, + ospfPacketType + } + STATUS current + DESCRIPTION + "An ospfRxBadPacket trap signifies that an OSPF + packet has been received on a virtual interface + that cannot be parsed." + ::= { ospfTraps 9 } + + + ospfTxRetransmit NOTIFICATION-TYPE + OBJECTS { + ospfRouterId, -- The originator of the trap + ospfIfIpAddress, + ospfAddressLessIf, + ospfNbrRtrId, -- Destination + ospfPacketType, + ospfLsdbType, + ospfLsdbLsid, + ospfLsdbRouterId + } + STATUS current + DESCRIPTION + "An ospfTxRetransmit trap signifies than an + OSPF packet has been retransmitted on a non- + virtual interface. All packets that may be re- + transmitted are associated with an LSDB entry. + The LS type, LS ID, and Router ID are used to + identify the LSDB entry." + ::= { ospfTraps 10 } + + + ospfVirtIfTxRetransmit NOTIFICATION-TYPE + OBJECTS { + ospfRouterId, -- The originator of the trap + ospfVirtIfAreaId, + ospfVirtIfNeighbor, + ospfPacketType, + ospfLsdbType, + ospfLsdbLsid, + ospfLsdbRouterId + } + STATUS current + DESCRIPTION + "An ospfTxRetransmit trap signifies than an + OSPF packet has been retransmitted on a virtual + interface. All packets that may be retransmit- + ted are associated with an LSDB entry. The LS + type, LS ID, and Router ID are used to identify + the LSDB entry." + ::= { ospfTraps 11 } + + + ospfOriginateLsa NOTIFICATION-TYPE + OBJECTS { + ospfRouterId, -- The originator of the trap + ospfLsdbAreaId, -- 0.0.0.0 for AS Externals + ospfLsdbType, + ospfLsdbLsid, + ospfLsdbRouterId + } + STATUS current + DESCRIPTION + "An ospfOriginateLsa trap signifies that a new + LSA has been originated by this router. This + trap should not be invoked for simple refreshes + of LSAs (which happesn every 30 minutes), but + instead will only be invoked when an LSA is + (re)originated due to a topology change. Addi- + tionally, this trap does not include LSAs that + are being flushed because they have reached + MaxAge." + ::= { ospfTraps 12 } + + + ospfMaxAgeLsa NOTIFICATION-TYPE + OBJECTS { + ospfRouterId, -- The originator of the trap + ospfLsdbAreaId, -- 0.0.0.0 for AS Externals + ospfLsdbType, + ospfLsdbLsid, + ospfLsdbRouterId + } + STATUS current + DESCRIPTION + "An ospfMaxAgeLsa trap signifies that one of + the LSA in the router's link-state database has + aged to MaxAge." + ::= { ospfTraps 13 } + + + ospfLsdbOverflow NOTIFICATION-TYPE + OBJECTS { + ospfRouterId, -- The originator of the trap + ospfExtLsdbLimit + } + STATUS current + DESCRIPTION + "An ospfLsdbOverflow trap signifies that the + number of LSAs in the router's link-state data- + base has exceeded ospfExtLsdbLimit." + ::= { ospfTraps 14 } + + + ospfLsdbApproachingOverflow NOTIFICATION-TYPE + OBJECTS { + ospfRouterId, -- The originator of the trap + ospfExtLsdbLimit + } + STATUS current + DESCRIPTION + "An ospfLsdbApproachingOverflow trap signifies + that the number of LSAs in the router's link- + state database has exceeded ninety percent of + ospfExtLsdbLimit." + ::= { ospfTraps 15 } + + +-- conformance information + +ospfTrapConformance OBJECT IDENTIFIER ::= { ospfTrap 3 } + +ospfTrapGroups OBJECT IDENTIFIER ::= { ospfTrapConformance 1 } +ospfTrapCompliances OBJECT IDENTIFIER ::= { ospfTrapConformance 2 } + +-- compliance statements + + ospfTrapCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement " + MODULE -- this module + MANDATORY-GROUPS { ospfTrapControlGroup } + + + GROUP ospfTrapControlGroup + DESCRIPTION + "This group is optional but recommended for all + OSPF systems" + ::= { ospfTrapCompliances 1 } + + +-- units of conformance + + ospfTrapControlGroup OBJECT-GROUP + OBJECTS { + ospfSetTrap, + ospfConfigErrorType, + ospfPacketType, + ospfPacketSrc + } + STATUS current + DESCRIPTION + "These objects are required to control traps + from OSPF systems." + ::= { ospfTrapGroups 1 } + + +END diff --git a/mibs/RFC1155-SMI.txt b/mibs/RFC1155-SMI.txt new file mode 100644 index 0000000..3abc7ff --- /dev/null +++ b/mibs/RFC1155-SMI.txt @@ -0,0 +1,119 @@ +RFC1155-SMI DEFINITIONS ::= BEGIN + +EXPORTS -- EVERYTHING + internet, directory, mgmt, + experimental, private, enterprises, + OBJECT-TYPE, ObjectName, ObjectSyntax, SimpleSyntax, + ApplicationSyntax, NetworkAddress, IpAddress, + Counter, Gauge, TimeTicks, Opaque; + + -- the path to the root + + internet OBJECT IDENTIFIER ::= { iso org(3) dod(6) 1 } + + directory OBJECT IDENTIFIER ::= { internet 1 } + + mgmt OBJECT IDENTIFIER ::= { internet 2 } + + experimental OBJECT IDENTIFIER ::= { internet 3 } + + private OBJECT IDENTIFIER ::= { internet 4 } + enterprises OBJECT IDENTIFIER ::= { private 1 } + + -- definition of object types + + OBJECT-TYPE MACRO ::= + BEGIN + TYPE NOTATION ::= "SYNTAX" type (TYPE ObjectSyntax) + "ACCESS" Access + "STATUS" Status + VALUE NOTATION ::= value (VALUE ObjectName) + + Access ::= "read-only" + | "read-write" + | "write-only" + | "not-accessible" + Status ::= "mandatory" + | "optional" + | "obsolete" + END + + -- names of objects in the MIB + + ObjectName ::= + OBJECT IDENTIFIER + + -- syntax of objects in the MIB + + ObjectSyntax ::= + CHOICE { + simple + SimpleSyntax, + -- note that simple SEQUENCEs are not directly + -- mentioned here to keep things simple (i.e., + -- prevent mis-use). However, application-wide + -- types which are IMPLICITly encoded simple + -- SEQUENCEs may appear in the following CHOICE + + application-wide + ApplicationSyntax + } + + SimpleSyntax ::= + CHOICE { + number + INTEGER, + string + OCTET STRING, + object + OBJECT IDENTIFIER, + empty + NULL + } + + ApplicationSyntax ::= + CHOICE { + address + NetworkAddress, + counter + Counter, + gauge + Gauge, + ticks + TimeTicks, + arbitrary + Opaque + + -- other application-wide types, as they are + -- defined, will be added here + } + + -- application-wide types + + NetworkAddress ::= + CHOICE { + internet + IpAddress + } + + IpAddress ::= + [APPLICATION 0] -- in network-byte order + IMPLICIT OCTET STRING (SIZE (4)) + + Counter ::= + [APPLICATION 1] + IMPLICIT INTEGER (0..4294967295) + + Gauge ::= + [APPLICATION 2] + IMPLICIT INTEGER (0..4294967295) + + TimeTicks ::= + [APPLICATION 3] + IMPLICIT INTEGER (0..4294967295) + + Opaque ::= + [APPLICATION 4] -- arbitrary ASN.1 value, + IMPLICIT OCTET STRING -- "double-wrapped" + + END diff --git a/mibs/RFC1213-MIB.txt b/mibs/RFC1213-MIB.txt new file mode 100644 index 0000000..408ccd7 --- /dev/null +++ b/mibs/RFC1213-MIB.txt @@ -0,0 +1,2613 @@ +RFC1213-MIB DEFINITIONS ::= BEGIN + +IMPORTS + mgmt, NetworkAddress, IpAddress, Counter, Gauge, + TimeTicks + FROM RFC1155-SMI + OBJECT-TYPE + FROM RFC-1212; + +-- This MIB module uses the extended OBJECT-TYPE macro as +-- defined in [14]; + +-- MIB-II (same prefix as MIB-I) + +mib-2 OBJECT IDENTIFIER ::= { mgmt 1 } + +-- textual conventions + +DisplayString ::= + OCTET STRING +-- This data type is used to model textual information taken +-- from the NVT ASCII character set. By convention, objects +-- with this syntax are declared as having + +-- +-- SIZE (0..255) + +PhysAddress ::= + OCTET STRING +-- This data type is used to model media addresses. For many +-- types of media, this will be in a binary representation. +-- For example, an ethernet address would be represented as +-- a string of 6 octets. + +-- groups in MIB-II + +system OBJECT IDENTIFIER ::= { mib-2 1 } + +interfaces OBJECT IDENTIFIER ::= { mib-2 2 } + +at OBJECT IDENTIFIER ::= { mib-2 3 } + +ip OBJECT IDENTIFIER ::= { mib-2 4 } + +icmp OBJECT IDENTIFIER ::= { mib-2 5 } + +tcp OBJECT IDENTIFIER ::= { mib-2 6 } + +udp OBJECT IDENTIFIER ::= { mib-2 7 } + +egp OBJECT IDENTIFIER ::= { mib-2 8 } + +-- historical (some say hysterical) +-- cmot OBJECT IDENTIFIER ::= { mib-2 9 } + +transmission OBJECT IDENTIFIER ::= { mib-2 10 } + +snmp OBJECT IDENTIFIER ::= { mib-2 11 } + +-- the System group + +-- Implementation of the System group is mandatory for all +-- systems. If an agent is not configured to have a value +-- for any of these variables, a string of length 0 is +-- returned. + +sysDescr OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A textual description of the entity. This value + should include the full name and version + identification of the system's hardware type, + software operating-system, and networking + software. It is mandatory that this only contain + printable ASCII characters." + ::= { system 1 } + +sysObjectID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The vendor's authoritative identification of the + network management subsystem contained in the + entity. This value is allocated within the SMI + enterprises subtree (1.3.6.1.4.1) and provides an + easy and unambiguous means for determining `what + kind of box' is being managed. For example, if + vendor `Flintstones, Inc.' was assigned the + subtree 1.3.6.1.4.1.4242, it could assign the + identifier 1.3.6.1.4.1.4242.1.1 to its `Fred + Router'." + ::= { system 2 } + +sysUpTime OBJECT-TYPE + SYNTAX TimeTicks + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The time (in hundredths of a second) since the + network management portion of the system was last + re-initialized." + ::= { system 3 } + +sysContact OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The textual identification of the contact person + for this managed node, together with information + on how to contact this person." + ::= { system 4 } + +sysName OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "An administratively-assigned name for this + managed node. By convention, this is the node's + fully-qualified domain name." + ::= { system 5 } + +sysLocation OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The physical location of this node (e.g., + `telephone closet, 3rd floor')." + ::= { system 6 } + +sysServices OBJECT-TYPE + SYNTAX INTEGER (0..127) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A value which indicates the set of services that + this entity primarily offers. + + The value is a sum. This sum initially takes the + value zero, Then, for each layer, L, in the range + 1 through 7, that this node performs transactions + for, 2 raised to (L - 1) is added to the sum. For + example, a node which performs primarily routing + functions would have a value of 4 (2^(3-1)). In + contrast, a node which is a host offering + application services would have a value of 72 + (2^(4-1) + 2^(7-1)). Note that in the context of + the Internet suite of protocols, values should be + calculated accordingly: + + layer functionality + 1 physical (e.g., repeaters) + 2 datalink/subnetwork (e.g., bridges) + 3 internet (e.g., IP gateways) + 4 end-to-end (e.g., IP hosts) + 7 applications (e.g., mail relays) + + For systems including OSI protocols, layers 5 and + 6 may also be counted." + ::= { system 7 } + +-- the Interfaces group + +-- Implementation of the Interfaces group is mandatory for +-- all systems. + +ifNumber OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of network interfaces (regardless of + their current state) present on this system." + ::= { interfaces 1 } + +-- the Interfaces table + +-- The Interfaces table contains information on the entity's +-- interfaces. Each interface is thought of as being +-- attached to a `subnetwork'. Note that this term should +-- not be confused with `subnet' which refers to an +-- addressing partitioning scheme used in the Internet suite +-- of protocols. + +ifTable OBJECT-TYPE + SYNTAX SEQUENCE OF IfEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "A list of interface entries. The number of + entries is given by the value of ifNumber." + ::= { interfaces 2 } + +ifEntry OBJECT-TYPE + SYNTAX IfEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "An interface entry containing objects at the + subnetwork layer and below for a particular + interface." + INDEX { ifIndex } + ::= { ifTable 1 } + +IfEntry ::= + SEQUENCE { + ifIndex + INTEGER, + ifDescr + DisplayString, + ifType + INTEGER, + ifMtu + INTEGER, + ifSpeed + Gauge, + ifPhysAddress + PhysAddress, + ifAdminStatus + INTEGER, + ifOperStatus + INTEGER, + ifLastChange + TimeTicks, + ifInOctets + Counter, + ifInUcastPkts + Counter, + ifInNUcastPkts + Counter, + ifInDiscards + Counter, + ifInErrors + Counter, + ifInUnknownProtos + Counter, + ifOutOctets + Counter, + ifOutUcastPkts + Counter, + ifOutNUcastPkts + Counter, + ifOutDiscards + Counter, + ifOutErrors + Counter, + ifOutQLen + Gauge, + ifSpecific + OBJECT IDENTIFIER + } + +ifIndex OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A unique value for each interface. Its value + ranges between 1 and the value of ifNumber. The + value for each interface must remain constant at + least from one re-initialization of the entity's + network management system to the next re- + initialization." + ::= { ifEntry 1 } + +ifDescr OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A textual string containing information about the + interface. This string should include the name of + the manufacturer, the product name and the version + of the hardware interface." + ::= { ifEntry 2 } + +ifType OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- none of the following + regular1822(2), + hdh1822(3), + ddn-x25(4), + rfc877-x25(5), + ethernet-csmacd(6), + iso88023-csmacd(7), + iso88024-tokenBus(8), + iso88025-tokenRing(9), + iso88026-man(10), + starLan(11), + proteon-10Mbit(12), + proteon-80Mbit(13), + hyperchannel(14), + fddi(15), + lapb(16), + sdlc(17), + ds1(18), -- T-1 + e1(19), -- european equiv. of T-1 + basicISDN(20), + primaryISDN(21), -- proprietary serial + propPointToPointSerial(22), + ppp(23), + softwareLoopback(24), + eon(25), -- CLNP over IP [11] + ethernet-3Mbit(26), + nsip(27), -- XNS over IP + slip(28), -- generic SLIP + ultra(29), -- ULTRA technologies + ds3(30), -- T-3 + sip(31), -- SMDS + frame-relay(32) + } + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The type of interface, distinguished according to + the physical/link protocol(s) immediately `below' + the network layer in the protocol stack." + ::= { ifEntry 3 } + +ifMtu OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The size of the largest datagram which can be + sent/received on the interface, specified in + octets. For interfaces that are used for + transmitting network datagrams, this is the size + of the largest network datagram that can be sent + on the interface." + ::= { ifEntry 4 } + +ifSpeed OBJECT-TYPE + SYNTAX Gauge + ACCESS read-only + STATUS mandatory + DESCRIPTION + "An estimate of the interface's current bandwidth + in bits per second. For interfaces which do not + vary in bandwidth or for those where no accurate + estimation can be made, this object should contain + the nominal bandwidth." + ::= { ifEntry 5 } + +ifPhysAddress OBJECT-TYPE + SYNTAX PhysAddress + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The interface's address at the protocol layer + immediately `below' the network layer in the + protocol stack. For interfaces which do not have + + such an address (e.g., a serial line), this object + should contain an octet string of zero length." + ::= { ifEntry 6 } + +ifAdminStatus OBJECT-TYPE + SYNTAX INTEGER { + up(1), -- ready to pass packets + down(2), + testing(3) -- in some test mode + } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The desired state of the interface. The + testing(3) state indicates that no operational + packets can be passed." + ::= { ifEntry 7 } + +ifOperStatus OBJECT-TYPE + SYNTAX INTEGER { + up(1), -- ready to pass packets + down(2), + testing(3) -- in some test mode + } + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The current operational state of the interface. + The testing(3) state indicates that no operational + packets can be passed." + ::= { ifEntry 8 } + +ifLastChange OBJECT-TYPE + SYNTAX TimeTicks + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The value of sysUpTime at the time the interface + entered its current operational state. If the + current state was entered prior to the last re- + initialization of the local network management + subsystem, then this object contains a zero + value." + ::= { ifEntry 9 } + +ifInOctets OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of octets received on the + interface, including framing characters." + ::= { ifEntry 10 } + +ifInUcastPkts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of subnetwork-unicast packets + delivered to a higher-layer protocol." + ::= { ifEntry 11 } + +ifInNUcastPkts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of non-unicast (i.e., subnetwork- + broadcast or subnetwork-multicast) packets + delivered to a higher-layer protocol." + ::= { ifEntry 12 } + +ifInDiscards OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of inbound packets which were chosen + to be discarded even though no errors had been + detected to prevent their being deliverable to a + higher-layer protocol. One possible reason for + discarding such a packet could be to free up + buffer space." + ::= { ifEntry 13 } + +ifInErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of inbound packets that contained + errors preventing them from being deliverable to a + higher-layer protocol." + ::= { ifEntry 14 } + +ifInUnknownProtos OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of packets received via the interface + which were discarded because of an unknown or + unsupported protocol." + ::= { ifEntry 15 } + +ifOutOctets OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of octets transmitted out of the + interface, including framing characters." + ::= { ifEntry 16 } + +ifOutUcastPkts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of packets that higher-level + protocols requested be transmitted to a + subnetwork-unicast address, including those that + were discarded or not sent." + ::= { ifEntry 17 } + +ifOutNUcastPkts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of packets that higher-level + protocols requested be transmitted to a non- + unicast (i.e., a subnetwork-broadcast or + subnetwork-multicast) address, including those + that were discarded or not sent." + ::= { ifEntry 18 } + +ifOutDiscards OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of outbound packets which were chosen + + to be discarded even though no errors had been + detected to prevent their being transmitted. One + possible reason for discarding such a packet could + be to free up buffer space." + ::= { ifEntry 19 } + +ifOutErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of outbound packets that could not be + transmitted because of errors." + ::= { ifEntry 20 } + +ifOutQLen OBJECT-TYPE + SYNTAX Gauge + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The length of the output packet queue (in + packets)." + ::= { ifEntry 21 } + +ifSpecific OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A reference to MIB definitions specific to the + particular media being used to realize the + interface. For example, if the interface is + realized by an ethernet, then the value of this + object refers to a document defining objects + specific to ethernet. If this information is not + present, its value should be set to the OBJECT + IDENTIFIER { 0 0 }, which is a syntatically valid + object identifier, and any conformant + implementation of ASN.1 and BER must be able to + generate and recognize this value." + ::= { ifEntry 22 } + +-- the Address Translation group + +-- Implementation of the Address Translation group is +-- mandatory for all systems. Note however that this group +-- is deprecated by MIB-II. That is, it is being included + +-- solely for compatibility with MIB-I nodes, and will most +-- likely be excluded from MIB-III nodes. From MIB-II and +-- onwards, each network protocol group contains its own +-- address translation tables. + +-- The Address Translation group contains one table which is +-- the union across all interfaces of the translation tables +-- for converting a NetworkAddress (e.g., an IP address) into +-- a subnetwork-specific address. For lack of a better term, +-- this document refers to such a subnetwork-specific address +-- as a `physical' address. + +-- Examples of such translation tables are: for broadcast +-- media where ARP is in use, the translation table is +-- equivalent to the ARP cache; or, on an X.25 network where +-- non-algorithmic translation to X.121 addresses is +-- required, the translation table contains the +-- NetworkAddress to X.121 address equivalences. + +atTable OBJECT-TYPE + SYNTAX SEQUENCE OF AtEntry + ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "The Address Translation tables contain the + NetworkAddress to `physical' address equivalences. + Some interfaces do not use translation tables for + determining address equivalences (e.g., DDN-X.25 + has an algorithmic method); if all interfaces are + of this type, then the Address Translation table + is empty, i.e., has zero entries." + ::= { at 1 } + +atEntry OBJECT-TYPE + SYNTAX AtEntry + ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "Each entry contains one NetworkAddress to + `physical' address equivalence." + INDEX { atIfIndex, + atNetAddress } + ::= { atTable 1 } + +AtEntry ::= + SEQUENCE { + atIfIndex + INTEGER, + atPhysAddress + PhysAddress, + atNetAddress + NetworkAddress + } + +atIfIndex OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS deprecated + DESCRIPTION + "The interface on which this entry's equivalence + is effective. The interface identified by a + particular value of this index is the same + interface as identified by the same value of + ifIndex." + ::= { atEntry 1 } + +atPhysAddress OBJECT-TYPE + SYNTAX PhysAddress + ACCESS read-write + STATUS deprecated + DESCRIPTION + "The media-dependent `physical' address. + + Setting this object to a null string (one of zero + length) has the effect of invaliding the + corresponding entry in the atTable object. That + is, it effectively dissasociates the interface + identified with said entry from the mapping + identified with said entry. It is an + implementation-specific matter as to whether the + agent removes an invalidated entry from the table. + Accordingly, management stations must be prepared + to receive tabular information from agents that + corresponds to entries not currently in use. + Proper interpretation of such entries requires + examination of the relevant atPhysAddress object." + ::= { atEntry 2 } + +atNetAddress OBJECT-TYPE + SYNTAX NetworkAddress + ACCESS read-write + STATUS deprecated + DESCRIPTION + "The NetworkAddress (e.g., the IP address) + corresponding to the media-dependent `physical' + address." + ::= { atEntry 3 } + +-- the IP group + +-- Implementation of the IP group is mandatory for all +-- systems. + +ipForwarding OBJECT-TYPE + SYNTAX INTEGER { + forwarding(1), -- acting as a gateway + not-forwarding(2) -- NOT acting as a gateway + } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The indication of whether this entity is acting + as an IP gateway in respect to the forwarding of + datagrams received by, but not addressed to, this + entity. IP gateways forward datagrams. IP hosts + do not (except those source-routed via the host). + + Note that for some managed nodes, this object may + take on only a subset of the values possible. + Accordingly, it is appropriate for an agent to + return a `badValue' response if a management + station attempts to change this object to an + inappropriate value." + ::= { ip 1 } + +ipDefaultTTL OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The default value inserted into the Time-To-Live + field of the IP header of datagrams originated at + this entity, whenever a TTL value is not supplied + by the transport layer protocol." + ::= { ip 2 } + +ipInReceives OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of input datagrams received from + interfaces, including those received in error." + ::= { ip 3 } + +ipInHdrErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of input datagrams discarded due to + errors in their IP headers, including bad + checksums, version number mismatch, other format + errors, time-to-live exceeded, errors discovered + in processing their IP options, etc." + ::= { ip 4 } + +ipInAddrErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of input datagrams discarded because + the IP address in their IP header's destination + field was not a valid address to be received at + this entity. This count includes invalid + addresses (e.g., 0.0.0.0) and addresses of + unsupported Classes (e.g., Class E). For entities + which are not IP Gateways and therefore do not + forward datagrams, this counter includes datagrams + discarded because the destination address was not + a local address." + ::= { ip 5 } + +ipForwDatagrams OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of input datagrams for which this + entity was not their final IP destination, as a + result of which an attempt was made to find a + route to forward them to that final destination. + In entities which do not act as IP Gateways, this + counter will include only those packets which were + Source-Routed via this entity, and the Source- + Route option processing was successful." + ::= { ip 6 } + +ipInUnknownProtos OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of locally-addressed datagrams + received successfully but discarded because of an + unknown or unsupported protocol." + ::= { ip 7 } + +ipInDiscards OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of input IP datagrams for which no + problems were encountered to prevent their + continued processing, but which were discarded + (e.g., for lack of buffer space). Note that this + counter does not include any datagrams discarded + while awaiting re-assembly." + ::= { ip 8 } + +ipInDelivers OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of input datagrams successfully + delivered to IP user-protocols (including ICMP)." + ::= { ip 9 } + +ipOutRequests OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of IP datagrams which local IP + user-protocols (including ICMP) supplied to IP in + requests for transmission. Note that this counter + does not include any datagrams counted in + ipForwDatagrams." + ::= { ip 10 } + +ipOutDiscards OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of output IP datagrams for which no + + problem was encountered to prevent their + transmission to their destination, but which were + discarded (e.g., for lack of buffer space). Note + that this counter would include datagrams counted + in ipForwDatagrams if any such packets met this + (discretionary) discard criterion." + ::= { ip 11 } + +ipOutNoRoutes OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of IP datagrams discarded because no + route could be found to transmit them to their + destination. Note that this counter includes any + packets counted in ipForwDatagrams which meet this + `no-route' criterion. Note that this includes any + datagarms which a host cannot route because all of + its default gateways are down." + ::= { ip 12 } + +ipReasmTimeout OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The maximum number of seconds which received + fragments are held while they are awaiting + reassembly at this entity." + ::= { ip 13 } + +ipReasmReqds OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of IP fragments received which needed + to be reassembled at this entity." + ::= { ip 14 } + +ipReasmOKs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of IP datagrams successfully re- + assembled." + ::= { ip 15 } + +ipReasmFails OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of failures detected by the IP re- + assembly algorithm (for whatever reason: timed + out, errors, etc). Note that this is not + necessarily a count of discarded IP fragments + since some algorithms (notably the algorithm in + RFC 815) can lose track of the number of fragments + by combining them as they are received." + ::= { ip 16 } + +ipFragOKs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of IP datagrams that have been + successfully fragmented at this entity." + ::= { ip 17 } + +ipFragFails OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of IP datagrams that have been + discarded because they needed to be fragmented at + this entity but could not be, e.g., because their + Don't Fragment flag was set." + ::= { ip 18 } + +ipFragCreates OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of IP datagram fragments that have + been generated as a result of fragmentation at + this entity." + ::= { ip 19 } + +-- the IP address table + +-- The IP address table contains this entity's IP addressing +-- information. + +ipAddrTable OBJECT-TYPE + SYNTAX SEQUENCE OF IpAddrEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "The table of addressing information relevant to + this entity's IP addresses." + ::= { ip 20 } + +ipAddrEntry OBJECT-TYPE + SYNTAX IpAddrEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "The addressing information for one of this + entity's IP addresses." + INDEX { ipAdEntAddr } + ::= { ipAddrTable 1 } + +IpAddrEntry ::= + SEQUENCE { + ipAdEntAddr + IpAddress, + ipAdEntIfIndex + INTEGER, + ipAdEntNetMask + IpAddress, + ipAdEntBcastAddr + INTEGER, + ipAdEntReasmMaxSize + INTEGER (0..65535) + } + +ipAdEntAddr OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The IP address to which this entry's addressing + information pertains." + ::= { ipAddrEntry 1 } + +ipAdEntIfIndex OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The index value which uniquely identifies the + interface to which this entry is applicable. The + interface identified by a particular value of this + index is the same interface as identified by the + same value of ifIndex." + ::= { ipAddrEntry 2 } + +ipAdEntNetMask OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The subnet mask associated with the IP address of + this entry. The value of the mask is an IP + address with all the network bits set to 1 and all + the hosts bits set to 0." + ::= { ipAddrEntry 3 } + +ipAdEntBcastAddr OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The value of the least-significant bit in the IP + broadcast address used for sending datagrams on + the (logical) interface associated with the IP + address of this entry. For example, when the + Internet standard all-ones broadcast address is + used, the value will be 1. This value applies to + both the subnet and network broadcasts addresses + used by the entity on this (logical) interface." + ::= { ipAddrEntry 4 } + +ipAdEntReasmMaxSize OBJECT-TYPE + SYNTAX INTEGER (0..65535) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The size of the largest IP datagram which this + entity can re-assemble from incoming IP fragmented + datagrams received on this interface." + ::= { ipAddrEntry 5 } + +-- the IP routing table + +-- The IP routing table contains an entry for each route +-- presently known to this entity. + +ipRouteTable OBJECT-TYPE + SYNTAX SEQUENCE OF IpRouteEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "This entity's IP Routing table." + ::= { ip 21 } + +ipRouteEntry OBJECT-TYPE + SYNTAX IpRouteEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "A route to a particular destination." + INDEX { ipRouteDest } + ::= { ipRouteTable 1 } + +IpRouteEntry ::= + SEQUENCE { + ipRouteDest + IpAddress, + ipRouteIfIndex + INTEGER, + ipRouteMetric1 + INTEGER, + ipRouteMetric2 + INTEGER, + ipRouteMetric3 + INTEGER, + ipRouteMetric4 + INTEGER, + ipRouteNextHop + IpAddress, + ipRouteType + INTEGER, + ipRouteProto + INTEGER, + ipRouteAge + INTEGER, + ipRouteMask + IpAddress, + ipRouteMetric5 + INTEGER, + ipRouteInfo + OBJECT IDENTIFIER + } + +ipRouteDest OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The destination IP address of this route. An + entry with a value of 0.0.0.0 is considered a + default route. Multiple routes to a single + destination can appear in the table, but access to + such multiple entries is dependent on the table- + access mechanisms defined by the network + management protocol in use." + ::= { ipRouteEntry 1 } + +ipRouteIfIndex OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The index value which uniquely identifies the + local interface through which the next hop of this + route should be reached. The interface identified + by a particular value of this index is the same + interface as identified by the same value of + ifIndex." + ::= { ipRouteEntry 2 } + +ipRouteMetric1 OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The primary routing metric for this route. The + semantics of this metric are determined by the + routing-protocol specified in the route's + ipRouteProto value. If this metric is not used, + its value should be set to -1." + ::= { ipRouteEntry 3 } + +ipRouteMetric2 OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the + routing-protocol specified in the route's + ipRouteProto value. If this metric is not used, + its value should be set to -1." + ::= { ipRouteEntry 4 } + +ipRouteMetric3 OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the + routing-protocol specified in the route's + ipRouteProto value. If this metric is not used, + its value should be set to -1." + ::= { ipRouteEntry 5 } + +ipRouteMetric4 OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the + routing-protocol specified in the route's + ipRouteProto value. If this metric is not used, + its value should be set to -1." + ::= { ipRouteEntry 6 } + +ipRouteNextHop OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The IP address of the next hop of this route. + (In the case of a route bound to an interface + which is realized via a broadcast media, the value + of this field is the agent's IP address on that + interface.)" + ::= { ipRouteEntry 7 } + +ipRouteType OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- none of the following + + invalid(2), -- an invalidated route + + -- route to directly + direct(3), -- connected (sub-)network + + -- route to a non-local + indirect(4) -- host/network/sub-network + } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The type of route. Note that the values + direct(3) and indirect(4) refer to the notion of + direct and indirect routing in the IP + architecture. + + Setting this object to the value invalid(2) has + the effect of invalidating the corresponding entry + in the ipRouteTable object. That is, it + effectively dissasociates the destination + identified with said entry from the route + identified with said entry. It is an + implementation-specific matter as to whether the + agent removes an invalidated entry from the table. + Accordingly, management stations must be prepared + to receive tabular information from agents that + corresponds to entries not currently in use. + Proper interpretation of such entries requires + examination of the relevant ipRouteType object." + ::= { ipRouteEntry 8 } + +ipRouteProto OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- none of the following + + -- non-protocol information, + -- e.g., manually configured + local(2), -- entries + + -- set via a network + netmgmt(3), -- management protocol + + -- obtained via ICMP, + icmp(4), -- e.g., Redirect + + -- the remaining values are + -- all gateway routing + -- protocols + egp(5), + ggp(6), + hello(7), + rip(8), + is-is(9), + es-is(10), + ciscoIgrp(11), + bbnSpfIgp(12), + ospf(13), + bgp(14) + } + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The routing mechanism via which this route was + learned. Inclusion of values for gateway routing + protocols is not intended to imply that hosts + should support those protocols." + ::= { ipRouteEntry 9 } + +ipRouteAge OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The number of seconds since this route was last + updated or otherwise determined to be correct. + Note that no semantics of `too old' can be implied + except through knowledge of the routing protocol + by which the route was learned." + ::= { ipRouteEntry 10 } + +ipRouteMask OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-write + STATUS mandatory + DESCRIPTION + "Indicate the mask to be logical-ANDed with the + destination address before being compared to the + value in the ipRouteDest field. For those systems + that do not support arbitrary subnet masks, an + agent constructs the value of the ipRouteMask by + determining whether the value of the correspondent + ipRouteDest field belong to a class-A, B, or C + network, and then using one of: + + mask network + 255.0.0.0 class-A + 255.255.0.0 class-B + 255.255.255.0 class-C + + If the value of the ipRouteDest is 0.0.0.0 (a + default route), then the mask value is also + 0.0.0.0. It should be noted that all IP routing + subsystems implicitly use this mechanism." + ::= { ipRouteEntry 11 } + +ipRouteMetric5 OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the + routing-protocol specified in the route's + ipRouteProto value. If this metric is not used, + its value should be set to -1." + ::= { ipRouteEntry 12 } + +ipRouteInfo OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A reference to MIB definitions specific to the + particular routing protocol which is responsible + for this route, as determined by the value + specified in the route's ipRouteProto value. If + this information is not present, its value should + be set to the OBJECT IDENTIFIER { 0 0 }, which is + a syntatically valid object identifier, and any + conformant implementation of ASN.1 and BER must be + able to generate and recognize this value." + ::= { ipRouteEntry 13 } + +-- the IP Address Translation table + +-- The IP address translation table contain the IpAddress to +-- `physical' address equivalences. Some interfaces do not +-- use translation tables for determining address +-- equivalences (e.g., DDN-X.25 has an algorithmic method); +-- if all interfaces are of this type, then the Address +-- Translation table is empty, i.e., has zero entries. + +ipNetToMediaTable OBJECT-TYPE + SYNTAX SEQUENCE OF IpNetToMediaEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "The IP Address Translation table used for mapping + from IP addresses to physical addresses." + ::= { ip 22 } + +ipNetToMediaEntry OBJECT-TYPE + SYNTAX IpNetToMediaEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "Each entry contains one IpAddress to `physical' + address equivalence." + INDEX { ipNetToMediaIfIndex, + ipNetToMediaNetAddress } + ::= { ipNetToMediaTable 1 } + +IpNetToMediaEntry ::= + SEQUENCE { + ipNetToMediaIfIndex + INTEGER, + ipNetToMediaPhysAddress + PhysAddress, + ipNetToMediaNetAddress + IpAddress, + ipNetToMediaType + INTEGER + } + +ipNetToMediaIfIndex OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The interface on which this entry's equivalence + is effective. The interface identified by a + particular value of this index is the same + interface as identified by the same value of + ifIndex." + ::= { ipNetToMediaEntry 1 } + +ipNetToMediaPhysAddress OBJECT-TYPE + SYNTAX PhysAddress + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The media-dependent `physical' address." + ::= { ipNetToMediaEntry 2 } + +ipNetToMediaNetAddress OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The IpAddress corresponding to the media- + dependent `physical' address." + ::= { ipNetToMediaEntry 3 } + +ipNetToMediaType OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- none of the following + invalid(2), -- an invalidated mapping + dynamic(3), + static(4) + } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The type of mapping. + + Setting this object to the value invalid(2) has + the effect of invalidating the corresponding entry + in the ipNetToMediaTable. That is, it effectively + dissasociates the interface identified with said + entry from the mapping identified with said entry. + It is an implementation-specific matter as to + whether the agent removes an invalidated entry + from the table. Accordingly, management stations + must be prepared to receive tabular information + from agents that corresponds to entries not + currently in use. Proper interpretation of such + entries requires examination of the relevant + ipNetToMediaType object." + ::= { ipNetToMediaEntry 4 } + +-- additional IP objects + +ipRoutingDiscards OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of routing entries which were chosen + to be discarded even though they are valid. One + possible reason for discarding such an entry could + be to free-up buffer space for other routing + + entries." + ::= { ip 23 } + +-- the ICMP group + +-- Implementation of the ICMP group is mandatory for all +-- systems. + +icmpInMsgs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of ICMP messages which the + entity received. Note that this counter includes + all those counted by icmpInErrors." + ::= { icmp 1 } + +icmpInErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP messages which the entity + received but determined as having ICMP-specific + errors (bad ICMP checksums, bad length, etc.)." + ::= { icmp 2 } + +icmpInDestUnreachs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Destination Unreachable + messages received." + ::= { icmp 3 } + +icmpInTimeExcds OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Time Exceeded messages + received." + ::= { icmp 4 } + +icmpInParmProbs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Parameter Problem messages + received." + ::= { icmp 5 } + +icmpInSrcQuenchs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Source Quench messages + received." + ::= { icmp 6 } + +icmpInRedirects OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Redirect messages received." + ::= { icmp 7 } + +icmpInEchos OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Echo (request) messages + received." + ::= { icmp 8 } + +icmpInEchoReps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Echo Reply messages received." + ::= { icmp 9 } + +icmpInTimestamps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Timestamp (request) messages + received." + ::= { icmp 10 } + +icmpInTimestampReps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Timestamp Reply messages + received." + ::= { icmp 11 } + +icmpInAddrMasks OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Address Mask Request messages + received." + ::= { icmp 12 } + +icmpInAddrMaskReps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Address Mask Reply messages + received." + ::= { icmp 13 } + +icmpOutMsgs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of ICMP messages which this + entity attempted to send. Note that this counter + includes all those counted by icmpOutErrors." + ::= { icmp 14 } + +icmpOutErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP messages which this entity did + not send due to problems discovered within ICMP + + such as a lack of buffers. This value should not + include errors discovered outside the ICMP layer + such as the inability of IP to route the resultant + datagram. In some implementations there may be no + types of error which contribute to this counter's + value." + ::= { icmp 15 } + +icmpOutDestUnreachs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Destination Unreachable + messages sent." + ::= { icmp 16 } + +icmpOutTimeExcds OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Time Exceeded messages sent." + ::= { icmp 17 } + +icmpOutParmProbs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Parameter Problem messages + sent." + ::= { icmp 18 } + +icmpOutSrcQuenchs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Source Quench messages sent." + ::= { icmp 19 } + +icmpOutRedirects OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Redirect messages sent. For a + + host, this object will always be zero, since hosts + do not send redirects." + ::= { icmp 20 } + +icmpOutEchos OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Echo (request) messages sent." + ::= { icmp 21 } + +icmpOutEchoReps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Echo Reply messages sent." + ::= { icmp 22 } + +icmpOutTimestamps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Timestamp (request) messages + sent." + ::= { icmp 23 } + +icmpOutTimestampReps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Timestamp Reply messages + sent." + ::= { icmp 24 } + +icmpOutAddrMasks OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Address Mask Request messages + sent." + ::= { icmp 25 } + +icmpOutAddrMaskReps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Address Mask Reply messages + sent." + ::= { icmp 26 } + +-- the TCP group + +-- Implementation of the TCP group is mandatory for all +-- systems that implement the TCP. + +-- Note that instances of object types that represent +-- information about a particular TCP connection are +-- transient; they persist only as long as the connection +-- in question. + +tcpRtoAlgorithm OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- none of the following + + constant(2), -- a constant rto + rsre(3), -- MIL-STD-1778, Appendix B + vanj(4) -- Van Jacobson's algorithm [10] + } + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The algorithm used to determine the timeout value + used for retransmitting unacknowledged octets." + ::= { tcp 1 } + +tcpRtoMin OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The minimum value permitted by a TCP + implementation for the retransmission timeout, + measured in milliseconds. More refined semantics + for objects of this type depend upon the algorithm + used to determine the retransmission timeout. In + particular, when the timeout algorithm is rsre(3), + an object of this type has the semantics of the + LBOUND quantity described in RFC 793." + ::= { tcp 2 } + +tcpRtoMax OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The maximum value permitted by a TCP + implementation for the retransmission timeout, + measured in milliseconds. More refined semantics + for objects of this type depend upon the algorithm + used to determine the retransmission timeout. In + particular, when the timeout algorithm is rsre(3), + an object of this type has the semantics of the + UBOUND quantity described in RFC 793." + ::= { tcp 3 } + +tcpMaxConn OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The limit on the total number of TCP connections + the entity can support. In entities where the + maximum number of connections is dynamic, this + object should contain the value -1." + ::= { tcp 4 } + +tcpActiveOpens OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of times TCP connections have made a + direct transition to the SYN-SENT state from the + CLOSED state." + ::= { tcp 5 } + +tcpPassiveOpens OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of times TCP connections have made a + direct transition to the SYN-RCVD state from the + LISTEN state." + ::= { tcp 6 } + +tcpAttemptFails OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of times TCP connections have made a + direct transition to the CLOSED state from either + the SYN-SENT state or the SYN-RCVD state, plus the + number of times TCP connections have made a direct + transition to the LISTEN state from the SYN-RCVD + state." + ::= { tcp 7 } + +tcpEstabResets OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of times TCP connections have made a + direct transition to the CLOSED state from either + the ESTABLISHED state or the CLOSE-WAIT state." + ::= { tcp 8 } + +tcpCurrEstab OBJECT-TYPE + SYNTAX Gauge + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of TCP connections for which the + current state is either ESTABLISHED or CLOSE- + WAIT." + ::= { tcp 9 } + +tcpInSegs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of segments received, including + those received in error. This count includes + segments received on currently established + connections." + ::= { tcp 10 } + +tcpOutSegs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of segments sent, including + those on current connections but excluding those + containing only retransmitted octets." + ::= { tcp 11 } + +tcpRetransSegs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of segments retransmitted - that + is, the number of TCP segments transmitted + containing one or more previously transmitted + octets." + ::= { tcp 12 } + +-- the TCP Connection table + +-- The TCP connection table contains information about this +-- entity's existing TCP connections. + +tcpConnTable OBJECT-TYPE + SYNTAX SEQUENCE OF TcpConnEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "A table containing TCP connection-specific + information." + ::= { tcp 13 } + +tcpConnEntry OBJECT-TYPE + SYNTAX TcpConnEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "Information about a particular current TCP + connection. An object of this type is transient, + in that it ceases to exist when (or soon after) + the connection makes the transition to the CLOSED + state." + INDEX { tcpConnLocalAddress, + tcpConnLocalPort, + tcpConnRemAddress, + tcpConnRemPort } + ::= { tcpConnTable 1 } + +TcpConnEntry ::= + SEQUENCE { + tcpConnState + INTEGER, + tcpConnLocalAddress + IpAddress, + tcpConnLocalPort + INTEGER (0..65535), + tcpConnRemAddress + IpAddress, + tcpConnRemPort + INTEGER (0..65535) + } + +tcpConnState OBJECT-TYPE + SYNTAX INTEGER { + closed(1), + listen(2), + synSent(3), + synReceived(4), + established(5), + finWait1(6), + finWait2(7), + closeWait(8), + lastAck(9), + closing(10), + timeWait(11), + deleteTCB(12) + } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The state of this TCP connection. + + The only value which may be set by a management + station is deleteTCB(12). Accordingly, it is + appropriate for an agent to return a `badValue' + response if a management station attempts to set + this object to any other value. + + If a management station sets this object to the + value deleteTCB(12), then this has the effect of + deleting the TCB (as defined in RFC 793) of the + corresponding connection on the managed node, + resulting in immediate termination of the + connection. + + As an implementation-specific option, a RST + + segment may be sent from the managed node to the + other TCP endpoint (note however that RST segments + are not sent reliably)." + ::= { tcpConnEntry 1 } + +tcpConnLocalAddress OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The local IP address for this TCP connection. In + the case of a connection in the listen state which + is willing to accept connections for any IP + interface associated with the node, the value + 0.0.0.0 is used." + ::= { tcpConnEntry 2 } + +tcpConnLocalPort OBJECT-TYPE + SYNTAX INTEGER (0..65535) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The local port number for this TCP connection." + ::= { tcpConnEntry 3 } + +tcpConnRemAddress OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The remote IP address for this TCP connection." + ::= { tcpConnEntry 4 } + +tcpConnRemPort OBJECT-TYPE + SYNTAX INTEGER (0..65535) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The remote port number for this TCP connection." + ::= { tcpConnEntry 5 } + +-- additional TCP objects + +tcpInErrs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of segments received in error + (e.g., bad TCP checksums)." + ::= { tcp 14 } + +tcpOutRsts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of TCP segments sent containing the + RST flag." + ::= { tcp 15 } + +-- the UDP group + +-- Implementation of the UDP group is mandatory for all +-- systems which implement the UDP. + +udpInDatagrams OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of UDP datagrams delivered to + UDP users." + ::= { udp 1 } + +udpNoPorts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of received UDP datagrams for + which there was no application at the destination + port." + ::= { udp 2 } + +udpInErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of received UDP datagrams that could + not be delivered for reasons other than the lack + of an application at the destination port." + ::= { udp 3 } + +udpOutDatagrams OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of UDP datagrams sent from this + entity." + ::= { udp 4 } + +-- the UDP Listener table + +-- The UDP listener table contains information about this +-- entity's UDP end-points on which a local application is +-- currently accepting datagrams. + +udpTable OBJECT-TYPE + SYNTAX SEQUENCE OF UdpEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "A table containing UDP listener information." + ::= { udp 5 } + +udpEntry OBJECT-TYPE + SYNTAX UdpEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "Information about a particular current UDP + listener." + INDEX { udpLocalAddress, udpLocalPort } + ::= { udpTable 1 } + +UdpEntry ::= + SEQUENCE { + udpLocalAddress + IpAddress, + udpLocalPort + INTEGER (0..65535) + } + +udpLocalAddress OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The local IP address for this UDP listener. In + + the case of a UDP listener which is willing to + accept datagrams for any IP interface associated + with the node, the value 0.0.0.0 is used." + ::= { udpEntry 1 } + +udpLocalPort OBJECT-TYPE + SYNTAX INTEGER (0..65535) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The local port number for this UDP listener." + ::= { udpEntry 2 } + +-- the EGP group + +-- Implementation of the EGP group is mandatory for all +-- systems which implement the EGP. + +egpInMsgs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of EGP messages received without + error." + ::= { egp 1 } + +egpInErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of EGP messages received that proved + to be in error." + ::= { egp 2 } + +egpOutMsgs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of locally generated EGP + messages." + ::= { egp 3 } + +egpOutErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of locally generated EGP messages not + sent due to resource limitations within an EGP + entity." + ::= { egp 4 } + +-- the EGP Neighbor table + +-- The EGP neighbor table contains information about this +-- entity's EGP neighbors. + +egpNeighTable OBJECT-TYPE + SYNTAX SEQUENCE OF EgpNeighEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "The EGP neighbor table." + ::= { egp 5 } + +egpNeighEntry OBJECT-TYPE + SYNTAX EgpNeighEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "Information about this entity's relationship with + a particular EGP neighbor." + INDEX { egpNeighAddr } + ::= { egpNeighTable 1 } + +EgpNeighEntry ::= + SEQUENCE { + egpNeighState + INTEGER, + egpNeighAddr + IpAddress, + egpNeighAs + INTEGER, + egpNeighInMsgs + Counter, + egpNeighInErrs + Counter, + egpNeighOutMsgs + Counter, + egpNeighOutErrs + Counter, + egpNeighInErrMsgs + Counter, + egpNeighOutErrMsgs + Counter, + egpNeighStateUps + Counter, + egpNeighStateDowns + Counter, + egpNeighIntervalHello + INTEGER, + egpNeighIntervalPoll + INTEGER, + egpNeighMode + INTEGER, + egpNeighEventTrigger + INTEGER + } + +egpNeighState OBJECT-TYPE + SYNTAX INTEGER { + idle(1), + acquisition(2), + down(3), + up(4), + cease(5) + } + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The EGP state of the local system with respect to + this entry's EGP neighbor. Each EGP state is + represented by a value that is one greater than + the numerical value associated with said state in + RFC 904." + ::= { egpNeighEntry 1 } + +egpNeighAddr OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The IP address of this entry's EGP neighbor." + ::= { egpNeighEntry 2 } + +egpNeighAs OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The autonomous system of this EGP peer. Zero + should be specified if the autonomous system + number of the neighbor is not yet known." + ::= { egpNeighEntry 3 } + +egpNeighInMsgs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of EGP messages received without error + from this EGP peer." + ::= { egpNeighEntry 4 } + +egpNeighInErrs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of EGP messages received from this EGP + peer that proved to be in error (e.g., bad EGP + checksum)." + ::= { egpNeighEntry 5 } + +egpNeighOutMsgs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of locally generated EGP messages to + this EGP peer." + ::= { egpNeighEntry 6 } + +egpNeighOutErrs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of locally generated EGP messages not + sent to this EGP peer due to resource limitations + within an EGP entity." + ::= { egpNeighEntry 7 } + +egpNeighInErrMsgs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of EGP-defined error messages received + from this EGP peer." + ::= { egpNeighEntry 8 } + +egpNeighOutErrMsgs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of EGP-defined error messages sent to + this EGP peer." + ::= { egpNeighEntry 9 } + +egpNeighStateUps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of EGP state transitions to the UP + state with this EGP peer." + ::= { egpNeighEntry 10 } + +egpNeighStateDowns OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of EGP state transitions from the UP + state to any other state with this EGP peer." + ::= { egpNeighEntry 11 } + +egpNeighIntervalHello OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The interval between EGP Hello command + retransmissions (in hundredths of a second). This + represents the t1 timer as defined in RFC 904." + ::= { egpNeighEntry 12 } + +egpNeighIntervalPoll OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The interval between EGP poll command + + retransmissions (in hundredths of a second). This + represents the t3 timer as defined in RFC 904." + ::= { egpNeighEntry 13 } + +egpNeighMode OBJECT-TYPE + SYNTAX INTEGER { active(1), passive(2) } + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The polling mode of this EGP entity, either + passive or active." + ::= { egpNeighEntry 14 } + +egpNeighEventTrigger OBJECT-TYPE + SYNTAX INTEGER { start(1), stop(2) } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A control variable used to trigger operator- + initiated Start and Stop events. When read, this + variable always returns the most recent value that + egpNeighEventTrigger was set to. If it has not + been set since the last initialization of the + network management subsystem on the node, it + returns a value of `stop'. + + When set, this variable causes a Start or Stop + event on the specified neighbor, as specified on + pages 8-10 of RFC 904. Briefly, a Start event + causes an Idle peer to begin neighbor acquisition + and a non-Idle peer to reinitiate neighbor + acquisition. A stop event causes a non-Idle peer + to return to the Idle state until a Start event + occurs, either via egpNeighEventTrigger or + otherwise." + ::= { egpNeighEntry 15 } + +-- additional EGP objects + +egpAs OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The autonomous system number of this EGP entity." + ::= { egp 6 } + +-- the Transmission group + +-- Based on the transmission media underlying each interface +-- on a system, the corresponding portion of the Transmission +-- group is mandatory for that system. + +-- When Internet-standard definitions for managing +-- transmission media are defined, the transmission group is +-- used to provide a prefix for the names of those objects. + +-- Typically, such definitions reside in the experimental +-- portion of the MIB until they are "proven", then as a +-- part of the Internet standardization process, the +-- definitions are accordingly elevated and a new object +-- identifier, under the transmission group is defined. By +-- convention, the name assigned is: +-- +-- type OBJECT IDENTIFIER ::= { transmission number } +-- +-- where "type" is the symbolic value used for the media in +-- the ifType column of the ifTable object, and "number" is +-- the actual integer value corresponding to the symbol. + +-- the SNMP group + +-- Implementation of the SNMP group is mandatory for all +-- systems which support an SNMP protocol entity. Some of +-- the objects defined below will be zero-valued in those +-- SNMP implementations that are optimized to support only +-- those functions specific to either a management agent or +-- a management station. In particular, it should be +-- observed that the objects below refer to an SNMP entity, +-- and there may be several SNMP entities residing on a +-- managed node (e.g., if the node is hosting acting as +-- a management station). + +snmpInPkts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of Messages delivered to the + SNMP entity from the transport service." + ::= { snmp 1 } + +snmpOutPkts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Messages which were + passed from the SNMP protocol entity to the + transport service." + ::= { snmp 2 } + +snmpInBadVersions OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Messages which were + delivered to the SNMP protocol entity and were for + an unsupported SNMP version." + ::= { snmp 3 } + +snmpInBadCommunityNames OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Messages delivered to + the SNMP protocol entity which used a SNMP + community name not known to said entity." + ::= { snmp 4 } + +snmpInBadCommunityUses OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Messages delivered to + the SNMP protocol entity which represented an SNMP + operation which was not allowed by the SNMP + community named in the Message." + ::= { snmp 5 } + +snmpInASNParseErrs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of ASN.1 or BER errors + encountered by the SNMP protocol entity when + decoding received SNMP Messages." + ::= { snmp 6 } + +-- { snmp 7 } is not used + +snmpInTooBigs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP PDUs which were + delivered to the SNMP protocol entity and for + which the value of the error-status field is + `tooBig'." + ::= { snmp 8 } + +snmpInNoSuchNames OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP PDUs which were + delivered to the SNMP protocol entity and for + which the value of the error-status field is + `noSuchName'." + ::= { snmp 9 } + +snmpInBadValues OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP PDUs which were + delivered to the SNMP protocol entity and for + which the value of the error-status field is + `badValue'." + ::= { snmp 10 } + +snmpInReadOnlys OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number valid SNMP PDUs which were + delivered to the SNMP protocol entity and for + which the value of the error-status field is + `readOnly'. It should be noted that it is a + protocol error to generate an SNMP PDU which + contains the value `readOnly' in the error-status + field, as such this object is provided as a means + of detecting incorrect implementations of the + + SNMP." + ::= { snmp 11 } + +snmpInGenErrs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP PDUs which were + delivered to the SNMP protocol entity and for + which the value of the error-status field is + `genErr'." + ::= { snmp 12 } + +snmpInTotalReqVars OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of MIB objects which have been + retrieved successfully by the SNMP protocol entity + as the result of receiving valid SNMP Get-Request + and Get-Next PDUs." + ::= { snmp 13 } + +snmpInTotalSetVars OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of MIB objects which have been + altered successfully by the SNMP protocol entity + as the result of receiving valid SNMP Set-Request + PDUs." + ::= { snmp 14 } + +snmpInGetRequests OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Get-Request PDUs which + have been accepted and processed by the SNMP + protocol entity." + ::= { snmp 15 } + +snmpInGetNexts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Get-Next PDUs which have + been accepted and processed by the SNMP protocol + entity." + ::= { snmp 16 } + +snmpInSetRequests OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Set-Request PDUs which + have been accepted and processed by the SNMP + protocol entity." + ::= { snmp 17 } + +snmpInGetResponses OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Get-Response PDUs which + have been accepted and processed by the SNMP + protocol entity." + ::= { snmp 18 } + +snmpInTraps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Trap PDUs which have + been accepted and processed by the SNMP protocol + entity." + ::= { snmp 19 } + +snmpOutTooBigs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP PDUs which were + generated by the SNMP protocol entity and for + which the value of the error-status field is + `tooBig.'" + ::= { snmp 20 } + +snmpOutNoSuchNames OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP PDUs which were + generated by the SNMP protocol entity and for + which the value of the error-status is + `noSuchName'." + ::= { snmp 21 } + +snmpOutBadValues OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP PDUs which were + generated by the SNMP protocol entity and for + which the value of the error-status field is + `badValue'." + ::= { snmp 22 } + +-- { snmp 23 } is not used + +snmpOutGenErrs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP PDUs which were + generated by the SNMP protocol entity and for + which the value of the error-status field is + `genErr'." + ::= { snmp 24 } + +snmpOutGetRequests OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Get-Request PDUs which + have been generated by the SNMP protocol entity." + ::= { snmp 25 } + +snmpOutGetNexts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Get-Next PDUs which have + been generated by the SNMP protocol entity." + ::= { snmp 26 } + +snmpOutSetRequests OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Set-Request PDUs which + have been generated by the SNMP protocol entity." + ::= { snmp 27 } + +snmpOutGetResponses OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Get-Response PDUs which + have been generated by the SNMP protocol entity." + ::= { snmp 28 } + +snmpOutTraps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Trap PDUs which have + been generated by the SNMP protocol entity." + ::= { snmp 29 } + +snmpEnableAuthenTraps OBJECT-TYPE + SYNTAX INTEGER { enabled(1), disabled(2) } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "Indicates whether the SNMP agent process is + permitted to generate authentication-failure + traps. The value of this object overrides any + configuration information; as such, it provides a + means whereby all authentication-failure traps may + be disabled. + + Note that it is strongly recommended that this + object be stored in non-volatile memory so that it + remains constant between re-initializations of the + network management system." + ::= { snmp 30 } + +END diff --git a/mibs/RIPv2-MIB.txt b/mibs/RIPv2-MIB.txt new file mode 100644 index 0000000..6c92fb5 --- /dev/null +++ b/mibs/RIPv2-MIB.txt @@ -0,0 +1,530 @@ + RIPv2-MIB DEFINITIONS ::= BEGIN + + IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, Counter32, + TimeTicks, IpAddress FROM SNMPv2-SMI + TEXTUAL-CONVENTION, RowStatus FROM SNMPv2-TC + MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF + mib-2 FROM RFC1213-MIB; + + -- This MIB module uses the extended OBJECT-TYPE macro as + -- defined in [9]. + + rip2 MODULE-IDENTITY + LAST-UPDATED "9407272253Z" -- Wed Jul 27 22:53:04 PDT 1994 + ORGANIZATION "IETF RIP-II Working Group" + CONTACT-INFO + " Fred Baker + Postal: Cisco Systems + 519 Lado Drive + Santa Barbara, California 93111 + Tel: +1 805 681 0115 + E-Mail: fbaker@cisco.com + + Postal: Gary Malkin + Xylogics, Inc. + 53 Third Avenue + Burlington, MA 01803 + + Phone: (617) 272-8140 + EMail: gmalkin@Xylogics.COM" + DESCRIPTION + "The MIB module to describe the RIP2 Version 2 Protocol" + ::= { mib-2 23 } + + -- RIP-2 Management Information Base + + -- the RouteTag type represents the contents of the + -- Route Domain field in the packet header or route entry. + -- The use of the Route Domain is deprecated. + + RouteTag ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "the RouteTag type represents the contents of the Route Domain + field in the packet header or route entry" + SYNTAX OCTET STRING (SIZE (2)) + +--4.1 Global Counters + +-- The RIP-2 Globals Group. +-- Implementation of this group is mandatory for systems +-- which implement RIP-2. + +-- These counters are intended to facilitate debugging quickly +-- changing routes or failing neighbors + +rip2Globals OBJECT IDENTIFIER ::= { rip2 1 } + + rip2GlobalRouteChanges OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of route changes made to the IP Route + Database by RIP. This does not include the refresh + of a route's age." + ::= { rip2Globals 1 } + + rip2GlobalQueries OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of responses sent to RIP queries + from other systems." + ::= { rip2Globals 2 } + +--4.2 RIP Interface Tables + +-- RIP Interfaces Groups +-- Implementation of these Groups is mandatory for systems +-- which implement RIP-2. + +-- The RIP Interface Status Table. + + rip2IfStatTable OBJECT-TYPE + SYNTAX SEQUENCE OF Rip2IfStatEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of subnets which require separate + status monitoring in RIP." + ::= { rip2 2 } + + rip2IfStatEntry OBJECT-TYPE + SYNTAX Rip2IfStatEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A Single Routing Domain in a single Subnet." + INDEX { rip2IfStatAddress } + ::= { rip2IfStatTable 1 } + + Rip2IfStatEntry ::= + SEQUENCE { + rip2IfStatAddress + IpAddress, + rip2IfStatRcvBadPackets + Counter32, + rip2IfStatRcvBadRoutes + Counter32, + rip2IfStatSentUpdates + Counter32, + rip2IfStatStatus + RowStatus + } + + rip2IfStatAddress OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The IP Address of this system on the indicated + subnet. For unnumbered interfaces, the value 0.0.0.N, + where the least significant 24 bits (N) is the ifIndex + for the IP Interface in network byte order." + ::= { rip2IfStatEntry 1 } + + rip2IfStatRcvBadPackets OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of RIP response packets received by + the RIP process which were subsequently discarded + for any reason (e.g. a version 0 packet, or an + unknown command type)." + ::= { rip2IfStatEntry 2 } + + rip2IfStatRcvBadRoutes OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of routes, in valid RIP packets, + which were ignored for any reason (e.g. unknown + address family, or invalid metric)." + ::= { rip2IfStatEntry 3 } + + rip2IfStatSentUpdates OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of triggered RIP updates actually + sent on this interface. This explicitly does + NOT include full updates sent containing new + information." + ::= { rip2IfStatEntry 4 } + + rip2IfStatStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Writing invalid has the effect of deleting + this interface." + ::= { rip2IfStatEntry 5 } + +-- The RIP Interface Configuration Table. + + rip2IfConfTable OBJECT-TYPE + SYNTAX SEQUENCE OF Rip2IfConfEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of subnets which require separate + configuration in RIP." + ::= { rip2 3 } + + rip2IfConfEntry OBJECT-TYPE + SYNTAX Rip2IfConfEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A Single Routing Domain in a single Subnet." + INDEX { rip2IfConfAddress } + ::= { rip2IfConfTable 1 } + + Rip2IfConfEntry ::= + SEQUENCE { + rip2IfConfAddress + IpAddress, + rip2IfConfDomain + RouteTag, + rip2IfConfAuthType + INTEGER, + rip2IfConfAuthKey + OCTET STRING (SIZE(0..16)), + rip2IfConfSend + INTEGER, + rip2IfConfReceive + INTEGER, + rip2IfConfDefaultMetric + INTEGER, + rip2IfConfStatus + RowStatus, + rip2IfConfSrcAddress + IpAddress + } + + rip2IfConfAddress OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The IP Address of this system on the indicated + subnet. For unnumbered interfaces, the value 0.0.0.N, + where the least significant 24 bits (N) is the ifIndex + for the IP Interface in network byte order." + ::= { rip2IfConfEntry 1 } + + rip2IfConfDomain OBJECT-TYPE + SYNTAX RouteTag + MAX-ACCESS read-create + STATUS obsolete + DESCRIPTION + "Value inserted into the Routing Domain field + of all RIP packets sent on this interface." + DEFVAL { '0000'h } + ::= { rip2IfConfEntry 2 } + + rip2IfConfAuthType OBJECT-TYPE + SYNTAX INTEGER { + noAuthentication (1), + simplePassword (2), + md5 (3) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The type of Authentication used on this + interface." + DEFVAL { noAuthentication } + ::= { rip2IfConfEntry 3 } + + rip2IfConfAuthKey OBJECT-TYPE + SYNTAX OCTET STRING (SIZE(0..16)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value to be used as the Authentication Key + whenever the corresponding instance of + rip2IfConfAuthType has a value other than + noAuthentication. A modification of the corresponding + instance of rip2IfConfAuthType does not modify + the rip2IfConfAuthKey value. If a string shorter + than 16 octets is supplied, it will be left- + justified and padded to 16 octets, on the right, + with nulls (0x00). + + Reading this object always results in an OCTET + STRING of length zero; authentication may not + be bypassed by reading the MIB object." + DEFVAL { ''h } + ::= { rip2IfConfEntry 4 } + + rip2IfConfSend OBJECT-TYPE + SYNTAX INTEGER { + doNotSend (1), + ripVersion1 (2), + rip1Compatible (3), + ripVersion2 (4), + ripV1Demand (5), + ripV2Demand (6) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "What the router sends on this interface. + ripVersion1 implies sending RIP updates compliant + with RFC 1058. rip1Compatible implies + broadcasting RIP-2 updates using RFC 1058 route + subsumption rules. ripVersion2 implies + multicasting RIP-2 updates. ripV1Demand indicates + the use of Demand RIP on a WAN interface under RIP + Version 1 rules. ripV2Demand indicates the use of + Demand RIP on a WAN interface under Version 2 rules." + DEFVAL { rip1Compatible } + ::= { rip2IfConfEntry 5 } + + rip2IfConfReceive OBJECT-TYPE + SYNTAX INTEGER { + rip1 (1), + rip2 (2), + rip1OrRip2 (3), + doNotRecieve (4) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This indicates which version of RIP updates + are to be accepted. Note that rip2 and + rip1OrRip2 implies reception of multicast + packets." + DEFVAL { rip1OrRip2 } + ::= { rip2IfConfEntry 6 } + + rip2IfConfDefaultMetric OBJECT-TYPE + SYNTAX INTEGER ( 0..15 ) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This variable indicates the metric that is to + be used for the default route entry in RIP updates + originated on this interface. A value of zero + indicates that no default route should be + originated; in this case, a default route via + another router may be propagated." + ::= { rip2IfConfEntry 7 } + + rip2IfConfStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Writing invalid has the effect of deleting + this interface." + ::= { rip2IfConfEntry 8 } + + rip2IfConfSrcAddress OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The IP Address this system will use as a source + address on this interface. If it is a numbered + interface, this MUST be the same value as + rip2IfConfAddress. On unnumbered interfaces, + it must be the value of rip2IfConfAddress for + some interface on the system." + ::= { rip2IfConfEntry 9 } + +--4.3 Peer Table + +-- Peer Table + +-- The RIP Peer Group +-- Implementation of this Group is Optional + +-- This group provides information about active peer +-- relationships intended to assist in debugging. An +-- active peer is a router from which a valid RIP +-- updated has been heard in the last 180 seconds. + + rip2PeerTable OBJECT-TYPE + SYNTAX SEQUENCE OF Rip2PeerEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of RIP Peers." + ::= { rip2 4 } + + rip2PeerEntry OBJECT-TYPE + SYNTAX Rip2PeerEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information regarding a single routing peer." + INDEX { rip2PeerAddress, rip2PeerDomain } + ::= { rip2PeerTable 1 } + + Rip2PeerEntry ::= + SEQUENCE { + rip2PeerAddress + IpAddress, + rip2PeerDomain + RouteTag, + rip2PeerLastUpdate + TimeTicks, + rip2PeerVersion + INTEGER, + rip2PeerRcvBadPackets + Counter32, + rip2PeerRcvBadRoutes + Counter32 + } + + rip2PeerAddress OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The IP Address that the peer is using as its source + address. Note that on an unnumbered link, this may + not be a member of any subnet on the system." + ::= { rip2PeerEntry 1 } + + rip2PeerDomain OBJECT-TYPE + SYNTAX RouteTag + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value in the Routing Domain field in RIP + packets received from the peer. As domain suuport + is deprecated, this must be zero." + ::= { rip2PeerEntry 2 } + + rip2PeerLastUpdate OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime when the most recent + RIP update was received from this system." + ::= { rip2PeerEntry 3 } + + rip2PeerVersion OBJECT-TYPE + SYNTAX INTEGER ( 0..255 ) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The RIP version number in the header of the + last RIP packet received." + ::= { rip2PeerEntry 4 } + + rip2PeerRcvBadPackets OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of RIP response packets from this + peer discarded as invalid." + ::= { rip2PeerEntry 5 } + + + rip2PeerRcvBadRoutes OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of routes from this peer that were + ignored because the entry format was invalid." + ::= { rip2PeerEntry 6 } + +-- conformance information + +rip2Conformance OBJECT IDENTIFIER ::= { rip2 5 } + +rip2Groups OBJECT IDENTIFIER ::= { rip2Conformance 1 } +rip2Compliances OBJECT IDENTIFIER ::= { rip2Conformance 2 } + +-- compliance statements +rip2Compliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement " + MODULE -- this module + MANDATORY-GROUPS { + rip2GlobalGroup, + rip2IfStatGroup, + rip2IfConfGroup, + rip2PeerGroup + } + GROUP rip2GlobalGroup + DESCRIPTION + "This group defines global controls for RIP-II systems." + GROUP rip2IfStatGroup + DESCRIPTION + "This group defines interface statistics for RIP-II systems." + GROUP rip2IfConfGroup + DESCRIPTION + "This group defines interface configuration for RIP-II systems." + GROUP rip2PeerGroup + DESCRIPTION + "This group defines peer information for RIP-II systems." + ::= { rip2Compliances 1 } + +-- units of conformance + +rip2GlobalGroup OBJECT-GROUP + OBJECTS { + rip2GlobalRouteChanges, + rip2GlobalQueries + } + STATUS current + DESCRIPTION + "This group defines global controls for RIP-II systems." + ::= { rip2Groups 1 } +rip2IfStatGroup OBJECT-GROUP + OBJECTS { + rip2IfStatAddress, + rip2IfStatRcvBadPackets, + rip2IfStatRcvBadRoutes, + rip2IfStatSentUpdates, + rip2IfStatStatus + } + STATUS current + DESCRIPTION + "This group defines interface statistics for RIP-II systems." + ::= { rip2Groups 2 } +rip2IfConfGroup OBJECT-GROUP + OBJECTS { + rip2IfConfAddress, + rip2IfConfAuthType, + rip2IfConfAuthKey, + rip2IfConfSend, + rip2IfConfReceive, + rip2IfConfDefaultMetric, + rip2IfConfStatus, + rip2IfConfSrcAddress + } + STATUS current + DESCRIPTION + "This group defines interface configuration for RIP-II systems." + ::= { rip2Groups 3 } +rip2PeerGroup OBJECT-GROUP + OBJECTS { + rip2PeerAddress, + rip2PeerDomain, + rip2PeerLastUpdate, + rip2PeerVersion, + rip2PeerRcvBadPackets, + rip2PeerRcvBadRoutes + } + STATUS current + DESCRIPTION + "This group defines peer information for RIP-II systems." + ::= { rip2Groups 4 } +END diff --git a/mibs/RMON-MIB.txt b/mibs/RMON-MIB.txt new file mode 100644 index 0000000..983c22e --- /dev/null +++ b/mibs/RMON-MIB.txt @@ -0,0 +1,3980 @@ +RMON-MIB DEFINITIONS ::= BEGIN + + IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, OBJECT-IDENTITY, + NOTIFICATION-TYPE, mib-2, Counter32, + Integer32, TimeTicks FROM SNMPv2-SMI + + TEXTUAL-CONVENTION, DisplayString FROM SNMPv2-TC + + MODULE-COMPLIANCE, OBJECT-GROUP, + NOTIFICATION-GROUP FROM SNMPv2-CONF; + +-- Remote Network Monitoring MIB + +rmonMibModule MODULE-IDENTITY + LAST-UPDATED "200005110000Z" -- 11 May, 2000 + ORGANIZATION "IETF RMON MIB Working Group" + CONTACT-INFO + "Steve Waldbusser + Phone: +1-650-948-6500 + Fax: +1-650-745-0671 + Email: waldbusser@nextbeacon.com" + DESCRIPTION + "Remote network monitoring devices, often called + monitors or probes, are instruments that exist for + the purpose of managing a network. This MIB defines + objects for managing remote network monitoring devices." + + REVISION "200005110000Z" -- 11 May, 2000 + DESCRIPTION + "Reformatted into SMIv2 format. + + This version published as RFC 2819." + + REVISION "199502010000Z" -- 1 Feb, 1995 + DESCRIPTION + "Bug fixes, clarifications and minor changes based on + implementation experience, published as RFC1757 [18]. + + Two changes were made to object definitions: + + 1) A new status bit has been defined for the + captureBufferPacketStatus object, indicating that the + packet order within the capture buffer may not be identical to + the packet order as received off the wire. This bit may only + + be used for packets transmitted by the probe. Older NMS + applications can safely ignore this status bit, which might be + used by newer agents. + + 2) The packetMatch trap has been removed. This trap was never + actually 'approved' and was not added to this document along + with the risingAlarm and fallingAlarm traps. The packetMatch + trap could not be throttled, which could cause disruption of + normal network traffic under some circumstances. An NMS should + configure a risingAlarm threshold on the appropriate + channelMatches instance if a trap is desired for a packetMatch + event. Note that logging of packetMatch events is still + supported--only trap generation for such events has been + removed. + + In addition, several clarifications to individual object + definitions have been added to assist agent and NMS + implementors: + + - global definition of 'good packets' and 'bad packets' + + - more detailed text governing conceptual row creation and + modification + + - instructions for probes relating to interface changes and + disruptions + + - clarification of some ethernet counter definitions + + - recommended formula for calculating network utilization + + - clarification of channel and captureBuffer behavior for some + unusual conditions + + - examples of proper instance naming for each table" + + REVISION "199111010000Z" -- 1 Nov, 1991 + DESCRIPTION + "The original version of this MIB, published as RFC1271." + ::= { rmonConformance 8 } + + rmon OBJECT IDENTIFIER ::= { mib-2 16 } + + -- textual conventions + +OwnerString ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "This data type is used to model an administratively + assigned name of the owner of a resource. Implementations + must accept values composed of well-formed NVT ASCII + sequences. In addition, implementations should accept + values composed of well-formed UTF-8 sequences. + + It is suggested that this name contain one or more of + the following: IP address, management station name, + network manager's name, location, or phone number. + In some cases the agent itself will be the owner of + an entry. In these cases, this string shall be set + to a string starting with 'monitor'. + + SNMP access control is articulated entirely in terms + of the contents of MIB views; access to a particular + SNMP object instance depends only upon its presence + or absence in a particular MIB view and never upon + its value or the value of related object instances. + Thus, objects of this type afford resolution of + resource contention only among cooperating + managers; they realize no access control function + with respect to uncooperative parties." + SYNTAX OCTET STRING (SIZE (0..127)) + +EntryStatus ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The status of a table entry. + + Setting this object to the value invalid(4) has the + effect of invalidating the corresponding entry. + That is, it effectively disassociates the mapping + identified with said entry. + It is an implementation-specific matter as to whether + the agent removes an invalidated entry from the table. + Accordingly, management stations must be prepared to + receive tabular information from agents that corresponds + to entries currently not in use. Proper + interpretation of such entries requires examination + of the relevant EntryStatus object. + + An existing instance of this object cannot be set to + createRequest(2). This object may only be set to + createRequest(2) when this instance is created. When + this object is created, the agent may wish to create + supplemental object instances with default values + to complete a conceptual row in this table. Because the + + creation of these default objects is entirely at the option + of the agent, the manager must not assume that any will be + created, but may make use of any that are created. + Immediately after completing the create operation, the agent + must set this object to underCreation(3). + + When in the underCreation(3) state, an entry is allowed to + exist in a possibly incomplete, possibly inconsistent state, + usually to allow it to be modified in multiple PDUs. When in + this state, an entry is not fully active. + Entries shall exist in the underCreation(3) state until + the management station is finished configuring the entry + and sets this object to valid(1) or aborts, setting this + object to invalid(4). If the agent determines that an + entry has been in the underCreation(3) state for an + abnormally long time, it may decide that the management + station has crashed. If the agent makes this decision, + it may set this object to invalid(4) to reclaim the + entry. A prudent agent will understand that the + management station may need to wait for human input + and will allow for that possibility in its + determination of this abnormally long period. + + An entry in the valid(1) state is fully configured and + consistent and fully represents the configuration or + operation such a row is intended to represent. For + example, it could be a statistical function that is + configured and active, or a filter that is available + in the list of filters processed by the packet capture + process. + + A manager is restricted to changing the state of an entry in + the following ways: + + To: valid createRequest underCreation invalid + From: + valid OK NO OK OK + createRequest N/A N/A N/A N/A + underCreation OK NO OK OK + invalid NO NO NO OK + nonExistent NO OK NO OK + + In the table above, it is not applicable to move the state + from the createRequest state to any other state because the + manager will never find the variable in that state. The + nonExistent state is not a value of the enumeration, rather + it means that the entryStatus variable does not exist at all. + + An agent may allow an entryStatus variable to change state in + additional ways, so long as the semantics of the states are + followed. This allowance is made to ease the implementation of + the agent and is made despite the fact that managers should + never exercise these additional state transitions." + SYNTAX INTEGER { + valid(1), + createRequest(2), + underCreation(3), + invalid(4) + } + + statistics OBJECT IDENTIFIER ::= { rmon 1 } + history OBJECT IDENTIFIER ::= { rmon 2 } + alarm OBJECT IDENTIFIER ::= { rmon 3 } + hosts OBJECT IDENTIFIER ::= { rmon 4 } + hostTopN OBJECT IDENTIFIER ::= { rmon 5 } + matrix OBJECT IDENTIFIER ::= { rmon 6 } + filter OBJECT IDENTIFIER ::= { rmon 7 } + capture OBJECT IDENTIFIER ::= { rmon 8 } + event OBJECT IDENTIFIER ::= { rmon 9 } + rmonConformance OBJECT IDENTIFIER ::= { rmon 20 } + +-- The Ethernet Statistics Group +-- +-- Implementation of the Ethernet Statistics group is optional. +-- Consult the MODULE-COMPLIANCE macro for the authoritative +-- conformance information for this MIB. +-- +-- The ethernet statistics group contains statistics measured by the +-- probe for each monitored interface on this device. These +-- statistics take the form of free running counters that start from +-- zero when a valid entry is created. +-- +-- This group currently has statistics defined only for +-- Ethernet interfaces. Each etherStatsEntry contains statistics +-- for one Ethernet interface. The probe must create one +-- etherStats entry for each monitored Ethernet interface +-- on the device. + +etherStatsTable OBJECT-TYPE + SYNTAX SEQUENCE OF EtherStatsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of Ethernet statistics entries." + ::= { statistics 1 } + +etherStatsEntry OBJECT-TYPE + SYNTAX EtherStatsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A collection of statistics kept for a particular + Ethernet interface. As an example, an instance of the + etherStatsPkts object might be named etherStatsPkts.1" + INDEX { etherStatsIndex } + ::= { etherStatsTable 1 } + +EtherStatsEntry ::= SEQUENCE { + etherStatsIndex Integer32, + etherStatsDataSource OBJECT IDENTIFIER, + etherStatsDropEvents Counter32, + etherStatsOctets Counter32, + etherStatsPkts Counter32, + etherStatsBroadcastPkts Counter32, + etherStatsMulticastPkts Counter32, + etherStatsCRCAlignErrors Counter32, + etherStatsUndersizePkts Counter32, + etherStatsOversizePkts Counter32, + etherStatsFragments Counter32, + etherStatsJabbers Counter32, + etherStatsCollisions Counter32, + etherStatsPkts64Octets Counter32, + etherStatsPkts65to127Octets Counter32, + etherStatsPkts128to255Octets Counter32, + etherStatsPkts256to511Octets Counter32, + etherStatsPkts512to1023Octets Counter32, + etherStatsPkts1024to1518Octets Counter32, + etherStatsOwner OwnerString, + etherStatsStatus EntryStatus +} + +etherStatsIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of this object uniquely identifies this + etherStats entry." + ::= { etherStatsEntry 1 } + +etherStatsDataSource OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object identifies the source of the data that + this etherStats entry is configured to analyze. This + source can be any ethernet interface on this device. + In order to identify a particular interface, this object + shall identify the instance of the ifIndex object, + defined in RFC 2233 [17], for the desired interface. + For example, if an entry were to receive data from + interface #1, this object would be set to ifIndex.1. + + The statistics in this group reflect all packets + on the local network segment attached to the identified + interface. + + An agent may or may not be able to tell if fundamental + changes to the media of the interface have occurred and + necessitate an invalidation of this entry. For example, a + hot-pluggable ethernet card could be pulled out and replaced + by a token-ring card. In such a case, if the agent has such + knowledge of the change, it is recommended that it + invalidate this entry. + + This object may not be modified if the associated + etherStatsStatus object is equal to valid(1)." + ::= { etherStatsEntry 2 } + +etherStatsDropEvents OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of events in which packets + were dropped by the probe due to lack of resources. + Note that this number is not necessarily the number of + packets dropped; it is just the number of times this + condition has been detected." + ::= { etherStatsEntry 3 } + +etherStatsOctets OBJECT-TYPE + SYNTAX Counter32 + UNITS "Octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets of data (including + those in bad packets) received on the + network (excluding framing bits but including + FCS octets). + + This object can be used as a reasonable estimate of + 10-Megabit ethernet utilization. If greater precision is + desired, the etherStatsPkts and etherStatsOctets objects + should be sampled before and after a common interval. The + differences in the sampled values are Pkts and Octets, + respectively, and the number of seconds in the interval is + Interval. These values are used to calculate the Utilization + as follows: + + Pkts * (9.6 + 6.4) + (Octets * .8) + Utilization = ------------------------------------- + Interval * 10,000 + + The result of this equation is the value Utilization which + is the percent utilization of the ethernet segment on a + scale of 0 to 100 percent." + ::= { etherStatsEntry 4 } + +etherStatsPkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets (including bad packets, + broadcast packets, and multicast packets) received." + ::= { etherStatsEntry 5 } + +etherStatsBroadcastPkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of good packets received that were + directed to the broadcast address. Note that this + does not include multicast packets." + ::= { etherStatsEntry 6 } + +etherStatsMulticastPkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of good packets received that were + directed to a multicast address. Note that this number + does not include packets directed to the broadcast + + address." + ::= { etherStatsEntry 7 } + +etherStatsCRCAlignErrors OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets received that + had a length (excluding framing bits, but + including FCS octets) of between 64 and 1518 + octets, inclusive, but had either a bad + Frame Check Sequence (FCS) with an integral + number of octets (FCS Error) or a bad FCS with + a non-integral number of octets (Alignment Error)." + ::= { etherStatsEntry 8 } + +etherStatsUndersizePkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets received that were + less than 64 octets long (excluding framing bits, + but including FCS octets) and were otherwise well + formed." + ::= { etherStatsEntry 9 } + +etherStatsOversizePkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets received that were + longer than 1518 octets (excluding framing bits, + but including FCS octets) and were otherwise + well formed." + ::= { etherStatsEntry 10 } + +etherStatsFragments OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets received that were less than + 64 octets in length (excluding framing bits but including + FCS octets) and had either a bad Frame Check Sequence + (FCS) with an integral number of octets (FCS Error) or a + bad FCS with a non-integral number of octets (Alignment + Error). + + Note that it is entirely normal for etherStatsFragments to + increment. This is because it counts both runts (which are + normal occurrences due to collisions) and noise hits." + ::= { etherStatsEntry 11 } + +etherStatsJabbers OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets received that were + longer than 1518 octets (excluding framing bits, + but including FCS octets), and had either a bad + Frame Check Sequence (FCS) with an integral number + of octets (FCS Error) or a bad FCS with a non-integral + number of octets (Alignment Error). + + Note that this definition of jabber is different + than the definition in IEEE-802.3 section 8.2.1.5 + (10BASE5) and section 10.3.1.4 (10BASE2). These + documents define jabber as the condition where any + packet exceeds 20 ms. The allowed range to detect + jabber is between 20 ms and 150 ms." + ::= { etherStatsEntry 12 } + +etherStatsCollisions OBJECT-TYPE + SYNTAX Counter32 + UNITS "Collisions" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The best estimate of the total number of collisions + on this Ethernet segment. + + The value returned will depend on the location of the + RMON probe. Section 8.2.1.3 (10BASE-5) and section + 10.3.1.3 (10BASE-2) of IEEE standard 802.3 states that a + station must detect a collision, in the receive mode, if + three or more stations are transmitting simultaneously. A + repeater port must detect a collision when two or more + + stations are transmitting simultaneously. Thus a probe + placed on a repeater port could record more collisions + than a probe connected to a station on the same segment + would. + + Probe location plays a much smaller role when considering + 10BASE-T. 14.2.1.4 (10BASE-T) of IEEE standard 802.3 + defines a collision as the simultaneous presence of signals + on the DO and RD circuits (transmitting and receiving + at the same time). A 10BASE-T station can only detect + collisions when it is transmitting. Thus probes placed on + a station and a repeater, should report the same number of + collisions. + + Note also that an RMON probe inside a repeater should + ideally report collisions between the repeater and one or + more other hosts (transmit collisions as defined by IEEE + 802.3k) plus receiver collisions observed on any coax + segments to which the repeater is connected." + ::= { etherStatsEntry 13 } + +etherStatsPkts64Octets OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets (including bad + packets) received that were 64 octets in length + (excluding framing bits but including FCS octets)." + ::= { etherStatsEntry 14 } + +etherStatsPkts65to127Octets OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets (including bad + packets) received that were between + 65 and 127 octets in length inclusive + (excluding framing bits but including FCS octets)." + ::= { etherStatsEntry 15 } + +etherStatsPkts128to255Octets OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets (including bad + packets) received that were between + 128 and 255 octets in length inclusive + (excluding framing bits but including FCS octets)." + ::= { etherStatsEntry 16 } + +etherStatsPkts256to511Octets OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets (including bad + packets) received that were between + 256 and 511 octets in length inclusive + (excluding framing bits but including FCS octets)." + ::= { etherStatsEntry 17 } + +etherStatsPkts512to1023Octets OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets (including bad + packets) received that were between + 512 and 1023 octets in length inclusive + (excluding framing bits but including FCS octets)." + ::= { etherStatsEntry 18 } + +etherStatsPkts1024to1518Octets OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets (including bad + packets) received that were between + 1024 and 1518 octets in length inclusive + (excluding framing bits but including FCS octets)." + ::= { etherStatsEntry 19 } + +etherStatsOwner OBJECT-TYPE + SYNTAX OwnerString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The entity that configured this entry and is therefore + using the resources assigned to it." + ::= { etherStatsEntry 20 } + +etherStatsStatus OBJECT-TYPE + SYNTAX EntryStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this etherStats entry." + ::= { etherStatsEntry 21 } + +-- The History Control Group + +-- Implementation of the History Control group is optional. +-- Consult the MODULE-COMPLIANCE macro for the authoritative +-- conformance information for this MIB. +-- +-- The history control group controls the periodic statistical +-- sampling of data from various types of networks. The +-- historyControlTable stores configuration entries that each +-- define an interface, polling period, and other parameters. +-- Once samples are taken, their data is stored in an entry +-- in a media-specific table. Each such entry defines one +-- sample, and is associated with the historyControlEntry that +-- caused the sample to be taken. Each counter in the +-- etherHistoryEntry counts the same event as its similarly-named +-- counterpart in the etherStatsEntry, except that each value here +-- is a cumulative sum during a sampling period. +-- +-- If the probe keeps track of the time of day, it should start +-- the first sample of the history at a time such that +-- when the next hour of the day begins, a sample is +-- started at that instant. This tends to make more +-- user-friendly reports, and enables comparison of reports +-- from different probes that have relatively accurate time +-- of day. +-- +-- The probe is encouraged to add two history control entries +-- per monitored interface upon initialization that describe a short +-- term and a long term polling period. Suggested parameters are 30 +-- seconds for the short term polling period and 30 minutes for +-- the long term period. + +historyControlTable OBJECT-TYPE + SYNTAX SEQUENCE OF HistoryControlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of history control entries." + ::= { history 1 } + +historyControlEntry OBJECT-TYPE + SYNTAX HistoryControlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of parameters that set up a periodic sampling of + statistics. As an example, an instance of the + historyControlInterval object might be named + historyControlInterval.2" + INDEX { historyControlIndex } + ::= { historyControlTable 1 } + +HistoryControlEntry ::= SEQUENCE { + historyControlIndex Integer32, + historyControlDataSource OBJECT IDENTIFIER, + historyControlBucketsRequested Integer32, + historyControlBucketsGranted Integer32, + historyControlInterval Integer32, + historyControlOwner OwnerString, + historyControlStatus EntryStatus +} + +historyControlIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index that uniquely identifies an entry in the + historyControl table. Each such entry defines a + set of samples at a particular interval for an + interface on the device." + ::= { historyControlEntry 1 } + +historyControlDataSource OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object identifies the source of the data for + which historical data was collected and + placed in a media-specific table on behalf of this + historyControlEntry. This source can be any + interface on this device. In order to identify + + a particular interface, this object shall identify + the instance of the ifIndex object, defined + in RFC 2233 [17], for the desired interface. + For example, if an entry were to receive data from + interface #1, this object would be set to ifIndex.1. + + The statistics in this group reflect all packets + on the local network segment attached to the identified + interface. + + An agent may or may not be able to tell if fundamental + changes to the media of the interface have occurred and + necessitate an invalidation of this entry. For example, a + hot-pluggable ethernet card could be pulled out and replaced + by a token-ring card. In such a case, if the agent has such + knowledge of the change, it is recommended that it + invalidate this entry. + + This object may not be modified if the associated + historyControlStatus object is equal to valid(1)." + ::= { historyControlEntry 2 } + +historyControlBucketsRequested OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The requested number of discrete time intervals + over which data is to be saved in the part of the + media-specific table associated with this + historyControlEntry. + + When this object is created or modified, the probe + should set historyControlBucketsGranted as closely to + this object as is possible for the particular probe + implementation and available resources." + DEFVAL { 50 } + ::= { historyControlEntry 3 } + +historyControlBucketsGranted OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of discrete sampling intervals + over which data shall be saved in the part of + the media-specific table associated with this + historyControlEntry. + + When the associated historyControlBucketsRequested + object is created or modified, the probe + should set this object as closely to the requested + value as is possible for the particular + probe implementation and available resources. The + probe must not lower this value except as a result + of a modification to the associated + historyControlBucketsRequested object. + + There will be times when the actual number of + buckets associated with this entry is less than + the value of this object. In this case, at the + end of each sampling interval, a new bucket will + be added to the media-specific table. + + When the number of buckets reaches the value of + this object and a new bucket is to be added to the + media-specific table, the oldest bucket associated + with this historyControlEntry shall be deleted by + the agent so that the new bucket can be added. + + When the value of this object changes to a value less + than the current value, entries are deleted + from the media-specific table associated with this + historyControlEntry. Enough of the oldest of these + entries shall be deleted by the agent so that their + number remains less than or equal to the new value of + this object. + + When the value of this object changes to a value greater + than the current value, the number of associated media- + specific entries may be allowed to grow." + ::= { historyControlEntry 4 } + +historyControlInterval OBJECT-TYPE + SYNTAX Integer32 (1..3600) + UNITS "Seconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The interval in seconds over which the data is + sampled for each bucket in the part of the + media-specific table associated with this + historyControlEntry. This interval can + be set to any number of seconds between 1 and + 3600 (1 hour). + + Because the counters in a bucket may overflow at their + + maximum value with no indication, a prudent manager will + take into account the possibility of overflow in any of + the associated counters. It is important to consider the + minimum time in which any counter could overflow on a + particular media type and set the historyControlInterval + object to a value less than this interval. This is + typically most important for the 'octets' counter in any + media-specific table. For example, on an Ethernet + network, the etherHistoryOctets counter could overflow + in about one hour at the Ethernet's maximum + utilization. + + This object may not be modified if the associated + historyControlStatus object is equal to valid(1)." + DEFVAL { 1800 } + ::= { historyControlEntry 5 } + +historyControlOwner OBJECT-TYPE + SYNTAX OwnerString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The entity that configured this entry and is therefore + using the resources assigned to it." + ::= { historyControlEntry 6 } + +historyControlStatus OBJECT-TYPE + SYNTAX EntryStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this historyControl entry. + + Each instance of the media-specific table associated + with this historyControlEntry will be deleted by the agent + if this historyControlEntry is not equal to valid(1)." + ::= { historyControlEntry 7 } + +-- The Ethernet History Group + +-- Implementation of the Ethernet History group is optional. +-- Consult the MODULE-COMPLIANCE macro for the authoritative +-- conformance information for this MIB. +-- +-- The Ethernet History group records periodic statistical samples +-- from a network and stores them for later retrieval. +-- Once samples are taken, their data is stored in an entry +-- in a media-specific table. Each such entry defines one + +-- sample, and is associated with the historyControlEntry that +-- caused the sample to be taken. This group defines the +-- etherHistoryTable, for Ethernet networks. +-- + +etherHistoryTable OBJECT-TYPE + SYNTAX SEQUENCE OF EtherHistoryEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of Ethernet history entries." + ::= { history 2 } + +etherHistoryEntry OBJECT-TYPE + SYNTAX EtherHistoryEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An historical sample of Ethernet statistics on a particular + Ethernet interface. This sample is associated with the + historyControlEntry which set up the parameters for + a regular collection of these samples. As an example, an + instance of the etherHistoryPkts object might be named + etherHistoryPkts.2.89" + INDEX { etherHistoryIndex , etherHistorySampleIndex } + ::= { etherHistoryTable 1 } + +EtherHistoryEntry ::= SEQUENCE { + etherHistoryIndex Integer32, + etherHistorySampleIndex Integer32, + etherHistoryIntervalStart TimeTicks, + etherHistoryDropEvents Counter32, + etherHistoryOctets Counter32, + etherHistoryPkts Counter32, + etherHistoryBroadcastPkts Counter32, + etherHistoryMulticastPkts Counter32, + etherHistoryCRCAlignErrors Counter32, + etherHistoryUndersizePkts Counter32, + etherHistoryOversizePkts Counter32, + etherHistoryFragments Counter32, + etherHistoryJabbers Counter32, + etherHistoryCollisions Counter32, + etherHistoryUtilization Integer32 +} + +etherHistoryIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The history of which this entry is a part. The + history identified by a particular value of this + index is the same history as identified + by the same value of historyControlIndex." + ::= { etherHistoryEntry 1 } + +etherHistorySampleIndex OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index that uniquely identifies the particular + sample this entry represents among all samples + associated with the same historyControlEntry. + This index starts at 1 and increases by one + as each new sample is taken." + ::= { etherHistoryEntry 2 } + +etherHistoryIntervalStart OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the start of the interval + over which this sample was measured. If the probe + keeps track of the time of day, it should start + the first sample of the history at a time such that + when the next hour of the day begins, a sample is + started at that instant. Note that following this + rule may require the probe to delay collecting the + first sample of the history, as each sample must be + of the same interval. Also note that the sample which + is currently being collected is not accessible in this + table until the end of its interval." + ::= { etherHistoryEntry 3 } + +etherHistoryDropEvents OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of events in which packets + were dropped by the probe due to lack of resources + during this sampling interval. Note that this number + is not necessarily the number of packets dropped, it + is just the number of times this condition has been + + detected." + ::= { etherHistoryEntry 4 } + +etherHistoryOctets OBJECT-TYPE + SYNTAX Counter32 + UNITS "Octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of octets of data (including + those in bad packets) received on the + network (excluding framing bits but including + FCS octets)." + ::= { etherHistoryEntry 5 } + +etherHistoryPkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of packets (including bad packets) + received during this sampling interval." + ::= { etherHistoryEntry 6 } + +etherHistoryBroadcastPkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of good packets received during this + sampling interval that were directed to the + broadcast address." + ::= { etherHistoryEntry 7 } + +etherHistoryMulticastPkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of good packets received during this + sampling interval that were directed to a + multicast address. Note that this number does not + include packets addressed to the broadcast address." + ::= { etherHistoryEntry 8 } + +etherHistoryCRCAlignErrors OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of packets received during this + sampling interval that had a length (excluding + framing bits but including FCS octets) between + 64 and 1518 octets, inclusive, but had either a bad Frame + Check Sequence (FCS) with an integral number of octets + (FCS Error) or a bad FCS with a non-integral number + of octets (Alignment Error)." + ::= { etherHistoryEntry 9 } + +etherHistoryUndersizePkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of packets received during this + sampling interval that were less than 64 octets + long (excluding framing bits but including FCS + octets) and were otherwise well formed." + ::= { etherHistoryEntry 10 } + +etherHistoryOversizePkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of packets received during this + sampling interval that were longer than 1518 + octets (excluding framing bits but including + FCS octets) but were otherwise well formed." + ::= { etherHistoryEntry 11 } + +etherHistoryFragments OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets received during this + sampling interval that were less than 64 octets in + length (excluding framing bits but including FCS + + octets) had either a bad Frame Check Sequence (FCS) + with an integral number of octets (FCS Error) or a bad + FCS with a non-integral number of octets (Alignment + Error). + + Note that it is entirely normal for etherHistoryFragments to + increment. This is because it counts both runts (which are + normal occurrences due to collisions) and noise hits." + ::= { etherHistoryEntry 12 } + +etherHistoryJabbers OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of packets received during this + sampling interval that were longer than 1518 octets + (excluding framing bits but including FCS octets), + and had either a bad Frame Check Sequence (FCS) + with an integral number of octets (FCS Error) or + a bad FCS with a non-integral number of octets + (Alignment Error). + + Note that this definition of jabber is different + than the definition in IEEE-802.3 section 8.2.1.5 + (10BASE5) and section 10.3.1.4 (10BASE2). These + documents define jabber as the condition where any + packet exceeds 20 ms. The allowed range to detect + jabber is between 20 ms and 150 ms." + ::= { etherHistoryEntry 13 } + +etherHistoryCollisions OBJECT-TYPE + SYNTAX Counter32 + UNITS "Collisions" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The best estimate of the total number of collisions + on this Ethernet segment during this sampling + interval. + + The value returned will depend on the location of the + RMON probe. Section 8.2.1.3 (10BASE-5) and section + 10.3.1.3 (10BASE-2) of IEEE standard 802.3 states that a + station must detect a collision, in the receive mode, if + three or more stations are transmitting simultaneously. A + repeater port must detect a collision when two or more + + stations are transmitting simultaneously. Thus a probe + placed on a repeater port could record more collisions + than a probe connected to a station on the same segment + would. + + Probe location plays a much smaller role when considering + 10BASE-T. 14.2.1.4 (10BASE-T) of IEEE standard 802.3 + defines a collision as the simultaneous presence of signals + on the DO and RD circuits (transmitting and receiving + at the same time). A 10BASE-T station can only detect + collisions when it is transmitting. Thus probes placed on + a station and a repeater, should report the same number of + collisions. + + Note also that an RMON probe inside a repeater should + ideally report collisions between the repeater and one or + more other hosts (transmit collisions as defined by IEEE + 802.3k) plus receiver collisions observed on any coax + segments to which the repeater is connected." + ::= { etherHistoryEntry 14 } + +etherHistoryUtilization OBJECT-TYPE + SYNTAX Integer32 (0..10000) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The best estimate of the mean physical layer + network utilization on this interface during this + sampling interval, in hundredths of a percent." + ::= { etherHistoryEntry 15 } + +-- The Alarm Group + +-- Implementation of the Alarm group is optional. The Alarm Group +-- requires the implementation of the Event group. +-- Consult the MODULE-COMPLIANCE macro for the authoritative +-- conformance information for this MIB. +-- +-- The Alarm group periodically takes statistical samples from +-- variables in the probe and compares them to thresholds that have +-- been configured. The alarm table stores configuration +-- entries that each define a variable, polling period, and +-- threshold parameters. If a sample is found to cross the +-- threshold values, an event is generated. Only variables that +-- resolve to an ASN.1 primitive type of INTEGER (INTEGER, Integer32, +-- Counter32, Counter64, Gauge32, or TimeTicks) may be monitored in +-- this way. +-- + +-- This function has a hysteresis mechanism to limit the generation +-- of events. This mechanism generates one event as a threshold +-- is crossed in the appropriate direction. No more events are +-- generated for that threshold until the opposite threshold is +-- crossed. +-- +-- In the case of a sampling a deltaValue, a probe may implement +-- this mechanism with more precision if it takes a delta sample +-- twice per period, each time comparing the sum of the latest two +-- samples to the threshold. This allows the detection of threshold +-- crossings that span the sampling boundary. Note that this does +-- not require any special configuration of the threshold value. +-- It is suggested that probes implement this more precise algorithm. + +alarmTable OBJECT-TYPE + SYNTAX SEQUENCE OF AlarmEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of alarm entries." + ::= { alarm 1 } + +alarmEntry OBJECT-TYPE + SYNTAX AlarmEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of parameters that set up a periodic checking + for alarm conditions. For example, an instance of the + alarmValue object might be named alarmValue.8" + INDEX { alarmIndex } + ::= { alarmTable 1 } + +AlarmEntry ::= SEQUENCE { + alarmIndex Integer32, + alarmInterval Integer32, + alarmVariable OBJECT IDENTIFIER, + alarmSampleType INTEGER, + alarmValue Integer32, + alarmStartupAlarm INTEGER, + alarmRisingThreshold Integer32, + alarmFallingThreshold Integer32, + alarmRisingEventIndex Integer32, + alarmFallingEventIndex Integer32, + alarmOwner OwnerString, + alarmStatus EntryStatus +} + +alarmIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index that uniquely identifies an entry in the + alarm table. Each such entry defines a + diagnostic sample at a particular interval + for an object on the device." + ::= { alarmEntry 1 } + +alarmInterval OBJECT-TYPE + SYNTAX Integer32 + UNITS "Seconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The interval in seconds over which the data is + sampled and compared with the rising and falling + thresholds. When setting this variable, care + should be taken in the case of deltaValue + sampling - the interval should be set short enough + that the sampled variable is very unlikely to + increase or decrease by more than 2^31 - 1 during + a single sampling interval. + + This object may not be modified if the associated + alarmStatus object is equal to valid(1)." + ::= { alarmEntry 2 } + +alarmVariable OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The object identifier of the particular variable to be + sampled. Only variables that resolve to an ASN.1 primitive + type of INTEGER (INTEGER, Integer32, Counter32, Counter64, + Gauge, or TimeTicks) may be sampled. + + Because SNMP access control is articulated entirely + in terms of the contents of MIB views, no access + control mechanism exists that can restrict the value of + this object to identify only those objects that exist + in a particular MIB view. Because there is thus no + acceptable means of restricting the read access that + could be obtained through the alarm mechanism, the + probe must only grant write access to this object in + + those views that have read access to all objects on + the probe. + + During a set operation, if the supplied variable name is + not available in the selected MIB view, a badValue error + must be returned. If at any time the variable name of + an established alarmEntry is no longer available in the + selected MIB view, the probe must change the status of + this alarmEntry to invalid(4). + + This object may not be modified if the associated + alarmStatus object is equal to valid(1)." + ::= { alarmEntry 3 } + +alarmSampleType OBJECT-TYPE + SYNTAX INTEGER { + absoluteValue(1), + deltaValue(2) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The method of sampling the selected variable and + calculating the value to be compared against the + thresholds. If the value of this object is + absoluteValue(1), the value of the selected variable + will be compared directly with the thresholds at the + end of the sampling interval. If the value of this + object is deltaValue(2), the value of the selected + variable at the last sample will be subtracted from + the current value, and the difference compared with + the thresholds. + + This object may not be modified if the associated + alarmStatus object is equal to valid(1)." + ::= { alarmEntry 4 } + +alarmValue OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of the statistic during the last sampling + period. For example, if the sample type is deltaValue, + this value will be the difference between the samples + at the beginning and end of the period. If the sample + type is absoluteValue, this value will be the sampled + value at the end of the period. + + This is the value that is compared with the rising and + falling thresholds. + + The value during the current sampling period is not + made available until the period is completed and will + remain available until the next period completes." + ::= { alarmEntry 5 } + +alarmStartupAlarm OBJECT-TYPE + SYNTAX INTEGER { + risingAlarm(1), + fallingAlarm(2), + risingOrFallingAlarm(3) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The alarm that may be sent when this entry is first + set to valid. If the first sample after this entry + becomes valid is greater than or equal to the + risingThreshold and alarmStartupAlarm is equal to + risingAlarm(1) or risingOrFallingAlarm(3), then a single + rising alarm will be generated. If the first sample + after this entry becomes valid is less than or equal + to the fallingThreshold and alarmStartupAlarm is equal + to fallingAlarm(2) or risingOrFallingAlarm(3), then a + single falling alarm will be generated. + + This object may not be modified if the associated + alarmStatus object is equal to valid(1)." + ::= { alarmEntry 6 } + +alarmRisingThreshold OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A threshold for the sampled statistic. When the current + sampled value is greater than or equal to this threshold, + and the value at the last sampling interval was less than + this threshold, a single event will be generated. + A single event will also be generated if the first + sample after this entry becomes valid is greater than or + equal to this threshold and the associated + alarmStartupAlarm is equal to risingAlarm(1) or + risingOrFallingAlarm(3). + + After a rising event is generated, another such event + + will not be generated until the sampled value + falls below this threshold and reaches the + alarmFallingThreshold. + + This object may not be modified if the associated + alarmStatus object is equal to valid(1)." + ::= { alarmEntry 7 } + +alarmFallingThreshold OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A threshold for the sampled statistic. When the current + sampled value is less than or equal to this threshold, + and the value at the last sampling interval was greater than + this threshold, a single event will be generated. + A single event will also be generated if the first + sample after this entry becomes valid is less than or + equal to this threshold and the associated + alarmStartupAlarm is equal to fallingAlarm(2) or + risingOrFallingAlarm(3). + + After a falling event is generated, another such event + will not be generated until the sampled value + rises above this threshold and reaches the + alarmRisingThreshold. + + This object may not be modified if the associated + alarmStatus object is equal to valid(1)." + ::= { alarmEntry 8 } + +alarmRisingEventIndex OBJECT-TYPE + SYNTAX Integer32 (0..65535) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The index of the eventEntry that is + used when a rising threshold is crossed. The + eventEntry identified by a particular value of + this index is the same as identified by the same value + of the eventIndex object. If there is no + corresponding entry in the eventTable, then + no association exists. In particular, if this value + is zero, no associated event will be generated, as + zero is not a valid event index. + + This object may not be modified if the associated + + alarmStatus object is equal to valid(1)." + ::= { alarmEntry 9 } + +alarmFallingEventIndex OBJECT-TYPE + SYNTAX Integer32 (0..65535) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The index of the eventEntry that is + used when a falling threshold is crossed. The + eventEntry identified by a particular value of + this index is the same as identified by the same value + of the eventIndex object. If there is no + corresponding entry in the eventTable, then + no association exists. In particular, if this value + is zero, no associated event will be generated, as + zero is not a valid event index. + + This object may not be modified if the associated + alarmStatus object is equal to valid(1)." + ::= { alarmEntry 10 } + +alarmOwner OBJECT-TYPE + SYNTAX OwnerString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The entity that configured this entry and is therefore + using the resources assigned to it." + ::= { alarmEntry 11 } + +alarmStatus OBJECT-TYPE + SYNTAX EntryStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this alarm entry." + ::= { alarmEntry 12 } + +-- The Host Group + +-- Implementation of the Host group is optional. +-- Consult the MODULE-COMPLIANCE macro for the authoritative +-- conformance information for this MIB. +-- +-- The host group discovers new hosts on the network by +-- keeping a list of source and destination MAC Addresses seen +-- in good packets. For each of these addresses, the host group + +-- keeps a set of statistics. The hostControlTable controls +-- which interfaces this function is performed on, and contains +-- some information about the process. On behalf of each +-- hostControlEntry, data is collected on an interface and placed +-- in both the hostTable and the hostTimeTable. If the +-- monitoring device finds itself short of resources, it may +-- delete entries as needed. It is suggested that the device +-- delete the least recently used entries first. + +-- The hostTable contains entries for each address discovered on +-- a particular interface. Each entry contains statistical +-- data about that host. This table is indexed by the +-- MAC address of the host, through which a random access +-- may be achieved. + +-- The hostTimeTable contains data in the same format as the +-- hostTable, and must contain the same set of hosts, but is +-- indexed using hostTimeCreationOrder rather than hostAddress. +-- The hostTimeCreationOrder is an integer which reflects +-- the relative order in which a particular entry was discovered +-- and thus inserted into the table. As this order, and thus +-- the index, is among those entries currently in the table, +-- the index for a particular entry may change if an +-- (earlier) entry is deleted. Thus the association between +-- hostTimeCreationOrder and hostTimeEntry may be broken at +-- any time. + +-- The hostTimeTable has two important uses. The first is the +-- fast download of this potentially large table. Because the +-- index of this table runs from 1 to the size of the table, +-- inclusive, its values are predictable. This allows very +-- efficient packing of variables into SNMP PDU's and allows +-- a table transfer to have multiple packets outstanding. +-- These benefits increase transfer rates tremendously. + +-- The second use of the hostTimeTable is the efficient discovery +-- by the management station of new entries added to the table. +-- After the management station has downloaded the entire table, +-- it knows that new entries will be added immediately after the +-- end of the current table. It can thus detect new entries there +-- and retrieve them easily. + +-- Because the association between hostTimeCreationOrder and +-- hostTimeEntry may be broken at any time, the management +-- station must monitor the related hostControlLastDeleteTime +-- object. When the management station thus detects a deletion, +-- it must assume that any such associations have been broken, +-- and invalidate any it has stored locally. This includes + +-- restarting any download of the hostTimeTable that may have been +-- in progress, as well as rediscovering the end of the +-- hostTimeTable so that it may detect new entries. If the +-- management station does not detect the broken association, +-- it may continue to refer to a particular host by its +-- creationOrder while unwittingly retrieving the data associated +-- with another host entirely. If this happens while downloading +-- the host table, the management station may fail to download +-- all of the entries in the table. + +hostControlTable OBJECT-TYPE + SYNTAX SEQUENCE OF HostControlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of host table control entries." + ::= { hosts 1 } + +hostControlEntry OBJECT-TYPE + SYNTAX HostControlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of parameters that set up the discovery of hosts + on a particular interface and the collection of statistics + about these hosts. For example, an instance of the + hostControlTableSize object might be named + hostControlTableSize.1" + INDEX { hostControlIndex } + ::= { hostControlTable 1 } + +HostControlEntry ::= SEQUENCE { + + hostControlIndex Integer32, + hostControlDataSource OBJECT IDENTIFIER, + hostControlTableSize Integer32, + hostControlLastDeleteTime TimeTicks, + hostControlOwner OwnerString, + hostControlStatus EntryStatus +} + +hostControlIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index that uniquely identifies an entry in the + + hostControl table. Each such entry defines + a function that discovers hosts on a particular interface + and places statistics about them in the hostTable and + the hostTimeTable on behalf of this hostControlEntry." + ::= { hostControlEntry 1 } + +hostControlDataSource OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object identifies the source of the data for + this instance of the host function. This source + can be any interface on this device. In order + to identify a particular interface, this object shall + identify the instance of the ifIndex object, defined + in RFC 2233 [17], for the desired interface. + For example, if an entry were to receive data from + interface #1, this object would be set to ifIndex.1. + + The statistics in this group reflect all packets + on the local network segment attached to the identified + interface. + + An agent may or may not be able to tell if fundamental + changes to the media of the interface have occurred and + necessitate an invalidation of this entry. For example, a + hot-pluggable ethernet card could be pulled out and replaced + by a token-ring card. In such a case, if the agent has such + knowledge of the change, it is recommended that it + invalidate this entry. + + This object may not be modified if the associated + hostControlStatus object is equal to valid(1)." + ::= { hostControlEntry 2 } + +hostControlTableSize OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of hostEntries in the hostTable and the + hostTimeTable associated with this hostControlEntry." + ::= { hostControlEntry 3 } + +hostControlLastDeleteTime OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime when the last entry + was deleted from the portion of the hostTable + associated with this hostControlEntry. If no + deletions have occurred, this value shall be zero." + ::= { hostControlEntry 4 } + +hostControlOwner OBJECT-TYPE + SYNTAX OwnerString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The entity that configured this entry and is therefore + using the resources assigned to it." + ::= { hostControlEntry 5 } + +hostControlStatus OBJECT-TYPE + SYNTAX EntryStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this hostControl entry. + + If this object is not equal to valid(1), all associated + entries in the hostTable, hostTimeTable, and the + hostTopNTable shall be deleted by the agent." + ::= { hostControlEntry 6 } + +hostTable OBJECT-TYPE + SYNTAX SEQUENCE OF HostEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of host entries." + ::= { hosts 2 } + +hostEntry OBJECT-TYPE + SYNTAX HostEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A collection of statistics for a particular host that has + been discovered on an interface of this device. For example, + an instance of the hostOutBroadcastPkts object might be + named hostOutBroadcastPkts.1.6.8.0.32.27.3.176" + INDEX { hostIndex, hostAddress } + ::= { hostTable 1 } + +HostEntry ::= SEQUENCE { + hostAddress OCTET STRING, + hostCreationOrder Integer32, + hostIndex Integer32, + hostInPkts Counter32, + hostOutPkts Counter32, + hostInOctets Counter32, + hostOutOctets Counter32, + hostOutErrors Counter32, + hostOutBroadcastPkts Counter32, + hostOutMulticastPkts Counter32 +} + +hostAddress OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The physical address of this host." + ::= { hostEntry 1 } + +hostCreationOrder OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index that defines the relative ordering of + the creation time of hosts captured for a + particular hostControlEntry. This index shall + be between 1 and N, where N is the value of + the associated hostControlTableSize. The ordering + of the indexes is based on the order of each entry's + insertion into the table, in which entries added earlier + have a lower index value than entries added later. + + It is important to note that the order for a + particular entry may change as an (earlier) entry + is deleted from the table. Because this order may + change, management stations should make use of the + hostControlLastDeleteTime variable in the + hostControlEntry associated with the relevant + portion of the hostTable. By observing + this variable, the management station may detect + the circumstances where a previous association + between a value of hostCreationOrder + and a hostEntry may no longer hold." + ::= { hostEntry 2 } + +hostIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The set of collected host statistics of which + this entry is a part. The set of hosts + identified by a particular value of this + index is associated with the hostControlEntry + as identified by the same value of hostControlIndex." + ::= { hostEntry 3 } + +hostInPkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of good packets transmitted to this + address since it was added to the hostTable." + ::= { hostEntry 4 } + +hostOutPkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of packets, including bad packets, transmitted + by this address since it was added to the hostTable." + ::= { hostEntry 5 } + +hostInOctets OBJECT-TYPE + SYNTAX Counter32 + UNITS "Octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of octets transmitted to this address since + it was added to the hostTable (excluding framing + bits but including FCS octets), except for those + octets in bad packets." + ::= { hostEntry 6 } + +hostOutOctets OBJECT-TYPE + SYNTAX Counter32 + UNITS "Octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of octets transmitted by this address since + it was added to the hostTable (excluding framing + bits but including FCS octets), including those + octets in bad packets." + ::= { hostEntry 7 } + +hostOutErrors OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of bad packets transmitted by this address + since this host was added to the hostTable." + ::= { hostEntry 8 } + +hostOutBroadcastPkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of good packets transmitted by this + address that were directed to the broadcast address + since this host was added to the hostTable." + ::= { hostEntry 9 } + +hostOutMulticastPkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of good packets transmitted by this + address that were directed to a multicast address + since this host was added to the hostTable. + Note that this number does not include packets + directed to the broadcast address." + ::= { hostEntry 10 } + +-- host Time Table + +hostTimeTable OBJECT-TYPE + SYNTAX SEQUENCE OF HostTimeEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of time-ordered host table entries." + ::= { hosts 3 } + +hostTimeEntry OBJECT-TYPE + SYNTAX HostTimeEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A collection of statistics for a particular host that has + been discovered on an interface of this device. This + collection includes the relative ordering of the creation + time of this object. For example, an instance of the + hostTimeOutBroadcastPkts object might be named + hostTimeOutBroadcastPkts.1.687" + INDEX { hostTimeIndex, hostTimeCreationOrder } + ::= { hostTimeTable 1 } + +HostTimeEntry ::= SEQUENCE { + hostTimeAddress OCTET STRING, + hostTimeCreationOrder Integer32, + hostTimeIndex Integer32, + hostTimeInPkts Counter32, + hostTimeOutPkts Counter32, + hostTimeInOctets Counter32, + hostTimeOutOctets Counter32, + hostTimeOutErrors Counter32, + hostTimeOutBroadcastPkts Counter32, + hostTimeOutMulticastPkts Counter32 +} + +hostTimeAddress OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The physical address of this host." + ::= { hostTimeEntry 1 } + +hostTimeCreationOrder OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index that uniquely identifies an entry in + the hostTime table among those entries associated + with the same hostControlEntry. This index shall + be between 1 and N, where N is the value of + + the associated hostControlTableSize. The ordering + of the indexes is based on the order of each entry's + insertion into the table, in which entries added earlier + have a lower index value than entries added later. + Thus the management station has the ability to + learn of new entries added to this table without + downloading the entire table. + + It is important to note that the index for a + particular entry may change as an (earlier) entry + is deleted from the table. Because this order may + change, management stations should make use of the + hostControlLastDeleteTime variable in the + hostControlEntry associated with the relevant + portion of the hostTimeTable. By observing + this variable, the management station may detect + the circumstances where a download of the table + may have missed entries, and where a previous + association between a value of hostTimeCreationOrder + and a hostTimeEntry may no longer hold." + ::= { hostTimeEntry 2 } + +hostTimeIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The set of collected host statistics of which + this entry is a part. The set of hosts + identified by a particular value of this + index is associated with the hostControlEntry + as identified by the same value of hostControlIndex." + ::= { hostTimeEntry 3 } + +hostTimeInPkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of good packets transmitted to this + address since it was added to the hostTimeTable." + ::= { hostTimeEntry 4 } + +hostTimeOutPkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of packets, including bad packets, transmitted + by this address since it was added to the hostTimeTable." + ::= { hostTimeEntry 5 } + +hostTimeInOctets OBJECT-TYPE + SYNTAX Counter32 + UNITS "Octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of octets transmitted to this address since + it was added to the hostTimeTable (excluding framing + bits but including FCS octets), except for those + octets in bad packets." + ::= { hostTimeEntry 6 } + +hostTimeOutOctets OBJECT-TYPE + SYNTAX Counter32 + UNITS "Octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of octets transmitted by this address since + it was added to the hostTimeTable (excluding framing + bits but including FCS octets), including those + octets in bad packets." + ::= { hostTimeEntry 7 } + +hostTimeOutErrors OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of bad packets transmitted by this address + since this host was added to the hostTimeTable." + ::= { hostTimeEntry 8 } + +hostTimeOutBroadcastPkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of good packets transmitted by this + address that were directed to the broadcast address + + since this host was added to the hostTimeTable." + ::= { hostTimeEntry 9 } + +hostTimeOutMulticastPkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of good packets transmitted by this + address that were directed to a multicast address + since this host was added to the hostTimeTable. + Note that this number does not include packets directed + to the broadcast address." + ::= { hostTimeEntry 10 } + +-- The Host Top "N" Group + +-- Implementation of the Host Top N group is optional. The Host Top N +-- group requires the implementation of the host group. +-- Consult the MODULE-COMPLIANCE macro for the authoritative +-- conformance information for this MIB. +-- +-- The Host Top N group is used to prepare reports that describe +-- the hosts that top a list ordered by one of their statistics. +-- The available statistics are samples of one of their +-- base statistics, over an interval specified by the management +-- station. Thus, these statistics are rate based. The management +-- station also selects how many such hosts are reported. + +-- The hostTopNControlTable is used to initiate the generation of +-- such a report. The management station may select the parameters +-- of such a report, such as which interface, which statistic, +-- how many hosts, and the start and stop times of the sampling. +-- When the report is prepared, entries are created in the +-- hostTopNTable associated with the relevant hostTopNControlEntry. +-- These entries are static for each report after it has been +-- prepared. + +hostTopNControlTable OBJECT-TYPE + SYNTAX SEQUENCE OF HostTopNControlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of top N host control entries." + ::= { hostTopN 1 } + +hostTopNControlEntry OBJECT-TYPE + SYNTAX HostTopNControlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A set of parameters that control the creation of a report + of the top N hosts according to several metrics. For + example, an instance of the hostTopNDuration object might + be named hostTopNDuration.3" + INDEX { hostTopNControlIndex } + ::= { hostTopNControlTable 1 } + +HostTopNControlEntry ::= SEQUENCE { + hostTopNControlIndex Integer32, + hostTopNHostIndex Integer32, + hostTopNRateBase INTEGER, + hostTopNTimeRemaining Integer32, + hostTopNDuration Integer32, + hostTopNRequestedSize Integer32, + hostTopNGrantedSize Integer32, + hostTopNStartTime TimeTicks, + hostTopNOwner OwnerString, + hostTopNStatus EntryStatus +} + +hostTopNControlIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index that uniquely identifies an entry + in the hostTopNControl table. Each such + entry defines one top N report prepared for + one interface." + ::= { hostTopNControlEntry 1 } + +hostTopNHostIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The host table for which a top N report will be prepared + on behalf of this entry. The host table identified by a + particular value of this index is associated with the same + host table as identified by the same value of + hostIndex. + + This object may not be modified if the associated + hostTopNStatus object is equal to valid(1)." + ::= { hostTopNControlEntry 2 } + +hostTopNRateBase OBJECT-TYPE + SYNTAX INTEGER { + hostTopNInPkts(1), + hostTopNOutPkts(2), + hostTopNInOctets(3), + hostTopNOutOctets(4), + hostTopNOutErrors(5), + hostTopNOutBroadcastPkts(6), + hostTopNOutMulticastPkts(7) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The variable for each host that the hostTopNRate + variable is based upon. + + This object may not be modified if the associated + hostTopNStatus object is equal to valid(1)." + ::= { hostTopNControlEntry 3 } + +hostTopNTimeRemaining OBJECT-TYPE + SYNTAX Integer32 + UNITS "Seconds" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The number of seconds left in the report currently being + collected. When this object is modified by the management + station, a new collection is started, possibly aborting + a currently running report. The new value is used + as the requested duration of this report, which is + loaded into the associated hostTopNDuration object. + + When this object is set to a non-zero value, any + associated hostTopNEntries shall be made + inaccessible by the monitor. While the value of this + object is non-zero, it decrements by one per second until + it reaches zero. During this time, all associated + hostTopNEntries shall remain inaccessible. At the time + that this object decrements to zero, the report is made + accessible in the hostTopNTable. Thus, the hostTopN + table needs to be created only at the end of the collection + interval." + DEFVAL { 0 } + ::= { hostTopNControlEntry 4 } + +hostTopNDuration OBJECT-TYPE + SYNTAX Integer32 + UNITS "Seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of seconds that this report has collected + during the last sampling interval, or if this + report is currently being collected, the number + of seconds that this report is being collected + during this sampling interval. + + When the associated hostTopNTimeRemaining object is set, + this object shall be set by the probe to the same value + and shall not be modified until the next time + the hostTopNTimeRemaining is set. + + This value shall be zero if no reports have been + requested for this hostTopNControlEntry." + DEFVAL { 0 } + ::= { hostTopNControlEntry 5 } + +hostTopNRequestedSize OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The maximum number of hosts requested for the top N + table. + + When this object is created or modified, the probe + should set hostTopNGrantedSize as closely to this + object as is possible for the particular probe + implementation and available resources." + DEFVAL { 10 } + ::= { hostTopNControlEntry 6 } + +hostTopNGrantedSize OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The maximum number of hosts in the top N table. + + When the associated hostTopNRequestedSize object is + created or modified, the probe should set this + object as closely to the requested value as is possible + for the particular implementation and available + + resources. The probe must not lower this value except + as a result of a set to the associated + hostTopNRequestedSize object. + + Hosts with the highest value of hostTopNRate shall be + placed in this table in decreasing order of this rate + until there is no more room or until there are no more + hosts." + ::= { hostTopNControlEntry 7 } + +hostTopNStartTime OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime when this top N report was + last started. In other words, this is the time that + the associated hostTopNTimeRemaining object was + modified to start the requested report." + ::= { hostTopNControlEntry 8 } + +hostTopNOwner OBJECT-TYPE + SYNTAX OwnerString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The entity that configured this entry and is therefore + using the resources assigned to it." + ::= { hostTopNControlEntry 9 } + +hostTopNStatus OBJECT-TYPE + SYNTAX EntryStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this hostTopNControl entry. + + If this object is not equal to valid(1), all associated + hostTopNEntries shall be deleted by the agent." + ::= { hostTopNControlEntry 10 } + +hostTopNTable OBJECT-TYPE + SYNTAX SEQUENCE OF HostTopNEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of top N host entries." + ::= { hostTopN 2 } + +hostTopNEntry OBJECT-TYPE + SYNTAX HostTopNEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A set of statistics for a host that is part of a top N + report. For example, an instance of the hostTopNRate + object might be named hostTopNRate.3.10" + INDEX { hostTopNReport, hostTopNIndex } + ::= { hostTopNTable 1 } + +HostTopNEntry ::= SEQUENCE { + hostTopNReport Integer32, + hostTopNIndex Integer32, + hostTopNAddress OCTET STRING, + hostTopNRate Integer32 +} + +hostTopNReport OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object identifies the top N report of which + this entry is a part. The set of hosts + identified by a particular value of this + object is part of the same report as identified + by the same value of the hostTopNControlIndex object." + ::= { hostTopNEntry 1 } + +hostTopNIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index that uniquely identifies an entry in + the hostTopN table among those in the same report. + This index is between 1 and N, where N is the + number of entries in this table. Increasing values + of hostTopNIndex shall be assigned to entries with + decreasing values of hostTopNRate until index N + is assigned to the entry with the lowest value of + hostTopNRate or there are no more hostTopNEntries." + ::= { hostTopNEntry 2 } + +hostTopNAddress OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The physical address of this host." + ::= { hostTopNEntry 3 } + +hostTopNRate OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The amount of change in the selected variable + during this sampling interval. The selected + variable is this host's instance of the object + selected by hostTopNRateBase." + ::= { hostTopNEntry 4 } + +-- The Matrix Group + +-- Implementation of the Matrix group is optional. +-- Consult the MODULE-COMPLIANCE macro for the authoritative +-- conformance information for this MIB. +-- +-- The Matrix group consists of the matrixControlTable, matrixSDTable +-- and the matrixDSTable. These tables store statistics for a +-- particular conversation between two addresses. As the device +-- detects a new conversation, including those to a non-unicast +-- address, it creates a new entry in both of the matrix tables. +-- It must only create new entries based on information +-- received in good packets. If the monitoring device finds +-- itself short of resources, it may delete entries as needed. +-- It is suggested that the device delete the least recently used +-- entries first. + +matrixControlTable OBJECT-TYPE + SYNTAX SEQUENCE OF MatrixControlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of information entries for the + traffic matrix on each interface." + ::= { matrix 1 } + +matrixControlEntry OBJECT-TYPE + SYNTAX MatrixControlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a traffic matrix on a particular + + interface. For example, an instance of the + matrixControlLastDeleteTime object might be named + matrixControlLastDeleteTime.1" + INDEX { matrixControlIndex } + ::= { matrixControlTable 1 } + +MatrixControlEntry ::= SEQUENCE { + matrixControlIndex Integer32, + matrixControlDataSource OBJECT IDENTIFIER, + matrixControlTableSize Integer32, + matrixControlLastDeleteTime TimeTicks, + matrixControlOwner OwnerString, + matrixControlStatus EntryStatus +} + +matrixControlIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index that uniquely identifies an entry in the + matrixControl table. Each such entry defines + a function that discovers conversations on a particular + interface and places statistics about them in the + matrixSDTable and the matrixDSTable on behalf of this + matrixControlEntry." + ::= { matrixControlEntry 1 } + +matrixControlDataSource OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object identifies the source of + the data from which this entry creates a traffic matrix. + This source can be any interface on this device. In + order to identify a particular interface, this object + shall identify the instance of the ifIndex object, + defined in RFC 2233 [17], for the desired + interface. For example, if an entry were to receive data + from interface #1, this object would be set to ifIndex.1. + + The statistics in this group reflect all packets + on the local network segment attached to the identified + interface. + + An agent may or may not be able to tell if fundamental + changes to the media of the interface have occurred and + + necessitate an invalidation of this entry. For example, a + hot-pluggable ethernet card could be pulled out and replaced + by a token-ring card. In such a case, if the agent has such + knowledge of the change, it is recommended that it + invalidate this entry. + + This object may not be modified if the associated + matrixControlStatus object is equal to valid(1)." + ::= { matrixControlEntry 2 } + +matrixControlTableSize OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of matrixSDEntries in the matrixSDTable + for this interface. This must also be the value of + the number of entries in the matrixDSTable for this + interface." + ::= { matrixControlEntry 3 } + +matrixControlLastDeleteTime OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime when the last entry + was deleted from the portion of the matrixSDTable + or matrixDSTable associated with this matrixControlEntry. + If no deletions have occurred, this value shall be + zero." + ::= { matrixControlEntry 4 } + +matrixControlOwner OBJECT-TYPE + SYNTAX OwnerString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The entity that configured this entry and is therefore + using the resources assigned to it." + ::= { matrixControlEntry 5 } + +matrixControlStatus OBJECT-TYPE + SYNTAX EntryStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this matrixControl entry. + + If this object is not equal to valid(1), all associated + entries in the matrixSDTable and the matrixDSTable + shall be deleted by the agent." + ::= { matrixControlEntry 6 } + +matrixSDTable OBJECT-TYPE + SYNTAX SEQUENCE OF MatrixSDEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of traffic matrix entries indexed by + source and destination MAC address." + ::= { matrix 2 } + +matrixSDEntry OBJECT-TYPE + SYNTAX MatrixSDEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A collection of statistics for communications between + two addresses on a particular interface. For example, + an instance of the matrixSDPkts object might be named + matrixSDPkts.1.6.8.0.32.27.3.176.6.8.0.32.10.8.113" + INDEX { matrixSDIndex, + matrixSDSourceAddress, matrixSDDestAddress } + ::= { matrixSDTable 1 } + +MatrixSDEntry ::= SEQUENCE { + matrixSDSourceAddress OCTET STRING, + matrixSDDestAddress OCTET STRING, + matrixSDIndex Integer32, + matrixSDPkts Counter32, + matrixSDOctets Counter32, + matrixSDErrors Counter32 +} + +matrixSDSourceAddress OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The source physical address." + ::= { matrixSDEntry 1 } + +matrixSDDestAddress OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The destination physical address." + ::= { matrixSDEntry 2 } + +matrixSDIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The set of collected matrix statistics of which + this entry is a part. The set of matrix statistics + identified by a particular value of this index + is associated with the same matrixControlEntry + as identified by the same value of matrixControlIndex." + ::= { matrixSDEntry 3 } + +matrixSDPkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of packets transmitted from the source + address to the destination address (this number includes + bad packets)." + ::= { matrixSDEntry 4 } + +matrixSDOctets OBJECT-TYPE + SYNTAX Counter32 + UNITS "Octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of octets (excluding framing bits but + including FCS octets) contained in all packets + transmitted from the source address to the + destination address." + ::= { matrixSDEntry 5 } + +matrixSDErrors OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of bad packets transmitted from + the source address to the destination address." + ::= { matrixSDEntry 6 } + +-- Traffic matrix tables from destination to source + +matrixDSTable OBJECT-TYPE + SYNTAX SEQUENCE OF MatrixDSEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of traffic matrix entries indexed by + destination and source MAC address." + ::= { matrix 3 } + +matrixDSEntry OBJECT-TYPE + SYNTAX MatrixDSEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A collection of statistics for communications between + two addresses on a particular interface. For example, + an instance of the matrixSDPkts object might be named + matrixSDPkts.1.6.8.0.32.10.8.113.6.8.0.32.27.3.176" + INDEX { matrixDSIndex, + matrixDSDestAddress, matrixDSSourceAddress } + ::= { matrixDSTable 1 } + +MatrixDSEntry ::= SEQUENCE { + matrixDSSourceAddress OCTET STRING, + matrixDSDestAddress OCTET STRING, + matrixDSIndex Integer32, + matrixDSPkts Counter32, + matrixDSOctets Counter32, + matrixDSErrors Counter32 +} + +matrixDSSourceAddress OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The source physical address." + ::= { matrixDSEntry 1 } + +matrixDSDestAddress OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The destination physical address." + ::= { matrixDSEntry 2 } + +matrixDSIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The set of collected matrix statistics of which + this entry is a part. The set of matrix statistics + identified by a particular value of this index + is associated with the same matrixControlEntry + as identified by the same value of matrixControlIndex." + ::= { matrixDSEntry 3 } + +matrixDSPkts OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of packets transmitted from the source + address to the destination address (this number includes + bad packets)." + ::= { matrixDSEntry 4 } + +matrixDSOctets OBJECT-TYPE + SYNTAX Counter32 + UNITS "Octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of octets (excluding framing bits + but including FCS octets) contained in all packets + transmitted from the source address to the + destination address." + ::= { matrixDSEntry 5 } + +matrixDSErrors OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of bad packets transmitted from + the source address to the destination address." + ::= { matrixDSEntry 6 } + +-- The Filter Group + +-- Implementation of the Filter group is optional. + +-- Consult the MODULE-COMPLIANCE macro for the authoritative +-- conformance information for this MIB. +-- +-- The Filter group allows packets to be captured with an +-- arbitrary filter expression. A logical data and +-- event stream or "channel" is formed by the packets +-- that match the filter expression. +-- +-- This filter mechanism allows the creation of an arbitrary +-- logical expression with which to filter packets. Each +-- filter associated with a channel is OR'ed with the others. +-- Within a filter, any bits checked in the data and status are +-- AND'ed with respect to other bits in the same filter. The +-- NotMask also allows for checking for inequality. Finally, +-- the channelAcceptType object allows for inversion of the +-- whole equation. +-- +-- If a management station wishes to receive a trap to alert it +-- that new packets have been captured and are available for +-- download, it is recommended that it set up an alarm entry that +-- monitors the value of the relevant channelMatches instance. +-- +-- The channel can be turned on or off, and can also +-- generate events when packets pass through it. + +filterTable OBJECT-TYPE + SYNTAX SEQUENCE OF FilterEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of packet filter entries." + ::= { filter 1 } + +filterEntry OBJECT-TYPE + SYNTAX FilterEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A set of parameters for a packet filter applied on a + particular interface. As an example, an instance of the + filterPktData object might be named filterPktData.12" + INDEX { filterIndex } + ::= { filterTable 1 } + +FilterEntry ::= SEQUENCE { + filterIndex Integer32, + filterChannelIndex Integer32, + filterPktDataOffset Integer32, + filterPktData OCTET STRING, + filterPktDataMask OCTET STRING, + filterPktDataNotMask OCTET STRING, + filterPktStatus Integer32, + filterPktStatusMask Integer32, + filterPktStatusNotMask Integer32, + filterOwner OwnerString, + filterStatus EntryStatus +} + +filterIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index that uniquely identifies an entry + in the filter table. Each such entry defines + one filter that is to be applied to every packet + received on an interface." + ::= { filterEntry 1 } + +filterChannelIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object identifies the channel of which this filter + is a part. The filters identified by a particular value + of this object are associated with the same channel as + identified by the same value of the channelIndex object." + ::= { filterEntry 2 } + +filterPktDataOffset OBJECT-TYPE + SYNTAX Integer32 + UNITS "Octets" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The offset from the beginning of each packet where + a match of packet data will be attempted. This offset + is measured from the point in the physical layer + packet after the framing bits, if any. For example, + in an Ethernet frame, this point is at the beginning of + the destination MAC address. + + This object may not be modified if the associated + filterStatus object is equal to valid(1)." + DEFVAL { 0 } + ::= { filterEntry 3 } + +filterPktData OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The data that is to be matched with the input packet. + For each packet received, this filter and the accompanying + filterPktDataMask and filterPktDataNotMask will be + adjusted for the offset. The only bits relevant to this + match algorithm are those that have the corresponding + filterPktDataMask bit equal to one. The following three + rules are then applied to every packet: + + (1) If the packet is too short and does not have data + corresponding to part of the filterPktData, the packet + will fail this data match. + + (2) For each relevant bit from the packet with the + corresponding filterPktDataNotMask bit set to zero, if + the bit from the packet is not equal to the corresponding + bit from the filterPktData, then the packet will fail + this data match. + + (3) If for every relevant bit from the packet with the + corresponding filterPktDataNotMask bit set to one, the + bit from the packet is equal to the corresponding bit + from the filterPktData, then the packet will fail this + data match. + + Any packets that have not failed any of the three matches + above have passed this data match. In particular, a zero + length filter will match any packet. + + This object may not be modified if the associated + filterStatus object is equal to valid(1)." + ::= { filterEntry 4 } + +filterPktDataMask OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The mask that is applied to the match process. + After adjusting this mask for the offset, only those + bits in the received packet that correspond to bits set + in this mask are relevant for further processing by the + + match algorithm. The offset is applied to filterPktDataMask + in the same way it is applied to the filter. For the + purposes of the matching algorithm, if the associated + filterPktData object is longer than this mask, this mask is + conceptually extended with '1' bits until it reaches the + length of the filterPktData object. + + This object may not be modified if the associated + filterStatus object is equal to valid(1)." + ::= { filterEntry 5 } + +filterPktDataNotMask OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The inversion mask that is applied to the match + process. After adjusting this mask for the offset, + those relevant bits in the received packet that correspond + to bits cleared in this mask must all be equal to their + corresponding bits in the filterPktData object for the packet + to be accepted. In addition, at least one of those relevant + bits in the received packet that correspond to bits set in + this mask must be different to its corresponding bit in the + filterPktData object. + + For the purposes of the matching algorithm, if the associated + filterPktData object is longer than this mask, this mask is + conceptually extended with '0' bits until it reaches the + length of the filterPktData object. + + This object may not be modified if the associated + filterStatus object is equal to valid(1)." + ::= { filterEntry 6 } + +filterPktStatus OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status that is to be matched with the input packet. + The only bits relevant to this match algorithm are those that + have the corresponding filterPktStatusMask bit equal to one. + The following two rules are then applied to every packet: + + (1) For each relevant bit from the packet status with the + corresponding filterPktStatusNotMask bit set to zero, if + the bit from the packet status is not equal to the + + corresponding bit from the filterPktStatus, then the + packet will fail this status match. + + (2) If for every relevant bit from the packet status with the + corresponding filterPktStatusNotMask bit set to one, the + bit from the packet status is equal to the corresponding + bit from the filterPktStatus, then the packet will fail + this status match. + + Any packets that have not failed either of the two matches + above have passed this status match. In particular, a zero + length status filter will match any packet's status. + + The value of the packet status is a sum. This sum + initially takes the value zero. Then, for each + error, E, that has been discovered in this packet, + 2 raised to a value representing E is added to the sum. + The errors and the bits that represent them are dependent + on the media type of the interface that this channel + is receiving packets from. + + The errors defined for a packet captured off of an + Ethernet interface are as follows: + + bit # Error + 0 Packet is longer than 1518 octets + 1 Packet is shorter than 64 octets + 2 Packet experienced a CRC or Alignment error + + For example, an Ethernet fragment would have a + value of 6 (2^1 + 2^2). + + As this MIB is expanded to new media types, this object + will have other media-specific errors defined. + + For the purposes of this status matching algorithm, if the + packet status is longer than this filterPktStatus object, + this object is conceptually extended with '0' bits until it + reaches the size of the packet status. + + This object may not be modified if the associated + filterStatus object is equal to valid(1)." + ::= { filterEntry 7 } + +filterPktStatusMask OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The mask that is applied to the status match process. + Only those bits in the received packet that correspond to + bits set in this mask are relevant for further processing + by the status match algorithm. For the purposes + of the matching algorithm, if the associated filterPktStatus + object is longer than this mask, this mask is conceptually + extended with '1' bits until it reaches the size of the + filterPktStatus. In addition, if a packet status is longer + than this mask, this mask is conceptually extended with '0' + bits until it reaches the size of the packet status. + + This object may not be modified if the associated + filterStatus object is equal to valid(1)." + ::= { filterEntry 8 } + +filterPktStatusNotMask OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The inversion mask that is applied to the status match + process. Those relevant bits in the received packet status + that correspond to bits cleared in this mask must all be + equal to their corresponding bits in the filterPktStatus + object for the packet to be accepted. In addition, at least + one of those relevant bits in the received packet status + that correspond to bits set in this mask must be different + to its corresponding bit in the filterPktStatus object for + the packet to be accepted. + + For the purposes of the matching algorithm, if the associated + filterPktStatus object or a packet status is longer than this + mask, this mask is conceptually extended with '0' bits until + it reaches the longer of the lengths of the filterPktStatus + object and the packet status. + + This object may not be modified if the associated + filterStatus object is equal to valid(1)." + ::= { filterEntry 9 } + +filterOwner OBJECT-TYPE + SYNTAX OwnerString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The entity that configured this entry and is therefore + using the resources assigned to it." + ::= { filterEntry 10 } + +filterStatus OBJECT-TYPE + SYNTAX EntryStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this filter entry." + ::= { filterEntry 11 } + +channelTable OBJECT-TYPE + SYNTAX SEQUENCE OF ChannelEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of packet channel entries." + ::= { filter 2 } + +channelEntry OBJECT-TYPE + SYNTAX ChannelEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A set of parameters for a packet channel applied on a + particular interface. As an example, an instance of the + channelMatches object might be named channelMatches.3" + INDEX { channelIndex } + ::= { channelTable 1 } + +ChannelEntry ::= SEQUENCE { + channelIndex Integer32, + channelIfIndex Integer32, + channelAcceptType INTEGER, + channelDataControl INTEGER, + channelTurnOnEventIndex Integer32, + channelTurnOffEventIndex Integer32, + channelEventIndex Integer32, + channelEventStatus INTEGER, + channelMatches Counter32, + channelDescription DisplayString, + channelOwner OwnerString, + channelStatus EntryStatus +} + +channelIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index that uniquely identifies an entry in the channel + table. Each such entry defines one channel, a logical + data and event stream. + + It is suggested that before creating a channel, an + application should scan all instances of the + filterChannelIndex object to make sure that there are no + pre-existing filters that would be inadvertently be linked + to the channel." + ::= { channelEntry 1 } + +channelIfIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object uniquely identifies the + interface on this remote network monitoring device to which + the associated filters are applied to allow data into this + channel. The interface identified by a particular value + of this object is the same interface as identified by the + same value of the ifIndex object, defined in RFC 2233 [17]. + + The filters in this group are applied to all packets on + the local network segment attached to the identified + interface. + + An agent may or may not be able to tell if fundamental + changes to the media of the interface have occurred and + necessitate an invalidation of this entry. For example, a + hot-pluggable ethernet card could be pulled out and replaced + by a token-ring card. In such a case, if the agent has such + knowledge of the change, it is recommended that it + invalidate this entry. + + This object may not be modified if the associated + channelStatus object is equal to valid(1)." + ::= { channelEntry 2 } + +channelAcceptType OBJECT-TYPE + SYNTAX INTEGER { + acceptMatched(1), + acceptFailed(2) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object controls the action of the filters + associated with this channel. If this object is equal + to acceptMatched(1), packets will be accepted to this + channel if they are accepted by both the packet data and + packet status matches of an associated filter. If + this object is equal to acceptFailed(2), packets will + be accepted to this channel only if they fail either + the packet data match or the packet status match of + each of the associated filters. + + In particular, a channel with no associated filters will + match no packets if set to acceptMatched(1) case and will + match all packets in the acceptFailed(2) case. + + This object may not be modified if the associated + channelStatus object is equal to valid(1)." + ::= { channelEntry 3 } + +channelDataControl OBJECT-TYPE + SYNTAX INTEGER { + on(1), + off(2) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object controls the flow of data through this channel. + If this object is on(1), data, status and events flow + through this channel. If this object is off(2), data, + status and events will not flow through this channel." + DEFVAL { off } + ::= { channelEntry 4 } + +channelTurnOnEventIndex OBJECT-TYPE + SYNTAX Integer32 (0..65535) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object identifies the event + that is configured to turn the associated + channelDataControl from off to on when the event is + generated. The event identified by a particular value + of this object is the same event as identified by the + same value of the eventIndex object. If there is no + corresponding entry in the eventTable, then no + association exists. In fact, if no event is intended + for this channel, channelTurnOnEventIndex must be + set to zero, a non-existent event index. + + This object may not be modified if the associated + channelStatus object is equal to valid(1)." + ::= { channelEntry 5 } + +channelTurnOffEventIndex OBJECT-TYPE + SYNTAX Integer32 (0..65535) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object identifies the event + that is configured to turn the associated + channelDataControl from on to off when the event is + generated. The event identified by a particular value + of this object is the same event as identified by the + same value of the eventIndex object. If there is no + corresponding entry in the eventTable, then no + association exists. In fact, if no event is intended + for this channel, channelTurnOffEventIndex must be + set to zero, a non-existent event index. + + This object may not be modified if the associated + channelStatus object is equal to valid(1)." + ::= { channelEntry 6 } + +channelEventIndex OBJECT-TYPE + SYNTAX Integer32 (0..65535) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object identifies the event + that is configured to be generated when the + associated channelDataControl is on and a packet + is matched. The event identified by a particular value + of this object is the same event as identified by the + same value of the eventIndex object. If there is no + corresponding entry in the eventTable, then no + association exists. In fact, if no event is intended + for this channel, channelEventIndex must be + set to zero, a non-existent event index. + + This object may not be modified if the associated + channelStatus object is equal to valid(1)." + ::= { channelEntry 7 } + +channelEventStatus OBJECT-TYPE + SYNTAX INTEGER { + eventReady(1), + eventFired(2), + eventAlwaysReady(3) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The event status of this channel. + + If this channel is configured to generate events + when packets are matched, a means of controlling + the flow of those events is often needed. When + this object is equal to eventReady(1), a single + event may be generated, after which this object + will be set by the probe to eventFired(2). While + in the eventFired(2) state, no events will be + generated until the object is modified to + eventReady(1) (or eventAlwaysReady(3)). The + management station can thus easily respond to a + notification of an event by re-enabling this object. + + If the management station wishes to disable this + flow control and allow events to be generated + at will, this object may be set to + eventAlwaysReady(3). Disabling the flow control + is discouraged as it can result in high network + traffic or other performance problems." + DEFVAL { eventReady } + ::= { channelEntry 8 } + +channelMatches OBJECT-TYPE + SYNTAX Counter32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times this channel has matched a packet. + Note that this object is updated even when + channelDataControl is set to off." + ::= { channelEntry 9 } + +channelDescription OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..127)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A comment describing this channel." + ::= { channelEntry 10 } + +channelOwner OBJECT-TYPE + SYNTAX OwnerString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The entity that configured this entry and is therefore + using the resources assigned to it." + ::= { channelEntry 11 } + +channelStatus OBJECT-TYPE + SYNTAX EntryStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this channel entry." + ::= { channelEntry 12 } + +-- The Packet Capture Group + +-- Implementation of the Packet Capture group is optional. The Packet +-- Capture Group requires implementation of the Filter Group. +-- Consult the MODULE-COMPLIANCE macro for the authoritative +-- conformance information for this MIB. +-- +-- The Packet Capture group allows packets to be captured +-- upon a filter match. The bufferControlTable controls +-- the captured packets output from a channel that is +-- associated with it. The captured packets are placed +-- in entries in the captureBufferTable. These entries are +-- associated with the bufferControlEntry on whose behalf they +-- were stored. + +bufferControlTable OBJECT-TYPE + SYNTAX SEQUENCE OF BufferControlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of buffers control entries." + ::= { capture 1 } + +bufferControlEntry OBJECT-TYPE + SYNTAX BufferControlEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A set of parameters that control the collection of a stream + of packets that have matched filters. As an example, an + instance of the bufferControlCaptureSliceSize object might + be named bufferControlCaptureSliceSize.3" + INDEX { bufferControlIndex } + ::= { bufferControlTable 1 } + +BufferControlEntry ::= SEQUENCE { + bufferControlIndex Integer32, + bufferControlChannelIndex Integer32, + bufferControlFullStatus INTEGER, + bufferControlFullAction INTEGER, + bufferControlCaptureSliceSize Integer32, + bufferControlDownloadSliceSize Integer32, + bufferControlDownloadOffset Integer32, + bufferControlMaxOctetsRequested Integer32, + bufferControlMaxOctetsGranted Integer32, + bufferControlCapturedPackets Integer32, + bufferControlTurnOnTime TimeTicks, + bufferControlOwner OwnerString, + bufferControlStatus EntryStatus +} + +bufferControlIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index that uniquely identifies an entry + in the bufferControl table. The value of this + index shall never be zero. Each such + entry defines one set of packets that is + captured and controlled by one or more filters." + ::= { bufferControlEntry 1 } + +bufferControlChannelIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "An index that identifies the channel that is the + source of packets for this bufferControl table. + The channel identified by a particular value of this + index is the same as identified by the same value of + the channelIndex object. + + This object may not be modified if the associated + bufferControlStatus object is equal to valid(1)." + ::= { bufferControlEntry 2 } + +bufferControlFullStatus OBJECT-TYPE + SYNTAX INTEGER { + + spaceAvailable(1), + full(2) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object shows whether the buffer has room to + accept new packets or if it is full. + + If the status is spaceAvailable(1), the buffer is + accepting new packets normally. If the status is + full(2) and the associated bufferControlFullAction + object is wrapWhenFull, the buffer is accepting new + packets by deleting enough of the oldest packets + to make room for new ones as they arrive. Otherwise, + if the status is full(2) and the + bufferControlFullAction object is lockWhenFull, + then the buffer has stopped collecting packets. + + When this object is set to full(2) the probe must + not later set it to spaceAvailable(1) except in the + case of a significant gain in resources such as + an increase of bufferControlOctetsGranted. In + particular, the wrap-mode action of deleting old + packets to make room for newly arrived packets + must not affect the value of this object." + ::= { bufferControlEntry 3 } + +bufferControlFullAction OBJECT-TYPE + SYNTAX INTEGER { + lockWhenFull(1), + wrapWhenFull(2) -- FIFO + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "Controls the action of the buffer when it + reaches the full status. When in the lockWhenFull(1) + state and a packet is added to the buffer that + fills the buffer, the bufferControlFullStatus will + be set to full(2) and this buffer will stop capturing + packets." + ::= { bufferControlEntry 4 } + +bufferControlCaptureSliceSize OBJECT-TYPE + SYNTAX Integer32 + UNITS "Octets" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The maximum number of octets of each packet + that will be saved in this capture buffer. + For example, if a 1500 octet packet is received by + the probe and this object is set to 500, then only + 500 octets of the packet will be stored in the + associated capture buffer. If this variable is set + to 0, the capture buffer will save as many octets + as is possible. + + This object may not be modified if the associated + bufferControlStatus object is equal to valid(1)." + DEFVAL { 100 } + ::= { bufferControlEntry 5 } + +bufferControlDownloadSliceSize OBJECT-TYPE + SYNTAX Integer32 + UNITS "Octets" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The maximum number of octets of each packet + in this capture buffer that will be returned in + an SNMP retrieval of that packet. For example, + if 500 octets of a packet have been stored in the + associated capture buffer, the associated + bufferControlDownloadOffset is 0, and this + object is set to 100, then the captureBufferPacket + object that contains the packet will contain only + the first 100 octets of the packet. + + A prudent manager will take into account possible + interoperability or fragmentation problems that may + occur if the download slice size is set too large. + In particular, conformant SNMP implementations are not + required to accept messages whose length exceeds 484 + octets, although they are encouraged to support larger + datagrams whenever feasible." + DEFVAL { 100 } + ::= { bufferControlEntry 6 } + +bufferControlDownloadOffset OBJECT-TYPE + SYNTAX Integer32 + UNITS "Octets" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The offset of the first octet of each packet + in this capture buffer that will be returned in + an SNMP retrieval of that packet. For example, + if 500 octets of a packet have been stored in the + associated capture buffer and this object is set to + 100, then the captureBufferPacket object that + contains the packet will contain bytes starting + 100 octets into the packet." + DEFVAL { 0 } + ::= { bufferControlEntry 7 } + +bufferControlMaxOctetsRequested OBJECT-TYPE + SYNTAX Integer32 + UNITS "Octets" + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The requested maximum number of octets to be + saved in this captureBuffer, including any + implementation-specific overhead. If this variable + is set to -1, the capture buffer will save as many + octets as is possible. + + When this object is created or modified, the probe + should set bufferControlMaxOctetsGranted as closely + to this object as is possible for the particular probe + implementation and available resources. However, if + the object has the special value of -1, the probe + must set bufferControlMaxOctetsGranted to -1." + DEFVAL { -1 } + ::= { bufferControlEntry 8 } + +bufferControlMaxOctetsGranted OBJECT-TYPE + SYNTAX Integer32 + UNITS "Octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The maximum number of octets that can be + saved in this captureBuffer, including overhead. + If this variable is -1, the capture buffer will save + as many octets as possible. + + When the bufferControlMaxOctetsRequested object is + created or modified, the probe should set this object + as closely to the requested value as is possible for the + particular probe implementation and available resources. + However, if the request object has the special value + + of -1, the probe must set this object to -1. + + The probe must not lower this value except as a result of + a modification to the associated + bufferControlMaxOctetsRequested object. + + When this maximum number of octets is reached + and a new packet is to be added to this + capture buffer and the corresponding + bufferControlFullAction is set to wrapWhenFull(2), + enough of the oldest packets associated with this + capture buffer shall be deleted by the agent so + that the new packet can be added. If the corresponding + bufferControlFullAction is set to lockWhenFull(1), + the new packet shall be discarded. In either case, + the probe must set bufferControlFullStatus to + full(2). + + When the value of this object changes to a value less + than the current value, entries are deleted from + the captureBufferTable associated with this + bufferControlEntry. Enough of the + oldest of these captureBufferEntries shall be + deleted by the agent so that the number of octets + used remains less than or equal to the new value of + this object. + + When the value of this object changes to a value greater + than the current value, the number of associated + captureBufferEntries may be allowed to grow." + ::= { bufferControlEntry 9 } + +bufferControlCapturedPackets OBJECT-TYPE + SYNTAX Integer32 + UNITS "Packets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of packets currently in this captureBuffer." + ::= { bufferControlEntry 10 } + +bufferControlTurnOnTime OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime when this capture buffer was + first turned on." + ::= { bufferControlEntry 11 } + +bufferControlOwner OBJECT-TYPE + SYNTAX OwnerString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The entity that configured this entry and is therefore + using the resources assigned to it." + ::= { bufferControlEntry 12 } + +bufferControlStatus OBJECT-TYPE + SYNTAX EntryStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this buffer Control Entry." + ::= { bufferControlEntry 13 } + +captureBufferTable OBJECT-TYPE + SYNTAX SEQUENCE OF CaptureBufferEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of packets captured off of a channel." + ::= { capture 2 } + +captureBufferEntry OBJECT-TYPE + SYNTAX CaptureBufferEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A packet captured off of an attached network. As an + example, an instance of the captureBufferPacketData + object might be named captureBufferPacketData.3.1783" + INDEX { captureBufferControlIndex, captureBufferIndex } + ::= { captureBufferTable 1 } + +CaptureBufferEntry ::= SEQUENCE { + captureBufferControlIndex Integer32, + captureBufferIndex Integer32, + captureBufferPacketID Integer32, + captureBufferPacketData OCTET STRING, + captureBufferPacketLength Integer32, + captureBufferPacketTime Integer32, + captureBufferPacketStatus Integer32 +} + +captureBufferControlIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The index of the bufferControlEntry with which + this packet is associated." + ::= { captureBufferEntry 1 } + +captureBufferIndex OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index that uniquely identifies an entry + in the captureBuffer table associated with a + particular bufferControlEntry. This index will + start at 1 and increase by one for each new packet + added with the same captureBufferControlIndex. + + Should this value reach 2147483647, the next packet + added with the same captureBufferControlIndex shall + cause this value to wrap around to 1." + ::= { captureBufferEntry 2 } + +captureBufferPacketID OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index that describes the order of packets + that are received on a particular interface. + The packetID of a packet captured on an + interface is defined to be greater than the + packetID's of all packets captured previously on + the same interface. As the captureBufferPacketID + object has a maximum positive value of 2^31 - 1, + any captureBufferPacketID object shall have the + value of the associated packet's packetID mod 2^31." + ::= { captureBufferEntry 3 } + +captureBufferPacketData OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The data inside the packet, starting at the beginning + of the packet plus any offset specified in the + + associated bufferControlDownloadOffset, including any + link level headers. The length of the data in this object + is the minimum of the length of the captured packet minus + the offset, the length of the associated + bufferControlCaptureSliceSize minus the offset, and the + associated bufferControlDownloadSliceSize. If this minimum + is less than zero, this object shall have a length of zero." + ::= { captureBufferEntry 4 } + +captureBufferPacketLength OBJECT-TYPE + SYNTAX Integer32 + UNITS "Octets" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The actual length (off the wire) of the packet stored + in this entry, including FCS octets." + ::= { captureBufferEntry 5 } + +captureBufferPacketTime OBJECT-TYPE + SYNTAX Integer32 + UNITS "Milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of milliseconds that had passed since + this capture buffer was first turned on when this + packet was captured." + ::= { captureBufferEntry 6 } + +captureBufferPacketStatus OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A value which indicates the error status of this packet. + + The value of this object is defined in the same way as + filterPktStatus. The value is a sum. This sum + initially takes the value zero. Then, for each + error, E, that has been discovered in this packet, + 2 raised to a value representing E is added to the sum. + + The errors defined for a packet captured off of an + Ethernet interface are as follows: + + bit # Error + 0 Packet is longer than 1518 octets + + 1 Packet is shorter than 64 octets + 2 Packet experienced a CRC or Alignment error + 3 First packet in this capture buffer after + it was detected that some packets were + not processed correctly. + 4 Packet's order in buffer is only approximate + (May only be set for packets sent from + the probe) + + For example, an Ethernet fragment would have a + value of 6 (2^1 + 2^2). + + As this MIB is expanded to new media types, this object + will have other media-specific errors defined." + ::= { captureBufferEntry 7 } + +-- The Event Group + +-- Implementation of the Event group is optional. +-- Consult the MODULE-COMPLIANCE macro for the authoritative +-- conformance information for this MIB. +-- +-- The Event group controls the generation and notification +-- of events from this device. Each entry in the eventTable +-- describes the parameters of the event that can be triggered. +-- Each event entry is fired by an associated condition located +-- elsewhere in the MIB. An event entry may also be associated +-- with a function elsewhere in the MIB that will be executed +-- when the event is generated. For example, a channel may +-- be turned on or off by the firing of an event. +-- +-- Each eventEntry may optionally specify that a log entry +-- be created on its behalf whenever the event occurs. +-- Each entry may also specify that notification should +-- occur by way of SNMP trap messages. In this case, the +-- community for the trap message is given in the associated +-- eventCommunity object. The enterprise and specific trap +-- fields of the trap are determined by the condition that +-- triggered the event. Two traps are defined: risingAlarm and +-- fallingAlarm. If the eventTable is triggered by a condition +-- specified elsewhere, the enterprise and specific trap fields +-- must be specified for traps generated for that condition. + +eventTable OBJECT-TYPE + SYNTAX SEQUENCE OF EventEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of events to be generated." + ::= { event 1 } + +eventEntry OBJECT-TYPE + SYNTAX EventEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A set of parameters that describe an event to be generated + when certain conditions are met. As an example, an instance + of the eventLastTimeSent object might be named + eventLastTimeSent.6" + INDEX { eventIndex } + ::= { eventTable 1 } + +EventEntry ::= SEQUENCE { + eventIndex Integer32, + eventDescription DisplayString, + eventType INTEGER, + eventCommunity OCTET STRING, + eventLastTimeSent TimeTicks, + eventOwner OwnerString, + eventStatus EntryStatus +} + +eventIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index that uniquely identifies an entry in the + event table. Each such entry defines one event that + is to be generated when the appropriate conditions + occur." + ::= { eventEntry 1 } + +eventDescription OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..127)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A comment describing this event entry." + ::= { eventEntry 2 } + +eventType OBJECT-TYPE + SYNTAX INTEGER { + none(1), + log(2), + snmptrap(3), -- send an SNMP trap + logandtrap(4) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The type of notification that the probe will make + about this event. In the case of log, an entry is + made in the log table for each event. In the case of + snmp-trap, an SNMP trap is sent to one or more + management stations." + ::= { eventEntry 3 } + +eventCommunity OBJECT-TYPE + SYNTAX OCTET STRING (SIZE (0..127)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "If an SNMP trap is to be sent, it will be sent to + the SNMP community specified by this octet string." + ::= { eventEntry 4 } + +eventLastTimeSent OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time this event + entry last generated an event. If this entry has + not generated any events, this value will be + zero." + ::= { eventEntry 5 } + +eventOwner OBJECT-TYPE + SYNTAX OwnerString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The entity that configured this entry and is therefore + using the resources assigned to it. + + If this object contains a string starting with 'monitor' + and has associated entries in the log table, all connected + management stations should retrieve those log entries, + as they may have significance to all management stations + connected to this device" + ::= { eventEntry 6 } + +eventStatus OBJECT-TYPE + SYNTAX EntryStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this event entry. + + If this object is not equal to valid(1), all associated + log entries shall be deleted by the agent." + ::= { eventEntry 7 } + +-- +logTable OBJECT-TYPE + SYNTAX SEQUENCE OF LogEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A list of events that have been logged." + ::= { event 2 } + +logEntry OBJECT-TYPE + SYNTAX LogEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A set of data describing an event that has been + logged. For example, an instance of the logDescription + object might be named logDescription.6.47" + INDEX { logEventIndex, logIndex } + ::= { logTable 1 } + +LogEntry ::= SEQUENCE { + logEventIndex Integer32, + logIndex Integer32, + logTime TimeTicks, + logDescription DisplayString +} + +logEventIndex OBJECT-TYPE + SYNTAX Integer32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The event entry that generated this log + entry. The log identified by a particular + value of this index is associated with the same + eventEntry as identified by the same value + of eventIndex." + ::= { logEntry 1 } + +logIndex OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An index that uniquely identifies an entry + in the log table amongst those generated by the + same eventEntries. These indexes are + assigned beginning with 1 and increase by one + with each new log entry. The association + between values of logIndex and logEntries + is fixed for the lifetime of each logEntry. + The agent may choose to delete the oldest + instances of logEntry as required because of + lack of memory. It is an implementation-specific + matter as to when this deletion may occur." + ::= { logEntry 2 } + +logTime OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime when this log entry was created." + ::= { logEntry 3 } + +logDescription OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An implementation dependent description of the + event that activated this log entry." + ::= { logEntry 4 } + +-- Remote Network Monitoring Traps + +rmonEventsV2 OBJECT-IDENTITY + STATUS current + DESCRIPTION "Definition point for RMON notifications." + ::= { rmon 0 } + +risingAlarm NOTIFICATION-TYPE + OBJECTS { alarmIndex, alarmVariable, alarmSampleType, + alarmValue, alarmRisingThreshold } + STATUS current + DESCRIPTION + "The SNMP trap that is generated when an alarm + entry crosses its rising threshold and generates + an event that is configured for sending SNMP + traps." + ::= { rmonEventsV2 1 } + +fallingAlarm NOTIFICATION-TYPE + OBJECTS { alarmIndex, alarmVariable, alarmSampleType, + alarmValue, alarmFallingThreshold } + STATUS current + DESCRIPTION + "The SNMP trap that is generated when an alarm + entry crosses its falling threshold and generates + an event that is configured for sending SNMP + traps." + ::= { rmonEventsV2 2 } + +-- Conformance information + +rmonCompliances OBJECT IDENTIFIER ::= { rmonConformance 9 } +rmonGroups OBJECT IDENTIFIER ::= { rmonConformance 10 } + +-- Compliance Statements +rmonCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The requirements for conformance to the RMON MIB. At least + one of the groups in this module must be implemented to + conform to the RMON MIB. Implementations of this MIB + must also implement the system group of MIB-II [16] and the + IF-MIB [17]." + MODULE -- this module + + GROUP rmonEtherStatsGroup + DESCRIPTION + "The RMON Ethernet Statistics Group is optional." + + GROUP rmonHistoryControlGroup + DESCRIPTION + "The RMON History Control Group is optional." + + GROUP rmonEthernetHistoryGroup + DESCRIPTION + "The RMON Ethernet History Group is optional." + + GROUP rmonAlarmGroup + DESCRIPTION + "The RMON Alarm Group is optional." + + GROUP rmonHostGroup + DESCRIPTION + "The RMON Host Group is mandatory when the + rmonHostTopNGroup is implemented." + + GROUP rmonHostTopNGroup + DESCRIPTION + "The RMON Host Top N Group is optional." + + GROUP rmonMatrixGroup + DESCRIPTION + "The RMON Matrix Group is optional." + + GROUP rmonFilterGroup + DESCRIPTION + "The RMON Filter Group is mandatory when the + rmonPacketCaptureGroup is implemented." + + GROUP rmonPacketCaptureGroup + DESCRIPTION + "The RMON Packet Capture Group is optional." + + GROUP rmonEventGroup + DESCRIPTION + "The RMON Event Group is mandatory when the + rmonAlarmGroup is implemented." + ::= { rmonCompliances 1 } + + rmonEtherStatsGroup OBJECT-GROUP + OBJECTS { + etherStatsIndex, etherStatsDataSource, + etherStatsDropEvents, etherStatsOctets, etherStatsPkts, + etherStatsBroadcastPkts, etherStatsMulticastPkts, + etherStatsCRCAlignErrors, etherStatsUndersizePkts, + etherStatsOversizePkts, etherStatsFragments, + etherStatsJabbers, etherStatsCollisions, + etherStatsPkts64Octets, etherStatsPkts65to127Octets, + etherStatsPkts128to255Octets, + etherStatsPkts256to511Octets, + etherStatsPkts512to1023Octets, + etherStatsPkts1024to1518Octets, + etherStatsOwner, etherStatsStatus + } + STATUS current + DESCRIPTION + "The RMON Ethernet Statistics Group." + ::= { rmonGroups 1 } + + rmonHistoryControlGroup OBJECT-GROUP + OBJECTS { + historyControlIndex, historyControlDataSource, + historyControlBucketsRequested, + historyControlBucketsGranted, historyControlInterval, + historyControlOwner, historyControlStatus + } + STATUS current + DESCRIPTION + "The RMON History Control Group." + ::= { rmonGroups 2 } + + rmonEthernetHistoryGroup OBJECT-GROUP + OBJECTS { + etherHistoryIndex, etherHistorySampleIndex, + etherHistoryIntervalStart, etherHistoryDropEvents, + etherHistoryOctets, etherHistoryPkts, + etherHistoryBroadcastPkts, etherHistoryMulticastPkts, + etherHistoryCRCAlignErrors, etherHistoryUndersizePkts, + etherHistoryOversizePkts, etherHistoryFragments, + etherHistoryJabbers, etherHistoryCollisions, + etherHistoryUtilization + } + STATUS current + DESCRIPTION + "The RMON Ethernet History Group." + ::= { rmonGroups 3 } + + rmonAlarmGroup OBJECT-GROUP + OBJECTS { + alarmIndex, alarmInterval, alarmVariable, + alarmSampleType, alarmValue, alarmStartupAlarm, + alarmRisingThreshold, alarmFallingThreshold, + alarmRisingEventIndex, alarmFallingEventIndex, + alarmOwner, alarmStatus + } + STATUS current + DESCRIPTION + "The RMON Alarm Group." + ::= { rmonGroups 4 } + + rmonHostGroup OBJECT-GROUP + OBJECTS { + hostControlIndex, hostControlDataSource, + hostControlTableSize, hostControlLastDeleteTime, + hostControlOwner, hostControlStatus, + hostAddress, hostCreationOrder, hostIndex, + hostInPkts, hostOutPkts, hostInOctets, + hostOutOctets, hostOutErrors, hostOutBroadcastPkts, + hostOutMulticastPkts, hostTimeAddress, + hostTimeCreationOrder, hostTimeIndex, + hostTimeInPkts, hostTimeOutPkts, hostTimeInOctets, + hostTimeOutOctets, hostTimeOutErrors, + hostTimeOutBroadcastPkts, hostTimeOutMulticastPkts + } + STATUS current + DESCRIPTION + "The RMON Host Group." + ::= { rmonGroups 5 } + + rmonHostTopNGroup OBJECT-GROUP + OBJECTS { + hostTopNControlIndex, hostTopNHostIndex, + hostTopNRateBase, hostTopNTimeRemaining, + hostTopNDuration, hostTopNRequestedSize, + hostTopNGrantedSize, hostTopNStartTime, + hostTopNOwner, hostTopNStatus, + hostTopNReport, hostTopNIndex, + hostTopNAddress, hostTopNRate + } + STATUS current + DESCRIPTION + "The RMON Host Top 'N' Group." + ::= { rmonGroups 6 } + + rmonMatrixGroup OBJECT-GROUP + OBJECTS { + matrixControlIndex, matrixControlDataSource, + matrixControlTableSize, matrixControlLastDeleteTime, + matrixControlOwner, matrixControlStatus, + matrixSDSourceAddress, matrixSDDestAddress, + matrixSDIndex, matrixSDPkts, + matrixSDOctets, matrixSDErrors, + matrixDSSourceAddress, matrixDSDestAddress, + matrixDSIndex, matrixDSPkts, + matrixDSOctets, matrixDSErrors + } + STATUS current + DESCRIPTION + "The RMON Matrix Group." + ::= { rmonGroups 7 } + + rmonFilterGroup OBJECT-GROUP + OBJECTS { + + filterIndex, filterChannelIndex, filterPktDataOffset, + filterPktData, filterPktDataMask, + filterPktDataNotMask, filterPktStatus, + filterPktStatusMask, filterPktStatusNotMask, + filterOwner, filterStatus, + channelIndex, channelIfIndex, channelAcceptType, + channelDataControl, channelTurnOnEventIndex, + channelTurnOffEventIndex, channelEventIndex, + channelEventStatus, channelMatches, + channelDescription, channelOwner, channelStatus + } + STATUS current + DESCRIPTION + "The RMON Filter Group." + ::= { rmonGroups 8 } + + rmonPacketCaptureGroup OBJECT-GROUP + OBJECTS { + bufferControlIndex, bufferControlChannelIndex, + bufferControlFullStatus, bufferControlFullAction, + bufferControlCaptureSliceSize, + bufferControlDownloadSliceSize, + bufferControlDownloadOffset, + bufferControlMaxOctetsRequested, + bufferControlMaxOctetsGranted, + bufferControlCapturedPackets, + bufferControlTurnOnTime, + bufferControlOwner, bufferControlStatus, + captureBufferControlIndex, captureBufferIndex, + captureBufferPacketID, captureBufferPacketData, + captureBufferPacketLength, captureBufferPacketTime, + captureBufferPacketStatus + } + STATUS current + DESCRIPTION + "The RMON Packet Capture Group." + ::= { rmonGroups 9 } + + rmonEventGroup OBJECT-GROUP + OBJECTS { + eventIndex, eventDescription, eventType, + eventCommunity, eventLastTimeSent, + eventOwner, eventStatus, + logEventIndex, logIndex, logTime, + logDescription + } + STATUS current + DESCRIPTION + "The RMON Event Group." + ::= { rmonGroups 10 } + + rmonNotificationGroup NOTIFICATION-GROUP + NOTIFICATIONS { risingAlarm, fallingAlarm } + STATUS current + DESCRIPTION + "The RMON Notification Group." + ::= { rmonGroups 11 } +END diff --git a/mibs/SCTP-MIB.txt b/mibs/SCTP-MIB.txt new file mode 100644 index 0000000..9d809d2 --- /dev/null +++ b/mibs/SCTP-MIB.txt @@ -0,0 +1,1342 @@ +SCTP-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, Integer32, Unsigned32, Gauge32, + Counter32, Counter64, mib-2 + FROM SNMPv2-SMI -- [RFC2578] + TimeStamp, TruthValue + FROM SNMPv2-TC -- [RFC2579] + MODULE-COMPLIANCE, OBJECT-GROUP + FROM SNMPv2-CONF -- [RFC2580] + InetAddressType, InetAddress, InetPortNumber + FROM INET-ADDRESS-MIB; -- [RFC3291] + +sctpMIB MODULE-IDENTITY + LAST-UPDATED "200409020000Z" -- 2nd September 2004 + ORGANIZATION "IETF SIGTRAN Working Group" + CONTACT-INFO + " + WG EMail: sigtran@ietf.org + + Web Page: + http://www.ietf.org/html.charters/sigtran-charter.html + + Chair: Lyndon Ong + Ciena Corporation + 0480 Ridgeview Drive + Cupertino, CA 95014 + USA + Tel: + Email: lyong@ciena.com + + Editors: Maria-Carmen Belinchon + R&D Department + Ericsson Espana S. A. + Via de los Poblados, 13 + 28033 Madrid + Spain + Tel: +34 91 339 3535 + Email: Maria.C.Belinchon@ericsson.com + + Jose-Javier Pastor-Balbas + R&D Department + Ericsson Espana S. A. + Via de los Poblados, 13 + 28033 Madrid + Spain + Tel: +34 91 339 1397 + Email: J.Javier.Pastor@ericsson.com + " + DESCRIPTION + "The MIB module for managing SCTP implementations. + + Copyright (C) The Internet Society (2004). This version of + this MIB module is part of RFC 3873; see the RFC itself for + full legal notices. " + + REVISION "200409020000Z" -- 2nd September 2004 + DESCRIPTION " Initial version, published as RFC 3873" + ::= { mib-2 104 } + +-- the SCTP base variables group + +sctpObjects OBJECT IDENTIFIER ::= { sctpMIB 1 } + +sctpStats OBJECT IDENTIFIER ::= { sctpObjects 1 } +sctpParams OBJECT IDENTIFIER ::= { sctpObjects 2 } + +-- STATISTICS +-- ********** + +-- STATE-RELATED STATISTICS + +sctpCurrEstab OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of associations for which the current state is + either ESTABLISHED, SHUTDOWN-RECEIVED or SHUTDOWN-PENDING." + REFERENCE + "Section 4 in RFC2960 covers the SCTP Association state + diagram." + ::= { sctpStats 1 } + +sctpActiveEstabs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times that associations have made a direct + transition to the ESTABLISHED state from the COOKIE-ECHOED + state: COOKIE-ECHOED -> ESTABLISHED. The upper layer initiated + the association attempt." + REFERENCE + "Section 4 in RFC2960 covers the SCTP Association state + diagram." + ::= { sctpStats 2 } + +sctpPassiveEstabs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times that associations have made a direct + transition to the ESTABLISHED state from the CLOSED state: + CLOSED -> ESTABLISHED. The remote endpoint initiated the + association attempt." + REFERENCE + "Section 4 in RFC2960 covers the SCTP Association state + diagram." + ::= { sctpStats 3 } + +sctpAborteds OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times that associations have made a direct + transition to the CLOSED state from any state using the + primitive 'ABORT': AnyState --Abort--> CLOSED. Ungraceful + termination of the association." + REFERENCE + "Section 4 in RFC2960 covers the SCTP Association state + diagram." + ::= { sctpStats 4 } + +sctpShutdowns OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times that associations have made a direct + transition to the CLOSED state from either the SHUTDOWN-SENT + state or the SHUTDOWN-ACK-SENT state. Graceful termination of + the association." + REFERENCE + "Section 4 in RFC2960 covers the SCTP Association state + diagram." + ::= { sctpStats 5 } + +-- OTHER LAYER STATISTICS + +sctpOutOfBlues OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of out of the blue packets received by the host. + An out of the blue packet is an SCTP packet correctly formed, + including the proper checksum, but for which the receiver was + unable to identify an appropriate association." + REFERENCE + "Section 8.4 in RFC2960 deals with the Out-Of-The-Blue + (OOTB) packet definition and procedures." + ::= { sctpStats 6 } + +sctpChecksumErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of SCTP packets received with an invalid + checksum." + REFERENCE + "The checksum is located at the end of the SCTP packet as per + Section 3.1 in RFC2960. RFC3309 updates SCTP to use a 32 bit + CRC checksum." +::= { sctpStats 7 } + +sctpOutCtrlChunks OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of SCTP control chunks sent (retransmissions are + not included). Control chunks are those chunks different from + DATA." + REFERENCE + "Sections 1.3.5 and 1.4 in RFC2960 refer to control chunk as + those chunks different from those that contain user + information, i.e., DATA chunks." + ::= { sctpStats 8 } + +sctpOutOrderChunks OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of SCTP ordered data chunks sent (retransmissions + are not included)." + REFERENCE + "Section 3.3.1 in RFC2960 defines the ordered data chunk." + ::= { sctpStats 9 } + +sctpOutUnorderChunks OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of SCTP unordered chunks (data chunks in which the + U bit is set to 1) sent (retransmissions are not included)." + REFERENCE + "Section 3.3.1 in RFC2960 defines the unordered data chunk." + ::= { sctpStats 10 } + +sctpInCtrlChunks OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of SCTP control chunks received (no duplicate + chunks included)." + REFERENCE + "Sections 1.3.5 and 1.4 in RFC2960 refer to control chunk as + those chunks different from those that contain user + information, i.e., DATA chunks." + ::= { sctpStats 11 } + +sctpInOrderChunks OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of SCTP ordered data chunks received (no duplicate + chunks included)." + REFERENCE + "Section 3.3.1 in RFC2960 defines the ordered data chunk." + ::= { sctpStats 12 } + +sctpInUnorderChunks OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of SCTP unordered chunks (data chunks in which the + U bit is set to 1) received (no duplicate chunks included)." + REFERENCE + "Section 3.3.1 in RFC2960 defines the unordered data chunk." + ::= { sctpStats 13 } + +sctpFragUsrMsgs OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of user messages that have to be fragmented + because of the MTU." + ::= { sctpStats 14 } + +sctpReasmUsrMsgs OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of user messages reassembled, after conversion + into DATA chunks." + REFERENCE + "Section 6.9 in RFC2960 includes a description of the + reassembly process." + ::= { sctpStats 15 } + +sctpOutSCTPPacks OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of SCTP packets sent. Retransmitted DATA chunks + are included." + ::= { sctpStats 16 } + +sctpInSCTPPacks OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of SCTP packets received. Duplicates are + included." + ::= { sctpStats 17 } + +sctpDiscontinuityTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime on the most recent occasion at which + any one or more of this general statistics counters suffered a + discontinuity. The relevant counters are the specific + instances associated with this interface of any Counter32 or + Counter64 object contained in the SCTP layer statistics + (defined below sctpStats branch). If no such discontinuities + have occurred since the last re-initialization of the local + management subsystem, then this object contains a zero value." + REFERENCE + "The inclusion of this object is recommended by RFC2578." + ::= { sctpStats 18 } + +-- PROTOCOL GENERAL VARIABLES +-- ************************** + +sctpRtoAlgorithm OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- Other new one. Future use + vanj(2) -- Van Jacobson's algorithm + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The algorithm used to determine the timeout value (T3-rtx) + used for re-transmitting unacknowledged chunks." + REFERENCE + "Section 6.3.1 and 6.3.2 in RFC2960 cover the RTO calculation + and retransmission timer rules." + DEFVAL {vanj} -- vanj(2) + ::= { sctpParams 1 } + +sctpRtoMin OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The minimum value permitted by a SCTP implementation for the + retransmission timeout value, measured in milliseconds. More + refined semantics for objects of this type depend upon the + algorithm used to determine the retransmission timeout value. + + A retransmission time value of zero means immediate + retransmission. + + The value of this object has to be lower than or equal to + stcpRtoMax's value." + DEFVAL {1000} -- milliseconds + ::= { sctpParams 2 } + +sctpRtoMax OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The maximum value permitted by a SCTP implementation for the + retransmission timeout value, measured in milliseconds. More + refined semantics for objects of this type depend upon the + algorithm used to determine the retransmission timeout value. + + A retransmission time value of zero means immediate re- + transmission. + + The value of this object has to be greater than or equal to + stcpRtoMin's value." + DEFVAL {60000} -- milliseconds + ::= { sctpParams 3 } + +sctpRtoInitial OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The initial value for the retransmission timer. + + A retransmission time value of zero means immediate re- + transmission." + DEFVAL {3000} -- milliseconds + ::= { sctpParams 4 } + +sctpMaxAssocs OBJECT-TYPE + SYNTAX Integer32 (-1 | 0..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The limit on the total number of associations the entity can + support. In entities where the maximum number of associations + is dynamic, this object should contain the value -1." + ::= { sctpParams 5 } + +sctpValCookieLife OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Valid cookie life in the 4-way start-up handshake procedure." + REFERENCE + "Section 5.1.3 in RFC2960 explains the cookie generation + process. Recommended value is per section 14 in RFC2960." + DEFVAL {60000} -- milliseconds + ::= { sctpParams 6 } + +sctpMaxInitRetr OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The maximum number of retransmissions at the start-up phase + (INIT and COOKIE ECHO chunks). " + REFERENCE + "Section 5.1.4, 5.1.6 in RFC2960 refers to Max.Init.Retransmit + parameter. Recommended value is per section 14 in RFC2960." + DEFVAL {8} -- number of attempts + ::= { sctpParams 7 } + +-- TABLES +-- ****** + +-- the SCTP Association TABLE + +-- The SCTP association table contains information about each +-- association in which the local endpoint is involved. + +sctpAssocTable OBJECT-TYPE + SYNTAX SEQUENCE OF SctpAssocEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table containing SCTP association-specific information." + ::= { sctpObjects 3 } + +sctpAssocEntry OBJECT-TYPE + SYNTAX SctpAssocEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "General common variables and statistics for the whole + association." + INDEX { sctpAssocId } + ::= { sctpAssocTable 1 } + +SctpAssocEntry ::= SEQUENCE { + sctpAssocId Unsigned32, + sctpAssocRemHostName OCTET STRING, + sctpAssocLocalPort InetPortNumber, + sctpAssocRemPort InetPortNumber, + sctpAssocRemPrimAddrType InetAddressType, + sctpAssocRemPrimAddr InetAddress, + sctpAssocHeartBeatInterval Unsigned32, + sctpAssocState INTEGER, + sctpAssocInStreams Unsigned32, + sctpAssocOutStreams Unsigned32, + sctpAssocMaxRetr Unsigned32, + sctpAssocPrimProcess Unsigned32, + sctpAssocT1expireds Counter32, -- Statistic + sctpAssocT2expireds Counter32, -- Statistic + sctpAssocRtxChunks Counter32, -- Statistic + sctpAssocStartTime TimeStamp, + sctpAssocDiscontinuityTime TimeStamp + } + +sctpAssocId OBJECT-TYPE + SYNTAX Unsigned32 (1..4294967295) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Association Identification. Value identifying the + association. " + ::= { sctpAssocEntry 1 } + +sctpAssocRemHostName OBJECT-TYPE + SYNTAX OCTET STRING (SIZE(0..255)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The peer's DNS name. This object needs to have the same + format as the encoding in the DNS protocol. This implies that + the domain name can be up to 255 octets long, each octet being + 0<=x<=255 as value with US-ASCII A-Z having a case insensitive + matching. + + If no DNS domain name was received from the peer at init time + (embedded in the INIT or INIT-ACK chunk), this object is + meaningless. In such cases the object MUST contain a zero- + length string value. Otherwise, it contains the remote host + name received at init time." + ::= { sctpAssocEntry 2 } + +sctpAssocLocalPort OBJECT-TYPE + SYNTAX InetPortNumber (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The local SCTP port number used for this association." + ::= { sctpAssocEntry 3 } + +sctpAssocRemPort OBJECT-TYPE + SYNTAX InetPortNumber (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The remote SCTP port number used for this association." + ::= { sctpAssocEntry 4 } + +sctpAssocRemPrimAddrType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The internet type of primary remote IP address. " + ::= { sctpAssocEntry 5 } + +sctpAssocRemPrimAddr OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The primary remote IP address. The type of this address is + determined by the value of sctpAssocRemPrimAddrType. + + The client side will know this value after INIT_ACK message + reception, the server side will know this value when sending + INIT_ACK message. However, values will be filled in at + established(4) state." + ::= { sctpAssocEntry 6 } + +sctpAssocHeartBeatInterval OBJECT-TYPE + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current heartbeat interval.. + + Zero value means no HeartBeat, even when the concerned + sctpAssocRemAddrHBFlag object is true." + DEFVAL {30000} -- milliseconds + ::= { sctpAssocEntry 7 } + +sctpAssocState OBJECT-TYPE + SYNTAX INTEGER { + closed(1), + cookieWait(2), + cookieEchoed(3), + established(4), + shutdownPending(5), + shutdownSent(6), + shutdownReceived(7), + shutdownAckSent(8), + deleteTCB(9) + } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The state of this SCTP association. + + As in TCP, deleteTCB(9) is the only value that may be set by a + management station. If any other value is received, then the + agent must return a wrongValue error. + + If a management station sets this object to the value + deleteTCB(9), then this has the effect of deleting the TCB (as + defined in SCTP) of the corresponding association on the + managed node, resulting in immediate termination of the + association. + + As an implementation-specific option, an ABORT chunk may be + sent from the managed node to the other SCTP endpoint as a + result of setting the deleteTCB(9) value. The ABORT chunk + implies an ungraceful association shutdown." + REFERENCE + "Section 4 in RFC2960 covers the SCTP Association state + diagram." + ::= { sctpAssocEntry 8 } + +sctpAssocInStreams OBJECT-TYPE + SYNTAX Unsigned32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Inbound Streams according to the negotiation at association + start up." + REFERENCE + "Section 1.3 in RFC2960 includes a definition of stream. + Section 5.1.1 in RFC2960 covers the streams negotiation + process." + ::= { sctpAssocEntry 9 } + +sctpAssocOutStreams OBJECT-TYPE + SYNTAX Unsigned32 (1..65535) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Outbound Streams according to the negotiation at association + start up. " + REFERENCE + "Section 1.3 in RFC2960 includes a definition of stream. + Section 5.1.1 in RFC2960 covers the streams negotiation + process." + ::= { sctpAssocEntry 10 } + +sctpAssocMaxRetr OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The maximum number of data retransmissions in the association + context. This value is specific for each association and the + upper layer can change it by calling the appropriate + primitives. This value has to be smaller than the addition of + all the maximum number for all the paths + (sctpAssocRemAddrMaxPathRtx). + + A value of zero value means no retransmissions." + DEFVAL {10} -- number of attempts + ::= { sctpAssocEntry 11 } + +sctpAssocPrimProcess OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object identifies the system level process which holds + primary responsibility for the SCTP association. + Wherever possible, this should be the system's native unique + identification number. The special value 0 can be used to + indicate that no primary process is known. + + Note that the value of this object can be used as a pointer + into the swRunTable of the HOST-RESOURCES-MIB(if the value is + smaller than 2147483647) or into the sysApplElmtRunTable of + the SYSAPPL-MIB." + ::= { sctpAssocEntry 12 } + +-- Association Statistics + +sctpAssocT1expireds OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The T1 timer determines how long to wait for an + acknowledgement after sending an INIT or COOKIE-ECHO chunk. + This object reflects the number of times the T1 timer expires + without having received the acknowledgement. + + Discontinuities in the value of this counter can occur at re- + initialization of the management system, and at other times as + indicated by the value of sctpAssocDiscontinuityTime." + REFERENCE + "Section 5 in RFC2960." + ::= { sctpAssocEntry 13 } + +sctpAssocT2expireds OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The T2 timer determines how long to wait for an + acknowledgement after sending a SHUTDOWN or SHUTDOWN-ACK + chunk. This object reflects the number of times that T2- timer + expired. + + Discontinuities in the value of this counter can occur at re- + initialization of the management system, and at other times as + indicated by the value of sctpAssocDiscontinuityTime." +REFERENCE + "Section 9.2 in RFC2960." + ::= { sctpAssocEntry 14 } + +sctpAssocRtxChunks OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "When T3-rtx expires, the DATA chunks that triggered the T3 + timer will be re-sent according with the retransmissions + rules. Every DATA chunk that was included in the SCTP packet + that triggered the T3-rtx timer must be added to the value of + this counter. + + Discontinuities in the value of this counter can occur at re- + initialization of the management system, and at other times as + indicated by the value of sctpAssocDiscontinuityTime." + REFERENCE + "Section 6 in RFC2960 covers the retransmission process and + rules." + ::= { sctpAssocEntry 15 } + +sctpAssocStartTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time that the association + represented by this row enters the ESTABLISHED state, i.e., + the sctpAssocState object is set to established(4). The + value of this object will be zero: + - before the association enters the established(4) + state, or + + - if the established(4) state was entered prior to + the last re-initialization of the local network management + subsystem." + ::= { sctpAssocEntry 16 } + +sctpAssocDiscontinuityTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime on the most recent occasion at which + any one or more of this SCTP association counters suffered a + discontinuity. The relevant counters are the specific + instances associated with this interface of any Counter32 or + Counter64 object contained in the sctpAssocTable or + sctpLocalAddrTable or sctpRemAddrTable. If no such + discontinuities have occurred since the last re-initialization + of the local management subsystem, then this object contains a + zero value. " + REFERENCE + "The inclusion of this object is recommended by RFC2578." + ::= { sctpAssocEntry 17 } + +-- Expanded tables: Including Multi-home feature + +-- Local Address TABLE +-- ******************* + +sctpAssocLocalAddrTable OBJECT-TYPE + SYNTAX SEQUENCE OF SctpAssocLocalAddrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Expanded table of sctpAssocTable based on the AssocId index. + This table shows data related to each local IP address which + is used by this association." + ::= { sctpObjects 4 } + +sctpAssocLocalAddrEntry OBJECT-TYPE + SYNTAX SctpAssocLocalAddrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Local information about the available addresses. There will + be an entry for every local IP address defined for this + + association. + Implementors need to be aware that if the size of + sctpAssocLocalAddr exceeds 114 octets then OIDs of column + instances in this table will have more than 128 sub- + identifiers and cannot be accessed using SNMPv1, SNMPv2c, or + SNMPv3." + INDEX { sctpAssocId, -- shared index + sctpAssocLocalAddrType, + sctpAssocLocalAddr } + ::= { sctpAssocLocalAddrTable 1 } + +SctpAssocLocalAddrEntry ::= SEQUENCE { + sctpAssocLocalAddrType InetAddressType, + sctpAssocLocalAddr InetAddress, + sctpAssocLocalAddrStartTime TimeStamp + } + +sctpAssocLocalAddrType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Internet type of local IP address used for this association." + ::= { sctpAssocLocalAddrEntry 1 } + +sctpAssocLocalAddr OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The value of a local IP address available for this + association. The type of this address is determined by the + value of sctpAssocLocalAddrType." + ::= { sctpAssocLocalAddrEntry 2 } + +sctpAssocLocalAddrStartTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time that this row was + created." + ::= { sctpAssocLocalAddrEntry 3 } + +-- Remote Addresses TABLE +-- ********************** + +sctpAssocRemAddrTable OBJECT-TYPE + SYNTAX SEQUENCE OF SctpAssocRemAddrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Expanded table of sctpAssocTable based on the AssocId index. + This table shows data related to each remote peer IP address + which is used by this association." + ::= { sctpObjects 5 } + +sctpAssocRemAddrEntry OBJECT-TYPE + SYNTAX SctpAssocRemAddrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about the most important variables for every + remote IP address. There will be an entry for every remote IP + address defined for this association. + + Implementors need to be aware that if the size of + sctpAssocRemAddr exceeds 114 octets then OIDs of column + instances in this table will have more than 128 sub- + identifiers and cannot be accessed using SNMPv1, SNMPv2c, or + SNMPv3." + INDEX { sctpAssocId, -- shared index + sctpAssocRemAddrType, + sctpAssocRemAddr } + ::= { sctpAssocRemAddrTable 1 } + +SctpAssocRemAddrEntry ::= SEQUENCE { + sctpAssocRemAddrType InetAddressType, + sctpAssocRemAddr InetAddress, + sctpAssocRemAddrActive TruthValue, + sctpAssocRemAddrHBActive TruthValue, + sctpAssocRemAddrRTO Unsigned32, + sctpAssocRemAddrMaxPathRtx Unsigned32, + sctpAssocRemAddrRtx Counter32, -- Statistic + sctpAssocRemAddrStartTime TimeStamp + } + +sctpAssocRemAddrType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Internet type of a remote IP address available for this + association." + ::= { sctpAssocRemAddrEntry 1 } + +sctpAssocRemAddr OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The value of a remote IP address available for this + association. The type of this address is determined by the + value of sctpAssocLocalAddrType." + ::= { sctpAssocRemAddrEntry 2 } + +sctpAssocRemAddrActive OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object gives information about the reachability of this + specific remote IP address. + + When the object is set to 'true' (1), the remote IP address is + understood as Active. Active means that the threshold of no + answers received from this IP address has not been reached. + + When the object is set to 'false' (2), the remote IP address + is understood as Inactive. Inactive means that either no + heartbeat or any other message was received from this address, + reaching the threshold defined by the protocol." + REFERENCE + "The remote transport states are defined as Active and + Inactive in the SCTP, RFC2960." + ::= { sctpAssocRemAddrEntry 3 } + +sctpAssocRemAddrHBActive OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "This object indicates whether the optional Heartbeat check + associated to one destination transport address is activated + or not (value equal to true or false, respectively). " + ::= { sctpAssocRemAddrEntry 4 } + +sctpAssocRemAddrRTO OBJECT-TYPE -- T3-rtx- Timer + SYNTAX Unsigned32 + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current Retransmission Timeout. T3-rtx timer as defined + in the protocol SCTP." + REFERENCE + "Section 6.3 in RFC2960 deals with the Retransmission Timer + Management." + ::= { sctpAssocRemAddrEntry 5 } + +sctpAssocRemAddrMaxPathRtx OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Maximum number of DATA chunks retransmissions allowed to a + remote IP address before it is considered inactive, as defined + in RFC2960." + REFERENCE + "Section 8.2, 8.3 and 14 in RFC2960." + DEFVAL {5} -- number of attempts + ::= { sctpAssocRemAddrEntry 6 } + +-- Remote Address Statistic + +sctpAssocRemAddrRtx OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Number of DATA chunks retransmissions to this specific IP + address. When T3-rtx expires, the DATA chunk that triggered + the T3 timer will be re-sent according to the retransmissions + rules. Every DATA chunk that is included in a SCTP packet and + was transmitted to this specific IP address before, will be + included in this counter. + + Discontinuities in the value of this counter can occur at re- + initialization of the management system, and at other times as + indicated by the value of sctpAssocDiscontinuityTime." + ::= { sctpAssocRemAddrEntry 7 } + +sctpAssocRemAddrStartTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time that this row was + created." + ::= { sctpAssocRemAddrEntry 8 } + +-- ASSOCIATION INVERSE TABLE +-- ************************* + +-- BY LOCAL PORT + +sctpLookupLocalPortTable OBJECT-TYPE + SYNTAX SEQUENCE OF SctpLookupLocalPortEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "With the use of this table, a list of associations which are + + using the specified local port can be retrieved." + ::= { sctpObjects 6 } + +sctpLookupLocalPortEntry OBJECT-TYPE + SYNTAX SctpLookupLocalPortEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table is indexed by local port and association ID. + Specifying a local port, we would get a list of the + associations whose local port is the one specified." + INDEX { sctpAssocLocalPort, + sctpAssocId } + ::= { sctpLookupLocalPortTable 1 } + +SctpLookupLocalPortEntry::= SEQUENCE { + sctpLookupLocalPortStartTime TimeStamp + } + +sctpLookupLocalPortStartTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time that this row was created. + + As the table will be created after the sctpAssocTable + creation, this value could be equal to the sctpAssocStartTime + object from the main table." + ::= { sctpLookupLocalPortEntry 1 } + +-- BY REMOTE PORT + +sctpLookupRemPortTable OBJECT-TYPE + SYNTAX SEQUENCE OF SctpLookupRemPortEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "With the use of this table, a list of associations which are + using the specified remote port can be got" + ::= { sctpObjects 7 } + +sctpLookupRemPortEntry OBJECT-TYPE + SYNTAX SctpLookupRemPortEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table is indexed by remote port and association ID. + Specifying a remote port we would get a list of the + associations whose local port is the one specified " + INDEX { sctpAssocRemPort, + sctpAssocId } + ::= { sctpLookupRemPortTable 1 } + +SctpLookupRemPortEntry::= SEQUENCE { + sctpLookupRemPortStartTime TimeStamp + } + +sctpLookupRemPortStartTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time that this row was created. + + As the table will be created after the sctpAssocTable + creation, this value could be equal to the sctpAssocStartTime + object from the main table." + ::= { sctpLookupRemPortEntry 1 } + +-- BY REMOTE HOST NAME + +sctpLookupRemHostNameTable OBJECT-TYPE + SYNTAX SEQUENCE OF SctpLookupRemHostNameEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "With the use of this table, a list of associations with that + particular host can be retrieved." + ::= { sctpObjects 8 } + +sctpLookupRemHostNameEntry OBJECT-TYPE + SYNTAX SctpLookupRemHostNameEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table is indexed by remote host name and association ID. + Specifying a host name we would get a list of the associations + specifying that host name as the remote one. + + Implementors need to be aware that if the size of + sctpAssocRemHostName exceeds 115 octets then OIDs of column + instances in this table will have more than 128 sub- + identifiers and cannot be accessed using SNMPv1, SNMPv2c, or + SNMPv3." + INDEX { sctpAssocRemHostName, + sctpAssocId } + ::= { sctpLookupRemHostNameTable 1 } + +SctpLookupRemHostNameEntry::= SEQUENCE { + sctpLookupRemHostNameStartTime TimeStamp + } + +sctpLookupRemHostNameStartTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time that this row was created. + + As the table will be created after the sctpAssocTable + creation, this value could be equal to the sctpAssocStartTime + object from the main table." + ::= { sctpLookupRemHostNameEntry 1 } + +-- BY REMOTE PRIMARY IP ADDRESS + +sctpLookupRemPrimIPAddrTable OBJECT-TYPE + SYNTAX SEQUENCE OF SctpLookupRemPrimIPAddrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "With the use of this table, a list of associations that have + the specified IP address as primary within the remote set of + active addresses can be retrieved." + ::= { sctpObjects 9 } + +sctpLookupRemPrimIPAddrEntry OBJECT-TYPE + SYNTAX SctpLookupRemPrimIPAddrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table is indexed by primary address and association ID. + Specifying a primary address, we would get a list of the + associations that have the specified remote IP address marked + as primary. + Implementors need to be aware that if the size of + sctpAssocRemPrimAddr exceeds 114 octets then OIDs of column + instances in this table will have more than 128 sub- + identifiers and cannot be accessed using SNMPv1, SNMPv2c, or + SNMPv3." + INDEX { sctpAssocRemPrimAddrType, + sctpAssocRemPrimAddr, + sctpAssocId } + ::= { sctpLookupRemPrimIPAddrTable 1 } + +SctpLookupRemPrimIPAddrEntry::= SEQUENCE { + sctpLookupRemPrimIPAddrStartTime TimeStamp + } + +sctpLookupRemPrimIPAddrStartTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of SysUpTime at the time that this row was created. + + As the table will be created after the sctpAssocTable + creation, this value could be equal to the sctpAssocStartTime + object from the main table." + ::= { sctpLookupRemPrimIPAddrEntry 1 } + +-- BY REMOTE IP ADDRESS + +sctpLookupRemIPAddrTable OBJECT-TYPE + SYNTAX SEQUENCE OF SctpLookupRemIPAddrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "With the use of this table, a list of associations that have + the specified IP address as one of the remote ones can be + retrieved. " + ::= { sctpObjects 10 } + +sctpLookupRemIPAddrEntry OBJECT-TYPE + SYNTAX SctpLookupRemIPAddrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table is indexed by a remote IP address and association + ID. Specifying an IP address we would get a list of the + associations that have the specified IP address included + within the set of remote IP addresses." + INDEX { sctpAssocRemAddrType, + sctpAssocRemAddr, + sctpAssocId } + ::= { sctpLookupRemIPAddrTable 1 } + +SctpLookupRemIPAddrEntry::= SEQUENCE { + + sctpLookupRemIPAddrStartTime TimeStamp + } + +sctpLookupRemIPAddrStartTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of SysUpTime at the time that this row was created. + + As the table will be created after the sctpAssocTable + creation, this value could be equal to the sctpAssocStartTime + object from the main table." + ::= { sctpLookupRemIPAddrEntry 1 } + +-- 4.1 Conformance Information + +sctpMibConformance OBJECT IDENTIFIER ::= { sctpMIB 2 } +sctpMibCompliances OBJECT IDENTIFIER ::= { sctpMibConformance 1 } +sctpMibGroups OBJECT IDENTIFIER ::= { sctpMibConformance 2 } + +-- 4.1.1 Units of conformance + +-- +-- MODULE GROUPS +-- + +sctpLayerParamsGroup OBJECT-GROUP + OBJECTS { sctpRtoAlgorithm, + sctpRtoMin, + sctpRtoMax, + sctpRtoInitial, + sctpMaxAssocs, + sctpValCookieLife, + sctpMaxInitRetr + } + STATUS current + DESCRIPTION + "Common parameters for the SCTP layer, i.e., for all the + associations. They can usually be referred to as configuration + parameters." + ::= { sctpMibGroups 1 } + +sctpStatsGroup OBJECT-GROUP + OBJECTS { sctpCurrEstab, + sctpActiveEstabs, + sctpPassiveEstabs, + sctpAborteds, + sctpShutdowns, + sctpOutOfBlues, + sctpChecksumErrors, + sctpOutCtrlChunks, + sctpOutOrderChunks, + sctpOutUnorderChunks, + sctpInCtrlChunks, + sctpInOrderChunks, + sctpInUnorderChunks, + sctpFragUsrMsgs, + sctpReasmUsrMsgs, + sctpOutSCTPPacks, + sctpInSCTPPacks, + sctpDiscontinuityTime, + sctpAssocT1expireds, + sctpAssocT2expireds, + sctpAssocRtxChunks, + sctpAssocRemAddrRtx + } + STATUS current + DESCRIPTION + "Statistics group. It includes the objects to collect state + changes in the SCTP protocol local layer and flow control + statistics." + ::= { sctpMibGroups 2 } + +sctpPerAssocParamsGroup OBJECT-GROUP + OBJECTS { sctpAssocRemHostName, + sctpAssocLocalPort, + sctpAssocRemPort, + sctpAssocRemPrimAddrType, + sctpAssocRemPrimAddr, + sctpAssocHeartBeatInterval, + sctpAssocState, + sctpAssocInStreams, + sctpAssocOutStreams, + sctpAssocMaxRetr, + sctpAssocPrimProcess, + sctpAssocStartTime, + sctpAssocDiscontinuityTime, + sctpAssocLocalAddrStartTime, + sctpAssocRemAddrActive, + sctpAssocRemAddrHBActive, + sctpAssocRemAddrRTO, + sctpAssocRemAddrMaxPathRtx, + sctpAssocRemAddrStartTime + } + STATUS current + DESCRIPTION + "The SCTP group of objects to manage per-association + parameters. These variables include all the SCTP basic + features." + ::= { sctpMibGroups 3 } + +sctpPerAssocStatsGroup OBJECT-GROUP + OBJECTS + { sctpAssocT1expireds, + sctpAssocT2expireds, + sctpAssocRtxChunks, + sctpAssocRemAddrRtx + } + STATUS current + DESCRIPTION + "Per Association Statistics group. It includes the objects to + collect flow control statistics per association." + ::= { sctpMibGroups 4 } + +sctpInverseGroup OBJECT-GROUP + OBJECTS { sctpLookupLocalPortStartTime, + sctpLookupRemPortStartTime, + sctpLookupRemHostNameStartTime, + sctpLookupRemPrimIPAddrStartTime, + sctpLookupRemIPAddrStartTime + } + STATUS current + DESCRIPTION + "Objects used in the inverse lookup tables." + ::= { sctpMibGroups 5 } + +-- 4.1.2 Compliance Statements + +-- +-- MODULE COMPLIANCES +-- + +sctpMibCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP entities which implement + this SCTP MIB Module. + + There are a number of INDEX objects that cannot be represented + in the form of OBJECT clauses in SMIv2, but for which we have + the following compliance requirements, expressed in OBJECT + clause form in this description clause: + +-- OBJECT sctpAssocLocalAddrType +-- SYNTAX InetAddressType {ipv4(1), ipv6(2)} +-- DESCRIPTION +-- It is only required to have IPv4 and IPv6 addresses without +-- zone indices. +-- The address with zone indices is required if an +-- implementation can connect multiple zones. +-- +-- OBJECT sctpAssocLocalAddr +-- SYNTAX InetAddress (SIZE(4|16)) +-- DESCRIPTION +-- An implementation is only required to support globally +-- unique IPv4 and IPv6 addresses. +-- +-- OBJECT sctpAssocRemAddrType +-- SYNTAX InetAddressType {ipv4(1), ipv6(2)} +-- DESCRIPTION +-- It is only required to have IPv4 and IPv6 addresses without +-- zone indices. +-- The address with zone indices is required if an +-- implementation can connect multiple zones. +-- +-- OBJECT sctpAssocRemAddr +-- SYNTAX InetAddress (SIZE(4|16)) +-- DESCRIPTION +-- An implementation is only required to support globally +-- unique IPv4 and IPv6 addresses. +-- + " -- closes DESCRIPTION clause of MODULE-COMPLIANCE + + MODULE -- this module + + MANDATORY-GROUPS { sctpLayerParamsGroup, + sctpPerAssocParamsGroup, + sctpStatsGroup, + sctpPerAssocStatsGroup + } + + OBJECT sctpAssocRemPrimAddrType + SYNTAX InetAddressType { ipv4(1), + ipv6(2) + } + DESCRIPTION + "It is only required to have IPv4 and IPv6 addresses + without zone indices. + + The address with zone indices is required if an + implementation can connect multiple zones." + + OBJECT sctpAssocRemPrimAddr + SYNTAX InetAddress (SIZE(4|16)) + DESCRIPTION + "An implementation is only required to support globally + unique IPv4 and globally unique IPv6 addresses." + + OBJECT sctpAssocState + WRITE-SYNTAX INTEGER { deleteTCB(9) } + MIN-ACCESS read-only + DESCRIPTION + "Only the deleteTCB(9) value MAY be set by a management + station at most. A read-only option is also considered to + be compliant with this MIB module description." + + GROUP sctpInverseGroup + DESCRIPTION + "Objects used in inverse lookup tables. This should be + implemented, at the discretion of the implementers, for + easier lookups in the association tables" + ::= { sctpMibCompliances 1 } + +END diff --git a/mibs/SMUX-MIB.txt b/mibs/SMUX-MIB.txt new file mode 100644 index 0000000..1fe3455 --- /dev/null +++ b/mibs/SMUX-MIB.txt @@ -0,0 +1,160 @@ +SMUX-MIB DEFINITIONS ::= BEGIN + +IMPORTS + enterprises + FROM RFC1155-SMI + DisplayString + FROM SNMPv2-TC + OBJECT-TYPE + FROM RFC-1212; + +unix OBJECT IDENTIFIER ::= { enterprises 4 } + +smux OBJECT IDENTIFIER ::= { unix 4 } + +smuxPeerTable OBJECT-TYPE + SYNTAX SEQUENCE OF SmuxPeerEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "The SMUX peer table." + ::= { smux 1 } + +smuxPeerEntry OBJECT-TYPE + SYNTAX SmuxPeerEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "An entry in the SMUX peer table." + INDEX { smuxPindex } + ::= { smuxPeerTable 1} + +SmuxPeerEntry ::= + SEQUENCE { + smuxPindex + INTEGER, + smuxPidentity + OBJECT IDENTIFIER, + smuxPdescription + DisplayString, + smuxPstatus + INTEGER + } + +smuxPindex OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "An index which uniquely identifies a SMUX peer." + ::= { smuxPeerEntry 1 } + +smuxPidentity OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The authoritative designation for a SMUX peer." + ::= { smuxPeerEntry 2 } + +smuxPdescription OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A human-readable description of a SMUX peer." + ::= { smuxPeerEntry 3 } + +smuxPstatus OBJECT-TYPE + SYNTAX INTEGER { valid(1), invalid(2), connecting(3) } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The type of SMUX peer. + + Setting this object to the value invalid(2) has + the effect of invaliding the corresponding entry + in the smuxPeerTable. It is an implementation- + specific matter as to whether the agent removes an + invalidated entry from the table. Accordingly, + management stations must be prepared to receive + tabular information from agents that correspond to + entries not currently in use. Proper + interpretation of such entries requires + examination of the relative smuxPstatus object." + ::= { smuxPeerEntry 4 } + +smuxTreeTable OBJECT-TYPE + SYNTAX SEQUENCE OF SmuxTreeEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "The SMUX tree table." + ::= { smux 2 } + +smuxTreeEntry OBJECT-TYPE + SYNTAX SmuxTreeEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "An entry in the SMUX tree table." + INDEX { smuxTsubtree, smuxTpriority } + ::= { smuxTreeTable 1} + +SmuxTreeEntry ::= + SEQUENCE { + smuxTsubtree + OBJECT IDENTIFIER, + smuxTpriority + INTEGER, + smuxTindex + INTEGER, + smuxTstatus + INTEGER + } + +smuxTsubtree OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The MIB subtree being exported by a SMUX peer." + ::= { smuxTreeEntry 1 } + +smuxTpriority OBJECT-TYPE + SYNTAX INTEGER (0..'07fffffff'h) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The SMUX peer's priority when exporting the MIB + subtree." + ::= { smuxTreeEntry 2 } + +smuxTindex OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The SMUX peer's identity." + ::= { smuxTreeEntry 3 } + +smuxTstatus OBJECT-TYPE + SYNTAX INTEGER { valid(1), invalid(2) } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The type of SMUX tree. + + Setting this object to the value invalid(2) has + the effect of invaliding the corresponding entry + in the smuxTreeTable. It is an implementation- + specific matter as to whether the agent removes an + invalidated entry from the table. Accordingly, + management stations must be prepared to receive + tabular information from agents that correspond to + entries not currently in use. Proper + interpretation of such entries requires + examination of the relative smuxTstatus object." + ::= { smuxTreeEntry 4 } + +END diff --git a/mibs/SNMP-COMMUNITY-MIB.txt b/mibs/SNMP-COMMUNITY-MIB.txt new file mode 100644 index 0000000..bc3d4d2 --- /dev/null +++ b/mibs/SNMP-COMMUNITY-MIB.txt @@ -0,0 +1,429 @@ +SNMP-COMMUNITY-MIB DEFINITIONS ::= BEGIN + +IMPORTS + IpAddress, + MODULE-IDENTITY, + OBJECT-TYPE, + Integer32, + snmpModules + FROM SNMPv2-SMI + RowStatus, + StorageType + FROM SNMPv2-TC + SnmpAdminString, + SnmpEngineID + FROM SNMP-FRAMEWORK-MIB + SnmpTagValue, + snmpTargetAddrEntry + FROM SNMP-TARGET-MIB + MODULE-COMPLIANCE, + OBJECT-GROUP + FROM SNMPv2-CONF; + +snmpCommunityMIB MODULE-IDENTITY + LAST-UPDATED "200003060000Z" -- 6 Mar 2000, midnight + ORGANIZATION "SNMPv3 Working Group" + CONTACT-INFO "WG-email: snmpv3@lists.tislabs.com + Subscribe: majordomo@lists.tislabs.com + In msg body: subscribe snmpv3 + + Chair: Russ Mundy + TIS Labs at Network Associates + Postal: 3060 Washington Rd + Glenwood MD 21738 + USA + Email: mundy@tislabs.com + Phone: +1-301-854-6889 + + Co-editor: Rob Frye + CoSine Communications + Postal: 1200 Bridge Parkway + Redwood City, CA 94065 + USA + E-mail: rfrye@cosinecom.com + Phone: +1 703 725 1130 + + Co-editor: David B. Levi + Nortel Networks + Postal: 3505 Kesterwood Drive + Knoxville, TN 37918 + E-mail: dlevi@nortelnetworks.com + Phone: +1 423 686 0432 + + Co-editor: Shawn A. Routhier + Integrated Systems Inc. + Postal: 333 North Ave 4th Floor + Wakefield, MA 01880 + E-mail: sar@epilogue.com + Phone: +1 781 245 0804 + + Co-editor: Bert Wijnen + Lucent Technologies + Postal: Schagen 33 + 3461 GL Linschoten + Netherlands + Email: bwijnen@lucent.com + Phone: +31-348-407-775 + " + DESCRIPTION + "This MIB module defines objects to help support coexistence + between SNMPv1, SNMPv2c, and SNMPv3." + REVISION "200003060000Z" -- 6 Mar 2000 + DESCRIPTION "This version published as RFC 2576." + REVISION "199905130000Z" -- 13 May 1999 + DESCRIPTION "The Initial Revision" + ::= { snmpModules 18 } + +-- Administrative assignments **************************************** + +snmpCommunityMIBObjects OBJECT IDENTIFIER ::= { snmpCommunityMIB 1 } +snmpCommunityMIBConformance OBJECT IDENTIFIER ::= { snmpCommunityMIB 2 } + +-- +-- The snmpCommunityTable contains a database of community strings. +-- This table provides mappings between community strings, and the + +-- parameters required for View-based Access Control. +-- + +snmpCommunityTable OBJECT-TYPE + SYNTAX SEQUENCE OF SnmpCommunityEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table of community strings configured in the SNMP + engine's Local Configuration Datastore (LCD)." + ::= { snmpCommunityMIBObjects 1 } + +snmpCommunityEntry OBJECT-TYPE + SYNTAX SnmpCommunityEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a particular community string." + INDEX { IMPLIED snmpCommunityIndex } + ::= { snmpCommunityTable 1 } + +SnmpCommunityEntry ::= SEQUENCE { + snmpCommunityIndex SnmpAdminString, + snmpCommunityName OCTET STRING, + snmpCommunitySecurityName SnmpAdminString, + snmpCommunityContextEngineID SnmpEngineID, + snmpCommunityContextName SnmpAdminString, + snmpCommunityTransportTag SnmpTagValue, + snmpCommunityStorageType StorageType, + snmpCommunityStatus RowStatus +} + +snmpCommunityIndex OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(1..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The unique index value of a row in this table." + ::= { snmpCommunityEntry 1 } + +snmpCommunityName OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The community string for which a row in this table + represents a configuration." + ::= { snmpCommunityEntry 2 } + +snmpCommunitySecurityName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(1..32)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "A human readable string representing the corresponding + value of snmpCommunityName in a Security Model + independent format." + ::= { snmpCommunityEntry 3 } + +snmpCommunityContextEngineID OBJECT-TYPE + SYNTAX SnmpEngineID + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The contextEngineID indicating the location of the + context in which management information is accessed + when using the community string specified by the + corresponding instance of snmpCommunityName. + + The default value is the snmpEngineID of the entity in + which this object is instantiated." + ::= { snmpCommunityEntry 4 } + +snmpCommunityContextName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The context in which management information is accessed + when using the community string specified by the corresponding + instance of snmpCommunityName." + DEFVAL { ''H } -- the empty string + ::= { snmpCommunityEntry 5 } + +snmpCommunityTransportTag OBJECT-TYPE + SYNTAX SnmpTagValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object specifies a set of transport endpoints + from which a command responder application will accept + management requests. If a management request containing + this community is received on a transport endpoint other + than the transport endpoints identified by this object, + the request is deemed unauthentic. + + The transports identified by this object are specified + + in the snmpTargetAddrTable. Entries in that table + whose snmpTargetAddrTagList contains this tag value + are identified. + + If the value of this object has zero-length, transport + endpoints are not checked when authenticating messages + containing this community string." + DEFVAL { ''H } -- the empty string + ::= { snmpCommunityEntry 6 } + +snmpCommunityStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The storage type for this conceptual row in the + snmpCommunityTable. Conceptual rows having the value + 'permanent' need not allow write-access to any + columnar object in the row." + ::= { snmpCommunityEntry 7 } + +snmpCommunityStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this conceptual row in the snmpCommunityTable. + + An entry in this table is not qualified for activation + until instances of all corresponding columns have been + initialized, either through default values, or through + Set operations. The snmpCommunityName and + snmpCommunitySecurityName objects must be explicitly set. + + There is no restriction on setting columns in this table + when the value of snmpCommunityStatus is active(1)." + ::= { snmpCommunityEntry 8 } + +-- +-- The snmpTargetAddrExtTable +-- + +snmpTargetAddrExtTable OBJECT-TYPE + SYNTAX SEQUENCE OF SnmpTargetAddrExtEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table of mask and mms values associated with the + + snmpTargetAddrTable. + + The snmpTargetAddrExtTable augments the + snmpTargetAddrTable with a transport address mask value + and a maximum message size value. The transport address + mask allows entries in the snmpTargetAddrTable to define + a set of addresses instead of just a single address. + The maximum message size value allows the maximum + message size of another SNMP entity to be configured for + use in SNMPv1 (and SNMPv2c) transactions, where the + message format does not specify a maximum message size." + ::= { snmpCommunityMIBObjects 2 } + +snmpTargetAddrExtEntry OBJECT-TYPE + SYNTAX SnmpTargetAddrExtEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a particular mask and mms value." + AUGMENTS { snmpTargetAddrEntry } + ::= { snmpTargetAddrExtTable 1 } + +SnmpTargetAddrExtEntry ::= SEQUENCE { + snmpTargetAddrTMask OCTET STRING, + snmpTargetAddrMMS Integer32 +} + +snmpTargetAddrTMask OBJECT-TYPE + SYNTAX OCTET STRING (SIZE (0..255)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The mask value associated with an entry in the + snmpTargetAddrTable. The value of this object must + have the same length as the corresponding instance of + snmpTargetAddrTAddress, or must have length 0. An + attempt to set it to any other value will result in + an inconsistentValue error. + + The value of this object allows an entry in the + snmpTargetAddrTable to specify multiple addresses. + The mask value is used to select which bits of + a transport address must match bits of the corresponding + instance of snmpTargetAddrTAddress, in order for the + transport address to match a particular entry in the + snmpTargetAddrTable. Bits which are 1 in the mask + value indicate bits in the transport address which + must match bits in the snmpTargetAddrTAddress value. + + Bits which are 0 in the mask indicate bits in the + transport address which need not match. If the + length of the mask is 0, the mask should be treated + as if all its bits were 1 and its length were equal + to the length of the corresponding value of + snmpTargetAddrTable. + + This object may not be modified while the value of the + corresponding instance of snmpTargetAddrRowStatus is + active(1). An attempt to set this object in this case + will result in an inconsistentValue error." + DEFVAL { ''H } + ::= { snmpTargetAddrExtEntry 1 } + +snmpTargetAddrMMS OBJECT-TYPE + SYNTAX Integer32 (0|484..2147483647) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The maximum message size value associated with an entry + in the snmpTargetAddrTable." + DEFVAL { 484 } + ::= { snmpTargetAddrExtEntry 2 } + +-- +-- The snmpTrapAddress and snmpTrapCommunity objects are included +-- in notifications that are forwarded by a proxy, which were +-- originally received as SNMPv1 Trap messages. +-- + +snmpTrapAddress OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "The value of the agent-addr field of a Trap PDU which + is forwarded by a proxy forwarder application using + an SNMP version other than SNMPv1. The value of this + object SHOULD contain the value of the agent-addr field + from the original Trap PDU as generated by an SNMPv1 + agent." + ::= { snmpCommunityMIBObjects 3 } + +snmpTrapCommunity OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "The value of the community string field of an SNMPv1 + message containing a Trap PDU which is forwarded by a + a proxy forwarder application using an SNMP version + other than SNMPv1. The value of this object SHOULD + contain the value of the community string field from + the original SNMPv1 message containing a Trap PDU as + generated by an SNMPv1 agent." + ::= { snmpCommunityMIBObjects 4 } + +-- Conformance Information ******************************************* + +snmpCommunityMIBCompliances OBJECT IDENTIFIER + ::= { snmpCommunityMIBConformance 1 } +snmpCommunityMIBGroups OBJECT IDENTIFIER + ::= { snmpCommunityMIBConformance 2 } + +-- Compliance statements + +snmpCommunityMIBCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP engines which + implement the SNMP-COMMUNITY-MIB." + + MODULE -- this module + MANDATORY-GROUPS { snmpCommunityGroup } + + OBJECT snmpCommunityName + MIN-ACCESS read-only + DESCRIPTION "Write access is not required." + + OBJECT snmpCommunitySecurityName + MIN-ACCESS read-only + DESCRIPTION "Write access is not required." + + OBJECT snmpCommunityContextEngineID + MIN-ACCESS read-only + DESCRIPTION "Write access is not required." + + OBJECT snmpCommunityContextName + MIN-ACCESS read-only + DESCRIPTION "Write access is not required." + + OBJECT snmpCommunityTransportTag + MIN-ACCESS read-only + DESCRIPTION "Write access is not required." + + OBJECT snmpCommunityStorageType + MIN-ACCESS read-only + DESCRIPTION "Write access is not required." + + OBJECT snmpCommunityStatus + MIN-ACCESS read-only + DESCRIPTION "Write access is not required." + ::= { snmpCommunityMIBCompliances 1 } + +snmpProxyTrapForwardCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP engines which + contain a proxy forwarding application which is + capable of forwarding SNMPv1 traps using SNMPv2c + or SNMPv3." + MODULE -- this module + MANDATORY-GROUPS { snmpProxyTrapForwardGroup } + ::= { snmpCommunityMIBCompliances 2 } + +snmpCommunityGroup OBJECT-GROUP + OBJECTS { + snmpCommunityName, + snmpCommunitySecurityName, + snmpCommunityContextEngineID, + snmpCommunityContextName, + snmpCommunityTransportTag, + snmpCommunityStorageType, + snmpCommunityStatus, + snmpTargetAddrTMask, + snmpTargetAddrMMS + } + STATUS current + DESCRIPTION + "A collection of objects providing for configuration + of community strings for SNMPv1 (and SNMPv2c) usage." + ::= { snmpCommunityMIBGroups 1 } + +snmpProxyTrapForwardGroup OBJECT-GROUP + OBJECTS { + snmpTrapAddress, + snmpTrapCommunity + } + STATUS current + DESCRIPTION + "Objects which are used by proxy forwarding applications + when translating traps between SNMP versions. These are + used to preserve SNMPv1-specific information when + + translating to SNMPv2c or SNMPv3." + ::= { snmpCommunityMIBGroups 3 } + +END diff --git a/mibs/SNMP-FRAMEWORK-MIB.txt b/mibs/SNMP-FRAMEWORK-MIB.txt new file mode 100644 index 0000000..aa273c2 --- /dev/null +++ b/mibs/SNMP-FRAMEWORK-MIB.txt @@ -0,0 +1,526 @@ +SNMP-FRAMEWORK-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, + OBJECT-IDENTITY, + snmpModules FROM SNMPv2-SMI + TEXTUAL-CONVENTION FROM SNMPv2-TC + MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF; + +snmpFrameworkMIB MODULE-IDENTITY + LAST-UPDATED "200210140000Z" + ORGANIZATION "SNMPv3 Working Group" + CONTACT-INFO "WG-EMail: snmpv3@lists.tislabs.com + Subscribe: snmpv3-request@lists.tislabs.com + + Co-Chair: Russ Mundy + Network Associates Laboratories + postal: 15204 Omega Drive, Suite 300 + Rockville, MD 20850-4601 + USA + EMail: mundy@tislabs.com + phone: +1 301-947-7107 + + Co-Chair & + Co-editor: David Harrington + Enterasys Networks + postal: 35 Industrial Way + P. O. Box 5005 + Rochester, New Hampshire 03866-5005 + USA + EMail: dbh@enterasys.com + phone: +1 603-337-2614 + + Co-editor: Randy Presuhn + BMC Software, Inc. + postal: 2141 North First Street + San Jose, California 95131 + USA + EMail: randy_presuhn@bmc.com + phone: +1 408-546-1006 + + Co-editor: Bert Wijnen + Lucent Technologies + postal: Schagen 33 + 3461 GL Linschoten + Netherlands + + EMail: bwijnen@lucent.com + phone: +31 348-680-485 + " + DESCRIPTION "The SNMP Management Architecture MIB + + Copyright (C) The Internet Society (2002). This + version of this MIB module is part of RFC 3411; + see the RFC itself for full legal notices. + " + + REVISION "200210140000Z" -- 14 October 2002 + DESCRIPTION "Changes in this revision: + - Updated various administrative information. + - Corrected some typos. + - Corrected typo in description of SnmpEngineID + that led to range overlap for 127. + - Changed '255a' to '255t' in definition of + SnmpAdminString to align with current SMI. + - Reworded 'reserved' for value zero in + DESCRIPTION of SnmpSecurityModel. + - The algorithm for allocating security models + should give 256 per enterprise block, rather + than 255. + - The example engine ID of 'abcd' is not + legal. Replaced with '800002b804616263'H based + on example enterprise 696, string 'abc'. + - Added clarification that engineID should + persist across re-initializations. + This revision published as RFC 3411. + " + REVISION "199901190000Z" -- 19 January 1999 + DESCRIPTION "Updated editors' addresses, fixed typos. + Published as RFC 2571. + " + REVISION "199711200000Z" -- 20 November 1997 + DESCRIPTION "The initial version, published in RFC 2271. + " + ::= { snmpModules 10 } + + -- Textual Conventions used in the SNMP Management Architecture *** + +SnmpEngineID ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION "An SNMP engine's administratively-unique identifier. + Objects of this type are for identification, not for + addressing, even though it is possible that an + address may have been used in the generation of + a specific value. + + The value for this object may not be all zeros or + all 'ff'H or the empty (zero length) string. + + The initial value for this object may be configured + via an operator console entry or via an algorithmic + function. In the latter case, the following + example algorithm is recommended. + + In cases where there are multiple engines on the + same system, the use of this algorithm is NOT + appropriate, as it would result in all of those + engines ending up with the same ID value. + + 1) The very first bit is used to indicate how the + rest of the data is composed. + + 0 - as defined by enterprise using former methods + that existed before SNMPv3. See item 2 below. + + 1 - as defined by this architecture, see item 3 + below. + + Note that this allows existing uses of the + engineID (also known as AgentID [RFC1910]) to + co-exist with any new uses. + + 2) The snmpEngineID has a length of 12 octets. + + The first four octets are set to the binary + equivalent of the agent's SNMP management + private enterprise number as assigned by the + Internet Assigned Numbers Authority (IANA). + For example, if Acme Networks has been assigned + { enterprises 696 }, the first four octets would + be assigned '000002b8'H. + + The remaining eight octets are determined via + one or more enterprise-specific methods. Such + methods must be designed so as to maximize the + possibility that the value of this object will + be unique in the agent's administrative domain. + For example, it may be the IP address of the SNMP + entity, or the MAC address of one of the + interfaces, with each address suitably padded + with random octets. If multiple methods are + defined, then it is recommended that the first + octet indicate the method being used and the + remaining octets be a function of the method. + + 3) The length of the octet string varies. + + The first four octets are set to the binary + equivalent of the agent's SNMP management + private enterprise number as assigned by the + Internet Assigned Numbers Authority (IANA). + For example, if Acme Networks has been assigned + { enterprises 696 }, the first four octets would + be assigned '000002b8'H. + + The very first bit is set to 1. For example, the + above value for Acme Networks now changes to be + '800002b8'H. + + The fifth octet indicates how the rest (6th and + following octets) are formatted. The values for + the fifth octet are: + + 0 - reserved, unused. + + 1 - IPv4 address (4 octets) + lowest non-special IP address + + 2 - IPv6 address (16 octets) + lowest non-special IP address + + 3 - MAC address (6 octets) + lowest IEEE MAC address, canonical + order + + 4 - Text, administratively assigned + Maximum remaining length 27 + + 5 - Octets, administratively assigned + Maximum remaining length 27 + + 6-127 - reserved, unused + + 128-255 - as defined by the enterprise + Maximum remaining length 27 + " + SYNTAX OCTET STRING (SIZE(5..32)) + +SnmpSecurityModel ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION "An identifier that uniquely identifies a + Security Model of the Security Subsystem within + this SNMP Management Architecture. + + The values for securityModel are allocated as + follows: + + - The zero value does not identify any particular + security model. + + - Values between 1 and 255, inclusive, are reserved + for standards-track Security Models and are + managed by the Internet Assigned Numbers Authority + (IANA). + - Values greater than 255 are allocated to + enterprise-specific Security Models. An + enterprise-specific securityModel value is defined + to be: + + enterpriseID * 256 + security model within + enterprise + + For example, the fourth Security Model defined by + the enterprise whose enterpriseID is 1 would be + 259. + + This scheme for allocation of securityModel + values allows for a maximum of 255 standards- + based Security Models, and for a maximum of + 256 Security Models per enterprise. + + It is believed that the assignment of new + securityModel values will be rare in practice + because the larger the number of simultaneously + utilized Security Models, the larger the + chance that interoperability will suffer. + Consequently, it is believed that such a range + will be sufficient. In the unlikely event that + the standards committee finds this number to be + insufficient over time, an enterprise number + can be allocated to obtain an additional 256 + possible values. + + Note that the most significant bit must be zero; + hence, there are 23 bits allocated for various + organizations to design and define non-standard + + securityModels. This limits the ability to + define new proprietary implementations of Security + Models to the first 8,388,608 enterprises. + + It is worthwhile to note that, in its encoded + form, the securityModel value will normally + require only a single byte since, in practice, + the leftmost bits will be zero for most messages + and sign extension is suppressed by the encoding + rules. + + As of this writing, there are several values + of securityModel defined for use with SNMP or + reserved for use with supporting MIB objects. + They are as follows: + + 0 reserved for 'any' + 1 reserved for SNMPv1 + 2 reserved for SNMPv2c + 3 User-Based Security Model (USM) + " + SYNTAX INTEGER(0 .. 2147483647) + +SnmpMessageProcessingModel ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION "An identifier that uniquely identifies a Message + Processing Model of the Message Processing + Subsystem within this SNMP Management Architecture. + + The values for messageProcessingModel are + allocated as follows: + + - Values between 0 and 255, inclusive, are + reserved for standards-track Message Processing + Models and are managed by the Internet Assigned + Numbers Authority (IANA). + + - Values greater than 255 are allocated to + enterprise-specific Message Processing Models. + An enterprise messageProcessingModel value is + defined to be: + + enterpriseID * 256 + + messageProcessingModel within enterprise + + For example, the fourth Message Processing Model + defined by the enterprise whose enterpriseID + + is 1 would be 259. + + This scheme for allocating messageProcessingModel + values allows for a maximum of 255 standards- + based Message Processing Models, and for a + maximum of 256 Message Processing Models per + enterprise. + + It is believed that the assignment of new + messageProcessingModel values will be rare + in practice because the larger the number of + simultaneously utilized Message Processing Models, + the larger the chance that interoperability + will suffer. It is believed that such a range + will be sufficient. In the unlikely event that + the standards committee finds this number to be + insufficient over time, an enterprise number + can be allocated to obtain an additional 256 + possible values. + + Note that the most significant bit must be zero; + hence, there are 23 bits allocated for various + organizations to design and define non-standard + messageProcessingModels. This limits the ability + to define new proprietary implementations of + Message Processing Models to the first 8,388,608 + enterprises. + + It is worthwhile to note that, in its encoded + form, the messageProcessingModel value will + normally require only a single byte since, in + practice, the leftmost bits will be zero for + most messages and sign extension is suppressed + by the encoding rules. + + As of this writing, there are several values of + messageProcessingModel defined for use with SNMP. + They are as follows: + + 0 reserved for SNMPv1 + 1 reserved for SNMPv2c + 2 reserved for SNMPv2u and SNMPv2* + 3 reserved for SNMPv3 + " + SYNTAX INTEGER(0 .. 2147483647) + +SnmpSecurityLevel ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION "A Level of Security at which SNMP messages can be + sent or with which operations are being processed; + in particular, one of: + + noAuthNoPriv - without authentication and + without privacy, + authNoPriv - with authentication but + without privacy, + authPriv - with authentication and + with privacy. + + These three values are ordered such that + noAuthNoPriv is less than authNoPriv and + authNoPriv is less than authPriv. + " + SYNTAX INTEGER { noAuthNoPriv(1), + authNoPriv(2), + authPriv(3) + } + +SnmpAdminString ::= TEXTUAL-CONVENTION + DISPLAY-HINT "255t" + STATUS current + DESCRIPTION "An octet string containing administrative + information, preferably in human-readable form. + + To facilitate internationalization, this + information is represented using the ISO/IEC + IS 10646-1 character set, encoded as an octet + string using the UTF-8 transformation format + described in [RFC2279]. + + Since additional code points are added by + amendments to the 10646 standard from time + to time, implementations must be prepared to + encounter any code point from 0x00000000 to + 0x7fffffff. Byte sequences that do not + correspond to the valid UTF-8 encoding of a + code point or are outside this range are + prohibited. + + The use of control codes should be avoided. + + When it is necessary to represent a newline, + the control code sequence CR LF should be used. + + The use of leading or trailing white space should + be avoided. + + For code points not directly supported by user + interface hardware or software, an alternative + means of entry and display, such as hexadecimal, + may be provided. + + For information encoded in 7-bit US-ASCII, + the UTF-8 encoding is identical to the + US-ASCII encoding. + + UTF-8 may require multiple bytes to represent a + single character / code point; thus the length + of this object in octets may be different from + the number of characters encoded. Similarly, + size constraints refer to the number of encoded + octets, not the number of characters represented + by an encoding. + + Note that when this TC is used for an object that + is used or envisioned to be used as an index, then + a SIZE restriction MUST be specified so that the + number of sub-identifiers for any object instance + does not exceed the limit of 128, as defined by + [RFC3416]. + + Note that the size of an SnmpAdminString object is + measured in octets, not characters. + " + SYNTAX OCTET STRING (SIZE (0..255)) + +-- Administrative assignments *************************************** + +snmpFrameworkAdmin + OBJECT IDENTIFIER ::= { snmpFrameworkMIB 1 } +snmpFrameworkMIBObjects + OBJECT IDENTIFIER ::= { snmpFrameworkMIB 2 } +snmpFrameworkMIBConformance + OBJECT IDENTIFIER ::= { snmpFrameworkMIB 3 } + +-- the snmpEngine Group ******************************************** + +snmpEngine OBJECT IDENTIFIER ::= { snmpFrameworkMIBObjects 1 } + +snmpEngineID OBJECT-TYPE + SYNTAX SnmpEngineID + MAX-ACCESS read-only + STATUS current + DESCRIPTION "An SNMP engine's administratively-unique identifier. + + This information SHOULD be stored in non-volatile + storage so that it remains constant across + re-initializations of the SNMP engine. + " + ::= { snmpEngine 1 } + +snmpEngineBoots OBJECT-TYPE + SYNTAX INTEGER (1..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION "The number of times that the SNMP engine has + (re-)initialized itself since snmpEngineID + was last configured. + " + ::= { snmpEngine 2 } + +snmpEngineTime OBJECT-TYPE + SYNTAX INTEGER (0..2147483647) + UNITS "seconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION "The number of seconds since the value of + the snmpEngineBoots object last changed. + When incrementing this object's value would + cause it to exceed its maximum, + snmpEngineBoots is incremented as if a + re-initialization had occurred, and this + object's value consequently reverts to zero. + " + ::= { snmpEngine 3 } + +snmpEngineMaxMessageSize OBJECT-TYPE + SYNTAX INTEGER (484..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION "The maximum length in octets of an SNMP message + which this SNMP engine can send or receive and + process, determined as the minimum of the maximum + message size values supported among all of the + transports available to and supported by the engine. + " + ::= { snmpEngine 4 } + +-- Registration Points for Authentication and Privacy Protocols ** + +snmpAuthProtocols OBJECT-IDENTITY + STATUS current + DESCRIPTION "Registration point for standards-track + authentication protocols used in SNMP Management + Frameworks. + " + ::= { snmpFrameworkAdmin 1 } + +snmpPrivProtocols OBJECT-IDENTITY + STATUS current + DESCRIPTION "Registration point for standards-track privacy + protocols used in SNMP Management Frameworks. + " + ::= { snmpFrameworkAdmin 2 } + +-- Conformance information ****************************************** + +snmpFrameworkMIBCompliances + OBJECT IDENTIFIER ::= {snmpFrameworkMIBConformance 1} +snmpFrameworkMIBGroups + OBJECT IDENTIFIER ::= {snmpFrameworkMIBConformance 2} + +-- compliance statements + +snmpFrameworkMIBCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION "The compliance statement for SNMP engines which + implement the SNMP Management Framework MIB. + " + MODULE -- this module + MANDATORY-GROUPS { snmpEngineGroup } + ::= { snmpFrameworkMIBCompliances 1 } + +-- units of conformance + +snmpEngineGroup OBJECT-GROUP + OBJECTS { + snmpEngineID, + snmpEngineBoots, + snmpEngineTime, + snmpEngineMaxMessageSize + } + STATUS current + DESCRIPTION "A collection of objects for identifying and + determining the configuration and current timeliness + + values of an SNMP engine. + " + ::= { snmpFrameworkMIBGroups 1 } + +END diff --git a/mibs/SNMP-MPD-MIB.txt b/mibs/SNMP-MPD-MIB.txt new file mode 100644 index 0000000..d4c605b --- /dev/null +++ b/mibs/SNMP-MPD-MIB.txt @@ -0,0 +1,145 @@ +SNMP-MPD-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF + MODULE-IDENTITY, OBJECT-TYPE, + snmpModules, Counter32 FROM SNMPv2-SMI; + +snmpMPDMIB MODULE-IDENTITY + LAST-UPDATED "200210140000Z" + ORGANIZATION "SNMPv3 Working Group" + CONTACT-INFO "WG-EMail: snmpv3@lists.tislabs.com + Subscribe: snmpv3-request@lists.tislabs.com + + Co-Chair: Russ Mundy + Network Associates Laboratories + postal: 15204 Omega Drive, Suite 300 + Rockville, MD 20850-4601 + USA + + EMail: mundy@tislabs.com + phone: +1 301-947-7107 + + Co-Chair & + Co-editor: David Harrington + Enterasys Networks + postal: 35 Industrial Way + P. O. Box 5005 + Rochester NH 03866-5005 + USA + EMail: dbh@enterasys.com + phone: +1 603-337-2614 + + Co-editor: Jeffrey Case + SNMP Research, Inc. + postal: 3001 Kimberlin Heights Road + Knoxville, TN 37920-9716 + USA + EMail: case@snmp.com + phone: +1 423-573-1434 + + Co-editor: Randy Presuhn + BMC Software, Inc. + postal: 2141 North First Street + San Jose, CA 95131 + USA + EMail: randy_presuhn@bmc.com + phone: +1 408-546-1006 + + Co-editor: Bert Wijnen + Lucent Technologies + postal: Schagen 33 + 3461 GL Linschoten + Netherlands + EMail: bwijnen@lucent.com + phone: +31 348-680-485 + " + DESCRIPTION "The MIB for Message Processing and Dispatching + + Copyright (C) The Internet Society (2002). This + version of this MIB module is part of RFC 3412; + see the RFC itself for full legal notices. + " + REVISION "200210140000Z" -- 14 October 2002 + DESCRIPTION "Updated addresses, published as RFC 3412." + REVISION "199905041636Z" -- 4 May 1999 + DESCRIPTION "Updated addresses, published as RFC 2572." + + REVISION "199709300000Z" -- 30 September 1997 + DESCRIPTION "Original version, published as RFC 2272." + ::= { snmpModules 11 } + +-- Administrative assignments *************************************** + +snmpMPDAdmin OBJECT IDENTIFIER ::= { snmpMPDMIB 1 } +snmpMPDMIBObjects OBJECT IDENTIFIER ::= { snmpMPDMIB 2 } +snmpMPDMIBConformance OBJECT IDENTIFIER ::= { snmpMPDMIB 3 } + +-- Statistics for SNMP Messages ************************************* + +snmpMPDStats OBJECT IDENTIFIER ::= { snmpMPDMIBObjects 1 } + +snmpUnknownSecurityModels OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "The total number of packets received by the SNMP + engine which were dropped because they referenced a + securityModel that was not known to or supported by + the SNMP engine. + " + ::= { snmpMPDStats 1 } + +snmpInvalidMsgs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "The total number of packets received by the SNMP + engine which were dropped because there were invalid + or inconsistent components in the SNMP message. + " + ::= { snmpMPDStats 2 } + +snmpUnknownPDUHandlers OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "The total number of packets received by the SNMP + engine which were dropped because the PDU contained + in the packet could not be passed to an application + responsible for handling the pduType, e.g. no SNMP + application had registered for the proper + combination of the contextEngineID and the pduType. + " + ::= { snmpMPDStats 3 } + +-- Conformance information ****************************************** + +snmpMPDMIBCompliances OBJECT IDENTIFIER ::= {snmpMPDMIBConformance 1} +snmpMPDMIBGroups OBJECT IDENTIFIER ::= {snmpMPDMIBConformance 2} + +-- Compliance statements + +snmpMPDCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION "The compliance statement for SNMP entities which + implement the SNMP-MPD-MIB. + " + MODULE -- this module + MANDATORY-GROUPS { snmpMPDGroup } + ::= { snmpMPDMIBCompliances 1 } + +snmpMPDGroup OBJECT-GROUP + OBJECTS { + snmpUnknownSecurityModels, + snmpInvalidMsgs, + snmpUnknownPDUHandlers + } + STATUS current + DESCRIPTION "A collection of objects providing for remote + monitoring of the SNMP Message Processing and + Dispatching process. + " + ::= { snmpMPDMIBGroups 1 } + +END diff --git a/mibs/SNMP-NOTIFICATION-MIB.txt b/mibs/SNMP-NOTIFICATION-MIB.txt new file mode 100644 index 0000000..0ef06b6 --- /dev/null +++ b/mibs/SNMP-NOTIFICATION-MIB.txt @@ -0,0 +1,589 @@ +SNMP-NOTIFICATION-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, + OBJECT-TYPE, + snmpModules + FROM SNMPv2-SMI + + RowStatus, + StorageType + FROM SNMPv2-TC + + SnmpAdminString + FROM SNMP-FRAMEWORK-MIB + + SnmpTagValue, + snmpTargetParamsName + FROM SNMP-TARGET-MIB + + MODULE-COMPLIANCE, + OBJECT-GROUP + FROM SNMPv2-CONF; + +snmpNotificationMIB MODULE-IDENTITY + LAST-UPDATED "200210140000Z" + ORGANIZATION "IETF SNMPv3 Working Group" + CONTACT-INFO + "WG-email: snmpv3@lists.tislabs.com + Subscribe: majordomo@lists.tislabs.com + In message body: subscribe snmpv3 + + Co-Chair: Russ Mundy + Network Associates Laboratories + Postal: 15204 Omega Drive, Suite 300 + Rockville, MD 20850-4601 + USA + EMail: mundy@tislabs.com + Phone: +1 301-947-7107 + + Co-Chair: David Harrington + Enterasys Networks + Postal: 35 Industrial Way + P. O. Box 5004 + Rochester, New Hampshire 03866-5005 + USA + EMail: dbh@enterasys.com + Phone: +1 603-337-2614 + + Co-editor: David B. Levi + Nortel Networks + Postal: 3505 Kesterwood Drive + Knoxville, Tennessee 37918 + EMail: dlevi@nortelnetworks.com + Phone: +1 865 686 0432 + + Co-editor: Paul Meyer + Secure Computing Corporation + Postal: 2675 Long Lake Road + Roseville, Minnesota 55113 + EMail: paul_meyer@securecomputing.com + Phone: +1 651 628 1592 + + Co-editor: Bob Stewart + Retired" + DESCRIPTION + "This MIB module defines MIB objects which provide + mechanisms to remotely configure the parameters + used by an SNMP entity for the generation of + notifications. + + Copyright (C) The Internet Society (2002). This + version of this MIB module is part of RFC 3413; + see the RFC itself for full legal notices. + " + REVISION "200210140000Z" -- 14 October 2002 + DESCRIPTION "Clarifications, published as + RFC 3413." + REVISION "199808040000Z" -- 4 August 1998 + DESCRIPTION "Clarifications, published as + RFC 2573." + REVISION "199707140000Z" -- 14 July 1997 + DESCRIPTION "The initial revision, published as RFC2273." + ::= { snmpModules 13 } + +snmpNotifyObjects OBJECT IDENTIFIER ::= + { snmpNotificationMIB 1 } +snmpNotifyConformance OBJECT IDENTIFIER ::= + { snmpNotificationMIB 3 } + +-- +-- +-- The snmpNotifyObjects group +-- +-- + +snmpNotifyTable OBJECT-TYPE + SYNTAX SEQUENCE OF SnmpNotifyEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table is used to select management targets which should + receive notifications, as well as the type of notification + which should be sent to each selected management target." + ::= { snmpNotifyObjects 1 } + +snmpNotifyEntry OBJECT-TYPE + SYNTAX SnmpNotifyEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry in this table selects a set of management targets + which should receive notifications, as well as the type of + + notification which should be sent to each selected + management target. + + Entries in the snmpNotifyTable are created and + deleted using the snmpNotifyRowStatus object." + INDEX { IMPLIED snmpNotifyName } + ::= { snmpNotifyTable 1 } + +SnmpNotifyEntry ::= SEQUENCE { + snmpNotifyName SnmpAdminString, + snmpNotifyTag SnmpTagValue, + snmpNotifyType INTEGER, + snmpNotifyStorageType StorageType, + snmpNotifyRowStatus RowStatus +} + +snmpNotifyName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(1..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The locally arbitrary, but unique identifier associated + with this snmpNotifyEntry." + ::= { snmpNotifyEntry 1 } + +snmpNotifyTag OBJECT-TYPE + SYNTAX SnmpTagValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object contains a single tag value which is used + to select entries in the snmpTargetAddrTable. Any entry + in the snmpTargetAddrTable which contains a tag value + which is equal to the value of an instance of this + object is selected. If this object contains a value + of zero length, no entries are selected." + DEFVAL { "" } + ::= { snmpNotifyEntry 2 } + +snmpNotifyType OBJECT-TYPE + SYNTAX INTEGER { + trap(1), + inform(2) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object determines the type of notification to + + be generated for entries in the snmpTargetAddrTable + selected by the corresponding instance of + snmpNotifyTag. This value is only used when + generating notifications, and is ignored when + using the snmpTargetAddrTable for other purposes. + + If the value of this object is trap(1), then any + messages generated for selected rows will contain + Unconfirmed-Class PDUs. + + If the value of this object is inform(2), then any + messages generated for selected rows will contain + Confirmed-Class PDUs. + + Note that if an SNMP entity only supports + generation of Unconfirmed-Class PDUs (and not + Confirmed-Class PDUs), then this object may be + read-only." + DEFVAL { trap } + ::= { snmpNotifyEntry 3 } + +snmpNotifyStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The storage type for this conceptual row. + Conceptual rows having the value 'permanent' need not + allow write-access to any columnar objects in the row." + DEFVAL { nonVolatile } + ::= { snmpNotifyEntry 4 } + +snmpNotifyRowStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this conceptual row. + + To create a row in this table, a manager must + set this object to either createAndGo(4) or + createAndWait(5)." + ::= { snmpNotifyEntry 5 } + +snmpNotifyFilterProfileTable OBJECT-TYPE + SYNTAX SEQUENCE OF SnmpNotifyFilterProfileEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table is used to associate a notification filter + profile with a particular set of target parameters." + ::= { snmpNotifyObjects 2 } + +snmpNotifyFilterProfileEntry OBJECT-TYPE + SYNTAX SnmpNotifyFilterProfileEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry in this table indicates the name of the filter + profile to be used when generating notifications using + the corresponding entry in the snmpTargetParamsTable. + + Entries in the snmpNotifyFilterProfileTable are created + and deleted using the snmpNotifyFilterProfileRowStatus + object." + INDEX { IMPLIED snmpTargetParamsName } + ::= { snmpNotifyFilterProfileTable 1 } + +SnmpNotifyFilterProfileEntry ::= SEQUENCE { + snmpNotifyFilterProfileName SnmpAdminString, + snmpNotifyFilterProfileStorType StorageType, + snmpNotifyFilterProfileRowStatus RowStatus +} + +snmpNotifyFilterProfileName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(1..32)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The name of the filter profile to be used when generating + notifications using the corresponding entry in the + snmpTargetAddrTable." + ::= { snmpNotifyFilterProfileEntry 1 } + +snmpNotifyFilterProfileStorType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The storage type for this conceptual row. + Conceptual rows having the value 'permanent' need not + allow write-access to any columnar objects in the row." + DEFVAL { nonVolatile } + ::= { snmpNotifyFilterProfileEntry 2 } + +snmpNotifyFilterProfileRowStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this conceptual row. + + To create a row in this table, a manager must + set this object to either createAndGo(4) or + createAndWait(5). + + Until instances of all corresponding columns are + appropriately configured, the value of the + corresponding instance of the + snmpNotifyFilterProfileRowStatus column is 'notReady'. + + In particular, a newly created row cannot be made + active until the corresponding instance of + snmpNotifyFilterProfileName has been set." + ::= { snmpNotifyFilterProfileEntry 3 } + +snmpNotifyFilterTable OBJECT-TYPE + SYNTAX SEQUENCE OF SnmpNotifyFilterEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table of filter profiles. Filter profiles are used + to determine whether particular management targets should + receive particular notifications. + + When a notification is generated, it must be compared + with the filters associated with each management target + which is configured to receive notifications, in order to + determine whether it may be sent to each such management + target. + + A more complete discussion of notification filtering + can be found in section 6. of [SNMP-APPL]." + ::= { snmpNotifyObjects 3 } + +snmpNotifyFilterEntry OBJECT-TYPE + SYNTAX SnmpNotifyFilterEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An element of a filter profile. + + Entries in the snmpNotifyFilterTable are created and + deleted using the snmpNotifyFilterRowStatus object." + INDEX { snmpNotifyFilterProfileName, + IMPLIED snmpNotifyFilterSubtree } + ::= { snmpNotifyFilterTable 1 } + +SnmpNotifyFilterEntry ::= SEQUENCE { + snmpNotifyFilterSubtree OBJECT IDENTIFIER, + snmpNotifyFilterMask OCTET STRING, + snmpNotifyFilterType INTEGER, + snmpNotifyFilterStorageType StorageType, + snmpNotifyFilterRowStatus RowStatus +} + +snmpNotifyFilterSubtree OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The MIB subtree which, when combined with the corresponding + instance of snmpNotifyFilterMask, defines a family of + subtrees which are included in or excluded from the + filter profile." + ::= { snmpNotifyFilterEntry 1 } + +snmpNotifyFilterMask OBJECT-TYPE + SYNTAX OCTET STRING (SIZE(0..16)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The bit mask which, in combination with the corresponding + instance of snmpNotifyFilterSubtree, defines a family of + subtrees which are included in or excluded from the + filter profile. + + Each bit of this bit mask corresponds to a + sub-identifier of snmpNotifyFilterSubtree, with the + most significant bit of the i-th octet of this octet + string value (extended if necessary, see below) + corresponding to the (8*i - 7)-th sub-identifier, and + the least significant bit of the i-th octet of this + octet string corresponding to the (8*i)-th + sub-identifier, where i is in the range 1 through 16. + + Each bit of this bit mask specifies whether or not + the corresponding sub-identifiers must match when + determining if an OBJECT IDENTIFIER matches this + family of filter subtrees; a '1' indicates that an + exact match must occur; a '0' indicates 'wild card', + i.e., any sub-identifier value matches. + + Thus, the OBJECT IDENTIFIER X of an object instance + is contained in a family of filter subtrees if, for + each sub-identifier of the value of + snmpNotifyFilterSubtree, either: + + the i-th bit of snmpNotifyFilterMask is 0, or + + the i-th sub-identifier of X is equal to the i-th + sub-identifier of the value of + snmpNotifyFilterSubtree. + + If the value of this bit mask is M bits long and + there are more than M sub-identifiers in the + corresponding instance of snmpNotifyFilterSubtree, + then the bit mask is extended with 1's to be the + required length. + + Note that when the value of this object is the + zero-length string, this extension rule results in + a mask of all-1's being used (i.e., no 'wild card'), + and the family of filter subtrees is the one + subtree uniquely identified by the corresponding + instance of snmpNotifyFilterSubtree." + DEFVAL { ''H } + ::= { snmpNotifyFilterEntry 2 } + +snmpNotifyFilterType OBJECT-TYPE + SYNTAX INTEGER { + included(1), + excluded(2) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object indicates whether the family of filter subtrees + defined by this entry are included in or excluded from a + filter. A more detailed discussion of the use of this + object can be found in section 6. of [SNMP-APPL]." + DEFVAL { included } + ::= { snmpNotifyFilterEntry 3 } + +snmpNotifyFilterStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The storage type for this conceptual row. + Conceptual rows having the value 'permanent' need not + + allow write-access to any columnar objects in the row." + DEFVAL { nonVolatile } + ::= { snmpNotifyFilterEntry 4 } + +snmpNotifyFilterRowStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this conceptual row. + + To create a row in this table, a manager must + set this object to either createAndGo(4) or + createAndWait(5)." + ::= { snmpNotifyFilterEntry 5 } + +-- +-- +-- Conformance information +-- +-- + +snmpNotifyCompliances OBJECT IDENTIFIER ::= + { snmpNotifyConformance 1 } +snmpNotifyGroups OBJECT IDENTIFIER ::= + { snmpNotifyConformance 2 } + +-- +-- +-- Compliance statements +-- +-- + +snmpNotifyBasicCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for minimal SNMP entities which + implement only SNMP Unconfirmed-Class notifications and + read-create operations on only the snmpTargetAddrTable." + MODULE SNMP-TARGET-MIB + MANDATORY-GROUPS { snmpTargetBasicGroup } + + OBJECT snmpTargetParamsMPModel + MIN-ACCESS read-only + DESCRIPTION + "Create/delete/modify access is not required." + + OBJECT snmpTargetParamsSecurityModel + MIN-ACCESS read-only + DESCRIPTION + "Create/delete/modify access is not required." + + OBJECT snmpTargetParamsSecurityName + MIN-ACCESS read-only + DESCRIPTION + "Create/delete/modify access is not required." + + OBJECT snmpTargetParamsSecurityLevel + MIN-ACCESS read-only + DESCRIPTION + "Create/delete/modify access is not required." + + OBJECT snmpTargetParamsStorageType + SYNTAX INTEGER { + readOnly(5) + } + MIN-ACCESS read-only + DESCRIPTION + "Create/delete/modify access is not required. + Support of the values other(1), volatile(2), + nonVolatile(3), and permanent(4) is not required." + + OBJECT snmpTargetParamsRowStatus + SYNTAX INTEGER { + active(1) + } + MIN-ACCESS read-only + DESCRIPTION + "Create/delete/modify access to the + snmpTargetParamsTable is not required. + Support of the values notInService(2), notReady(3), + createAndGo(4), createAndWait(5), and destroy(6) is + not required." + + MODULE -- This Module + MANDATORY-GROUPS { snmpNotifyGroup } + + OBJECT snmpNotifyTag + MIN-ACCESS read-only + DESCRIPTION + "Create/delete/modify access is not required." + + OBJECT snmpNotifyType + SYNTAX INTEGER { + trap(1) + } + MIN-ACCESS read-only + DESCRIPTION + "Create/delete/modify access is not required. + Support of the value notify(2) is not required." + + OBJECT snmpNotifyStorageType + SYNTAX INTEGER { + readOnly(5) + } + MIN-ACCESS read-only + DESCRIPTION + "Create/delete/modify access is not required. + Support of the values other(1), volatile(2), + nonVolatile(3), and permanent(4) is not required." + + OBJECT snmpNotifyRowStatus + SYNTAX INTEGER { + active(1) + } + MIN-ACCESS read-only + DESCRIPTION + "Create/delete/modify access to the + snmpNotifyTable is not required. + Support of the values notInService(2), notReady(3), + createAndGo(4), createAndWait(5), and destroy(6) is + not required." + ::= { snmpNotifyCompliances 1 } + +snmpNotifyBasicFiltersCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP entities which implement + SNMP Unconfirmed-Class notifications with filtering, and + read-create operations on all related tables." + MODULE SNMP-TARGET-MIB + MANDATORY-GROUPS { snmpTargetBasicGroup } + MODULE -- This Module + MANDATORY-GROUPS { snmpNotifyGroup, + snmpNotifyFilterGroup } + ::= { snmpNotifyCompliances 2 } + +snmpNotifyFullCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP entities which either + implement only SNMP Confirmed-Class notifications, or both + SNMP Unconfirmed-Class and Confirmed-Class notifications, + plus filtering and read-create operations on all related + tables." + MODULE SNMP-TARGET-MIB + MANDATORY-GROUPS { snmpTargetBasicGroup, + snmpTargetResponseGroup } + MODULE -- This Module + MANDATORY-GROUPS { snmpNotifyGroup, + snmpNotifyFilterGroup } + ::= { snmpNotifyCompliances 3 } + +snmpNotifyGroup OBJECT-GROUP + OBJECTS { + snmpNotifyTag, + snmpNotifyType, + snmpNotifyStorageType, + snmpNotifyRowStatus + } + STATUS current + DESCRIPTION + "A collection of objects for selecting which management + targets are used for generating notifications, and the + type of notification to be generated for each selected + management target." + ::= { snmpNotifyGroups 1 } + +snmpNotifyFilterGroup OBJECT-GROUP + OBJECTS { + snmpNotifyFilterProfileName, + snmpNotifyFilterProfileStorType, + snmpNotifyFilterProfileRowStatus, + snmpNotifyFilterMask, + snmpNotifyFilterType, + snmpNotifyFilterStorageType, + snmpNotifyFilterRowStatus + } + STATUS current + DESCRIPTION + "A collection of objects providing remote configuration + of notification filters." + ::= { snmpNotifyGroups 2 } + +END diff --git a/mibs/SNMP-PROXY-MIB.txt b/mibs/SNMP-PROXY-MIB.txt new file mode 100644 index 0000000..4a72e86 --- /dev/null +++ b/mibs/SNMP-PROXY-MIB.txt @@ -0,0 +1,294 @@ +SNMP-PROXY-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, + OBJECT-TYPE, + snmpModules + FROM SNMPv2-SMI + + RowStatus, + StorageType + FROM SNMPv2-TC + + SnmpEngineID, + SnmpAdminString + FROM SNMP-FRAMEWORK-MIB + + SnmpTagValue + FROM SNMP-TARGET-MIB + + MODULE-COMPLIANCE, + OBJECT-GROUP + FROM SNMPv2-CONF; + +snmpProxyMIB MODULE-IDENTITY + LAST-UPDATED "200210140000Z" + ORGANIZATION "IETF SNMPv3 Working Group" + CONTACT-INFO + "WG-email: snmpv3@lists.tislabs.com + Subscribe: majordomo@lists.tislabs.com + In message body: subscribe snmpv3 + + Co-Chair: Russ Mundy + Network Associates Laboratories + Postal: 15204 Omega Drive, Suite 300 + Rockville, MD 20850-4601 + USA + EMail: mundy@tislabs.com + Phone: +1 301-947-7107 + + Co-Chair: David Harrington + Enterasys Networks + Postal: 35 Industrial Way + P. O. Box 5004 + Rochester, New Hampshire 03866-5005 + USA + EMail: dbh@enterasys.com + Phone: +1 603-337-2614 + + Co-editor: David B. Levi + Nortel Networks + Postal: 3505 Kesterwood Drive + Knoxville, Tennessee 37918 + EMail: dlevi@nortelnetworks.com + Phone: +1 865 686 0432 + + Co-editor: Paul Meyer + Secure Computing Corporation + Postal: 2675 Long Lake Road + Roseville, Minnesota 55113 + EMail: paul_meyer@securecomputing.com + Phone: +1 651 628 1592 + + Co-editor: Bob Stewart + Retired" + DESCRIPTION + "This MIB module defines MIB objects which provide + mechanisms to remotely configure the parameters + used by a proxy forwarding application. + + Copyright (C) The Internet Society (2002). This + version of this MIB module is part of RFC 3413; + see the RFC itself for full legal notices. + " + REVISION "200210140000Z" -- 14 October 2002 + DESCRIPTION "Clarifications, published as + RFC 3413." + REVISION "199808040000Z" -- 4 August 1998 + DESCRIPTION "Clarifications, published as + RFC 2573." + REVISION "199707140000Z" -- 14 July 1997 + DESCRIPTION "The initial revision, published as RFC2273." + ::= { snmpModules 14 } + +snmpProxyObjects OBJECT IDENTIFIER ::= { snmpProxyMIB 1 } +snmpProxyConformance OBJECT IDENTIFIER ::= { snmpProxyMIB 3 } + +-- + +-- +-- The snmpProxyObjects group +-- +-- + +snmpProxyTable OBJECT-TYPE + SYNTAX SEQUENCE OF SnmpProxyEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The table of translation parameters used by proxy forwarder + applications for forwarding SNMP messages." + ::= { snmpProxyObjects 2 } + +snmpProxyEntry OBJECT-TYPE + SYNTAX SnmpProxyEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A set of translation parameters used by a proxy forwarder + application for forwarding SNMP messages. + + Entries in the snmpProxyTable are created and deleted + using the snmpProxyRowStatus object." + INDEX { IMPLIED snmpProxyName } + ::= { snmpProxyTable 1 } + +SnmpProxyEntry ::= SEQUENCE { + snmpProxyName SnmpAdminString, + snmpProxyType INTEGER, + snmpProxyContextEngineID SnmpEngineID, + snmpProxyContextName SnmpAdminString, + snmpProxyTargetParamsIn SnmpAdminString, + snmpProxySingleTargetOut SnmpAdminString, + snmpProxyMultipleTargetOut SnmpTagValue, + snmpProxyStorageType StorageType, + snmpProxyRowStatus RowStatus +} + +snmpProxyName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(1..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The locally arbitrary, but unique identifier associated + with this snmpProxyEntry." + ::= { snmpProxyEntry 1 } + +snmpProxyType OBJECT-TYPE + SYNTAX INTEGER { + read(1), + write(2), + trap(3), + inform(4) + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The type of message that may be forwarded using + the translation parameters defined by this entry." + ::= { snmpProxyEntry 2 } + +snmpProxyContextEngineID OBJECT-TYPE + SYNTAX SnmpEngineID + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The contextEngineID contained in messages that + may be forwarded using the translation parameters + defined by this entry." + ::= { snmpProxyEntry 3 } + +snmpProxyContextName OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The contextName contained in messages that may be + forwarded using the translation parameters defined + by this entry. + + This object is optional, and if not supported, the + contextName contained in a message is ignored when + selecting an entry in the snmpProxyTable." + ::= { snmpProxyEntry 4 } + +snmpProxyTargetParamsIn OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object selects an entry in the snmpTargetParamsTable. + The selected entry is used to determine which row of the + snmpProxyTable to use for forwarding received messages." + ::= { snmpProxyEntry 5 } + +snmpProxySingleTargetOut OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object selects a management target defined in the + snmpTargetAddrTable (in the SNMP-TARGET-MIB). The + selected target is defined by an entry in the + snmpTargetAddrTable whose index value (snmpTargetAddrName) + is equal to this object. + + This object is only used when selection of a single + target is required (i.e. when forwarding an incoming + read or write request)." + ::= { snmpProxyEntry 6 } + +snmpProxyMultipleTargetOut OBJECT-TYPE + SYNTAX SnmpTagValue + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object selects a set of management targets defined + in the snmpTargetAddrTable (in the SNMP-TARGET-MIB). + + This object is only used when selection of multiple + targets is required (i.e. when forwarding an incoming + notification)." + ::= { snmpProxyEntry 7 } + +snmpProxyStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The storage type of this conceptual row. + Conceptual rows having the value 'permanent' need not + allow write-access to any columnar objects in the row." + DEFVAL { nonVolatile } + ::= { snmpProxyEntry 8 } + +snmpProxyRowStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this conceptual row. + + To create a row in this table, a manager must + + set this object to either createAndGo(4) or + createAndWait(5). + + The following objects may not be modified while the + value of this object is active(1): + - snmpProxyType + - snmpProxyContextEngineID + - snmpProxyContextName + - snmpProxyTargetParamsIn + - snmpProxySingleTargetOut + - snmpProxyMultipleTargetOut" + ::= { snmpProxyEntry 9 } + +-- +-- +-- Conformance information +-- +-- + +snmpProxyCompliances OBJECT IDENTIFIER ::= + { snmpProxyConformance 1 } +snmpProxyGroups OBJECT IDENTIFIER ::= + { snmpProxyConformance 2 } + +-- +-- +-- Compliance statements +-- +-- + +snmpProxyCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP entities which include + a proxy forwarding application." + MODULE SNMP-TARGET-MIB + MANDATORY-GROUPS { snmpTargetBasicGroup, + snmpTargetResponseGroup } + MODULE -- This Module + MANDATORY-GROUPS { snmpProxyGroup } + ::= { snmpProxyCompliances 1 } + +snmpProxyGroup OBJECT-GROUP + OBJECTS { + snmpProxyType, + snmpProxyContextEngineID, + snmpProxyContextName, + snmpProxyTargetParamsIn, + snmpProxySingleTargetOut, + snmpProxyMultipleTargetOut, + snmpProxyStorageType, + snmpProxyRowStatus + } + STATUS current + DESCRIPTION + "A collection of objects providing remote configuration of + management target translation parameters for use by + proxy forwarder applications." + ::= { snmpProxyGroups 3 } + +END diff --git a/mibs/SNMP-TARGET-MIB.txt b/mibs/SNMP-TARGET-MIB.txt new file mode 100644 index 0000000..654afdd --- /dev/null +++ b/mibs/SNMP-TARGET-MIB.txt @@ -0,0 +1,660 @@ +SNMP-TARGET-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, + OBJECT-TYPE, + snmpModules, + Counter32, + Integer32 + FROM SNMPv2-SMI + + TEXTUAL-CONVENTION, + TDomain, + TAddress, + TimeInterval, + RowStatus, + StorageType, + TestAndIncr + FROM SNMPv2-TC + + SnmpSecurityModel, + SnmpMessageProcessingModel, + SnmpSecurityLevel, + SnmpAdminString + FROM SNMP-FRAMEWORK-MIB + + MODULE-COMPLIANCE, + OBJECT-GROUP + FROM SNMPv2-CONF; + +snmpTargetMIB MODULE-IDENTITY + LAST-UPDATED "200210140000Z" + ORGANIZATION "IETF SNMPv3 Working Group" + CONTACT-INFO + "WG-email: snmpv3@lists.tislabs.com + Subscribe: majordomo@lists.tislabs.com + In message body: subscribe snmpv3 + + Co-Chair: Russ Mundy + Network Associates Laboratories + Postal: 15204 Omega Drive, Suite 300 + Rockville, MD 20850-4601 + USA + EMail: mundy@tislabs.com + Phone: +1 301-947-7107 + + Co-Chair: David Harrington + Enterasys Networks + Postal: 35 Industrial Way + P. O. Box 5004 + Rochester, New Hampshire 03866-5005 + USA + EMail: dbh@enterasys.com + Phone: +1 603-337-2614 + + Co-editor: David B. Levi + Nortel Networks + Postal: 3505 Kesterwood Drive + Knoxville, Tennessee 37918 + EMail: dlevi@nortelnetworks.com + Phone: +1 865 686 0432 + + Co-editor: Paul Meyer + Secure Computing Corporation + Postal: 2675 Long Lake Road + + Roseville, Minnesota 55113 + EMail: paul_meyer@securecomputing.com + Phone: +1 651 628 1592 + + Co-editor: Bob Stewart + Retired" + DESCRIPTION + "This MIB module defines MIB objects which provide + mechanisms to remotely configure the parameters used + by an SNMP entity for the generation of SNMP messages. + + Copyright (C) The Internet Society (2002). This + version of this MIB module is part of RFC 3413; + see the RFC itself for full legal notices. + " + REVISION "200210140000Z" -- 14 October 2002 + DESCRIPTION "Fixed DISPLAY-HINTS for UTF-8 strings, fixed hex + value of LF characters, clarified meaning of zero + length tag values, improved tag list examples. + Published as RFC 3413." + REVISION "199808040000Z" -- 4 August 1998 + DESCRIPTION "Clarifications, published as + RFC 2573." + REVISION "199707140000Z" -- 14 July 1997 + DESCRIPTION "The initial revision, published as RFC2273." + ::= { snmpModules 12 } + +snmpTargetObjects OBJECT IDENTIFIER ::= { snmpTargetMIB 1 } +snmpTargetConformance OBJECT IDENTIFIER ::= { snmpTargetMIB 3 } + +SnmpTagValue ::= TEXTUAL-CONVENTION + DISPLAY-HINT "255t" + STATUS current + DESCRIPTION + "An octet string containing a tag value. + Tag values are preferably in human-readable form. + + To facilitate internationalization, this information + is represented using the ISO/IEC IS 10646-1 character + set, encoded as an octet string using the UTF-8 + character encoding scheme described in RFC 2279. + + Since additional code points are added by amendments + to the 10646 standard from time to time, + implementations must be prepared to encounter any code + point from 0x00000000 to 0x7fffffff. + + The use of control codes should be avoided, and certain + + control codes are not allowed as described below. + + For code points not directly supported by user + interface hardware or software, an alternative means + of entry and display, such as hexadecimal, may be + provided. + + For information encoded in 7-bit US-ASCII, the UTF-8 + representation is identical to the US-ASCII encoding. + + Note that when this TC is used for an object that + is used or envisioned to be used as an index, then a + SIZE restriction must be specified so that the number + of sub-identifiers for any object instance does not + exceed the limit of 128, as defined by [RFC1905]. + + An object of this type contains a single tag value + which is used to select a set of entries in a table. + + A tag value is an arbitrary string of octets, but + may not contain a delimiter character. Delimiter + characters are defined to be one of the following: + + - An ASCII space character (0x20). + + - An ASCII TAB character (0x09). + + - An ASCII carriage return (CR) character (0x0D). + + - An ASCII line feed (LF) character (0x0A). + + Delimiter characters are used to separate tag values + in a tag list. An object of this type may only + contain a single tag value, and so delimiter + characters are not allowed in a value of this type. + + Note that a tag value of 0 length means that no tag is + defined. In other words, a tag value of 0 length would + never match anything in a tag list, and would never + select any table entries. + + Some examples of valid tag values are: + + - 'acme' + + - 'router' + + - 'host' + + The use of a tag value to select table entries is + application and MIB specific." + SYNTAX OCTET STRING (SIZE (0..255)) + +SnmpTagList ::= TEXTUAL-CONVENTION + DISPLAY-HINT "255t" + STATUS current + DESCRIPTION + "An octet string containing a list of tag values. + Tag values are preferably in human-readable form. + + To facilitate internationalization, this information + is represented using the ISO/IEC IS 10646-1 character + set, encoded as an octet string using the UTF-8 + character encoding scheme described in RFC 2279. + + Since additional code points are added by amendments + to the 10646 standard from time to time, + implementations must be prepared to encounter any code + point from 0x00000000 to 0x7fffffff. + + The use of control codes should be avoided, except as + described below. + + For code points not directly supported by user + interface hardware or software, an alternative means + of entry and display, such as hexadecimal, may be + provided. + + For information encoded in 7-bit US-ASCII, the UTF-8 + representation is identical to the US-ASCII encoding. + + An object of this type contains a list of tag values + which are used to select a set of entries in a table. + + A tag value is an arbitrary string of octets, but + may not contain a delimiter character. Delimiter + characters are defined to be one of the following: + + - An ASCII space character (0x20). + + - An ASCII TAB character (0x09). + + - An ASCII carriage return (CR) character (0x0D). + + - An ASCII line feed (LF) character (0x0A). + + Delimiter characters are used to separate tag values + + in a tag list. Only a single delimiter character may + occur between two tag values. A tag value may not + have a zero length. These constraints imply certain + restrictions on the contents of this object: + + - There cannot be a leading or trailing delimiter + character. + + - There cannot be multiple adjacent delimiter + characters. + + Some examples of valid tag lists are: + + - '' -- an empty list + + - 'acme' -- list of one tag + + - 'host router bridge' -- list of several tags + + Note that although a tag value may not have a length of + zero, an empty string is still valid. This indicates + an empty list (i.e. there are no tag values in the list). + + The use of the tag list to select table entries is + application and MIB specific. Typically, an application + will provide one or more tag values, and any entry + which contains some combination of these tag values + will be selected." + SYNTAX OCTET STRING (SIZE (0..255)) + +-- +-- +-- The snmpTargetObjects group +-- +-- + +snmpTargetSpinLock OBJECT-TYPE + SYNTAX TestAndIncr + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "This object is used to facilitate modification of table + entries in the SNMP-TARGET-MIB module by multiple + managers. In particular, it is useful when modifying + the value of the snmpTargetAddrTagList object. + + The procedure for modifying the snmpTargetAddrTagList + object is as follows: + + 1. Retrieve the value of snmpTargetSpinLock and + of snmpTargetAddrTagList. + + 2. Generate a new value for snmpTargetAddrTagList. + + 3. Set the value of snmpTargetSpinLock to the + retrieved value, and the value of + snmpTargetAddrTagList to the new value. If + the set fails for the snmpTargetSpinLock + object, go back to step 1." + ::= { snmpTargetObjects 1 } + +snmpTargetAddrTable OBJECT-TYPE + SYNTAX SEQUENCE OF SnmpTargetAddrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of transport addresses to be used in the generation + of SNMP messages." + ::= { snmpTargetObjects 2 } + +snmpTargetAddrEntry OBJECT-TYPE + SYNTAX SnmpTargetAddrEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A transport address to be used in the generation + of SNMP operations. + + Entries in the snmpTargetAddrTable are created and + deleted using the snmpTargetAddrRowStatus object." + INDEX { IMPLIED snmpTargetAddrName } + ::= { snmpTargetAddrTable 1 } + +SnmpTargetAddrEntry ::= SEQUENCE { + snmpTargetAddrName SnmpAdminString, + snmpTargetAddrTDomain TDomain, + snmpTargetAddrTAddress TAddress, + snmpTargetAddrTimeout TimeInterval, + snmpTargetAddrRetryCount Integer32, + snmpTargetAddrTagList SnmpTagList, + snmpTargetAddrParams SnmpAdminString, + snmpTargetAddrStorageType StorageType, + snmpTargetAddrRowStatus RowStatus +} + +snmpTargetAddrName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(1..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The locally arbitrary, but unique identifier associated + with this snmpTargetAddrEntry." + ::= { snmpTargetAddrEntry 1 } + +snmpTargetAddrTDomain OBJECT-TYPE + SYNTAX TDomain + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object indicates the transport type of the address + contained in the snmpTargetAddrTAddress object." + ::= { snmpTargetAddrEntry 2 } + +snmpTargetAddrTAddress OBJECT-TYPE + SYNTAX TAddress + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object contains a transport address. The format of + this address depends on the value of the + snmpTargetAddrTDomain object." + ::= { snmpTargetAddrEntry 3 } + +snmpTargetAddrTimeout OBJECT-TYPE + SYNTAX TimeInterval + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object should reflect the expected maximum round + trip time for communicating with the transport address + defined by this row. When a message is sent to this + address, and a response (if one is expected) is not + received within this time period, an implementation + may assume that the response will not be delivered. + + Note that the time interval that an application waits + for a response may actually be derived from the value + of this object. The method for deriving the actual time + interval is implementation dependent. One such method + is to derive the expected round trip time based on a + particular retransmission algorithm and on the number + of timeouts which have occurred. The type of message may + also be considered when deriving expected round trip + times for retransmissions. For example, if a message is + being sent with a securityLevel that indicates both + + authentication and privacy, the derived value may be + increased to compensate for extra processing time spent + during authentication and encryption processing." + DEFVAL { 1500 } + ::= { snmpTargetAddrEntry 4 } + +snmpTargetAddrRetryCount OBJECT-TYPE + SYNTAX Integer32 (0..255) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object specifies a default number of retries to be + attempted when a response is not received for a generated + message. An application may provide its own retry count, + in which case the value of this object is ignored." + DEFVAL { 3 } + ::= { snmpTargetAddrEntry 5 } + +snmpTargetAddrTagList OBJECT-TYPE + SYNTAX SnmpTagList + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "This object contains a list of tag values which are + used to select target addresses for a particular + operation." + DEFVAL { "" } + ::= { snmpTargetAddrEntry 6 } + +snmpTargetAddrParams OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(1..32)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The value of this object identifies an entry in the + snmpTargetParamsTable. The identified entry + contains SNMP parameters to be used when generating + messages to be sent to this transport address." + ::= { snmpTargetAddrEntry 7 } + +snmpTargetAddrStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The storage type for this conceptual row. + Conceptual rows having the value 'permanent' need not + allow write-access to any columnar objects in the row." + DEFVAL { nonVolatile } + ::= { snmpTargetAddrEntry 8 } + +snmpTargetAddrRowStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this conceptual row. + + To create a row in this table, a manager must + set this object to either createAndGo(4) or + createAndWait(5). + + Until instances of all corresponding columns are + appropriately configured, the value of the + corresponding instance of the snmpTargetAddrRowStatus + column is 'notReady'. + + In particular, a newly created row cannot be made + active until the corresponding instances of + snmpTargetAddrTDomain, snmpTargetAddrTAddress, and + snmpTargetAddrParams have all been set. + + The following objects may not be modified while the + value of this object is active(1): + - snmpTargetAddrTDomain + - snmpTargetAddrTAddress + An attempt to set these objects while the value of + snmpTargetAddrRowStatus is active(1) will result in + an inconsistentValue error." + ::= { snmpTargetAddrEntry 9 } + +snmpTargetParamsTable OBJECT-TYPE + SYNTAX SEQUENCE OF SnmpTargetParamsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of SNMP target information to be used + in the generation of SNMP messages." + ::= { snmpTargetObjects 3 } + +snmpTargetParamsEntry OBJECT-TYPE + SYNTAX SnmpTargetParamsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A set of SNMP target information. + + Entries in the snmpTargetParamsTable are created and + deleted using the snmpTargetParamsRowStatus object." + INDEX { IMPLIED snmpTargetParamsName } + ::= { snmpTargetParamsTable 1 } + +SnmpTargetParamsEntry ::= SEQUENCE { + snmpTargetParamsName SnmpAdminString, + snmpTargetParamsMPModel SnmpMessageProcessingModel, + snmpTargetParamsSecurityModel SnmpSecurityModel, + snmpTargetParamsSecurityName SnmpAdminString, + snmpTargetParamsSecurityLevel SnmpSecurityLevel, + snmpTargetParamsStorageType StorageType, + snmpTargetParamsRowStatus RowStatus +} + +snmpTargetParamsName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(1..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The locally arbitrary, but unique identifier associated + with this snmpTargetParamsEntry." + ::= { snmpTargetParamsEntry 1 } + +snmpTargetParamsMPModel OBJECT-TYPE + SYNTAX SnmpMessageProcessingModel + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The Message Processing Model to be used when generating + SNMP messages using this entry." + ::= { snmpTargetParamsEntry 2 } + +snmpTargetParamsSecurityModel OBJECT-TYPE + SYNTAX SnmpSecurityModel (1..2147483647) + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The Security Model to be used when generating SNMP + messages using this entry. An implementation may + choose to return an inconsistentValue error if an + attempt is made to set this variable to a value + for a security model which the implementation does + not support." + ::= { snmpTargetParamsEntry 3 } + +snmpTargetParamsSecurityName OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The securityName which identifies the Principal on + whose behalf SNMP messages will be generated using + this entry." + ::= { snmpTargetParamsEntry 4 } + +snmpTargetParamsSecurityLevel OBJECT-TYPE + SYNTAX SnmpSecurityLevel + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The Level of Security to be used when generating + SNMP messages using this entry." + ::= { snmpTargetParamsEntry 5 } + +snmpTargetParamsStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The storage type for this conceptual row. + Conceptual rows having the value 'permanent' need not + allow write-access to any columnar objects in the row." + DEFVAL { nonVolatile } + ::= { snmpTargetParamsEntry 6 } + +snmpTargetParamsRowStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this conceptual row. + + To create a row in this table, a manager must + set this object to either createAndGo(4) or + createAndWait(5). + + Until instances of all corresponding columns are + appropriately configured, the value of the + corresponding instance of the snmpTargetParamsRowStatus + column is 'notReady'. + + In particular, a newly created row cannot be made + active until the corresponding + snmpTargetParamsMPModel, + snmpTargetParamsSecurityModel, + snmpTargetParamsSecurityName, + and snmpTargetParamsSecurityLevel have all been set. + + The following objects may not be modified while the + value of this object is active(1): + - snmpTargetParamsMPModel + - snmpTargetParamsSecurityModel + - snmpTargetParamsSecurityName + - snmpTargetParamsSecurityLevel + An attempt to set these objects while the value of + snmpTargetParamsRowStatus is active(1) will result in + an inconsistentValue error." + ::= { snmpTargetParamsEntry 7 } + +snmpUnavailableContexts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets received by the SNMP + engine which were dropped because the context + contained in the message was unavailable." + ::= { snmpTargetObjects 4 } + +snmpUnknownContexts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of packets received by the SNMP + engine which were dropped because the context + contained in the message was unknown." + ::= { snmpTargetObjects 5 } + +-- +-- +-- Conformance information +-- +-- + +snmpTargetCompliances OBJECT IDENTIFIER ::= + { snmpTargetConformance 1 } +snmpTargetGroups OBJECT IDENTIFIER ::= + { snmpTargetConformance 2 } + +-- +-- +-- Compliance statements + +-- +-- + +snmpTargetCommandResponderCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP entities which include + a command responder application." + MODULE -- This Module + MANDATORY-GROUPS { snmpTargetCommandResponderGroup } + ::= { snmpTargetCompliances 1 } + +snmpTargetBasicGroup OBJECT-GROUP + OBJECTS { + snmpTargetSpinLock, + snmpTargetAddrTDomain, + snmpTargetAddrTAddress, + snmpTargetAddrTagList, + snmpTargetAddrParams, + snmpTargetAddrStorageType, + snmpTargetAddrRowStatus, + snmpTargetParamsMPModel, + snmpTargetParamsSecurityModel, + snmpTargetParamsSecurityName, + snmpTargetParamsSecurityLevel, + snmpTargetParamsStorageType, + snmpTargetParamsRowStatus + } + STATUS current + DESCRIPTION + "A collection of objects providing basic remote + configuration of management targets." + ::= { snmpTargetGroups 1 } + +snmpTargetResponseGroup OBJECT-GROUP + OBJECTS { + snmpTargetAddrTimeout, + snmpTargetAddrRetryCount + } + STATUS current + DESCRIPTION + "A collection of objects providing remote configuration + of management targets for applications which generate + SNMP messages for which a response message would be + expected." + ::= { snmpTargetGroups 2 } + +snmpTargetCommandResponderGroup OBJECT-GROUP + + OBJECTS { + snmpUnavailableContexts, + snmpUnknownContexts + } + STATUS current + DESCRIPTION + "A collection of objects required for command responder + applications, used for counting error conditions." + ::= { snmpTargetGroups 3 } + +END diff --git a/mibs/SNMP-USER-BASED-SM-MIB.txt b/mibs/SNMP-USER-BASED-SM-MIB.txt new file mode 100644 index 0000000..3b71403 --- /dev/null +++ b/mibs/SNMP-USER-BASED-SM-MIB.txt @@ -0,0 +1,912 @@ +SNMP-USER-BASED-SM-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, + OBJECT-IDENTITY, + snmpModules, Counter32 FROM SNMPv2-SMI + TEXTUAL-CONVENTION, TestAndIncr, + RowStatus, RowPointer, + StorageType, AutonomousType FROM SNMPv2-TC + MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF + SnmpAdminString, SnmpEngineID, + snmpAuthProtocols, snmpPrivProtocols FROM SNMP-FRAMEWORK-MIB; + +snmpUsmMIB MODULE-IDENTITY + LAST-UPDATED "200210160000Z" -- 16 Oct 2002, midnight + ORGANIZATION "SNMPv3 Working Group" + CONTACT-INFO "WG-email: snmpv3@lists.tislabs.com + Subscribe: majordomo@lists.tislabs.com + In msg body: subscribe snmpv3 + + Chair: Russ Mundy + Network Associates Laboratories + postal: 15204 Omega Drive, Suite 300 + Rockville, MD 20850-4601 + USA + email: mundy@tislabs.com + + phone: +1 301-947-7107 + + Co-Chair: David Harrington + Enterasys Networks + Postal: 35 Industrial Way + P. O. Box 5004 + Rochester, New Hampshire 03866-5005 + USA + EMail: dbh@enterasys.com + Phone: +1 603-337-2614 + + Co-editor Uri Blumenthal + Lucent Technologies + postal: 67 Whippany Rd. + Whippany, NJ 07981 + USA + email: uri@lucent.com + phone: +1-973-386-2163 + + Co-editor: Bert Wijnen + Lucent Technologies + postal: Schagen 33 + 3461 GL Linschoten + Netherlands + email: bwijnen@lucent.com + phone: +31-348-480-685 + " + DESCRIPTION "The management information definitions for the + SNMP User-based Security Model. + + Copyright (C) The Internet Society (2002). This + version of this MIB module is part of RFC 3414; + see the RFC itself for full legal notices. + " +-- Revision history + + REVISION "200210160000Z" -- 16 Oct 2002, midnight + DESCRIPTION "Changes in this revision: + - Updated references and contact info. + - Clarification to usmUserCloneFrom DESCRIPTION + clause + - Fixed 'command responder' into 'command generator' + in last para of DESCRIPTION clause of + usmUserTable. + This revision published as RFC3414. + " + REVISION "199901200000Z" -- 20 Jan 1999, midnight + DESCRIPTION "Clarifications, published as RFC2574" + + REVISION "199711200000Z" -- 20 Nov 1997, midnight + DESCRIPTION "Initial version, published as RFC2274" + ::= { snmpModules 15 } + +-- Administrative assignments **************************************** + +usmMIBObjects OBJECT IDENTIFIER ::= { snmpUsmMIB 1 } +usmMIBConformance OBJECT IDENTIFIER ::= { snmpUsmMIB 2 } + +-- Identification of Authentication and Privacy Protocols ************ + +usmNoAuthProtocol OBJECT-IDENTITY + STATUS current + DESCRIPTION "No Authentication Protocol." + ::= { snmpAuthProtocols 1 } + +usmHMACMD5AuthProtocol OBJECT-IDENTITY + STATUS current + DESCRIPTION "The HMAC-MD5-96 Digest Authentication Protocol." + REFERENCE "- H. Krawczyk, M. Bellare, R. Canetti HMAC: + Keyed-Hashing for Message Authentication, + RFC2104, Feb 1997. + - Rivest, R., Message Digest Algorithm MD5, RFC1321. + " + ::= { snmpAuthProtocols 2 } + +usmHMACSHAAuthProtocol OBJECT-IDENTITY + STATUS current + DESCRIPTION "The HMAC-SHA-96 Digest Authentication Protocol." + REFERENCE "- H. Krawczyk, M. Bellare, R. Canetti, HMAC: + Keyed-Hashing for Message Authentication, + RFC2104, Feb 1997. + - Secure Hash Algorithm. NIST FIPS 180-1. + " + ::= { snmpAuthProtocols 3 } + +usmNoPrivProtocol OBJECT-IDENTITY + STATUS current + DESCRIPTION "No Privacy Protocol." + ::= { snmpPrivProtocols 1 } + +usmDESPrivProtocol OBJECT-IDENTITY + STATUS current + DESCRIPTION "The CBC-DES Symmetric Encryption Protocol." + REFERENCE "- Data Encryption Standard, National Institute of + Standards and Technology. Federal Information + Processing Standard (FIPS) Publication 46-1. + + Supersedes FIPS Publication 46, + (January, 1977; reaffirmed January, 1988). + + - Data Encryption Algorithm, American National + Standards Institute. ANSI X3.92-1981, + (December, 1980). + + - DES Modes of Operation, National Institute of + Standards and Technology. Federal Information + Processing Standard (FIPS) Publication 81, + (December, 1980). + + - Data Encryption Algorithm - Modes of Operation, + American National Standards Institute. + ANSI X3.106-1983, (May 1983). + " + ::= { snmpPrivProtocols 2 } + +-- Textual Conventions *********************************************** + +KeyChange ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Every definition of an object with this syntax must identify + a protocol P, a secret key K, and a hash algorithm H + that produces output of L octets. + + The object's value is a manager-generated, partially-random + value which, when modified, causes the value of the secret + key K, to be modified via a one-way function. + + The value of an instance of this object is the concatenation + of two components: first a 'random' component and then a + 'delta' component. + + The lengths of the random and delta components + are given by the corresponding value of the protocol P; + if P requires K to be a fixed length, the length of both the + random and delta components is that fixed length; if P + allows the length of K to be variable up to a particular + maximum length, the length of the random component is that + maximum length and the length of the delta component is any + length less than or equal to that maximum length. + For example, usmHMACMD5AuthProtocol requires K to be a fixed + length of 16 octets and L - of 16 octets. + usmHMACSHAAuthProtocol requires K to be a fixed length of + 20 octets and L - of 20 octets. Other protocols may define + other sizes, as deemed appropriate. + + When a requester wants to change the old key K to a new + key keyNew on a remote entity, the 'random' component is + obtained from either a true random generator, or from a + pseudorandom generator, and the 'delta' component is + computed as follows: + + - a temporary variable is initialized to the existing value + of K; + - if the length of the keyNew is greater than L octets, + then: + - the random component is appended to the value of the + temporary variable, and the result is input to the + the hash algorithm H to produce a digest value, and + the temporary variable is set to this digest value; + - the value of the temporary variable is XOR-ed with + the first (next) L-octets (16 octets in case of MD5) + of the keyNew to produce the first (next) L-octets + (16 octets in case of MD5) of the 'delta' component. + - the above two steps are repeated until the unused + portion of the keyNew component is L octets or less, + - the random component is appended to the value of the + temporary variable, and the result is input to the + hash algorithm H to produce a digest value; + - this digest value, truncated if necessary to be the same + length as the unused portion of the keyNew, is XOR-ed + with the unused portion of the keyNew to produce the + (final portion of the) 'delta' component. + + For example, using MD5 as the hash algorithm H: + + iterations = (lenOfDelta - 1)/16; /* integer division */ + temp = keyOld; + for (i = 0; i < iterations; i++) { + temp = MD5 (temp || random); + delta[i*16 .. (i*16)+15] = + temp XOR keyNew[i*16 .. (i*16)+15]; + } + temp = MD5 (temp || random); + delta[i*16 .. lenOfDelta-1] = + temp XOR keyNew[i*16 .. lenOfDelta-1]; + + The 'random' and 'delta' components are then concatenated as + described above, and the resulting octet string is sent to + the recipient as the new value of an instance of this object. + + At the receiver side, when an instance of this object is set + to a new value, then a new value of K is computed as follows: + + - a temporary variable is initialized to the existing value + of K; + - if the length of the delta component is greater than L + octets, then: + - the random component is appended to the value of the + temporary variable, and the result is input to the + hash algorithm H to produce a digest value, and the + temporary variable is set to this digest value; + - the value of the temporary variable is XOR-ed with + the first (next) L-octets (16 octets in case of MD5) + of the delta component to produce the first (next) + L-octets (16 octets in case of MD5) of the new value + of K. + - the above two steps are repeated until the unused + portion of the delta component is L octets or less, + - the random component is appended to the value of the + temporary variable, and the result is input to the + hash algorithm H to produce a digest value; + - this digest value, truncated if necessary to be the same + length as the unused portion of the delta component, is + XOR-ed with the unused portion of the delta component to + produce the (final portion of the) new value of K. + + For example, using MD5 as the hash algorithm H: + + iterations = (lenOfDelta - 1)/16; /* integer division */ + temp = keyOld; + for (i = 0; i < iterations; i++) { + temp = MD5 (temp || random); + keyNew[i*16 .. (i*16)+15] = + temp XOR delta[i*16 .. (i*16)+15]; + } + temp = MD5 (temp || random); + keyNew[i*16 .. lenOfDelta-1] = + temp XOR delta[i*16 .. lenOfDelta-1]; + + The value of an object with this syntax, whenever it is + retrieved by the management protocol, is always the zero + length string. + + Note that the keyOld and keyNew are the localized keys. + + Note that it is probably wise that when an SNMP entity sends + a SetRequest to change a key, that it keeps a copy of the old + key until it has confirmed that the key change actually + succeeded. + " + SYNTAX OCTET STRING + +-- Statistics for the User-based Security Model ********************** + +usmStats OBJECT IDENTIFIER ::= { usmMIBObjects 1 } + +usmStatsUnsupportedSecLevels OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "The total number of packets received by the SNMP + engine which were dropped because they requested a + securityLevel that was unknown to the SNMP engine + or otherwise unavailable. + " + ::= { usmStats 1 } + +usmStatsNotInTimeWindows OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "The total number of packets received by the SNMP + engine which were dropped because they appeared + outside of the authoritative SNMP engine's window. + " + ::= { usmStats 2 } + +usmStatsUnknownUserNames OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "The total number of packets received by the SNMP + engine which were dropped because they referenced a + user that was not known to the SNMP engine. + " + ::= { usmStats 3 } + +usmStatsUnknownEngineIDs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "The total number of packets received by the SNMP + engine which were dropped because they referenced an + snmpEngineID that was not known to the SNMP engine. + " + ::= { usmStats 4 } + +usmStatsWrongDigests OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "The total number of packets received by the SNMP + engine which were dropped because they didn't + contain the expected digest value. + " + ::= { usmStats 5 } + +usmStatsDecryptionErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION "The total number of packets received by the SNMP + engine which were dropped because they could not be + decrypted. + " + ::= { usmStats 6 } + +-- The usmUser Group ************************************************ + +usmUser OBJECT IDENTIFIER ::= { usmMIBObjects 2 } + +usmUserSpinLock OBJECT-TYPE + SYNTAX TestAndIncr + MAX-ACCESS read-write + STATUS current + DESCRIPTION "An advisory lock used to allow several cooperating + Command Generator Applications to coordinate their + use of facilities to alter secrets in the + usmUserTable. + " + ::= { usmUser 1 } + +-- The table of valid users for the User-based Security Model ******** + +usmUserTable OBJECT-TYPE + SYNTAX SEQUENCE OF UsmUserEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "The table of users configured in the SNMP engine's + Local Configuration Datastore (LCD). + + To create a new user (i.e., to instantiate a new + conceptual row in this table), it is recommended to + follow this procedure: + + 1) GET(usmUserSpinLock.0) and save in sValue. + + 2) SET(usmUserSpinLock.0=sValue, + usmUserCloneFrom=templateUser, + usmUserStatus=createAndWait) + You should use a template user to clone from + which has the proper auth/priv protocol defined. + + If the new user is to use privacy: + + 3) generate the keyChange value based on the secret + privKey of the clone-from user and the secret key + to be used for the new user. Let us call this + pkcValue. + 4) GET(usmUserSpinLock.0) and save in sValue. + 5) SET(usmUserSpinLock.0=sValue, + usmUserPrivKeyChange=pkcValue + usmUserPublic=randomValue1) + 6) GET(usmUserPulic) and check it has randomValue1. + If not, repeat steps 4-6. + + If the new user will never use privacy: + + 7) SET(usmUserPrivProtocol=usmNoPrivProtocol) + + If the new user is to use authentication: + + 8) generate the keyChange value based on the secret + authKey of the clone-from user and the secret key + to be used for the new user. Let us call this + akcValue. + 9) GET(usmUserSpinLock.0) and save in sValue. + 10) SET(usmUserSpinLock.0=sValue, + usmUserAuthKeyChange=akcValue + usmUserPublic=randomValue2) + 11) GET(usmUserPulic) and check it has randomValue2. + If not, repeat steps 9-11. + + If the new user will never use authentication: + + 12) SET(usmUserAuthProtocol=usmNoAuthProtocol) + + Finally, activate the new user: + + 13) SET(usmUserStatus=active) + + The new user should now be available and ready to be + used for SNMPv3 communication. Note however that access + to MIB data must be provided via configuration of the + SNMP-VIEW-BASED-ACM-MIB. + + The use of usmUserSpinlock is to avoid conflicts with + another SNMP command generator application which may + also be acting on the usmUserTable. + " + ::= { usmUser 2 } + +usmUserEntry OBJECT-TYPE + SYNTAX UsmUserEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "A user configured in the SNMP engine's Local + Configuration Datastore (LCD) for the User-based + Security Model. + " + INDEX { usmUserEngineID, + usmUserName + } + ::= { usmUserTable 1 } + +UsmUserEntry ::= SEQUENCE + { + usmUserEngineID SnmpEngineID, + usmUserName SnmpAdminString, + usmUserSecurityName SnmpAdminString, + usmUserCloneFrom RowPointer, + usmUserAuthProtocol AutonomousType, + usmUserAuthKeyChange KeyChange, + usmUserOwnAuthKeyChange KeyChange, + usmUserPrivProtocol AutonomousType, + usmUserPrivKeyChange KeyChange, + usmUserOwnPrivKeyChange KeyChange, + usmUserPublic OCTET STRING, + usmUserStorageType StorageType, + usmUserStatus RowStatus + } + +usmUserEngineID OBJECT-TYPE + SYNTAX SnmpEngineID + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "An SNMP engine's administratively-unique identifier. + + In a simple agent, this value is always that agent's + own snmpEngineID value. + + The value can also take the value of the snmpEngineID + of a remote SNMP engine with which this user can + communicate. + " + ::= { usmUserEntry 1 } + +usmUserName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(1..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "A human readable string representing the name of + the user. + + This is the (User-based Security) Model dependent + security ID. + " + ::= { usmUserEntry 2 } + +usmUserSecurityName OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A human readable string representing the user in + Security Model independent format. + + The default transformation of the User-based Security + Model dependent security ID to the securityName and + vice versa is the identity function so that the + securityName is the same as the userName. + " + ::= { usmUserEntry 3 } + +usmUserCloneFrom OBJECT-TYPE + SYNTAX RowPointer + MAX-ACCESS read-create + STATUS current + DESCRIPTION "A pointer to another conceptual row in this + usmUserTable. The user in this other conceptual + row is called the clone-from user. + + When a new user is created (i.e., a new conceptual + row is instantiated in this table), the privacy and + authentication parameters of the new user must be + cloned from its clone-from user. These parameters are: + - authentication protocol (usmUserAuthProtocol) + - privacy protocol (usmUserPrivProtocol) + They will be copied regardless of what the current + value is. + + Cloning also causes the initial values of the secret + authentication key (authKey) and the secret encryption + + key (privKey) of the new user to be set to the same + values as the corresponding secrets of the clone-from + user to allow the KeyChange process to occur as + required during user creation. + + The first time an instance of this object is set by + a management operation (either at or after its + instantiation), the cloning process is invoked. + Subsequent writes are successful but invoke no + action to be taken by the receiver. + The cloning process fails with an 'inconsistentName' + error if the conceptual row representing the + clone-from user does not exist or is not in an active + state when the cloning process is invoked. + + When this object is read, the ZeroDotZero OID + is returned. + " + ::= { usmUserEntry 4 } + +usmUserAuthProtocol OBJECT-TYPE + SYNTAX AutonomousType + MAX-ACCESS read-create + STATUS current + DESCRIPTION "An indication of whether messages sent on behalf of + this user to/from the SNMP engine identified by + usmUserEngineID, can be authenticated, and if so, + the type of authentication protocol which is used. + + An instance of this object is created concurrently + with the creation of any other object instance for + the same user (i.e., as part of the processing of + the set operation which creates the first object + instance in the same conceptual row). + + If an initial set operation (i.e. at row creation time) + tries to set a value for an unknown or unsupported + protocol, then a 'wrongValue' error must be returned. + + The value will be overwritten/set when a set operation + is performed on the corresponding instance of + usmUserCloneFrom. + + Once instantiated, the value of such an instance of + this object can only be changed via a set operation to + the value of the usmNoAuthProtocol. + + If a set operation tries to change the value of an + + existing instance of this object to any value other + than usmNoAuthProtocol, then an 'inconsistentValue' + error must be returned. + + If a set operation tries to set the value to the + usmNoAuthProtocol while the usmUserPrivProtocol value + in the same row is not equal to usmNoPrivProtocol, + then an 'inconsistentValue' error must be returned. + That means that an SNMP command generator application + must first ensure that the usmUserPrivProtocol is set + to the usmNoPrivProtocol value before it can set + the usmUserAuthProtocol value to usmNoAuthProtocol. + " + DEFVAL { usmNoAuthProtocol } + ::= { usmUserEntry 5 } + +usmUserAuthKeyChange OBJECT-TYPE + SYNTAX KeyChange -- typically (SIZE (0 | 32)) for HMACMD5 + -- typically (SIZE (0 | 40)) for HMACSHA + MAX-ACCESS read-create + STATUS current + DESCRIPTION "An object, which when modified, causes the secret + authentication key used for messages sent on behalf + of this user to/from the SNMP engine identified by + usmUserEngineID, to be modified via a one-way + function. + + The associated protocol is the usmUserAuthProtocol. + The associated secret key is the user's secret + authentication key (authKey). The associated hash + algorithm is the algorithm used by the user's + usmUserAuthProtocol. + + When creating a new user, it is an 'inconsistentName' + error for a set operation to refer to this object + unless it is previously or concurrently initialized + through a set operation on the corresponding instance + of usmUserCloneFrom. + + When the value of the corresponding usmUserAuthProtocol + is usmNoAuthProtocol, then a set is successful, but + effectively is a no-op. + + When this object is read, the zero-length (empty) + string is returned. + + The recommended way to do a key change is as follows: + + 1) GET(usmUserSpinLock.0) and save in sValue. + 2) generate the keyChange value based on the old + (existing) secret key and the new secret key, + let us call this kcValue. + + If you do the key change on behalf of another user: + + 3) SET(usmUserSpinLock.0=sValue, + usmUserAuthKeyChange=kcValue + usmUserPublic=randomValue) + + If you do the key change for yourself: + + 4) SET(usmUserSpinLock.0=sValue, + usmUserOwnAuthKeyChange=kcValue + usmUserPublic=randomValue) + + If you get a response with error-status of noError, + then the SET succeeded and the new key is active. + If you do not get a response, then you can issue a + GET(usmUserPublic) and check if the value is equal + to the randomValue you did send in the SET. If so, then + the key change succeeded and the new key is active + (probably the response got lost). If not, then the SET + request probably never reached the target and so you + can start over with the procedure above. + " + DEFVAL { ''H } -- the empty string + ::= { usmUserEntry 6 } + +usmUserOwnAuthKeyChange OBJECT-TYPE + SYNTAX KeyChange -- typically (SIZE (0 | 32)) for HMACMD5 + -- typically (SIZE (0 | 40)) for HMACSHA + MAX-ACCESS read-create + STATUS current + DESCRIPTION "Behaves exactly as usmUserAuthKeyChange, with one + notable difference: in order for the set operation + to succeed, the usmUserName of the operation + requester must match the usmUserName that + indexes the row which is targeted by this + operation. + In addition, the USM security model must be + used for this operation. + + The idea here is that access to this column can be + public, since it will only allow a user to change + his own secret authentication key (authKey). + Note that this can only be done once the row is active. + + When a set is received and the usmUserName of the + requester is not the same as the umsUserName that + indexes the row which is targeted by this operation, + then a 'noAccess' error must be returned. + + When a set is received and the security model in use + is not USM, then a 'noAccess' error must be returned. + " + DEFVAL { ''H } -- the empty string + ::= { usmUserEntry 7 } + +usmUserPrivProtocol OBJECT-TYPE + SYNTAX AutonomousType + MAX-ACCESS read-create + STATUS current + DESCRIPTION "An indication of whether messages sent on behalf of + this user to/from the SNMP engine identified by + usmUserEngineID, can be protected from disclosure, + and if so, the type of privacy protocol which is used. + + An instance of this object is created concurrently + with the creation of any other object instance for + the same user (i.e., as part of the processing of + the set operation which creates the first object + instance in the same conceptual row). + + If an initial set operation (i.e. at row creation time) + tries to set a value for an unknown or unsupported + protocol, then a 'wrongValue' error must be returned. + + The value will be overwritten/set when a set operation + is performed on the corresponding instance of + usmUserCloneFrom. + + Once instantiated, the value of such an instance of + this object can only be changed via a set operation to + the value of the usmNoPrivProtocol. + + If a set operation tries to change the value of an + existing instance of this object to any value other + than usmNoPrivProtocol, then an 'inconsistentValue' + error must be returned. + + Note that if any privacy protocol is used, then you + must also use an authentication protocol. In other + words, if usmUserPrivProtocol is set to anything else + than usmNoPrivProtocol, then the corresponding instance + of usmUserAuthProtocol cannot have a value of + + usmNoAuthProtocol. If it does, then an + 'inconsistentValue' error must be returned. + " + DEFVAL { usmNoPrivProtocol } + ::= { usmUserEntry 8 } + +usmUserPrivKeyChange OBJECT-TYPE + SYNTAX KeyChange -- typically (SIZE (0 | 32)) for DES + MAX-ACCESS read-create + STATUS current + DESCRIPTION "An object, which when modified, causes the secret + encryption key used for messages sent on behalf + of this user to/from the SNMP engine identified by + usmUserEngineID, to be modified via a one-way + function. + + The associated protocol is the usmUserPrivProtocol. + The associated secret key is the user's secret + privacy key (privKey). The associated hash + algorithm is the algorithm used by the user's + usmUserAuthProtocol. + + When creating a new user, it is an 'inconsistentName' + error for a set operation to refer to this object + unless it is previously or concurrently initialized + through a set operation on the corresponding instance + of usmUserCloneFrom. + + When the value of the corresponding usmUserPrivProtocol + is usmNoPrivProtocol, then a set is successful, but + effectively is a no-op. + + When this object is read, the zero-length (empty) + string is returned. + See the description clause of usmUserAuthKeyChange for + a recommended procedure to do a key change. + " + DEFVAL { ''H } -- the empty string + ::= { usmUserEntry 9 } + +usmUserOwnPrivKeyChange OBJECT-TYPE + SYNTAX KeyChange -- typically (SIZE (0 | 32)) for DES + MAX-ACCESS read-create + STATUS current + DESCRIPTION "Behaves exactly as usmUserPrivKeyChange, with one + notable difference: in order for the Set operation + to succeed, the usmUserName of the operation + requester must match the usmUserName that indexes + + the row which is targeted by this operation. + In addition, the USM security model must be + used for this operation. + + The idea here is that access to this column can be + public, since it will only allow a user to change + his own secret privacy key (privKey). + Note that this can only be done once the row is active. + + When a set is received and the usmUserName of the + requester is not the same as the umsUserName that + indexes the row which is targeted by this operation, + then a 'noAccess' error must be returned. + + When a set is received and the security model in use + is not USM, then a 'noAccess' error must be returned. + " + DEFVAL { ''H } -- the empty string + ::= { usmUserEntry 10 } + +usmUserPublic OBJECT-TYPE + SYNTAX OCTET STRING (SIZE(0..32)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION "A publicly-readable value which can be written as part + of the procedure for changing a user's secret + authentication and/or privacy key, and later read to + determine whether the change of the secret was + effected. + " + DEFVAL { ''H } -- the empty string + ::= { usmUserEntry 11 } + +usmUserStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION "The storage type for this conceptual row. + + Conceptual rows having the value 'permanent' must + allow write-access at a minimum to: + + - usmUserAuthKeyChange, usmUserOwnAuthKeyChange + and usmUserPublic for a user who employs + authentication, and + - usmUserPrivKeyChange, usmUserOwnPrivKeyChange + and usmUserPublic for a user who employs + privacy. + + Note that any user who employs authentication or + privacy must allow its secret(s) to be updated and + thus cannot be 'readOnly'. + + If an initial set operation tries to set the value to + 'readOnly' for a user who employs authentication or + privacy, then an 'inconsistentValue' error must be + returned. Note that if the value has been previously + set (implicit or explicit) to any value, then the rules + as defined in the StorageType Textual Convention apply. + + It is an implementation issue to decide if a SET for + a readOnly or permanent row is accepted at all. In some + contexts this may make sense, in others it may not. If + a SET for a readOnly or permanent row is not accepted + at all, then a 'wrongValue' error must be returned. + " + DEFVAL { nonVolatile } + ::= { usmUserEntry 12 } + +usmUserStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION "The status of this conceptual row. + + Until instances of all corresponding columns are + appropriately configured, the value of the + corresponding instance of the usmUserStatus column + is 'notReady'. + + In particular, a newly created row for a user who + employs authentication, cannot be made active until the + corresponding usmUserCloneFrom and usmUserAuthKeyChange + have been set. + + Further, a newly created row for a user who also + employs privacy, cannot be made active until the + usmUserPrivKeyChange has been set. + + The RowStatus TC [RFC2579] requires that this + DESCRIPTION clause states under which circumstances + other objects in this row can be modified: + + The value of this object has no effect on whether + other objects in this conceptual row can be modified, + except for usmUserOwnAuthKeyChange and + usmUserOwnPrivKeyChange. For these 2 objects, the + + value of usmUserStatus MUST be active. + " + ::= { usmUserEntry 13 } + +-- Conformance Information ******************************************* + +usmMIBCompliances OBJECT IDENTIFIER ::= { usmMIBConformance 1 } +usmMIBGroups OBJECT IDENTIFIER ::= { usmMIBConformance 2 } + +-- Compliance statements + +usmMIBCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION "The compliance statement for SNMP engines which + implement the SNMP-USER-BASED-SM-MIB. + " + + MODULE -- this module + MANDATORY-GROUPS { usmMIBBasicGroup } + + OBJECT usmUserAuthProtocol + MIN-ACCESS read-only + DESCRIPTION "Write access is not required." + + OBJECT usmUserPrivProtocol + MIN-ACCESS read-only + DESCRIPTION "Write access is not required." + ::= { usmMIBCompliances 1 } + +-- Units of compliance +usmMIBBasicGroup OBJECT-GROUP + OBJECTS { + usmStatsUnsupportedSecLevels, + usmStatsNotInTimeWindows, + usmStatsUnknownUserNames, + usmStatsUnknownEngineIDs, + usmStatsWrongDigests, + usmStatsDecryptionErrors, + usmUserSpinLock, + usmUserSecurityName, + usmUserCloneFrom, + usmUserAuthProtocol, + usmUserAuthKeyChange, + usmUserOwnAuthKeyChange, + usmUserPrivProtocol, + usmUserPrivKeyChange, + usmUserOwnPrivKeyChange, + usmUserPublic, + usmUserStorageType, + usmUserStatus + } + STATUS current + DESCRIPTION "A collection of objects providing for configuration + of an SNMP engine which implements the SNMP + User-based Security Model. + " + ::= { usmMIBGroups 1 } + +END diff --git a/mibs/SNMP-USM-AES-MIB.txt b/mibs/SNMP-USM-AES-MIB.txt new file mode 100644 index 0000000..4c17302 --- /dev/null +++ b/mibs/SNMP-USM-AES-MIB.txt @@ -0,0 +1,62 @@ +SNMP-USM-AES-MIB DEFINITIONS ::= BEGIN + IMPORTS + MODULE-IDENTITY, OBJECT-IDENTITY, + snmpModules FROM SNMPv2-SMI -- [RFC2578] + snmpPrivProtocols FROM SNMP-FRAMEWORK-MIB; -- [RFC3411] + +snmpUsmAesMIB MODULE-IDENTITY + LAST-UPDATED "200406140000Z" + ORGANIZATION "IETF" + CONTACT-INFO "Uri Blumenthal + Lucent Technologies / Bell Labs + 67 Whippany Rd. + 14D-318 + Whippany, NJ 07981, USA + 973-386-2163 + uri@bell-labs.com + + Fabio Maino + Andiamo Systems, Inc. + 375 East Tasman Drive + San Jose, CA 95134, USA + 408-853-7530 + fmaino@andiamo.com + + Keith McCloghrie + Cisco Systems, Inc. + 170 West Tasman Drive + San Jose, CA 95134-1706, USA + + 408-526-5260 + kzm@cisco.com" + DESCRIPTION "Definitions of Object Identities needed for + the use of AES by SNMP's User-based Security + Model. + + Copyright (C) The Internet Society (2004). + + This version of this MIB module is part of RFC 3826; + see the RFC itself for full legal notices. + Supplementary information may be available on + http://www.ietf.org/copyrights/ianamib.html." + + REVISION "200406140000Z" + DESCRIPTION "Initial version, published as RFC3826" + ::= { snmpModules 20 } + +usmAesCfb128Protocol OBJECT-IDENTITY + STATUS current + DESCRIPTION "The CFB128-AES-128 Privacy Protocol." + REFERENCE "- Specification for the ADVANCED ENCRYPTION + STANDARD. Federal Information Processing + Standard (FIPS) Publication 197. + (November 2001). + + - Dworkin, M., NIST Recommendation for Block + Cipher Modes of Operation, Methods and + Techniques. NIST Special Publication 800-38A + (December 2001). + " + ::= { snmpPrivProtocols 4 } + +END diff --git a/mibs/SNMP-USM-DH-OBJECTS-MIB.txt b/mibs/SNMP-USM-DH-OBJECTS-MIB.txt new file mode 100644 index 0000000..7377425 --- /dev/null +++ b/mibs/SNMP-USM-DH-OBJECTS-MIB.txt @@ -0,0 +1,532 @@ +SNMP-USM-DH-OBJECTS-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, + -- OBJECT-IDENTITY, + experimental, Integer32 + FROM SNMPv2-SMI + TEXTUAL-CONVENTION + FROM SNMPv2-TC + MODULE-COMPLIANCE, OBJECT-GROUP + FROM SNMPv2-CONF + usmUserEntry + FROM SNMP-USER-BASED-SM-MIB + SnmpAdminString + FROM SNMP-FRAMEWORK-MIB; + +snmpUsmDHObjectsMIB MODULE-IDENTITY + LAST-UPDATED "200003060000Z" -- 6 March 2000, Midnight + ORGANIZATION "Excite@Home" + CONTACT-INFO "Author: Mike StJohns + Postal: Excite@Home + 450 Broadway + Redwood City, CA 94063 + Email: stjohns@corp.home.net + Phone: +1-650-556-5368" + DESCRIPTION + "The management information definitions for providing forward + secrecy for key changes for the usmUserTable, and for providing a + method for 'kickstarting' access to the agent via a Diffie-Helman + key agreement." + + REVISION "200003060000Z" + DESCRIPTION + "Initial version published as RFC 2786." + ::= { experimental 101 } -- IANA DHKEY-CHANGE 101 + +-- Administrative assignments + +usmDHKeyObjects OBJECT IDENTIFIER ::= { snmpUsmDHObjectsMIB 1 } +usmDHKeyConformance OBJECT IDENTIFIER ::= { snmpUsmDHObjectsMIB 2 } + +-- Textual conventions + +DHKeyChange ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Upon initialization, or upon creation of a row containing an + object of this type, and after any successful SET of this value, a + GET of this value returns 'y' where y = g^xa MOD p, and where g is + the base from usmDHParameters, p is the prime from + usmDHParameters, and xa is a new random integer selected by the + agent in the interval 2^(l-1) <= xa < 2^l < p-1. 'l' is the + optional privateValueLength from usmDHParameters in bits. If 'l' + is omitted, then xa (and xr below) is selected in the interval 0 + <= xa < p-1. y is expressed as an OCTET STRING 'PV' of length 'k' + which satisfies + + k + y = SUM 2^(8(k-i)) PV'i + i=1 + + where PV1,...,PVk are the octets of PV from first to last, and + where PV1 <> 0. + + A successful SET consists of the value 'y' expressed as an OCTET + STRING as above concatenated with the value 'z'(expressed as an + OCTET STRING in the same manner as y) where z = g^xr MOD p, where + g, p and l are as above, and where xr is a new random integer + selected by the manager in the interval 2^(l-1) <= xr < 2^l < + p-1. A SET to an object of this type will fail with the error + wrongValue if the current 'y' does not match the 'y' portion of + the value of the varbind for the object. (E.g. GET yout, SET + concat(yin, z), yout <> yin). + + Note that the private values xa and xr are never transmitted from + manager to device or vice versa, only the values y and z. + Obviously, these values must be retained until a successful SET on + the associated object. + + The shared secret 'sk' is calculated at the agent as sk = z^xa MOD + p, and at the manager as sk = y^xr MOD p. + + Each object definition of this type MUST describe how to map from + the shared secret 'sk' to the operational key value used by the + protocols and operations related to the object. In general, if n + bits of key are required, the author suggests using the n + right-most bits of the shared secret as the operational key value." + REFERENCE + "-- Diffie-Hellman Key-Agreement Standard, PKCS #3; + RSA Laboratories, November 1993" + SYNTAX OCTET STRING + +-- Diffie Hellman public values + +usmDHPublicObjects OBJECT IDENTIFIER ::= { usmDHKeyObjects 1 } + +usmDHParameters OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The public Diffie-Hellman parameters for doing a Diffie-Hellman + key agreement for this device. This is encoded as an ASN.1 + DHParameter per PKCS #3, section 9. E.g. + + DHParameter ::= SEQUENCE { + prime INTEGER, -- p + base INTEGER, -- g + privateValueLength INTEGER OPTIONAL } + + Implementors are encouraged to use either the values from + Oakley Group 1 or the values of from Oakley Group 2 as specified + in RFC-2409, The Internet Key Exchange, Section 6.1, 6.2 as the + default for this object. Other values may be used, but the + security properties of those values MUST be well understood and + MUST meet the requirements of PKCS #3 for the selection of + Diffie-Hellman primes. + + In addition, any time usmDHParameters changes, all values of + type DHKeyChange will change and new random numbers MUST be + generated by the agent for each DHKeyChange object." + REFERENCE + "-- Diffie-Hellman Key-Agreement Standard, PKCS #3, + RSA Laboratories, November 1993 + -- The Internet Key Exchange, RFC 2409, November 1998, + Sec 6.1, 6.2" + ::= { usmDHPublicObjects 1 } + +usmDHUserKeyTable OBJECT-TYPE + SYNTAX SEQUENCE OF UsmDHUserKeyEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "This table augments and extends the usmUserTable and provides + 4 objects which exactly mirror the objects in that table with the + textual convention of 'KeyChange'. This extension allows key + changes to be done in a manner where the knowledge of the current + secret plus knowledge of the key change data exchanges (e.g. via + wiretapping) will not reveal the new key." + ::= { usmDHPublicObjects 2 } + +usmDHUserKeyEntry OBJECT-TYPE + SYNTAX UsmDHUserKeyEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A row of DHKeyChange objects which augment or replace the + functionality of the KeyChange objects in the base table row." + AUGMENTS { usmUserEntry } + ::= {usmDHUserKeyTable 1 } + +UsmDHUserKeyEntry ::= SEQUENCE { + usmDHUserAuthKeyChange DHKeyChange, + usmDHUserOwnAuthKeyChange DHKeyChange, + usmDHUserPrivKeyChange DHKeyChange, + usmDHUserOwnPrivKeyChange DHKeyChange + } + +usmDHUserAuthKeyChange OBJECT-TYPE + SYNTAX DHKeyChange + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The object used to change any given user's Authentication Key + using a Diffie-Hellman key exchange. + + The right-most n bits of the shared secret 'sk', where 'n' is the + number of bits required for the protocol defined by + usmUserAuthProtocol, are installed as the operational + authentication key for this row after a successful SET." + ::= { usmDHUserKeyEntry 1 } + +usmDHUserOwnAuthKeyChange OBJECT-TYPE + SYNTAX DHKeyChange + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The object used to change the agents own Authentication Key + using a Diffie-Hellman key exchange. + + The right-most n bits of the shared secret 'sk', where 'n' is the + number of bits required for the protocol defined by + usmUserAuthProtocol, are installed as the operational + authentication key for this row after a successful SET." + ::= { usmDHUserKeyEntry 2 } + +usmDHUserPrivKeyChange OBJECT-TYPE + SYNTAX DHKeyChange + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The object used to change any given user's Privacy Key using + a Diffie-Hellman key exchange. + + The right-most n bits of the shared secret 'sk', where 'n' is the + number of bits required for the protocol defined by + usmUserPrivProtocol, are installed as the operational privacy key + for this row after a successful SET." + ::= { usmDHUserKeyEntry 3 } + +usmDHUserOwnPrivKeyChange OBJECT-TYPE + SYNTAX DHKeyChange + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The object used to change the agent's own Privacy Key using a + Diffie-Hellman key exchange. + + The right-most n bits of the shared secret 'sk', where 'n' is the + number of bits required for the protocol defined by + usmUserPrivProtocol, are installed as the operational privacy key + for this row after a successful SET." + ::= { usmDHUserKeyEntry 4 } + +usmDHKickstartGroup OBJECT IDENTIFIER ::= { usmDHKeyObjects 2 } + +usmDHKickstartTable OBJECT-TYPE + SYNTAX SEQUENCE OF UsmDHKickstartEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table of mappings between zero or more Diffie-Helman key + agreement values and entries in the usmUserTable. Entries in this + table are created by providing the associated device with a + Diffie-Helman public value and a usmUserName/usmUserSecurityName + pair during initialization. How these values are provided is + outside the scope of this MIB, but could be provided manually, or + through a configuration file. Valid public value/name pairs + result in the creation of a row in this table as well as the + creation of an associated row (with keys derived as indicated) in + the usmUserTable. The actual access the related usmSecurityName + has is dependent on the entries in the VACM tables. In general, + an implementor will specify one or more standard security names + and will provide entries in the VACM tables granting various + levels of access to those names. The actual content of the VACM + + table is beyond the scope of this MIB. + + Note: This table is expected to be readable without authentication + using the usmUserSecurityName 'dhKickstart'. See the conformance + statements for details." + ::= { usmDHKickstartGroup 1 } + +usmDHKickstartEntry OBJECT-TYPE + SYNTAX UsmDHKickstartEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry in the usmDHKickstartTable. The agent SHOULD either + delete this entry or mark it as inactive upon a successful SET of + any of the KeyChange-typed objects in the usmUserEntry or upon a + successful SET of any of the DHKeyChange-typed objects in the + usmDhKeyChangeEntry where the related usmSecurityName (e.g. row of + usmUserTable or row of ushDhKeyChangeTable) equals this entry's + usmDhKickstartSecurityName. In otherwords, once you've changed + one or more of the keys for a row in usmUserTable with a + particular security name, the row in this table with that same + security name is no longer useful or meaningful." + INDEX { usmDHKickstartIndex } + ::= {usmDHKickstartTable 1 } + +UsmDHKickstartEntry ::= SEQUENCE { + usmDHKickstartIndex Integer32, + usmDHKickstartMyPublic OCTET STRING, + usmDHKickstartMgrPublic OCTET STRING, + usmDHKickstartSecurityName SnmpAdminString + } + +usmDHKickstartIndex OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Index value for this row." + ::= { usmDHKickstartEntry 1 } + +usmDHKickstartMyPublic OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The agent's Diffie-Hellman public value for this row. At + + initialization, the agent generates a random number and derives + its public value from that number. This public value is published + here. This public value 'y' equals g^r MOD p where g is the from + the set of Diffie-Hellman parameters, p is the prime from those + parameters, and r is a random integer selected by the agent in the + interval 2^(l-1) <= r < p-1 < 2^l. If l is unspecified, then r is + a random integer selected in the interval 0 <= r < p-1 + + The public value is expressed as an OCTET STRING 'PV' of length + 'k' which satisfies + + k + y = SUM 2^(8(k-i)) PV'i + i = 1 + + where PV1,...,PVk are the octets of PV from first to last, and + where PV1 != 0. + + The following DH parameters (Oakley group #2, RFC 2409, sec 6.1, + 6.2) are used for this object: + + g = 2 + p = FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 + 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD + EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 + E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED + EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE65381 + FFFFFFFF FFFFFFFF + l=1024 + " + REFERENCE + "-- Diffie-Hellman Key-Agreement Standard, PKCS#3v1.4; + RSA Laboratories, November 1993 + -- The Internet Key Exchange, RFC2409; + Harkins, D., Carrel, D.; November 1998" + ::= { usmDHKickstartEntry 2 } + +usmDHKickstartMgrPublic OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The manager's Diffie-Hellman public value for this row. Note + that this value is not set via the SNMP agent, but may be set via + some out of band method, such as the device's configuration file. + + The manager calculates this value in the same manner and using the + same parameter set as the agent does. E.g. it selects a random + number 'r', calculates y = g^r mod p and provides 'y' as the + public number expressed as an OCTET STRING. See + usmDHKickstartMyPublic for details. + + When this object is set with a valid value during initialization, + a row is created in the usmUserTable with the following values: + + usmUserEngineID localEngineID + usmUserName [value of usmDHKickstartSecurityName] + usmUserSecurityName [value of usmDHKickstartSecurityName] + usmUserCloneFrom ZeroDotZero + usmUserAuthProtocol usmHMACMD5AuthProtocol + usmUserAuthKeyChange -- derived from set value + usmUserOwnAuthKeyChange -- derived from set value + usmUserPrivProtocol usmDESPrivProtocol + usmUserPrivKeyChange -- derived from set value + usmUserOwnPrivKeyChange -- derived from set value + usmUserPublic '' + usmUserStorageType permanent + usmUserStatus active + + A shared secret 'sk' is calculated at the agent as sk = + mgrPublic^r mod p where r is the agents random number and p is the + DH prime from the common parameters. The underlying privacy key + for this row is derived from sk by applying the key derivation + function PBKDF2 defined in PKCS#5v2.0 with a salt of 0xd1310ba6, + and iterationCount of 500, a keyLength of 16 (for + usmDESPrivProtocol), and a prf (pseudo random function) of + 'id-hmacWithSHA1'. The underlying authentication key for this row + is derived from sk by applying the key derivation function PBKDF2 + with a salt of 0x98dfb5ac , an interation count of 500, a + keyLength of 16 (for usmHMAC5AuthProtocol), and a prf of + 'id-hmacWithSHA1'. Note: The salts are the first two words in the + ks0 [key schedule 0] of the BLOWFISH cipher from 'Applied + Cryptography' by Bruce Schnier - they could be any relatively + random string of bits. + + The manager can use its knowledge of its own random number and the + agent's public value to kickstart its access to the agent in a + secure manner. Note that the security of this approach is + directly related to the strength of the authorization security of + the out of band provisioning of the managers public value + (e.g. the configuration file), but is not dependent at all on the + strength of the confidentiality of the out of band provisioning + data." + REFERENCE + "-- Password-Based Cryptography Standard, PKCS#5v2.0; + RSA Laboratories, March 1999 + -- Applied Cryptography, 2nd Ed.; B. Schneier, + Counterpane Systems; John Wiley & Sons, 1996" + ::= { usmDHKickstartEntry 3 } + +usmDHKickstartSecurityName OBJECT-TYPE + SYNTAX SnmpAdminString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The usmUserName and usmUserSecurityName in the usmUserTable + associated with this row. This is provided in the same manner and + at the same time as the usmDHKickstartMgrPublic value - + e.g. possibly manually, or via the device's configuration file." + ::= { usmDHKickstartEntry 4 } + +-- Conformance Information + +usmDHKeyMIBCompliances OBJECT IDENTIFIER ::= { usmDHKeyConformance 1 } +usmDHKeyMIBGroups OBJECT IDENTIFIER ::= { usmDHKeyConformance 2 } + +-- Compliance statements + +usmDHKeyMIBCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for this module." + MODULE + GROUP usmDHKeyMIBBasicGroup + DESCRIPTION + "This group MAY be implemented by any agent which + implements the usmUserTable and which wishes to provide the + ability to change user and agent authentication and privacy + keys via Diffie-Hellman key exchanges." + + GROUP usmDHKeyParamGroup + DESCRIPTION + "This group MUST be implemented by any agent which + implements a MIB containing the DHKeyChange Textual + Convention defined in this module." + + GROUP usmDHKeyKickstartGroup + DESCRIPTION + "This group MAY be implemented by any agent which + implements the usmUserTable and which wishes the ability to + populate the USM table based on out-of-band provided DH + ignition values. + + Any agent implementing this group is expected to provide + preinstalled entries in the vacm tables as follows: + + In the usmUserTable: This entry allows access to the + system and dhKickstart groups + + usmUserEngineID localEngineID + usmUserName 'dhKickstart' + usmUserSecurityName 'dhKickstart' + usmUserCloneFrom ZeroDotZero + usmUserAuthProtocol none + usmUserAuthKeyChange '' + usmUserOwnAuthKeyChange '' + usmUserPrivProtocol none + usmUserPrivKeyChange '' + usmUserOwnPrivKeyChange '' + usmUserPublic '' + usmUserStorageType permanent + usmUserStatus active + + In the vacmSecurityToGroupTable: This maps the initial + user into the accessible objects. + + vacmSecurityModel 3 (USM) + vacmSecurityName 'dhKickstart' + vacmGroupName 'dhKickstart' + vacmSecurityToGroupStorageType permanent + vacmSecurityToGroupStatus active + + In the vacmAccessTable: Group name to view name translation. + + vacmGroupName 'dhKickstart' + vacmAccessContextPrefix '' + vacmAccessSecurityModel 3 (USM) + vacmAccessSecurityLevel noAuthNoPriv + vacmAccessContextMatch exact + vacmAccessReadViewName 'dhKickRestricted' + vacmAccessWriteViewName '' + vacmAccessNotifyViewName 'dhKickRestricted' + vacmAccessStorageType permanent + vacmAccessStatus active + + In the vacmViewTreeFamilyTable: Two entries to allow the + initial entry to access the system and kickstart groups. + + vacmViewTreeFamilyViewName 'dhKickRestricted' + vacmViewTreeFamilySubtree 1.3.6.1.2.1.1 (system) + vacmViewTreeFamilyMask '' + + vacmViewTreeFamilyType 1 + vacmViewTreeFamilyStorageType permanent + vacmViewTreeFamilyStatus active + + vacmViewTreeFamilyViewName 'dhKickRestricted' + vacmViewTreeFamilySubtree (usmDHKickstartTable OID) + vacmViewTreeFamilyMask '' + vacmViewTreeFamilyType 1 + vacmViewTreeFamilyStorageType permanent + vacmViewTreeFamilyStatus active + " + + OBJECT usmDHParameters + MIN-ACCESS read-only + DESCRIPTION + "It is compliant to implement this object as read-only for + any device." + ::= { usmDHKeyMIBCompliances 1 } + +-- Units of Compliance + +usmDHKeyMIBBasicGroup OBJECT-GROUP + OBJECTS { + usmDHUserAuthKeyChange, + usmDHUserOwnAuthKeyChange, + usmDHUserPrivKeyChange, + usmDHUserOwnPrivKeyChange + } + STATUS current + DESCRIPTION + "" + ::= { usmDHKeyMIBGroups 1 } + +usmDHKeyParamGroup OBJECT-GROUP + OBJECTS { + usmDHParameters + } + STATUS current + DESCRIPTION + "The mandatory object for all MIBs which use the DHKeyChange + textual convention." + ::= { usmDHKeyMIBGroups 2 } + +usmDHKeyKickstartGroup OBJECT-GROUP + OBJECTS { + usmDHKickstartMyPublic, + usmDHKickstartMgrPublic, + usmDHKickstartSecurityName + } + STATUS current + DESCRIPTION + "The objects used for kickstarting one or more SNMPv3 USM + associations via a configuration file or other out of band, + non-confidential access." + ::= { usmDHKeyMIBGroups 3 } + +END diff --git a/mibs/SNMP-VIEW-BASED-ACM-MIB.txt b/mibs/SNMP-VIEW-BASED-ACM-MIB.txt new file mode 100644 index 0000000..7244ad0 --- /dev/null +++ b/mibs/SNMP-VIEW-BASED-ACM-MIB.txt @@ -0,0 +1,830 @@ +SNMP-VIEW-BASED-ACM-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF + MODULE-IDENTITY, OBJECT-TYPE, + snmpModules FROM SNMPv2-SMI + TestAndIncr, + RowStatus, StorageType FROM SNMPv2-TC + SnmpAdminString, + SnmpSecurityLevel, + SnmpSecurityModel FROM SNMP-FRAMEWORK-MIB; + +snmpVacmMIB MODULE-IDENTITY + LAST-UPDATED "200210160000Z" -- 16 Oct 2002, midnight + ORGANIZATION "SNMPv3 Working Group" + CONTACT-INFO "WG-email: snmpv3@lists.tislabs.com + Subscribe: majordomo@lists.tislabs.com + In message body: subscribe snmpv3 + + Co-Chair: Russ Mundy + Network Associates Laboratories + postal: 15204 Omega Drive, Suite 300 + Rockville, MD 20850-4601 + USA + email: mundy@tislabs.com + phone: +1 301-947-7107 + + Co-Chair: David Harrington + Enterasys Networks + Postal: 35 Industrial Way + P. O. Box 5004 + Rochester, New Hampshire 03866-5005 + USA + EMail: dbh@enterasys.com + Phone: +1 603-337-2614 + + Co-editor: Bert Wijnen + Lucent Technologies + postal: Schagen 33 + 3461 GL Linschoten + Netherlands + email: bwijnen@lucent.com + phone: +31-348-480-685 + + Co-editor: Randy Presuhn + BMC Software, Inc. + + postal: 2141 North First Street + San Jose, CA 95131 + USA + email: randy_presuhn@bmc.com + phone: +1 408-546-1006 + + Co-editor: Keith McCloghrie + Cisco Systems, Inc. + postal: 170 West Tasman Drive + San Jose, CA 95134-1706 + USA + email: kzm@cisco.com + phone: +1-408-526-5260 + " + DESCRIPTION "The management information definitions for the + View-based Access Control Model for SNMP. + + Copyright (C) The Internet Society (2002). This + version of this MIB module is part of RFC 3415; + see the RFC itself for full legal notices. + " +-- Revision history + + REVISION "200210160000Z" -- 16 Oct 2002, midnight + DESCRIPTION "Clarifications, published as RFC3415" + + REVISION "199901200000Z" -- 20 Jan 1999, midnight + DESCRIPTION "Clarifications, published as RFC2575" + + REVISION "199711200000Z" -- 20 Nov 1997, midnight + DESCRIPTION "Initial version, published as RFC2275" + ::= { snmpModules 16 } + +-- Administrative assignments **************************************** + +vacmMIBObjects OBJECT IDENTIFIER ::= { snmpVacmMIB 1 } +vacmMIBConformance OBJECT IDENTIFIER ::= { snmpVacmMIB 2 } + +-- Information about Local Contexts ********************************** + +vacmContextTable OBJECT-TYPE + SYNTAX SEQUENCE OF VacmContextEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "The table of locally available contexts. + + This table provides information to SNMP Command + + Generator applications so that they can properly + configure the vacmAccessTable to control access to + all contexts at the SNMP entity. + + This table may change dynamically if the SNMP entity + allows that contexts are added/deleted dynamically + (for instance when its configuration changes). Such + changes would happen only if the management + instrumentation at that SNMP entity recognizes more + (or fewer) contexts. + + The presence of entries in this table and of entries + in the vacmAccessTable are independent. That is, a + context identified by an entry in this table is not + necessarily referenced by any entries in the + vacmAccessTable; and the context(s) referenced by an + entry in the vacmAccessTable does not necessarily + currently exist and thus need not be identified by an + entry in this table. + + This table must be made accessible via the default + context so that Command Responder applications have + a standard way of retrieving the information. + + This table is read-only. It cannot be configured via + SNMP. + " + ::= { vacmMIBObjects 1 } + +vacmContextEntry OBJECT-TYPE + SYNTAX VacmContextEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "Information about a particular context." + INDEX { + vacmContextName + } + ::= { vacmContextTable 1 } + +VacmContextEntry ::= SEQUENCE + { + vacmContextName SnmpAdminString + } + +vacmContextName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION "A human readable name identifying a particular + context at a particular SNMP entity. + + The empty contextName (zero length) represents the + default context. + " + ::= { vacmContextEntry 1 } + +-- Information about Groups ****************************************** + +vacmSecurityToGroupTable OBJECT-TYPE + SYNTAX SEQUENCE OF VacmSecurityToGroupEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "This table maps a combination of securityModel and + securityName into a groupName which is used to define + an access control policy for a group of principals. + " + ::= { vacmMIBObjects 2 } + +vacmSecurityToGroupEntry OBJECT-TYPE + SYNTAX VacmSecurityToGroupEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "An entry in this table maps the combination of a + securityModel and securityName into a groupName. + " + INDEX { + vacmSecurityModel, + vacmSecurityName + } + ::= { vacmSecurityToGroupTable 1 } + +VacmSecurityToGroupEntry ::= SEQUENCE + { + vacmSecurityModel SnmpSecurityModel, + vacmSecurityName SnmpAdminString, + vacmGroupName SnmpAdminString, + vacmSecurityToGroupStorageType StorageType, + vacmSecurityToGroupStatus RowStatus + } + +vacmSecurityModel OBJECT-TYPE + SYNTAX SnmpSecurityModel(1..2147483647) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "The Security Model, by which the vacmSecurityName + referenced by this entry is provided. + + Note, this object may not take the 'any' (0) value. + " + ::= { vacmSecurityToGroupEntry 1 } + +vacmSecurityName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(1..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "The securityName for the principal, represented in a + Security Model independent format, which is mapped by + this entry to a groupName. + " + ::= { vacmSecurityToGroupEntry 2 } + +vacmGroupName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(1..32)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION "The name of the group to which this entry (e.g., the + combination of securityModel and securityName) + belongs. + + This groupName is used as index into the + vacmAccessTable to select an access control policy. + However, a value in this table does not imply that an + instance with the value exists in table vacmAccesTable. + " + ::= { vacmSecurityToGroupEntry 3 } + +vacmSecurityToGroupStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION "The storage type for this conceptual row. + Conceptual rows having the value 'permanent' need not + allow write-access to any columnar objects in the row. + " + DEFVAL { nonVolatile } + ::= { vacmSecurityToGroupEntry 4 } + +vacmSecurityToGroupStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION "The status of this conceptual row. + + Until instances of all corresponding columns are + appropriately configured, the value of the + + corresponding instance of the vacmSecurityToGroupStatus + column is 'notReady'. + + In particular, a newly created row cannot be made + active until a value has been set for vacmGroupName. + + The RowStatus TC [RFC2579] requires that this + DESCRIPTION clause states under which circumstances + other objects in this row can be modified: + + The value of this object has no effect on whether + other objects in this conceptual row can be modified. + " + ::= { vacmSecurityToGroupEntry 5 } + +-- Information about Access Rights *********************************** + +vacmAccessTable OBJECT-TYPE + SYNTAX SEQUENCE OF VacmAccessEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "The table of access rights for groups. + + Each entry is indexed by a groupName, a contextPrefix, + a securityModel and a securityLevel. To determine + whether access is allowed, one entry from this table + needs to be selected and the proper viewName from that + entry must be used for access control checking. + + To select the proper entry, follow these steps: + + 1) the set of possible matches is formed by the + intersection of the following sets of entries: + + the set of entries with identical vacmGroupName + the union of these two sets: + - the set with identical vacmAccessContextPrefix + - the set of entries with vacmAccessContextMatch + value of 'prefix' and matching + vacmAccessContextPrefix + intersected with the union of these two sets: + - the set of entries with identical + vacmSecurityModel + - the set of entries with vacmSecurityModel + value of 'any' + intersected with the set of entries with + vacmAccessSecurityLevel value less than or equal + to the requested securityLevel + + 2) if this set has only one member, we're done + otherwise, it comes down to deciding how to weight + the preferences between ContextPrefixes, + SecurityModels, and SecurityLevels as follows: + a) if the subset of entries with securityModel + matching the securityModel in the message is + not empty, then discard the rest. + b) if the subset of entries with + vacmAccessContextPrefix matching the contextName + in the message is not empty, + then discard the rest + c) discard all entries with ContextPrefixes shorter + than the longest one remaining in the set + d) select the entry with the highest securityLevel + + Please note that for securityLevel noAuthNoPriv, all + groups are really equivalent since the assumption that + the securityName has been authenticated does not hold. + " + ::= { vacmMIBObjects 4 } + +vacmAccessEntry OBJECT-TYPE + SYNTAX VacmAccessEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "An access right configured in the Local Configuration + Datastore (LCD) authorizing access to an SNMP context. + + Entries in this table can use an instance value for + object vacmGroupName even if no entry in table + vacmAccessSecurityToGroupTable has a corresponding + value for object vacmGroupName. + " + INDEX { vacmGroupName, + vacmAccessContextPrefix, + vacmAccessSecurityModel, + vacmAccessSecurityLevel + } + ::= { vacmAccessTable 1 } + +VacmAccessEntry ::= SEQUENCE + { + vacmAccessContextPrefix SnmpAdminString, + vacmAccessSecurityModel SnmpSecurityModel, + vacmAccessSecurityLevel SnmpSecurityLevel, + vacmAccessContextMatch INTEGER, + vacmAccessReadViewName SnmpAdminString, + vacmAccessWriteViewName SnmpAdminString, + vacmAccessNotifyViewName SnmpAdminString, + vacmAccessStorageType StorageType, + vacmAccessStatus RowStatus + } + +vacmAccessContextPrefix OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "In order to gain the access rights allowed by this + conceptual row, a contextName must match exactly + (if the value of vacmAccessContextMatch is 'exact') + or partially (if the value of vacmAccessContextMatch + is 'prefix') to the value of the instance of this + object. + " + ::= { vacmAccessEntry 1 } + +vacmAccessSecurityModel OBJECT-TYPE + SYNTAX SnmpSecurityModel + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "In order to gain the access rights allowed by this + conceptual row, this securityModel must be in use. + " + ::= { vacmAccessEntry 2 } + +vacmAccessSecurityLevel OBJECT-TYPE + SYNTAX SnmpSecurityLevel + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "The minimum level of security required in order to + gain the access rights allowed by this conceptual + row. A securityLevel of noAuthNoPriv is less than + authNoPriv which in turn is less than authPriv. + + If multiple entries are equally indexed except for + this vacmAccessSecurityLevel index, then the entry + which has the highest value for + vacmAccessSecurityLevel is selected. + " + ::= { vacmAccessEntry 3 } + +vacmAccessContextMatch OBJECT-TYPE + SYNTAX INTEGER + { exact (1), -- exact match of prefix and contextName + prefix (2) -- Only match to the prefix + } + MAX-ACCESS read-create + STATUS current + DESCRIPTION "If the value of this object is exact(1), then all + rows where the contextName exactly matches + vacmAccessContextPrefix are selected. + + If the value of this object is prefix(2), then all + rows where the contextName whose starting octets + exactly match vacmAccessContextPrefix are selected. + This allows for a simple form of wildcarding. + " + DEFVAL { exact } + ::= { vacmAccessEntry 4 } + +vacmAccessReadViewName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION "The value of an instance of this object identifies + the MIB view of the SNMP context to which this + conceptual row authorizes read access. + + The identified MIB view is that one for which the + vacmViewTreeFamilyViewName has the same value as the + instance of this object; if the value is the empty + string or if there is no active MIB view having this + value of vacmViewTreeFamilyViewName, then no access + is granted. + " + DEFVAL { ''H } -- the empty string + ::= { vacmAccessEntry 5 } + +vacmAccessWriteViewName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION "The value of an instance of this object identifies + the MIB view of the SNMP context to which this + conceptual row authorizes write access. + + The identified MIB view is that one for which the + vacmViewTreeFamilyViewName has the same value as the + instance of this object; if the value is the empty + string or if there is no active MIB view having this + value of vacmViewTreeFamilyViewName, then no access + is granted. + " + DEFVAL { ''H } -- the empty string + ::= { vacmAccessEntry 6 } + +vacmAccessNotifyViewName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(0..32)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION "The value of an instance of this object identifies + the MIB view of the SNMP context to which this + conceptual row authorizes access for notifications. + + The identified MIB view is that one for which the + vacmViewTreeFamilyViewName has the same value as the + instance of this object; if the value is the empty + string or if there is no active MIB view having this + value of vacmViewTreeFamilyViewName, then no access + is granted. + " + DEFVAL { ''H } -- the empty string + ::= { vacmAccessEntry 7 } + +vacmAccessStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION "The storage type for this conceptual row. + + Conceptual rows having the value 'permanent' need not + allow write-access to any columnar objects in the row. + " + DEFVAL { nonVolatile } + ::= { vacmAccessEntry 8 } + +vacmAccessStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION "The status of this conceptual row. + + The RowStatus TC [RFC2579] requires that this + DESCRIPTION clause states under which circumstances + other objects in this row can be modified: + + The value of this object has no effect on whether + other objects in this conceptual row can be modified. + " + ::= { vacmAccessEntry 9 } + +-- Information about MIB views *************************************** + +-- Support for instance-level granularity is optional. +-- +-- In some implementations, instance-level access control +-- granularity may come at a high performance cost. Managers +-- should avoid requesting such configurations unnecessarily. + +vacmMIBViews OBJECT IDENTIFIER ::= { vacmMIBObjects 5 } + +vacmViewSpinLock OBJECT-TYPE + SYNTAX TestAndIncr + MAX-ACCESS read-write + STATUS current + DESCRIPTION "An advisory lock used to allow cooperating SNMP + Command Generator applications to coordinate their + use of the Set operation in creating or modifying + views. + + When creating a new view or altering an existing + view, it is important to understand the potential + interactions with other uses of the view. The + vacmViewSpinLock should be retrieved. The name of + the view to be created should be determined to be + unique by the SNMP Command Generator application by + consulting the vacmViewTreeFamilyTable. Finally, + the named view may be created (Set), including the + advisory lock. + If another SNMP Command Generator application has + altered the views in the meantime, then the spin + lock's value will have changed, and so this creation + will fail because it will specify the wrong value for + the spin lock. + + Since this is an advisory lock, the use of this lock + is not enforced. + " + ::= { vacmMIBViews 1 } + +vacmViewTreeFamilyTable OBJECT-TYPE + SYNTAX SEQUENCE OF VacmViewTreeFamilyEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "Locally held information about families of subtrees + within MIB views. + + Each MIB view is defined by two sets of view subtrees: + - the included view subtrees, and + - the excluded view subtrees. + Every such view subtree, both the included and the + + excluded ones, is defined in this table. + + To determine if a particular object instance is in + a particular MIB view, compare the object instance's + OBJECT IDENTIFIER with each of the MIB view's active + entries in this table. If none match, then the + object instance is not in the MIB view. If one or + more match, then the object instance is included in, + or excluded from, the MIB view according to the + value of vacmViewTreeFamilyType in the entry whose + value of vacmViewTreeFamilySubtree has the most + sub-identifiers. If multiple entries match and have + the same number of sub-identifiers (when wildcarding + is specified with the value of vacmViewTreeFamilyMask), + then the lexicographically greatest instance of + vacmViewTreeFamilyType determines the inclusion or + exclusion. + + An object instance's OBJECT IDENTIFIER X matches an + active entry in this table when the number of + sub-identifiers in X is at least as many as in the + value of vacmViewTreeFamilySubtree for the entry, + and each sub-identifier in the value of + vacmViewTreeFamilySubtree matches its corresponding + sub-identifier in X. Two sub-identifiers match + either if the corresponding bit of the value of + vacmViewTreeFamilyMask for the entry is zero (the + 'wild card' value), or if they are equal. + + A 'family' of subtrees is the set of subtrees defined + by a particular combination of values of + vacmViewTreeFamilySubtree and vacmViewTreeFamilyMask. + + In the case where no 'wild card' is defined in the + vacmViewTreeFamilyMask, the family of subtrees reduces + to a single subtree. + + When creating or changing MIB views, an SNMP Command + Generator application should utilize the + vacmViewSpinLock to try to avoid collisions. See + DESCRIPTION clause of vacmViewSpinLock. + + When creating MIB views, it is strongly advised that + first the 'excluded' vacmViewTreeFamilyEntries are + created and then the 'included' entries. + + When deleting MIB views, it is strongly advised that + first the 'included' vacmViewTreeFamilyEntries are + + deleted and then the 'excluded' entries. + + If a create for an entry for instance-level access + control is received and the implementation does not + support instance-level granularity, then an + inconsistentName error must be returned. + " + ::= { vacmMIBViews 2 } + +vacmViewTreeFamilyEntry OBJECT-TYPE + SYNTAX VacmViewTreeFamilyEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "Information on a particular family of view subtrees + included in or excluded from a particular SNMP + context's MIB view. + + Implementations must not restrict the number of + families of view subtrees for a given MIB view, + except as dictated by resource constraints on the + overall number of entries in the + vacmViewTreeFamilyTable. + + If no conceptual rows exist in this table for a given + MIB view (viewName), that view may be thought of as + consisting of the empty set of view subtrees. + " + INDEX { vacmViewTreeFamilyViewName, + vacmViewTreeFamilySubtree + } + ::= { vacmViewTreeFamilyTable 1 } + +VacmViewTreeFamilyEntry ::= SEQUENCE + { + vacmViewTreeFamilyViewName SnmpAdminString, + vacmViewTreeFamilySubtree OBJECT IDENTIFIER, + vacmViewTreeFamilyMask OCTET STRING, + vacmViewTreeFamilyType INTEGER, + vacmViewTreeFamilyStorageType StorageType, + vacmViewTreeFamilyStatus RowStatus + } + +vacmViewTreeFamilyViewName OBJECT-TYPE + SYNTAX SnmpAdminString (SIZE(1..32)) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "The human readable name for a family of view subtrees. + " + ::= { vacmViewTreeFamilyEntry 1 } + +vacmViewTreeFamilySubtree OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "The MIB subtree which when combined with the + corresponding instance of vacmViewTreeFamilyMask + defines a family of view subtrees. + " + ::= { vacmViewTreeFamilyEntry 2 } + +vacmViewTreeFamilyMask OBJECT-TYPE + SYNTAX OCTET STRING (SIZE (0..16)) + MAX-ACCESS read-create + STATUS current + DESCRIPTION "The bit mask which, in combination with the + corresponding instance of vacmViewTreeFamilySubtree, + defines a family of view subtrees. + + Each bit of this bit mask corresponds to a + sub-identifier of vacmViewTreeFamilySubtree, with the + most significant bit of the i-th octet of this octet + string value (extended if necessary, see below) + corresponding to the (8*i - 7)-th sub-identifier, and + the least significant bit of the i-th octet of this + octet string corresponding to the (8*i)-th + sub-identifier, where i is in the range 1 through 16. + + Each bit of this bit mask specifies whether or not + the corresponding sub-identifiers must match when + determining if an OBJECT IDENTIFIER is in this + family of view subtrees; a '1' indicates that an + exact match must occur; a '0' indicates 'wild card', + i.e., any sub-identifier value matches. + + Thus, the OBJECT IDENTIFIER X of an object instance + is contained in a family of view subtrees if, for + each sub-identifier of the value of + vacmViewTreeFamilySubtree, either: + + the i-th bit of vacmViewTreeFamilyMask is 0, or + + the i-th sub-identifier of X is equal to the i-th + sub-identifier of the value of + vacmViewTreeFamilySubtree. + + If the value of this bit mask is M bits long and + + there are more than M sub-identifiers in the + corresponding instance of vacmViewTreeFamilySubtree, + then the bit mask is extended with 1's to be the + required length. + + Note that when the value of this object is the + zero-length string, this extension rule results in + a mask of all-1's being used (i.e., no 'wild card'), + and the family of view subtrees is the one view + subtree uniquely identified by the corresponding + instance of vacmViewTreeFamilySubtree. + + Note that masks of length greater than zero length + do not need to be supported. In this case this + object is made read-only. + " + DEFVAL { ''H } + ::= { vacmViewTreeFamilyEntry 3 } + +vacmViewTreeFamilyType OBJECT-TYPE + SYNTAX INTEGER { included(1), excluded(2) } + MAX-ACCESS read-create + STATUS current + DESCRIPTION "Indicates whether the corresponding instances of + vacmViewTreeFamilySubtree and vacmViewTreeFamilyMask + define a family of view subtrees which is included in + or excluded from the MIB view. + " + DEFVAL { included } + ::= { vacmViewTreeFamilyEntry 4 } + +vacmViewTreeFamilyStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION "The storage type for this conceptual row. + + Conceptual rows having the value 'permanent' need not + allow write-access to any columnar objects in the row. + " + DEFVAL { nonVolatile } + ::= { vacmViewTreeFamilyEntry 5 } + +vacmViewTreeFamilyStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION "The status of this conceptual row. + + The RowStatus TC [RFC2579] requires that this + DESCRIPTION clause states under which circumstances + other objects in this row can be modified: + + The value of this object has no effect on whether + other objects in this conceptual row can be modified. + " + ::= { vacmViewTreeFamilyEntry 6 } + +-- Conformance information ******************************************* + +vacmMIBCompliances OBJECT IDENTIFIER ::= { vacmMIBConformance 1 } +vacmMIBGroups OBJECT IDENTIFIER ::= { vacmMIBConformance 2 } + +-- Compliance statements ********************************************* + +vacmMIBCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION "The compliance statement for SNMP engines which + implement the SNMP View-based Access Control Model + configuration MIB. + " + MODULE -- this module + MANDATORY-GROUPS { vacmBasicGroup } + + OBJECT vacmAccessContextMatch + MIN-ACCESS read-only + DESCRIPTION "Write access is not required." + + OBJECT vacmAccessReadViewName + MIN-ACCESS read-only + DESCRIPTION "Write access is not required." + + OBJECT vacmAccessWriteViewName + MIN-ACCESS read-only + DESCRIPTION "Write access is not required." + + OBJECT vacmAccessNotifyViewName + MIN-ACCESS read-only + DESCRIPTION "Write access is not required." + + OBJECT vacmAccessStorageType + MIN-ACCESS read-only + DESCRIPTION "Write access is not required." + + OBJECT vacmAccessStatus + MIN-ACCESS read-only + DESCRIPTION "Create/delete/modify access to the + + vacmAccessTable is not required. + " + + OBJECT vacmViewTreeFamilyMask + WRITE-SYNTAX OCTET STRING (SIZE (0)) + MIN-ACCESS read-only + DESCRIPTION "Support for configuration via SNMP of subtree + families using wild-cards is not required. + " + + OBJECT vacmViewTreeFamilyType + MIN-ACCESS read-only + DESCRIPTION "Write access is not required." + + OBJECT vacmViewTreeFamilyStorageType + MIN-ACCESS read-only + DESCRIPTION "Write access is not required." + + OBJECT vacmViewTreeFamilyStatus + MIN-ACCESS read-only + DESCRIPTION "Create/delete/modify access to the + vacmViewTreeFamilyTable is not required. + " + ::= { vacmMIBCompliances 1 } + +-- Units of conformance ********************************************** + +vacmBasicGroup OBJECT-GROUP + OBJECTS { + vacmContextName, + vacmGroupName, + vacmSecurityToGroupStorageType, + vacmSecurityToGroupStatus, + vacmAccessContextMatch, + vacmAccessReadViewName, + vacmAccessWriteViewName, + vacmAccessNotifyViewName, + vacmAccessStorageType, + vacmAccessStatus, + vacmViewSpinLock, + vacmViewTreeFamilyMask, + vacmViewTreeFamilyType, + vacmViewTreeFamilyStorageType, + vacmViewTreeFamilyStatus + } + STATUS current + DESCRIPTION "A collection of objects providing for remote + configuration of an SNMP engine which implements + + the SNMP View-based Access Control Model. + " + ::= { vacmMIBGroups 1 } + +END diff --git a/mibs/SNMPv2-CONF.txt b/mibs/SNMPv2-CONF.txt new file mode 100644 index 0000000..24a1eed --- /dev/null +++ b/mibs/SNMPv2-CONF.txt @@ -0,0 +1,322 @@ +SNMPv2-CONF DEFINITIONS ::= BEGIN + +IMPORTS ObjectName, NotificationName, ObjectSyntax + FROM SNMPv2-SMI; + +-- definitions for conformance groups + +OBJECT-GROUP MACRO ::= +BEGIN + TYPE NOTATION ::= + ObjectsPart + "STATUS" Status + "DESCRIPTION" Text + ReferPart + + VALUE NOTATION ::= + value(VALUE OBJECT IDENTIFIER) + + ObjectsPart ::= + "OBJECTS" "{" Objects "}" + Objects ::= + Object + | Objects "," Object + Object ::= + + value(ObjectName) + + Status ::= + "current" + | "deprecated" + | "obsolete" + + ReferPart ::= + "REFERENCE" Text + | empty + + -- a character string as defined in [2] + Text ::= value(IA5String) +END + +-- more definitions for conformance groups + +NOTIFICATION-GROUP MACRO ::= +BEGIN + TYPE NOTATION ::= + NotificationsPart + "STATUS" Status + "DESCRIPTION" Text + ReferPart + + VALUE NOTATION ::= + value(VALUE OBJECT IDENTIFIER) + + NotificationsPart ::= + "NOTIFICATIONS" "{" Notifications "}" + Notifications ::= + Notification + | Notifications "," Notification + Notification ::= + value(NotificationName) + + Status ::= + "current" + | "deprecated" + | "obsolete" + + ReferPart ::= + "REFERENCE" Text + | empty + + -- a character string as defined in [2] + Text ::= value(IA5String) +END + +-- definitions for compliance statements + +MODULE-COMPLIANCE MACRO ::= +BEGIN + TYPE NOTATION ::= + "STATUS" Status + "DESCRIPTION" Text + ReferPart + ModulePart + + VALUE NOTATION ::= + value(VALUE OBJECT IDENTIFIER) + + Status ::= + "current" + | "deprecated" + | "obsolete" + + ReferPart ::= + "REFERENCE" Text + | empty + + ModulePart ::= + Modules + Modules ::= + Module + | Modules Module + Module ::= + -- name of module -- + "MODULE" ModuleName + MandatoryPart + CompliancePart + + ModuleName ::= + -- identifier must start with uppercase letter + identifier ModuleIdentifier + -- must not be empty unless contained + -- in MIB Module + | empty + ModuleIdentifier ::= + value(OBJECT IDENTIFIER) + | empty + + MandatoryPart ::= + "MANDATORY-GROUPS" "{" Groups "}" + | empty + + Groups ::= + + Group + | Groups "," Group + Group ::= + value(OBJECT IDENTIFIER) + + CompliancePart ::= + Compliances + | empty + + Compliances ::= + Compliance + | Compliances Compliance + Compliance ::= + ComplianceGroup + | Object + + ComplianceGroup ::= + "GROUP" value(OBJECT IDENTIFIER) + "DESCRIPTION" Text + + Object ::= + "OBJECT" value(ObjectName) + SyntaxPart + WriteSyntaxPart + AccessPart + "DESCRIPTION" Text + + -- must be a refinement for object's SYNTAX clause + SyntaxPart ::= "SYNTAX" Syntax + | empty + + -- must be a refinement for object's SYNTAX clause + WriteSyntaxPart ::= "WRITE-SYNTAX" Syntax + | empty + + Syntax ::= -- Must be one of the following: + -- a base type (or its refinement), + -- a textual convention (or its refinement), or + -- a BITS pseudo-type + type + | "BITS" "{" NamedBits "}" + + NamedBits ::= NamedBit + | NamedBits "," NamedBit + + NamedBit ::= identifier "(" number ")" -- number is nonnegative + + AccessPart ::= + "MIN-ACCESS" Access + | empty + Access ::= + "not-accessible" + | "accessible-for-notify" + | "read-only" + | "read-write" + | "read-create" + + -- a character string as defined in [2] + Text ::= value(IA5String) +END + +-- definitions for capabilities statements + +AGENT-CAPABILITIES MACRO ::= +BEGIN + TYPE NOTATION ::= + "PRODUCT-RELEASE" Text + "STATUS" Status + "DESCRIPTION" Text + ReferPart + ModulePart + + VALUE NOTATION ::= + value(VALUE OBJECT IDENTIFIER) + + Status ::= + "current" + | "obsolete" + + ReferPart ::= + "REFERENCE" Text + | empty + + ModulePart ::= + Modules + | empty + Modules ::= + Module + | Modules Module + Module ::= + -- name of module -- + "SUPPORTS" ModuleName + "INCLUDES" "{" Groups "}" + VariationPart + + ModuleName ::= + + -- identifier must start with uppercase letter + identifier ModuleIdentifier + ModuleIdentifier ::= + value(OBJECT IDENTIFIER) + | empty + + Groups ::= + Group + | Groups "," Group + Group ::= + value(OBJECT IDENTIFIER) + + VariationPart ::= + Variations + | empty + Variations ::= + Variation + | Variations Variation + + Variation ::= + ObjectVariation + | NotificationVariation + + NotificationVariation ::= + "VARIATION" value(NotificationName) + AccessPart + "DESCRIPTION" Text + + ObjectVariation ::= + "VARIATION" value(ObjectName) + SyntaxPart + WriteSyntaxPart + AccessPart + CreationPart + DefValPart + "DESCRIPTION" Text + + -- must be a refinement for object's SYNTAX clause + SyntaxPart ::= "SYNTAX" Syntax + | empty + + WriteSyntaxPart ::= "WRITE-SYNTAX" Syntax + | empty + + Syntax ::= -- Must be one of the following: + -- a base type (or its refinement), + -- a textual convention (or its refinement), or + -- a BITS pseudo-type + + type + | "BITS" "{" NamedBits "}" + + NamedBits ::= NamedBit + | NamedBits "," NamedBit + + NamedBit ::= identifier "(" number ")" -- number is nonnegative + + AccessPart ::= + "ACCESS" Access + | empty + + Access ::= + "not-implemented" + -- only "not-implemented" for notifications + | "accessible-for-notify" + | "read-only" + | "read-write" + | "read-create" + -- following is for backward-compatibility only + | "write-only" + + CreationPart ::= + "CREATION-REQUIRES" "{" Cells "}" + | empty + Cells ::= + Cell + | Cells "," Cell + Cell ::= + value(ObjectName) + + DefValPart ::= "DEFVAL" "{" Defvalue "}" + | empty + + Defvalue ::= -- must be valid for the object's syntax + -- in this macro's SYNTAX clause, if present, + -- or if not, in object's OBJECT-TYPE macro + value(ObjectSyntax) + | "{" BitsValue "}" + + BitsValue ::= BitNames + | empty + + BitNames ::= BitName + | BitNames "," BitName + + BitName ::= identifier + + -- a character string as defined in [2] + Text ::= value(IA5String) +END + +END diff --git a/mibs/SNMPv2-MIB.txt b/mibs/SNMPv2-MIB.txt new file mode 100644 index 0000000..8c82830 --- /dev/null +++ b/mibs/SNMPv2-MIB.txt @@ -0,0 +1,854 @@ +SNMPv2-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE, + TimeTicks, Counter32, snmpModules, mib-2 + FROM SNMPv2-SMI + DisplayString, TestAndIncr, TimeStamp + + FROM SNMPv2-TC + MODULE-COMPLIANCE, OBJECT-GROUP, NOTIFICATION-GROUP + FROM SNMPv2-CONF; + +snmpMIB MODULE-IDENTITY + LAST-UPDATED "200210160000Z" + ORGANIZATION "IETF SNMPv3 Working Group" + CONTACT-INFO + "WG-EMail: snmpv3@lists.tislabs.com + Subscribe: snmpv3-request@lists.tislabs.com + + Co-Chair: Russ Mundy + Network Associates Laboratories + postal: 15204 Omega Drive, Suite 300 + Rockville, MD 20850-4601 + USA + EMail: mundy@tislabs.com + phone: +1 301 947-7107 + + Co-Chair: David Harrington + Enterasys Networks + postal: 35 Industrial Way + P. O. Box 5005 + Rochester, NH 03866-5005 + USA + EMail: dbh@enterasys.com + phone: +1 603 337-2614 + + Editor: Randy Presuhn + BMC Software, Inc. + postal: 2141 North First Street + San Jose, CA 95131 + USA + EMail: randy_presuhn@bmc.com + phone: +1 408 546-1006" + DESCRIPTION + "The MIB module for SNMP entities. + + Copyright (C) The Internet Society (2002). This + version of this MIB module is part of RFC 3418; + see the RFC itself for full legal notices. + " + REVISION "200210160000Z" + DESCRIPTION + "This revision of this MIB module was published as + RFC 3418." + REVISION "199511090000Z" + DESCRIPTION + "This revision of this MIB module was published as + RFC 1907." + REVISION "199304010000Z" + DESCRIPTION + "The initial revision of this MIB module was published + as RFC 1450." + ::= { snmpModules 1 } + +snmpMIBObjects OBJECT IDENTIFIER ::= { snmpMIB 1 } + +-- ::= { snmpMIBObjects 1 } this OID is obsolete +-- ::= { snmpMIBObjects 2 } this OID is obsolete +-- ::= { snmpMIBObjects 3 } this OID is obsolete + +-- the System group +-- +-- a collection of objects common to all managed systems. + +system OBJECT IDENTIFIER ::= { mib-2 1 } + +sysDescr OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A textual description of the entity. This value should + include the full name and version identification of + the system's hardware type, software operating-system, + and networking software." + ::= { system 1 } + +sysObjectID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The vendor's authoritative identification of the + network management subsystem contained in the entity. + This value is allocated within the SMI enterprises + subtree (1.3.6.1.4.1) and provides an easy and + unambiguous means for determining `what kind of box' is + being managed. For example, if vendor `Flintstones, + Inc.' was assigned the subtree 1.3.6.1.4.1.424242, + it could assign the identifier 1.3.6.1.4.1.424242.1.1 + to its `Fred Router'." + ::= { system 2 } + +sysUpTime OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The time (in hundredths of a second) since the + network management portion of the system was last + re-initialized." + ::= { system 3 } + +sysContact OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The textual identification of the contact person for + this managed node, together with information on how + to contact this person. If no contact information is + known, the value is the zero-length string." + ::= { system 4 } + +sysName OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "An administratively-assigned name for this managed + node. By convention, this is the node's fully-qualified + domain name. If the name is unknown, the value is + the zero-length string." + ::= { system 5 } + +sysLocation OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The physical location of this node (e.g., 'telephone + closet, 3rd floor'). If the location is unknown, the + value is the zero-length string." + ::= { system 6 } + +sysServices OBJECT-TYPE + SYNTAX INTEGER (0..127) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A value which indicates the set of services that this + entity may potentially offer. The value is a sum. + + This sum initially takes the value zero. Then, for + each layer, L, in the range 1 through 7, that this node + performs transactions for, 2 raised to (L - 1) is added + to the sum. For example, a node which performs only + routing functions would have a value of 4 (2^(3-1)). + In contrast, a node which is a host offering application + services would have a value of 72 (2^(4-1) + 2^(7-1)). + Note that in the context of the Internet suite of + protocols, values should be calculated accordingly: + + layer functionality + 1 physical (e.g., repeaters) + 2 datalink/subnetwork (e.g., bridges) + 3 internet (e.g., supports the IP) + 4 end-to-end (e.g., supports the TCP) + 7 applications (e.g., supports the SMTP) + + For systems including OSI protocols, layers 5 and 6 + may also be counted." + ::= { system 7 } + +-- object resource information +-- +-- a collection of objects which describe the SNMP entity's +-- (statically and dynamically configurable) support of +-- various MIB modules. + +sysORLastChange OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time of the most recent + change in state or value of any instance of sysORID." + ::= { system 8 } + +sysORTable OBJECT-TYPE + SYNTAX SEQUENCE OF SysOREntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The (conceptual) table listing the capabilities of + the local SNMP application acting as a command + responder with respect to various MIB modules. + SNMP entities having dynamically-configurable support + of MIB modules will have a dynamically-varying number + of conceptual rows." + ::= { system 9 } + +sysOREntry OBJECT-TYPE + SYNTAX SysOREntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry (conceptual row) in the sysORTable." + INDEX { sysORIndex } + ::= { sysORTable 1 } + +SysOREntry ::= SEQUENCE { + sysORIndex INTEGER, + sysORID OBJECT IDENTIFIER, + sysORDescr DisplayString, + sysORUpTime TimeStamp +} + +sysORIndex OBJECT-TYPE + SYNTAX INTEGER (1..2147483647) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The auxiliary variable used for identifying instances + of the columnar objects in the sysORTable." + ::= { sysOREntry 1 } + +sysORID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An authoritative identification of a capabilities + statement with respect to various MIB modules supported + by the local SNMP application acting as a command + responder." + ::= { sysOREntry 2 } + +sysORDescr OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A textual description of the capabilities identified + by the corresponding instance of sysORID." + ::= { sysOREntry 3 } + +sysORUpTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time this conceptual + row was last instantiated." + ::= { sysOREntry 4 } + +-- the SNMP group +-- +-- a collection of objects providing basic instrumentation and +-- control of an SNMP entity. + +snmp OBJECT IDENTIFIER ::= { mib-2 11 } + +snmpInPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of messages delivered to the SNMP + entity from the transport service." + ::= { snmp 1 } + +snmpInBadVersions OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of SNMP messages which were delivered + to the SNMP entity and were for an unsupported SNMP + version." + ::= { snmp 3 } + +snmpInBadCommunityNames OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of community-based SNMP messages (for + example, SNMPv1) delivered to the SNMP entity which + used an SNMP community name not known to said entity. + Also, implementations which authenticate community-based + SNMP messages using check(s) in addition to matching + the community name (for example, by also checking + whether the message originated from a transport address + allowed to use a specified community name) MAY include + in this value the number of messages which failed the + additional check(s). It is strongly RECOMMENDED that + + the documentation for any security model which is used + to authenticate community-based SNMP messages specify + the precise conditions that contribute to this value." + ::= { snmp 4 } + +snmpInBadCommunityUses OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of community-based SNMP messages (for + example, SNMPv1) delivered to the SNMP entity which + represented an SNMP operation that was not allowed for + the SNMP community named in the message. The precise + conditions under which this counter is incremented + (if at all) depend on how the SNMP entity implements + its access control mechanism and how its applications + interact with that access control mechanism. It is + strongly RECOMMENDED that the documentation for any + access control mechanism which is used to control access + to and visibility of MIB instrumentation specify the + precise conditions that contribute to this value." + ::= { snmp 5 } + +snmpInASNParseErrs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of ASN.1 or BER errors encountered by + the SNMP entity when decoding received SNMP messages." + ::= { snmp 6 } + +snmpEnableAuthenTraps OBJECT-TYPE + SYNTAX INTEGER { enabled(1), disabled(2) } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "Indicates whether the SNMP entity is permitted to + generate authenticationFailure traps. The value of this + object overrides any configuration information; as such, + it provides a means whereby all authenticationFailure + traps may be disabled. + + Note that it is strongly recommended that this object + be stored in non-volatile memory so that it remains + constant across re-initializations of the network + management system." + ::= { snmp 30 } + +snmpSilentDrops OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of Confirmed Class PDUs (such as + GetRequest-PDUs, GetNextRequest-PDUs, + GetBulkRequest-PDUs, SetRequest-PDUs, and + InformRequest-PDUs) delivered to the SNMP entity which + were silently dropped because the size of a reply + containing an alternate Response Class PDU (such as a + Response-PDU) with an empty variable-bindings field + was greater than either a local constraint or the + maximum message size associated with the originator of + the request." + ::= { snmp 31 } + +snmpProxyDrops OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of Confirmed Class PDUs + (such as GetRequest-PDUs, GetNextRequest-PDUs, + GetBulkRequest-PDUs, SetRequest-PDUs, and + InformRequest-PDUs) delivered to the SNMP entity which + were silently dropped because the transmission of + the (possibly translated) message to a proxy target + failed in a manner (other than a time-out) such that + no Response Class PDU (such as a Response-PDU) could + be returned." + ::= { snmp 32 } + +-- information for notifications +-- +-- a collection of objects which allow the SNMP entity, when +-- supporting a notification originator application, +-- to be configured to generate SNMPv2-Trap-PDUs. + +snmpTrap OBJECT IDENTIFIER ::= { snmpMIBObjects 4 } + +snmpTrapOID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "The authoritative identification of the notification + currently being sent. This variable occurs as + the second varbind in every SNMPv2-Trap-PDU and + InformRequest-PDU." + ::= { snmpTrap 1 } + +-- ::= { snmpTrap 2 } this OID is obsolete + +snmpTrapEnterprise OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "The authoritative identification of the enterprise + associated with the trap currently being sent. When an + SNMP proxy agent is mapping an RFC1157 Trap-PDU + into a SNMPv2-Trap-PDU, this variable occurs as the + last varbind." + ::= { snmpTrap 3 } + +-- ::= { snmpTrap 4 } this OID is obsolete + +-- well-known traps + +snmpTraps OBJECT IDENTIFIER ::= { snmpMIBObjects 5 } + +coldStart NOTIFICATION-TYPE + STATUS current + DESCRIPTION + "A coldStart trap signifies that the SNMP entity, + supporting a notification originator application, is + reinitializing itself and that its configuration may + have been altered." + ::= { snmpTraps 1 } + +warmStart NOTIFICATION-TYPE + STATUS current + DESCRIPTION + "A warmStart trap signifies that the SNMP entity, + supporting a notification originator application, + is reinitializing itself such that its configuration + is unaltered." + ::= { snmpTraps 2 } + +-- Note the linkDown NOTIFICATION-TYPE ::= { snmpTraps 3 } +-- and the linkUp NOTIFICATION-TYPE ::= { snmpTraps 4 } +-- are defined in RFC 2863 [RFC2863] + +authenticationFailure NOTIFICATION-TYPE + STATUS current + DESCRIPTION + "An authenticationFailure trap signifies that the SNMP + entity has received a protocol message that is not + properly authenticated. While all implementations + of SNMP entities MAY be capable of generating this + trap, the snmpEnableAuthenTraps object indicates + whether this trap will be generated." + ::= { snmpTraps 5 } + +-- Note the egpNeighborLoss notification is defined +-- as { snmpTraps 6 } in RFC 1213 + +-- the set group +-- +-- a collection of objects which allow several cooperating +-- command generator applications to coordinate their use of the +-- set operation. + +snmpSet OBJECT IDENTIFIER ::= { snmpMIBObjects 6 } + +snmpSetSerialNo OBJECT-TYPE + SYNTAX TestAndIncr + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "An advisory lock used to allow several cooperating + command generator applications to coordinate their + use of the SNMP set operation. + + This object is used for coarse-grain coordination. + To achieve fine-grain coordination, one or more similar + objects might be defined within each MIB group, as + appropriate." + ::= { snmpSet 1 } + +-- conformance information + +snmpMIBConformance + OBJECT IDENTIFIER ::= { snmpMIB 2 } + +snmpMIBCompliances + OBJECT IDENTIFIER ::= { snmpMIBConformance 1 } +snmpMIBGroups OBJECT IDENTIFIER ::= { snmpMIBConformance 2 } + +-- compliance statements + +-- ::= { snmpMIBCompliances 1 } this OID is obsolete +snmpBasicCompliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION + "The compliance statement for SNMPv2 entities which + implement the SNMPv2 MIB. + + This compliance statement is replaced by + snmpBasicComplianceRev2." + MODULE -- this module + MANDATORY-GROUPS { snmpGroup, snmpSetGroup, systemGroup, + snmpBasicNotificationsGroup } + + GROUP snmpCommunityGroup + DESCRIPTION + "This group is mandatory for SNMPv2 entities which + support community-based authentication." + ::= { snmpMIBCompliances 2 } + +snmpBasicComplianceRev2 MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP entities which + implement this MIB module." + MODULE -- this module + MANDATORY-GROUPS { snmpGroup, snmpSetGroup, systemGroup, + snmpBasicNotificationsGroup } + + GROUP snmpCommunityGroup + DESCRIPTION + "This group is mandatory for SNMP entities which + support community-based authentication." + + GROUP snmpWarmStartNotificationGroup + DESCRIPTION + "This group is mandatory for an SNMP entity which + supports command responder applications, and is + able to reinitialize itself such that its + configuration is unaltered." + ::= { snmpMIBCompliances 3 } + +-- units of conformance + +-- ::= { snmpMIBGroups 1 } this OID is obsolete +-- ::= { snmpMIBGroups 2 } this OID is obsolete +-- ::= { snmpMIBGroups 3 } this OID is obsolete + +-- ::= { snmpMIBGroups 4 } this OID is obsolete + +snmpGroup OBJECT-GROUP + OBJECTS { snmpInPkts, + snmpInBadVersions, + snmpInASNParseErrs, + snmpSilentDrops, + snmpProxyDrops, + snmpEnableAuthenTraps } + STATUS current + DESCRIPTION + "A collection of objects providing basic instrumentation + and control of an SNMP entity." + ::= { snmpMIBGroups 8 } + +snmpCommunityGroup OBJECT-GROUP + OBJECTS { snmpInBadCommunityNames, + snmpInBadCommunityUses } + STATUS current + DESCRIPTION + "A collection of objects providing basic instrumentation + of a SNMP entity which supports community-based + authentication." + ::= { snmpMIBGroups 9 } + +snmpSetGroup OBJECT-GROUP + OBJECTS { snmpSetSerialNo } + STATUS current + DESCRIPTION + "A collection of objects which allow several cooperating + command generator applications to coordinate their + use of the set operation." + ::= { snmpMIBGroups 5 } + +systemGroup OBJECT-GROUP + OBJECTS { sysDescr, sysObjectID, sysUpTime, + sysContact, sysName, sysLocation, + sysServices, + sysORLastChange, sysORID, + sysORUpTime, sysORDescr } + STATUS current + DESCRIPTION + "The system group defines objects which are common to all + managed systems." + ::= { snmpMIBGroups 6 } + +snmpBasicNotificationsGroup NOTIFICATION-GROUP + NOTIFICATIONS { coldStart, authenticationFailure } + STATUS current + DESCRIPTION + "The basic notifications implemented by an SNMP entity + supporting command responder applications." + ::= { snmpMIBGroups 7 } + +snmpWarmStartNotificationGroup NOTIFICATION-GROUP + NOTIFICATIONS { warmStart } + STATUS current + DESCRIPTION + "An additional notification for an SNMP entity supporting + command responder applications, if it is able to reinitialize + itself such that its configuration is unaltered." + ::= { snmpMIBGroups 11 } + +snmpNotificationGroup OBJECT-GROUP + OBJECTS { snmpTrapOID, snmpTrapEnterprise } + STATUS current + DESCRIPTION + "These objects are required for entities + which support notification originator applications." + ::= { snmpMIBGroups 12 } + +-- definitions in RFC 1213 made obsolete by the inclusion of a +-- subset of the snmp group in this MIB + +snmpOutPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Messages which were + passed from the SNMP protocol entity to the + transport service." + ::= { snmp 2 } + +-- { snmp 7 } is not used + +snmpInTooBigs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP PDUs which were + delivered to the SNMP protocol entity and for + which the value of the error-status field was + `tooBig'." + ::= { snmp 8 } + +snmpInNoSuchNames OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP PDUs which were + delivered to the SNMP protocol entity and for + which the value of the error-status field was + `noSuchName'." + ::= { snmp 9 } + +snmpInBadValues OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP PDUs which were + delivered to the SNMP protocol entity and for + which the value of the error-status field was + `badValue'." + ::= { snmp 10 } + +snmpInReadOnlys OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number valid SNMP PDUs which were delivered + to the SNMP protocol entity and for which the value + of the error-status field was `readOnly'. It should + be noted that it is a protocol error to generate an + SNMP PDU which contains the value `readOnly' in the + error-status field, as such this object is provided + as a means of detecting incorrect implementations of + the SNMP." + ::= { snmp 11 } + +snmpInGenErrs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP PDUs which were delivered + to the SNMP protocol entity and for which the value + of the error-status field was `genErr'." + ::= { snmp 12 } + +snmpInTotalReqVars OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of MIB objects which have been + retrieved successfully by the SNMP protocol entity + as the result of receiving valid SNMP Get-Request + and Get-Next PDUs." + ::= { snmp 13 } + +snmpInTotalSetVars OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of MIB objects which have been + altered successfully by the SNMP protocol entity as + the result of receiving valid SNMP Set-Request PDUs." + ::= { snmp 14 } + +snmpInGetRequests OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Get-Request PDUs which + have been accepted and processed by the SNMP + protocol entity." + ::= { snmp 15 } + +snmpInGetNexts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Get-Next PDUs which have been + accepted and processed by the SNMP protocol entity." + ::= { snmp 16 } + +snmpInSetRequests OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Set-Request PDUs which + have been accepted and processed by the SNMP protocol + entity." + ::= { snmp 17 } + +snmpInGetResponses OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Get-Response PDUs which + have been accepted and processed by the SNMP protocol + entity." + ::= { snmp 18 } + +snmpInTraps OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Trap PDUs which have been + accepted and processed by the SNMP protocol entity." + ::= { snmp 19 } + +snmpOutTooBigs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP PDUs which were generated + by the SNMP protocol entity and for which the value + of the error-status field was `tooBig.'" + ::= { snmp 20 } + +snmpOutNoSuchNames OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP PDUs which were generated + by the SNMP protocol entity and for which the value + of the error-status was `noSuchName'." + ::= { snmp 21 } + +snmpOutBadValues OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP PDUs which were generated + by the SNMP protocol entity and for which the value + of the error-status field was `badValue'." + ::= { snmp 22 } + +-- { snmp 23 } is not used + +snmpOutGenErrs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP PDUs which were generated + by the SNMP protocol entity and for which the value + of the error-status field was `genErr'." + ::= { snmp 24 } + +snmpOutGetRequests OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Get-Request PDUs which + have been generated by the SNMP protocol entity." + ::= { snmp 25 } + +snmpOutGetNexts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Get-Next PDUs which have + been generated by the SNMP protocol entity." + ::= { snmp 26 } + +snmpOutSetRequests OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Set-Request PDUs which + have been generated by the SNMP protocol entity." + ::= { snmp 27 } + +snmpOutGetResponses OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Get-Response PDUs which + have been generated by the SNMP protocol entity." + ::= { snmp 28 } + +snmpOutTraps OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Trap PDUs which have + been generated by the SNMP protocol entity." + ::= { snmp 29 } + +snmpObsoleteGroup OBJECT-GROUP + OBJECTS { snmpOutPkts, snmpInTooBigs, snmpInNoSuchNames, + snmpInBadValues, snmpInReadOnlys, snmpInGenErrs, + snmpInTotalReqVars, snmpInTotalSetVars, + snmpInGetRequests, snmpInGetNexts, snmpInSetRequests, + snmpInGetResponses, snmpInTraps, snmpOutTooBigs, + snmpOutNoSuchNames, snmpOutBadValues, + snmpOutGenErrs, snmpOutGetRequests, snmpOutGetNexts, + snmpOutSetRequests, snmpOutGetResponses, snmpOutTraps + } + STATUS obsolete + DESCRIPTION + "A collection of objects from RFC 1213 made obsolete + by this MIB module." + ::= { snmpMIBGroups 10 } + +END diff --git a/mibs/SNMPv2-SMI.txt b/mibs/SNMPv2-SMI.txt new file mode 100644 index 0000000..1c01e1d --- /dev/null +++ b/mibs/SNMPv2-SMI.txt @@ -0,0 +1,344 @@ +SNMPv2-SMI DEFINITIONS ::= BEGIN + +-- the path to the root + +org OBJECT IDENTIFIER ::= { iso 3 } -- "iso" = 1 +dod OBJECT IDENTIFIER ::= { org 6 } +internet OBJECT IDENTIFIER ::= { dod 1 } + +directory OBJECT IDENTIFIER ::= { internet 1 } + +mgmt OBJECT IDENTIFIER ::= { internet 2 } +mib-2 OBJECT IDENTIFIER ::= { mgmt 1 } +transmission OBJECT IDENTIFIER ::= { mib-2 10 } + +experimental OBJECT IDENTIFIER ::= { internet 3 } + +private OBJECT IDENTIFIER ::= { internet 4 } +enterprises OBJECT IDENTIFIER ::= { private 1 } + +security OBJECT IDENTIFIER ::= { internet 5 } + +snmpV2 OBJECT IDENTIFIER ::= { internet 6 } + +-- transport domains +snmpDomains OBJECT IDENTIFIER ::= { snmpV2 1 } + +-- transport proxies +snmpProxys OBJECT IDENTIFIER ::= { snmpV2 2 } + +-- module identities +snmpModules OBJECT IDENTIFIER ::= { snmpV2 3 } + +-- Extended UTCTime, to allow dates with four-digit years +-- (Note that this definition of ExtUTCTime is not to be IMPORTed +-- by MIB modules.) +ExtUTCTime ::= OCTET STRING(SIZE(11 | 13)) + -- format is YYMMDDHHMMZ or YYYYMMDDHHMMZ + + -- where: YY - last two digits of year (only years + -- between 1900-1999) + -- YYYY - last four digits of the year (any year) + -- MM - month (01 through 12) + -- DD - day of month (01 through 31) + -- HH - hours (00 through 23) + -- MM - minutes (00 through 59) + -- Z - denotes GMT (the ASCII character Z) + -- + -- For example, "9502192015Z" and "199502192015Z" represent + -- 8:15pm GMT on 19 February 1995. Years after 1999 must use + -- the four digit year format. Years 1900-1999 may use the + -- two or four digit format. + +-- definitions for information modules + +MODULE-IDENTITY MACRO ::= +BEGIN + TYPE NOTATION ::= + "LAST-UPDATED" value(Update ExtUTCTime) + "ORGANIZATION" Text + "CONTACT-INFO" Text + "DESCRIPTION" Text + RevisionPart + + VALUE NOTATION ::= + value(VALUE OBJECT IDENTIFIER) + + RevisionPart ::= + Revisions + | empty + Revisions ::= + Revision + | Revisions Revision + Revision ::= + "REVISION" value(Update ExtUTCTime) + "DESCRIPTION" Text + + -- a character string as defined in section 3.1.1 + Text ::= value(IA5String) +END + +OBJECT-IDENTITY MACRO ::= +BEGIN + TYPE NOTATION ::= + "STATUS" Status + "DESCRIPTION" Text + + ReferPart + + VALUE NOTATION ::= + value(VALUE OBJECT IDENTIFIER) + + Status ::= + "current" + | "deprecated" + | "obsolete" + + ReferPart ::= + "REFERENCE" Text + | empty + + -- a character string as defined in section 3.1.1 + Text ::= value(IA5String) +END + +-- names of objects +-- (Note that these definitions of ObjectName and NotificationName +-- are not to be IMPORTed by MIB modules.) + +ObjectName ::= + OBJECT IDENTIFIER + +NotificationName ::= + OBJECT IDENTIFIER + +-- syntax of objects + +-- the "base types" defined here are: +-- 3 built-in ASN.1 types: INTEGER, OCTET STRING, OBJECT IDENTIFIER +-- 8 application-defined types: Integer32, IpAddress, Counter32, +-- Gauge32, Unsigned32, TimeTicks, Opaque, and Counter64 + +ObjectSyntax ::= + CHOICE { + simple + SimpleSyntax, + -- note that SEQUENCEs for conceptual tables and + -- rows are not mentioned here... + + application-wide + ApplicationSyntax + } + +-- built-in ASN.1 types + +SimpleSyntax ::= + CHOICE { + -- INTEGERs with a more restrictive range + -- may also be used + integer-value -- includes Integer32 + INTEGER (-2147483648..2147483647), + -- OCTET STRINGs with a more restrictive size + -- may also be used + string-value + OCTET STRING (SIZE (0..65535)), + objectID-value + OBJECT IDENTIFIER + } + +-- indistinguishable from INTEGER, but never needs more than +-- 32-bits for a two's complement representation +Integer32 ::= + INTEGER (-2147483648..2147483647) + +-- application-wide types + +ApplicationSyntax ::= + CHOICE { + ipAddress-value + IpAddress, + counter-value + Counter32, + timeticks-value + TimeTicks, + arbitrary-value + Opaque, + big-counter-value + Counter64, + unsigned-integer-value -- includes Gauge32 + Unsigned32 + } + +-- in network-byte order + +-- (this is a tagged type for historical reasons) +IpAddress ::= + [APPLICATION 0] + IMPLICIT OCTET STRING (SIZE (4)) + +-- this wraps +Counter32 ::= + [APPLICATION 1] + IMPLICIT INTEGER (0..4294967295) + +-- this doesn't wrap +Gauge32 ::= + [APPLICATION 2] + IMPLICIT INTEGER (0..4294967295) + +-- an unsigned 32-bit quantity +-- indistinguishable from Gauge32 +Unsigned32 ::= + [APPLICATION 2] + IMPLICIT INTEGER (0..4294967295) + +-- hundredths of seconds since an epoch +TimeTicks ::= + [APPLICATION 3] + IMPLICIT INTEGER (0..4294967295) + +-- for backward-compatibility only +Opaque ::= + [APPLICATION 4] + IMPLICIT OCTET STRING + +-- for counters that wrap in less than one hour with only 32 bits +Counter64 ::= + [APPLICATION 6] + IMPLICIT INTEGER (0..18446744073709551615) + +-- definition for objects + +OBJECT-TYPE MACRO ::= +BEGIN + TYPE NOTATION ::= + "SYNTAX" Syntax + UnitsPart + "MAX-ACCESS" Access + "STATUS" Status + "DESCRIPTION" Text + ReferPart + + IndexPart + DefValPart + + VALUE NOTATION ::= + value(VALUE ObjectName) + + Syntax ::= -- Must be one of the following: + -- a base type (or its refinement), + -- a textual convention (or its refinement), or + -- a BITS pseudo-type + type + | "BITS" "{" NamedBits "}" + + NamedBits ::= NamedBit + | NamedBits "," NamedBit + + NamedBit ::= identifier "(" number ")" -- number is nonnegative + + UnitsPart ::= + "UNITS" Text + | empty + + Access ::= + "not-accessible" + | "accessible-for-notify" + | "read-only" + | "read-write" + | "read-create" + + Status ::= + "current" + | "deprecated" + | "obsolete" + + ReferPart ::= + "REFERENCE" Text + | empty + + IndexPart ::= + "INDEX" "{" IndexTypes "}" + | "AUGMENTS" "{" Entry "}" + | empty + IndexTypes ::= + IndexType + | IndexTypes "," IndexType + IndexType ::= + "IMPLIED" Index + | Index + + Index ::= + -- use the SYNTAX value of the + -- correspondent OBJECT-TYPE invocation + value(ObjectName) + Entry ::= + -- use the INDEX value of the + -- correspondent OBJECT-TYPE invocation + value(ObjectName) + + DefValPart ::= "DEFVAL" "{" Defvalue "}" + | empty + + Defvalue ::= -- must be valid for the type specified in + -- SYNTAX clause of same OBJECT-TYPE macro + value(ObjectSyntax) + | "{" BitsValue "}" + + BitsValue ::= BitNames + | empty + + BitNames ::= BitName + | BitNames "," BitName + + BitName ::= identifier + + -- a character string as defined in section 3.1.1 + Text ::= value(IA5String) +END + +-- definitions for notifications + +NOTIFICATION-TYPE MACRO ::= +BEGIN + TYPE NOTATION ::= + ObjectsPart + "STATUS" Status + "DESCRIPTION" Text + ReferPart + + VALUE NOTATION ::= + value(VALUE NotificationName) + + ObjectsPart ::= + "OBJECTS" "{" Objects "}" + | empty + Objects ::= + Object + + | Objects "," Object + Object ::= + value(ObjectName) + + Status ::= + "current" + | "deprecated" + | "obsolete" + + ReferPart ::= + "REFERENCE" Text + | empty + + -- a character string as defined in section 3.1.1 + Text ::= value(IA5String) +END + +-- definitions of administrative identifiers + +zeroDotZero OBJECT-IDENTITY + STATUS current + DESCRIPTION + "A value used for null identifiers." + ::= { 0 0 } + +END diff --git a/mibs/SNMPv2-TC.txt b/mibs/SNMPv2-TC.txt new file mode 100644 index 0000000..860bf71 --- /dev/null +++ b/mibs/SNMPv2-TC.txt @@ -0,0 +1,772 @@ +SNMPv2-TC DEFINITIONS ::= BEGIN + +IMPORTS + TimeTicks FROM SNMPv2-SMI; + +-- definition of textual conventions + +TEXTUAL-CONVENTION MACRO ::= + +BEGIN + TYPE NOTATION ::= + DisplayPart + "STATUS" Status + "DESCRIPTION" Text + ReferPart + "SYNTAX" Syntax + + VALUE NOTATION ::= + value(VALUE Syntax) -- adapted ASN.1 + + DisplayPart ::= + "DISPLAY-HINT" Text + | empty + + Status ::= + "current" + | "deprecated" + | "obsolete" + + ReferPart ::= + "REFERENCE" Text + | empty + + -- a character string as defined in [2] + Text ::= value(IA5String) + + Syntax ::= -- Must be one of the following: + -- a base type (or its refinement), or + -- a BITS pseudo-type + type + | "BITS" "{" NamedBits "}" + + NamedBits ::= NamedBit + | NamedBits "," NamedBit + + NamedBit ::= identifier "(" number ")" -- number is nonnegative + +END + +DisplayString ::= TEXTUAL-CONVENTION + DISPLAY-HINT "255a" + STATUS current + DESCRIPTION + "Represents textual information taken from the NVT ASCII + + character set, as defined in pages 4, 10-11 of RFC 854. + + To summarize RFC 854, the NVT ASCII repertoire specifies: + + - the use of character codes 0-127 (decimal) + + - the graphics characters (32-126) are interpreted as + US ASCII + + - NUL, LF, CR, BEL, BS, HT, VT and FF have the special + meanings specified in RFC 854 + + - the other 25 codes have no standard interpretation + + - the sequence 'CR LF' means newline + + - the sequence 'CR NUL' means carriage-return + + - an 'LF' not preceded by a 'CR' means moving to the + same column on the next line. + + - the sequence 'CR x' for any x other than LF or NUL is + illegal. (Note that this also means that a string may + end with either 'CR LF' or 'CR NUL', but not with CR.) + + Any object defined using this syntax may not exceed 255 + characters in length." + SYNTAX OCTET STRING (SIZE (0..255)) + +PhysAddress ::= TEXTUAL-CONVENTION + DISPLAY-HINT "1x:" + STATUS current + DESCRIPTION + "Represents media- or physical-level addresses." + SYNTAX OCTET STRING + +MacAddress ::= TEXTUAL-CONVENTION + DISPLAY-HINT "1x:" + STATUS current + DESCRIPTION + "Represents an 802 MAC address represented in the + `canonical' order defined by IEEE 802.1a, i.e., as if it + were transmitted least significant bit first, even though + 802.5 (in contrast to other 802.x protocols) requires MAC + addresses to be transmitted most significant bit first." + SYNTAX OCTET STRING (SIZE (6)) + +TruthValue ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Represents a boolean value." + SYNTAX INTEGER { true(1), false(2) } + +TestAndIncr ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Represents integer-valued information used for atomic + operations. When the management protocol is used to specify + that an object instance having this syntax is to be + modified, the new value supplied via the management protocol + must precisely match the value presently held by the + instance. If not, the management protocol set operation + fails with an error of `inconsistentValue'. Otherwise, if + the current value is the maximum value of 2^31-1 (2147483647 + decimal), then the value held by the instance is wrapped to + zero; otherwise, the value held by the instance is + incremented by one. (Note that regardless of whether the + management protocol set operation succeeds, the variable- + binding in the request and response PDUs are identical.) + + The value of the ACCESS clause for objects having this + syntax is either `read-write' or `read-create'. When an + instance of a columnar object having this syntax is created, + any value may be supplied via the management protocol. + + When the network management portion of the system is re- + initialized, the value of every object instance having this + syntax must either be incremented from its value prior to + the re-initialization, or (if the value prior to the re- + initialization is unknown) be set to a pseudo-randomly + generated value." + SYNTAX INTEGER (0..2147483647) + +AutonomousType ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Represents an independently extensible type identification + value. It may, for example, indicate a particular sub-tree + with further MIB definitions, or define a particular type of + protocol or hardware." + SYNTAX OBJECT IDENTIFIER + +InstancePointer ::= TEXTUAL-CONVENTION + STATUS obsolete + DESCRIPTION + "A pointer to either a specific instance of a MIB object or + a conceptual row of a MIB table in the managed device. In + the latter case, by convention, it is the name of the + particular instance of the first accessible columnar object + in the conceptual row. + + The two uses of this textual convention are replaced by + VariablePointer and RowPointer, respectively." + SYNTAX OBJECT IDENTIFIER + +VariablePointer ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "A pointer to a specific object instance. For example, + sysContact.0 or ifInOctets.3." + SYNTAX OBJECT IDENTIFIER + +RowPointer ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Represents a pointer to a conceptual row. The value is the + name of the instance of the first accessible columnar object + in the conceptual row. + + For example, ifIndex.3 would point to the 3rd row in the + ifTable (note that if ifIndex were not-accessible, then + ifDescr.3 would be used instead)." + SYNTAX OBJECT IDENTIFIER + +RowStatus ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The RowStatus textual convention is used to manage the + creation and deletion of conceptual rows, and is used as the + value of the SYNTAX clause for the status column of a + conceptual row (as described in Section 7.7.1 of [2].) + + The status column has six defined values: + + - `active', which indicates that the conceptual row is + available for use by the managed device; + + - `notInService', which indicates that the conceptual + row exists in the agent, but is unavailable for use by + the managed device (see NOTE below); 'notInService' has + no implication regarding the internal consistency of + the row, availability of resources, or consistency with + the current state of the managed device; + + - `notReady', which indicates that the conceptual row + exists in the agent, but is missing information + necessary in order to be available for use by the + managed device (i.e., one or more required columns in + the conceptual row have not been instanciated); + + - `createAndGo', which is supplied by a management + station wishing to create a new instance of a + conceptual row and to have its status automatically set + to active, making it available for use by the managed + device; + + - `createAndWait', which is supplied by a management + station wishing to create a new instance of a + conceptual row (but not make it available for use by + the managed device); and, + - `destroy', which is supplied by a management station + wishing to delete all of the instances associated with + an existing conceptual row. + + Whereas five of the six values (all except `notReady') may + be specified in a management protocol set operation, only + three values will be returned in response to a management + protocol retrieval operation: `notReady', `notInService' or + `active'. That is, when queried, an existing conceptual row + has only three states: it is either available for use by + the managed device (the status column has value `active'); + it is not available for use by the managed device, though + the agent has sufficient information to attempt to make it + so (the status column has value `notInService'); or, it is + not available for use by the managed device, and an attempt + to make it so would fail because the agent has insufficient + information (the state column has value `notReady'). + + NOTE WELL + + This textual convention may be used for a MIB table, + irrespective of whether the values of that table's + conceptual rows are able to be modified while it is + active, or whether its conceptual rows must be taken + out of service in order to be modified. That is, it is + the responsibility of the DESCRIPTION clause of the + status column to specify whether the status column must + not be `active' in order for the value of some other + column of the same conceptual row to be modified. If + such a specification is made, affected columns may be + changed by an SNMP set PDU if the RowStatus would not + be equal to `active' either immediately before or after + processing the PDU. In other words, if the PDU also + contained a varbind that would change the RowStatus + value, the column in question may be changed if the + RowStatus was not equal to `active' as the PDU was + received, or if the varbind sets the status to a value + other than 'active'. + + Also note that whenever any elements of a row exist, the + RowStatus column must also exist. + + To summarize the effect of having a conceptual row with a + status column having a SYNTAX clause value of RowStatus, + consider the following state diagram: + + STATE + +--------------+-----------+-------------+------------- + | A | B | C | D + | |status col.|status column| + |status column | is | is |status column + ACTION |does not exist| notReady | notInService| is active +--------------+--------------+-----------+-------------+------------- +set status |noError ->D|inconsist- |inconsistent-|inconsistent- +column to | or | entValue| Value| Value +createAndGo |inconsistent- | | | + | Value| | | +--------------+--------------+-----------+-------------+------------- +set status |noError see 1|inconsist- |inconsistent-|inconsistent- +column to | or | entValue| Value| Value +createAndWait |wrongValue | | | +--------------+--------------+-----------+-------------+------------- +set status |inconsistent- |inconsist- |noError |noError +column to | Value| entValue| | +active | | | | + | | or | | + | | | | + | |see 2 ->D|see 8 ->D| ->D +--------------+--------------+-----------+-------------+------------- +set status |inconsistent- |inconsist- |noError |noError ->C +column to | Value| entValue| | +notInService | | | | + | | or | | or + | | | | + | |see 3 ->C| ->C|see 6 +--------------+--------------+-----------+-------------+------------- +set status |noError |noError |noError |noError ->A +column to | | | | or +destroy | ->A| ->A| ->A|see 7 +--------------+--------------+-----------+-------------+------------- +set any other |see 4 |noError |noError |see 5 +column to some| | | | +value | | see 1| ->C| ->D +--------------+--------------+-----------+-------------+------------- + + (1) goto B or C, depending on information available to the + agent. + + (2) if other variable bindings included in the same PDU, + provide values for all columns which are missing but + required, and all columns have acceptable values, then + return noError and goto D. + + (3) if other variable bindings included in the same PDU, + provide legal values for all columns which are missing but + required, then return noError and goto C. + + (4) at the discretion of the agent, the return value may be + either: + + inconsistentName: because the agent does not choose to + create such an instance when the corresponding + RowStatus instance does not exist, or + + inconsistentValue: if the supplied value is + inconsistent with the state of some other MIB object's + value, or + + noError: because the agent chooses to create the + instance. + + If noError is returned, then the instance of the status + column must also be created, and the new state is B or C, + depending on the information available to the agent. If + inconsistentName or inconsistentValue is returned, the row + remains in state A. + + (5) depending on the MIB definition for the column/table, + either noError or inconsistentValue may be returned. + + (6) the return value can indicate one of the following + errors: + + wrongValue: because the agent does not support + notInService (e.g., an agent which does not support + createAndWait), or + + inconsistentValue: because the agent is unable to take + the row out of service at this time, perhaps because it + is in use and cannot be de-activated. + + (7) the return value can indicate the following error: + + inconsistentValue: because the agent is unable to + remove the row at this time, perhaps because it is in + use and cannot be de-activated. + + (8) the transition to D can fail, e.g., if the values of the + conceptual row are inconsistent, then the error code would + be inconsistentValue. + + NOTE: Other processing of (this and other varbinds of) the + set request may result in a response other than noError + being returned, e.g., wrongValue, noCreation, etc. + + Conceptual Row Creation + + There are four potential interactions when creating a + conceptual row: selecting an instance-identifier which is + not in use; creating the conceptual row; initializing any + objects for which the agent does not supply a default; and, + making the conceptual row available for use by the managed + device. + + Interaction 1: Selecting an Instance-Identifier + + The algorithm used to select an instance-identifier varies + for each conceptual row. In some cases, the instance- + identifier is semantically significant, e.g., the + destination address of a route, and a management station + selects the instance-identifier according to the semantics. + + In other cases, the instance-identifier is used solely to + distinguish conceptual rows, and a management station + without specific knowledge of the conceptual row might + examine the instances present in order to determine an + unused instance-identifier. (This approach may be used, but + it is often highly sub-optimal; however, it is also a + questionable practice for a naive management station to + attempt conceptual row creation.) + + Alternately, the MIB module which defines the conceptual row + might provide one or more objects which provide assistance + in determining an unused instance-identifier. For example, + if the conceptual row is indexed by an integer-value, then + an object having an integer-valued SYNTAX clause might be + defined for such a purpose, allowing a management station to + issue a management protocol retrieval operation. In order + to avoid unnecessary collisions between competing management + stations, `adjacent' retrievals of this object should be + different. + + Finally, the management station could select a pseudo-random + number to use as the index. In the event that this index + + was already in use and an inconsistentValue was returned in + response to the management protocol set operation, the + management station should simply select a new pseudo-random + number and retry the operation. + + A MIB designer should choose between the two latter + algorithms based on the size of the table (and therefore the + efficiency of each algorithm). For tables in which a large + number of entries are expected, it is recommended that a MIB + object be defined that returns an acceptable index for + creation. For tables with small numbers of entries, it is + recommended that the latter pseudo-random index mechanism be + used. + + Interaction 2: Creating the Conceptual Row + + Once an unused instance-identifier has been selected, the + management station determines if it wishes to create and + activate the conceptual row in one transaction or in a + negotiated set of interactions. + + Interaction 2a: Creating and Activating the Conceptual Row + + The management station must first determine the column + requirements, i.e., it must determine those columns for + which it must or must not provide values. Depending on the + complexity of the table and the management station's + knowledge of the agent's capabilities, this determination + can be made locally by the management station. Alternately, + the management station issues a management protocol get + operation to examine all columns in the conceptual row that + it wishes to create. In response, for each column, there + are three possible outcomes: + + - a value is returned, indicating that some other + management station has already created this conceptual + row. We return to interaction 1. + + - the exception `noSuchInstance' is returned, + indicating that the agent implements the object-type + associated with this column, and that this column in at + least one conceptual row would be accessible in the MIB + view used by the retrieval were it to exist. For those + columns to which the agent provides read-create access, + the `noSuchInstance' exception tells the management + station that it should supply a value for this column + when the conceptual row is to be created. + + - the exception `noSuchObject' is returned, indicating + that the agent does not implement the object-type + associated with this column or that there is no + conceptual row for which this column would be + accessible in the MIB view used by the retrieval. As + such, the management station can not issue any + management protocol set operations to create an + instance of this column. + + Once the column requirements have been determined, a + management protocol set operation is accordingly issued. + This operation also sets the new instance of the status + column to `createAndGo'. + + When the agent processes the set operation, it verifies that + it has sufficient information to make the conceptual row + available for use by the managed device. The information + available to the agent is provided by two sources: the + management protocol set operation which creates the + conceptual row, and, implementation-specific defaults + supplied by the agent (note that an agent must provide + implementation-specific defaults for at least those objects + which it implements as read-only). If there is sufficient + information available, then the conceptual row is created, a + `noError' response is returned, the status column is set to + `active', and no further interactions are necessary (i.e., + interactions 3 and 4 are skipped). If there is insufficient + information, then the conceptual row is not created, and the + set operation fails with an error of `inconsistentValue'. + On this error, the management station can issue a management + protocol retrieval operation to determine if this was + because it failed to specify a value for a required column, + or, because the selected instance of the status column + already existed. In the latter case, we return to + interaction 1. In the former case, the management station + can re-issue the set operation with the additional + information, or begin interaction 2 again using + `createAndWait' in order to negotiate creation of the + conceptual row. + + NOTE WELL + + Regardless of the method used to determine the column + requirements, it is possible that the management + station might deem a column necessary when, in fact, + the agent will not allow that particular columnar + instance to be created or written. In this case, the + management protocol set operation will fail with an + error such as `noCreation' or `notWritable'. In this + case, the management station decides whether it needs + to be able to set a value for that particular columnar + instance. If not, the management station re-issues the + management protocol set operation, but without setting + a value for that particular columnar instance; + otherwise, the management station aborts the row + creation algorithm. + + Interaction 2b: Negotiating the Creation of the Conceptual + Row + + The management station issues a management protocol set + operation which sets the desired instance of the status + column to `createAndWait'. If the agent is unwilling to + process a request of this sort, the set operation fails with + an error of `wrongValue'. (As a consequence, such an agent + must be prepared to accept a single management protocol set + operation, i.e., interaction 2a above, containing all of the + columns indicated by its column requirements.) Otherwise, + the conceptual row is created, a `noError' response is + returned, and the status column is immediately set to either + `notInService' or `notReady', depending on whether it has + sufficient information to (attempt to) make the conceptual + row available for use by the managed device. If there is + sufficient information available, then the status column is + set to `notInService'; otherwise, if there is insufficient + information, then the status column is set to `notReady'. + Regardless, we proceed to interaction 3. + + Interaction 3: Initializing non-defaulted Objects + + The management station must now determine the column + requirements. It issues a management protocol get operation + to examine all columns in the created conceptual row. In + the response, for each column, there are three possible + outcomes: + + - a value is returned, indicating that the agent + implements the object-type associated with this column + and had sufficient information to provide a value. For + those columns to which the agent provides read-create + access (and for which the agent allows their values to + be changed after their creation), a value return tells + the management station that it may issue additional + management protocol set operations, if it desires, in + order to change the value associated with this column. + + - the exception `noSuchInstance' is returned, + indicating that the agent implements the object-type + associated with this column, and that this column in at + least one conceptual row would be accessible in the MIB + view used by the retrieval were it to exist. However, + the agent does not have sufficient information to + provide a value, and until a value is provided, the + conceptual row may not be made available for use by the + managed device. For those columns to which the agent + provides read-create access, the `noSuchInstance' + exception tells the management station that it must + issue additional management protocol set operations, in + order to provide a value associated with this column. + + - the exception `noSuchObject' is returned, indicating + that the agent does not implement the object-type + associated with this column or that there is no + conceptual row for which this column would be + accessible in the MIB view used by the retrieval. As + such, the management station can not issue any + management protocol set operations to create an + instance of this column. + + If the value associated with the status column is + `notReady', then the management station must first deal with + all `noSuchInstance' columns, if any. Having done so, the + value of the status column becomes `notInService', and we + proceed to interaction 4. + + Interaction 4: Making the Conceptual Row Available + + Once the management station is satisfied with the values + associated with the columns of the conceptual row, it issues + a management protocol set operation to set the status column + to `active'. If the agent has sufficient information to + make the conceptual row available for use by the managed + device, the management protocol set operation succeeds (a + `noError' response is returned). Otherwise, the management + protocol set operation fails with an error of + `inconsistentValue'. + + NOTE WELL + + A conceptual row having a status column with value + `notInService' or `notReady' is unavailable to the + managed device. As such, it is possible for the + managed device to create its own instances during the + time between the management protocol set operation + which sets the status column to `createAndWait' and the + management protocol set operation which sets the status + column to `active'. In this case, when the management + protocol set operation is issued to set the status + column to `active', the values held in the agent + supersede those used by the managed device. + + If the management station is prevented from setting the + status column to `active' (e.g., due to management station + or network failure) the conceptual row will be left in the + `notInService' or `notReady' state, consuming resources + indefinitely. The agent must detect conceptual rows that + have been in either state for an abnormally long period of + time and remove them. It is the responsibility of the + DESCRIPTION clause of the status column to indicate what an + abnormally long period of time would be. This period of + time should be long enough to allow for human response time + (including `think time') between the creation of the + conceptual row and the setting of the status to `active'. + In the absence of such information in the DESCRIPTION + clause, it is suggested that this period be approximately 5 + minutes in length. This removal action applies not only to + newly-created rows, but also to previously active rows which + are set to, and left in, the notInService state for a + prolonged period exceeding that which is considered normal + for such a conceptual row. + + Conceptual Row Suspension + + When a conceptual row is `active', the management station + may issue a management protocol set operation which sets the + instance of the status column to `notInService'. If the + agent is unwilling to do so, the set operation fails with an + error of `wrongValue' or `inconsistentValue'. Otherwise, + the conceptual row is taken out of service, and a `noError' + response is returned. It is the responsibility of the + DESCRIPTION clause of the status column to indicate under + what circumstances the status column should be taken out of + service (e.g., in order for the value of some other column + of the same conceptual row to be modified). + + Conceptual Row Deletion + + For deletion of conceptual rows, a management protocol set + operation is issued which sets the instance of the status + column to `destroy'. This request may be made regardless of + the current value of the status column (e.g., it is possible + to delete conceptual rows which are either `notReady', + `notInService' or `active'.) If the operation succeeds, + then all instances associated with the conceptual row are + immediately removed." + SYNTAX INTEGER { + -- the following two values are states: + -- these values may be read or written + active(1), + notInService(2), + -- the following value is a state: + -- this value may be read, but not written + notReady(3), + -- the following three values are + -- actions: these values may be written, + -- but are never read + createAndGo(4), + createAndWait(5), + destroy(6) + } + +TimeStamp ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The value of the sysUpTime object at which a specific + occurrence happened. The specific occurrence must be + + defined in the description of any object defined using this + type. + + If sysUpTime is reset to zero as a result of a re- + initialization of the network management (sub)system, then + the values of all TimeStamp objects are also reset. + However, after approximately 497 days without a re- + initialization, the sysUpTime object will reach 2^^32-1 and + then increment around to zero; in this case, existing values + of TimeStamp objects do not change. This can lead to + ambiguities in the value of TimeStamp objects." + SYNTAX TimeTicks + +TimeInterval ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "A period of time, measured in units of 0.01 seconds." + SYNTAX INTEGER (0..2147483647) + +DateAndTime ::= TEXTUAL-CONVENTION + DISPLAY-HINT "2d-1d-1d,1d:1d:1d.1d,1a1d:1d" + STATUS current + DESCRIPTION + "A date-time specification. + + field octets contents range + ----- ------ -------- ----- + 1 1-2 year* 0..65536 + 2 3 month 1..12 + 3 4 day 1..31 + 4 5 hour 0..23 + 5 6 minutes 0..59 + 6 7 seconds 0..60 + (use 60 for leap-second) + 7 8 deci-seconds 0..9 + 8 9 direction from UTC '+' / '-' + 9 10 hours from UTC* 0..13 + 10 11 minutes from UTC 0..59 + + * Notes: + - the value of year is in network-byte order + - daylight saving time in New Zealand is +13 + + For example, Tuesday May 26, 1992 at 1:30:15 PM EDT would be + displayed as: + + 1992-5-26,13:30:15.0,-4:0 + + Note that if only local time is known, then timezone + information (fields 8-10) is not present." + SYNTAX OCTET STRING (SIZE (8 | 11)) + +StorageType ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Describes the memory realization of a conceptual row. A + row which is volatile(2) is lost upon reboot. A row which + is either nonVolatile(3), permanent(4) or readOnly(5), is + backed up by stable storage. A row which is permanent(4) + can be changed but not deleted. A row which is readOnly(5) + cannot be changed nor deleted. + + If the value of an object with this syntax is either + permanent(4) or readOnly(5), it cannot be written. + Conversely, if the value is either other(1), volatile(2) or + nonVolatile(3), it cannot be modified to be permanent(4) or + readOnly(5). (All illegal modifications result in a + 'wrongValue' error.) + + Every usage of this textual convention is required to + specify the columnar objects which a permanent(4) row must + at a minimum allow to be writable." + SYNTAX INTEGER { + other(1), -- eh? + volatile(2), -- e.g., in RAM + nonVolatile(3), -- e.g., in NVRAM + permanent(4), -- e.g., partially in ROM + readOnly(5) -- e.g., completely in ROM + } + +TDomain ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Denotes a kind of transport service. + + Some possible values, such as snmpUDPDomain, are defined in + the SNMPv2-TM MIB module. Other possible values are defined + in other MIB modules." + REFERENCE "The SNMPv2-TM MIB module is defined in RFC 1906." + SYNTAX OBJECT IDENTIFIER + +TAddress ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Denotes a transport service address. + + A TAddress value is always interpreted within the context of a + TDomain value. Thus, each definition of a TDomain value must + be accompanied by a definition of a textual convention for use + with that TDomain. Some possible textual conventions, such as + SnmpUDPAddress for snmpUDPDomain, are defined in the SNMPv2-TM + MIB module. Other possible textual conventions are defined in + other MIB modules." + REFERENCE "The SNMPv2-TM MIB module is defined in RFC 1906." + SYNTAX OCTET STRING (SIZE (1..255)) + +END diff --git a/mibs/SNMPv2-TM.txt b/mibs/SNMPv2-TM.txt new file mode 100644 index 0000000..949f99c --- /dev/null +++ b/mibs/SNMPv2-TM.txt @@ -0,0 +1,176 @@ +SNMPv2-TM DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-IDENTITY, + snmpModules, snmpDomains, snmpProxys + FROM SNMPv2-SMI + TEXTUAL-CONVENTION + FROM SNMPv2-TC; + +snmpv2tm MODULE-IDENTITY + LAST-UPDATED "200210160000Z" + ORGANIZATION "IETF SNMPv3 Working Group" + CONTACT-INFO + "WG-EMail: snmpv3@lists.tislabs.com + Subscribe: snmpv3-request@lists.tislabs.com + + Co-Chair: Russ Mundy + Network Associates Laboratories + postal: 15204 Omega Drive, Suite 300 + Rockville, MD 20850-4601 + USA + EMail: mundy@tislabs.com + phone: +1 301 947-7107 + + Co-Chair: David Harrington + Enterasys Networks + postal: 35 Industrial Way + P. O. Box 5005 + Rochester, NH 03866-5005 + USA + EMail: dbh@enterasys.com + phone: +1 603 337-2614 + + Editor: Randy Presuhn + BMC Software, Inc. + postal: 2141 North First Street + San Jose, CA 95131 + USA + EMail: randy_presuhn@bmc.com + phone: +1 408 546-1006" + DESCRIPTION + "The MIB module for SNMP transport mappings. + + Copyright (C) The Internet Society (2002). This + version of this MIB module is part of RFC 3417; + see the RFC itself for full legal notices. + " + REVISION "200210160000Z" + DESCRIPTION + "Clarifications, published as RFC 3417." + REVISION "199601010000Z" + DESCRIPTION + "Clarifications, published as RFC 1906." + REVISION "199304010000Z" + DESCRIPTION + "The initial version, published as RFC 1449." + ::= { snmpModules 19 } + +-- SNMP over UDP over IPv4 + +snmpUDPDomain OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The SNMP over UDP over IPv4 transport domain. + The corresponding transport address is of type + SnmpUDPAddress." + ::= { snmpDomains 1 } + +SnmpUDPAddress ::= TEXTUAL-CONVENTION + DISPLAY-HINT "1d.1d.1d.1d/2d" + STATUS current + DESCRIPTION + "Represents a UDP over IPv4 address: + + octets contents encoding + 1-4 IP-address network-byte order + 5-6 UDP-port network-byte order + " + SYNTAX OCTET STRING (SIZE (6)) + +-- SNMP over OSI + +snmpCLNSDomain OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The SNMP over CLNS transport domain. + The corresponding transport address is of type + SnmpOSIAddress." + ::= { snmpDomains 2 } + +snmpCONSDomain OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The SNMP over CONS transport domain. + The corresponding transport address is of type + SnmpOSIAddress." + ::= { snmpDomains 3 } + +SnmpOSIAddress ::= TEXTUAL-CONVENTION + DISPLAY-HINT "*1x:/1x:" + STATUS current + DESCRIPTION + "Represents an OSI transport-address: + + octets contents encoding + 1 length of NSAP 'n' as an unsigned-integer + (either 0 or from 3 to 20) + 2..(n+1) NSAP concrete binary representation + (n+2)..m TSEL string of (up to 64) octets + " + SYNTAX OCTET STRING (SIZE (1 | 4..85)) + +-- SNMP over DDP + +snmpDDPDomain OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The SNMP over DDP transport domain. The corresponding + transport address is of type SnmpNBPAddress." + ::= { snmpDomains 4 } + +SnmpNBPAddress ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Represents an NBP name: + + octets contents encoding + 1 length of object 'n' as an unsigned integer + 2..(n+1) object string of (up to 32) octets + n+2 length of type 'p' as an unsigned integer + (n+3)..(n+2+p) type string of (up to 32) octets + n+3+p length of zone 'q' as an unsigned integer + (n+4+p)..(n+3+p+q) zone string of (up to 32) octets + + For comparison purposes, strings are + case-insensitive. All strings may contain any octet + other than 255 (hex ff)." + SYNTAX OCTET STRING (SIZE (3..99)) + +-- SNMP over IPX + +snmpIPXDomain OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The SNMP over IPX transport domain. The corresponding + transport address is of type SnmpIPXAddress." + ::= { snmpDomains 5 } + +SnmpIPXAddress ::= TEXTUAL-CONVENTION + DISPLAY-HINT "4x.1x:1x:1x:1x:1x:1x.2d" + STATUS current + DESCRIPTION + "Represents an IPX address: + + octets contents encoding + 1-4 network-number network-byte order + 5-10 physical-address network-byte order + 11-12 socket-number network-byte order + " + SYNTAX OCTET STRING (SIZE (12)) + +-- for proxy to SNMPv1 (RFC 1157) + +rfc1157Proxy OBJECT IDENTIFIER ::= { snmpProxys 1 } + +rfc1157Domain OBJECT-IDENTITY + STATUS deprecated + DESCRIPTION + "The transport domain for SNMPv1 over UDP over IPv4. + The corresponding transport address is of type + SnmpUDPAddress." + ::= { rfc1157Proxy 1 } + +-- ::= { rfc1157Proxy 2 } this OID is obsolete + +END diff --git a/mibs/SOURCE-ROUTING-MIB.txt b/mibs/SOURCE-ROUTING-MIB.txt new file mode 100644 index 0000000..988b1b0 --- /dev/null +++ b/mibs/SOURCE-ROUTING-MIB.txt @@ -0,0 +1,452 @@ +SOURCE-ROUTING-MIB DEFINITIONS ::= BEGIN + +IMPORTS + Counter, Gauge + FROM RFC1155-SMI + dot1dBridge, dot1dSr + FROM BRIDGE-MIB + OBJECT-TYPE + FROM RFC-1212; + +-- groups in the SR MIB + +-- dot1dSr is imported from the Bridge MIB + +dot1dPortPair OBJECT IDENTIFIER ::= { dot1dBridge 10 } + +-- the dot1dSr group + +-- this group is implemented by those bridges that +-- support the source route bridging mode, including Source +-- Routing and SRT bridges. + +dot1dSrPortTable OBJECT-TYPE + SYNTAX SEQUENCE OF Dot1dSrPortEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "A table that contains information about every + port that is associated with this source route + bridge." + ::= { dot1dSr 1 } + +dot1dSrPortEntry OBJECT-TYPE + SYNTAX Dot1dSrPortEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "A list of information for each port of a source + route bridge." + INDEX { dot1dSrPort } + + ::= { dot1dSrPortTable 1 } + +Dot1dSrPortEntry ::= + SEQUENCE { + dot1dSrPort + INTEGER, + dot1dSrPortHopCount + INTEGER, + dot1dSrPortLocalSegment + INTEGER, + dot1dSrPortBridgeNum + INTEGER, + dot1dSrPortTargetSegment + INTEGER, + dot1dSrPortLargestFrame + INTEGER, + dot1dSrPortSTESpanMode + INTEGER, + dot1dSrPortSpecInFrames + Counter, + dot1dSrPortSpecOutFrames + Counter, + dot1dSrPortApeInFrames + Counter, + dot1dSrPortApeOutFrames + Counter, + dot1dSrPortSteInFrames + Counter, + dot1dSrPortSteOutFrames + Counter, + dot1dSrPortSegmentMismatchDiscards + Counter, + dot1dSrPortDuplicateSegmentDiscards + Counter, + dot1dSrPortHopCountExceededDiscards + Counter, + dot1dSrPortDupLanIdOrTreeErrors + Counter, + dot1dSrPortLanIdMismatches + Counter + } + +dot1dSrPort OBJECT-TYPE + SYNTAX INTEGER (1..65535) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The port number of the port for which this entry + + contains Source Route management information." + ::= { dot1dSrPortEntry 1 } + +dot1dSrPortHopCount OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The maximum number of routing descriptors allowed + in an All Paths or Spanning Tree Explorer frames." + ::= { dot1dSrPortEntry 2 } + +dot1dSrPortLocalSegment OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The segment number that uniquely identifies the + segment to which this port is connected. Current + source routing protocols limit this value to the + range: 0 through 4095. (The value 0 is used by + some management applications for special test + cases.) A value of 65535 signifies that no segment + number is assigned to this port." + ::= { dot1dSrPortEntry 3 } + +dot1dSrPortBridgeNum OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A bridge number uniquely identifies a bridge when + more than one bridge is used to span the same two + segments. Current source routing protocols limit + this value to the range: 0 through 15. A value of + 65535 signifies that no bridge number is assigned + to this bridge." + ::= { dot1dSrPortEntry 4 } + +dot1dSrPortTargetSegment OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The segment number that corresponds to the target + segment this port is considered to be connected to + by the bridge. Current source routing protocols + limit this value to the range: 0 through 4095. + + (The value 0 is used by some management + applications for special test cases.) A value of + 65535 signifies that no target segment is assigned + to this port." + ::= { dot1dSrPortEntry 5 } + +-- It would be nice if we could use ifMtu as the size of the +-- largest frame, but we can't because ifMtu is defined to be +-- the size that the (inter-)network layer can use which can +-- differ from the MAC layer (especially if several layers of +-- encapsulation are used). + +dot1dSrPortLargestFrame OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The maximum size of the INFO field (LLC and + above) that this port can send/receive. It does + not include any MAC level (framing) octets. The + value of this object is used by this bridge to + determine whether a modification of the + LargestFrame (LF, see [14]) field of the Routing + Control field of the Routing Information Field is + necessary. + + 64 valid values are defined by the IEEE 802.5M SRT + Addendum: 516, 635, 754, 873, 993, 1112, 1231, + 1350, 1470, 1542, 1615, 1688, 1761, 1833, 1906, + 1979, 2052, 2345, 2638, 2932, 3225, 3518, 3812, + 4105, 4399, 4865, 5331, 5798, 6264, 6730, 7197, + 7663, 8130, 8539, 8949, 9358, 9768, 10178, 10587, + 10997, 11407, 12199, 12992, 13785, 14578, 15370, + 16163, 16956, 17749, 20730, 23711, 26693, 29674, + 32655, 35637, 38618, 41600, 44591, 47583, 50575, + 53567, 56559, 59551, and 65535. + + An illegal value will not be accepted by the + bridge." + ::= { dot1dSrPortEntry 6 } + +dot1dSrPortSTESpanMode OBJECT-TYPE + SYNTAX INTEGER { + auto-span(1), + disabled(2), + forced(3) + } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "Determines how this port behaves when presented + with a Spanning Tree Explorer frame. The value + 'disabled(2)' indicates that the port will not + accept or send Spanning Tree Explorer packets; any + STE packets received will be silently discarded. + The value 'forced(3)' indicates the port will + always accept and propagate Spanning Tree Explorer + frames. This allows a manually configured + Spanning Tree for this class of packet to be + configured. Note that unlike transparent + bridging, this is not catastrophic to the network + if there are loops. The value 'auto-span(1)' can + only be returned by a bridge that both implements + the Spanning Tree Protocol and has use of the + protocol enabled on this port. The behavior of the + port for Spanning Tree Explorer frames is + determined by the state of dot1dStpPortState. If + the port is in the 'forwarding' state, the frame + will be accepted or propagated. Otherwise, it + will be silently discarded." + ::= { dot1dSrPortEntry 7 } + +dot1dSrPortSpecInFrames OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of Specifically Routed frames, also + referred to as Source Routed Frames, that have + been received from this port's segment." + ::= { dot1dSrPortEntry 8 } + +dot1dSrPortSpecOutFrames OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of Specifically Routed frames, also + referred to as Source Routed Frames, that this + port has transmitted on its segment." + ::= { dot1dSrPortEntry 9 } + +dot1dSrPortApeInFrames OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of All Paths Explorer frames, also + referred to as All Routes Explorer frames, that + have been received by this port from its segment." + ::= { dot1dSrPortEntry 10 } + +dot1dSrPortApeOutFrames OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of all Paths Explorer Frames, also + referred to as All Routes Explorer frames, that + have been transmitted by this port on its + segment." + ::= { dot1dSrPortEntry 11 } + +dot1dSrPortSteInFrames OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of spanning tree explorer frames that + have been received by this port from its segment." + ::= { dot1dSrPortEntry 12 } + +dot1dSrPortSteOutFrames OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of spanning tree explorer frames that + have been transmitted by this port on its + segment." + ::= { dot1dSrPortEntry 13 } + +dot1dSrPortSegmentMismatchDiscards OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of explorer frames that have been + discarded by this port because the routing + descriptor field contained an invalid adjacent + segment value." + ::= { dot1dSrPortEntry 14 } + +dot1dSrPortDuplicateSegmentDiscards OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of frames that have been discarded by + this port because the routing descriptor field + contained a duplicate segment identifier." + ::= { dot1dSrPortEntry 15 } + +dot1dSrPortHopCountExceededDiscards OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of explorer frames that have been + discarded by this port because the Routing + Information Field has exceeded the maximum route + descriptor length." + ::= { dot1dSrPortEntry 16 } + +dot1dSrPortDupLanIdOrTreeErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of duplicate LAN IDs or Tree errors. + This helps in detection of problems in networks + containing older IBM Source Routing Bridges." + ::= { dot1dSrPortEntry 17 } + +dot1dSrPortLanIdMismatches OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ARE and STE frames that were + discarded because the last LAN ID in the routing + information field did not equal the LAN-in ID. + This error can occur in implementations which do + only a LAN-in ID and Bridge Number check instead + of a LAN-in ID, Bridge Number, and LAN-out ID + check before they forward broadcast frames." + ::= { dot1dSrPortEntry 18 } + +-- scalar object in dot1dSr + +dot1dSrBridgeLfMode OBJECT-TYPE + SYNTAX INTEGER { + mode3(1), + mode6(2) + } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "Indicates whether the bridge operates using older + 3 bit length negotiation fields or the newer 6 bit + length field in its RIF." + ::= { dot1dSr 2 } + +-- The Port-Pair Database + +-- Implementation of this group is optional. + +-- This group is implemented by those bridges that support +-- the direct multiport model of the source route bridging +-- mode as defined in the IEEE 802.5 SRT Addendum to +-- 802.1d. + +-- Bridges implementing this group may report 65535 for +-- dot1dSrPortBridgeNumber and dot1dSrPortTargetSegment, +-- indicating that those objects are not applicable. + +dot1dPortPairTableSize OBJECT-TYPE + SYNTAX Gauge + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of entries in the Bridge Port + Pair Database." + ::= { dot1dPortPair 1 } + +-- the Bridge Port-Pair table + +-- this table represents port pairs within a bridge forming +-- a unique bridge path, as defined in the IEEE 802.5M SRT +-- Addendum. + +dot1dPortPairTable OBJECT-TYPE + SYNTAX SEQUENCE OF Dot1dPortPairEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "A table that contains information about every + + port pair database entity associated with this + source routing bridge." + ::= { dot1dPortPair 2 } + +dot1dPortPairEntry OBJECT-TYPE + SYNTAX Dot1dPortPairEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "A list of information for each port pair entity + of a bridge." + INDEX { dot1dPortPairLowPort, dot1dPortPairHighPort } + ::= { dot1dPortPairTable 1 } + +Dot1dPortPairEntry ::= + SEQUENCE { + dot1dPortPairLowPort + INTEGER, + dot1dPortPairHighPort + INTEGER, + dot1dPortPairBridgeNum + INTEGER, + dot1dPortPairBridgeState + INTEGER + } + +dot1dPortPairLowPort OBJECT-TYPE + SYNTAX INTEGER (1..65535) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The port number of the lower numbered port for + which this entry contains port pair database + information." + ::= { dot1dPortPairEntry 1 } + +dot1dPortPairHighPort OBJECT-TYPE + SYNTAX INTEGER (1..65535) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The port number of the higher numbered port for + which this entry contains port pair database + information." + ::= { dot1dPortPairEntry 2 } + +dot1dPortPairBridgeNum OBJECT-TYPE + SYNTAX INTEGER + + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A bridge number that uniquely identifies the path + provided by this source routing bridge between the + segments connected to dot1dPortPairLowPort and + dot1dPortPairHighPort. The purpose of bridge + number is to disambiguate between multiple paths + connecting the same two LANs." + ::= { dot1dPortPairEntry 3 } + +dot1dPortPairBridgeState OBJECT-TYPE + SYNTAX INTEGER { + enabled(1), + disabled(2), + invalid(3) + } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The state of dot1dPortPairBridgeNum. Writing + 'invalid(3)' to this object removes the + corresponding entry." + ::= { dot1dPortPairEntry 4 } + +END diff --git a/mibs/TCP-MIB.txt b/mibs/TCP-MIB.txt new file mode 100644 index 0000000..50e7f57 --- /dev/null +++ b/mibs/TCP-MIB.txt @@ -0,0 +1,785 @@ +TCP-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, Integer32, Unsigned32, + Gauge32, Counter32, Counter64, IpAddress, mib-2 + FROM SNMPv2-SMI + MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF + InetAddress, InetAddressType, + InetPortNumber FROM INET-ADDRESS-MIB; + +tcpMIB MODULE-IDENTITY + LAST-UPDATED "200502180000Z" -- 18 February 2005 + ORGANIZATION + "IETF IPv6 MIB Revision Team + http://www.ietf.org/html.charters/ipv6-charter.html" + CONTACT-INFO + "Rajiv Raghunarayan (editor) + + Cisco Systems Inc. + 170 West Tasman Drive + San Jose, CA 95134 + + Phone: +1 408 853 9612 + Email: <raraghun@cisco.com> + + Send comments to <ipv6@ietf.org>" + DESCRIPTION + "The MIB module for managing TCP implementations. + + Copyright (C) The Internet Society (2005). This version + of this MIB module is a part of RFC 4022; see the RFC + itself for full legal notices." + REVISION "200502180000Z" -- 18 February 2005 + DESCRIPTION + "IP version neutral revision, published as RFC 4022." + REVISION "9411010000Z" + DESCRIPTION + "Initial SMIv2 version, published as RFC 2012." + REVISION "9103310000Z" + DESCRIPTION + "The initial revision of this MIB module was part of + MIB-II." + ::= { mib-2 49 } + +-- the TCP base variables group + +tcp OBJECT IDENTIFIER ::= { mib-2 6 } + +-- Scalars + +tcpRtoAlgorithm OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- none of the following + constant(2), -- a constant rto + rsre(3), -- MIL-STD-1778, Appendix B + vanj(4), -- Van Jacobson's algorithm + rfc2988(5) -- RFC 2988 + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The algorithm used to determine the timeout value used for + retransmitting unacknowledged octets." + ::= { tcp 1 } + +tcpRtoMin OBJECT-TYPE + SYNTAX Integer32 (0..2147483647) + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The minimum value permitted by a TCP implementation for + the retransmission timeout, measured in milliseconds. + More refined semantics for objects of this type depend + on the algorithm used to determine the retransmission + timeout; in particular, the IETF standard algorithm + rfc2988(5) provides a minimum value." + ::= { tcp 2 } + +tcpRtoMax OBJECT-TYPE + SYNTAX Integer32 (0..2147483647) + UNITS "milliseconds" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The maximum value permitted by a TCP implementation for + the retransmission timeout, measured in milliseconds. + More refined semantics for objects of this type depend + on the algorithm used to determine the retransmission + timeout; in particular, the IETF standard algorithm + rfc2988(5) provides an upper bound (as part of an + adaptive backoff algorithm)." + ::= { tcp 3 } + +tcpMaxConn OBJECT-TYPE + SYNTAX Integer32 (-1 | 0..2147483647) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The limit on the total number of TCP connections the entity + can support. In entities where the maximum number of + connections is dynamic, this object should contain the + value -1." + ::= { tcp 4 } + +tcpActiveOpens OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times that TCP connections have made a direct + transition to the SYN-SENT state from the CLOSED state. + + Discontinuities in the value of this counter are + indicated via discontinuities in the value of sysUpTime." + ::= { tcp 5 } + +tcpPassiveOpens OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times TCP connections have made a direct + transition to the SYN-RCVD state from the LISTEN state. + + Discontinuities in the value of this counter are + indicated via discontinuities in the value of sysUpTime." + ::= { tcp 6 } + +tcpAttemptFails OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times that TCP connections have made a direct + transition to the CLOSED state from either the SYN-SENT + state or the SYN-RCVD state, plus the number of times that + TCP connections have made a direct transition to the + LISTEN state from the SYN-RCVD state. + + Discontinuities in the value of this counter are + indicated via discontinuities in the value of sysUpTime." + ::= { tcp 7 } + +tcpEstabResets OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of times that TCP connections have made a direct + transition to the CLOSED state from either the ESTABLISHED + state or the CLOSE-WAIT state. + + Discontinuities in the value of this counter are + indicated via discontinuities in the value of sysUpTime." + ::= { tcp 8 } + +tcpCurrEstab OBJECT-TYPE + SYNTAX Gauge32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of TCP connections for which the current state + is either ESTABLISHED or CLOSE-WAIT." + ::= { tcp 9 } + +tcpInSegs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of segments received, including those + received in error. This count includes segments received + on currently established connections. + + Discontinuities in the value of this counter are + indicated via discontinuities in the value of sysUpTime." + ::= { tcp 10 } + +tcpOutSegs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of segments sent, including those on + current connections but excluding those containing only + retransmitted octets. + + Discontinuities in the value of this counter are + indicated via discontinuities in the value of sysUpTime." + ::= { tcp 11 } + +tcpRetransSegs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of segments retransmitted; that is, the + number of TCP segments transmitted containing one or more + previously transmitted octets. + + Discontinuities in the value of this counter are + indicated via discontinuities in the value of sysUpTime." + ::= { tcp 12 } + +tcpInErrs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of segments received in error (e.g., bad + TCP checksums). + + Discontinuities in the value of this counter are + indicated via discontinuities in the value of sysUpTime." + ::= { tcp 14 } + +tcpOutRsts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of TCP segments sent containing the RST flag. + + Discontinuities in the value of this counter are + indicated via discontinuities in the value of sysUpTime." + ::= { tcp 15 } + +-- { tcp 16 } was used to represent the ipv6TcpConnTable in RFC 2452, +-- which has since been obsoleted. It MUST not be used. + +tcpHCInSegs OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of segments received, including those + received in error. This count includes segments received + + on currently established connections. This object is + the 64-bit equivalent of tcpInSegs. + + Discontinuities in the value of this counter are + indicated via discontinuities in the value of sysUpTime." + ::= { tcp 17 } + +tcpHCOutSegs OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of segments sent, including those on + current connections but excluding those containing only + retransmitted octets. This object is the 64-bit + equivalent of tcpOutSegs. + + Discontinuities in the value of this counter are + indicated via discontinuities in the value of sysUpTime." + ::= { tcp 18 } + +-- The TCP Connection table + +tcpConnectionTable OBJECT-TYPE + SYNTAX SEQUENCE OF TcpConnectionEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table containing information about existing TCP + connections. Note that unlike earlier TCP MIBs, there + is a separate table for connections in the LISTEN state." + ::= { tcp 19 } + +tcpConnectionEntry OBJECT-TYPE + SYNTAX TcpConnectionEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A conceptual row of the tcpConnectionTable containing + information about a particular current TCP connection. + Each row of this table is transient in that it ceases to + exist when (or soon after) the connection makes the + transition to the CLOSED state." + INDEX { tcpConnectionLocalAddressType, + tcpConnectionLocalAddress, + tcpConnectionLocalPort, + tcpConnectionRemAddressType, + tcpConnectionRemAddress, + tcpConnectionRemPort } + ::= { tcpConnectionTable 1 } + +TcpConnectionEntry ::= SEQUENCE { + tcpConnectionLocalAddressType InetAddressType, + tcpConnectionLocalAddress InetAddress, + tcpConnectionLocalPort InetPortNumber, + tcpConnectionRemAddressType InetAddressType, + tcpConnectionRemAddress InetAddress, + tcpConnectionRemPort InetPortNumber, + tcpConnectionState INTEGER, + tcpConnectionProcess Unsigned32 + } + +tcpConnectionLocalAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The address type of tcpConnectionLocalAddress." + ::= { tcpConnectionEntry 1 } + +tcpConnectionLocalAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The local IP address for this TCP connection. The type + of this address is determined by the value of + tcpConnectionLocalAddressType. + + As this object is used in the index for the + tcpConnectionTable, implementors should be + careful not to create entries that would result in OIDs + with more than 128 subidentifiers; otherwise the information + cannot be accessed by using SNMPv1, SNMPv2c, or SNMPv3." + ::= { tcpConnectionEntry 2 } + +tcpConnectionLocalPort OBJECT-TYPE + SYNTAX InetPortNumber + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The local port number for this TCP connection." + ::= { tcpConnectionEntry 3 } + +tcpConnectionRemAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The address type of tcpConnectionRemAddress." + ::= { tcpConnectionEntry 4 } + +tcpConnectionRemAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The remote IP address for this TCP connection. The type + of this address is determined by the value of + tcpConnectionRemAddressType. + + As this object is used in the index for the + tcpConnectionTable, implementors should be + careful not to create entries that would result in OIDs + with more than 128 subidentifiers; otherwise the information + cannot be accessed by using SNMPv1, SNMPv2c, or SNMPv3." + ::= { tcpConnectionEntry 5 } + +tcpConnectionRemPort OBJECT-TYPE + SYNTAX InetPortNumber + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The remote port number for this TCP connection." + ::= { tcpConnectionEntry 6 } + +tcpConnectionState OBJECT-TYPE + SYNTAX INTEGER { + closed(1), + listen(2), + synSent(3), + synReceived(4), + established(5), + finWait1(6), + finWait2(7), + closeWait(8), + lastAck(9), + closing(10), + timeWait(11), + deleteTCB(12) + } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The state of this TCP connection. + + The value listen(2) is included only for parallelism to the + old tcpConnTable and should not be used. A connection in + LISTEN state should be present in the tcpListenerTable. + + The only value that may be set by a management station is + deleteTCB(12). Accordingly, it is appropriate for an agent + to return a `badValue' response if a management station + attempts to set this object to any other value. + + If a management station sets this object to the value + deleteTCB(12), then the TCB (as defined in [RFC793]) of + the corresponding connection on the managed node is + deleted, resulting in immediate termination of the + connection. + + As an implementation-specific option, a RST segment may be + sent from the managed node to the other TCP endpoint (note, + however, that RST segments are not sent reliably)." + ::= { tcpConnectionEntry 7 } + +tcpConnectionProcess OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The system's process ID for the process associated with + this connection, or zero if there is no such process. This + value is expected to be the same as HOST-RESOURCES-MIB:: + hrSWRunIndex or SYSAPPL-MIB::sysApplElmtRunIndex for some + row in the appropriate tables." + ::= { tcpConnectionEntry 8 } + +-- The TCP Listener table + +tcpListenerTable OBJECT-TYPE + SYNTAX SEQUENCE OF TcpListenerEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table containing information about TCP listeners. A + listening application can be represented in three + possible ways: + + 1. An application that is willing to accept both IPv4 and + IPv6 datagrams is represented by + + a tcpListenerLocalAddressType of unknown (0) and + a tcpListenerLocalAddress of ''h (a zero-length + octet-string). + + 2. An application that is willing to accept only IPv4 or + IPv6 datagrams is represented by a + tcpListenerLocalAddressType of the appropriate address + type and a tcpListenerLocalAddress of '0.0.0.0' or '::' + respectively. + + 3. An application that is listening for data destined + only to a specific IP address, but from any remote + system, is represented by a tcpListenerLocalAddressType + of an appropriate address type, with + tcpListenerLocalAddress as the specific local address. + + NOTE: The address type in this table represents the + address type used for the communication, irrespective + of the higher-layer abstraction. For example, an + application using IPv6 'sockets' to communicate via + IPv4 between ::ffff:10.0.0.1 and ::ffff:10.0.0.2 would + use InetAddressType ipv4(1))." + ::= { tcp 20 } + +tcpListenerEntry OBJECT-TYPE + SYNTAX TcpListenerEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A conceptual row of the tcpListenerTable containing + information about a particular TCP listener." + INDEX { tcpListenerLocalAddressType, + tcpListenerLocalAddress, + tcpListenerLocalPort } + ::= { tcpListenerTable 1 } + +TcpListenerEntry ::= SEQUENCE { + tcpListenerLocalAddressType InetAddressType, + tcpListenerLocalAddress InetAddress, + tcpListenerLocalPort InetPortNumber, + tcpListenerProcess Unsigned32 + } + +tcpListenerLocalAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The address type of tcpListenerLocalAddress. The value + should be unknown (0) if connection initiations to all + local IP addresses are accepted." + ::= { tcpListenerEntry 1 } + +tcpListenerLocalAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The local IP address for this TCP connection. + + The value of this object can be represented in three + possible ways, depending on the characteristics of the + listening application: + + 1. For an application willing to accept both IPv4 and + IPv6 datagrams, the value of this object must be + ''h (a zero-length octet-string), with the value + of the corresponding tcpListenerLocalAddressType + object being unknown (0). + + 2. For an application willing to accept only IPv4 or + IPv6 datagrams, the value of this object must be + '0.0.0.0' or '::' respectively, with + tcpListenerLocalAddressType representing the + appropriate address type. + + 3. For an application which is listening for data + destined only to a specific IP address, the value + of this object is the specific local address, with + tcpListenerLocalAddressType representing the + appropriate address type. + + As this object is used in the index for the + tcpListenerTable, implementors should be + careful not to create entries that would result in OIDs + with more than 128 subidentifiers; otherwise the information + cannot be accessed, using SNMPv1, SNMPv2c, or SNMPv3." + ::= { tcpListenerEntry 2 } + +tcpListenerLocalPort OBJECT-TYPE + SYNTAX InetPortNumber + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The local port number for this TCP connection." + ::= { tcpListenerEntry 3 } + +tcpListenerProcess OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The system's process ID for the process associated with + this listener, or zero if there is no such process. This + value is expected to be the same as HOST-RESOURCES-MIB:: + hrSWRunIndex or SYSAPPL-MIB::sysApplElmtRunIndex for some + row in the appropriate tables." + ::= { tcpListenerEntry 4 } + +-- The deprecated TCP Connection table + +tcpConnTable OBJECT-TYPE + SYNTAX SEQUENCE OF TcpConnEntry + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "A table containing information about existing IPv4-specific + TCP connections or listeners. This table has been + deprecated in favor of the version neutral + tcpConnectionTable." + ::= { tcp 13 } + +tcpConnEntry OBJECT-TYPE + SYNTAX TcpConnEntry + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "A conceptual row of the tcpConnTable containing information + about a particular current IPv4 TCP connection. Each row + of this table is transient in that it ceases to exist when + (or soon after) the connection makes the transition to the + CLOSED state." + INDEX { tcpConnLocalAddress, + tcpConnLocalPort, + tcpConnRemAddress, + tcpConnRemPort } + ::= { tcpConnTable 1 } + +TcpConnEntry ::= SEQUENCE { + tcpConnState INTEGER, + tcpConnLocalAddress IpAddress, + tcpConnLocalPort Integer32, + tcpConnRemAddress IpAddress, + tcpConnRemPort Integer32 + + } + +tcpConnState OBJECT-TYPE + SYNTAX INTEGER { + closed(1), + listen(2), + synSent(3), + synReceived(4), + established(5), + finWait1(6), + finWait2(7), + closeWait(8), + lastAck(9), + closing(10), + timeWait(11), + deleteTCB(12) + } + MAX-ACCESS read-write + STATUS deprecated + DESCRIPTION + "The state of this TCP connection. + + The only value that may be set by a management station is + deleteTCB(12). Accordingly, it is appropriate for an agent + to return a `badValue' response if a management station + attempts to set this object to any other value. + + If a management station sets this object to the value + deleteTCB(12), then the TCB (as defined in [RFC793]) of + the corresponding connection on the managed node is + deleted, resulting in immediate termination of the + connection. + + As an implementation-specific option, a RST segment may be + sent from the managed node to the other TCP endpoint (note, + however, that RST segments are not sent reliably)." + ::= { tcpConnEntry 1 } + +tcpConnLocalAddress OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The local IP address for this TCP connection. In the case + of a connection in the listen state willing to + accept connections for any IP interface associated with the + node, the value 0.0.0.0 is used." + ::= { tcpConnEntry 2 } + +tcpConnLocalPort OBJECT-TYPE + SYNTAX Integer32 (0..65535) + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The local port number for this TCP connection." + ::= { tcpConnEntry 3 } + +tcpConnRemAddress OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The remote IP address for this TCP connection." + ::= { tcpConnEntry 4 } + +tcpConnRemPort OBJECT-TYPE + SYNTAX Integer32 (0..65535) + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The remote port number for this TCP connection." + ::= { tcpConnEntry 5 } + +-- conformance information + +tcpMIBConformance OBJECT IDENTIFIER ::= { tcpMIB 2 } + +tcpMIBCompliances OBJECT IDENTIFIER ::= { tcpMIBConformance 1 } +tcpMIBGroups OBJECT IDENTIFIER ::= { tcpMIBConformance 2 } + +-- compliance statements + +tcpMIBCompliance2 MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for systems that implement TCP. + + A number of INDEX objects cannot be + represented in the form of OBJECT clauses in SMIv2 but + have the following compliance requirements, + expressed in OBJECT clause form in this description + clause: + + -- OBJECT tcpConnectionLocalAddressType + -- SYNTAX InetAddressType { ipv4(1), ipv6(2) } + -- DESCRIPTION + -- This MIB requires support for only global IPv4 + + -- and IPv6 address types. + -- + -- OBJECT tcpConnectionRemAddressType + -- SYNTAX InetAddressType { ipv4(1), ipv6(2) } + -- DESCRIPTION + -- This MIB requires support for only global IPv4 + -- and IPv6 address types. + -- + -- OBJECT tcpListenerLocalAddressType + -- SYNTAX InetAddressType { unknown(0), ipv4(1), + -- ipv6(2) } + -- DESCRIPTION + -- This MIB requires support for only global IPv4 + -- and IPv6 address types. The type unknown also + -- needs to be supported to identify a special + -- case in the listener table: a listen using + -- both IPv4 and IPv6 addresses on the device. + -- + " + MODULE -- this module + MANDATORY-GROUPS { tcpBaseGroup, tcpConnectionGroup, + tcpListenerGroup } + GROUP tcpHCGroup + DESCRIPTION + "This group is mandatory for systems that are capable + of receiving or transmitting more than 1 million TCP + segments per second. 1 million segments per second will + cause a Counter32 to wrap in just over an hour." + OBJECT tcpConnectionState + SYNTAX INTEGER { closed(1), listen(2), synSent(3), + synReceived(4), established(5), + finWait1(6), finWait2(7), closeWait(8), + lastAck(9), closing(10), timeWait(11) } + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required, nor is support for the value + deleteTCB (12)." + ::= { tcpMIBCompliances 2 } + +tcpMIBCompliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION + "The compliance statement for IPv4-only systems that + implement TCP. In order to be IP version independent, this + compliance statement is deprecated in favor of + tcpMIBCompliance2. However, agents are still encouraged + to implement these objects in order to interoperate with + the deployed base of managers." + + MODULE -- this module + MANDATORY-GROUPS { tcpGroup } + OBJECT tcpConnState + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + ::= { tcpMIBCompliances 1 } + +-- units of conformance + +tcpGroup OBJECT-GROUP + OBJECTS { tcpRtoAlgorithm, tcpRtoMin, tcpRtoMax, + tcpMaxConn, tcpActiveOpens, + tcpPassiveOpens, tcpAttemptFails, + tcpEstabResets, tcpCurrEstab, tcpInSegs, + tcpOutSegs, tcpRetransSegs, tcpConnState, + tcpConnLocalAddress, tcpConnLocalPort, + tcpConnRemAddress, tcpConnRemPort, + tcpInErrs, tcpOutRsts } + STATUS deprecated + DESCRIPTION + "The tcp group of objects providing for management of TCP + entities." + ::= { tcpMIBGroups 1 } + +tcpBaseGroup OBJECT-GROUP + OBJECTS { tcpRtoAlgorithm, tcpRtoMin, tcpRtoMax, + tcpMaxConn, tcpActiveOpens, + tcpPassiveOpens, tcpAttemptFails, + tcpEstabResets, tcpCurrEstab, tcpInSegs, + tcpOutSegs, tcpRetransSegs, + tcpInErrs, tcpOutRsts } + STATUS current + DESCRIPTION + "The group of counters common to TCP entities." + ::= { tcpMIBGroups 2 } + +tcpConnectionGroup OBJECT-GROUP + OBJECTS { tcpConnectionState, tcpConnectionProcess } + STATUS current + DESCRIPTION + "The group provides general information about TCP + connections." + ::= { tcpMIBGroups 3 } + +tcpListenerGroup OBJECT-GROUP + OBJECTS { tcpListenerProcess } + STATUS current + DESCRIPTION + "This group has objects providing general information about + TCP listeners." + ::= { tcpMIBGroups 4 } + +tcpHCGroup OBJECT-GROUP + OBJECTS { tcpHCInSegs, tcpHCOutSegs } + STATUS current + DESCRIPTION + "The group of objects providing for counters of high speed + TCP implementations." + ::= { tcpMIBGroups 5 } + +END diff --git a/mibs/TRANSPORT-ADDRESS-MIB.txt b/mibs/TRANSPORT-ADDRESS-MIB.txt new file mode 100644 index 0000000..227886e --- /dev/null +++ b/mibs/TRANSPORT-ADDRESS-MIB.txt @@ -0,0 +1,421 @@ +TRANSPORT-ADDRESS-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-IDENTITY, mib-2 FROM SNMPv2-SMI + TEXTUAL-CONVENTION FROM SNMPv2-TC; + +transportAddressMIB MODULE-IDENTITY + LAST-UPDATED "200211010000Z" + ORGANIZATION + "IETF Operations and Management Area" + CONTACT-INFO + "Juergen Schoenwaelder (Editor) + TU Braunschweig + Bueltenweg 74/75 + 38106 Braunschweig, Germany + + Phone: +49 531 391-3289 + EMail: schoenw@ibr.cs.tu-bs.de + + Send comments to <mibs@ops.ietf.org>." + DESCRIPTION + "This MIB module provides commonly used transport + address definitions. + + Copyright (C) The Internet Society (2002). This version of + this MIB module is part of RFC 3419; see the RFC itself for + full legal notices." + + -- Revision log + + REVISION "200211010000Z" + DESCRIPTION + "Initial version, published as RFC 3419." + ::= { mib-2 100 } + +transportDomains OBJECT IDENTIFIER ::= { transportAddressMIB 1 } + +transportDomainUdpIpv4 OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The UDP over IPv4 transport domain. The corresponding + transport address is of type TransportAddressIPv4 for + global IPv4 addresses." + ::= { transportDomains 1 } + +transportDomainUdpIpv6 OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The UDP over IPv6 transport domain. The corresponding + transport address is of type TransportAddressIPv6 for + global IPv6 addresses." + ::= { transportDomains 2 } + +transportDomainUdpIpv4z OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The UDP over IPv4 transport domain. The corresponding + transport address is of type TransportAddressIPv4z for + scoped IPv4 addresses with a zone index." + ::= { transportDomains 3 } + +transportDomainUdpIpv6z OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The UDP over IPv6 transport domain. The corresponding + transport address is of type TransportAddressIPv6z for + scoped IPv6 addresses with a zone index." + ::= { transportDomains 4 } + +transportDomainTcpIpv4 OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The TCP over IPv4 transport domain. The corresponding + transport address is of type TransportAddressIPv4 for + global IPv4 addresses." + ::= { transportDomains 5 } + +transportDomainTcpIpv6 OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The TCP over IPv6 transport domain. The corresponding + transport address is of type TransportAddressIPv6 for + global IPv6 addresses." + ::= { transportDomains 6 } + +transportDomainTcpIpv4z OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The TCP over IPv4 transport domain. The corresponding + transport address is of type TransportAddressIPv4z for + scoped IPv4 addresses with a zone index." + ::= { transportDomains 7 } + +transportDomainTcpIpv6z OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The TCP over IPv6 transport domain. The corresponding + transport address is of type TransportAddressIPv6z for + scoped IPv6 addresses with a zone index." + ::= { transportDomains 8 } + +transportDomainSctpIpv4 OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The SCTP over IPv4 transport domain. The corresponding + transport address is of type TransportAddressIPv4 for + global IPv4 addresses. This transport domain usually + represents the primary address on multihomed SCTP + endpoints." + ::= { transportDomains 9 } + +transportDomainSctpIpv6 OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The SCTP over IPv6 transport domain. The corresponding + transport address is of type TransportAddressIPv6 for + global IPv6 addresses. This transport domain usually + represents the primary address on multihomed SCTP + endpoints." + ::= { transportDomains 10 } + +transportDomainSctpIpv4z OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The SCTP over IPv4 transport domain. The corresponding + transport address is of type TransportAddressIPv4z for + scoped IPv4 addresses with a zone index. This transport + domain usually represents the primary address on + multihomed SCTP endpoints." + ::= { transportDomains 11 } + +transportDomainSctpIpv6z OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The SCTP over IPv6 transport domain. The corresponding + transport address is of type TransportAddressIPv6z for + scoped IPv6 addresses with a zone index. This transport + domain usually represents the primary address on + multihomed SCTP endpoints." + ::= { transportDomains 12 } + +transportDomainLocal OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The Posix Local IPC transport domain. The corresponding + transport address is of type TransportAddressLocal. + + The Posix Local IPC transport domain incorporates the + well-known UNIX domain sockets." + ::= { transportDomains 13 } + +transportDomainUdpDns OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The UDP transport domain using fully qualified domain + names. The corresponding transport address is of type + TransportAddressDns." + ::= { transportDomains 14 } + +transportDomainTcpDns OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The TCP transport domain using fully qualified domain + names. The corresponding transport address is of type + TransportAddressDns." + ::= { transportDomains 15 } + +transportDomainSctpDns OBJECT-IDENTITY + STATUS current + DESCRIPTION + "The SCTP transport domain using fully qualified domain + names. The corresponding transport address is of type + TransportAddressDns." + ::= { transportDomains 16 } + +TransportDomain ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "A value that represents a transport domain. + + Some possible values, such as transportDomainUdpIpv4, are + defined in this module. Other possible values can be + defined in other MIB modules." + SYNTAX OBJECT IDENTIFIER + +-- +-- The enumerated values of the textual convention below should +-- be identical to the last sub-identifier of the OID registered +-- for the same domain. +-- + +TransportAddressType ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "A value that represents a transport domain. This is the + enumerated version of the transport domain registrations + in this MIB module. The enumerated values have the + following meaning: + + unknown(0) unknown transport address type + udpIpv4(1) transportDomainUdpIpv4 + udpIpv6(2) transportDomainUdpIpv6 + udpIpv4z(3) transportDomainUdpIpv4z + udpIpv6z(4) transportDomainUdpIpv6z + tcpIpv4(5) transportDomainTcpIpv4 + tcpIpv6(6) transportDomainTcpIpv6 + tcpIpv4z(7) transportDomainTcpIpv4z + + tcpIpv6z(8) transportDomainTcpIpv6z + sctpIpv4(9) transportDomainSctpIpv4 + sctpIpv6(10) transportDomainSctpIpv6 + sctpIpv4z(11) transportDomainSctpIpv4z + sctpIpv6z(12) transportDomainSctpIpv6z + local(13) transportDomainLocal + udpDns(14) transportDomainUdpDns + tcpDns(15) transportDomainTcpDns + sctpDns(16) transportDomainSctpDns + + This textual convention can be used to represent transport + domains in situations where a syntax of TransportDomain is + unwieldy (for example, when used as an index). + + The usage of this textual convention implies that additional + transport domains can only be supported by updating this MIB + module. This extensibility restriction does not apply for the + TransportDomain textual convention which allows MIB authors + to define additional transport domains independently in + other MIB modules." + SYNTAX INTEGER { + unknown(0), + udpIpv4(1), + udpIpv6(2), + udpIpv4z(3), + udpIpv6z(4), + tcpIpv4(5), + tcpIpv6(6), + tcpIpv4z(7), + tcpIpv6z(8), + sctpIpv4(9), + sctpIpv6(10), + sctpIpv4z(11), + sctpIpv6z(12), + local(13), + udpDns(14), + tcpDns(15), + sctpDns(16) + } + +TransportAddress ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Denotes a generic transport address. + + A TransportAddress value is always interpreted within the + context of a TransportAddressType or TransportDomain value. + Every usage of the TransportAddress textual convention MUST + + specify the TransportAddressType or TransportDomain object + which provides the context. Furthermore, MIB authors SHOULD + define a separate TransportAddressType or TransportDomain + object for each TransportAddress object. It is suggested that + the TransportAddressType or TransportDomain is logically + registered before the object(s) which use the + TransportAddress textual convention if they appear in the + same logical row. + + The value of a TransportAddress object must always be + consistent with the value of the associated + TransportAddressType or TransportDomain object. Attempts + to set a TransportAddress object to a value which is + inconsistent with the associated TransportAddressType or + TransportDomain must fail with an inconsistentValue error. + + When this textual convention is used as a syntax of an + index object, there may be issues with the limit of 128 + sub-identifiers specified in SMIv2, STD 58. In this case, + the OBJECT-TYPE declaration MUST include a 'SIZE' clause + to limit the number of potential instance sub-identifiers." + SYNTAX OCTET STRING (SIZE (0..255)) + +TransportAddressIPv4 ::= TEXTUAL-CONVENTION + DISPLAY-HINT "1d.1d.1d.1d:2d" + STATUS current + DESCRIPTION + "Represents a transport address consisting of an IPv4 + address and a port number (as used for example by UDP, + TCP and SCTP): + + octets contents encoding + 1-4 IPv4 address network-byte order + 5-6 port number network-byte order + + This textual convention SHOULD NOT be used directly in object + definitions since it restricts addresses to a specific format. + However, if it is used, it MAY be used either on its own or + in conjunction with TransportAddressType or TransportDomain + as a pair." + SYNTAX OCTET STRING (SIZE (6)) + +TransportAddressIPv6 ::= TEXTUAL-CONVENTION + DISPLAY-HINT "0a[2x:2x:2x:2x:2x:2x:2x:2x]0a:2d" + STATUS current + DESCRIPTION + "Represents a transport address consisting of an IPv6 + address and a port number (as used for example by UDP, + TCP and SCTP): + + octets contents encoding + 1-16 IPv6 address network-byte order + 17-18 port number network-byte order + + This textual convention SHOULD NOT be used directly in object + definitions since it restricts addresses to a specific format. + However, if it is used, it MAY be used either on its own or + in conjunction with TransportAddressType or TransportDomain + as a pair." + SYNTAX OCTET STRING (SIZE (18)) + +TransportAddressIPv4z ::= TEXTUAL-CONVENTION + DISPLAY-HINT "1d.1d.1d.1d%4d:2d" + STATUS current + DESCRIPTION + "Represents a transport address consisting of an IPv4 + address, a zone index and a port number (as used for + example by UDP, TCP and SCTP): + + octets contents encoding + 1-4 IPv4 address network-byte order + 5-8 zone index network-byte order + 9-10 port number network-byte order + + This textual convention SHOULD NOT be used directly in object + definitions since it restricts addresses to a specific format. + However, if it is used, it MAY be used either on its own or + in conjunction with TransportAddressType or TransportDomain + as a pair." + SYNTAX OCTET STRING (SIZE (10)) + +TransportAddressIPv6z ::= TEXTUAL-CONVENTION + DISPLAY-HINT "0a[2x:2x:2x:2x:2x:2x:2x:2x%4d]0a:2d" + STATUS current + DESCRIPTION + "Represents a transport address consisting of an IPv6 + address, a zone index and a port number (as used for + example by UDP, TCP and SCTP): + + octets contents encoding + 1-16 IPv6 address network-byte order + 17-20 zone index network-byte order + 21-22 port number network-byte order + + This textual convention SHOULD NOT be used directly in object + definitions since it restricts addresses to a specific format. + + However, if it is used, it MAY be used either on its own or + in conjunction with TransportAddressType or TransportDomain + as a pair." + SYNTAX OCTET STRING (SIZE (22)) + +TransportAddressLocal ::= TEXTUAL-CONVENTION + DISPLAY-HINT "1a" + STATUS current + DESCRIPTION + "Represents a POSIX Local IPC transport address: + + octets contents encoding + all POSIX Local IPC address string + + The Posix Local IPC transport domain subsumes UNIX domain + sockets. + + This textual convention SHOULD NOT be used directly in object + definitions since it restricts addresses to a specific format. + However, if it is used, it MAY be used either on its own or + in conjunction with TransportAddressType or TransportDomain + as a pair. + + When this textual convention is used as a syntax of an + index object, there may be issues with the limit of 128 + sub-identifiers specified in SMIv2, STD 58. In this case, + the OBJECT-TYPE declaration MUST include a 'SIZE' clause + to limit the number of potential instance sub-identifiers." + REFERENCE + "Protocol Independent Interfaces (IEEE POSIX 1003.1g)" + SYNTAX OCTET STRING (SIZE (1..255)) + +TransportAddressDns ::= TEXTUAL-CONVENTION + DISPLAY-HINT "1a" + STATUS current + DESCRIPTION + "Represents a DNS domain name followed by a colon ':' + (ASCII character 0x3A) and a port number in ASCII. + The name SHOULD be fully qualified whenever possible. + + Values of this textual convention are not directly useable as + transport-layer addressing information, and require runtime + resolution. As such, applications that write them must be + prepared for handling errors if such values are not + supported, or cannot be resolved (if resolution occurs at the + time of the management operation). + + The DESCRIPTION clause of TransportAddress objects that may + + have TransportAddressDns values must fully describe how (and + when) such names are to be resolved to IP addresses and vice + versa. + + This textual convention SHOULD NOT be used directly in object + definitions since it restricts addresses to a specific format. + However, if it is used, it MAY be used either on its own or + in conjunction with TransportAddressType or TransportDomain + as a pair. + + When this textual convention is used as a syntax of an + index object, there may be issues with the limit of 128 + sub-identifiers specified in SMIv2, STD 58. In this case, + the OBJECT-TYPE declaration MUST include a 'SIZE' clause + to limit the number of potential instance sub-identifiers." + SYNTAX OCTET STRING (SIZE (1..255)) + +END diff --git a/mibs/TUNNEL-MIB.txt b/mibs/TUNNEL-MIB.txt new file mode 100644 index 0000000..5f9596b --- /dev/null +++ b/mibs/TUNNEL-MIB.txt @@ -0,0 +1,738 @@ +TUNNEL-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, transmission, + Integer32, IpAddress FROM SNMPv2-SMI -- [RFC2578] + + RowStatus, StorageType FROM SNMPv2-TC -- [RFC2579] + + MODULE-COMPLIANCE, + OBJECT-GROUP FROM SNMPv2-CONF -- [RFC2580] + + InetAddressType, + InetAddress FROM INET-ADDRESS-MIB -- [RFC4001] + + IPv6FlowLabelOrAny FROM IPV6-FLOW-LABEL-MIB -- [RFC3595] + + ifIndex, + InterfaceIndexOrZero FROM IF-MIB -- [RFC2863] + + IANAtunnelType FROM IANAifType-MIB; -- [IFTYPE] + +tunnelMIB MODULE-IDENTITY + LAST-UPDATED "200505160000Z" -- May 16, 2005 + ORGANIZATION "IETF IP Version 6 (IPv6) Working Group" + CONTACT-INFO + " Dave Thaler + Microsoft Corporation + One Microsoft Way + Redmond, WA 98052-6399 + EMail: dthaler@microsoft.com" + DESCRIPTION + "The MIB module for management of IP Tunnels, + independent of the specific encapsulation scheme in + use. + + Copyright (C) The Internet Society (2005). This + version of this MIB module is part of RFC 4087; see + the RFC itself for full legal notices." + + REVISION "200505160000Z" -- May 16, 2005 + DESCRIPTION + "IPv4-specific objects were deprecated, including + tunnelIfLocalAddress, tunnelIfRemoteAddress, the + tunnelConfigTable, and the tunnelMIBBasicGroup. + + Added IP version-agnostic objects that should be used + instead, including tunnelIfAddressType, + tunnelIfLocalInetAddress, tunnelIfRemoteInetAddress, + the tunnelInetConfigTable, and the + tunnelIMIBInetGroup. + + The new tunnelIfLocalInetAddress and + tunnelIfRemoteInetAddress objects are read-write, + rather than read-only. + + Updated DESCRIPTION clauses of existing version- + agnostic objects (e.g., tunnelIfTOS) that contained + IPv4-specific text to cover IPv6 as well. + + Added tunnelIfFlowLabel for tunnels over IPv6. + + The encapsulation method was previously an INTEGER + type, and is now an IANA-maintained textual + convention. + + Published as RFC 4087." + REVISION "199908241200Z" -- August 24, 1999 + DESCRIPTION + "Initial version, published as RFC 2667." + ::= { transmission 131 } + +tunnelMIBObjects OBJECT IDENTIFIER ::= { tunnelMIB 1 } + +tunnel OBJECT IDENTIFIER ::= { tunnelMIBObjects 1 } + +-- the IP Tunnel MIB-Group +-- +-- a collection of objects providing information about +-- IP Tunnels + +tunnelIfTable OBJECT-TYPE + SYNTAX SEQUENCE OF TunnelIfEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The (conceptual) table containing information on + configured tunnels." + ::= { tunnel 1 } + +tunnelIfEntry OBJECT-TYPE + SYNTAX TunnelIfEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry (conceptual row) containing the information + on a particular configured tunnel." + INDEX { ifIndex } + ::= { tunnelIfTable 1 } + +TunnelIfEntry ::= SEQUENCE { + tunnelIfLocalAddress IpAddress, -- deprecated + tunnelIfRemoteAddress IpAddress, -- deprecated + tunnelIfEncapsMethod IANAtunnelType, + tunnelIfHopLimit Integer32, + tunnelIfSecurity INTEGER, + tunnelIfTOS Integer32, + tunnelIfFlowLabel IPv6FlowLabelOrAny, + tunnelIfAddressType InetAddressType, + tunnelIfLocalInetAddress InetAddress, + tunnelIfRemoteInetAddress InetAddress, + tunnelIfEncapsLimit Integer32 +} + +tunnelIfLocalAddress OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The address of the local endpoint of the tunnel + (i.e., the source address used in the outer IP + header), or 0.0.0.0 if unknown or if the tunnel is + over IPv6. + + Since this object does not support IPv6, it is + deprecated in favor of tunnelIfLocalInetAddress." + ::= { tunnelIfEntry 1 } + +tunnelIfRemoteAddress OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The address of the remote endpoint of the tunnel + (i.e., the destination address used in the outer IP + header), or 0.0.0.0 if unknown, or an IPv6 address, or + + the tunnel is not a point-to-point link (e.g., if it + is a 6to4 tunnel). + + Since this object does not support IPv6, it is + deprecated in favor of tunnelIfRemoteInetAddress." + ::= { tunnelIfEntry 2 } + +tunnelIfEncapsMethod OBJECT-TYPE + SYNTAX IANAtunnelType + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The encapsulation method used by the tunnel." + ::= { tunnelIfEntry 3 } + +tunnelIfHopLimit OBJECT-TYPE + SYNTAX Integer32 (0 | 1..255) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The IPv4 TTL or IPv6 Hop Limit to use in the outer IP + header. A value of 0 indicates that the value is + copied from the payload's header." + ::= { tunnelIfEntry 4 } + +tunnelIfSecurity OBJECT-TYPE + SYNTAX INTEGER { + none(1), -- no security + ipsec(2), -- IPsec security + other(3) + } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The method used by the tunnel to secure the outer IP + header. The value ipsec indicates that IPsec is used + between the tunnel endpoints for authentication or + encryption or both. More specific security-related + information may be available in a MIB module for the + security protocol in use." + ::= { tunnelIfEntry 5 } + +tunnelIfTOS OBJECT-TYPE + SYNTAX Integer32 (-2..63) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The method used to set the high 6 bits (the + + differentiated services codepoint) of the IPv4 TOS or + IPv6 Traffic Class in the outer IP header. A value of + -1 indicates that the bits are copied from the + payload's header. A value of -2 indicates that a + traffic conditioner is invoked and more information + may be available in a traffic conditioner MIB module. + A value between 0 and 63 inclusive indicates that the + bit field is set to the indicated value. + + Note: instead of the name tunnelIfTOS, a better name + would have been tunnelIfDSCPMethod, but the existing + name appeared in RFC 2667 and existing objects cannot + be renamed." + ::= { tunnelIfEntry 6 } + +tunnelIfFlowLabel OBJECT-TYPE + SYNTAX IPv6FlowLabelOrAny + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The method used to set the IPv6 Flow Label value. + This object need not be present in rows where + tunnelIfAddressType indicates the tunnel is not over + IPv6. A value of -1 indicates that a traffic + conditioner is invoked and more information may be + available in a traffic conditioner MIB. Any other + value indicates that the Flow Label field is set to + the indicated value." + ::= { tunnelIfEntry 7 } + +tunnelIfAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The type of address in the corresponding + tunnelIfLocalInetAddress and tunnelIfRemoteInetAddress + objects." + ::= { tunnelIfEntry 8 } + +tunnelIfLocalInetAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The address of the local endpoint of the tunnel + (i.e., the source address used in the outer IP + header). If the address is unknown, the value is + + 0.0.0.0 for IPv4 or :: for IPv6. The type of this + object is given by tunnelIfAddressType." + ::= { tunnelIfEntry 9 } + +tunnelIfRemoteInetAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The address of the remote endpoint of the tunnel + (i.e., the destination address used in the outer IP + header). If the address is unknown or the tunnel is + not a point-to-point link (e.g., if it is a 6to4 + tunnel), the value is 0.0.0.0 for tunnels over IPv4 or + :: for tunnels over IPv6. The type of this object is + given by tunnelIfAddressType." + ::= { tunnelIfEntry 10 } + +tunnelIfEncapsLimit OBJECT-TYPE + SYNTAX Integer32 (-1 | 0..255) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The maximum number of additional encapsulations + permitted for packets undergoing encapsulation at this + node. A value of -1 indicates that no limit is + present (except as a result of the packet size)." + REFERENCE "RFC 2473, section 4.1.1" + ::= { tunnelIfEntry 11 } + +tunnelConfigTable OBJECT-TYPE + SYNTAX SEQUENCE OF TunnelConfigEntry + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "The (conceptual) table containing information on + configured tunnels. This table can be used to map a + set of tunnel endpoints to the associated ifIndex + value. It can also be used for row creation. Note + that every row in the tunnelIfTable with a fixed IPv4 + destination address should have a corresponding row in + the tunnelConfigTable, regardless of whether it was + created via SNMP. + + Since this table does not support IPv6, it is + deprecated in favor of tunnelInetConfigTable." + ::= { tunnel 2 } + +tunnelConfigEntry OBJECT-TYPE + SYNTAX TunnelConfigEntry + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "An entry (conceptual row) containing the information + on a particular configured tunnel. + + Since this entry does not support IPv6, it is + deprecated in favor of tunnelInetConfigEntry." + INDEX { tunnelConfigLocalAddress, + tunnelConfigRemoteAddress, + tunnelConfigEncapsMethod, + tunnelConfigID } + ::= { tunnelConfigTable 1 } + +TunnelConfigEntry ::= SEQUENCE { + tunnelConfigLocalAddress IpAddress, + tunnelConfigRemoteAddress IpAddress, + tunnelConfigEncapsMethod IANAtunnelType, + tunnelConfigID Integer32, + tunnelConfigIfIndex InterfaceIndexOrZero, + tunnelConfigStatus RowStatus +} + +tunnelConfigLocalAddress OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "The address of the local endpoint of the tunnel, or + 0.0.0.0 if the device is free to choose any of its + addresses at tunnel establishment time. + + Since this object does not support IPv6, it is + deprecated in favor of tunnelInetConfigLocalAddress." + ::= { tunnelConfigEntry 1 } + +tunnelConfigRemoteAddress OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "The address of the remote endpoint of the tunnel. + + Since this object does not support IPv6, it is + deprecated in favor of tunnelInetConfigRemoteAddress." + ::= { tunnelConfigEntry 2 } + +tunnelConfigEncapsMethod OBJECT-TYPE + SYNTAX IANAtunnelType + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "The encapsulation method used by the tunnel. + + Since this object does not support IPv6, it is + deprecated in favor of tunnelInetConfigEncapsMethod." + ::= { tunnelConfigEntry 3 } + +tunnelConfigID OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "An identifier used to distinguish between multiple + tunnels of the same encapsulation method, with the + same endpoints. If the encapsulation protocol only + allows one tunnel per set of endpoint addresses (such + as for GRE or IP-in-IP), the value of this object is + 1. For encapsulation methods (such as L2F) which + allow multiple parallel tunnels, the manager is + responsible for choosing any ID which does not + conflict with an existing row, such as choosing a + random number. + + Since this object does not support IPv6, it is + deprecated in favor of tunnelInetConfigID." + ::= { tunnelConfigEntry 4 } + +tunnelConfigIfIndex OBJECT-TYPE + SYNTAX InterfaceIndexOrZero + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "If the value of tunnelConfigStatus for this row is + active, then this object contains the value of ifIndex + corresponding to the tunnel interface. A value of 0 + is not legal in the active state, and means that the + interface index has not yet been assigned. + + Since this object does not support IPv6, it is + deprecated in favor of tunnelInetConfigIfIndex." + ::= { tunnelConfigEntry 5 } + +tunnelConfigStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS deprecated + DESCRIPTION + "The status of this row, by which new entries may be + created, or old entries deleted from this table. The + agent need not support setting this object to + createAndWait or notInService since there are no other + writable objects in this table, and writable objects + in rows of corresponding tables such as the + tunnelIfTable may be modified while this row is + active. + + To create a row in this table for an encapsulation + method which does not support multiple parallel + tunnels with the same endpoints, the management + station should simply use a tunnelConfigID of 1, and + set tunnelConfigStatus to createAndGo. For + encapsulation methods such as L2F which allow multiple + parallel tunnels, the management station may select a + pseudo-random number to use as the tunnelConfigID and + set tunnelConfigStatus to createAndGo. In the event + that this ID is already in use and an + inconsistentValue is returned in response to the set + operation, the management station should simply select + a new pseudo-random number and retry the operation. + + Creating a row in this table will cause an interface + index to be assigned by the agent in an + implementation-dependent manner, and corresponding + rows will be instantiated in the ifTable and the + tunnelIfTable. The status of this row will become + active as soon as the agent assigns the interface + index, regardless of whether the interface is + operationally up. + + Deleting a row in this table will likewise delete the + corresponding row in the ifTable and in the + tunnelIfTable. + + Since this object does not support IPv6, it is + deprecated in favor of tunnelInetConfigStatus." + ::= { tunnelConfigEntry 6 } + +tunnelInetConfigTable OBJECT-TYPE + SYNTAX SEQUENCE OF TunnelInetConfigEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The (conceptual) table containing information on + configured tunnels. This table can be used to map a + set of tunnel endpoints to the associated ifIndex + value. It can also be used for row creation. Note + that every row in the tunnelIfTable with a fixed + destination address should have a corresponding row in + the tunnelInetConfigTable, regardless of whether it + was created via SNMP." + ::= { tunnel 3 } + +tunnelInetConfigEntry OBJECT-TYPE + SYNTAX TunnelInetConfigEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry (conceptual row) containing the information + on a particular configured tunnel. Note that there is + a 128 subid maximum for object OIDs. Implementers + need to be aware that if the total number of octets in + tunnelInetConfigLocalAddress and + tunnelInetConfigRemoteAddress exceeds 110 then OIDs of + column instances in this table will have more than 128 + sub-identifiers and cannot be accessed using SNMPv1, + SNMPv2c, or SNMPv3. In practice this is not expected + to be a problem since IPv4 and IPv6 addresses will not + cause the limit to be reached, but if other types are + supported by an agent, care must be taken to ensure + that the sum of the lengths do not cause the limit to + be exceeded." + INDEX { tunnelInetConfigAddressType, + tunnelInetConfigLocalAddress, + tunnelInetConfigRemoteAddress, + tunnelInetConfigEncapsMethod, + tunnelInetConfigID } + ::= { tunnelInetConfigTable 1 } + +TunnelInetConfigEntry ::= SEQUENCE { + tunnelInetConfigAddressType InetAddressType, + tunnelInetConfigLocalAddress InetAddress, + tunnelInetConfigRemoteAddress InetAddress, + tunnelInetConfigEncapsMethod IANAtunnelType, + tunnelInetConfigID Integer32, + tunnelInetConfigIfIndex InterfaceIndexOrZero, + tunnelInetConfigStatus RowStatus, + tunnelInetConfigStorageType StorageType +} + +tunnelInetConfigAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The address type over which the tunnel encapsulates + packets." + ::= { tunnelInetConfigEntry 1 } + +tunnelInetConfigLocalAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The address of the local endpoint of the tunnel, or + 0.0.0.0 (for IPv4) or :: (for IPv6) if the device is + free to choose any of its addresses at tunnel + establishment time." + ::= { tunnelInetConfigEntry 2 } + +tunnelInetConfigRemoteAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The address of the remote endpoint of the tunnel." + ::= { tunnelInetConfigEntry 3 } + +tunnelInetConfigEncapsMethod OBJECT-TYPE + SYNTAX IANAtunnelType + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The encapsulation method used by the tunnel." + ::= { tunnelInetConfigEntry 4 } + +tunnelInetConfigID OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An identifier used to distinguish between multiple + tunnels of the same encapsulation method, with the + same endpoints. If the encapsulation protocol only + allows one tunnel per set of endpoint addresses (such + as for GRE or IP-in-IP), the value of this object is + 1. For encapsulation methods (such as L2F) which + allow multiple parallel tunnels, the manager is + responsible for choosing any ID which does not + + conflict with an existing row, such as choosing a + random number." + ::= { tunnelInetConfigEntry 5 } + +tunnelInetConfigIfIndex OBJECT-TYPE + SYNTAX InterfaceIndexOrZero + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "If the value of tunnelInetConfigStatus for this row + is active, then this object contains the value of + ifIndex corresponding to the tunnel interface. A + value of 0 is not legal in the active state, and means + that the interface index has not yet been assigned." + ::= { tunnelInetConfigEntry 6 } + +tunnelInetConfigStatus OBJECT-TYPE + SYNTAX RowStatus + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The status of this row, by which new entries may be + created, or old entries deleted from this table. The + agent need not support setting this object to + createAndWait or notInService since there are no other + writable objects in this table, and writable objects + in rows of corresponding tables such as the + tunnelIfTable may be modified while this row is + active. + + To create a row in this table for an encapsulation + method which does not support multiple parallel + tunnels with the same endpoints, the management + station should simply use a tunnelInetConfigID of 1, + and set tunnelInetConfigStatus to createAndGo. For + encapsulation methods such as L2F which allow multiple + parallel tunnels, the management station may select a + pseudo-random number to use as the tunnelInetConfigID + and set tunnelInetConfigStatus to createAndGo. In the + event that this ID is already in use and an + inconsistentValue is returned in response to the set + operation, the management station should simply select + a new pseudo-random number and retry the operation. + + Creating a row in this table will cause an interface + index to be assigned by the agent in an + implementation-dependent manner, and corresponding + rows will be instantiated in the ifTable and the + + tunnelIfTable. The status of this row will become + active as soon as the agent assigns the interface + index, regardless of whether the interface is + operationally up. + + Deleting a row in this table will likewise delete the + corresponding row in the ifTable and in the + tunnelIfTable." + ::= { tunnelInetConfigEntry 7 } + +tunnelInetConfigStorageType OBJECT-TYPE + SYNTAX StorageType + MAX-ACCESS read-create + STATUS current + DESCRIPTION + "The storage type of this row. If the row is + permanent(4), no objects in the row need be writable." + ::= { tunnelInetConfigEntry 8 } + +-- conformance information + +tunnelMIBConformance + OBJECT IDENTIFIER ::= { tunnelMIB 2 } +tunnelMIBCompliances + OBJECT IDENTIFIER ::= { tunnelMIBConformance 1 } +tunnelMIBGroups OBJECT IDENTIFIER ::= { tunnelMIBConformance 2 } + +-- compliance statements + +tunnelMIBCompliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION + "The (deprecated) IPv4-only compliance statement for + the IP Tunnel MIB. + + This is deprecated in favor of + tunnelMIBInetFullCompliance and + tunnelMIBInetReadOnlyCompliance." + MODULE -- this module + MANDATORY-GROUPS { tunnelMIBBasicGroup } + + OBJECT tunnelIfHopLimit + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT tunnelIfTOS + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT tunnelConfigStatus + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + ::= { tunnelMIBCompliances 1 } + +tunnelMIBInetFullCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The full compliance statement for the IP Tunnel MIB." + MODULE -- this module + MANDATORY-GROUPS { tunnelMIBInetGroup } + + OBJECT tunnelIfAddressType + SYNTAX InetAddressType { ipv4(1), ipv6(2), + ipv4z(3), ipv6z(4) } + DESCRIPTION + "An implementation is only required to support IPv4 + and/or IPv6 addresses. An implementation only needs to + support the addresses it actually supports on the + device." + ::= { tunnelMIBCompliances 2 } + +tunnelMIBInetReadOnlyCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The read-only compliance statement for the IP Tunnel + MIB." + MODULE -- this module + MANDATORY-GROUPS { tunnelMIBInetGroup } + + OBJECT tunnelIfHopLimit + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT tunnelIfTOS + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT tunnelIfFlowLabel + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT tunnelIfAddressType + SYNTAX InetAddressType { ipv4(1), ipv6(2), + ipv4z(3), ipv6z(4) } + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required. + + An implementation is only required to support IPv4 + and/or IPv6 addresses. An implementation only needs to + support the addresses it actually supports on the + device." + + OBJECT tunnelIfLocalInetAddress + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT tunnelIfRemoteInetAddress + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT tunnelIfEncapsLimit + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + + OBJECT tunnelInetConfigStatus + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required, and active is the only + status that needs to be supported." + + OBJECT tunnelInetConfigStorageType + MIN-ACCESS read-only + DESCRIPTION + "Write access is not required." + ::= { tunnelMIBCompliances 3 } + +-- units of conformance + +tunnelMIBBasicGroup OBJECT-GROUP + OBJECTS { tunnelIfLocalAddress, tunnelIfRemoteAddress, + tunnelIfEncapsMethod, tunnelIfHopLimit, tunnelIfTOS, + tunnelIfSecurity, tunnelConfigIfIndex, tunnelConfigStatus } + STATUS deprecated + DESCRIPTION + "A collection of objects to support basic management + + of IPv4 Tunnels. Since this group cannot support + IPv6, it is deprecated in favor of + tunnelMIBInetGroup." + ::= { tunnelMIBGroups 1 } + +tunnelMIBInetGroup OBJECT-GROUP + OBJECTS { tunnelIfAddressType, tunnelIfLocalInetAddress, + tunnelIfRemoteInetAddress, tunnelIfEncapsMethod, + tunnelIfEncapsLimit, + tunnelIfHopLimit, tunnelIfTOS, tunnelIfFlowLabel, + tunnelIfSecurity, tunnelInetConfigIfIndex, + tunnelInetConfigStatus, tunnelInetConfigStorageType } + STATUS current + DESCRIPTION + "A collection of objects to support basic management + of IPv4 and IPv6 Tunnels." + ::= { tunnelMIBGroups 2 } + +END diff --git a/mibs/UDP-MIB.txt b/mibs/UDP-MIB.txt new file mode 100644 index 0000000..eec9dba --- /dev/null +++ b/mibs/UDP-MIB.txt @@ -0,0 +1,549 @@ +UDP-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, Integer32, Counter32, Counter64, + Unsigned32, IpAddress, mib-2 FROM SNMPv2-SMI + MODULE-COMPLIANCE, OBJECT-GROUP FROM SNMPv2-CONF + InetAddress, InetAddressType, + InetPortNumber FROM INET-ADDRESS-MIB; + +udpMIB MODULE-IDENTITY + LAST-UPDATED "200505200000Z" -- May 20, 2005 + ORGANIZATION + "IETF IPv6 Working Group + http://www.ietf.org/html.charters/ipv6-charter.html" + CONTACT-INFO + "Bill Fenner (editor) + + AT&T Labs -- Research + 75 Willow Rd. + Menlo Park, CA 94025 + + Phone: +1 650 330-7893 + Email: <fenner@research.att.com> + + John Flick (editor) + + Hewlett-Packard Company + 8000 Foothills Blvd. M/S 5557 + Roseville, CA 95747 + + Phone: +1 916 785 4018 + Email: <john.flick@hp.com> + + Send comments to <ipv6@ietf.org>" + DESCRIPTION + "The MIB module for managing UDP implementations. + Copyright (C) The Internet Society (2005). This + version of this MIB module is part of RFC 4113; + see the RFC itself for full legal notices." + REVISION "200505200000Z" -- May 20, 2005 + DESCRIPTION + "IP version neutral revision, incorporating the + following revisions: + + - Added udpHCInDatagrams and udpHCOutDatagrams in order + to provide high-capacity counters for fast networks. + - Added text to the descriptions of all counter objects + to indicate how discontinuities are detected. + - Deprecated the IPv4-specific udpTable and replaced it + with the version neutral udpEndpointTable. This + table includes support for connected UDP endpoints + and support for identification of the operating + system process associated with a UDP endpoint. + - Deprecated the udpGroup and replaced it with object + groups representing the current set of objects. + - Deprecated udpMIBCompliance and replaced it with + udpMIBCompliance2, which includes the compliance + information for the new object groups. + + This version published as RFC 4113." + REVISION "199411010000Z" -- November 1, 1994 + DESCRIPTION + "Initial SMIv2 version, published as RFC 2013." + REVISION "199103310000Z" -- March 31, 1991 + DESCRIPTION + "The initial revision of this MIB module was part of + MIB-II, published as RFC 1213." + ::= { mib-2 50 } + +-- the UDP group + +udp OBJECT IDENTIFIER ::= { mib-2 7 } + +udpInDatagrams OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of UDP datagrams delivered to UDP + users. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by discontinuities in the + value of sysUpTime." + ::= { udp 1 } + +udpNoPorts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of received UDP datagrams for which + there was no application at the destination port. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by discontinuities in the + value of sysUpTime." + ::= { udp 2 } + +udpInErrors OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The number of received UDP datagrams that could not be + delivered for reasons other than the lack of an + application at the destination port. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by discontinuities in the + value of sysUpTime." + ::= { udp 3 } + +udpOutDatagrams OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of UDP datagrams sent from this + entity. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by discontinuities in the + value of sysUpTime." + ::= { udp 4 } + +udpHCInDatagrams OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of UDP datagrams delivered to UDP + users, for devices that can receive more than 1 + million UDP datagrams per second. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by discontinuities in the + value of sysUpTime." + ::= { udp 8 } + +udpHCOutDatagrams OBJECT-TYPE + SYNTAX Counter64 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of UDP datagrams sent from this + entity, for devices that can transmit more than 1 + million UDP datagrams per second. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by discontinuities in the + value of sysUpTime." + ::= { udp 9 } + +-- +-- { udp 6 } was defined as the ipv6UdpTable in RFC2454's +-- IPV6-UDP-MIB. This RFC obsoletes RFC 2454, so { udp 6 } is +-- obsoleted. +-- + +-- The UDP "Endpoint" table. + +udpEndpointTable OBJECT-TYPE + SYNTAX SEQUENCE OF UdpEndpointEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table containing information about this entity's UDP + endpoints on which a local application is currently + accepting or sending datagrams. + + The address type in this table represents the address + type used for the communication, irrespective of the + higher-layer abstraction. For example, an application + using IPv6 'sockets' to communicate via IPv4 between + ::ffff:10.0.0.1 and ::ffff:10.0.0.2 would use + InetAddressType ipv4(1). + + Unlike the udpTable in RFC 2013, this table also allows + the representation of an application that completely + specifies both local and remote addresses and ports. A + listening application is represented in three possible + ways: + + 1) An application that is willing to accept both IPv4 + and IPv6 datagrams is represented by a + udpEndpointLocalAddressType of unknown(0) and a + udpEndpointLocalAddress of ''h (a zero-length + octet-string). + + 2) An application that is willing to accept only IPv4 + or only IPv6 datagrams is represented by a + udpEndpointLocalAddressType of the appropriate + address type and a udpEndpointLocalAddress of + '0.0.0.0' or '::' respectively. + + 3) An application that is listening for datagrams only + for a specific IP address but from any remote + system is represented by a + udpEndpointLocalAddressType of the appropriate + address type, with udpEndpointLocalAddress + specifying the local address. + + In all cases where the remote is a wildcard, the + udpEndpointRemoteAddressType is unknown(0), the + udpEndpointRemoteAddress is ''h (a zero-length + octet-string), and the udpEndpointRemotePort is 0. + + If the operating system is demultiplexing UDP packets + by remote address and port, or if the application has + 'connected' the socket specifying a default remote + address and port, the udpEndpointRemote* values should + be used to reflect this." + ::= { udp 7 } + +udpEndpointEntry OBJECT-TYPE + SYNTAX UdpEndpointEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Information about a particular current UDP endpoint. + + Implementers need to be aware that if the total number + of elements (octets or sub-identifiers) in + udpEndpointLocalAddress and udpEndpointRemoteAddress + exceeds 111, then OIDs of column instances in this table + will have more than 128 sub-identifiers and cannot be + accessed using SNMPv1, SNMPv2c, or SNMPv3." + INDEX { udpEndpointLocalAddressType, + udpEndpointLocalAddress, + udpEndpointLocalPort, + udpEndpointRemoteAddressType, + udpEndpointRemoteAddress, + udpEndpointRemotePort, + udpEndpointInstance } + ::= { udpEndpointTable 1 } + +UdpEndpointEntry ::= SEQUENCE { + udpEndpointLocalAddressType InetAddressType, + udpEndpointLocalAddress InetAddress, + udpEndpointLocalPort InetPortNumber, + udpEndpointRemoteAddressType InetAddressType, + udpEndpointRemoteAddress InetAddress, + udpEndpointRemotePort InetPortNumber, + udpEndpointInstance Unsigned32, + udpEndpointProcess Unsigned32 + } + +udpEndpointLocalAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The address type of udpEndpointLocalAddress. Only + IPv4, IPv4z, IPv6, and IPv6z addresses are expected, or + unknown(0) if datagrams for all local IP addresses are + accepted." + ::= { udpEndpointEntry 1 } + +udpEndpointLocalAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The local IP address for this UDP endpoint. + + The value of this object can be represented in three + + possible ways, depending on the characteristics of the + listening application: + + 1. For an application that is willing to accept both + IPv4 and IPv6 datagrams, the value of this object + must be ''h (a zero-length octet-string), with + the value of the corresponding instance of the + udpEndpointLocalAddressType object being unknown(0). + + 2. For an application that is willing to accept only IPv4 + or only IPv6 datagrams, the value of this object + must be '0.0.0.0' or '::', respectively, while the + corresponding instance of the + udpEndpointLocalAddressType object represents the + appropriate address type. + + 3. For an application that is listening for data + destined only to a specific IP address, the value + of this object is the specific IP address for which + this node is receiving packets, with the + corresponding instance of the + udpEndpointLocalAddressType object representing the + appropriate address type. + + As this object is used in the index for the + udpEndpointTable, implementors of this table should be + careful not to create entries that would result in OIDs + with more than 128 subidentifiers; else the information + cannot be accessed using SNMPv1, SNMPv2c, or SNMPv3." + ::= { udpEndpointEntry 2 } + +udpEndpointLocalPort OBJECT-TYPE + SYNTAX InetPortNumber + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The local port number for this UDP endpoint." + ::= { udpEndpointEntry 3 } + +udpEndpointRemoteAddressType OBJECT-TYPE + SYNTAX InetAddressType + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The address type of udpEndpointRemoteAddress. Only + IPv4, IPv4z, IPv6, and IPv6z addresses are expected, or + unknown(0) if datagrams for all remote IP addresses are + accepted. Also, note that some combinations of + + udpEndpointLocalAdressType and + udpEndpointRemoteAddressType are not supported. In + particular, if the value of this object is not + unknown(0), it is expected to always refer to the + same IP version as udpEndpointLocalAddressType." + ::= { udpEndpointEntry 4 } + +udpEndpointRemoteAddress OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The remote IP address for this UDP endpoint. If + datagrams from any remote system are to be accepted, + this value is ''h (a zero-length octet-string). + Otherwise, it has the type described by + udpEndpointRemoteAddressType and is the address of the + remote system from which datagrams are to be accepted + (or to which all datagrams will be sent). + + As this object is used in the index for the + udpEndpointTable, implementors of this table should be + careful not to create entries that would result in OIDs + with more than 128 subidentifiers; else the information + cannot be accessed using SNMPv1, SNMPv2c, or SNMPv3." + ::= { udpEndpointEntry 5 } + +udpEndpointRemotePort OBJECT-TYPE + SYNTAX InetPortNumber + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The remote port number for this UDP endpoint. If + datagrams from any remote system are to be accepted, + this value is zero." + ::= { udpEndpointEntry 6 } + +udpEndpointInstance OBJECT-TYPE + SYNTAX Unsigned32 (1..'ffffffff'h) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The instance of this tuple. This object is used to + distinguish among multiple processes 'connected' to + the same UDP endpoint. For example, on a system + implementing the BSD sockets interface, this would be + used to support the SO_REUSEADDR and SO_REUSEPORT + socket options." + ::= { udpEndpointEntry 7 } + +udpEndpointProcess OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The system's process ID for the process associated with + this endpoint, or zero if there is no such process. + This value is expected to be the same as + HOST-RESOURCES-MIB::hrSWRunIndex or SYSAPPL-MIB:: + sysApplElmtRunIndex for some row in the appropriate + tables." + ::= { udpEndpointEntry 8 } + +-- The deprecated UDP Listener table + +-- The deprecated UDP listener table only contains information +-- about this entity's IPv4 UDP end-points on which a local +-- application is currently accepting datagrams. It does not +-- provide more detailed connection information, or information +-- about IPv6 endpoints. + +udpTable OBJECT-TYPE + SYNTAX SEQUENCE OF UdpEntry + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "A table containing IPv4-specific UDP listener + information. It contains information about all local + IPv4 UDP end-points on which an application is + currently accepting datagrams. This table has been + deprecated in favor of the version neutral + udpEndpointTable." + ::= { udp 5 } + +udpEntry OBJECT-TYPE + SYNTAX UdpEntry + MAX-ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "Information about a particular current UDP listener." + INDEX { udpLocalAddress, udpLocalPort } + ::= { udpTable 1 } + +UdpEntry ::= SEQUENCE { + udpLocalAddress IpAddress, + udpLocalPort Integer32 + +} + +udpLocalAddress OBJECT-TYPE + SYNTAX IpAddress + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The local IP address for this UDP listener. In the + case of a UDP listener that is willing to accept + datagrams for any IP interface associated with the + node, the value 0.0.0.0 is used." + ::= { udpEntry 1 } + +udpLocalPort OBJECT-TYPE + SYNTAX Integer32 (0..65535) + MAX-ACCESS read-only + STATUS deprecated + DESCRIPTION + "The local port number for this UDP listener." + ::= { udpEntry 2 } + +-- conformance information + +udpMIBConformance OBJECT IDENTIFIER ::= { udpMIB 2 } +udpMIBCompliances OBJECT IDENTIFIER ::= { udpMIBConformance 1 } +udpMIBGroups OBJECT IDENTIFIER ::= { udpMIBConformance 2 } + +-- compliance statements + +udpMIBCompliance2 MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for systems that implement + UDP. + + There are a number of INDEX objects that cannot be + represented in the form of OBJECT clauses in SMIv2, but + for which we have the following compliance + requirements, expressed in OBJECT clause form in this + description clause: + + -- OBJECT udpEndpointLocalAddressType + -- SYNTAX InetAddressType { unknown(0), ipv4(1), + -- ipv6(2), ipv4z(3), + -- ipv6z(4) } + -- DESCRIPTION + -- Support for dns(5) is not required. + -- OBJECT udpEndpointLocalAddress + + -- SYNTAX InetAddress (SIZE(0|4|8|16|20)) + -- DESCRIPTION + -- Support is only required for zero-length + -- octet-strings, and for scoped and unscoped + -- IPv4 and IPv6 addresses. + -- OBJECT udpEndpointRemoteAddressType + -- SYNTAX InetAddressType { unknown(0), ipv4(1), + -- ipv6(2), ipv4z(3), + -- ipv6z(4) } + -- DESCRIPTION + -- Support for dns(5) is not required. + -- OBJECT udpEndpointRemoteAddress + -- SYNTAX InetAddress (SIZE(0|4|8|16|20)) + -- DESCRIPTION + -- Support is only required for zero-length + -- octet-strings, and for scoped and unscoped + -- IPv4 and IPv6 addresses. + " + MODULE -- this module + MANDATORY-GROUPS { udpBaseGroup, udpEndpointGroup } + GROUP udpHCGroup + DESCRIPTION + "This group is mandatory for systems that + are capable of receiving or transmitting more than + 1 million UDP datagrams per second. 1 million + datagrams per second will cause a Counter32 to + wrap in just over an hour." + ::= { udpMIBCompliances 2 } + +udpMIBCompliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION + "The compliance statement for IPv4-only systems that + implement UDP. For IP version independence, this + compliance statement is deprecated in favor of + udpMIBCompliance2. However, agents are still + encouraged to implement these objects in order to + interoperate with the deployed base of managers." + MODULE -- this module + MANDATORY-GROUPS { udpGroup } + ::= { udpMIBCompliances 1 } + +-- units of conformance + +udpGroup OBJECT-GROUP + OBJECTS { udpInDatagrams, udpNoPorts, + udpInErrors, udpOutDatagrams, + udpLocalAddress, udpLocalPort } + STATUS deprecated + DESCRIPTION + "The deprecated group of objects providing for + management of UDP over IPv4." + ::= { udpMIBGroups 1 } + +udpBaseGroup OBJECT-GROUP + OBJECTS { udpInDatagrams, udpNoPorts, udpInErrors, + udpOutDatagrams } + STATUS current + DESCRIPTION + "The group of objects providing for counters of UDP + statistics." + ::= { udpMIBGroups 2 } + +udpHCGroup OBJECT-GROUP + OBJECTS { udpHCInDatagrams, udpHCOutDatagrams } + STATUS current + DESCRIPTION + "The group of objects providing for counters of high + speed UDP implementations." + ::= { udpMIBGroups 3 } + +udpEndpointGroup OBJECT-GROUP + OBJECTS { udpEndpointProcess } + STATUS current + DESCRIPTION + "The group of objects providing for the IP version + independent management of UDP 'endpoints'." + ::= { udpMIBGroups 4 } + +END diff --git a/mibs/VYATTA-TRAP-MIB.txt b/mibs/VYATTA-TRAP-MIB.txt new file mode 100644 index 0000000..9983c29 --- /dev/null +++ b/mibs/VYATTA-TRAP-MIB.txt @@ -0,0 +1,97 @@ +VYATTA-TRAP-MIB DEFINITIONS ::= BEGIN + + IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE, enterprises + FROM SNMPv2-SMI + MODULE-COMPLIANCE, OBJECT-GROUP + FROM SNMPv2-CONF + ; + + vyattaTrap MODULE-IDENTITY + LAST-UPDATED "201305060000Z" -- May 6, 2013 + ORGANIZATION "Vyatta, A Brocade Company" + CONTACT-INFO + " Support + Postal: Vyatta, A Brocade Company + 1301 Shoreway Road Suite 200 + Belmont, California 94002 + Tel: +1 650 413 7200 + E-Mail: support@vyatta.com" + DESCRIPTION + "The MIB module to describe traps for the Vyatta + Router." + ::= { enterprises 30803 1 } + + +-- Trap Support Objects + +mgmtTrap OBJECT IDENTIFIER ::= { vyattaTrap 1 } +mgmtEventObjects OBJECT IDENTIFIER ::= { mgmtTrap 1 } +mgmtEvent OBJECT IDENTIFIER ::= { mgmtTrap 2 } + +mgmtEventUser OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The user that initiated the event the trap is reporting." + ::= { mgmtEventObjects 1 } + + +mgmtEventSource OBJECT-TYPE + SYNTAX INTEGER { + unknown (0), + firewall (1) } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The source of the event the trap is reporting." + ::= { mgmtEventObjects 2 } + + +mgmtEventType OBJECT-TYPE + SYNTAX INTEGER { + unknown (0), + added (1), + deleted (2), + changed (3) } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The event type the trap is reporting." + ::= { mgmtEventObjects 3 } + + +mgmtEventPrevCfg OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The previous configuration. This field is only valid + for mgmtEventType deleted and changed." + ::= { mgmtEventObjects 4 } + +mgmtEventCurrCfg OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current configuration. This field is only valid + for mgmtEventType added and changed." + ::= { mgmtEventObjects 5 } + + +-- Traps + + mgmtEventTrap NOTIFICATION-TYPE + OBJECTS { mgmtEventUser, + mgmtEventSource, + mgmtEventType, + mgmtEventPrevCfg, + mgmtEventCurrCfg } + STATUS current + DESCRIPTION + "Notification of a configuration related event." + ::= { mgmtEvent 1 } + +END diff --git a/op-mode-definitions/clear-interfaces.xml.in b/op-mode-definitions/clear-interfaces.xml.in new file mode 100644 index 0000000..de2c344 --- /dev/null +++ b/op-mode-definitions/clear-interfaces.xml.in @@ -0,0 +1,614 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="clear"> + <properties> + <help>Clear system information</help> + </properties> + <children> + <node name="interfaces"> + <properties> + <help>Clear interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear interface counters for all interfaces</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters</command> + </node> + <tagNode name="connection"> + <properties> + <help>Bring connection-oriented network interface down and up</help> + <completionHelp> + <path>interfaces pppoe</path> + <path>interfaces sstpc</path> + <path>interfaces wwan</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/connect_disconnect.py --connect --disconnect --interface "$3"</command> + </tagNode> + <node name="bonding"> + <properties> + <help>Clear Bonding interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all bonding interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="bonding"> + <properties> + <help>Clear interface information for a given bonding interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type bonding</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given bonding interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="bridge"> + <properties> + <help>Clear Bridge interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all bridge interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="bridge"> + <properties> + <help>Clear interface information for a given bridge interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type bridge</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given bridge interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="dummy"> + <properties> + <help>Clear Dummy interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all dummy interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="dummy"> + <properties> + <help>Clear interface information for a given dummy interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type dummy</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given dummy interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="ethernet"> + <properties> + <help>Clear Ethernet interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all ethernet interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="ethernet"> + <properties> + <help>Clear interface information for a given ethernet interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type ethernet</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given ethernet interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="geneve"> + <properties> + <help>Clear GENEVE interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all GENEVE interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="geneve"> + <properties> + <help>Clear interface information for a given GENEVE interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type geneve</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given GENEVE interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="input"> + <properties> + <help>Clear Input (ifb) interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all Input interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="input"> + <properties> + <help>Clear interface information for a given Input interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type input</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given Input interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="l2tpv3"> + <properties> + <help>Clear L2TPv3 interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all L2TPv3 interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="l2tpv3"> + <properties> + <help>Clear interface information for a given L2TPv3 interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type l2tpeth</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given L2TPv3 interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="loopback"> + <properties> + <help>Clear Loopback interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all loopback interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="loopback"> + <properties> + <help>Clear interface information for a given loopback interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type loopback</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given loopback interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="macsec"> + <properties> + <help>Clear MACsec interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all MACsec interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="macsec"> + <properties> + <help>Clear interface information for a given MACsec interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type macsec</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given MACsec interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="openvpn"> + <properties> + <help>Clear OpenVPN interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all OpenVPN interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="openvpn"> + <properties> + <help>Clear interface information for a given OpenVPN interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type openvpn</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given OpenVPN interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="pppoe"> + <properties> + <help>Clear PPPoE interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all PPPoE interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="pppoe"> + <properties> + <help>Clear interface information for a given PPPoE interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type pppoe</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given PPPoE interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="pseudo-ethernet"> + <properties> + <help>Clear Pseudo-Ethernet/MACvlan interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all Pseudo-Ethernet interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="pseudo-ethernet"> + <properties> + <help>Clear interface information for a given Pseudo-Ethernet interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type pseudo-ethernet</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given Pseudo-Ethernet interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="sstp"> + <properties> + <help>Clear SSTP interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all SSTP interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="sstp"> + <properties> + <help>Clear interface information for a given SSTP interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type sstp</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given SSTP interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="tunnel"> + <properties> + <help>Clear Tunnel interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all tunnel interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="tunnel"> + <properties> + <help>Clear interface information for a given tunnel interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type tunnel</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given tunnel interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="virtual-ethernet"> + <properties> + <help>Clear virtual-ethernet interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all virtual-ethernet interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="virtual-ethernet"> + <properties> + <help>Clear interface information for a given virtual-ethernet interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type virtual-ethernet</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given virtual-ethernet interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="vti"> + <properties> + <help>Clear VTI interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all VTI interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="vti"> + <properties> + <help>Clear interface information for a given VTI interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type vti</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given VTI interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="vxlan"> + <properties> + <help>Clear VXLAN interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all VXLAN interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="vxlan"> + <properties> + <help>Clear interface information for a given VXLAN interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type vxlan</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given VXLAN interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="wireguard"> + <properties> + <help>Clear Wireguard interface information</help> + </properties> + <children> + <node name="counters"> + <properties> + <help>Clear all Wireguard interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </node> + </children> + </node> + <tagNode name="wireguard"> + <properties> + <help>Clear interface information for a given Wireguard interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type wireguard</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear interface counters for a given Wireguard interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="wireless"> + <properties> + <help>Clear Wireless (WLAN) interface information</help> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear all wireless interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </leafNode> + </children> + </node> + <tagNode name="wireless"> + <properties> + <help>Clear interface information for a given wireless interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type wireless</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear counters for a given wireless interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + <node name="wwan"> + <properties> + <help>Clear Wireless Modem (WWAN) interface information</help> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear all WWAN interface counters</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-type "$3"</command> + </leafNode> + </children> + </node> + <tagNode name="wwan"> + <properties> + <help>Clear interface information for a given WWAN interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type wwan</script> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear counters for a given WWAN interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces.py clear_counters --intf-name "$4"</command> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/clear-ip.xml.in b/op-mode-definitions/clear-ip.xml.in new file mode 100644 index 0000000..3c75ed1 --- /dev/null +++ b/op-mode-definitions/clear-ip.xml.in @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="clear"> + <children> + <node name="ip"> + <properties> + <help>Clear Internet Protocol (IP) statistics or status</help> + </properties> + <children> + <node name="prefix-list"> + <properties> + <help>Clear prefix-list statistics or status</help> + </properties> + <command>vtysh -c "$*"</command> + </node> + <tagNode name="prefix-list"> + <properties> + <help>Clear prefix-list statistics or status for specified word</help> + <completionHelp> + <list><WORD></list> + </completionHelp> + </properties> + <command>vtysh -c "$*"</command> + <children> + <leafNode name="node.tag"> + <properties> + <help>Clear prefix-list statistics or status for given word|network</help> + <completionHelp> + <list><x.x.x.x/x></list> + </completionHelp> + </properties> + <command>vtysh -c "$*"</command> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/clear-ipv6.xml.in b/op-mode-definitions/clear-ipv6.xml.in new file mode 100644 index 0000000..c062102 --- /dev/null +++ b/op-mode-definitions/clear-ipv6.xml.in @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="clear"> + <children> + <node name="ipv6"> + <properties> + <help>Clear Internet Protocol (IPv6) statistics or status</help> + </properties> + <children> + <node name="prefix-list"> + <properties> + <help>Clear prefix-list statistics or status</help> + </properties> + <command>vtysh -c "$*"</command> + </node> + <tagNode name="prefix-list"> + <properties> + <help>Clear prefix-list statistics or status for specified word</help> + <completionHelp> + <list>WORD</list> + </completionHelp> + </properties> + <command>vtysh -c "$*"</command> + <children> + <leafNode name="node.tag"> + <properties> + <help>Clear prefix-list statistics or status for given word|network</help> + <completionHelp> + <list><h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <command>vtysh -c "$*"</command> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/clear-log.xml.in b/op-mode-definitions/clear-log.xml.in new file mode 100644 index 0000000..1f4a1aa --- /dev/null +++ b/op-mode-definitions/clear-log.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="clear"> + <children> + <leafNode name="log"> + <properties> + <help>Clear contents of current master log file</help> + </properties> + <command>sudo journalctl --rotate --vacuum-time=1s</command> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/clear-session.xml.in b/op-mode-definitions/clear-session.xml.in new file mode 100644 index 0000000..bfafe63 --- /dev/null +++ b/op-mode-definitions/clear-session.xml.in @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="clear"> + <children> + <tagNode name="session"> + <properties> + <help>Terminate TTY or PTS user session</help> + <completionHelp> + <script>who | awk '{print $2}'</script> + </completionHelp> + </properties> + <command>sudo pkill -9 -t $3</command> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/configure.xml.in b/op-mode-definitions/configure.xml.in new file mode 100644 index 0000000..d765728 --- /dev/null +++ b/op-mode-definitions/configure.xml.in @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="configure"> + <properties> + <help>Enter configuration mode</help> + </properties> + <command>if [ `id -u` == 0 ]; then + echo "You are attempting to enter configuration mode as root." + echo "It may have unintended consequences and render your system" + echo "unusable until restart." + echo "Please do it as an administrator level VyOS user instead." + else + if grep -q -e '^overlay.*/filesystem.squashfs' /proc/mounts; then + echo "WARNING: You are currently configuring a live-ISO environment, changes will not persist until installed" + else + if grep -q -s '1' /tmp/vyos-config-status; then + echo "WARNING: There was a config error on boot: saving the configuration now could overwrite data." + echo "You may want to check and reload the boot config" + fi + fi + history -w + export _OFR_CONFIGURE=ok + newgrp vyattacfg + unset _OFR_CONFIGURE + _vyatta_op_do_key_bindings + history -r + fi</command> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/connect.xml.in b/op-mode-definitions/connect.xml.in new file mode 100644 index 0000000..9027056 --- /dev/null +++ b/op-mode-definitions/connect.xml.in @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="connect"> + <properties> + <help>Establish connection</help> + </properties> + <children> + <tagNode name="console"> + <properties> + <help>Connect to device attached to serial console server</help> + <completionHelp> + <path>service console-server device</path> + <script>${vyos_completion_dir}/list_consoles.sh</script> + </completionHelp> + </properties> + <command>/usr/bin/console "$3"</command> + </tagNode> + <tagNode name="interface"> + <properties> + <help>Bring up a connection-oriented network interface</help> + <completionHelp> + <path>interfaces pppoe</path> + <path>interfaces sstpc</path> + <path>interfaces wwan</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/connect_disconnect.py --connect --interface "$3"</command> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/conntrack-sync.xml.in b/op-mode-definitions/conntrack-sync.xml.in new file mode 100644 index 0000000..a66331f --- /dev/null +++ b/op-mode-definitions/conntrack-sync.xml.in @@ -0,0 +1,106 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="reset"> + <children> + <node name="conntrack-sync"> + <properties> + <help>Reset connection syncing parameters</help> + </properties> + <children> + <leafNode name="external-cache"> + <properties> + <help>Reset external cache and request resync with other systems</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/conntrack_sync.py reset_external_cache</command> + </leafNode> + <leafNode name="internal-cache"> + <properties> + <help>Reset internal cache and request resync with other systems</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/conntrack_sync.py reset_internal_cache</command> + </leafNode> + </children> + </node> + </children> + </node> + <node name="restart"> + <children> + <leafNode name="conntrack-sync"> + <properties> + <help>Restart the connection tracking synchronization service</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/conntrack_sync.py restart</command> + </leafNode> + </children> + </node> + <node name="show"> + <children> + <node name="conntrack-sync"> + <properties> + <help>Show connection tracking synchronization information</help> + </properties> + <children> + <node name="cache"> + <properties> + <help>Show connection tracking cache entries</help> + </properties> + <children> + <node name="external"> + <properties> + <help>Show external connection tracking cache entries</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/conntrack_sync.py show_external_cache</command> + <children> + <leafNode name="main"> + <properties> + <help>Show external main connection tracking cache entries</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/conntrack_sync.py show_external_cache</command> + </leafNode> + <leafNode name="expect"> + <properties> + <help>Show external expect connection tracking cache entries</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/conntrack_sync.py show_external_expect</command> + </leafNode> + </children> + </node> + <node name="internal"> + <properties> + <help>Show internal connection tracking cache entries</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/conntrack_sync.py show_internal_cache</command> + <children> + <leafNode name="main"> + <properties> + <help>Show internal main connection tracking cache entries</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/conntrack_sync.py show_internal_cache</command> + </leafNode> + <leafNode name="expect"> + <properties> + <help>Show internal expect connection tracking cache entries</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/conntrack_sync.py show_internal_expect</command> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="statistics"> + <properties> + <help>Show connection syncing statistics</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/conntrack_sync.py show_statistics</command> + </leafNode> + <leafNode name="status"> + <properties> + <help>Show conntrack-sync status</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/conntrack_sync.py show_status</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/container.xml.in b/op-mode-definitions/container.xml.in new file mode 100644 index 0000000..bb6f97b --- /dev/null +++ b/op-mode-definitions/container.xml.in @@ -0,0 +1,207 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="add"> + <children> + <node name="container"> + <properties> + <help>Add container image</help> + </properties> + <children> + <tagNode name="image"> + <properties> + <help>Pull a new image for container</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/container.py add_image --name "${4}"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="connect"> + <children> + <tagNode name="container"> + <properties> + <help>Attach to a running container</help> + <completionHelp> + <path>container name</path> + </completionHelp> + </properties> + <command>sudo podman exec --interactive --tty "$3" /bin/sh</command> + </tagNode> + </children> + </node> + <node name="delete"> + <children> + <node name="container"> + <properties> + <help>Delete container image</help> + </properties> + <children> + <tagNode name="image"> + <properties> + <help>Delete container image</help> + <completionHelp> + <list>all</list> + <script>sudo podman image ls -q</script> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/container.py delete_image --name "${4}"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="generate"> + <children> + <node name="container"> + <properties> + <help>Generate Container Image</help> + </properties> + <children> + <tagNode name="image"> + <properties> + <help>Name of container image (tag)</help> + </properties> + <children> + <tagNode name="path"> + <properties> + <help>Path to Dockerfile</help> + <completionHelp> + <list><filename></list> + </completionHelp> + </properties> + <command>sudo podman build --net host --layers --force-rm --tag "$4" $6</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + <node name="monitor"> + <children> + <node name="log"> + <children> + <tagNode name="container"> + <properties> + <help>Monitor last lines of container log</help> + <completionHelp> + <path>container name</path> + </completionHelp> + </properties> + <command>sudo podman logs --follow --names "$4"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="show"> + <children> + <node name="container"> + <properties> + <help>Show containers</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/container.py show_container</command> + <children> + <node name="json"> + <properties> + <help>Show containers in JSON format</help> + </properties> + <!-- no admin check --> + <command>sudo ${vyos_op_scripts_dir}/container.py show_container --raw</command> + </node> + <node name="image"> + <properties> + <help>Show container image</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/container.py show_image</command> + <children> + <node name="json"> + <properties> + <help>Show container image in JSON format</help> + </properties> + <!-- no admin check --> + <command>sudo ${vyos_op_scripts_dir}/container.py show_image --raw</command> + </node> + </children> + </node> + <tagNode name="log"> + <properties> + <help>Show logs from a given container</help> + <completionHelp> + <path>container name</path> + </completionHelp> + </properties> + <!-- no admin check --> + <command>sudo podman logs --names "$4"</command> + </tagNode> + <node name="network"> + <properties> + <help>Show available container networks</help> + </properties> + <!-- no admin check --> + <command>sudo ${vyos_op_scripts_dir}/container.py show_network</command> + <children> + <node name="json"> + <properties> + <help>Show available container networks in JSON format</help> + </properties> + <!-- no admin check --> + <command>sudo ${vyos_op_scripts_dir}/container.py show_network --raw</command> + </node> + </children> + </node> + </children> + </node> + <node name="log"> + <children> + <tagNode name="container"> + <properties> + <help>Show logs from a given container</help> + <completionHelp> + <path>container name</path> + </completionHelp> + </properties> + <command>sudo podman logs --names "$4"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="restart"> + <children> + <tagNode name="container"> + <properties> + <help>Restart a given container</help> + <completionHelp> + <path>container name</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/container.py restart --name="$3"</command> + </tagNode> + </children> + </node> + <node name="update"> + <properties> + <help>Update data for a service</help> + </properties> + <children> + <node name="container"> + <properties> + <help>Update a container image</help> + </properties> + <children> + <tagNode name="image"> + <properties> + <help>Update container image</help> + <completionHelp> + <path>container name</path> + </completionHelp> + </properties> + <command>if cli-shell-api existsActive container name "$4"; then sudo podman pull $(cli-shell-api returnActiveValue container name "$4" image); else echo "Container $4 does not exist"; fi</command> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/crypt.xml.in b/op-mode-definitions/crypt.xml.in new file mode 100644 index 0000000..105592a --- /dev/null +++ b/op-mode-definitions/crypt.xml.in @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="encryption"> + <properties> + <help>Manage config encryption</help> + </properties> + <children> + <node name="disable"> + <properties> + <help>Disable config encryption using TPM or recovery key</help> + </properties> + <command>sudo ${vyos_libexec_dir}/vyos-config-encrypt.py --disable</command> + </node> + <node name="enable"> + <properties> + <help>Enable config encryption using TPM</help> + </properties> + <command>sudo ${vyos_libexec_dir}/vyos-config-encrypt.py --enable</command> + </node> + <node name="load"> + <properties> + <help>Load encrypted config volume using TPM or recovery key</help> + </properties> + <command>sudo ${vyos_libexec_dir}/vyos-config-encrypt.py --load</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/date.xml.in b/op-mode-definitions/date.xml.in new file mode 100644 index 0000000..4e62a83 --- /dev/null +++ b/op-mode-definitions/date.xml.in @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="date"> + <properties> + <help>Show system time and date</help> + </properties> + <command>/bin/date</command> + <children> + <node name="utc"> + <properties> + <help>Show system date and time as Coordinated Universal Time</help> + </properties> + <command>/bin/date -u</command> + <children> + <leafNode name="maya"> + <properties> + <help>Show UTC date in Maya calendar format</help> + </properties> + <command>${vyos_op_scripts_dir}/maya_date.py $(date +%s)</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="set"> + <children> + <tagNode name="date"> + <properties> + <help>Set system date and time</help> + <completionHelp> + <list><MMDDhhmm> <MMDDhhmmYY> <MMDDhhmmCCYY> <MMDDhhmmCCYY.ss></list> + </completionHelp> + </properties> + <command>sudo bash -c "/bin/date '$3' && hwclock --systohc --localtime"</command> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/dhcp.xml.in b/op-mode-definitions/dhcp.xml.in new file mode 100644 index 0000000..b3438ab --- /dev/null +++ b/op-mode-definitions/dhcp.xml.in @@ -0,0 +1,357 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="clear"> + <children> + <node name="dhcp-server"> + <properties> + <help>Clear DHCP server lease</help> + </properties> + <children> + <tagNode name="lease"> + <properties> + <help>DHCP server lease</help> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py clear_dhcp_server_lease --family inet --address $4</command> + </tagNode> + </children> + </node> + <node name="dhcpv6-server"> + <properties> + <help>Clear DHCPv6 server lease</help> + </properties> + <children> + <tagNode name="lease"> + <properties> + <help>DHCPv6 server lease</help> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py clear_dhcp_server_lease --family inet6 --address $4</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="show"> + <children> + <node name="dhcp"> + <properties> + <help>Show DHCP (Dynamic Host Configuration Protocol) information</help> + </properties> + <children> + <node name="client"> + <properties> + <help>Show DHCP client information</help> + </properties> + <children> + <node name="leases"> + <properties> + <help>Show DHCP client leases</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help> Show DHCP client information for a given interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --broadcast</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_client_leases --family inet --interface $6</command> + </tagNode> + </children> + <command>${vyos_op_scripts_dir}/dhcp.py show_client_leases --family inet</command> + </node> + </children> + </node> + <node name="server"> + <properties> + <help>Show DHCP server information</help> + </properties> + <children> + <node name="leases"> + <properties> + <help>Show DHCP server leases</help> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet</command> + <children> + <tagNode name="origin"> + <properties> + <help>Show DHCP server leases granted by local or remote DHCP server</help> + <completionHelp> + <list>local remote</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --origin $6</command> + </tagNode> + <tagNode name="pool"> + <properties> + <help>Show DHCP server leases for a specific pool</help> + <completionHelp> + <path>service dhcp-server shared-network-name</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --pool $6</command> + </tagNode> + <tagNode name="sort"> + <properties> + <help>Show DHCP server leases sorted by the specified key</help> + <completionHelp> + <list>end hostname ip mac pool remaining start state</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --sort $6</command> + </tagNode> + <tagNode name="state"> + <properties> + <help>Show DHCP server leases with a specific state (can be multiple, comma-separated)</help> + <completionHelp> + <list>abandoned active all backup expired free released reset</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --state $6</command> + </tagNode> + </children> + </node> + <node name="static-mappings"> + <properties> + <help>Show DHCP server static mappings</help> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet</command> + <children> + <tagNode name="pool"> + <properties> + <help>Show DHCP server static mappings for a specific pool</help> + <completionHelp> + <path>service dhcp-server shared-network-name</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet --pool $6</command> + </tagNode> + <tagNode name="sort"> + <properties> + <help>Show DHCP server static mappings sorted by the specified key</help> + <completionHelp> + <list>ip mac duid pool</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet --sort $6</command> + </tagNode> + </children> + </node> + <node name="statistics"> + <properties> + <help>Show DHCP server statistics</help> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_pool_statistics --family inet</command> + <children> + <tagNode name="pool"> + <properties> + <help>Show DHCP server statistics for a specific pool</help> + <completionHelp> + <path>service dhcp-server shared-network-name</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_pool_statistics --family inet --pool $6</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="dhcpv6"> + <properties> + <help>Show DHCPv6 (IPv6 Dynamic Host Configuration Protocol) information</help> + </properties> + <children> + <node name="server"> + <properties> + <help>Show DHCPv6 server information</help> + </properties> + <children> + <node name="leases"> + <properties> + <help>Show DHCPv6 server leases</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6</command> + <children> + <tagNode name="pool"> + <properties> + <help>Show DHCPv6 server leases for a specific pool</help> + <completionHelp> + <path>service dhcpv6-server shared-network-name</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --pool $6</command> + </tagNode> + <tagNode name="sort"> + <properties> + <help>Show DHCPv6 server leases sorted by the specified key</help> + <completionHelp> + <list>end duid ip last_communication pool remaining state type</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --sort $6</command> + </tagNode> + <tagNode name="state"> + <properties> + <help>Show DHCPv6 server leases with a specific state (can be multiple, comma-separated)</help> + <completionHelp> + <list>abandoned active all backup expired free released reset</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --state $6</command> + </tagNode> + </children> + </node> + <node name="static-mappings"> + <properties> + <help>Show DHCPv6 server static mappings</help> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6</command> + <children> + <tagNode name="pool"> + <properties> + <help>Show DHCPv6 server static mappings for a specific pool</help> + <completionHelp> + <path>service dhcp-server shared-network-name</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6 --pool $6</command> + </tagNode> + <tagNode name="sort"> + <properties> + <help>Show DHCPv6 server static mappings sorted by the specified key</help> + <completionHelp> + <list>ip mac duid pool</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6 --sort $6</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="restart"> + <children> + <node name="dhcp"> + <properties> + <help>Restart DHCP processes</help> + </properties> + <children> + <node name="server"> + <properties> + <help>Restart DHCP server</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name dhcp</command> + </node> + <node name="relay-agent"> + <properties> + <help>Restart DHCP relay-agent</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_dhcp_relay.py --ipv4</command> + </node> + </children> + </node> + <node name="dhcpv6"> + <properties> + <help>Restart DHCPv6 processes</help> + </properties> + <children> + <node name="server"> + <properties> + <help>Restart DHCPv6 server</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name dhcpv6</command> + </node> + <node name="relay-agent"> + <properties> + <help>Restart DHCPv6 relay-agent</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_dhcp_relay.py --ipv6</command> + </node> + </children> + </node> + </children> + </node> + <node name="renew"> + <properties> + <help>Renew specified variable</help> + </properties> + <children> + <node name="dhcp"> + <properties> + <help>Renew DHCP client lease</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Renew DHCP client lease for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/dhcp.py renew_client_lease --family inet --interface "$4"</command> + </tagNode> + </children> + </node> + <node name="dhcpv6"> + <properties> + <help>Renew DHCPv6 client lease</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Renew DHCPv6 client lease for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/dhcp.py renew_client_lease --family inet6 --interface "$4"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="release"> + <properties> + <help>Release specified variable</help> + </properties> + <children> + <node name="dhcp"> + <properties> + <help>Release DHCP client lease</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Release DHCP client lease for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/dhcp.py release_client_lease --family inet --interface "$4"</command> + </tagNode> + </children> + </node> + <node name="dhcpv6"> + <properties> + <help>Release DHCPv6 client lease</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Release DHCPv6 client lease for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/dhcp.py release_client_lease --family inet6 --interface "$4"</command> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/disconnect.xml.in b/op-mode-definitions/disconnect.xml.in new file mode 100644 index 0000000..f0523d9 --- /dev/null +++ b/op-mode-definitions/disconnect.xml.in @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="disconnect"> + <properties> + <help>Take down a connection</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Take down a connection-oriented network interface</help> + <completionHelp> + <path>interfaces pppoe</path> + <path>interfaces sstpc</path> + <path>interfaces wwan</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/connect_disconnect.py --disconnect --interface "$3"</command> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/disks.xml.in b/op-mode-definitions/disks.xml.in new file mode 100644 index 0000000..8a1e2c8 --- /dev/null +++ b/op-mode-definitions/disks.xml.in @@ -0,0 +1,69 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="format"> + <properties> + <help>Format a device</help> + </properties> + <children> + <node name="by-id"> + <properties> + <help>Find disk by ending of id string</help> + </properties> + <children> + <tagNode name="disk"> + <properties> + <help>Format a disk drive</help> + </properties> + <children> + <tagNode name="like"> + <properties> + <help>Format this disk the same as another disk</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/format_disk.py --by-id --target $4 --proto $6</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + <tagNode name="disk"> + <properties> + <help>Format a disk drive</help> + <completionHelp> + <script>${vyos_completion_dir}/list_disks.py</script> + </completionHelp> + </properties> + <children> + <tagNode name="like"> + <properties> + <help>Format this disk the same as another disk</help> + <completionHelp> + <script>${vyos_completion_dir}/list_disks.py --exclude ${COMP_WORDS[2]}</script> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/format_disk.py --target $3 --proto $5</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + <node name="show"> + <children> + <tagNode name="disk"> + <properties> + <help>Show status of disk device</help> + <completionHelp> + <script>${vyos_completion_dir}/list_disks.py</script> + </completionHelp> + </properties> + <children> + <leafNode name="format"> + <properties> + <help>Show disk drive formatting</help> + </properties> + <command>${vyos_op_scripts_dir}/show_disk_format.sh $3</command> + </leafNode> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/dns-dynamic.xml.in b/op-mode-definitions/dns-dynamic.xml.in new file mode 100644 index 0000000..ef0f039 --- /dev/null +++ b/op-mode-definitions/dns-dynamic.xml.in @@ -0,0 +1,123 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="clear"> + <children> + <node name="dns"> + <properties> + <help>Clear Domain Name System (DNS) related service state</help> + </properties> + <children> + <node name="dynamic"> + <properties> + <help>Clear Dynamic DNS information</help> + </properties> + <children> + <leafNode name="cache"> + <properties> + <help>Clear Dynamic DNS information cache (ddclient)</help> + </properties> + <command>sudo rm -f /run/ddclient/ddclient.cache</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="monitor"> + <children> + <node name="log"> + <children> + <node name="dns"> + <properties> + <help>Monitor last lines of Domain Name System (DNS) related services</help> + </properties> + <children> + <node name="dynamic"> + <properties> + <help>Monitor last lines of Dynamic DNS update service</help> + </properties> + <command>journalctl --no-hostname --follow --boot --unit ddclient.service</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="show"> + <children> + <node name="log"> + <children> + <node name="dns"> + <properties> + <help>Show log for Domain Name System (DNS) related services</help> + </properties> + <children> + <node name="dynamic"> + <properties> + <help>Show log for Dynamic DNS update service</help> + </properties> + <command>journalctl --no-hostname --boot --unit ddclient.service</command> + </node> + </children> + </node> + </children> + </node> + <node name="dns"> + <properties> + <help>Show Domain Name System (DNS) related information</help> + </properties> + <children> + <node name="dynamic"> + <properties> + <help>Show Dynamic DNS information</help> + </properties> + <children> + <leafNode name="status"> + <properties> + <help>Show Dynamic DNS status</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/dns.py show_dynamic_status</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="restart"> + <children> + <node name="dns"> + <properties> + <help>Restart specific Domain Name System (DNS) related service</help> + </properties> + <children> + <node name="dynamic"> + <properties> + <help>Restart Dynamic DNS service</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name dns_dynamic</command> + </node> + </children> + </node> + </children> + </node> + <node name="reset"> + <children> + <node name="dns"> + <properties> + <help>Reset Domain Name System (DNS) related service state</help> + </properties> + <children> + <node name="dynamic"> + <properties> + <help>Reset Dynamic DNS information</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/dns.py reset_dynamic</command> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/dns-forwarding.xml.in b/op-mode-definitions/dns-forwarding.xml.in new file mode 100644 index 0000000..fac3fc3 --- /dev/null +++ b/op-mode-definitions/dns-forwarding.xml.in @@ -0,0 +1,112 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="monitor"> + <children> + <node name="log"> + <children> + <node name="dns"> + <properties> + <help>Monitor last lines of Domain Name System (DNS) related services</help> + </properties> + <children> + <node name="forwarding"> + <properties> + <help>Monitor last lines of DNS Forwarding service</help> + </properties> + <command>journalctl --no-hostname --follow --boot --unit pdns-recursor.service</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="show"> + <children> + <node name="log"> + <children> + <node name="dns"> + <properties> + <help>Show log for Domain Name System (DNS) related services</help> + </properties> + <children> + <node name="forwarding"> + <properties> + <help>Show log for DNS Forwarding</help> + </properties> + <command>journalctl --no-hostname --boot --unit pdns-recursor.service</command> + </node> + </children> + </node> + </children> + </node> + <node name="dns"> + <properties> + <help>Show Domain Name System (DNS) related information</help> + </properties> + <children> + <node name="forwarding"> + <properties> + <help>Show DNS Forwarding information</help> + </properties> + <children> + <leafNode name="statistics"> + <properties> + <help>Show DNS Forwarding statistics</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/dns.py show_forwarding_statistics</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="restart"> + <children> + <node name="dns"> + <properties> + <help>Restart specific Domain Name System (DNS) related service</help> + </properties> + <children> + <leafNode name="forwarding"> + <properties> + <help>Restart DNS Forwarding service</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name dns_forwarding</command> + </leafNode> + </children> + </node> + </children> + </node> + <node name="reset"> + <children> + <node name="dns"> + <properties> + <help>Reset Domain Name System (DNS) related service state</help> + </properties> + <children> + <node name="forwarding"> + <properties> + <help>Reset DNS Forwarding cache</help> + </properties> + <children> + <tagNode name="domain"> + <command>sudo ${vyos_op_scripts_dir}/dns.py reset_forwarding --domain $5</command> + <properties> + <help>Reset DNS Forwarding cache for a domain</help> + </properties> + </tagNode> + <leafNode name="all"> + <command>sudo ${vyos_op_scripts_dir}/dns.py reset_forwarding --all</command> + <properties> + <help>Reset DNS Forwarding cache for all domains</help> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/execute-bandwidth-test.xml.in b/op-mode-definitions/execute-bandwidth-test.xml.in new file mode 100644 index 0000000..1581d5c --- /dev/null +++ b/op-mode-definitions/execute-bandwidth-test.xml.in @@ -0,0 +1,59 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="execute"> + <children> + <node name="bandwidth-test"> + <properties> + <help>Initiate or wait for bandwidth test</help> + </properties> + <children> + <node name="accept"> + <properties> + <help>Wait for bandwidth test connections (port TCP/5001)</help> + </properties> + <command>/usr/bin/iperf -V -s</command> + <children> + <leafNode name="tcp"> + <properties> + <help>Wait for bandwidth test connections (port TCP/5001)</help> + </properties> + <command>/usr/bin/iperf -V -s</command> + </leafNode> + <leafNode name="udp"> + <properties> + <help>Wait for bandwidth test connections (port UDP/5001)</help> + </properties> + <command>/usr/bin/iperf -V -s -u</command> + </leafNode> + </children> + </node> + <node name="initiate"> + <properties> + <help>Initiate a bandwidth test to specified host</help> + </properties> + <children> + <tagNode name="tcp"> + <properties> + <help>Initiate a bandwidth test to specified host (port TCP/5001)</help> + <completionHelp> + <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/execute_bandwidth_test.sh "$5"</command> + </tagNode> + <tagNode name="udp"> + <properties> + <help>Initiate a bandwidth test to specified host (port UDP/5001)</help> + <completionHelp> + <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/execute_bandwidth_test.sh "$5" "-u"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/execute-port-scan.xml.in b/op-mode-definitions/execute-port-scan.xml.in new file mode 100644 index 0000000..52cdab5 --- /dev/null +++ b/op-mode-definitions/execute-port-scan.xml.in @@ -0,0 +1,34 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="execute"> + <children> + <node name="port-scan"> + <properties> + <help>Scan network for open ports</help> + </properties> + <children> + <tagNode name="host"> + <properties> + <help>IP address or domain name of the host to scan (scan all ports 1-65535)</help> + <completionHelp> + <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>nmap -p- -T4 --max-retries=1 --host-timeout=30s "$4"</command> + <children> + <leafNode name="node.tag"> + <properties> + <help>Port scan options</help> + <completionHelp> + <script>${vyos_op_scripts_dir}/execute_port-scan.py --get-options-nested "${COMP_WORDS[@]}"</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/execute_port-scan.py "${@:4}"</command> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/execute-shell.xml.in b/op-mode-definitions/execute-shell.xml.in new file mode 100644 index 0000000..dfdc1e3 --- /dev/null +++ b/op-mode-definitions/execute-shell.xml.in @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="execute"> + <children> + <node name="shell"> + <properties> + <help>Execute shell</help> + </properties> + <children> + <tagNode name="netns"> + <properties> + <help>Execute shell in given Network Namespace</help> + <completionHelp> + <path>netns name</path> + </completionHelp> + </properties> + <command>sudo ip netns exec $4 su - $(whoami)</command> + </tagNode> + <tagNode name="vrf"> + <properties> + <help>Execute shell in given VRF instance</help> + <completionHelp> + <path>vrf name</path> + </completionHelp> + </properties> + <command>sudo ip vrf exec $4 su - $(whoami)</command> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/execute-ssh.xml.in b/op-mode-definitions/execute-ssh.xml.in new file mode 100644 index 0000000..7fa656f --- /dev/null +++ b/op-mode-definitions/execute-ssh.xml.in @@ -0,0 +1,34 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="execute"> + <children> + <node name="ssh"> + <properties> + <help>SSH to a node</help> + </properties> + <children> + <tagNode name="host"> + <properties> + <help>Hostname or IP address</help> + <completionHelp> + <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>/usr/bin/ssh $4</command> + <children> + <tagNode name="user"> + <properties> + <help>Remote server username</help> + <completionHelp> + <list><username></list> + </completionHelp> + </properties> + <command>/usr/bin/ssh $6@$4</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/execute-wamp.xml.in b/op-mode-definitions/execute-wamp.xml.in new file mode 100644 index 0000000..bcceedc --- /dev/null +++ b/op-mode-definitions/execute-wamp.xml.in @@ -0,0 +1,25 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="execute"> + <children> + <tagNode name="owping"> + <properties> + <help>IP address of the remote OWAMP server</help> + <completionHelp> + <list><x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>owping $3</command> + </tagNode> + <tagNode name="twping"> + <properties> + <help>IP address of the remote TWAMP server</help> + <completionHelp> + <list><x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>twping $3</command> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/execute.xml.in b/op-mode-definitions/execute.xml.in new file mode 100644 index 0000000..66069c9 --- /dev/null +++ b/op-mode-definitions/execute.xml.in @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="execute"> + <properties> + <help>Initiate an operation</help> + </properties> + </node> +</interfaceDefinition>
\ No newline at end of file diff --git a/op-mode-definitions/file.xml.in b/op-mode-definitions/file.xml.in new file mode 100644 index 0000000..549b9ad --- /dev/null +++ b/op-mode-definitions/file.xml.in @@ -0,0 +1,86 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <tagNode name="file"> + <properties> + <help>Show the contents of a file, a directory or an image</help> + <completionHelp><imagePath/></completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/file.py --show $3</command> + </tagNode> + </children> + </node> + <node name="copy"> + <properties> + <help>Copy an object</help> + </properties> + <children> + <tagNode name="file"> + <properties> + <help>Copy a file or a directory</help> + <completionHelp><imagePath/></completionHelp> + </properties> + <children> + <tagNode name="to"> + <properties> + <help>Destination path</help> + <completionHelp><imagePath/></completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/file.py --copy $3 $5 + </command> + </tagNode> + </children> + </tagNode> + </children> + </node> + <node name="delete"> + <properties> + <help>Delete an object</help> + </properties> + <children> + <tagNode name="file"> + <properties> + <help>Delete a local file, possibly from an image</help> + <completionHelp><imagePath/></completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/file.py --delete $3</command> + </tagNode> + </children> + </node> + <node name="clone"> + <properties> + <help>Clone an object</help> + </properties> + <children> + <node name="system"> + <properties> + <help>Clone a system object</help> + </properties> + <children> + <tagNode name="config"> + <properties> + <help>Clone the current system configuration to an image</help> + <completionHelp> + <script>${vyos_completion_dir}/list_images.py --no-running</script> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/file.py --clone $4</command> + <children> + <tagNode name="from"> + <properties> + <help>Clone system configuration from an image to another one</help> + <completionHelp> + <list>running</list> + <script>${vyos_completion_dir}/list_images.py</script> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/file.py --clone-from $6 $4</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/firewall.xml.in b/op-mode-definitions/firewall.xml.in new file mode 100644 index 0000000..82e6c86 --- /dev/null +++ b/op-mode-definitions/firewall.xml.in @@ -0,0 +1,769 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="firewall"> + <properties> + <help>Show firewall information</help> + </properties> + <children> + <tagNode name="group"> + <properties> + <help>Show firewall group</help> + <completionHelp> + <path>firewall group address-group</path> + <path>firewall group network-group</path> + <path>firewall group port-group</path> + <path>firewall group interface-group</path> + <path>firewall group ipv6-address-group</path> + <path>firewall group ipv6-network-group</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of firewall groups</help> + <completionHelp> + <path>firewall group detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_group --name $4 --detail $5</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_group --name $4</command> + </tagNode> + <node name="group"> + <properties> + <help>Show firewall group</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of firewall group</help> + <completionHelp> + <path>firewall group detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_group --detail $4</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_group</command> + </node> + <node name="bridge"> + <properties> + <help>Show bridge firewall</help> + </properties> + <children> + <node name="forward"> + <properties> + <help>Show bridge forward firewall ruleset</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Show bridge forward filter firewall ruleset</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of bridge forward filter firewall rules</help> + <completionHelp> + <path>firewall bridge forward filter detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Show summary of bridge forward filter firewall rules</help> + <completionHelp> + <path>firewall bridge forward filter rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of specific bridge forward filter firewall rule</help> + <completionHelp> + <path>firewall bridge forward filter detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </node> + </children> + </node> + <node name="input"> + <properties> + <help>Show bridge input firewall ruleset</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Show bridge input filter firewall ruleset</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of bridge input filter firewall rules</help> + <completionHelp> + <path>firewall bridge input filter detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Show summary of bridge input filter firewall rules</help> + <completionHelp> + <path>firewall bridge input filter rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of specific bridge input filter firewall rule</help> + <completionHelp> + <path>firewall bridge input filter detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </node> + </children> + </node> + <node name="output"> + <properties> + <help>Show bridge output firewall ruleset</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Show bridge output filter firewall ruleset</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of bridge output filter firewall rules</help> + <completionHelp> + <path>firewall bridge output filter detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Show summary of bridge output filter firewall rules</help> + <completionHelp> + <path>firewall bridge output filter rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of specific bridge output filter firewall rule</help> + <completionHelp> + <path>firewall bridge output filter detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </node> + </children> + </node> + <node name="prerouting"> + <properties> + <help>Show bridge prerouting firewall ruleset</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Show bridge prerouting filter firewall ruleset</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of bridge prerouting filter firewall rules</help> + <completionHelp> + <path>firewall bridge prerouting filter detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Show summary of bridge prerouting filter firewall rules</help> + <completionHelp> + <path>firewall bridge prerouting filter rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of specific bridge prerouting filter firewall rule</help> + <completionHelp> + <path>firewall bridge prerouting filter detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </node> + </children> + </node> + <tagNode name="name"> + <properties> + <help>Show bridge custom firewall chains</help> + <completionHelp> + <path>firewall bridge name</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of bridge custom firewall chains</help> + <completionHelp> + <path>firewall bridge name detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Show summary of bridge custom firewall ruleset</help> + <completionHelp> + <path>firewall bridge name ${COMP_WORDS[4]} rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of bridge custom firewall rules</help> + <completionHelp> + <path>firewall bridge name ${COMP_WORDS[4]} rule detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_family --family $3</command> + </node> + <node name="ipv6"> + <properties> + <help>Show IPv6 firewall</help> + </properties> + <children> + <node name="forward"> + <properties> + <help>Show IPv6 forward firewall ruleset</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Show IPv6 forward filter firewall ruleset</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv6 forward filter firewall ruleset</help> + <completionHelp> + <path>firewall ipv6 forward filter detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Show summary of IPv6 forward filter firewall rules</help> + <completionHelp> + <path>firewall ipv6 forward filter rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv6 forward filter firewall rules</help> + <completionHelp> + <path>firewall ipv6 forward filter rule detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </node> + </children> + </node> + <node name="input"> + <properties> + <help>Show IPv6 input firewall ruleset</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Show IPv6 forward input firewall ruleset</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv6 input firewall ruleset</help> + <completionHelp> + <path>firewall ipv6 input filter detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Show summary of IPv6 input filter firewall rules</help> + <completionHelp> + <path>firewall ipv6 input filter rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv6 input filter firewall rules</help> + <completionHelp> + <path>firewall ipv6 input filter rule detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </node> + </children> + </node> + <node name="output"> + <properties> + <help>Show IPv6 output firewall ruleset</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Show IPv6 output filter firewall ruleset</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv6 output input firewall ruleset</help> + <completionHelp> + <path>firewall ipv6 output filter detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Show summary of IPv6 output filter firewall rules</help> + <completionHelp> + <path>firewall ipv6 output filter rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv6 output filter firewall rules</help> + <completionHelp> + <path>firewall ipv6 output filter rule detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </node> + </children> + </node> + <node name="prerouting"> + <properties> + <help>Show IPv6 prerouting firewall ruleset</help> + </properties> + <children> + <node name="raw"> + <properties> + <help>Show IPv6 prerouting raw firewall ruleset</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv6 prerouting raw firewall ruleset</help> + <completionHelp> + <path>firewall ipv6 prerouting raw detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Show summary of IPv6 prerouting raw firewall rules</help> + <completionHelp> + <path>firewall ipv6 prerouting raw rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv6 prerouting raw firewall rules</help> + <completionHelp> + <path>firewall ipv6 prerouting raw rule detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </node> + </children> + </node> + <tagNode name="name"> + <properties> + <help>Show IPv6 custom firewall chains</help> + <completionHelp> + <path>firewall ipv6 name</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv6 custom firewall chains</help> + <completionHelp> + <path>firewall ipv6 name detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Show summary of IPv6 custom firewall ruleset</help> + <completionHelp> + <path>firewall ipv6 name ${COMP_WORDS[4]} rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv6 custom firewall rules</help> + <completionHelp> + <path>firewall ipv6 name ${COMP_WORDS[4]} rule detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_family --family $3</command> + </node> + <node name="ipv4"> + <properties> + <help>Show IPv4 firewall</help> + </properties> + <children> + <node name="forward"> + <properties> + <help>Show IPv4 forward firewall ruleset</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Show IPv4 forward filter firewall ruleset</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv4 forward filter firewall ruleset</help> + <completionHelp> + <path>firewall ipv4 forward filter detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Show summary of IPv4 forward filter firewall rules</help> + <completionHelp> + <path>firewall ipv4 forward filter rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv4 forward filter firewall rules</help> + <completionHelp> + <path>firewall ipv4 forward filter rule detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </node> + </children> + </node> + <node name="input"> + <properties> + <help>Show IPv4 input firewall ruleset</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Show IPv4 forward input firewall ruleset</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv4 input filter firewall ruleset</help> + <completionHelp> + <path>firewall ipv4 input filter detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Show summary of IPv4 input filter firewall rules</help> + <completionHelp> + <path>firewall ipv4 input filter rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv4 input filter firewall rules</help> + <completionHelp> + <path>firewall ipv4 input filter rule detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </node> + </children> + </node> + <node name="output"> + <properties> + <help>Show IPv4 output firewall ruleset</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Show IPv4 output filter firewall ruleset</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv4 output filter firewall ruleset</help> + <completionHelp> + <path>firewall ipv4 input output detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Show summary of IPv4 output filter firewall rules</help> + <completionHelp> + <path>firewall ipv4 output filter rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv4 output filter firewall rules</help> + <completionHelp> + <path>firewall ipv4 input output rule detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </node> + </children> + </node> + <node name="prerouting"> + <properties> + <help>Show IPv4 prerouting firewall ruleset</help> + </properties> + <children> + <node name="raw"> + <properties> + <help>Show IPv4 prerouting raw firewall ruleset</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv4 prerouting raw firewall ruleset</help> + <completionHelp> + <path>firewall ipv4 prerouting raw detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Show summary of IPv4 prerouting raw firewall rules</help> + <completionHelp> + <path>firewall ipv4 prerouting raw rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv4 prerouting raw firewall rules</help> + <completionHelp> + <path>firewall ipv4 prerouting raw rule detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </node> + </children> + </node> + <tagNode name="name"> + <properties> + <help>Show IPv4 custom firewall chains</help> + <completionHelp> + <path>firewall ipv4 name</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv4 custom firewall chains</help> + <completionHelp> + <path>firewall ipv4 name detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Show summary of IPv4 custom firewall ruleset</help> + <completionHelp> + <path>firewall ipv4 name ${COMP_WORDS[4]} rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of IPv4 custom firewall ruleset</help> + <completionHelp> + <path>firewall ipv4 name ${COMP_WORDS[4]} rule detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_family --family $3</command> + </node> + <node name="statistics"> + <properties> + <help>Show statistics of firewall application</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show list view of firewall statistics</help> + <completionHelp> + <path>firewall statistics detail</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_statistics --detail $4</command> + </leafNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_statistics</command> + </node> + <leafNode name="summary"> + <properties> + <help>Show summary of firewall application</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_summary</command> + </leafNode> + <node name="zone-policy"> + <properties> + <help>Show zone policy information</help> + </properties> + <children> + <tagNode name="zone"> + <properties> + <help>Show summary of zone policy for a specific zone</help> + <completionHelp> + <path>firewall zone</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/zone.py show --zone $5</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/zone.py show</command> + </node> + </children> + <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_all</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/flow-accounting-op.xml.in b/op-mode-definitions/flow-accounting-op.xml.in new file mode 100644 index 0000000..46dc77d --- /dev/null +++ b/op-mode-definitions/flow-accounting-op.xml.in @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- flow-accounting op mode commands --> +<interfaceDefinition> + <node name="show"> + <children> + <node name="flow-accounting"> + <properties> + <help>Show flow accounting statistics</help> + </properties> + <command>${vyos_op_scripts_dir}/flow_accounting_op.py --action show</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show flow accounting statistics for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/flow_accounting_op.py --action show --interface $4</command> + <children> + <tagNode name="host"> + <properties> + <help>Show flow accounting statistics for specified interface/host</help> + <completionHelp> + <list><x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/flow_accounting_op.py --action show --interface $4 --host $6</command> + </tagNode> + <tagNode name="port"> + <properties> + <help>Show flow accounting statistics for specified interface/port</help> + <completionHelp> + <list>1-65535</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/flow_accounting_op.py --action show --interface $4 --ports $6</command> + </tagNode> + <tagNode name="top"> + <properties> + <help>Show top N flows for specified interface</help> + <completionHelp> + <list>1-100</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/flow_accounting_op.py --action show --interface $4 --top $6</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + <node name="restart"> + <children> + <leafNode name="flow-accounting"> + <properties> + <help>Restart (net)flow accounting process</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/flow_accounting_op.py --action restart</command> + </leafNode> + </children> + </node> + <node name="clear"> + <children> + <node name="flow-accounting"> + <properties> + <help>Clear flow accounting</help> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear flow accounting statistics</help> + </properties> + <command>${vyos_op_scripts_dir}/flow_accounting_op.py --action clear</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/force-arp.xml.in b/op-mode-definitions/force-arp.xml.in new file mode 100644 index 0000000..05aa04e --- /dev/null +++ b/op-mode-definitions/force-arp.xml.in @@ -0,0 +1,103 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="force"> + <properties> + <help>Force an operation</help> + </properties> + <children> + <node name="arp"> + <properties> + <help>Send gratuitous ARP request or reply</help> + </properties> + <children> + <node name="reply"> + <properties> + <help>Send gratuitous ARP reply</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Send gratuitous ARP reply on specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --broadcast</script> + </completionHelp> + </properties> + <children> + <tagNode name="address"> + <properties> + <help>Send gratuitous ARP reply for specified address</help> + </properties> + <command>sudo /usr/bin/arping -I $5 -c 1 -A $7</command> + <children> + <tagNode name="count"> + <properties> + <help>Send specified number of ARP replies</help> + </properties> + <command>sudo /usr/bin/arping -I $5 -c $9 -A $7</command> + </tagNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + <node name="request"> + <properties> + <help>Send gratuitous ARP request</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Send gratuitous ARP request on specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --broadcast</script> + </completionHelp> + </properties> + <children> + <tagNode name="address"> + <properties> + <help>Send gratuitous ARP request for specified address</help> + </properties> + <command>sudo /usr/bin/arping -I $5 -c 1 -U $7</command> + <children> + <tagNode name="count"> + <properties> + <help>Send specified number of ARP requests</help> + </properties> + <command>sudo /usr/bin/arping -I $5 -c $9 -U $7</command> + </tagNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + <node name="duplicate"> + <properties> + <help>Send ARP for DAD detection</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Send ARP for DAD detection on specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --broadcast</script> + </completionHelp> + </properties> + <children> + <tagNode name="address"> + <properties> + <help>Send ARP for DAD detection for specified address</help> + </properties> + <command>sudo /usr/bin/arping -I $5 -c 1 -D $7</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/force-commit-archive.xml.in b/op-mode-definitions/force-commit-archive.xml.in new file mode 100644 index 0000000..46836f9 --- /dev/null +++ b/op-mode-definitions/force-commit-archive.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="force"> + <children> + <leafNode name="commit-archive"> + <properties> + <help>Manually archive configuration</help> + </properties> + <command>/etc/commit/post-hooks.d/02vyos-commit-archive; printf "\n"</command> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/force-ipv6-nd.xml.in b/op-mode-definitions/force-ipv6-nd.xml.in new file mode 100644 index 0000000..664fee4 --- /dev/null +++ b/op-mode-definitions/force-ipv6-nd.xml.in @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="force"> + <children> + <node name="ipv6-nd"> + <properties> + <help>IPv6 Neighbor Discovery</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>IPv6 Neighbor Discovery on specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + <tagNode name="address"> + <properties> + <help>IPv6 address of node to lookup</help> + <completionHelp> + <list><h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>/usr/bin/ndisc6 -m "$6" "$4"</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/force-ipv6-rd.xml.in b/op-mode-definitions/force-ipv6-rd.xml.in new file mode 100644 index 0000000..c81b81a --- /dev/null +++ b/op-mode-definitions/force-ipv6-rd.xml.in @@ -0,0 +1,34 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="force"> + <children> + <node name="ipv6-rd"> + <properties> + <help>IPv6 Router Discovery</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>IPv6 Router Discovery on specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>/usr/bin/rdisc6 "$4"</command> + <children> + <tagNode name="address"> + <properties> + <help>IPv6 address of target</help> + <completionHelp> + <list><h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>/usr/bin/rdisc6 -m "$6" "$4"</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/force-mtu-host.xml.in b/op-mode-definitions/force-mtu-host.xml.in new file mode 100644 index 0000000..5624167 --- /dev/null +++ b/op-mode-definitions/force-mtu-host.xml.in @@ -0,0 +1,34 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="force"> + <children> + <node name="mtu"> + <properties> + <help>Show MTU max value for remote host protocol TCP</help> + </properties> + <children> + <tagNode name="host"> + <properties> + <help>IP address of the remote host</help> + <completionHelp> + <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/force_mtu_host.sh $4</command> + <children> + <tagNode name="interface"> + <properties> + <help>Source interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/force_mtu_host.sh $4 $6</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/force-root-partition-auto-resize.xml.in b/op-mode-definitions/force-root-partition-auto-resize.xml.in new file mode 100644 index 0000000..f84c073 --- /dev/null +++ b/op-mode-definitions/force-root-partition-auto-resize.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="force"> + <children> + <node name="root-partition-auto-resize"> + <properties> + <help>Resize the VyOS partition</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/force_root-partition-auto-resize.sh</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/generate-interfaces-debug-archive.xml.in b/op-mode-definitions/generate-interfaces-debug-archive.xml.in new file mode 100644 index 0000000..5e4f4da --- /dev/null +++ b/op-mode-definitions/generate-interfaces-debug-archive.xml.in @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <children> + <node name="interfaces"> + <properties> + <help>Interface specific commands</help> + </properties> + <children> + <node name="debug-archive"> + <properties> + <help>Generate interfaces debug-archive</help> + </properties> + <command>${vyos_op_scripts_dir}/generate_interfaces_debug_archive.py</command> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/generate-ipsec-debug-archive.xml.in b/op-mode-definitions/generate-ipsec-debug-archive.xml.in new file mode 100644 index 0000000..a9ce113 --- /dev/null +++ b/op-mode-definitions/generate-ipsec-debug-archive.xml.in @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <children> + <node name="ipsec"> + <children> + <node name="debug-archive"> + <properties> + <help>Generate IPSec debug-archive</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/generate_ipsec_debug_archive.py</command> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/generate-ipsec-profile.xml.in b/op-mode-definitions/generate-ipsec-profile.xml.in new file mode 100644 index 0000000..afa299d --- /dev/null +++ b/op-mode-definitions/generate-ipsec-profile.xml.in @@ -0,0 +1,114 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <children> + <node name="ipsec"> + <properties> + <help>Generate IPsec related configurations and archives</help> + </properties> + <children> + <node name="profile"> + <properties> + <help>Generate IKEv2 IPSec remote-access VPN profiles</help> + </properties> + <children> + <tagNode name="ios-remote-access"> + <properties> + <help>Generate iOS profile for specified remote-access connection name</help> + <completionHelp> + <path>vpn ipsec remote-access connection</path> + </completionHelp> + </properties> + <children> + <tagNode name="remote"> + <properties> + <help>Remote address where the client will connect to</help> + <completionHelp> + <list><fqdn></list> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7"</command> + <children> + <tagNode name="name"> + <properties> + <help>Connection name as seen in the VPN application</help> + <completionHelp> + <list><name></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --name "$9"</command> + <children> + <tagNode name="profile"> + <properties> + <help>Profile name as seen under system profiles</help> + <completionHelp> + <list><name></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --name "$9" --profile "${11}"</command> + </tagNode> + </children> + </tagNode> + <tagNode name="profile"> + <properties> + <help>Profile name as seen under system profiles</help> + <completionHelp> + <list><name></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --profile "$9"</command> + <children> + <tagNode name="name"> + <properties> + <help>Connection name as seen in the VPN application</help> + <completionHelp> + <list><name></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --profile "$9" --name "${11}"</command> + </tagNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="windows-remote-access"> + <properties> + <help>Generate iOS profile for specified remote-access connection name</help> + <completionHelp> + <path>vpn ipsec remote-access connection</path> + </completionHelp> + </properties> + <children> + <tagNode name="remote"> + <properties> + <help>Remote address where the client will connect to</help> + <completionHelp> + <list><fqdn></list> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os windows --connection "$5" --remote "$7"</command> + <children> + <tagNode name="name"> + <properties> + <help>Connection name as seen in the VPN application</help> + <completionHelp> + <list><name></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os windows --connection "$5" --remote "$7" --name "$9"</command> + </tagNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/generate-macsec-key.xml.in b/op-mode-definitions/generate-macsec-key.xml.in new file mode 100644 index 0000000..d8e514c --- /dev/null +++ b/op-mode-definitions/generate-macsec-key.xml.in @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <children> + <node name="macsec"> + <properties> + <help>Generate MACsec Key</help> + </properties> + <children> + <node name="mka"> + <properties> + <help>MACsec Key Agreement (MKA) protocol</help> + </properties> + <children> + <node name="cak"> + <properties> + <help>Generate MACsec connectivity association key (CAK)</help> + </properties> + <children> + <leafNode name="gcm-aes-128"> + <properties> + <help>Generate random key for GCM-AES-128 encryption - 128bit</help> + </properties> + <command>/usr/bin/hexdump -n 16 -e '4/4 "%08x" 1 "\n"' /dev/random</command> + </leafNode> + <leafNode name="gcm-aes-256"> + <properties> + <help>Generate random key for GCM-AES-256 encryption - 256bit</help> + </properties> + <command>/usr/bin/hexdump -n 32 -e '8/4 "%08x" 1 "\n"' /dev/random</command> + </leafNode> + </children> + </node> + <node name="ckn"> + <properties> + <help>Generate MACsec connectivity association name (CKN) - 256bit</help> + </properties> + <command>/usr/bin/hexdump -n 32 -e '8/4 "%08x" 1 "\n"' /dev/random</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/generate-openconnect-user-key.xml.in b/op-mode-definitions/generate-openconnect-user-key.xml.in new file mode 100644 index 0000000..80cdfb3 --- /dev/null +++ b/op-mode-definitions/generate-openconnect-user-key.xml.in @@ -0,0 +1,67 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <children> + <node name="openconnect"> + <properties> + <help>Generate OpenConnect client parameters</help> + </properties> + <children> + <tagNode name="username"> + <properties> + <help>Username used for authentication</help> + <completionHelp> + <list><username></list> + </completionHelp> + </properties> + <children> + <node name="otp-key"> + <properties> + <help>Generate OpenConnect OTP token</help> + </properties> + <children> + <node name="hotp-time"> + <properties> + <help>HOTP time-based token</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/generate_openconnect_otp_key.py --username "$4" --interval 30 --digits 6</command> + <children> + <tagNode name="interval"> + <properties> + <help>Duration of single time interval</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/generate_openconnect_otp_key.py --username "$4" --interval "$8" --digits 6</command> + <children> + <tagNode name="digits"> + <properties> + <help>The number of digits in the one-time password</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/generate_openconnect_otp_key.py --username "$4" --interval "$8" --digits "${10}"</command> + </tagNode> + </children> + </tagNode> + <tagNode name="digits"> + <properties> + <help>The number of digits in the one-time password</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/generate_openconnect_otp_key.py --username "$4" --interval 30 --digits "$8"</command> + <children> + <tagNode name="interval"> + <properties> + <help>Duration of single time interval</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/generate_openconnect_otp_key.py --username "$4" --interval "${10}" --digits $8</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/generate-openvpn-config-client.xml.in b/op-mode-definitions/generate-openvpn-config-client.xml.in new file mode 100644 index 0000000..fc8bfa3 --- /dev/null +++ b/op-mode-definitions/generate-openvpn-config-client.xml.in @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <children> + <node name="openvpn"> + <properties> + <help>Generate OpenVPN client configuration ovpn file</help> + </properties> + <children> + <node name="client-config"> + <properties> + <help>Generate Client config</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Local interface used for connection</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type openvpn</script> + </completionHelp> + </properties> + <children> + <tagNode name="ca"> + <properties> + <help>CA certificate</help> + <completionHelp> + <path>pki ca</path> + </completionHelp> + </properties> + <children> + <tagNode name="certificate"> + <properties> + <help>Cerificate used by client</help> + <completionHelp> + <path>pki certificate</path> + </completionHelp> + </properties> + <children> + <tagNode name="key"> + <properties> + <help>Certificate key used by client</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/generate_ovpn_client_file.py --interface "$5" --ca "$7" --cert "$9" --key "${11}"</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/generate_ovpn_client_file.py --interface "$5" --ca "$7" --cert "$9"</command> + </tagNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/generate-public-key-command.xml.in b/op-mode-definitions/generate-public-key-command.xml.in new file mode 100644 index 0000000..21f0f56 --- /dev/null +++ b/op-mode-definitions/generate-public-key-command.xml.in @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <children> + <node name="public-key-command"> + <properties> + <help>Generate configuration mode command to add OpenSSH public key from file</help> + </properties> + <children> + <tagNode name="user"> + <properties> + <help>Username of public key owner</help> + <completionHelp> + <list><username></list> + </completionHelp> + </properties> + <children> + <tagNode name="path"> + <properties> + <help>Local path or remote URL of OpenSSH public key</help> + <completionHelp> + <list><![CDATA[<http[s]://[<username>:<password>@]<hostname>/<path-to-file>> <ftp://[<username>[:<password>]@]<hostname>/<path-to-file>> <tftp://<hostname>/<path-to-file>> <sftp://[<username>[:<password>]@]<hostname>/<path-to-file>> <scp://[<username>[:<password>]@]<hostname>/<path-to-file>> <[file://]/<path-to-file>>]]></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/generate_public_key_command.py "$4" "$6"</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/generate-ssh-server-key.xml.in b/op-mode-definitions/generate-ssh-server-key.xml.in new file mode 100644 index 0000000..ecea3e5 --- /dev/null +++ b/op-mode-definitions/generate-ssh-server-key.xml.in @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <properties> + <help>Generate an object/key</help> + </properties> + <children> + <node name="ssh"> + <properties> + <help>Generate SSH related keypairs</help> + </properties> + <children> + <node name="server-key"> + <properties> + <help>Re-generate SSH host keys and restart SSH server</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/generate_ssh_server_key.py</command> + </node> + <tagNode name="client-key"> + <properties> + <help>Re-generate SSH client keypair</help> + <completionHelp> + <list><filename></list> + </completionHelp> + </properties> + <command>ssh-keygen -t rsa -f "$4" -N ""</command> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/generate-system-login-user.xml.in b/op-mode-definitions/generate-system-login-user.xml.in new file mode 100644 index 0000000..6f65c12 --- /dev/null +++ b/op-mode-definitions/generate-system-login-user.xml.in @@ -0,0 +1,90 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <children> + <node name="system"> + <properties> + <help>Generate system related parameters</help> + </properties> + <children> + <node name="login"> + <properties> + <help>Generate system login related parameters</help> + </properties> + <children> + <tagNode name="username"> + <properties> + <help>Username used for authentication</help> + <completionHelp> + <path>system login user</path> + </completionHelp> + </properties> + <children> + <node name="otp-key"> + <properties> + <help>Generate OpenConnect OTP token</help> + </properties> + <children> + <node name="hotp-time"> + <properties> + <help>HOTP time-based token</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5"</command> + <children> + <tagNode name="rate-limit"> + <properties> + <help>Duration of single time interval</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate_limit "$9"</command> + <children> + <tagNode name="rate-time"> + <properties> + <help>The number of digits in the one-time password</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate_limit "$9" --rate_time "${11}" </command> + <children> + <tagNode name="window-size"> + <properties> + <help>The number of digits in the one-time password</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate_limit "$9" --rate_time "${11}" --window_size "${13}"</command> + </tagNode> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="window-size"> + <properties> + <help>The number of digits in the one-time password</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --window_size "${9}"</command> + <children> + <tagNode name="rate-limit"> + <properties> + <help>Duration of single time interval</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate_limit "${11}" --window_size "${9}"</command> + <children> + <tagNode name="rate-time"> + <properties> + <help>Duration of single time interval</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate_limit "${11}" --rate_time "${13}" --window_size "${9}"</command> + </tagNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/generate-wireguard.xml.in b/op-mode-definitions/generate-wireguard.xml.in new file mode 100644 index 0000000..5f2463d --- /dev/null +++ b/op-mode-definitions/generate-wireguard.xml.in @@ -0,0 +1,66 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <children> + <node name="wireguard"> + <properties> + <help>Generate WireGuard client config QR code</help> + </properties> + <children> + <tagNode name="client-config"> + <properties> + <help>Generate Client config QR code</help> + <completionHelp> + <list><client-name></list> + </completionHelp> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Local interface used for connection</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type wireguard</script> + </completionHelp> + </properties> + <children> + <tagNode name="server"> + <properties> + <help>IP address/FQDN used for client connection</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + <list><hostname></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/wireguard_client.py --name "$4" --interface "$6" --server "$8"</command> + <children> + <tagNode name="address"> + <properties> + <help>IPv4/IPv6 address used by client</help> + <completionHelp> + <list><x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/wireguard_client.py --name "$4" --interface "$6" --server "$8" --address "${10}"</command> + <children> + <tagNode name="address"> + <properties> + <help>IPv4/IPv6 address used by client</help> + <completionHelp> + <list><x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/wireguard_client.py --name "$4" --interface "$6" --server "$8" --address "${10}" --address "${12}"</command> + </tagNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/generate_firewall_rule-resequence.xml.in b/op-mode-definitions/generate_firewall_rule-resequence.xml.in new file mode 100644 index 0000000..ef81579 --- /dev/null +++ b/op-mode-definitions/generate_firewall_rule-resequence.xml.in @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <children> + <node name="firewall"> + <properties> + <help>Firewall</help> + </properties> + <children> + #include <include/rule-resequence.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/generate_nat64_rule-resequence.xml.in b/op-mode-definitions/generate_nat64_rule-resequence.xml.in new file mode 100644 index 0000000..399253b --- /dev/null +++ b/op-mode-definitions/generate_nat64_rule-resequence.xml.in @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <children> + <node name="nat64"> + <properties> + <help>Network Address Translation (NAT64)</help> + </properties> + <children> + #include <include/rule-resequence.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/generate_nat66_rule-resequence.xml.in b/op-mode-definitions/generate_nat66_rule-resequence.xml.in new file mode 100644 index 0000000..d7159cf --- /dev/null +++ b/op-mode-definitions/generate_nat66_rule-resequence.xml.in @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <children> + <node name="nat66"> + <properties> + <help>Network Prefix Translation (NAT66/NPTv6)</help> + </properties> + <children> + #include <include/rule-resequence.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/generate_nat_rule-resequence.xml.in b/op-mode-definitions/generate_nat_rule-resequence.xml.in new file mode 100644 index 0000000..e32a89e --- /dev/null +++ b/op-mode-definitions/generate_nat_rule-resequence.xml.in @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <children> + <node name="nat"> + <properties> + <help>Network Address Translation (NAT)</help> + </properties> + <children> + #include <include/rule-resequence.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/generate_tech-support_archive.xml.in b/op-mode-definitions/generate_tech-support_archive.xml.in new file mode 100644 index 0000000..fc664eb --- /dev/null +++ b/op-mode-definitions/generate_tech-support_archive.xml.in @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <children> + <node name="tech-support"> + <properties> + <help>Generate tech support info</help> + </properties> + <children> + <node name="archive"> + <properties> + <help>Generate tech support archive</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/tech_support.py show --raw | gzip> $4.json.gz</command> + </node> + <tagNode name="archive"> + <properties> + <help>Generate tech support archive to defined location</help> + <completionHelp> + <list> <file> </list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/tech_support.py show --raw | gzip > $4.json.gz</command> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/geoip.xml.in b/op-mode-definitions/geoip.xml.in new file mode 100644 index 0000000..c1b6e87 --- /dev/null +++ b/op-mode-definitions/geoip.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="update"> + <children> + <leafNode name="geoip"> + <properties> + <help>Update GeoIP database and firewall sets</help> + </properties> + <command>sudo ${vyos_libexec_dir}/geoip-update.py --force</command> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/igmp-proxy.xml.in b/op-mode-definitions/igmp-proxy.xml.in new file mode 100644 index 0000000..d6ad7ed --- /dev/null +++ b/op-mode-definitions/igmp-proxy.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="restart"> + <children> + <node name="igmp-proxy"> + <properties> + <help>Restart the IGMP proxy process</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name igmp_proxy</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/include/bgp/afi-common.xml.i b/op-mode-definitions/include/bgp/afi-common.xml.i new file mode 100644 index 0000000..acf20d9 --- /dev/null +++ b/op-mode-definitions/include/bgp/afi-common.xml.i @@ -0,0 +1,64 @@ +<!-- included start from bgp/afi-common.xml.i --> +<tagNode name="community"> + <properties> + <help>Community number where AA and NN are (0-65535)</help> + <completionHelp> + <list>AA:NN</list> + </completionHelp> + </properties> + <children> + #include <include/bgp/exact-match.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<tagNode name="large-community"> + <properties> + <help>Display routes matching the large-communities</help> + <completionHelp> + <list>AA:BB:CC</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/exact-match.xml.i> + </children> +</tagNode> +<tagNode name="large-community-list"> + <properties> + <help>Display routes matching the large-community-list</help> + <completionHelp> + <path>policy large-community-list</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/exact-match.xml.i> + </children> +</tagNode> +<leafNode name="statistics"> + <properties> + <help>RIB advertisement statistics</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<node name="summary"> + <properties> + <help>Summary of BGP neighbor status</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <leafNode name="established"> + <properties> + <help>Show only sessions in Established state</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="failed"> + <properties> + <help>Show only sessions not in Established state</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i new file mode 100644 index 0000000..820d507 --- /dev/null +++ b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i @@ -0,0 +1,240 @@ +<!-- included start from bgp/afi-ipv4-ipv6-common.xml.i --> +<node name="community"> + <properties> + <help>Display routes matching the community</help> + </properties> + <children> + <leafNode name="accept-own"> + <properties> + <help>Should accept local VPN route if exported and imported into different VRF (well-known community)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="accept-own-nexthop"> + <properties> + <help>Should accept VPN route with local nexthop (well-known community)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="blackhole"> + <properties> + <help>Inform EBGP peers to blackhole traffic to prefix (well-known community)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + #include <include/bgp/exact-match.xml.i> + <leafNode name="graceful-shutdown"> + <properties> + <help>Graceful shutdown (well-known community)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="llgr-stale"> + <properties> + <help>Staled Long-lived Graceful Restart VPN route (well-known community)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="local-AS"> + <properties> + <help>Do not send outside local AS (well-known community)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="no-advertise"> + <properties> + <help>Do not advertise to any peer (well-known community)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="no-export"> + <properties> + <help>Do not export to next AS (well-known community)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="no-llgr"> + <properties> + <help>Removed because Long-lived Graceful Restart was not enabled for VPN route (well-known community)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="no-peer"> + <properties> + <help>Do not export to any peer (well-known community)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="route-filter-translated-v4"> + <properties> + <help>RT translated VPNv4 route filtering (well-known community)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="route-filter-translated-v6"> + <properties> + <help>RT translated VPNv6 route filtering (well-known community)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="route-filter-v4"> + <properties> + <help>RT VPNv4 route filtering (well-known community)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="route-filter-v6"> + <properties> + <help>RT VPNv6 route filtering (well-known community)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</node> +<tagNode name="community-list"> + <properties> + <help>Display routes matching the community-list</help> + <completionHelp> + <list>1-500 name</list> + </completionHelp> + </properties> + <children> + #include <include/bgp/exact-match.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<node name="dampening"> + <properties> + <help>Display detailed information about dampening</help> + </properties> + <children> + <leafNode name="dampened-paths"> + <properties> + <help>Display paths suppressed due to dampening</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="flap-statistics"> + <properties> + <help>Display flap statistics of routes</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="parameters"> + <properties> + <help>Display detail of configured dampening parameters</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> +</node> +<tagNode name="filter-list"> + <properties> + <help>Display routes conforming to the filter-list</help> + <completionHelp> + <script>vtysh -c 'show bgp as-path-access-list' | grep 'AS path access list' | awk '{print $NF}'</script> + </completionHelp> + </properties> +</tagNode> +<node name="large-community"> + <properties> + <help>Show BGP routes matching the specified large-communities</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</node> +<leafNode name="neighbors"> + <properties> + <help>Detailed information on TCP and BGP neighbor connections</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<tagNode name="neighbors"> + <properties> + <help>Show BGP information for specified neighbor</help> + <completionHelp> + <script>vtysh -c "$(IFS=$' '; echo "${COMP_WORDS[@]:0:${#COMP_WORDS[@]}-2} summary")" | awk '/^[0-9a-f]/ {print $1}'</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <leafNode name="advertised-routes"> + <properties> + <help>Show routes advertised to a BGP neighbor</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="dampened-routes"> + <properties> + <help>Show dampened routes received from BGP neighbor</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="flap-statistics"> + <properties> + <help>Show flap statistics of the routes learned from BGP neighbor</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="prefix-counts"> + <properties> + <help>Show detailed prefix count information for BGP neighbor</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <node name="received"> + <properties> + <help>Show information received from BGP neighbor</help> + </properties> + <children> + <leafNode name="prefix-filter"> + <properties> + <help>Show prefixlist filter</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> + <leafNode name="filtered-routes"> + <properties> + <help>Show filtered routes from BGP neighbor</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="received-routes"> + <properties> + <help>Show received routes from BGP neighbor</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="routes"> + <properties> + <help>Show routes learned from BGP neighbor</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> +</tagNode> +<tagNode name="prefix-list"> + <properties> + <help>Display routes conforming to the prefix-list</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<tagNode name="regexp"> + <properties> + <help>Display routes matching the AS path regular expression</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<tagNode name="route-map"> + <properties> + <help>Show BGP routes matching the specified route map</help> + <completionHelp> + <path>policy route-map</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +#include <include/vtysh-generic-wide.xml.i> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/afi-ipv4-ipv6-flowspec.xml.i b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-flowspec.xml.i new file mode 100644 index 0000000..34228fd --- /dev/null +++ b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-flowspec.xml.i @@ -0,0 +1,25 @@ +<!-- included start from bgp/afi-ipv4-ipv6-flowspec.xml.i --> +<tagNode name="flowspec"> + <properties> + <help>Network in the BGP routing table to display</help> + <completionHelp> + <list><x.x.x.x> <x.x.x.x/x> <h:h:h:h:h:h:h:h> <h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <children> + #include <include/bgp/prefix-bestpath-multipath.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<node name="flowspec"> + <properties> + <help>Flowspec Address Family modifier</help> + </properties> + <children> + #include <include/bgp/afi-common.xml.i> + #include <include/bgp/afi-ipv4-ipv6-common.xml.i> + #include <include/vtysh-generic-detail.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/afi-ipv4-ipv6-vpn.xml.i b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-vpn.xml.i new file mode 100644 index 0000000..f6737c8 --- /dev/null +++ b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-vpn.xml.i @@ -0,0 +1,24 @@ +<!-- included start from bgp/afi-ipv4-ipv6-vpn.xml.i --> +<tagNode name="vpn"> + <properties> + <help>Network in the BGP routing table to display</help> + <completionHelp> + <list><x.x.x.x> <x.x.x.x/x> <h:h:h:h:h:h:h:h> <h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <children> + #include <include/bgp/prefix-bestpath-multipath.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<node name="vpn"> + <properties> + <help>VPN Address Family modifier</help> + </properties> + <children> + #include <include/bgp/afi-common.xml.i> + #include <include/bgp/afi-ipv4-ipv6-common.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/evpn-type-1.xml.i b/op-mode-definitions/include/bgp/evpn-type-1.xml.i new file mode 100644 index 0000000..b5097c8 --- /dev/null +++ b/op-mode-definitions/include/bgp/evpn-type-1.xml.i @@ -0,0 +1,8 @@ +<!-- included start from bgp/evpn-type-1.xml.i --> +<leafNode name="1"> + <properties> + <help>EAD (Type-1) route</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/evpn-type-2.xml.i b/op-mode-definitions/include/bgp/evpn-type-2.xml.i new file mode 100644 index 0000000..827298d --- /dev/null +++ b/op-mode-definitions/include/bgp/evpn-type-2.xml.i @@ -0,0 +1,8 @@ +<!-- included start from bgp/evpn-type-2.xml.i --> +<leafNode name="2"> + <properties> + <help>MAC-IP (Type-2) route</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/evpn-type-3.xml.i b/op-mode-definitions/include/bgp/evpn-type-3.xml.i new file mode 100644 index 0000000..ae90b2e --- /dev/null +++ b/op-mode-definitions/include/bgp/evpn-type-3.xml.i @@ -0,0 +1,8 @@ +<!-- included start from bgp/evpn-type-3.xml.i --> +<leafNode name="3"> + <properties> + <help>Multicast (Type-3) route</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/evpn-type-4.xml.i b/op-mode-definitions/include/bgp/evpn-type-4.xml.i new file mode 100644 index 0000000..7248b47 --- /dev/null +++ b/op-mode-definitions/include/bgp/evpn-type-4.xml.i @@ -0,0 +1,8 @@ +<!-- included start from bgp/evpn-type-4.xml.i --> +<leafNode name="4"> + <properties> + <help>Ethernet Segment (Type-4) route</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/evpn-type-5.xml.i b/op-mode-definitions/include/bgp/evpn-type-5.xml.i new file mode 100644 index 0000000..e3a7216 --- /dev/null +++ b/op-mode-definitions/include/bgp/evpn-type-5.xml.i @@ -0,0 +1,8 @@ +<!-- included start from bgp/evpn-type-5.xml.i --> +<leafNode name="5"> + <properties> + <help>Prefix (Type-5) route</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/evpn-type-ead.xml.i b/op-mode-definitions/include/bgp/evpn-type-ead.xml.i new file mode 100644 index 0000000..452de2f --- /dev/null +++ b/op-mode-definitions/include/bgp/evpn-type-ead.xml.i @@ -0,0 +1,8 @@ +<!-- included start from bgp/evpn-type-ead.xml.i --> +<leafNode name="ead"> + <properties> + <help>EAD (Type-1) route</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/evpn-type-es.xml.i b/op-mode-definitions/include/bgp/evpn-type-es.xml.i new file mode 100644 index 0000000..50c4015 --- /dev/null +++ b/op-mode-definitions/include/bgp/evpn-type-es.xml.i @@ -0,0 +1,8 @@ +<!-- included start from bgp/evpn-type-es.xml.i --> +<leafNode name="es"> + <properties> + <help>Ethernet Segment (Type-4) route</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/evpn-type-macip.xml.i b/op-mode-definitions/include/bgp/evpn-type-macip.xml.i new file mode 100644 index 0000000..6f601eb --- /dev/null +++ b/op-mode-definitions/include/bgp/evpn-type-macip.xml.i @@ -0,0 +1,8 @@ +<!-- included start from bgp/evpn-type-macip.xml.i --> +<leafNode name="macip"> + <properties> + <help>MAC-IP (Type-2) route</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/evpn-type-multicast.xml.i b/op-mode-definitions/include/bgp/evpn-type-multicast.xml.i new file mode 100644 index 0000000..5194dbb --- /dev/null +++ b/op-mode-definitions/include/bgp/evpn-type-multicast.xml.i @@ -0,0 +1,8 @@ +<!-- included start from bgp/evpn-type-multicast.xml.i --> +<leafNode name="multicast"> + <properties> + <help>Multicast (Type-3) route</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/evpn-type-prefix.xml.i b/op-mode-definitions/include/bgp/evpn-type-prefix.xml.i new file mode 100644 index 0000000..d5054d8 --- /dev/null +++ b/op-mode-definitions/include/bgp/evpn-type-prefix.xml.i @@ -0,0 +1,8 @@ +<!-- included start from bgp/evpn-type-prefix.xml.i --> +<leafNode name="prefix"> + <properties> + <help>Prefix (Type-5) route</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/exact-match.xml.i b/op-mode-definitions/include/bgp/exact-match.xml.i new file mode 100644 index 0000000..49026db --- /dev/null +++ b/op-mode-definitions/include/bgp/exact-match.xml.i @@ -0,0 +1,8 @@ +<!-- included start from bgp/exact-match.xml.i --> +<leafNode name="exact-match"> + <properties> + <help>Exact match of the communities</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/martian-next-hop.xml.i b/op-mode-definitions/include/bgp/martian-next-hop.xml.i new file mode 100644 index 0000000..938d4ff --- /dev/null +++ b/op-mode-definitions/include/bgp/martian-next-hop.xml.i @@ -0,0 +1,15 @@ +<!-- included start from bgp/martian-next-hop.xml.i --> +<node name="martian"> + <properties> + <help>Martian next-hops</help> + </properties> + <children> + <leafNode name="next-hop"> + <properties> + <help>Martian next-hop database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/next-hop.xml.i b/op-mode-definitions/include/bgp/next-hop.xml.i new file mode 100644 index 0000000..517a448 --- /dev/null +++ b/op-mode-definitions/include/bgp/next-hop.xml.i @@ -0,0 +1,23 @@ +<!-- included start from bgp/next-hop.xml.i --> +<node name="nexthop"> + <properties> + <help>Show BGP nexthop table</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/vtysh-generic-detail.xml.i> + </children> +</node> +<tagNode name="nexthop"> + <properties> + <help>IPv4/IPv6 nexthop address</help> + <completionHelp> + <list><x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/vtysh-generic-detail.xml.i> + </children> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/prefix-bestpath-multipath.xml.i b/op-mode-definitions/include/bgp/prefix-bestpath-multipath.xml.i new file mode 100644 index 0000000..2d91a82 --- /dev/null +++ b/op-mode-definitions/include/bgp/prefix-bestpath-multipath.xml.i @@ -0,0 +1,20 @@ +<!-- included start from bgp/prefix-bestpath-multipath.xml.i --> +<leafNode name="bestpath"> + <properties> + <help>Display only the bestpath</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<leafNode name="multipath"> + <properties> + <help>Display only multipaths</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<leafNode name="longer-prefixes"> + <properties> + <help>Display route and more specific routes</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/reset-bgp-afi-common.xml.i b/op-mode-definitions/include/bgp/reset-bgp-afi-common.xml.i new file mode 100644 index 0000000..2f88daa --- /dev/null +++ b/op-mode-definitions/include/bgp/reset-bgp-afi-common.xml.i @@ -0,0 +1,20 @@ +<!-- included start from bgp/reset-bgp-afi-common.xml.i --> +<node name="external"> + <properties> + <help>Reset all external peers</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/reset-bgp-neighbor-options.xml.i> + </children> +</node> +<tagNode name="1-4294967295"> + <properties> + <help>Reset peers with the AS number</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/reset-bgp-neighbor-options.xml.i> + </children> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/reset-bgp-neighbor-options.xml.i b/op-mode-definitions/include/bgp/reset-bgp-neighbor-options.xml.i new file mode 100644 index 0000000..d9feee1 --- /dev/null +++ b/op-mode-definitions/include/bgp/reset-bgp-neighbor-options.xml.i @@ -0,0 +1,48 @@ +<!-- included start from bgp/reset-bgp-neighbor-options.xml.i --> +<node name="in"> + <properties> + <help>Send route-refresh unless using 'soft-reconfiguration inbound'</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <leafNode name="prefix-filter"> + <properties> + <help>Push out prefix-list ORF and do inbound soft reconfig</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> +</node> +<leafNode name="message-stats"> + <properties> + <help>Reset message statistics</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<leafNode name="out"> + <properties> + <help>Resend all outbound updates</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<node name="soft"> + <properties> + <help>Soft reconfig inbound and outbound updates</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="in"> + <properties> + <help>Send route-refresh unless using 'soft-reconfiguration inbound'</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + <node name="out"> + <properties> + <help>Resend all outbound updates</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + </children> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/reset-bgp-peer-group-vrf.xml.i b/op-mode-definitions/include/bgp/reset-bgp-peer-group-vrf.xml.i new file mode 100644 index 0000000..c1a24ba --- /dev/null +++ b/op-mode-definitions/include/bgp/reset-bgp-peer-group-vrf.xml.i @@ -0,0 +1,14 @@ +<!-- included start from bgp/reset-bgp-peer-group-vrf.xml.i --> +<tagNode name="peer-group"> + <properties> + <help>Reset all members of peer-group</help> + <completionHelp> + <path>vrf name ${COMP_WORDS[4]} protocols bgp peer-group</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/reset-bgp-neighbor-options.xml.i> + </children> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/reset-bgp-peer-group.xml.i b/op-mode-definitions/include/bgp/reset-bgp-peer-group.xml.i new file mode 100644 index 0000000..c26e47b --- /dev/null +++ b/op-mode-definitions/include/bgp/reset-bgp-peer-group.xml.i @@ -0,0 +1,14 @@ +<!-- included start from bgp/reset-bgp-peer-group.xml.i --> +<tagNode name="peer-group"> + <properties> + <help>Reset all members of peer-group</help> + <completionHelp> + <path>protocols bgp peer-group</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/reset-bgp-neighbor-options.xml.i> + </children> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/show-bgp-common.xml.i b/op-mode-definitions/include/bgp/show-bgp-common.xml.i new file mode 100644 index 0000000..d888bc3 --- /dev/null +++ b/op-mode-definitions/include/bgp/show-bgp-common.xml.i @@ -0,0 +1,212 @@ +<!-- included start from bgp/show-bgp-common.xml.i --> +#include <include/bgp/afi-common.xml.i> +#include <include/bgp/afi-ipv4-ipv6-common.xml.i> +<tagNode name="ipv4"> + <properties> + <help>Network in the BGP routing table to display</help> + <completionHelp> + <list><x.x.x.x> <x.x.x.x/x> <h:h:h:h:h:h:h:h> <h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <children> + #include <include/bgp/prefix-bestpath-multipath.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<node name="ipv4"> + <properties> + <help>IPv4 Address Family</help> + </properties> + <children> + #include <include/bgp/afi-common.xml.i> + #include <include/bgp/afi-ipv4-ipv6-common.xml.i> + #include <include/bgp/afi-ipv4-ipv6-flowspec.xml.i> + #include <include/bgp/afi-ipv4-ipv6-vpn.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</node> +<tagNode name="ipv6"> + <properties> + <help>Network in the BGP routing table to display</help> + <completionHelp> + <list><x.x.x.x> <x.x.x.x/x> <h:h:h:h:h:h:h:h> <h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <children> + #include <include/bgp/prefix-bestpath-multipath.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<node name="ipv6"> + <properties> + <help>IPv6 Address Family</help> + </properties> + <children> + #include <include/bgp/afi-common.xml.i> + #include <include/bgp/afi-ipv4-ipv6-common.xml.i> + #include <include/bgp/afi-ipv4-ipv6-vpn.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</node> +<node name="l2vpn"> + <properties> + <help>Layer 2 Virtual Private Network</help> + </properties> + <children> + <tagNode name="evpn"> + <properties> + <help>Network in the BGP routing table to display</help> + <completionHelp> + <list><x.x.x.x> <x.x.x.x/x> <h:h:h:h:h:h:h:h> <h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <node name="evpn"> + <properties> + <help>Ethernet Virtual Private Network</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/afi-common.xml.i> + <node name="all"> + <properties> + <help>Display information about all EVPN NLRIs</help> + </properties> + <children> + <leafNode name="overlay"> + <properties> + <help>Display BGP Overlay Information for prefixes</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="tags"> + <properties> + <help>Display BGP tags for prefixes</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> + <node name="es"> + <properties> + <help>Ethernet Segment</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/vtysh-generic-detail.xml.i> + </children> + </node> + <node name="es-evi"> + <properties> + <help>Ethernet Segment per EVI</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/vtysh-generic-detail.xml.i> + #include <include/vni-tagnode.xml.i> + </children> + </node> + <leafNode name="es-vrf"> + <properties> + <help>Ethernet Segment per VRF</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="import-rt"> + <properties> + <help>Show import route target</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <tagNode name="neighbors"> + <properties> + <help>Show detailed BGP neighbor information</help> + <completionHelp> + <script>vtysh -c 'show bgp summary' | awk '{print $1'} | grep -e '^[0-9a-f]'</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <leafNode name="advertised-routes"> + <properties> + <help>Show routes advertised to a BGP neighbor</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="routes"> + <properties> + <help>Show routes learned from BGP neighbor</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </tagNode> + <leafNode name="next-hops"> + <properties> + <help>EVPN Nexthops</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <tagNode name="rd"> + <properties> + <help>Display information for a route distinguisher</help> + <completionHelp> + <list>ASN:NN IPADDRESS:NN all</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <leafNode name="overlay"> + <properties> + <help>Display BGP Overlay Information for prefixes</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="tags"> + <properties> + <help>Display BGP tags for prefixes</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </tagNode> + <node name="route"> + <properties> + <help>EVPN route information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/vtysh-generic-detail.xml.i> + <node name="type"> + <properties> + <help>Specify Route type</help> + </properties> + <children> + #include <include/bgp/evpn-type-1.xml.i> + #include <include/bgp/evpn-type-2.xml.i> + #include <include/bgp/evpn-type-3.xml.i> + #include <include/bgp/evpn-type-4.xml.i> + #include <include/bgp/evpn-type-5.xml.i> + #include <include/bgp/evpn-type-ead.xml.i> + #include <include/bgp/evpn-type-es.xml.i> + #include <include/bgp/evpn-type-macip.xml.i> + #include <include/bgp/evpn-type-multicast.xml.i> + #include <include/bgp/evpn-type-prefix.xml.i> + </children> + </node> + #include <include/vni-tagnode-all.xml.i> + </children> + </node> + #include <include/vni-tagnode.xml.i> + <leafNode name="vni"> + <properties> + <help>VXLAN network identifier (VNI)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> + </children> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/show-ip-bgp-common.xml.i b/op-mode-definitions/include/bgp/show-ip-bgp-common.xml.i new file mode 100644 index 0000000..db9021f --- /dev/null +++ b/op-mode-definitions/include/bgp/show-ip-bgp-common.xml.i @@ -0,0 +1,177 @@ +<!-- included start from bgp/show-ip-bgp-common.xml.i --> +<leafNode name="attribute-info"> + <properties> + <help>Show BGP attribute information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<leafNode name="cidr-only"> + <properties> + <help>Display only routes with non-natural netmasks</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<leafNode name="community-info"> + <properties> + <help>List all bgp community information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +#include <include/bgp/afi-common.xml.i> +#include <include/bgp/afi-ipv4-ipv6-common.xml.i> +<tagNode name="prefix-list"> + <properties> + <completionHelp> + <path>policy prefix-list</path> + </completionHelp> + </properties> +</tagNode> +<node name="ipv4"> + <properties> + <help>Show BGP IPv4 information</help> + </properties> + <children> + <node name="unicast"> + <properties> + <help>Show BGP IPv4 unicast information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <leafNode name="cidr-only"> + <properties> + <help>Display only routes with non-natural netmasks</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <node name="community"> + <properties> + <help>Show BGP routes matching the communities</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + <tagNode name="community"> + <properties> + <help>Display routes matching the specified communities</help> + <completionHelp> + <list><AA:NN> local-AS no-advertise no-export</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <tagNode name="community-list"> + <properties> + <help>Show BGP routes matching specified community list</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <leafNode name="exact-match"> + <properties> + <help>Show BGP routes exactly matching specified community list</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </tagNode> + <tagNode name="neighbors"> + <properties> + <help>Show detailed BGP IPv4 unicast neighbor information</help> + <completionHelp> + <script>vtysh -c "show ip bgp ipv4 unicast summary" | awk '{print $1}' | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b"</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <leafNode name="advertised-routes"> + <properties> + <help>Show routes advertised to a BGP neighbor</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="prefix-counts"> + <properties> + <help>Show detailed prefix count information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="filtered-routes"> + <properties> + <help>Show the filtered routes from neighbor</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="received-routes"> + <properties> + <help>Show the received routes from neighbor</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="routes"> + <properties> + <help>Show routes learned from neighbor</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </tagNode> + <leafNode name="paths"> + <properties> + <help>Show BGP path information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <tagNode name="prefix-list"> + <properties> + <help>Show BGP routes matching the specified prefix list</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <tagNode name="regexp"> + <properties> + <help>Show BGP routes matching the specified AS path regular expression</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <tagNode name="route-map"> + <properties> + <help>Show BGP routes matching the specified route map</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <leafNode name="summary"> + <properties> + <help>Show summary of BGP information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> + <tagNode name="unicast"> + <properties> + <help>Show BGP information for specified IP address or prefix</help> + <completionHelp> + <list><x.x.x.x> <x.x.x.x/x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + </children> +</node> +<leafNode name="large-community-info"> + <properties> + <help>Show BGP large-community information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<leafNode name="memory"> + <properties> + <help>Show BGP memory usage</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<leafNode name="paths"> + <properties> + <help>Show BGP path information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/frr-detail.xml.i b/op-mode-definitions/include/frr-detail.xml.i new file mode 100644 index 0000000..4edf82e --- /dev/null +++ b/op-mode-definitions/include/frr-detail.xml.i @@ -0,0 +1,8 @@ +<!-- included start from frr-detail.xml.i --> +<leafNode name="detail"> + <properties> + <help>Show detailed information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/isis-common.xml.i b/op-mode-definitions/include/isis-common.xml.i new file mode 100644 index 0000000..493a566 --- /dev/null +++ b/op-mode-definitions/include/isis-common.xml.i @@ -0,0 +1,183 @@ +<!-- included start from isis-common.xml.i --> +<node name="database"> + <properties> + <help>Show IS-IS link state database</help> + </properties> + <children> + #include <include/vtysh-generic-detail.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</node> +<tagNode name="database"> + <properties> + <help>Show IS-IS link state database PDU</help> + <completionHelp> + <list>lsp-id detail</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<node name="fast-reroute"> + <properties> + <help>Show IS-IS fast reroute/loop free alternate (lfa) information</help> + </properties> + <children> + <node name="summary"> + <properties> + <help>Show summary of fast reroute/loop free alternate (lfa) information</help> + </properties> + <children> + <leafNode name="level-1"> + <properties> + <help>Show level-1 specific fast reroute/loop free alternate (lfa) information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="level-2"> + <properties> + <help>Show level-2 specific fast reroute/loop free alternate (lfa) information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + </children> +</node> +<leafNode name="hostname"> + <properties> + <help>Show IS-IS dynamic hostname mapping</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<node name="interface"> + <properties> + <help>Show IS-IS interfaces</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + #include <include/vtysh-generic-detail.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</node> +#include <include/vtysh-generic-interface-tagNode.xml.i> +<node name="mpls"> + <properties> + <help>Show MPLS information</help> + </properties> + <children> + #include <include/ldp-sync.xml.i> + </children> +</node> +<node name="mpls-te"> + <properties> + <help>Show MPLS traffic engineering information</help> + </properties> + <children> + <leafNode name="router"> + <properties> + <help>Show router information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="interface"> + <properties> + <help>Show interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + #include <include/vtysh-generic-interface-tagNode.xml.i> + </children> +</node> +<node name="neighbor"> + <properties> + <help>Show IS-IS neighbor adjacencies</help> + </properties> + <children> + #include <include/vtysh-generic-detail.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</node> +<tagNode name="neighbor"> + <properties> + <help>Show specific IS-IS neighbor adjacency </help> + <completionHelp> + <list>system-id</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<node name="route"> + <properties> + <help>Show IS-IS routing table</help> + </properties> + <children> + <leafNode name="level-1"> + <properties> + <help>Show level-1 routes</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="level-2"> + <properties> + <help>Show level-2 routes</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="prefix-sid"> + <properties> + <help>Show Prefix-SID information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</node> +<node name="segment-routing"> + <properties> + <help>Show IS-IS Segment-Routing (SPRING) information</help> + </properties> + <children> + <leafNode name="node"> + <properties> + <help>Show node information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> +</node> +<leafNode name="spf-delay-ietf"> + <properties> + <help>Show IS-IS SPF delay parameters</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<leafNode name="summary"> + <properties> + <help>Show IS-IS information summary</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<node name="topology"> + <properties> + <help>Show IS-IS paths to Intermediate Systems</help> + </properties> + <children> + <leafNode name="level-1"> + <properties> + <help>Show level-1 routes</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="level-2"> + <properties> + <help>Show level-2 routes</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</node> +<!-- included end -->
\ No newline at end of file diff --git a/op-mode-definitions/include/ldp-sync.xml.i b/op-mode-definitions/include/ldp-sync.xml.i new file mode 100644 index 0000000..b7b04e7 --- /dev/null +++ b/op-mode-definitions/include/ldp-sync.xml.i @@ -0,0 +1,11 @@ +<!-- included start from ldp-sync.xml.i --> +<node name="ldp-sync"> + <properties> + <help>Show LDP-IGP synchronization information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/vtysh-generic-interface-tagNode.xml.i> + </children> +</node> +<!-- included end -->
\ No newline at end of file diff --git a/op-mode-definitions/include/monitor-no-ospf-packet-detail.xml.i b/op-mode-definitions/include/monitor-no-ospf-packet-detail.xml.i new file mode 100644 index 0000000..8dbb5ac --- /dev/null +++ b/op-mode-definitions/include/monitor-no-ospf-packet-detail.xml.i @@ -0,0 +1,36 @@ +<!-- included start from monitor-ospf-packet-detail.xml.i --> +<node name="detail"> + <properties> + <help>Disable detailed OSPF packet debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:3}"</command> +</node> +<node name="recv"> + <properties> + <help>Disable OSPF recv packet debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:3}"</command> + <children> + <node name="detail"> + <properties> + <help>Disable detailed OSPF recv packet debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:3}"</command> + </node> + </children> +</node> +<node name="send"> + <properties> + <help>Disable OSPF send packet debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:3}"</command> + <children> + <node name="detail"> + <properties> + <help>Disable detailed OSPF send packet debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:3}"</command> + </node> + </children> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/monitor-ospf-packet-detail.xml.i b/op-mode-definitions/include/monitor-ospf-packet-detail.xml.i new file mode 100644 index 0000000..a4bd336 --- /dev/null +++ b/op-mode-definitions/include/monitor-ospf-packet-detail.xml.i @@ -0,0 +1,36 @@ +<!-- included start from monitor-ospf-packet-detail.xml.i --> +<node name="detail"> + <properties> + <help>Enable detailed OSPF packet debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:3}"</command> +</node> +<node name="recv"> + <properties> + <help>Enable OSPF recv packet debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:3}"</command> + <children> + <node name="detail"> + <properties> + <help>Enable detailed OSPF recv packet debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:3}"</command> + </node> + </children> +</node> +<node name="send"> + <properties> + <help>Enable OSPF send packet debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:3}"</command> + <children> + <node name="detail"> + <properties> + <help>Enable detailed OSPF send packet debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:3}"</command> + </node> + </children> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/ospf/common.xml.i b/op-mode-definitions/include/ospf/common.xml.i new file mode 100644 index 0000000..684073c --- /dev/null +++ b/op-mode-definitions/include/ospf/common.xml.i @@ -0,0 +1,554 @@ +<!-- included start from ospf-common.xml.i --> +<leafNode name="border-routers"> + <properties> + <help>Show IPv4 OSPF border-routers information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<node name="database"> + <properties> + <help>Show IPv4 OSPF database information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="asbr-summary"> + <properties> + <help>Show IPv4 OSPF ASBR summary database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF ASBR summary database for given address of advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <node name="adv-router"> + <properties> + <help>Show IPv4 OSPF ASBR summary database for given address of advertised router</help> + </properties> + </node> + </children> + </node> + <tagNode name="asbr-summary"> + <properties> + <help>Show IPv4 OSPF ASBR summary database information of given address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="adv-router"> + <properties> + <help>Show advertising router link states</help> + </properties> + </node> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF ASBR summary database of given address for given advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <leafNode name="self-originate"> + <properties> + <help>Show summary of self-originate IPv4 OSPF ASBR database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </tagNode> + <node name="external"> + <properties> + <help>Show IPv4 OSPF external database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF external database for specified IP address of advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <node name="adv-router"> + <properties> + <help>Show IPv4 OSPF external database for specified IP address of advertised router</help> + </properties> + </node> + </children> + </node> + <tagNode name="external"> + <properties> + <help>Show IPv4 OSPF external database information of specified IP address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="adv-router"> + <properties> + <help>Show advertising router link states</help> + </properties> + </node> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF external database of specified IP address for specified advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <leafNode name="self-originate"> + <properties> + <help>Show self-originate IPv4 OSPF external database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </tagNode> + <leafNode name="max-age"> + <properties> + <help>Show IPv4 OSPF max-age database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <node name="network"> + <properties> + <help>Show IPv4 OSPF network database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF network database for specified IP address of advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <node name="adv-router"> + <properties> + <help>Show IPv4 OSPF network database for given address of advertised router</help> + </properties> + </node> + </children> + </node> + <tagNode name="network"> + <properties> + <help>Show IPv4 OSPF network database information of specified IP address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="adv-router"> + <properties> + <help>Show advertising router link states</help> + </properties> + </node> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF network database of specified IP address for specified advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <leafNode name="self-originate"> + <properties> + <help>Show self-originate IPv4 OSPF network database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </tagNode> + <node name="nssa-external"> + <properties> + <help>Show IPv4 OSPF NSSA external database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF NSSA external database for specified IP address of advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <node name="adv-router"> + <properties> + <help>Show IPv4 OSPF NSSA external database for specified IP address of advertised router</help> + </properties> + </node> + </children> + </node> + <tagNode name="nssa-external"> + <properties> + <help>Show IPv4 OSPF NSSA external database information of specified IP address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="adv-router"> + <properties> + <help>Show advertising router link states</help> + </properties> + </node> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF NSSA external database of specified IP address for specified advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <leafNode name="self-originate"> + <properties> + <help>Show self-originate IPv4 OSPF NSSA external database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </tagNode> + <node name="opaque-area"> + <properties> + <help>Show IPv4 OSPF opaque-area database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF opaque-area database for specified IP address of advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <node name="adv-router"> + <properties> + <help>Show IPv4 OSPF opaque-area database for specified IP address of advertised router</help> + </properties> + </node> + </children> + </node> + <tagNode name="opaque-area"> + <properties> + <help>Show IPv4 OSPF opaque-area database information of specified IP address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="adv-router"> + <properties> + <help>Show advertising router link states</help> + </properties> + </node> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF opaque-area database of specified IP address for specified advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <leafNode name="self-originate"> + <properties> + <help>Show self-originate IPv4 OSPF opaque-area database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </tagNode> + <node name="opaque-as"> + <properties> + <help>Show IPv4 OSPF opaque-as database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF opaque-as database for specified IP address of advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <node name="adv-router"> + <properties> + <help>Show IPv4 OSPF opaque-as database for specified IP address of advertised router</help> + </properties> + </node> + </children> + </node> + <tagNode name="opaque-as"> + <properties> + <help>Show IPv4 OSPF opaque-as database information of specified IP address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="adv-router"> + <properties> + <help>Show advertising router link states</help> + </properties> + </node> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF opaque-as database of specified IP address for specified advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <leafNode name="self-originate"> + <properties> + <help>Show self-originate IPv4 OSPF opaque-as database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </tagNode> + <node name="opaque-link"> + <properties> + <help>Show IPv4 OSPF opaque-link database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF opaque-link database for specified IP address of advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <node name="adv-router"> + <properties> + <help>Show IPv4 OSPF opaque-link database for specified IP address of advertised router</help> + </properties> + </node> + </children> + </node> + <tagNode name="opaque-link"> + <properties> + <help>Show IPv4 OSPF opaque-link database information of specified IP address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="adv-router"> + <properties> + <help>Show advertising router link states</help> + </properties> + </node> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF opaque-link database of specified IP address for specified advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <leafNode name="self-originate"> + <properties> + <help>Show self-originate IPv4 OSPF opaque-link database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </tagNode> + <node name="router"> + <properties> + <help>Show IPv4 OSPF router database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF router database for specified IP address of advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <node name="adv-router"> + <properties> + <help>Show IPv4 OSPF router database for specified IP address of advertised router</help> + </properties> + </node> + </children> + </node> + <tagNode name="router"> + <properties> + <help>Show IPv4 OSPF router database information of specified IP address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="adv-router"> + <properties> + <help>Show advertising router link states</help> + </properties> + </node> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF router database of specified IP address for specified advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <leafNode name="self-originate"> + <properties> + <help>Show self-originate IPv4 OSPF router database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </tagNode> + <leafNode name="self-originate"> + <properties> + <help>Show IPv4 OSPF self-originate database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <node name="summary"> + <properties> + <help>Show summary of IPv4 OSPF database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF summary database for specified IP address of advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <node name="adv-router"> + <properties> + <help>Show IPv4 OSPF summary database for specified IP address of advertised router</help> + </properties> + </node> + </children> + </node> + <tagNode name="summary"> + <properties> + <help>Show IPv4 OSPF summary database information of specified IP address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="adv-router"> + <properties> + <help>Show advertising router link states</help> + </properties> + </node> + <tagNode name="adv-router"> + <properties> + <help>Show IPv4 OSPF summary database of specified IP address for specified advertised router</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <leafNode name="self-originate"> + <properties> + <help>Show self-originate IPv4 OSPF summary database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </tagNode> + </children> +</node> +#include <include/ospf/graceful-restart.xml.i> +<node name="interface"> + <properties> + <help>Show IPv4 OSPF interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</node> +#include <include/vtysh-generic-interface-tagNode.xml.i> +<node name="mpls"> + <properties> + <help>Show MPLS information</help> + </properties> + <children> + #include <include/ldp-sync.xml.i> + </children> +</node> +<node name="neighbor"> + <properties> + <help>Show IPv4 OSPF neighbor information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/frr-detail.xml.i> + </children> +</node> +<tagNode name="neighbor"> + <properties> + <help>Show IPv4 OSPF neighbor information for specified IP address or interface</help> + <completionHelp> + <list><x.x.x.x></list> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<node name="route"> + <properties> + <help>Show IPv4 OSPF route information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed IPv4 OSPF route information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/ospf/graceful-restart.xml.i b/op-mode-definitions/include/ospf/graceful-restart.xml.i new file mode 100644 index 0000000..848b198 --- /dev/null +++ b/op-mode-definitions/include/ospf/graceful-restart.xml.i @@ -0,0 +1,13 @@ +<node name="graceful-restart"> + <properties> + <help>Show OSPF Graceful Restart</help> + </properties> + <children> + <leafNode name="helper"> + <properties> + <help>OSPF Graceful Restart helper details</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> diff --git a/op-mode-definitions/include/ospfv3/adv-router-id-node-tag.xml.i b/op-mode-definitions/include/ospfv3/adv-router-id-node-tag.xml.i new file mode 100644 index 0000000..8063664 --- /dev/null +++ b/op-mode-definitions/include/ospfv3/adv-router-id-node-tag.xml.i @@ -0,0 +1,16 @@ +<!-- included start from ospfv3/adv-router-id-node-tag.xml.i --> +<node name="node.tag"> + <properties> + <help>Search by Advertising Router ID</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + </children> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/ospfv3/adv-router.xml.i b/op-mode-definitions/include/ospfv3/adv-router.xml.i new file mode 100644 index 0000000..238242d --- /dev/null +++ b/op-mode-definitions/include/ospfv3/adv-router.xml.i @@ -0,0 +1,16 @@ +<!-- included start from ospfv3/adv-router.xml.i --> +<tagNode name="adv-router"> + <properties> + <help>Search by Advertising Router ID</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <children> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + #include <include/ospfv3/linkstate-id.xml.i> + </children> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/include/ospfv3/border-routers.xml.i b/op-mode-definitions/include/ospfv3/border-routers.xml.i new file mode 100644 index 0000000..e8827a2 --- /dev/null +++ b/op-mode-definitions/include/ospfv3/border-routers.xml.i @@ -0,0 +1,20 @@ +<!-- included start from ospfv3/border-routers.xml.i --> +<node name="border-routers"> + <properties> + <help>Show OSPFv3 border-router (ABR and ASBR) information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/frr-detail.xml.i> + </children> +</node> +<tagNode name="border-routers"> + <properties> + <help>Border router ID</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/include/ospfv3/database.xml.i b/op-mode-definitions/include/ospfv3/database.xml.i new file mode 100644 index 0000000..fdc45f1 --- /dev/null +++ b/op-mode-definitions/include/ospfv3/database.xml.i @@ -0,0 +1,238 @@ +<!-- included start from ospfv3/database.xml.i --> +<node name="database"> + <properties> + <help>Show OSPFv3 Link state database information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <tagNode name="adv-router"> + <properties> + <help>Search by Advertising Router ID</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <children> + #include <include/ospfv3/linkstate-id.xml.i> + </children> + </tagNode> + <node name="any"> + <properties> + <help>Search by Any Link state Type</help> + </properties> + <children> + <tagNode name="any"> + <properties> + <help>Search by Link state ID</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <children> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + </children> + </tagNode> + </children> + </node> + <tagNode name="any"> + <properties> + <help>Search by Link state ID</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>vtysh -c "show ipv6 ospf6 database * $6"</command> + <children> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + #include <include/ospfv3/adv-router-id-node-tag.xml.i> + </children> + </tagNode> + <node name="as-external"> + <properties> + <help>Show AS-External LSAs</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/ospfv3/adv-router.xml.i> + <tagNode name="any"> + <properties> + <help>Search by Advertising Router ID</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>vtysh -c "show ipv6 ospf6 database as-external * $7"</command> + <children> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + </children> + </tagNode> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + #include <include/ospfv3/linkstate-id.xml.i> + #include <include/ospfv3/self-originated.xml.i> + </children> + </node> + <tagNode name="as-external"> + <properties> + <help>Search by Advertising Router IDs</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <children> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + #include <include/ospfv3/self-originated.xml.i> + #include <include/ospfv3/adv-router-id-node-tag.xml.i> + </children> + </tagNode> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/internal.xml.i> + #include <include/ospfv3/linkstate-id.xml.i> + #include <include/ospfv3/self-originated.xml.i> + <node name="group-membership"> + <properties> + <help>Show Group-Membership LSAs</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/ospfv3/adv-router.xml.i> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + #include <include/ospfv3/linkstate-id.xml.i> + #include <include/ospfv3/linkstate-id-node-tag.xml.i> + #include <include/ospfv3/self-originated.xml.i> + </children> + </node> + <node name="inter-prefix"> + <properties> + <help>Show Inter-Area-Prefix LSAs</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/ospfv3/adv-router.xml.i> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + #include <include/ospfv3/linkstate-id.xml.i> + #include <include/ospfv3/linkstate-id-node-tag.xml.i> + #include <include/ospfv3/self-originated.xml.i> + </children> + </node> + <node name="inter-router"> + <properties> + <help>Show Inter-Area-Router LSAs</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/ospfv3/adv-router.xml.i> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + #include <include/ospfv3/linkstate-id.xml.i> + #include <include/ospfv3/linkstate-id-node-tag.xml.i> + #include <include/ospfv3/self-originated.xml.i> + </children> + </node> + <node name="intra-prefix"> + <properties> + <help>Show Intra-Area-Prefix LSAs</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/ospfv3/adv-router.xml.i> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + #include <include/ospfv3/linkstate-id.xml.i> + #include <include/ospfv3/linkstate-id-node-tag.xml.i> + #include <include/ospfv3/self-originated.xml.i> + </children> + </node> + <node name="link"> + <properties> + <help>Show Link LSAs</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/ospfv3/adv-router.xml.i> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + #include <include/ospfv3/linkstate-id.xml.i> + #include <include/ospfv3/linkstate-id-node-tag.xml.i> + #include <include/ospfv3/self-originated.xml.i> + </children> + </node> + <node name="network"> + <properties> + <help>Show Network LSAs</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/ospfv3/adv-router.xml.i> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + #include <include/ospfv3/linkstate-id.xml.i> + #include <include/ospfv3/linkstate-id-node-tag.xml.i> + #include <include/ospfv3/self-originated.xml.i> + </children> + </node> + <node name="node.tag"> + <properties> + <help>Show LSAs</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/ospfv3/adv-router.xml.i> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + #include <include/ospfv3/linkstate-id.xml.i> + #include <include/ospfv3/linkstate-id-node-tag.xml.i> + #include <include/ospfv3/self-originated.xml.i> + </children> + </node> + <node name="router"> + <properties> + <help>Show router LSAs</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/ospfv3/adv-router.xml.i> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + #include <include/ospfv3/linkstate-id.xml.i> + #include <include/ospfv3/linkstate-id-node-tag.xml.i> + #include <include/ospfv3/self-originated.xml.i> + </children> + </node> + <node name="type-7"> + <properties> + <help>Show Type-7 LSAs</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/ospfv3/adv-router.xml.i> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + #include <include/ospfv3/linkstate-id.xml.i> + #include <include/ospfv3/linkstate-id-node-tag.xml.i> + #include <include/ospfv3/self-originated.xml.i> + </children> + </node> + </children> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/ospfv3/dump.xml.i b/op-mode-definitions/include/ospfv3/dump.xml.i new file mode 100644 index 0000000..55e10a5 --- /dev/null +++ b/op-mode-definitions/include/ospfv3/dump.xml.i @@ -0,0 +1,8 @@ +<!-- included start from ospfv3/dump.xml.i --> +<node name="dump"> + <properties> + <help>Show dump of LSAs</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/ospfv3/interface.xml.i b/op-mode-definitions/include/ospfv3/interface.xml.i new file mode 100644 index 0000000..45d5dbd --- /dev/null +++ b/op-mode-definitions/include/ospfv3/interface.xml.i @@ -0,0 +1,75 @@ +<!-- included start from ospfv3/interface.xml.i --> +<node name="interface"> + <properties> + <help>Show OSPFv3 interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="prefix"> + <properties> + <help>Show connected prefixes to advertise</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/frr-detail.xml.i> + </children> + </node> + <tagNode name="prefix"> + <properties> + <help>Show interface prefix route specific information</help> + <completionHelp> + <list><h:h:h:h:h:h:h:h> <h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/frr-detail.xml.i> + <node name="match"> + <properties> + <help>Matched interface prefix information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + </children> + </tagNode> + </children> +</node> +<tagNode name="interface"> + <properties> + <help>Specific insterface to examine</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="prefix"> + <properties> + <help>Show connected prefixes to advertise</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/frr-detail.xml.i> + </children> + </node> + <tagNode name="prefix"> + <properties> + <help>Show interface prefix route specific information</help> + <completionHelp> + <list><h:h:h:h:h:h:h:h> <h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/frr-detail.xml.i> + <node name="match"> + <properties> + <help>Matched interface prefix information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + </children> + </tagNode> + </children> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/include/ospfv3/internal.xml.i b/op-mode-definitions/include/ospfv3/internal.xml.i new file mode 100644 index 0000000..ac7c61e --- /dev/null +++ b/op-mode-definitions/include/ospfv3/internal.xml.i @@ -0,0 +1,8 @@ +<!-- included start from ospfv3/internal.xml.i --> +<node name="internal"> + <properties> + <help>Show internal LSA information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/ospfv3/linkstate-id-node-tag.xml.i b/op-mode-definitions/include/ospfv3/linkstate-id-node-tag.xml.i new file mode 100644 index 0000000..66674e7 --- /dev/null +++ b/op-mode-definitions/include/ospfv3/linkstate-id-node-tag.xml.i @@ -0,0 +1,17 @@ +<!-- included start from ospfv3/linkstate-id-node-tag.xml.i --> +<node name="node.tag"> + <properties> + <help>Search by Link state ID</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + #include <include/ospfv3/self-originated.xml.i> + </children> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/ospfv3/linkstate-id.xml.i b/op-mode-definitions/include/ospfv3/linkstate-id.xml.i new file mode 100644 index 0000000..aa226c9 --- /dev/null +++ b/op-mode-definitions/include/ospfv3/linkstate-id.xml.i @@ -0,0 +1,15 @@ +<!-- included start from ospfv3/linkstate-id.xml.i --> +<tagNode name="linkstate-id"> + <properties> + <help>Search by Link state ID</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <children> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + </children> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/include/ospfv3/linkstate.xml.i b/op-mode-definitions/include/ospfv3/linkstate.xml.i new file mode 100644 index 0000000..030dc79 --- /dev/null +++ b/op-mode-definitions/include/ospfv3/linkstate.xml.i @@ -0,0 +1,38 @@ +<!-- included start from ospfv3/linkstate.xml.i --> +<node name="linkstate"> + <properties> + <help>Show OSPFv3 linkstate routing information</help> + </properties> + <children> + #include <include/frr-detail.xml.i> + <tagNode name="network"> + <properties> + <help>Show linkstate Network information</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <children> + <node name="node.tag"> + <properties> + <help>Specify Link state ID as IPv4 address notation</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + </children> + </tagNode> + <tagNode name="router"> + <properties> + <help>Show linkstate Router information</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + </children> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/ospfv3/neighbor.xml.i b/op-mode-definitions/include/ospfv3/neighbor.xml.i new file mode 100644 index 0000000..b736270 --- /dev/null +++ b/op-mode-definitions/include/ospfv3/neighbor.xml.i @@ -0,0 +1,17 @@ +<!-- included start from ospfv3/neighbor.xml.i --> +<node name="neighbor"> + <properties> + <help>Show OSPFv3 neighbor information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/frr-detail.xml.i> + <node name="drchoice"> + <properties> + <help>Show neighbor DR choice information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + </children> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/ospfv3/redistribute.xml.i b/op-mode-definitions/include/ospfv3/redistribute.xml.i new file mode 100644 index 0000000..1c2d649 --- /dev/null +++ b/op-mode-definitions/include/ospfv3/redistribute.xml.i @@ -0,0 +1,8 @@ +<!-- included start from ospfv3/redistribute.xml.i --> +<node name="redistribute"> + <properties> + <help>Show OSPFv3 redistribute external information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/ospfv3/route.xml.i b/op-mode-definitions/include/ospfv3/route.xml.i new file mode 100644 index 0000000..a5b97cd --- /dev/null +++ b/op-mode-definitions/include/ospfv3/route.xml.i @@ -0,0 +1,79 @@ +<!-- included start from ospfv3/route.xml.i --> +<node name="route"> + <properties> + <help>Show OSPFv3 routing table information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="external-1"> + <properties> + <help>Show Type-1 External route information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/frr-detail.xml.i> + </children> + </node> + <node name="external-2"> + <properties> + <help>Show Type-2 External route information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/frr-detail.xml.i> + </children> + </node> + <node name="inter-area"> + <properties> + <help>Show Inter-Area route information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/frr-detail.xml.i> + </children> + </node> + <node name="intra-area"> + <properties> + <help>Show Intra-Area route information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/frr-detail.xml.i> + </children> + </node> + #include <include/frr-detail.xml.i> + <node name="summary"> + <properties> + <help>Show route table summary</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + </children> +</node> +<tagNode name="route"> + <properties> + <help>Show specified route/prefix information</help> + <completionHelp> + <list><h:h:h:h:h:h:h:h> <h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="longer"> + <properties> + <help>Show routes longer than specified prefix</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + <node name="match"> + <properties> + <help>Show routes matching specified prefix</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/frr-detail.xml.i> + </children> + </node> + </children> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/include/ospfv3/self-originated.xml.i b/op-mode-definitions/include/ospfv3/self-originated.xml.i new file mode 100644 index 0000000..7549ccb --- /dev/null +++ b/op-mode-definitions/include/ospfv3/self-originated.xml.i @@ -0,0 +1,13 @@ +<!-- included start from ospfv3/self-originated.xml.i --> +<node name="self-originated"> + <properties> + <help>Show Self-originated LSAs</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/frr-detail.xml.i> + #include <include/ospfv3/dump.xml.i> + #include <include/ospfv3/internal.xml.i> + </children> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/rule-resequence.xml.i b/op-mode-definitions/include/rule-resequence.xml.i new file mode 100644 index 0000000..987bf63 --- /dev/null +++ b/op-mode-definitions/include/rule-resequence.xml.i @@ -0,0 +1,30 @@ +<!-- included start from show-nht.xml.i --> +<node name="rule-resequence"> + <properties> + <help>Resequence rules</help> + </properties> + <command>${vyos_op_scripts_dir}/generate_service_rule-resequence.py --service $2</command> + <children> + <tagNode name="start"> + <properties> + <help>Set the first sequence number</help> + <completionHelp> + <list>1-1000</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/generate_service_rule-resequence.py --service $2 --start $5</command> + <children> + <tagNode name="step"> + <properties> + <help>Step between rules</help> + <completionHelp> + <list>1-1000</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/generate_service_rule-resequence.py --service $2 --start $5 --step $7</command> + </tagNode> + </children> + </tagNode> + </children> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/show-nht.xml.i b/op-mode-definitions/include/show-nht.xml.i new file mode 100644 index 0000000..55dacf3 --- /dev/null +++ b/op-mode-definitions/include/show-nht.xml.i @@ -0,0 +1,20 @@ +<!-- included start from show-nht.xml.i --> +<node name="nht"> + <properties> + <help>Show Nexthop tracking table</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <tagNode name="vrf"> + <properties> + <help>Specify the VRF</help> + <completionHelp> + <path>vrf name</path> + <list>all default</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + </children> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-bgp.xml.i b/op-mode-definitions/include/show-route-bgp.xml.i new file mode 100644 index 0000000..5c26bf4 --- /dev/null +++ b/op-mode-definitions/include/show-route-bgp.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-bgp.xml.i --> +<leafNode name="bgp"> + <properties> + <help>Border Gateway Protocol (BGP)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-connected.xml.i b/op-mode-definitions/include/show-route-connected.xml.i new file mode 100644 index 0000000..37364de --- /dev/null +++ b/op-mode-definitions/include/show-route-connected.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-connected.xml.i --> +<leafNode name="connected"> + <properties> + <help>Connected routes (directly attached subnet or host)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-isis.xml.i b/op-mode-definitions/include/show-route-isis.xml.i new file mode 100644 index 0000000..9ff2ccd --- /dev/null +++ b/op-mode-definitions/include/show-route-isis.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-isis.xml.i --> +<leafNode name="isis"> + <properties> + <help>Intermediate System to Intermediate System (IS-IS)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-kernel.xml.i b/op-mode-definitions/include/show-route-kernel.xml.i new file mode 100644 index 0000000..8c5ac41 --- /dev/null +++ b/op-mode-definitions/include/show-route-kernel.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-kernel.xml.i --> +<leafNode name="kernel"> + <properties> + <help>Kernel routes (not installed via the zebra RIB)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-openfabric.xml.i b/op-mode-definitions/include/show-route-openfabric.xml.i new file mode 100644 index 0000000..ae1ef38 --- /dev/null +++ b/op-mode-definitions/include/show-route-openfabric.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-openfabric.xml.i --> +<leafNode name="openfabric"> + <properties> + <help>OpenFabric routes</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-ospf.xml.i b/op-mode-definitions/include/show-route-ospf.xml.i new file mode 100644 index 0000000..1122aab --- /dev/null +++ b/op-mode-definitions/include/show-route-ospf.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-ospf.xml.i --> +<leafNode name="ospf"> + <properties> + <help>Open Shortest Path First (OSPFv2)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-ospfv3.xml.i b/op-mode-definitions/include/show-route-ospfv3.xml.i new file mode 100644 index 0000000..c7a11b7 --- /dev/null +++ b/op-mode-definitions/include/show-route-ospfv3.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-ospfv3.xml.i --> +<leafNode name="ospfv3"> + <properties> + <help>Open Shortest Path First (IPv6) (OSPFv3)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-rip.xml.i b/op-mode-definitions/include/show-route-rip.xml.i new file mode 100644 index 0000000..3c2fede --- /dev/null +++ b/op-mode-definitions/include/show-route-rip.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-rip.xml.i --> +<leafNode name="rip"> + <properties> + <help>Routing Information Protocol (RIP)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-ripng.xml.i b/op-mode-definitions/include/show-route-ripng.xml.i new file mode 100644 index 0000000..6e59cb0 --- /dev/null +++ b/op-mode-definitions/include/show-route-ripng.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-ripng.xml.i --> +<leafNode name="ripng"> + <properties> + <help>Routing Information Protocol next-generation (IPv6) (RIPng)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-static.xml.i b/op-mode-definitions/include/show-route-static.xml.i new file mode 100644 index 0000000..c2e3967 --- /dev/null +++ b/op-mode-definitions/include/show-route-static.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-static.xml.i --> +<leafNode name="static"> + <properties> + <help>Statically configured routes</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-supernets-only.xml.i b/op-mode-definitions/include/show-route-supernets-only.xml.i new file mode 100644 index 0000000..4d1e7c5 --- /dev/null +++ b/op-mode-definitions/include/show-route-supernets-only.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-supernets-only.xml.i --> +<leafNode name="supernets-only"> + <properties> + <help>Show supernet entries only</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-table.xml.i b/op-mode-definitions/include/show-route-table.xml.i new file mode 100644 index 0000000..c3cf82a --- /dev/null +++ b/op-mode-definitions/include/show-route-table.xml.i @@ -0,0 +1,17 @@ +<!-- included start from show-route-table.xml.i --> +<node name="table"> + <properties> + <help>Table to display</help> + </properties> +</node> +<tagNode name="table"> + <properties> + <help>The table number to display</help> + <completionHelp> + <list>all</list> + <path>protocols static table</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-tag.xml.i b/op-mode-definitions/include/show-route-tag.xml.i new file mode 100644 index 0000000..8bfa0ae --- /dev/null +++ b/op-mode-definitions/include/show-route-tag.xml.i @@ -0,0 +1,16 @@ +<!-- included start from show-route-tag.xml.i --> +<node name="tag"> + <properties> + <help>Show only routes with tag</help> + </properties> +</node> +<tagNode name="tag"> + <properties> + <help>Tag value</help> + <completionHelp> + <list><1-4294967295></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/include/vni-tagnode-all.xml.i b/op-mode-definitions/include/vni-tagnode-all.xml.i new file mode 100644 index 0000000..fabab19 --- /dev/null +++ b/op-mode-definitions/include/vni-tagnode-all.xml.i @@ -0,0 +1,12 @@ +<!-- included start from vni-tagnode-all.xml.i --> +<tagNode name="vni"> + <properties> + <help>VXLAN network identifier (VNI) number</help> + <completionHelp> + <list><1-16777215> all</list> + <script>${vyos_completion_dir}/list_vni.sh</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/include/vni-tagnode.xml.i b/op-mode-definitions/include/vni-tagnode.xml.i new file mode 100644 index 0000000..f5b99dc --- /dev/null +++ b/op-mode-definitions/include/vni-tagnode.xml.i @@ -0,0 +1,12 @@ +<!-- included start from vni-tagnode.xml.i --> +<tagNode name="vni"> + <properties> + <help>VXLAN network identifier (VNI) number</help> + <completionHelp> + <list><1-16777215></list> + <script>${vyos_completion_dir}/list_vni.sh</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/include/vtysh-generic-detail.xml.i b/op-mode-definitions/include/vtysh-generic-detail.xml.i new file mode 100644 index 0000000..d469f70 --- /dev/null +++ b/op-mode-definitions/include/vtysh-generic-detail.xml.i @@ -0,0 +1,8 @@ +<!-- included start from vtysh-generic-detail.xml.i --> +<leafNode name="detail"> + <properties> + <help>Detailed information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/vtysh-generic-interface-tagNode.xml.i b/op-mode-definitions/include/vtysh-generic-interface-tagNode.xml.i new file mode 100644 index 0000000..e959611 --- /dev/null +++ b/op-mode-definitions/include/vtysh-generic-interface-tagNode.xml.i @@ -0,0 +1,11 @@ +<!-- included start from vtysh-generic-interface.xml.i --> +<tagNode name="interface"> + <properties> + <help>Show information about specific interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/include/vtysh-generic-wide.xml.i b/op-mode-definitions/include/vtysh-generic-wide.xml.i new file mode 100644 index 0000000..acc68b4 --- /dev/null +++ b/op-mode-definitions/include/vtysh-generic-wide.xml.i @@ -0,0 +1,8 @@ +<!-- included start from vtysh-generic-wide.xml.i --> +<leafNode name="wide"> + <properties> + <help>Increase table width for longer prefixes</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/install-mok.xml.in b/op-mode-definitions/install-mok.xml.in new file mode 100644 index 0000000..18526a3 --- /dev/null +++ b/op-mode-definitions/install-mok.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="install"> + <children> + <leafNode name="mok"> + <properties> + <help>Install Secure Boot MOK (Machine Owner Key)</help> + </properties> + <command>if test -f /var/lib/shim-signed/mok/MOK.der; then sudo mokutil --ignore-keyring --import /var/lib/shim-signed/mok/MOK.der; else echo "Secure Boot Machine Owner Key not found"; fi</command> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/ipoe-server.xml.in b/op-mode-definitions/ipoe-server.xml.in new file mode 100644 index 0000000..3aee303 --- /dev/null +++ b/op-mode-definitions/ipoe-server.xml.in @@ -0,0 +1,81 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="reset"> + <children> + <node name="ipoe-server"> + <properties> + <help>IPoE (Internet Protocol over Ethernet) server</help> + </properties> + <children> + <node name="session"> + <properties> + <help>Clear IPoE (Internet Protocol over Ethernet) server session</help> + </properties> + <children> + <tagNode name="username"> + <properties> + <help>Clear IPoE server session by username</help> + <completionHelp> + <script>${vyos_completion_dir}/list_ipoe.py --selector="username"</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/ipoe-control.py --action="terminate" --selector="username" --target="$5"</command> + </tagNode> + <tagNode name="sid"> + <properties> + <help>Clear IPoE server session by Session ID</help> + <completionHelp> + <script>${vyos_completion_dir}/list_ipoe.py --selector="sid"</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/ipoe-control.py --action="terminate" --selector="sid" --target="$5"</command> + </tagNode> + <tagNode name="interface"> + <properties> + <help>Clear IPoE server session by interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_ipoe.py --selector="ifname"</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/ipoe-control.py --action="terminate" --selector="if" --target="$5"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="show"> + <children> + <node name="ipoe-server"> + <properties> + <help>Show IPoE (Internet Protocol over Ethernet) server status</help> + </properties> + <children> + <leafNode name="sessions"> + <properties> + <help>Show active IPoE server sessions</help> + </properties> + <command>${vyos_op_scripts_dir}/ipoe-control.py --action="show_sessions"</command> + </leafNode> + <leafNode name="statistics"> + <properties> + <help>Show IPoE server statistics</help> + </properties> + <command>${vyos_op_scripts_dir}/ipoe-control.py --action="show_stat"</command> + </leafNode> + </children> + </node> + </children> + </node> + <node name="restart"> + <children> + <leafNode name="ipoe-server"> + <properties> + <help>Restart IPoE (Internet Protocol over Ethernet) server process</help> + </properties> + <command>${vyos_op_scripts_dir}/ipoe-control.py --action="restart"</command> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/ipv4-route.xml.in b/op-mode-definitions/ipv4-route.xml.in new file mode 100644 index 0000000..17a0a4a --- /dev/null +++ b/op-mode-definitions/ipv4-route.xml.in @@ -0,0 +1,87 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <properties> + <help>Show system information</help> + </properties> + <children> + <node name="ip"> + <properties> + <help>Show IPv4 information</help> + </properties> + <children> + <leafNode name="groups"> + <properties> + <help>Show IP multicast group membership</help> + </properties> + <command>netstat -gn4</command> + </leafNode> + </children> + </node> + </children> + </node> + <node name="reset"> + <children> + <node name="ip"> + <properties> + <help>Reset Internet Protocol (IP) parameters</help> + </properties> + <children> + <node name="arp"> + <properties> + <help>Reset Address Resolution Protocol (ARP) cache</help> + </properties> + <children> + <tagNode name="address"> + <properties> + <help>Reset ARP cache for an IPv4 address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/neighbor.py reset --family inet --address "$5"</command> + </tagNode> + <tagNode name="interface"> + <properties> + <help>Reset ARP cache for interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/neighbor.py reset --family inet --interface "$5"</command> + </tagNode> + <node name="table"> + <properties> + <help>Flush the ARP cache completely</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/neighbor.py reset --family inet</command> + </node> + </children> + </node> + <node name="route"> + <properties> + <help>Reset IP route</help> + </properties> + <children> + <leafNode name= "cache"> + <properties> + <help>Flush the kernel route cache</help> + </properties> + <command>sudo ip route flush cache</command> + </leafNode> + <tagNode name="cache"> + <properties> + <help>Flush the kernel route cache for a given route</help> + <completionHelp> + <list><x.x.x.x> <x.x.x.x/x></list> + </completionHelp> + </properties> + <command>sudo ip route flush cache "$5"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/ipv6-route.xml.in b/op-mode-definitions/ipv6-route.xml.in new file mode 100644 index 0000000..5ed0b9d --- /dev/null +++ b/op-mode-definitions/ipv6-route.xml.in @@ -0,0 +1,107 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <properties> + <help>Show system information</help> + </properties> + <children> + <node name="ipv6"> + <properties> + <help>Show IPv6 networking information</help> + </properties> + <children> + <leafNode name="groups"> + <properties> + <help>Show IPv6 multicast group membership</help> + </properties> + <command>netstat -gn6</command> + </leafNode> + <node name="neighbors"> + <properties> + <help>Show IPv6 neighbor (NDP) table</help> + </properties> + <command>${vyos_op_scripts_dir}/neighbor.py show --family inet6</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show IPv6 neighbor table for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --broadcast</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/neighbor.py show --family inet6 --interface "$5"</command> + </tagNode> + <tagNode name="state"> + <properties> + <help>Show IPv6 neighbors with specified state</help> + <completionHelp> + <list>reachable stale failed permanent</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/neighbor.py show --family inet6 --state "$5"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="reset"> + <children> + <node name="ipv6"> + <properties> + <help>Reset Internet Protocol version 6 (IPv6) parameters</help> + </properties> + <children> + <node name="neighbors"> + <properties> + <help>Reset IPv6 Neighbor Discovery (ND) cache</help> + </properties> + <children> + <tagNode name="address"> + <properties> + <help>Reset ND cache for an IPv6 address</help> + <completionHelp> + <list><h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>sudo ip -f inet6 neigh flush to "$5"</command> + </tagNode> + <tagNode name="interface"> + <properties> + <help>Reset IPv6 ND cache for interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>sudo ip -f inet6 neigh flush dev "$5"</command> + </tagNode> + </children> + </node> + <node name="route"> + <properties> + <help>Reset IPv6 route</help> + </properties> + <children> + <leafNode name= "cache"> + <properties> + <help>Flush the kernel IPv6 route cache</help> + </properties> + <command>sudo ip -f inet6 route flush cache</command> + </leafNode> + <tagNode name="cache"> + <properties> + <help>Flush the kernel IPv6 route cache for a given route</help> + <completionHelp> + <list><h:h:h:h:h:h:h:h> <h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <command>sudo ip -f inet6 route flush cache "$5"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/l2tp-server.xml.in b/op-mode-definitions/l2tp-server.xml.in new file mode 100644 index 0000000..3e96b93 --- /dev/null +++ b/op-mode-definitions/l2tp-server.xml.in @@ -0,0 +1,26 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="l2tp-server"> + <properties> + <help>Show L2TP server information</help> + </properties> + <children> + <leafNode name="sessions"> + <properties> + <help>Show active L2TP server sessions</help> + </properties> + <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="l2tp" --action="show sessions"</command> + </leafNode> + <leafNode name="statistics"> + <properties> + <help>Show L2TP server statistics</help> + </properties> + <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="l2tp" --action="show stat"</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/lldp.xml.in b/op-mode-definitions/lldp.xml.in new file mode 100644 index 0000000..dc1331c --- /dev/null +++ b/op-mode-definitions/lldp.xml.in @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="lldp"> + <properties> + <help>Show LLDP (Link Layer Discovery Protocol)</help> + </properties> + <children> + <node name="neighbors"> + <properties> + <help>Show LLDP neighbors</help> + </properties> + <command>${vyos_op_scripts_dir}/lldp.py show_neighbors</command> + <children> + <node name="detail"> + <properties> + <help>Show extended detail for LLDP neighbors</help> + </properties> + <command>${vyos_op_scripts_dir}/lldp.py show_neighbors --detail</command> + </node> + <tagNode name="interface"> + <properties> + <help>Show LLDP for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/lldp.py show_neighbors --interface $5</command> + <children> + <node name="detail"> + <properties> + <help>Show detailed LLDP for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/lldp.py show_neighbors --interface $5 --detail</command> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/mdns-reflector.xml.in b/op-mode-definitions/mdns-reflector.xml.in new file mode 100644 index 0000000..115b285 --- /dev/null +++ b/op-mode-definitions/mdns-reflector.xml.in @@ -0,0 +1,62 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="monitor"> + <children> + <node name="log"> + <children> + <node name="mdns"> + <properties> + <help>Monitor last lines of multicast Domain Name System related services</help> + </properties> + <children> + <node name="repeater"> + <properties> + <help>Monitor last lines of mDNS repeater service</help> + </properties> + <command>journalctl --no-hostname --follow --boot --unit avahi-daemon.service</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="show"> + <children> + <node name="log"> + <children> + <node name="mdns"> + <properties> + <help>Show log for multicast Domain Name System related services</help> + </properties> + <children> + <node name="repeater"> + <properties> + <help>Show log for mDNS repeater service</help> + </properties> + <command>journalctl --no-hostname --boot --unit avahi-daemon.service</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="restart"> + <children> + <node name="mdns"> + <properties> + <help>Restart specific multicast Domain Name System service</help> + </properties> + <children> + <node name="repeater"> + <properties> + <help>Restart mDNS repeater service</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name mdns_repeater</command> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/monitor-bandwidth.xml.in b/op-mode-definitions/monitor-bandwidth.xml.in new file mode 100644 index 0000000..fc1d751 --- /dev/null +++ b/op-mode-definitions/monitor-bandwidth.xml.in @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="monitor"> + <children> + <node name="bandwidth"> + <properties> + <help>Monitor interface bandwidth in real time</help> + </properties> + <command>bmon --use-bit</command> + <children> + <tagNode name="interface"> + <command>bmon --use-bit --policy $4</command> + <properties> + <help>Monitor bandwidth usage on specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/monitor-bridge.xml.in b/op-mode-definitions/monitor-bridge.xml.in new file mode 100644 index 0000000..a43fa6d --- /dev/null +++ b/op-mode-definitions/monitor-bridge.xml.in @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="monitor"> + <children> + <node name="bridge"> + <properties> + <help>Monitor bridge database changes</help> + </properties> + <command>sudo bridge monitor all</command> + <children> + <node name="link"> + <command>sudo bridge monitor link</command> + <properties> + <help>Monitor bridge database generated connection interface changes</help> + </properties> + </node> + <node name="fdb"> + <command>sudo bridge monitor fdb</command> + <properties> + <help>Monitor the forwarding database changes generated by the bridge database</help> + </properties> + </node> + <node name="mdb"> + <command>sudo bridge monitor mdb</command> + <properties> + <help>Monitor the multicast database changes generated by the bridge database</help> + </properties> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/monitor-command.xml.in b/op-mode-definitions/monitor-command.xml.in new file mode 100644 index 0000000..31c68f0 --- /dev/null +++ b/op-mode-definitions/monitor-command.xml.in @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="monitor"> + <children> + <tagNode name="command"> + <properties> + <help>Monitor operational mode command (refreshes every 2 seconds)</help> + </properties> + <command>watch --no-title ${vyos_op_scripts_dir}/vyos-op-cmd-wrapper.sh ${@:3}</command> + </tagNode> + <node name="command"> + <children> + <node name="diff"> + <properties> + <help>Show differences during each run</help> + </properties> + </node> + <tagNode name="diff"> + <properties> + <help>Monitor operational mode command (refreshes every 2 seconds)</help> + </properties> + <command>watch --no-title --differences ${vyos_op_scripts_dir}/vyos-op-cmd-wrapper.sh ${@:4}</command> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in new file mode 100644 index 0000000..6a2b7e5 --- /dev/null +++ b/op-mode-definitions/monitor-log.xml.in @@ -0,0 +1,413 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="monitor"> + <properties> + <help>Monitor system information</help> + </properties> + <children> + <node name="log"> + <properties> + <help>Monitor last lines of messages file</help> + </properties> + <command>SYSTEMD_LOG_COLOR=false journalctl --no-hostname --follow --boot</command> + <children> + <node name="color"> + <properties> + <help>Output log in a colored fashion</help> + </properties> + <command>SYSTEMD_LOG_COLOR=false grc journalctl --no-hostname --follow --boot</command> + </node> + <node name="ids"> + <properties> + <help>Monitor Intrusion Detection System log</help> + </properties> + <children> + <leafNode name="ddos-protection"> + <properties> + <help>Monitor last lines of DDOS protection</help> + </properties> + <command>journalctl --no-hostname --follow --boot --unit fastnetmon.service</command> + </leafNode> + </children> + </node> + <leafNode name="certbot"> + <properties> + <help>Monitor last lines of certbot log</help> + </properties> + <command>if sudo test -f /var/log/letsencrypt/letsencrypt.log; then sudo tail --follow=name /var/log/letsencrypt/letsencrypt.log; else echo "Cerbot log does not exist"; fi</command> + </leafNode> + <leafNode name="conntrack-sync"> + <properties> + <help>Monitor last lines of conntrack-sync log</help> + </properties> + <command>journalctl --no-hostname --follow --boot --unit conntrackd.service</command> + </leafNode> + <leafNode name="console-server"> + <properties> + <help>Monitor last lines of console server log</help> + </properties> + <command>journalctl --no-hostname --follow --boot --unit conserver-server.service</command> + </leafNode> + <node name="dhcp"> + <properties> + <help>Monitor last lines of Dynamic Host Control Protocol log</help> + </properties> + <children> + <node name="server"> + <properties> + <help>Monitor last lines of DHCP server log</help> + </properties> + <command>journalctl --no-hostname --follow --boot --unit kea-dhcp4-server.service</command> + </node> + <node name="client"> + <properties> + <help>Monitor last lines of DHCP client log</help> + </properties> + <command>journalctl --no-hostname --follow --boot --unit "dhclient@*.service"</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show DHCP client log on specific interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --broadcast</script> + </completionHelp> + </properties> + <command>journalctl --no-hostname --follow --boot --unit "dhclient@$6.service"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="dhcpv6"> + <properties> + <help>Monitor last lines of Dynamic Host Control Protocol IPv6 log</help> + </properties> + <children> + <node name="server"> + <properties> + <help>Monitor last lines of DHCPv6 server log</help> + </properties> + <command>journalctl --no-hostname --follow --boot --unit kea-dhcp6-server.service</command> + </node> + <node name="client"> + <properties> + <help>Monitor last lines of DHCPv6 client log</help> + </properties> + <command>journalctl --no-hostname --follow --boot --unit "dhcp6c@*.service"</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show DHCPv6 client log on specific interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>journalctl --no-hostname --follow --boot --unit "dhcp6c@$6.service"</command> + </tagNode> + </children> + </node> + </children> + </node> + <leafNode name="flow-accounting"> + <properties> + <help>Monitor last lines of flow-accounting log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit uacctd.service</command> + </leafNode> + <leafNode name="ipoe-server"> + <properties> + <help>Monitor last lines of IP over Ethernet server log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit accel-ppp@ipoe.service</command> + </leafNode> + <leafNode name="kernel"> + <properties> + <help>Monitor last lines of Linux Kernel log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --dmesg</command> + </leafNode> + <leafNode name="ndp-proxy"> + <properties> + <help>Monitor last lines of Neighbor Discovery Protocol (NDP) Proxy</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit ndppd.service</command> + </leafNode> + <leafNode name="nhrp"> + <properties> + <help>Monitor last lines of Next Hop Resolution Protocol log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit opennhrp.service</command> + </leafNode> + <leafNode name="ntp"> + <properties> + <help>Monitor last lines of Network Time Protocol log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit chrony.service</command> + </leafNode> + <node name="openvpn"> + <properties> + <help>Monitor last lines of OpenVPN log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit openvpn@*.service</command> + <children> + <tagNode name="interface"> + <properties> + <help>Monitor last lines of specific OpenVPN interface log</help> + <completionHelp> + <path>interfaces openvpn</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot --unit openvpn@$5.service</command> + </tagNode> + </children> + </node> + <node name="pppoe"> + <properties> + <help>Monitor last lines of PPPoE interface log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit "ppp@pppoe*.service"</command> + <children> + <tagNode name="interface"> + <properties> + <help>Monitor last lines of PPPoE log for specific interface</help> + <completionHelp> + <path>interfaces pppoe</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot --follow --unit "ppp@$5.service"</command> + </tagNode> + </children> + </node> + <leafNode name="pppoe-server"> + <properties> + <help>Monitor last lines of PPPoE server log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit accel-ppp@pppoe.service</command> + </leafNode> + <node name="protocol"> + <properties> + <help>Monitor routing protocol logs</help> + </properties> + <children> + <leafNode name="ospf"> + <properties> + <help>Monitor log for OSPF</help> + </properties> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/ospfd</command> + </leafNode> + <leafNode name="ospfv3"> + <properties> + <help>Monitor log for OSPF for IPv6</help> + </properties> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/ospf6d</command> + </leafNode> + <leafNode name="bgp"> + <properties> + <help>Monitor log for BGP</help> + </properties> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/bgpd</command> + </leafNode> + <leafNode name="rip"> + <properties> + <help>Monitor log for RIP</help> + </properties> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/ripd</command> + </leafNode> + <leafNode name="ripng"> + <properties> + <help>Monitor log for RIPng</help> + </properties> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/ripngd</command> + </leafNode> + <leafNode name="static"> + <properties> + <help>Monitor log for static route</help> + </properties> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/staticd</command> + </leafNode> + <leafNode name="multicast"> + <properties> + <help>Monitor log for Multicast protocol</help> + </properties> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/pimd</command> + </leafNode> + <leafNode name="isis"> + <properties> + <help>Monitor log for ISIS</help> + </properties> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/isisd</command> + </leafNode> + <leafNode name="openfabric"> + <properties> + <help>Monitor log for OpenFabric</help> + </properties> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/fabricd</command> + </leafNode> + <leafNode name="nhrp"> + <properties> + <help>Monitor log for NHRP</help> + </properties> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/nhrpd</command> + </leafNode> + <leafNode name="bfd"> + <properties> + <help>Monitor log for BFD</help> + </properties> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/bfdd</command> + </leafNode> + <leafNode name="mpls"> + <properties> + <help>Monitor log for MPLS</help> + </properties> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/ldpd</command> + </leafNode> + </children> + </node> + <node name="macsec"> + <properties> + <help>Monitor last lines of MACsec</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit "wpa_supplicant-macsec@*.service"</command> + <children> + <tagNode name="interface"> + <properties> + <help>Monitor last lines of specific MACsec interface</help> + <completionHelp> + <path>interfaces macsec</path> + </completionHelp> + </properties> + <command>SRC=$(cli-shell-api returnValue interfaces macsec "$5" source-interface); journalctl --no-hostname --boot --follow --unit "wpa_supplicant-macsec@$SRC.service"</command> + </tagNode> + </children> + </node> + <leafNode name="router-advert"> + <properties> + <help>Monitor last lines of Router Advertisement Daemon log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit radvd.service</command> + </leafNode> + <leafNode name="snmp"> + <properties> + <help>Monitor last lines of Simple Network Monitoring Protocol log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit snmpd.service</command> + </leafNode> + <node name="ssh"> + <properties> + <help>Monitor last lines of Secure Shell log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit ssh.service</command> + <children> + <node name="dynamic-protection"> + <properties> + <help>Monitor last lines of SSH guard log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit sshguard.service</command> + </node> + </children> + </node> + <leafNode name="vpn"> + <properties> + <help>Monitor last lines of ALL Virtual Private Network services</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit strongswan.service --unit accel-ppp@*.service --unit ocserv.service</command> + </leafNode> + <leafNode name="ipsec"> + <properties> + <help>Monitor last lines of IPsec log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit strongswan.service</command> + </leafNode> + <leafNode name="l2tp"> + <properties> + <help>Monitor last lines of L2TP log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit accel-ppp@l2tp.service</command> + </leafNode> + <leafNode name="openconnect"> + <properties> + <help>Monitor last lines of OpenConnect log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit ocserv.service</command> + </leafNode> + <leafNode name="pptp"> + <properties> + <help>Monitor last lines of PPTP log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit accel-ppp@pptp.service</command> + </leafNode> + <leafNode name="sstp"> + <properties> + <help>Monitor last lines of Secure Socket Tunneling Protocol server</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit accel-ppp@sstp.service</command> + </leafNode> + <node name="sstpc"> + <properties> + <help>Monitor last lines of Secure Socket Tunneling Protocol client</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit "ppp@sstpc*.service"</command> + <children> + <tagNode name="interface"> + <properties> + <help>Monitor last lines of SSTP client log for specific interface</help> + <completionHelp> + <path>interfaces sstpc</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot --follow --unit "ppp@$5.service"</command> + </tagNode> + </children> + </node> + <leafNode name="vrrp"> + <properties> + <help>Monitor last lines of Virtual Router Redundancy Protocol log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit keepalived.service</command> + </leafNode> + <node name="wireless"> + <properties> + <help>Monitor last lines of Wireless interface log</help> + </properties> + <children> + <node name="wpa-supplicant"> + <properties> + <help>Monitor last lines of WPA supplicant</help> + </properties> + <command>if cli-shell-api existsActive interfaces wireless; then journalctl --no-hostname --boot --follow --unit "wpa_supplicant@*.service"; else echo "No wireless interface configured!"; fi</command> + <children> + <tagNode name="interface"> + <properties> + <help>Monitor last lines of specific wireless interface supplicant</help> + <completionHelp> + <path>interfaces wireless</path> + </completionHelp> + </properties> + <command>if [[ $(cli-shell-api returnActiveValue interfaces wireless $6 type) == "station" ]]; then journalctl --no-hostname --boot --follow --unit "wpa_supplicant@$6.service"; else echo "Wireless interface $6 not configured as station!"; fi</command> + </tagNode> + </children> + </node> + <node name="hostapd"> + <properties> + <help>Monitor last lines of host access point daemon</help> + </properties> + <command>if cli-shell-api existsActive interfaces wireless; then journalctl --no-hostname --boot --follow --unit "hostapd@*.service"; else echo "No wireless interface configured!"; fi</command> + <children> + <tagNode name="interface"> + <properties> + <help>Monitor last lines of specific host access point interface</help> + <completionHelp> + <path>interfaces wireless</path> + </completionHelp> + </properties> + <command>if [[ $(cli-shell-api returnActiveValue interfaces wireless $6 type) == "access-point" ]]; then journalctl --no-hostname --boot --follow --unit "hostapd@$6.service"; else echo "Wireless interface $6 not configured as access-point!"; fi</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/monitor-ndp.xml.in b/op-mode-definitions/monitor-ndp.xml.in new file mode 100644 index 0000000..3b08f3d --- /dev/null +++ b/op-mode-definitions/monitor-ndp.xml.in @@ -0,0 +1,44 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="monitor"> + <children> + <node name="ndp"> + <properties> + <help>Monitor Neighbor Discovery Protocol (NDP) information</help> + </properties> + <command>sudo ndptool monitor</command> + <children> + <tagNode name="interface"> + <command>sudo ndptool monitor --ifname=$4</command> + <properties> + <help>Monitor Neighbor Discovery Protocol on specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + <tagNode name="type"> + <command>sudo ndptool monitor --ifname=$4 --msg-type=$6</command> + <properties> + <help>Monitor specific Neighbor Discovery Protocol type</help> + <completionHelp> + <list>rs ra ns na</list> + </completionHelp> + </properties> + </tagNode> + </children> + </tagNode> + <tagNode name="type"> + <command>sudo ndptool monitor --msg-type=$4</command> + <properties> + <help>Monitor specific Neighbor Discovery Protocol type</help> + <completionHelp> + <list>rs ra ns na</list> + </completionHelp> + </properties> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/monitor-protocol.xml.in b/op-mode-definitions/monitor-protocol.xml.in new file mode 100644 index 0000000..f05a194 --- /dev/null +++ b/op-mode-definitions/monitor-protocol.xml.in @@ -0,0 +1,1504 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="monitor"> + <children> + <node name="protocol"> + <properties> + <help>Monitor routing protocols</help> + </properties> + <children> + <node name="bgp"> + <properties> + <help>Monitor the Border Gateway Protocol (BGP)</help> + </properties> + <children> + <node name="disable"> + <properties> + <help>Disable Border Gateway Protocol (BGP) debugging</help> + </properties> + <children> + <node name="all"> + <properties> + <help>Disable all BGP debugging</help> + </properties> + <command>vtysh -c "no debug bgp"</command> + </node> + <node name="allow-martians"> + <properties> + <help>Disable BGP martians next hops debugging</help> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + </node> + <node name="as4"> + <properties> + <help>Disable BGP allow AS4 actions debugging</help> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + </node> + <node name="bestpath"> + <properties> + <help>Disable BGP allow best path debugging</help> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + </node> + <tagNode name="bestpath"> + <properties> + <help>Disable BGP bestpath IPv4 IPv6</help> + <completionHelp> + <list><x.x.x.x/x> <h:h:h:h:h:h:h:h/h></list> + </completionHelp> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + </tagNode> + <node name="flowspec"> + <properties> + <help>Disable BGP allow flowspec debugging</help> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + </node> + <node name="keepalives"> + <properties> + <help>Disable BGP keepalives debugging</help> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + </node> + <node name="labelpool"> + <properties> + <help>Disable BGP label pool debugging</help> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + </node> + <node name="neighbor-events"> + <properties> + <help>Disable BGP Neighbor events debugging</help> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + </node> + <node name="nht"> + <properties> + <help>Disable BGP next hop tracking debugging</help> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + </node> + <node name="pbr"> + <properties> + <help>Disable BGP policy based routing debugging</help> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + </node> + <node name="rib"> + <properties> + <help>Disable BGP rib debugging</help> + </properties> + <command>vtysh -c "no debug bgp zebra"</command> + </node> + <node name="update-groups"> + <properties> + <help>Disable BGP update groups debugging</help> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + </node> + <node name="updates"> + <properties> + <help>Disable BGP updates debugging</help> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + </node> + <node name="vnc"> + <properties> + <help>Disable BGP VNC debugging</help> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + <children> + <node name="import-bi-attach"> + <properties> + <help>Disable BGP vnc import BI attachment debugging</help> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + </node> + <node name="import-del-remote"> + <properties> + <help>Disable BGP vnc import/delete remote routes debugging</help> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + </node> + <node name="rfapi-query"> + <properties> + <help>Disable BGP vnc rfapi query debugging</help> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + </node> + <node name="verbose"> + <properties> + <help>Disable BGP vnc verbose logging debugging</help> + </properties> + <command>vtysh -c "no debug bgp ${@:5}"</command> + </node> + </children> + </node> + </children> + </node> + <node name="enable"> + <properties> + <help>Enable Border Gateway Protocol (BGP) debugging</help> + </properties> + <children> + <node name="allow-martians"> + <properties> + <help>Enable BGP martians next hops debugging</help> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + </node> + <node name="as4"> + <properties> + <help>Enable BGP allow AS4 actions debugging</help> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + </node> + <node name="bestpath"> + <properties> + <help>Enable BGP allow best path debugging</help> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + </node> + <tagNode name="bestpath"> + <properties> + <help>Debug bestpath IPv4 IPv6</help> + <completionHelp> + <list><x.x.x.x/x> <h:h:h:h:h:h:h:h/h></list> + </completionHelp> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + </tagNode> + <node name="flowspec"> + <properties> + <help>Enable BGP allow flowspec debugging</help> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + </node> + <node name="keepalives"> + <properties> + <help>Enable BGP keepalives debugging</help> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + </node> + <node name="labelpool"> + <properties> + <help>Enable BGP label pool debugging</help> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + </node> + <node name="neighbor-events"> + <properties> + <help>Enable BGP Neighbor events debugging</help> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + </node> + <node name="nht"> + <properties> + <help>Enable BGP next hop tracking debugging</help> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + </node> + <node name="pbr"> + <properties> + <help>Enable BGP policy based routing debugging</help> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + </node> + <node name="rib"> + <properties> + <help>Enable BGP rib debugging</help> + </properties> + <command>vtysh -c "debug bgp zebra"</command> + </node> + <node name="update-groups"> + <properties> + <help>Enable BGP update groups debugging</help> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + </node> + <node name="updates"> + <properties> + <help>Enable BGP updates debugging</help> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + </node> + <node name="vnc"> + <properties> + <help>Enable BGP VNC debugging</help> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + <children> + <node name="import-bi-attach"> + <properties> + <help>Enable BGP vnc import BI attachment debugging</help> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + </node> + <node name="import-del-remote"> + <properties> + <help>Enable BGP vnc import/delete remote routes debugging</help> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + </node> + <node name="rfapi-query"> + <properties> + <help>Enable BGP vnc rfapi query debugging</help> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + </node> + <node name="verbose"> + <properties> + <help>Enable BGP vnc verbose logging debugging</help> + </properties> + <command>vtysh -c "debug bgp ${@:5}"</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="ospf"> + <properties> + <help>Monitor Open Shortest Path First (OSPF) protocol</help> + </properties> + <children> + <node name="disable"> + <properties> + <help>Disable Open Shortest Path First (OSPF) debugging</help> + </properties> + <children> + <node name="event"> + <properties> + <help>Disable OSPF debugging</help> + </properties> + <command>vtysh -c "no debug ospf"</command> + </node> + <node name="event"> + <properties> + <help>Disable OSPF event debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + </node> + <node name="ism"> + <properties> + <help>Disable OSPF ism debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + <children> + <node name="events"> + <properties> + <help>Disable OSPF ism events debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + </node> + <node name="status"> + <properties> + <help>Disable OSPF ism status debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + </node> + <node name="timers"> + <properties> + <help>Disable OSPF ism timers debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + </node> + </children> + </node> + <node name="lsa"> + <properties> + <help>Disable OSPF lsa debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + <children> + <node name="flooding"> + <properties> + <help>Disable OSPF lsa flooding debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + </node> + <node name="generate"> + <properties> + <help>Disable OSPF lsa generate debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + </node> + <node name="install"> + <properties> + <help>Disable OSPF lsa install debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + </node> + <node name="refresh"> + <properties> + <help>Disable OSPF lsa refresh debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + </node> + </children> + </node> + <node name="nsm"> + <properties> + <help>Disable OSPF nsm debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + <children> + <node name="events"> + <properties> + <help>Disable OSPF nsm events debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + </node> + <node name="status"> + <properties> + <help>Disable OSPF nsm status debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + </node> + <node name="timers"> + <properties> + <help>Disable OSPF nsm timers debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + </node> + </children> + </node> + <node name="nssa"> + <properties> + <help>Disable OSPF nssa debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + </node> + <node name="packet"> + <properties> + <help>Disable OSPF packet debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + <children> + <node name="all"> + <properties> + <help>Disable OSPF all packet debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + <children> + #include <include/monitor-no-ospf-packet-detail.xml.i> + </children> + </node> + <node name="dd"> + <properties> + <help>Disable OSPF dd packet debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + <children> + #include <include/monitor-no-ospf-packet-detail.xml.i> + </children> + </node> + <node name="hello"> + <properties> + <help>Disable OSPF hello packet debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + <children> + #include <include/monitor-no-ospf-packet-detail.xml.i> + </children> + </node> + <node name="ls-ack"> + <properties> + <help>Disable OSPF ls-ack packet debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + <children> + #include <include/monitor-no-ospf-packet-detail.xml.i> + </children> + </node> + <node name="ls-request"> + <properties> + <help>Disable OSPF ls-request packet debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + <children> + #include <include/monitor-no-ospf-packet-detail.xml.i> + </children> + </node> + <node name="ls-update"> + <properties> + <help>Disable OSPF ls-update packet debugging</help> + </properties> + <command>vtysh -c "no debug ospf ${@:5}"</command> + <children> + #include <include/monitor-no-ospf-packet-detail.xml.i> + </children> + </node> + </children> + </node> + <node name="rib"> + <properties> + <help>Disable OSPF rib debugging</help> + </properties> + <command>vtysh -c "no debug ospf zebra"</command> + <children> + <node name="interface"> + <properties> + <help>Disable OSPF rib interface debugging</help> + </properties> + <command>vtysh -c "no debug ospf zebra interface"</command> + </node> + <node name="redistribute"> + <properties> + <help>Disable OSPF rib redistribute debugging</help> + </properties> + <command>vtysh -c "no debug ospf zebra redistribute"</command> + </node> + </children> + </node> + </children> + </node> + <node name="enable"> + <properties> + <help>Enable Open Shortest Path First (OSPF) debugging</help> + </properties> + <children> + <node name="event"> + <properties> + <help>Enable OSPF event debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + </node> + <node name="ism"> + <properties> + <help>Enable OSPF ism debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + <children> + <node name="events"> + <properties> + <help>Enable OSPF ism events debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + </node> + <node name="status"> + <properties> + <help>Enable OSPF ism status debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + </node> + <node name="timers"> + <properties> + <help>Enable OSPF ism timers debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + </node> + </children> + </node> + <node name="lsa"> + <properties> + <help>Enable OSPF lsa debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + <children> + <node name="flooding"> + <properties> + <help>Enable OSPF lsa flooding debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + </node> + <node name="generate"> + <properties> + <help>Enable OSPF lsa generate debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + </node> + <node name="install"> + <properties> + <help>Enable OSPF lsa install debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + </node> + <node name="refresh"> + <properties> + <help>Enable OSPF lsa refresh debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + </node> + </children> + </node> + <node name="nsm"> + <properties> + <help>Enable OSPF nsm debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + <children> + <node name="events"> + <properties> + <help>Enable OSPF nsm events debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + </node> + <node name="status"> + <properties> + <help>Enable OSPF nsm status debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + </node> + <node name="timers"> + <properties> + <help>Enable OSPF nsm timers debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + </node> + </children> + </node> + <node name="nssa"> + <properties> + <help>Enable OSPF nssa debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + </node> + <node name="packet"> + <properties> + <help>Enable OSPF packet debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + <children> + <node name="all"> + <properties> + <help>Enable OSPF all packet debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + <children> + #include <include/monitor-ospf-packet-detail.xml.i> + </children> + </node> + <node name="dd"> + <properties> + <help>Enable OSPF dd packet debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + <children> + #include <include/monitor-ospf-packet-detail.xml.i> + </children> + </node> + <node name="hello"> + <properties> + <help>Enable OSPF hello packet debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + <children> + #include <include/monitor-ospf-packet-detail.xml.i> + </children> + </node> + <node name="ls-ack"> + <properties> + <help>Enable OSPF ls-ack packet debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + <children> + #include <include/monitor-ospf-packet-detail.xml.i> + </children> + </node> + <node name="ls-request"> + <properties> + <help>Enable OSPF ls-request packet debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + <children> + #include <include/monitor-ospf-packet-detail.xml.i> + </children> + </node> + <node name="ls-update"> + <properties> + <help>Enable OSPF ls-update packet debugging</help> + </properties> + <command>vtysh -c "debug ospf ${@:5}"</command> + <children> + #include <include/monitor-ospf-packet-detail.xml.i> + </children> + </node> + </children> + </node> + <node name="rib"> + <properties> + <help>Enable OSPF rib debugging</help> + </properties> + <command>vtysh -c "debug ospf zebra"</command> + <children> + <node name="interface"> + <properties> + <help>Enable OSPF rib interface debugging</help> + </properties> + <command>vtysh -c "debug ospf zebra interface"</command> + </node> + <node name="redistribute"> + <properties> + <help>Enable OSPF rib redistribute debugging</help> + </properties> + <command>vtysh -c "debug ospf zebra redistribute"</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="ospfv3"> + <properties> + <help>Monitor the IPv6 Open Shortest Path First (OSPFv3) protocol</help> + </properties> + <children> + <node name="disable"> + <properties> + <help>Disable IPv6 Open Shortest Path First (OSPFv3) protocol debugging</help> + </properties> + <children> + <node name="abr"> + <properties> + <help>Disable all OSPFv3 debugging</help> + </properties> + <command>vtysh -c "no debug ospf6"</command> + </node> + <node name="abr"> + <properties> + <help>Disable OSPFv3 ABR debugging</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="asbr"> + <properties> + <help>Disable OSPFv3 ASBR debugging</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="border-routers"> + <properties> + <help>Disable OSPFv3 border router debugging</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + <children> + <node name="area-id"> + <properties> + <help>Disable debug border routers in specific Area</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="router-id"> + <properties> + <help>Disable debug specific border router</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + </children> + </node> + <node name="flooding"> + <properties> + <help>Disable OSPFv3 flooding debugging</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="interface"> + <properties> + <help>Disable OSPFv3 Interface debugging</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="lsa"> + <properties> + <help>Disable OSPFv3 Link State Advertisments debugging</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + <children> + <node name="as-external"> + <properties> + <help>Display As-External LSAs</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="inter-prefix"> + <properties> + <help>Display Inter-Area-Prefix LSAs</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="inter-router"> + <properties> + <help>Display Inter-Router LSAs</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="intra-prefix"> + <properties> + <help>Display Intra-Area-Prefix LSAs</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="link"> + <properties> + <help>Display Link LSAs</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="network"> + <properties> + <help>Display Network LSAs</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="router"> + <properties> + <help>Display Router LSAs</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="unknown"> + <properties> + <help>Display LSAs of unknown origin</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + </children> + </node> + <node name="message"> + <properties> + <help>Disable OSPFv3 message debugging</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + <children> + <node name="all"> + <properties> + <help>Debug All message</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="dbdesc"> + <properties> + <help>Debug Database Description message</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="hello"> + <properties> + <help>Debug Hello message</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="lsack"> + <properties> + <help>Debug Link State Acknowledgement message</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="lsreq"> + <properties> + <help>Debug Link State Request message</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="lsupdate"> + <properties> + <help>Debug Link State Update message</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="unknown"> + <properties> + <help>Debug Unknown message</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + </children> + </node> + <node name="neighbor"> + <properties> + <help>Disable OSPFv3 Neighbor debugging</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + <children> + <node name="event"> + <properties> + <help>Debug OSPFv3 Neighbor Event</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="state"> + <properties> + <help>Debug OSPFv3 Neighbor State Change</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + </children> + </node> + <node name="rib"> + <properties> + <help>Disable OSPFv3 connection to RIB debugging</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + <children> + <node name="recv"> + <properties> + <help>Debug receiving zebra</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="send"> + <properties> + <help>Debug sending zebra</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + </children> + </node> + <node name="route"> + <properties> + <help>Disable OSPFv3 route table calculation debugging</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + <children> + <node name="inter-area"> + <properties> + <help>Debug inter-area route calculation</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="intra-area"> + <properties> + <help>Debug intra-area route calculation</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="memory"> + <properties> + <help>Debug route memory use</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="table"> + <properties> + <help>Debug route table calculation</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + </children> + </node> + <node name="spf"> + <properties> + <help>Disable OSPFv3 SPF calculation debugging</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + <children> + <node name="database"> + <properties> + <help>Log number of LSAs at SPF Calculation time</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="process"> + <properties> + <help>Debug Detailed SPF Process</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + <node name="time"> + <properties> + <help>Measure time taken by SPF Calculation</help> + </properties> + <command>vtysh -c "no debug ospf6 ${@:5}"</command> + </node> + </children> + </node> + </children> + </node> + <node name="enable"> + <properties> + <help>Enable IPv6 Open Shortest Path First (OSPFv3) protocol debugging</help> + </properties> + <children> + <node name="abr"> + <properties> + <help>Enable OSPFv3 ABR debugging</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="asbr"> + <properties> + <help>Enable OSPFv3 ASBR debugging</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="border-routers"> + <properties> + <help>Enable OSPFv3 border router debugging</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + <children> + <node name="area-id"> + <properties> + <help>Debug border routers in specific Area</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="router-id"> + <properties> + <help>Debug specific border router</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + </children> + </node> + <node name="flooding"> + <properties> + <help>Enable OSPFv3 flooding debugging</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="interface"> + <properties> + <help>Enable OSPFv3 Interface debugging</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="lsa"> + <properties> + <help>Enable OSPFv3 Link State Advertisments debugging</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + <children> + <node name="as-external"> + <properties> + <help>Display As-External LSAs</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="inter-prefix"> + <properties> + <help>Display Inter-Area-Prefix LSAs</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="inter-router"> + <properties> + <help>Display Inter-Router LSAs</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="intra-prefix"> + <properties> + <help>Display Intra-Area-Prefix LSAs</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="link"> + <properties> + <help>Display Link LSAs</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="network"> + <properties> + <help>Display Network LSAs</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="router"> + <properties> + <help>Display Router LSAs</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="unknown"> + <properties> + <help>Display LSAs of unknown origin</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + </children> + </node> + <node name="message"> + <properties> + <help>Enable OSPFv3 message debugging</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + <children> + <node name="all"> + <properties> + <help>Debug All message</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="dbdesc"> + <properties> + <help>Debug Database Description message</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="hello"> + <properties> + <help>Debug Hello message</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="lsack"> + <properties> + <help>Debug Link State Acknowledgement message</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="lsreq"> + <properties> + <help>Debug Link State Request message</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="lsupdate"> + <properties> + <help>Debug Link State Update message</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="unknown"> + <properties> + <help>Debug Unknown message</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + </children> + </node> + <node name="neighbor"> + <properties> + <help>Enable OSPFv3 Neighbor debugging</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + <children> + <node name="event"> + <properties> + <help>Debug OSPFv3 Neighbor Event</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="state"> + <properties> + <help>Debug OSPFv3 Neighbor State Change</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + </children> + </node> + <node name="rib"> + <properties> + <help>Enable OSPFv3 connection to RIB debugging</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + <children> + <node name="recv"> + <properties> + <help>Debug receiving zebra</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="send"> + <properties> + <help>Debug sending zebra</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + </children> + </node> + <node name="route"> + <properties> + <help>Enable OSPFv3 route table calculation debugging</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + <children> + <node name="inter-area"> + <properties> + <help>Debug inter-area route calculation</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="intra-area"> + <properties> + <help>Debug intra-area route calculation</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="memory"> + <properties> + <help>Debug route memory use</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="table"> + <properties> + <help>Debug route table calculation</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + </children> + </node> + <node name="spf"> + <properties> + <help>Enable OSPFv3 SPF calculation debugging</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + <children> + <node name="database"> + <properties> + <help>Log number of LSAs at SPF Calculation time</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="process"> + <properties> + <help>Debug Detailed SPF Process</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + <node name="time"> + <properties> + <help>Measure time taken by SPF Calculation</help> + </properties> + <command>vtysh -c "debug ospf6 ${@:5}"</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="rib"> + <properties> + <help>Monitor the Routing Information Base (RIB)</help> + </properties> + <children> + <node name="disable"> + <properties> + <help>Disable Route Information Base (RIB) debugging</help> + </properties> + <children> + <node name="events"> + <properties> + <help>Disable RIB events debugging</help> + </properties> + <command>vtysh -c "no debug zebra ${@:5}"</command> + </node> + <node name="kernel"> + <properties> + <help>Disable RIB kernel debugging</help> + </properties> + <command>vtysh -c "no debug zebra ${@:5}"</command> + </node> + <node name="packet"> + <properties> + <help>Disable RIB packet debugging</help> + </properties> + <command>vtysh -c "no debug zebra ${@:5}"</command> + <children> + <node name="detail"> + <properties> + <help>Disable detailed debugging</help> + </properties> + <command>vtysh -c "no debug zebra ${@:5}"</command> + </node> + <node name="recv"> + <properties> + <help>Disable receive packet debugging</help> + </properties> + <command>vtysh -c "no debug zebra ${@:5}"</command> + </node> + <node name="send"> + <properties> + <help>Disable send packet debugging</help> + </properties> + <command>vtysh -c "no debug zebra ${@:5}"</command> + </node> + </children> + </node> + <node name="nexthop"> + <properties> + <help>Disable RIB nexthop debugging</help> + </properties> + <command>vtysh -c "no debug zebra ${@:5}"</command> + </node> + <node name="mpls"> + <properties> + <help>Disable RIP MPLS LSP debugging</help> + </properties> + <command>vtysh -c "no debug zebra ${@:5}"</command> + </node> + <node name="rib"> + <properties> + <help>Disable RIB debugging</help> + </properties> + <command>vtysh -c "no debug zebra ${@:5}"</command> + <children> + <node name="detailed"> + <properties> + <help>Disable detailed debugging</help> + </properties> + <command>vtysh -c "no debug zebra ${@:5}"</command> + </node> + </children> + </node> + </children> + </node> + <node name="enable"> + <properties> + <help>Enable Route Information Base (RIB) debugging</help> + </properties> + <children> + <node name="events"> + <properties> + <help>Enable RIB events debugging</help> + </properties> + <command>vtysh -c "debug zebra ${@:5}"</command> + </node> + <node name="kernel"> + <properties> + <help>Enable RIB kernel debugging</help> + </properties> + <command>vtysh -c "debug zebra ${@:5}"</command> + </node> + <node name="packet"> + <properties> + <help>Enable RIB packet debugging</help> + </properties> + <command>vtysh -c "debug zebra ${@:5}"</command> + <children> + <node name="detail"> + <properties> + <help>Enable detailed debugging</help> + </properties> + <command>vtysh -c "debug zebra ${@:5}"</command> + </node> + <node name="recv"> + <properties> + <help>Enable receive packet debugging</help> + </properties> + <command>vtysh -c "debug zebra ${@:5}"</command> + </node> + <node name="send"> + <properties> + <help>Enable send packet debugging</help> + </properties> + <command>vtysh -c "debug zebra ${@:5}"</command> + </node> + </children> + </node> + <node name="nexthop"> + <properties> + <help>Enable RIB nexthop debugging</help> + </properties> + <command>vtysh -c "debug zebra ${@:5}"</command> + </node> + <node name="mpls"> + <properties> + <help>Enable RIP MPLS LSP debugging</help> + </properties> + <command>vtysh -c "debug zebra ${@:5}"</command> + </node> + <node name="rib"> + <properties> + <help>Enable RIB debugging</help> + </properties> + <command>vtysh -c "debug zebra ${@:5}"</command> + <children> + <node name="detailed"> + <properties> + <help>Enable detailed debugging</help> + </properties> + <command>vtysh -c "debug zebra ${@:5}"</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="rip"> + <properties> + <help>Monitor the Routing Information Protocol (RIP)</help> + </properties> + <children> + <node name="disable"> + <properties> + <help>Disable Routing Information Protocol (RIP) debugging</help> + </properties> + <children> + <node name="all"> + <properties> + <help>Disable RIP debugging</help> + </properties> + <command>vtysh -c "no debug rip"</command> + </node> + <node name="events"> + <properties> + <help>Disable RIP events debugging</help> + </properties> + <command>vtysh -c "no debug rip ${@:5}"</command> + </node> + <node name="packet"> + <properties> + <help>Disable RIP packet debugging</help> + </properties> + <command>vtysh -c "no debug rip ${@:5}"</command> + <children> + <node name="recv"> + <properties> + <help>Disable receive packet debugging</help> + </properties> + <command>vtysh -c "no debug rip ${@:5}"</command> + </node> + <node name="send"> + <properties> + <help>Disable send packet debugging</help> + </properties> + <command>vtysh -c "no debug rip ${@:5}"</command> + </node> + </children> + </node> + <node name="rib"> + <properties> + <help>Disable RIB debugging</help> + </properties> + <command>vtysh -c "no debug rip zebra"</command> + </node> + </children> + </node> + <node name="enable"> + <properties> + <help>Enable Routing Information Protocol (RIP) debugging</help> + </properties> + <children> + <node name="events"> + <properties> + <help>Enable RIP events debugging</help> + </properties> + <command>vtysh -c "debug rip ${@:5}"</command> + </node> + <node name="packet"> + <properties> + <help>Enable RIP packet debugging</help> + </properties> + <command>vtysh -c "debug rip ${@:5}"</command> + <children> + <node name="recv"> + <properties> + <help>Enable receive packet debugging</help> + </properties> + <command>vtysh -c "debug rip ${@:5}"</command> + </node> + <node name="send"> + <properties> + <help>Enable send packet debugging</help> + </properties> + <command>vtysh -c "debug rip ${@:5}"</command> + </node> + </children> + </node> + <node name="rib"> + <properties> + <help>Enable RIB debugging</help> + </properties> + <command>vtysh -c "debug rip zebra"</command> + </node> + </children> + </node> + </children> + </node> + <node name="ripng"> + <properties> + <help>Monitor the Routing Information Protocol Next Generation (RIPng) protocol</help> + </properties> + <children> + <node name="disable"> + <properties> + <help>Disable Routing Information Protocol Next Generation (RIPNG) debugging</help> + </properties> + <children> + <node name="all"> + <properties> + <help>Disable RIPNG debugging</help> + </properties> + <command>vtysh -c "no debug ripng"</command> + </node> + <node name="events"> + <properties> + <help>Disable RIPNG events debugging</help> + </properties> + <command>vtysh -c "no debug ripng ${@:5}"</command> + </node> + <node name="packet"> + <properties> + <help>Disable RIPNG packet debugging</help> + </properties> + <command>vtysh -c "no debug ripng ${@:5}"</command> + <children> + <node name="recv"> + <properties> + <help>Disable receive packet debugging</help> + </properties> + <command>vtysh -c "no debug ripng ${@:5}"</command> + </node> + <node name="send"> + <properties> + <help>Disable send packet debugging</help> + </properties> + <command>vtysh -c "no debug ripng ${@:5}"</command> + </node> + </children> + </node> + <node name="rib"> + <properties> + <help>Disable RIB debugging</help> + </properties> + <command>vtysh -c "no debug ripng zebra"</command> + </node> + </children> + </node> + <node name="enable"> + <properties> + <help>Enable Routing Information Protocol Next Generation (RIPNG) debugging</help> + </properties> + <children> + <node name="events"> + <properties> + <help>Enable RIPNG events debugging</help> + </properties> + <command>vtysh -c "debug ripng ${@:5}"</command> + </node> + <node name="packet"> + <properties> + <help>Enable RIPNG packet debugging</help> + </properties> + <command>vtysh -c "debug ripng ${@:5}"</command> + <children> + <node name="recv"> + <properties> + <help>Enable receive packet debugging</help> + </properties> + <command>vtysh -c "debug ripng ${@:5}"</command> + </node> + <node name="send"> + <properties> + <help>Enable send packet debugging</help> + </properties> + <command>vtysh -c "debug ripng ${@:5}"</command> + </node> + </children> + </node> + <node name="rib"> + <properties> + <help>Enable RIB debugging</help> + </properties> + <command>vtysh -c "debug ripng zebra"</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/mtr.xml.in b/op-mode-definitions/mtr.xml.in new file mode 100644 index 0000000..66729e2 --- /dev/null +++ b/op-mode-definitions/mtr.xml.in @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="monitor"> + <children> + <tagNode name="traceroute"> + <properties> + <help>Monitor Traceroute and ping path to target</help> + <completionHelp> + <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/mtr.py ${@:3}</command> + <children> + <leafNode name="node.tag"> + <properties> + <help>Traceroute options</help> + <completionHelp> + <script>${vyos_op_scripts_dir}/mtr.py --get-options-nested "${COMP_WORDS[@]}"</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/mtr.py ${@:3}</command> + </leafNode> + </children> + </tagNode> + </children> + </node> + <tagNode name="mtr"> + <properties> + <help>Monitor Traceroute and ping path to target</help> + <completionHelp> + <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/mtr.py ${@:2}</command> + <children> + <leafNode name="node.tag"> + <properties> + <help>mtr options</help> + <completionHelp> + <script>${vyos_op_scripts_dir}/mtr.py --get-options "${COMP_WORDS[@]}"</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/mtr.py ${@:2}</command> + </leafNode> + </children> + </tagNode> +</interfaceDefinition> diff --git a/op-mode-definitions/multicast-group.xml.in b/op-mode-definitions/multicast-group.xml.in new file mode 100644 index 0000000..39b4e34 --- /dev/null +++ b/op-mode-definitions/multicast-group.xml.in @@ -0,0 +1,63 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ip"> + <children> + <node name="multicast"> + <properties> + <help>Show IP multicast</help> + </properties> + <children> + <node name="group"> + <properties> + <help>Show IP multicast group membership</help> + </properties> + <command>${vyos_op_scripts_dir}/multicast.py show_group --family inet</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show IP multicast group membership of specific interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/multicast.py show_group --family inet --interface "$6"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="ipv6"> + <children> + <node name="multicast"> + <properties> + <help>Show IPv6 multicast</help> + </properties> + <children> + <node name="group"> + <properties> + <help>Show IPv6 multicast group membership</help> + </properties> + <command>${vyos_op_scripts_dir}/multicast.py show_group --family inet6</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show IP multicast group membership of specific interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/multicast.py show_group --family inet6 --interface "$6"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/nat.xml.in b/op-mode-definitions/nat.xml.in new file mode 100644 index 0000000..13e7fd8 --- /dev/null +++ b/op-mode-definitions/nat.xml.in @@ -0,0 +1,119 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="nat"> + <properties> + <help>Show IPv4 Network Address Translation (NAT) information</help> + </properties> + <children> + <node name="cgnat"> + <properties> + <help>Show Carrier-Grade Network Address Translation (CGNAT)</help> + </properties> + <children> + <node name="allocation"> + <properties> + <help>Show allocated CGNAT parameters</help> + </properties> + <children> + <tagNode name="external-address"> + <properties> + <help>Show CGNAT allocations for an external IP address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/cgnat.py show_allocation --external-address "$6"</command> + </tagNode> + <tagNode name="internal-address"> + <properties> + <help>Show CGNAT allocations for an internal IP address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/cgnat.py show_allocation --internal-address "$6"</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/cgnat.py show_allocation</command> + </node> + </children> + </node> + <node name="source"> + <properties> + <help>Show source IPv4 to IPv4 Network Address Translation (NAT) information</help> + </properties> + <children> + <node name="rules"> + <properties> + <help>Show configured source NAT rules</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_rules --direction source --family inet</command> + </node> + <node name="statistics"> + <properties> + <help>Show statistics for configured source NAT rules</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_statistics --direction source --family inet</command> + </node> + <node name="translations"> + <properties> + <help>Show active source NAT translations</help> + </properties> + <children> + <tagNode name="address"> + <properties> + <help>Show active source NAT translations for an IP address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet --address "$6"</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet</command> + </node> + </children> + </node> + <node name="destination"> + <properties> + <help>Show destination IPv4 to IPv4 Network Address Translation (NAT) information</help> + </properties> + <children> + <node name="rules"> + <properties> + <help>Show configured destination NAT rules</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_rules --direction destination --family inet</command> + </node> + <node name="statistics"> + <properties> + <help>Show statistics for configured destination NAT rules</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_statistics --direction destination --family inet</command> + </node> + <node name="translations"> + <properties> + <help>Show active destination NAT translations</help> + </properties> + <children> + <tagNode name="address"> + <properties> + <help>Show active NAT destination translations for an IP address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet --address "$6"</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/nat66.xml.in b/op-mode-definitions/nat66.xml.in new file mode 100644 index 0000000..4df20d8 --- /dev/null +++ b/op-mode-definitions/nat66.xml.in @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="nat66"> + <properties> + <help>Show IPv6 Network Address Translation (NAT66) information</help> + </properties> + <children> + <node name="source"> + <properties> + <help>Show source IPv6 to IPv6 Network Address Translation (NAT66) information</help> + </properties> + <children> + <node name="rules"> + <properties> + <help>Show configured source NAT66 rules</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_rules --direction source --family inet6</command> + </node> + <node name="statistics"> + <properties> + <help>Show statistics for configured source NAT66 rules</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_statistics --direction source --family inet6</command> + </node> + <node name="translations"> + <properties> + <help>Show active source NAT66 translations</help> + </properties> + <children> + <tagNode name="address"> + <properties> + <help>Show active source NAT66 translations for an IPv6 address</help> + <completionHelp> + <list><h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet6 --address "$6"</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet6</command> + </node> + </children> + </node> + <node name="destination"> + <properties> + <help>Show destination IPv6 to IPv6 Network Address Translation (NAT66) information</help> + </properties> + <children> + <node name="rules"> + <properties> + <help>Show configured destination NAT66 rules</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_rules --direction destination --family inet6</command> + </node> + <node name="statistics"> + <properties> + <help>Show statistics for configured destination NAT66 rules</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_statistics --direction destination --family inet6</command> + </node> + <node name="translations"> + <properties> + <help>Show active destination NAT66 translations</help> + </properties> + <children> + <tagNode name="address"> + <properties> + <help>Show active NAT66 destination translations for an IPv6 address</help> + <completionHelp> + <list><h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet6 --address "$6"</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet6</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/nhrp.xml.in b/op-mode-definitions/nhrp.xml.in new file mode 100644 index 0000000..11a4b88 --- /dev/null +++ b/op-mode-definitions/nhrp.xml.in @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="reset"> + <children> + <node name="nhrp"> + <properties> + <help>Clear/Purge NHRP entries</help> + </properties> + <children> + <node name="flush"> + <properties> + <help>Clear all non-permanent entries</help> + </properties> + <children> + <tagNode name="tunnel"> + <properties> + <help>Clear all non-permanent entries</help> + </properties> + <command>sudo opennhrpctl flush dev $5 || echo OpenNHRP is not running.</command> + </tagNode> + </children> + <command>sudo opennhrpctl flush || echo OpenNHRP is not running.</command> + </node> + <node name="purge"> + <properties> + <help>Purge entries from NHRP cache</help> + </properties> + <children> + <tagNode name="tunnel"> + <properties> + <help>Purge all entries from NHRP cache</help> + </properties> + <command>sudo opennhrpctl purge dev $5 || echo OpenNHRP is not running.</command> + </tagNode> + </children> + <command>sudo opennhrpctl purge || echo OpenNHRP is not running.</command> + </node> + </children> + </node> + </children> + </node> + <node name="show"> + <children> + <node name="nhrp"> + <properties> + <help>Show NHRP (Next Hop Resolution Protocol) information</help> + </properties> + <children> + <leafNode name="interface"> + <properties> + <help>Show NHRP interface connection information</help> + </properties> + <command>${vyos_op_scripts_dir}/nhrp.py show_interface</command> + </leafNode> + <leafNode name="tunnel"> + <properties> + <help>Show NHRP tunnel connection information</help> + </properties> + <command>${vyos_op_scripts_dir}/nhrp.py show_tunnel</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/ntp.xml.in b/op-mode-definitions/ntp.xml.in new file mode 100644 index 0000000..565a5ed --- /dev/null +++ b/op-mode-definitions/ntp.xml.in @@ -0,0 +1,61 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ntp"> + <properties> + <help>Show peer status of NTP daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ntp.py show_sourcestats</command> + <children> + <node name="activity"> + <properties> + <help>Report the number of servers and peers that are online and offline</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ntp.py show_activity</command> + </node> + <node name="sources"> + <properties> + <help>Show information about the current time sources being accessed</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ntp.py show_sources</command> + </node> + <node name="system"> + <properties> + <help>Show parameters about the system clock performance</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ntp.py show_tracking</command> + </node> + </children> + </node> + </children> + </node> + <node name="force"> + <children> + <node name="ntp"> + <properties> + <help>NTP (Network Time Protocol) operations</help> + </properties> + <children> + <node name="synchronization"> + <properties> + <help>Force NTP time synchronization</help> + </properties> + <children> + <tagNode name="vrf"> + <properties> + <help>Force NTP time synchronization in given VRF</help> + <completionHelp> + <path>vrf name</path> + </completionHelp> + </properties> + <command>sudo ip vrf exec $5 chronyc makestep</command> + </tagNode> + </children> + <command>sudo chronyc makestep</command> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/openconnect.xml.in b/op-mode-definitions/openconnect.xml.in new file mode 100644 index 0000000..88e1f9f --- /dev/null +++ b/op-mode-definitions/openconnect.xml.in @@ -0,0 +1,77 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="openconnect-server"> + <properties> + <help>Show OpenConnect server information</help> + </properties> + <children> + <leafNode name="sessions"> + <properties> + <help>Show active OpenConnect server sessions</help> + </properties> + <command>${vyos_op_scripts_dir}/openconnect.py show_sessions</command> + </leafNode> + <tagNode name="user"> + <properties> + <help>Show OpenConnect configured user settings</help> + <completionHelp> + <script>sudo ${vyos_completion_dir}/list_openconnect_users.py</script> + </completionHelp> + </properties> + <children> + <node name="otp"> + <properties> + <help>Show OTP key information</help> + </properties> + <children> + <leafNode name="full"> + <properties> + <help>Show full settings, including QR code and commands for VyOS</help> + </properties> + <command>${vyos_op_scripts_dir}/show_openconnect_otp.py --user="$4" --info="full"</command> + </leafNode> + <leafNode name="key-hex"> + <properties> + <help>Show OTP authentication secret in Hex (used in VyOS config)</help> + </properties> + <command>${vyos_op_scripts_dir}/show_openconnect_otp.py --user="$4" --info="key-hex"</command> + </leafNode> + <leafNode name="key-b32"> + <properties> + <help>Show OTP authentication secret in Base32 (used in mobile apps)</help> + </properties> + <command>${vyos_op_scripts_dir}/show_openconnect_otp.py --user="$4" --info="key-b32"</command> + </leafNode> + <leafNode name="qrcode"> + <properties> + <help>Show OTP authentication QR code</help> + </properties> + <command>${vyos_op_scripts_dir}/show_openconnect_otp.py --user="$4" --info="qrcode"</command> + </leafNode> + <leafNode name="uri"> + <properties> + <help>Show OTP authentication otpauth URI</help> + </properties> + <command>${vyos_op_scripts_dir}/show_openconnect_otp.py --user="$4" --info="uri"</command> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> + <node name="restart"> + <children> + <leafNode name="openconnect-server"> + <properties> + <help>Restart openconnect server process</help> + </properties> + <command>${vyos_op_scripts_dir}/openconnect-control.py --action="restart"</command> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/openvpn.xml.in b/op-mode-definitions/openvpn.xml.in new file mode 100644 index 0000000..f205b00 --- /dev/null +++ b/op-mode-definitions/openvpn.xml.in @@ -0,0 +1,131 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="reset"> + <children> + <node name="openvpn"> + <properties> + <help>Reset OpenVPN client/server connections</help> + </properties> + <children> + <tagNode name="client"> + <properties> + <help>Reset specified OpenVPN client</help> + <completionHelp> + <script>sudo ${vyos_completion_dir}/list_openvpn_clients.py --all</script> + </completionHelp> + </properties> + <command>echo kill $4 | socat - UNIX-CONNECT:/run/openvpn/openvpn-mgmt-intf > /dev/null</command> + </tagNode> + <tagNode name="interface"> + <properties> + <help>Reset OpenVPN process on interface</help> + <completionHelp> + <script>sudo ${vyos_completion_dir}/list_interfaces --type openvpn</script> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/openvpn.py reset --interface $4</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <node name="openvpn"> + <properties> + <help>Show OpenVPN interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=openvpn</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed OpenVPN interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=openvpn</command> + </leafNode> + </children> + </node> + <tagNode name="openvpn"> + <properties> + <help>Show OpenVPN interface information</help> + <completionHelp> + <script>sudo ${vyos_completion_dir}/list_interfaces --type openvpn</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name=$4</command> + <children> + <tagNode name="user"> + <properties> + <help>Show OpenVPN interface users</help> + <completionHelp> + <script>sudo ${vyos_completion_dir}/list_openvpn_users.py --interface ${COMP_WORDS[3]}</script> + </completionHelp> + </properties> + <children> + <node name="mfa"> + <properties> + <help>Show multi-factor authentication information</help> + </properties> + <children> + <leafNode name="secret"> + <properties> + <help>Show multi-factor authentication secret</help> + </properties> + <command>${vyos_op_scripts_dir}/show_openvpn_mfa.py --user="$6" --intf="$4" --action=secret</command> + </leafNode> + <leafNode name="uri"> + <properties> + <help>Show multi-factor authentication otpauth uri</help> + </properties> + <command>${vyos_op_scripts_dir}/show_openvpn_mfa.py --user="$6" --intf="$4" --action=uri</command> + </leafNode> + <leafNode name="qrcode"> + <properties> + <help>Show multi-factor authentication QR code</help> + </properties> + <command>${vyos_op_scripts_dir}/show_openvpn_mfa.py --user="$6" --intf="$4" --action=qrcode</command> + </leafNode> + </children> + </node> + </children> + </tagNode> + <leafNode name="brief"> + <properties> + <help>Show summary of specified OpenVPN interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4"</command> + </leafNode> + </children> + </tagNode> + </children> + </node> + <node name="openvpn"> + <properties> + <help>Show OpenVPN information</help> + </properties> + <children> + <leafNode name="client"> + <properties> + <help>Show tunnel status for OpenVPN client interfaces</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/openvpn.py show --mode client</command> + </leafNode> + <leafNode name="server"> + <properties> + <help>Show tunnel status for OpenVPN server interfaces</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/openvpn.py show --mode server</command> + </leafNode> + <leafNode name="site-to-site"> + <properties> + <help>Show tunnel status for OpenVPN site-to-site interfaces</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/openvpn.py show --mode site_to_site</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/ping.xml.in b/op-mode-definitions/ping.xml.in new file mode 100644 index 0000000..4c25a59 --- /dev/null +++ b/op-mode-definitions/ping.xml.in @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <tagNode name="ping"> + <properties> + <help>Send Internet Control Message Protocol (ICMP) echo request</help> + <completionHelp> + <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/ping.py ${@:2}</command> + <children> + <leafNode name="node.tag"> + <properties> + <help>Ping options</help> + <completionHelp> + <script>${vyos_op_scripts_dir}/ping.py --get-options "${COMP_WORDS[@]}"</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/ping.py ${@:2}</command> + </leafNode> + </children> + </tagNode> +</interfaceDefinition> diff --git a/op-mode-definitions/pki.xml.in b/op-mode-definitions/pki.xml.in new file mode 100644 index 0000000..254ef08 --- /dev/null +++ b/op-mode-definitions/pki.xml.in @@ -0,0 +1,587 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <children> + <node name="pki"> + <properties> + <help>Generate public key infrastructure (PKI) certificates and keys</help> + </properties> + <children> + <node name="ca"> + <properties> + <help>Generate CA certificate</help> + </properties> + <children> + <tagNode name="sign"> + <properties> + <help>Sign generated CA certificate with another specified CA certificate</help> + <completionHelp> + <path>pki ca</path> + </completionHelp> + </properties> + <children> + <tagNode name="file"> + <properties> + <help>Write generated CA certificate into the specified filename</help> + <completionHelp> + <list><filename></list> + </completionHelp> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --ca "$7" --sign "$5" --file</command> + </tagNode> + <tagNode name="install"> + <properties> + <help>Commands for installing generated CA certificate into running configuration</help> + <completionHelp> + <list><certificate name></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/pki.py --action generate --ca "$7" --sign "$5" --install</command> + </tagNode> + </children> + <command>${vyos_op_scripts_dir}/pki.py --action generate --ca "noname" --sign "$5"</command> + </tagNode> + <tagNode name="file"> + <properties> + <help>Write generated CA certificate into the specified filename</help> + <completionHelp> + <list><filename></list> + </completionHelp> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --ca "$5" --file</command> + </tagNode> + <tagNode name="install"> + <properties> + <help>Commands for installing generated CA certificate into running configuration</help> + <completionHelp> + <list><CA name></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/pki.py --action generate --ca "$5" --install</command> + </tagNode> + </children> + <command>${vyos_op_scripts_dir}/pki.py --action generate --ca "noname"</command> + </node> + <node name="certificate"> + <properties> + <help>Generate certificate request</help> + </properties> + <children> + <node name="self-signed"> + <properties> + <help>Generate self-signed certificate</help> + </properties> + <children> + <tagNode name="file"> + <properties> + <help>Write generated self-signed certificate into the specified filename</help> + <completionHelp> + <list><filename></list> + </completionHelp> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --certificate "$6" --self-sign --file</command> + </tagNode> + <tagNode name="install"> + <properties> + <help>Commands for installing generated self-signed certificate into running configuration</help> + <completionHelp> + <list><certificate name></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "$6" --self-sign --install</command> + </tagNode> + </children> + <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "noname" --self-sign</command> + </node> + <tagNode name="sign"> + <properties> + <help>Sign generated certificate with specified CA certificate</help> + <completionHelp> + <path>pki ca</path> + </completionHelp> + </properties> + <children> + <tagNode name="file"> + <properties> + <help>Write generated signed certificate into the specified filename</help> + <completionHelp> + <list><filename></list> + </completionHelp> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --certificate "$7" --sign "$5" --file</command> + </tagNode> + <tagNode name="install"> + <properties> + <help>Commands for installing generated signed certificate into running configuration</help> + <completionHelp> + <list><certificate name></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "$7" --sign "$5" --install</command> + </tagNode> + </children> + <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "noname" --sign "$5"</command> + </tagNode> + <tagNode name="file"> + <properties> + <help>Write generated certificate request and key into the specified filename</help> + <completionHelp> + <list><filename></list> + </completionHelp> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --certificate "$5" --file</command> + </tagNode> + <tagNode name="install"> + <properties> + <help>Commands for installing generated certificate private key into running configuration</help> + <completionHelp> + <list><certificate name></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "$5" --install</command> + </tagNode> + </children> + <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "noname"</command> + </node> + <tagNode name="crl"> + <properties> + <help>Generate CRL for specified CA certificate</help> + <completionHelp> + <path>pki ca</path> + </completionHelp> + </properties> + <children> + <tagNode name="file"> + <properties> + <help>Write generated CRL into the specified filename</help> + <completionHelp> + <list><filename></list> + </completionHelp> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --crl "$4" --file</command> + </tagNode> + <leafNode name="install"> + <properties> + <help>Commands for installing generated CRL into running configuration</help> + </properties> + <command>${vyos_op_scripts_dir}/pki.py --action generate --crl "$4" --install</command> + </leafNode> + </children> + <command>${vyos_op_scripts_dir}/pki.py --action generate --crl "$4"</command> + </tagNode> + <node name="dh"> + <properties> + <help>Generate DH parameters</help> + </properties> + <children> + <tagNode name="file"> + <properties> + <help>Write generated DH parameters into the specified filename</help> + <completionHelp> + <list><filename></list> + </completionHelp> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --dh "$5" --file</command> + </tagNode> + <tagNode name="install"> + <properties> + <help>Commands for installing generated DH parameters into running configuration</help> + <completionHelp> + <list><DH name></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/pki.py --action generate --dh "$5" --install</command> + </tagNode> + </children> + <command>${vyos_op_scripts_dir}/pki.py --action generate --dh "noname"</command> + </node> + <node name="key-pair"> + <properties> + <help>Generate a key pair</help> + </properties> + <children> + <tagNode name="file"> + <properties> + <help>Write generated key pair into the specified filename</help> + <completionHelp> + <list><filename></list> + </completionHelp> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --keypair "$5" --file</command> + </tagNode> + <tagNode name="install"> + <properties> + <help>Commands for installing generated key pair into running configuration</help> + <completionHelp> + <list><key name></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/pki.py --action generate --keypair "$5" --install</command> + </tagNode> + </children> + <command>${vyos_op_scripts_dir}/pki.py --action generate --keypair "noname"</command> + </node> + <node name="openvpn"> + <properties> + <help>Generate OpenVPN keys</help> + </properties> + <children> + <node name="shared-secret"> + <properties> + <help>Generate OpenVPN shared secret key</help> + </properties> + <children> + <tagNode name="file"> + <properties> + <help>Write generated OpenVPN shared secret key into the specified filename</help> + <completionHelp> + <list><filename></list> + </completionHelp> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --openvpn "$6" --file</command> + </tagNode> + <tagNode name="install"> + <properties> + <help>Commands for installing generated OpenVPN shared secret key into running configuration</help> + <completionHelp> + <list><key name></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/pki.py --action generate --openvpn "$6" --install</command> + </tagNode> + </children> + <command>${vyos_op_scripts_dir}/pki.py --action generate --openvpn "noname"</command> + </node> + </children> + </node> + <node name="ssh-key"> + <properties> + <help>Generate SSH key</help> + </properties> + <children> + <tagNode name="file"> + <properties> + <help>Write generated SSH keys into the specified filename</help> + <completionHelp> + <list><filename></list> + </completionHelp> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --ssh "$5" --file</command> + </tagNode> + <tagNode name="install"> + <properties> + <help>Commands for installing generated SSH key into running configuration</help> + <completionHelp> + <list><key name></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/pki.py --action generate --ssh "$5" --install</command> + </tagNode> + </children> + <command>${vyos_op_scripts_dir}/pki.py --action generate --ssh "noname"</command> + </node> + <node name="wireguard"> + <properties> + <help>Generate WireGuard keys</help> + </properties> + <children> + <node name="key-pair"> + <properties> + <help>Generate WireGuard public/private key-pair</help> + </properties> + <children> + <node name="install"> + <properties> + <help>Generate CLI commands to install WireGuard key to configuration</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>WireGuard Interface used in install command</help> + <completionHelp> + <path>interfaces wireguard</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/pki.py --action generate --wireguard --key --interface "$7" --install</command> + </tagNode> + </children> + </node> + </children> + <command>${vyos_op_scripts_dir}/pki.py --action generate --wireguard --key</command> + </node> + <node name="preshared-key"> + <properties> + <help>Generate WireGuard pre-shared key</help> + </properties> + <children> + <node name="install"> + <properties> + <help>Generate CLI commands to install WireGuard key to configuration</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>WireGuard Interface used in install command</help> + <completionHelp> + <path>interfaces wireguard</path> + </completionHelp> + </properties> + <children> + <tagNode name="peer"> + <properties> + <help>Interface used for install command</help> + <completionHelp> + <path>interfaces wireguard ${COMP_WORDS[COMP_CWORD-2]} peer</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/pki.py --action generate --wireguard --psk --interface "$7" --peer "$9" --install</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + <command>${vyos_op_scripts_dir}/pki.py --action generate --wireguard --psk</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="import"> + <properties> + <help>Import an object</help> + </properties> + <children> + <node name="pki"> + <properties> + <help>Import file into PKI configuration</help> + </properties> + <children> + <tagNode name="ca"> + <properties> + <help>Import CA certificate into PKI</help> + <completionHelp> + <list><name></list> + </completionHelp> + </properties> + <children> + <tagNode name="file"> + <properties> + <help>Path to CA certificate file</help> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --ca "$4" --filename "$6"</command> + </tagNode> + <tagNode name="key-file"> + <properties> + <help>Path to private key file</help> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --ca "$4" --key-filename "$6"</command> + </tagNode> + </children> + </tagNode> + <tagNode name="certificate"> + <properties> + <help>Import certificate into PKI</help> + <completionHelp> + <list><name></list> + </completionHelp> + </properties> + <children> + <tagNode name="file"> + <properties> + <help>Path to certificate file</help> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --certificate "$4" --filename "$6"</command> + </tagNode> + <tagNode name="key-file"> + <properties> + <help>Path to private key file</help> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --certificate "$4" --key-filename "$6"</command> + </tagNode> + </children> + </tagNode> + <tagNode name="crl"> + <properties> + <help>Import certificate revocation list into PKI</help> + <completionHelp> + <list><CA name></list> + </completionHelp> + </properties> + <children> + <tagNode name="file"> + <properties> + <help>Path to CRL file</help> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --crl "$4" --filename "$6"</command> + </tagNode> + </children> + </tagNode> + <tagNode name="dh"> + <properties> + <help>Import DH parameters into PKI</help> + <completionHelp> + <list><name></list> + </completionHelp> + </properties> + <children> + <tagNode name="file"> + <properties> + <help>Path to DH parameters file</help> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --dh "$4" --filename "$6"</command> + </tagNode> + </children> + </tagNode> + <tagNode name="key-pair"> + <properties> + <help>Import key pair into PKI</help> + <completionHelp> + <list><name></list> + </completionHelp> + </properties> + <children> + <tagNode name="public-file"> + <properties> + <help>Path to public key file</help> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --keypair "$4" --filename "$6"</command> + </tagNode> + <tagNode name="private-file"> + <properties> + <help>Path to private key file</help> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --keypair "$4" --key-filename "$6"</command> + </tagNode> + </children> + </tagNode> + <node name="openvpn"> + <properties> + <help>Import OpenVPN keys into PKI</help> + </properties> + <children> + <tagNode name="shared-secret"> + <properties> + <help>Import OpenVPN shared secret key into PKI</help> + <completionHelp> + <list><name></list> + </completionHelp> + </properties> + <children> + <tagNode name="file"> + <properties> + <help>Path to shared secret key file</help> + </properties> + <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --openvpn "$5" --filename "$7"</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="show"> + <children> + <node name="pki"> + <properties> + <help>Show PKI x509 certificates</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action show</command> + <children> + <leafNode name="ca"> + <properties> + <help>Show x509 CA certificates</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --ca "all"</command> + </leafNode> + <tagNode name="ca"> + <properties> + <help>Show x509 CA certificate by name</help> + <completionHelp> + <path>pki ca</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --ca "$4"</command> + <children> + <leafNode name="pem"> + <properties> + <help>Show x509 CA certificate in PEM format</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --ca "$4" --pem</command> + </leafNode> + </children> + </tagNode> + <leafNode name="certificate"> + <properties> + <help>Show x509 certificates</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "all"</command> + </leafNode> + <tagNode name="certificate"> + <properties> + <help>Show x509 certificate by name</help> + <completionHelp> + <path>pki certificate</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "$4"</command> + <children> + <leafNode name="pem"> + <properties> + <help>Show x509 certificate in PEM format</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "$4" --pem</command> + </leafNode> + <tagNode name="fingerprint"> + <properties> + <help>Show x509 certificate fingerprint</help> + <completionHelp> + <list>sha256 sha384 sha512</list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "$4" --fingerprint "$6"</command> + </tagNode> + </children> + </tagNode> + <leafNode name="crl"> + <properties> + <help>Show x509 certificate revocation lists</help> + </properties> + <command>${vyos_op_scripts_dir}/pki.py --action show --crl "all"</command> + </leafNode> + <tagNode name="crl"> + <properties> + <help>Show x509 certificate revocation lists by CA name</help> + <completionHelp> + <path>pki ca</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/pki.py --action show --crl "$4"</command> + <children> + <leafNode name="pem"> + <properties> + <help>Show x509 certificate revocation lists by CA name in PEM format</help> + </properties> + <command>${vyos_op_scripts_dir}/pki.py --action show --crl "$4" --pem</command> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + <node name="renew"> + <children> + <leafNode name="certbot"> + <properties> + <help>Start manual certbot renewal</help> + </properties> + <command>sudo systemctl start certbot.service</command> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/policy-route.xml.in b/op-mode-definitions/policy-route.xml.in new file mode 100644 index 0000000..bd4a61d --- /dev/null +++ b/op-mode-definitions/policy-route.xml.in @@ -0,0 +1,143 @@ +<?xml version="1.0"?> +<interfaceDefinition> +<!-- + <node name="clear"> + <children> + <node name="policy"> + <properties> + <help>Clear policy statistics</help> + </properties> + <children> + <tagNode name="ipv6-route"> + <properties> + <help>Clear policy statistics for chain</help> + <completionHelp> + <path>policy ipv6-route</path> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear counters for specified chain</help> + </properties> + <command>echo "TODO"</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Clear policy statistics for a rule</help> + <completionHelp> + <path>policy ipv6-route ${COMP_WORDS[4]} rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear counters for specified rule</help> + </properties> + <command>echo "TODO"</command> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="route"> + <properties> + <help>Clear policy statistics for chain</help> + <completionHelp> + <path>policy route</path> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear counters for specified chain</help> + </properties> + <command>echo "TODO"</command> + </leafNode> + <tagNode name="rule"> + <properties> + <help>Clear policy statistics for a rule</help> + <completionHelp> + <path>policy route ${COMP_WORDS[4]} rule</path> + </completionHelp> + </properties> + <children> + <leafNode name="counters"> + <properties> + <help>Clear counters for specified rule</help> + </properties> + <command>echo "TODO"</command> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +--> + <node name="show"> + <children> + <node name="policy"> + <properties> + <help>Show policy information</help> + </properties> + <children> + <node name="route6"> + <properties> + <help>Show IPv6 policy chain</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/policy_route.py --action show_all --ipv6</command> + </node> + <tagNode name="route6"> + <properties> + <help>Show IPv6 policy chains</help> + <completionHelp> + <path>policy route6</path> + </completionHelp> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>Show summary of IPv6 policy rules</help> + <completionHelp> + <path>policy route6 ${COMP_WORDS[4]} rule</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/policy_route.py --action show --name $4 --rule $6 --ipv6</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/policy_route.py --action show --name $4 --ipv6</command> + </tagNode> + <node name="route"> + <properties> + <help>Show IPv4 policy chain</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/policy_route.py --action show_all</command> + </node> + <tagNode name="route"> + <properties> + <help>Show IPv4 policy chains</help> + <completionHelp> + <path>policy route</path> + </completionHelp> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>Show summary of IPv4 policy rules</help> + <completionHelp> + <path>policy route ${COMP_WORDS[4]} rule</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/policy_route.py --action show --name $4 --rule $6</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/policy_route.py --action show --name $4</command> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/poweroff.xml.in b/op-mode-definitions/poweroff.xml.in new file mode 100644 index 0000000..b4163bc --- /dev/null +++ b/op-mode-definitions/poweroff.xml.in @@ -0,0 +1,52 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="poweroff"> + <properties> + <help>Poweroff the system</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/powerctrl.py --poweroff</command> + <children> + <leafNode name="now"> + <properties> + <help>Poweroff the system without confirmation</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/powerctrl.py --yes --poweroff</command> + </leafNode> + <leafNode name="cancel"> + <properties> + <help>Cancel a pending poweroff</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/powerctrl.py --cancel</command> + </leafNode> + <tagNode name="in"> + <properties> + <help>Poweroff in X minutes</help> + <completionHelp> + <list><Minutes></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/powerctrl.py --yes --poweroff $3 $4</command> + </tagNode> + <tagNode name="at"> + <properties> + <help>Poweroff at a specific time</help> + <completionHelp> + <list><HH:MM></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/powerctrl.py --yes --poweroff $3</command> + <children> + <tagNode name="date"> + <properties> + <help>Poweroff at a specific date</help> + <completionHelp> + <list><DDMMYYYY> <DD/MM/YYYY> <DD.MM.YYYY> <DD:MM:YYYY></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/powerctrl.py --yes --poweroff $3 $5</command> + </tagNode> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/pppoe-server.xml.in b/op-mode-definitions/pppoe-server.xml.in new file mode 100644 index 0000000..835e03a --- /dev/null +++ b/op-mode-definitions/pppoe-server.xml.in @@ -0,0 +1,101 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="pppoe-server"> + <properties> + <help>Show PPPoE server status</help> + </properties> + <children> + <leafNode name="sessions"> + <properties> + <help>Show active PPPoE server sessions</help> + </properties> + <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="pppoe" --action="show sessions"</command> + </leafNode> + <leafNode name="statistics"> + <properties> + <help>Show PPPoE server statistics</help> + </properties> + <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="pppoe" --action="show stat"</command> + </leafNode> + <leafNode name="interfaces"> + <properties> + <help>Show interfaces where PPPoE server listens on</help> + </properties> + <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="pppoe" --action="pppoe interface show"</command> + </leafNode> + </children> + </node> + </children> + </node> + <node name="restart"> + <children> + <leafNode name="pppoe-server"> + <properties> + <help>Restart PPPoE server process</help> + </properties> + <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="pppoe" --action="restart"</command> + </leafNode> + </children> + </node> + <node name="reset"> + <children> + <node name="pppoe-server"> + <properties> + <help>Reset PPPoE server sessions</help> + </properties> + <children> + <leafNode name="all"> + <properties> + <help>Terminate all PPPoE server users</help> + </properties> + <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="pppoe" --action="terminate all"</command> + </leafNode> + <tagNode name="interface"> + <properties> + <help>Terminate a PPP interface</help> + </properties> + <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="pppoe" --action="terminate if $4"</command> + </tagNode> + <tagNode name="username"> + <properties> + <help>Terminate specified users</help> + </properties> + <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="pppoe" --action="terminate username $4"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="set"> + <children> + <node name="pppoe-server"> + <properties> + <help>Set PPPoE server maintenance mode</help> + </properties> + <children> + <node name="maintenance-mode"> + <properties> + <help>Set PPPoE server maintenance mode</help> + </properties> + <children> + <leafNode name="enable"> + <properties> + <help>Deny new connections and stop serving PPPoE after disconnecting the last session</help> + </properties> + <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="pppoe" --action="shutdown soft"</command> + </leafNode> + <leafNode name="cancel"> + <properties> + <help>Cancel maintenance mode</help> + </properties> + <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="pppoe" --action="shutdown cancel"</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/pptp-server.xml.in b/op-mode-definitions/pptp-server.xml.in new file mode 100644 index 0000000..f6f8104 --- /dev/null +++ b/op-mode-definitions/pptp-server.xml.in @@ -0,0 +1,26 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="pptp-server"> + <properties> + <help>Show PPTP (Point-to-Point Tunneling Protocol) server information</help> + </properties> + <children> + <leafNode name="sessions"> + <properties> + <help>Show active PPTP server sessions</help> + </properties> + <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="pptp" --action="show sessions"</command> + </leafNode> + <leafNode name="statistics"> + <properties> + <help>Show PPTP server statistics</help> + </properties> + <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="pptp" --action="show stat"</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/raid.xml.in b/op-mode-definitions/raid.xml.in new file mode 100644 index 0000000..85fbf45 --- /dev/null +++ b/op-mode-definitions/raid.xml.in @@ -0,0 +1,69 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="add"> + <children> + <tagNode name="raid"> + <properties> + <help>Add a RAID set element</help> + <completionHelp> + <script>${vyos_completion_dir}/list_raidset.sh</script> + </completionHelp> + </properties> + <children> + <node name="by-id"> + <properties> + <help>Add a member by disk id to a RAID set</help> + </properties> + <children> + <tagNode name="member"> + <properties> + <help>Add a member to a RAID set</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/raid.py add --raid-set-name $3 --by-id --member $6</command> + </tagNode> + </children> + </node> + <tagNode name="member"> + <properties> + <help>Add a member to a RAID set</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/raid.py add --raid-set-name $3 --member $5</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + <node name="delete"> + <children> + <tagNode name="raid"> + <properties> + <help>Delete a RAID set element</help> + <completionHelp> + <script>${vyos_completion_dir}/list_raidset.sh</script> + </completionHelp> + </properties> + <children> + <node name="by-id"> + <properties> + <help>Add a member by disk id to a RAID set</help> + </properties> + <children> + <tagNode name="member"> + <properties> + <help>Delete a member from a RAID set</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/raid.py delete --raid-set-name $3 --by-id --member $6</command> + </tagNode> + </children> + </node> + <tagNode name="member"> + <properties> + <help>Delete a member from a RAID set</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/raid.py delete --raid-set-name $3 --member $5</command> + </tagNode> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/reboot.xml.in b/op-mode-definitions/reboot.xml.in new file mode 100644 index 0000000..d5a71f5 --- /dev/null +++ b/op-mode-definitions/reboot.xml.in @@ -0,0 +1,52 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="reboot"> + <properties> + <help>Reboot the system</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/powerctrl.py --reboot</command> + <children> + <leafNode name="now"> + <properties> + <help>Reboot the system without confirmation</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/powerctrl.py --yes --reboot</command> + </leafNode> + <leafNode name="cancel"> + <properties> + <help>Cancel a pending reboot</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/powerctrl.py --cancel</command> + </leafNode> + <tagNode name="in"> + <properties> + <help>Reboot in X minutes</help> + <completionHelp> + <list><Minutes></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/powerctrl.py --yes --reboot-in $3 $4</command> + </tagNode> + <tagNode name="at"> + <properties> + <help>Reboot at a specific time</help> + <completionHelp> + <list><HH:MM></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/powerctrl.py --yes --reboot $3</command> + <children> + <tagNode name="date"> + <properties> + <help>Reboot at a specific date</help> + <completionHelp> + <list><DD/MM/YYYY> <DD.MM.YYYY> <DD:MM:YYYY></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/powerctrl.py --yes --reboot $3 $5</command> + </tagNode> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/reset-bgp.xml.in b/op-mode-definitions/reset-bgp.xml.in new file mode 100644 index 0000000..a1d42d4 --- /dev/null +++ b/op-mode-definitions/reset-bgp.xml.in @@ -0,0 +1,258 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="reset"> + <children> + <node name="bgp"> + <properties> + <help>Border Gateway Protocol (BGP) information</help> + </properties> + <children> + <leafNode name="all"> + <properties> + <help>Clear all peers</help> + </properties> + <command>vtysh -c "clear bgp *"</command> + </leafNode> + #include <include/bgp/reset-bgp-afi-common.xml.i> + #include <include/bgp/reset-bgp-peer-group.xml.i> + <tagNode name="prefix"> + <properties> + <help>Clear bestpath and re-advertise</help> + <completionHelp> + <list><x.x.x.x/x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <node name="ipv4"> + <properties> + <help>IPv4 Address Family</help> + </properties> + <children> + <leafNode name="all"> + <properties> + <help>Clear all peers</help> + </properties> + <command>vtysh -c "clear bgp ipv4 *"</command> + </leafNode> + #include <include/bgp/reset-bgp-afi-common.xml.i> + #include <include/bgp/reset-bgp-peer-group.xml.i> + </children> + </node> + <tagNode name="ipv4"> + <properties> + <help>IPv4 neighbor to clear</help> + <completionHelp> + <script>${vyos_completion_dir}/list_bgp_neighbors.sh --ipv4</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/reset-bgp-neighbor-options.xml.i> + </children> + </tagNode> + <node name="ipv6"> + <properties> + <help>IPv6 Address Family</help> + </properties> + <children> + <leafNode name="all"> + <properties> + <help>Clear all peers</help> + </properties> + <command>vtysh -c "clear bgp ipv6 *"</command> + </leafNode> + #include <include/bgp/reset-bgp-afi-common.xml.i> + #include <include/bgp/reset-bgp-peer-group.xml.i> + </children> + </node> + <tagNode name="ipv6"> + <properties> + <help>IPv6 neighbor to clear</help> + <completionHelp> + <script>${vyos_completion_dir}/list_bgp_neighbors.sh --ipv6</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/reset-bgp-neighbor-options.xml.i> + </children> + </tagNode> + <node name="l2vpn"> + <properties> + <help>Layer 2 Virtual Private Network Address Family</help> + </properties> + <children> + <node name="evpn"> + <properties> + <help>Ethernet Virtual Private Network</help> + </properties> + <children> + <leafNode name="all"> + <properties> + <help>Clear all peers</help> + </properties> + <command>vtysh -c "clear bgp l2vpn evpn *"</command> + </leafNode> + #include <include/bgp/reset-bgp-afi-common.xml.i> + #include <include/bgp/reset-bgp-peer-group.xml.i> + </children> + </node> + <tagNode name="evpn"> + <properties> + <help>BGP IPv4/IPv6 neighbor to clear</help> + <completionHelp> + <script>${vyos_completion_dir}/list_bgp_neighbors.sh --both</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/reset-bgp-neighbor-options.xml.i> + </children> + </tagNode> + </children> + </node> + <tagNode name="vrf"> + <properties> + <help>Virtual Routing and Forwarding (VRF)</help> + <completionHelp> + <path>vrf name</path> + </completionHelp> + </properties> + <children> + <node name="node.tag"> + <properties> + <help>IPv4/IPv6 neighbor to clear</help> + <completionHelp> + <script>${vyos_completion_dir}/list_bgp_neighbors.sh --both --vrf ${COMP_WORDS[3]}</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/reset-bgp-neighbor-options.xml.i> + </children> + </node> + <leafNode name="all"> + <properties> + <help>Clear all peers</help> + </properties> + <command>vtysh -c "clear bgp vrf $4 *"</command> + </leafNode> + #include <include/bgp/reset-bgp-afi-common.xml.i> + #include <include/bgp/reset-bgp-peer-group-vrf.xml.i> + <tagNode name="prefix"> + <properties> + <help>Clear bestpath and re-advertise</help> + <completionHelp> + <list><x.x.x.x/x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <node name="ipv4"> + <properties> + <help>IPv4 Address Family</help> + </properties> + <children> + <leafNode name="all"> + <properties> + <help>Clear all peers</help> + </properties> + <command>vtysh -c "clear bgp vrf $4 ipv4 *"</command> + </leafNode> + #include <include/bgp/reset-bgp-afi-common.xml.i> + #include <include/bgp/reset-bgp-peer-group-vrf.xml.i> + </children> + </node> + <tagNode name="ipv4"> + <properties> + <help>IPv4 neighbor to clear</help> + <completionHelp> + <script>${vyos_completion_dir}/list_bgp_neighbors.sh --ipv4 --vrf ${COMP_WORDS[3]}</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/reset-bgp-neighbor-options.xml.i> + </children> + </tagNode> + <node name="ipv6"> + <properties> + <help>IPv6 Address Family</help> + </properties> + <children> + <leafNode name="all"> + <properties> + <help>Clear all peers</help> + </properties> + <command>vtysh -c "clear bgp vrf $4 ipv6 *"</command> + </leafNode> + #include <include/bgp/reset-bgp-afi-common.xml.i> + #include <include/bgp/reset-bgp-peer-group-vrf.xml.i> + </children> + </node> + <tagNode name="ipv6"> + <properties> + <help>IPv6 neighbor to clear</help> + <completionHelp> + <script>${vyos_completion_dir}/list_bgp_neighbors.sh --ipv6 --vrf ${COMP_WORDS[3]}</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/reset-bgp-neighbor-options.xml.i> + </children> + </tagNode> + <node name="l2vpn"> + <properties> + <help>Layer 2 Virtual Private Network Address Family</help> + </properties> + <children> + <node name="evpn"> + <properties> + <help>Ethernet Virtual Private Network</help> + </properties> + <children> + <leafNode name="all"> + <properties> + <help>Clear all peers</help> + </properties> + <command>vtysh -c "clear bgp vrf $4 l2vpn evpn *"</command> + </leafNode> + #include <include/bgp/reset-bgp-afi-common.xml.i> + #include <include/bgp/reset-bgp-peer-group-vrf.xml.i> + </children> + </node> + <tagNode name="evpn"> + <properties> + <help>BGP IPv4/IPv6 neighbor to clear</help> + <completionHelp> + <script>${vyos_completion_dir}/list_bgp_neighbors.sh --both --vrf ${COMP_WORDS[3]}</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/reset-bgp-neighbor-options.xml.i> + </children> + </tagNode> + </children> + </node> + </children> + </tagNode> + </children> + </node> + <tagNode name="bgp"> + <properties> + <help>BGP IPv4/IPv6 neighbor to clear</help> + <completionHelp> + <script>${vyos_completion_dir}/list_bgp_neighbors.sh --both</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/reset-bgp-neighbor-options.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/reset-conntrack.xml.in b/op-mode-definitions/reset-conntrack.xml.in new file mode 100644 index 0000000..9c8265f --- /dev/null +++ b/op-mode-definitions/reset-conntrack.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="reset"> + <children> + <node name="conntrack"> + <properties> + <help>Reset all currently tracked connections</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/clear_conntrack.py</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/reset-ip-bgp.xml.in b/op-mode-definitions/reset-ip-bgp.xml.in new file mode 100644 index 0000000..34a4503 --- /dev/null +++ b/op-mode-definitions/reset-ip-bgp.xml.in @@ -0,0 +1,89 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="reset"> + <children> + <node name="ip"> + <children> + <node name="bgp"> + <properties> + <help>Border Gateway Protocol (BGP) information</help> + </properties> + <children> + <leafNode name="all"> + <properties> + <help>Clear all BGP peering sessions</help> + </properties> + <command>vtysh -c "clear bgp ipv4 *"</command> + </leafNode> + <node name="dampening"> + <properties> + <help>Clear BGP route flap dampening information</help> + </properties> + <command>vtysh -c "clear ip bgp dampening"</command> + </node> + <tagNode name="dampening"> + <properties> + <help>Clear BGP route flap dampening information for given host|network address</help> + <completionHelp> + <list><x.x.x.x> <x.x.x.x/x></list> + </completionHelp> + </properties> + <command>vtysh -c "clear ip bgp dampening $5"</command> + <children> + <leafNode name="node.tag"> + <properties> + <help>Clear BGP route flap dampening information for given network address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>vtysh -c "clear ip bgp dampening $5 $6"</command> + </leafNode> + </children> + </tagNode> + #include <include/bgp/reset-bgp-afi-common.xml.i> + #include <include/bgp/reset-bgp-peer-group.xml.i> + <tagNode name="vrf"> + <properties> + <help>Clear BGP statistics or status for vrf</help> + <completionHelp> + <path>vrf name</path> + </completionHelp> + </properties> + <children> + <leafNode name="all"> + <properties> + <help>Clear all BGP peering sessions for vrf</help> + </properties> + <command>vtysh -c "clear bgp vrf $5 *"</command> + </leafNode> + <leafNode name="node.tag"> + <properties> + <help>Clear BGP neighbor IP address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>vtysh -c "clear bgp vrf $5 $6"</command> + </leafNode> + </children> + </tagNode> + </children> + </node> + <tagNode name="bgp"> + <properties> + <help>BGP IPv4/IPv6 neighbor to clear</help> + <completionHelp> + <script>${vyos_completion_dir}/list_bgp_neighbors.sh --ipv4</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/reset-bgp-neighbor-options.xml.i> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/reset-ip-igmp.xml.in b/op-mode-definitions/reset-ip-igmp.xml.in new file mode 100644 index 0000000..e79c33d --- /dev/null +++ b/op-mode-definitions/reset-ip-igmp.xml.in @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="reset"> + <children> + <node name="ip"> + <children> + <node name="igmp"> + <properties> + <help>IGMP clear commands</help> + </properties> + <children> + <leafNode name="interfaces"> + <properties> + <help>Reset IGMP interfaces</help> + </properties> + <command>vtysh -c "clear ip igmp interfaces"</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/reset-ip-multicast.xml.in b/op-mode-definitions/reset-ip-multicast.xml.in new file mode 100644 index 0000000..6cca078 --- /dev/null +++ b/op-mode-definitions/reset-ip-multicast.xml.in @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="reset"> + <children> + <node name="ip"> + <children> + <node name="multicast"> + <properties> + <help>IP multicast routing table</help> + </properties> + <children> + <leafNode name="route"> + <properties> + <help>Clear multicast routing table</help> + </properties> + <command>vtysh -c "clear ip mroute"</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/reset-mpls.xml.in b/op-mode-definitions/reset-mpls.xml.in new file mode 100644 index 0000000..829ed41 --- /dev/null +++ b/op-mode-definitions/reset-mpls.xml.in @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="reset"> + <children> + <node name="mpls"> + <properties> + <help>Reset MPLS and related protocol commands</help> + </properties> + <children> + <node name="ldp"> + <properties> + <help>Reset LDP commands</help> + </properties> + <children> + <tagNode name="neighbor"> + <properties> + <help>Reset MPLS LDP neighbor/session</help> + <completionHelp> + <list><x.x.x.x> <h:h:h:h:h:h:h:h></list> + <script>vtysh -c "show mpls ldp neighbor" | awk '{print $2}' | egrep -v "ID"</script> + </completionHelp> + </properties> + <command>vtysh -c "clear mpls ldp neighbor $5"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition>
\ No newline at end of file diff --git a/op-mode-definitions/reset-vpn.xml.in b/op-mode-definitions/reset-vpn.xml.in new file mode 100644 index 0000000..8de95d1 --- /dev/null +++ b/op-mode-definitions/reset-vpn.xml.in @@ -0,0 +1,89 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="reset"> + <children> + <node name="vpn"> + <properties> + <help>Reset Virtual Private Network (VPN) information</help> + </properties> + <children> + <node name="l2tp"> + <properties> + <help>Reset L2TP server VPN sessions</help> + </properties> + <children> + <node name="all"> + <properties> + <help>Reset all L2TP server VPN sessions</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="l2tp"</command> + </node> + <tagNode name="interface"> + <properties> + <help>Reset specified interface on L2TP VPN server</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="l2tp" --interface="$5"</command> + </tagNode> + <tagNode name="user"> + <properties> + <help>Reset specified user on L2TP VPN server</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="l2tp" --username="$5"</command> + </tagNode> + </children> + </node> + <node name="pptp"> + <properties> + <help>Reset PPTP server VPN sessions</help> + </properties> + <children> + <node name="all"> + <properties> + <help>Reset all PPTP server VPN sessions</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="pptp"</command> + </node> + <tagNode name="interface"> + <properties> + <help>Reset specified interface on PPTP VPN server</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="pptp" --interface="$5"</command> + </tagNode> + <tagNode name="user"> + <properties> + <help>Reset specified user on PPTP VPN server</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="pptp" --username="$5"</command> + </tagNode> + </children> + </node> + <node name="sstp"> + <properties> + <help>Reset SSTP server VPN sessions</help> + </properties> + <children> + <node name="all"> + <properties> + <help>Reset all SSTP server VPN sessions</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="sstp"</command> + </node> + <tagNode name="interface"> + <properties> + <help>Reset specified interface on SSTP VPN server</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="sstp" --interface="$5"</command> + </tagNode> + <tagNode name="user"> + <properties> + <help>Reset specified user on SSTP VPN server</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py reset_conn --protocol="sstp" --username="$5"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/restart-frr.xml.in b/op-mode-definitions/restart-frr.xml.in new file mode 100644 index 0000000..4772e8d --- /dev/null +++ b/op-mode-definitions/restart-frr.xml.in @@ -0,0 +1,91 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="restart"> + <children> + <leafNode name="all"> + <properties> + <help>Restart all routing daemons</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart</command> + </leafNode> + <leafNode name="zebra"> + <properties> + <help>Restart Routing Information Base (RIB) IP manager daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon zebra</command> + </leafNode> + <leafNode name="static"> + <properties> + <help>Restart static routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon staticd</command> + </leafNode> + <leafNode name="bgp"> + <properties> + <help>Restart Border Gateway Protocol (BGP) routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon bgpd</command> + </leafNode> + <leafNode name="ospf"> + <properties> + <help>Restart Open Shortest Path First (OSPF) routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ospfd</command> + </leafNode> + <leafNode name="ospfv3"> + <properties> + <help>Restart IPv6 Open Shortest Path First (OSPFv3) routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ospf6d</command> + </leafNode> + <leafNode name="rip"> + <properties> + <help>Restart Routing Information Protocol (RIP) routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ripd</command> + </leafNode> + <leafNode name="ripng"> + <properties> + <help>Restart IPv6 Routing Information Protocol (RIPng) routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ripngd</command> + </leafNode> + <leafNode name="isis"> + <properties> + <help>Restart Intermediate System to Intermediate System (IS-IS) routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon isisd</command> + </leafNode> + <leafNode name="openfabric"> + <properties> + <help>Restart OpenFabric routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon fabricd</command> + </leafNode> + <leafNode name="pim6"> + <properties> + <help>Restart IPv6 Protocol Independent Multicast (PIM) daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon pim6d</command> + </leafNode> + <leafNode name="ldp"> + <properties> + <help>Restart Label Distribution Protocol (LDP) daemon used by MPLS</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ldpd</command> + </leafNode> + <leafNode name="babel"> + <properties> + <help>Restart Babel routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon babeld</command> + </leafNode> + <leafNode name="bfd"> + <properties> + <help>Restart Bidirectional Forwarding Detection (BFD) daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon bfdd</command> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/restart-ntp.xml.in b/op-mode-definitions/restart-ntp.xml.in new file mode 100644 index 0000000..961fae2 --- /dev/null +++ b/op-mode-definitions/restart-ntp.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="restart"> + <children> + <node name="ntp"> + <properties> + <help>Restart NTP service</help> + </properties> + <command>if cli-shell-api existsActive service ntp; then sudo systemctl restart chrony.service; else echo "Service NTP not configured"; fi</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/restart-router-advert.xml.in b/op-mode-definitions/restart-router-advert.xml.in new file mode 100644 index 0000000..9eea3df --- /dev/null +++ b/op-mode-definitions/restart-router-advert.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="restart"> + <children> + <node name="router-advert"> + <properties> + <help>Restart IPv6 Router Advertisement service</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name router_advert</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/restart-serial.xml.in b/op-mode-definitions/restart-serial.xml.in new file mode 100644 index 0000000..4d8a036 --- /dev/null +++ b/op-mode-definitions/restart-serial.xml.in @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="restart"> + <children> + <node name="serial"> + <properties> + <help>Restart services on serial ports</help> + </properties> + <children> + <node name="console"> + <properties> + <help>Restart serial console service for login TTYs</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/serial.py restart_console</command> + <children> + <tagNode name="device"> + <properties> + <help>Restart specific TTY device</help> + <completionHelp> + <script>${vyos_completion_dir}/list_login_ttys.py</script> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/serial.py restart_console --device-name "$5"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/restart-snmp.xml.in b/op-mode-definitions/restart-snmp.xml.in new file mode 100644 index 0000000..e9c43de --- /dev/null +++ b/op-mode-definitions/restart-snmp.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="restart"> + <children> + <node name="snmp"> + <properties> + <help>Restart SNMP service</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name snmp</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/restart-ssh.xml.in b/op-mode-definitions/restart-ssh.xml.in new file mode 100644 index 0000000..914586d --- /dev/null +++ b/op-mode-definitions/restart-ssh.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="restart"> + <children> + <node name="ssh"> + <properties> + <help>Restart SSH service</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name ssh --vrf "*"</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/restart.xml.in b/op-mode-definitions/restart.xml.in new file mode 100644 index 0000000..c74ec90 --- /dev/null +++ b/op-mode-definitions/restart.xml.in @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="restart"> + <properties> + <help>Restart individual service</help> + </properties> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/reverse-proxy.xml.in b/op-mode-definitions/reverse-proxy.xml.in new file mode 100644 index 0000000..b45ce10 --- /dev/null +++ b/op-mode-definitions/reverse-proxy.xml.in @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="restart"> + <children> + <node name="reverse-proxy"> + <properties> + <help>Restart reverse-proxy service</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name reverse_proxy</command> + </node> + </children> + </node> + <node name="show"> + <children> + <node name="reverse-proxy"> + <properties> + <help>Show load-balancing reverse-proxy</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/reverseproxy.py show</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/rpki.xml.in b/op-mode-definitions/rpki.xml.in new file mode 100644 index 0000000..9e0f83e --- /dev/null +++ b/op-mode-definitions/rpki.xml.in @@ -0,0 +1,71 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="rpki"> + <properties> + <help>Show RPKI (Resource Public Key Infrastructure) information</help> + </properties> + <children> + <tagNode name="as-number"> + <properties> + <help>Lookup by ASN in prefix table</help> + <completionHelp> + <list><ASNUM></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <leafNode name="cache-connection"> + <properties> + <help>Show RPKI cache connections</help> + </properties> + <command>vtysh -c "show rpki cache-connection"</command> + </leafNode> + <leafNode name="cache-server"> + <properties> + <help>Show RPKI cache servers information</help> + </properties> + <command>vtysh -c "show rpki cache-server"</command> + </leafNode> + <tagNode name="prefix"> + <properties> + <help>Lookup IP prefix and optionally ASN in prefix table</help> + <completionHelp> + <list><x.x.x.x/x> <h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <tagNode name="as-number"> + <properties> + <help>AS Number</help> + <completionHelp> + <list><ASNUM></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $(echo $@ | sed -e "s/as-number //g")</command> + </tagNode> + </children> + </tagNode> + <leafNode name="prefix-table"> + <properties> + <help>Show RPKI-validated prefixes</help> + </properties> + <command>vtysh -c "show rpki prefix-table"</command> + </leafNode> + </children> + </node> + </children> + </node> + <node name="reset"> + <children> + <leafNode name="rpki"> + <properties> + <help>Reset RPKI</help> + </properties> + <command>vtysh -c "rpki reset"</command> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/sflow.xml.in b/op-mode-definitions/sflow.xml.in new file mode 100644 index 0000000..9f02dac --- /dev/null +++ b/op-mode-definitions/sflow.xml.in @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- sflow op mode commands --> +<interfaceDefinition> + <node name="show"> + <children> + <node name="sflow"> + <properties> + <help>Show sFlow statistics</help> + </properties> + <!-- requires sudo, do not remove it --> + <command>sudo ${vyos_op_scripts_dir}/sflow.py show</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-acceleration.xml.in b/op-mode-definitions/show-acceleration.xml.in new file mode 100644 index 0000000..fccfba5 --- /dev/null +++ b/op-mode-definitions/show-acceleration.xml.in @@ -0,0 +1,63 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="system"> + <properties> + <help>Show system information</help> + </properties> + <children> + <node name="acceleration"> + <properties> + <help>Acceleration components</help> + </properties> + <children> + <node name="qat"> + <properties> + <help>Intel QAT (Quick Assist Technology) Devices</help> + </properties> + <children> + <tagNode name="device"> + <properties> + <help>Show QAT information for a given acceleration device</help> + <completionHelp> + <script>${vyos_op_scripts_dir}/show_acceleration.py --dev-list</script> + </completionHelp> + </properties> + <children> + <node name="flows"> + <properties> + <help>Intel QAT flows</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_acceleration.py --flow --dev $6</command> + </node> + <node name="config"> + <properties> + <help>Intel QAT configuration</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_acceleration.py --conf --dev $6</command> + </node> + </children> + </tagNode> + <node name="status"> + <properties> + <help>Intel QAT status</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_acceleration.py --status</command> + </node> + <node name="interrupts"> + <properties> + <help>Intel QAT interrupts</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_acceleration.py --interrupts</command> + </node> + </children> + <command>sudo ${vyos_op_scripts_dir}/show_acceleration.py --hw</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-arp.xml.in b/op-mode-definitions/show-arp.xml.in new file mode 100644 index 0000000..84170f0 --- /dev/null +++ b/op-mode-definitions/show-arp.xml.in @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="arp"> + <properties> + <help>Show Address Resolution Protocol (ARP) information</help> + </properties> + <command>${vyos_op_scripts_dir}/neighbor.py show --family inet</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show Address Resolution Protocol (ARP) cache for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --broadcast</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/neighbor.py show --family inet --interface "$4"</command> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-babel.xml.in b/op-mode-definitions/show-babel.xml.in new file mode 100644 index 0000000..0a1f1b2 --- /dev/null +++ b/op-mode-definitions/show-babel.xml.in @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="babel"> + <properties> + <help>Show Babel routing protocol information</help> + </properties> + <children> + <leafNode name="interface"> + <properties> + <help>Show Babel Interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="neighbor"> + <properties> + <help>Show Babel neighbor information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <tagNode name="neighbor"> + <properties> + <help>Show Babel neighbor information for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <leafNode name="route"> + <properties> + <help>Show Babel route information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-bfd.xml.in b/op-mode-definitions/show-bfd.xml.in new file mode 100644 index 0000000..87d672e --- /dev/null +++ b/op-mode-definitions/show-bfd.xml.in @@ -0,0 +1,69 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="bfd"> + <properties> + <help>Show Bidirectional Forwarding Detection (BFD)</help> + </properties> + <children> + <node name="peer"> + <properties> + <help>Show all Bidirectional Forwarding Detection (BFD) peer status</help> + </properties> + </node> + <tagNode name="peer"> + <properties> + <help>Show Bidirectional Forwarding Detection (BFD) peer status</help> + <completionHelp> + <script>vtysh -c "show bfd peers" | awk '/[:blank:]*peer/ { printf "%s\n", $2 }'</script> + </completionHelp> + </properties> + <command>vtysh -c "show bfd peers" | sed -n "/peer $4 /,/^$/p"</command> + <children> + <leafNode name="counters"> + <properties> + <help>Show Bidirectional Forwarding Detection (BFD) peer counters</help> + </properties> + <command>vtysh -c "show bfd peers counters" | sed -n "/peer $4 /,/^$/p"</command> + </leafNode> + </children> + </tagNode> + <node name="peers"> + <properties> + <help>Show Bidirectional Forwarding Detection peers</help> + </properties> + <command>vtysh -c "show bfd peers"</command> + <children> + <leafNode name="counters"> + <properties> + <help>Show Bidirectional Forwarding Detection (BFD) peer counters</help> + </properties> + <command>vtysh -c "show bfd peers counters"</command> + </leafNode> + <leafNode name="brief"> + <properties> + <help>Show Bidirectional Forwarding Detection (BFD) peers brief</help> + </properties> + <command>vtysh -c "show bfd peers brief"</command> + </leafNode> + </children> + </node> + <node name="static"> + <properties> + <help>Show route Routing Table</help> + </properties> + <children> + <leafNode name="routes"> + <properties> + <help>Showing BFD monitored static routes</help> + </properties> + <command>vtysh -c "show bfd static route"</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-bgp.xml.in b/op-mode-definitions/show-bgp.xml.in new file mode 100644 index 0000000..8b19924 --- /dev/null +++ b/op-mode-definitions/show-bgp.xml.in @@ -0,0 +1,120 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="bgp"> + <properties> + <help>BGP information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="cidr-only"> + <properties> + <help>Display only routes with non-natural netmasks</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/vtysh-generic-wide.xml.i> + </children> + </node> + #include <include/bgp/show-bgp-common.xml.i> + <node name="mac"> + <properties> + <help>MAC address</help> + </properties> + <children> + <leafNode name="hash"> + <properties> + <help>MAC address database</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> + #include <include/bgp/martian-next-hop.xml.i> + <leafNode name="memory"> + <properties> + <help>Global BGP memory statistics</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + #include <include/bgp/next-hop.xml.i> + <leafNode name="statistics"> + <properties> + <help>BGP RIB advertisement statistics</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="statistics-all"> + <properties> + <help>Display number of prefixes for all afi/safi</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + #include <include/vni-tagnode-all.xml.i> + <tagNode name="vni"> + <children> + <tagNode name="vtep"> + <properties> + <help>Remote VTEP IP address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </tagNode> + <node name="type"> + <properties> + <help>Display number of prefixes for all afi/safi</help> + </properties> + <children> + #include <include/bgp/evpn-type-1.xml.i> + #include <include/bgp/evpn-type-2.xml.i> + #include <include/bgp/evpn-type-3.xml.i> + #include <include/bgp/evpn-type-ead.xml.i> + #include <include/bgp/evpn-type-macip.xml.i> + #include <include/bgp/evpn-type-multicast.xml.i> + </children> + </node> + </children> + </tagNode> + <leafNode name="vrf"> + <properties> + <help>Show BGP VRF information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <tagNode name="vrf"> + <properties> + <help>Show BGP VRF related information</help> + <completionHelp> + <path>vrf name</path> + <list>all</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/show-bgp-common.xml.i> + #include <include/bgp/martian-next-hop.xml.i> + #include <include/bgp/next-hop.xml.i> + </children> + </tagNode> + #include <include/vtysh-generic-wide.xml.i> + <node name="segment-routing"> + <properties> + <help>BGP Segment Routing</help> + </properties> + <children> + <leafNode name="srv6"> + <properties> + <help>BGP Segment Routing SRv6</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-bridge.xml.in b/op-mode-definitions/show-bridge.xml.in new file mode 100644 index 0000000..5d8cc38 --- /dev/null +++ b/op-mode-definitions/show-bridge.xml.in @@ -0,0 +1,75 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="bridge"> + <properties> + <help>Show bridging information</help> + </properties> + <children> + <node name="vlan"> + <properties> + <help>View the VLAN filter settings of the bridge</help> + </properties> + <command>${vyos_op_scripts_dir}/bridge.py show_vlan</command> + <children> + <leafNode name="tunnel"> + <properties> + <help>Show bridge VLAN tunnel mapping</help> + </properties> + <command>${vyos_op_scripts_dir}/bridge.py show_vlan --tunnel</command> + </leafNode> + </children> + </node> + <leafNode name="vni"> + <properties> + <help>Virtual Network Identifier</help> + </properties> + <command>${vyos_op_scripts_dir}/bridge.py show_vni</command> + </leafNode> + </children> + </node> + <leafNode name="bridge"> + <properties> + <help>Show bridging information</help> + </properties> + <command>${vyos_op_scripts_dir}/bridge.py show</command> + </leafNode> + <tagNode name="bridge"> + <properties> + <help>Show bridge information for a given bridge interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type bridge</script> + </completionHelp> + </properties> + <command>bridge -c link show | grep "master $3"</command> + <children> + <leafNode name="mdb"> + <properties> + <help>Displays the multicast group database for the bridge</help> + </properties> + <command>${vyos_op_scripts_dir}/bridge.py show_mdb --interface=$3</command> + </leafNode> + <leafNode name="fdb"> + <properties> + <help>Show the forwarding database of the bridge</help> + </properties> + <command>${vyos_op_scripts_dir}/bridge.py show_fdb --interface=$3</command> + </leafNode> + <leafNode name="detail"> + <properties> + <help>Display bridge interface details</help> + </properties> + <command>${vyos_op_scripts_dir}/bridge.py show_detail --interface=$3</command> + </leafNode> + <leafNode name="nexthop-group"> + <properties> + <help>Display bridge interface nexthop-group</help> + </properties> + <command>${vyos_op_scripts_dir}/bridge.py show_detail --nexthop_group --interface=$3</command> + </leafNode> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-configuration.xml.in b/op-mode-definitions/show-configuration.xml.in new file mode 100644 index 0000000..5a2fded --- /dev/null +++ b/op-mode-definitions/show-configuration.xml.in @@ -0,0 +1,52 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="configuration"> + <properties> + <help>Show available saved configurations</help> + </properties> + <!-- no admin check --> + <command>cli-shell-api showCfg --show-active-only --show-hide-secrets</command> + <children> + <node name="all"> + <properties> + <help>Show running configuration (including default values)</help> + </properties> + <!-- no admin check --> + <command>cli-shell-api showCfg --show-show-defaults --show-active-only --show-hide-secrets</command> + </node> + <node name="commands"> + <properties> + <help> Show running configuration as set commands </help> + </properties> + <!-- no admin check --> + <command>cli-shell-api showCfg --show-active-only | vyos-config-to-commands</command> + </node> + <node name="files"> + <properties> + <help> Show available saved configurations </help> + </properties> + <!-- no admin check --> + <command>${vyos_op_scripts_dir}/show_configuration_files.sh</command> + </node> + <node name="json"> + <properties> + <help>Show running configuration in JSON format</help> + </properties> + <!-- no admin check --> + <command>${vyos_op_scripts_dir}/show_configuration_json.py</command> + <children> + <node name="pretty"> + <properties> + <help>Show running configuration in readable JSON format</help> + </properties> + <command>${vyos_op_scripts_dir}/show_configuration_json.py --pretty</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-conntrack.xml.in b/op-mode-definitions/show-conntrack.xml.in new file mode 100644 index 0000000..4cdcffc --- /dev/null +++ b/op-mode-definitions/show-conntrack.xml.in @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="conntrack"> + <properties> + <help>Show conntrack tables entries</help> + </properties> + <children> + <node name="statistics"> + <properties> + <help>Show conntrack statistics</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/conntrack.py show_statistics</command> + </node> + <node name="table"> + <properties> + <help>Show conntrack entries for table</help> + </properties> + <children> + <node name="ipv4"> + <properties> + <help>Show conntrack entries for IPv4 protocol</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/conntrack.py show --family inet</command> + </node> + <node name="ipv6"> + <properties> + <help>Show conntrack entries for IPv6 protocol</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/conntrack.py show --family inet6</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-console-server.xml.in b/op-mode-definitions/show-console-server.xml.in new file mode 100644 index 0000000..eae6fd5 --- /dev/null +++ b/op-mode-definitions/show-console-server.xml.in @@ -0,0 +1,36 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="log"> + <children> + <leafNode name="console-server"> + <properties> + <help>Show log for serial console server</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit conserver-server.service</command> + </leafNode> + </children> + </node> + <node name="console-server"> + <properties> + <help>Show Console-Server information</help> + </properties> + <children> + <leafNode name="ports"> + <properties> + <help>Examine console ports and configured baud rates</help> + </properties> + <command>/usr/bin/console -x</command> + </leafNode> + <leafNode name="user"> + <properties> + <help>Show users on various consoles</help> + </properties> + <command>/usr/bin/console -u</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-environment.xml.in b/op-mode-definitions/show-environment.xml.in new file mode 100644 index 0000000..95b6587 --- /dev/null +++ b/op-mode-definitions/show-environment.xml.in @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="environment"> + <properties> + <help>Show current system environmental conditions</help> + </properties> + <children> + <leafNode name="sensors"> + <properties> + <help>Show hardware monitoring results</help> + </properties> + <!-- Linux always adds "hypervisor" to CPU flags --> + <command>if ! grep -q hypervisor /proc/cpuinfo; then ${vyos_libexec_dir}/vyos-sudo.py ${vyos_op_scripts_dir}/show_sensors.py; else echo "VyOS running under hypervisor, no sensors available"; fi</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-evpn.xml.in b/op-mode-definitions/show-evpn.xml.in new file mode 100644 index 0000000..3c1e5c7 --- /dev/null +++ b/op-mode-definitions/show-evpn.xml.in @@ -0,0 +1,129 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="evpn"> + <properties> + <help>Show Ethernet VPN (EVPN) information</help> + </properties> + <children> + <node name="access-vlan"> + <properties> + <help>Access VLANs</help> + </properties> + <children> + #include <include/frr-detail.xml.i> + </children> + <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command> + </node> + <tagNode name="access-vlan"> + <properties> + <help>Access VLANs interface name</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --bridgeable --no-vlan-subinterfaces</script> + </completionHelp> + </properties> + <children> + <node name="node.tag"> + <properties> + <help>VLAN ID</help> + <completionHelp> + <list><1-4094></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command> + </node> + </children> + </tagNode> + <node name="arp-cache"> + <properties> + <help>ARP and ND cache</help> + </properties> + <children> + #include <include/vni-tagnode-all.xml.i> + </children> + </node> + <tagNode name="es"> + <properties> + <help>Show ESI information for specified ESI</help> + <completionHelp> + <list><esi></list> + <script>${vyos_completion_dir}/list_esi.sh</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command> + </tagNode> + <node name="es"> + <properties> + <help>Show ESI information</help> + </properties> + <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show ESI details</help> + </properties> + <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command> + </leafNode> + </children> + </node> + <node name="es-evi"> + <properties> + <help>Show ESI information per EVI</help> + </properties> + <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show ESI per EVI details</help> + </properties> + <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command> + </leafNode> + #include <include/vni-tagnode.xml.i> + </children> + </node> + <node name="mac"> + <properties> + <help>MAC addresses</help> + </properties> + <children> + #include <include/vni-tagnode-all.xml.i> + </children> + </node> + <node name="next-hops"> + <properties> + <help>Remote VTEPs</help> + </properties> + <children> + #include <include/vni-tagnode-all.xml.i> + </children> + </node> + <node name="rmac"> + <properties> + <help>RMAC</help> + </properties> + <children> + #include <include/vni-tagnode-all.xml.i> + </children> + </node> + #include <include/vni-tagnode.xml.i> + <node name="vni"> + <properties> + <help>Show VNI information</help> + </properties> + <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show VNI details</help> + </properties> + <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command> + </leafNode> + </children> + </node> + </children> + <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-hardware.xml.in b/op-mode-definitions/show-hardware.xml.in new file mode 100644 index 0000000..2107976 --- /dev/null +++ b/op-mode-definitions/show-hardware.xml.in @@ -0,0 +1,116 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="hardware"> + <properties> + <help>Show system hardware details</help> + </properties> + <children> + <node name="cpu"> + <properties> + <help>Show CPU informaion</help> + </properties> + <command>${vyos_op_scripts_dir}/cpu.py show</command> + <children> + <node name="detail"> + <properties> + <help>Show system CPU details</help> + </properties> + <command>cat /proc/cpuinfo</command> + </node> + <node name="summary"> + <properties> + <help>Show system CPUs summary</help> + </properties> + <command>${vyos_op_scripts_dir}/cpu.py show_summary</command> + </node> + </children> + </node> + <node name="dmi"> + <properties> + <help>Show system DMI details</help> + </properties> + <command>sudo dmidecode</command> + </node> + <node name="mem"> + <properties> + <help>Show system RAM details</help> + </properties> + <command>cat /proc/meminfo</command> + </node> + <node name="pci"> + <properties> + <help>Show system PCI bus details</help> + </properties> + <command>lspci</command> + <children> + <node name="detail"> + <properties> + <help>Show verbose system PCI bus details</help> + </properties> + <command>lspci -vvv</command> + </node> + </children> + </node> + <node name="storage"> + <properties> + <help>Show system storage information</help> + </properties> + <children> + <leafNode name="nvme"> + <properties> + <help>Show NVMe device information</help> + </properties> + <command>sudo nvme list</command> + </leafNode> + <node name="scsi"> + <properties> + <help>Show SCSI device information</help> + </properties> + <command>lsscsi</command> + <children> + <node name="detail"> + <properties> + <help>Show detailed SCSI device information</help> + </properties> + <command>lsscsi -vvv</command> + </node> + </children> + </node> + <tagNode name="smart"> + <properties> + <help>Show S.M.A.R.T. device information</help> + <completionHelp> + <script>ls /dev | egrep '([hsv]d[a-z]|nvme[0-9]+n[0-9])$'</script> + </completionHelp> + </properties> + <command>sudo smartctl -a "/dev/$5" | sed 1,3d</command> + </tagNode> + </children> + </node> + <node name="usb"> + <properties> + <help>Show peripherals connected to the USB bus</help> + </properties> + <command>/usr/bin/lsusb -t</command> + <children> + <node name="detail"> + <properties> + <help>Show detailed USB bus information</help> + </properties> + <command>/usr/bin/lsusb -v</command> + </node> + <leafNode name="serial"> + <properties> + <help>Show information about connected USB serial ports</help> + </properties> + <command>${vyos_op_scripts_dir}/show_usb_serial.py</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-history.xml.in b/op-mode-definitions/show-history.xml.in new file mode 100644 index 0000000..7fb2862 --- /dev/null +++ b/op-mode-definitions/show-history.xml.in @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="history"> + <properties> + <help>Show command history</help> + </properties> + <command>HISTTIMEFORMAT='%FT%T%z ' HISTFILE="$HOME/.bash_history" \set -o history; history</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show recent command history</help> + </properties> + <command>HISTTIMEFORMAT='%FT%T%z ' HISTFILE="$HOME/.bash_history" \set -o history; history 20</command> + </leafNode> + </children> + </node> + + <tagNode name="history"> + <properties> + <help>Show last N commands in history</help> + <completionHelp> + <list><NUMBER></list> + </completionHelp> + </properties> + <command>HISTTIMEFORMAT='%FT%T%z ' HISTFILE="$HOME/.bash_history" \set -o history; history $3</command> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-host.xml.in b/op-mode-definitions/show-host.xml.in new file mode 100644 index 0000000..eee1288 --- /dev/null +++ b/op-mode-definitions/show-host.xml.in @@ -0,0 +1,44 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="host"> + <properties> + <help>Show host information</help> + </properties> + <children> + <leafNode name="date"> + <properties> + <help>Show host current date</help> + </properties> + <command>/bin/date</command> + </leafNode> + <leafNode name="domain"> + <properties> + <help>Show domain name</help> + </properties> + <command>/bin/domainname -d</command> + </leafNode> + <leafNode name="name"> + <properties> + <help>Show host name</help> + </properties> + <command>/bin/hostname</command> + </leafNode> + <tagNode name="lookup"> + <properties> + <help>Lookup host information for hostname|IPv4 address</help> + </properties> + <command>/usr/bin/host $4</command> + </tagNode> + <leafNode name="os"> + <properties> + <help>Show host operating system details</help> + </properties> + <command>/bin/uname -a</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-bonding.xml.in b/op-mode-definitions/show-interfaces-bonding.xml.in new file mode 100644 index 0000000..e295033 --- /dev/null +++ b/op-mode-definitions/show-interfaces-bonding.xml.in @@ -0,0 +1,109 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="bonding"> + <properties> + <help>Show specified Bonding interface information</help> + <completionHelp> + <path>interfaces bonding</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=bonding</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of the specified bonding interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4" --intf-type=bonding</command> + </leafNode> + <leafNode name="detail"> + <properties> + <help>Show detailed interface information</help> + </properties> + <command>if [ -f "/proc/net/bonding/$4" ]; then sudo cat "/proc/net/bonding/$4"; else echo "Interface $4 does not exist!"; fi</command> + </leafNode> + <node name="lacp"> + <properties> + <help>Show LACP related info</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show LACP details</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/bonding.py show_lacp_detail --interface="$4" </command> + </leafNode> + <leafNode name="neighbors"> + <properties> + <help>Show LACP Neighbors</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/bonding.py show_lacp_neighbors --interface="$4"</command> + </leafNode> + </children> + </node> + <leafNode name="slaves"> + <properties> + <help>Show specified bonding interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/show-bond.py --interface "$4"</command> + </leafNode> + <tagNode name="vif"> + <properties> + <help>Show specified virtual network interface (vif) information</help> + <completionHelp> + <path>interfaces bonding ${COMP_WORDS[3]} vif</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4.$6" --intf-type=bonding</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of specified virtual network interface (vif) information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4.$6" --intf-type=bonding</command> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <node name="bonding"> + <properties> + <help>Show Bonding interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=bonding</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed bonding interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=bonding</command> + </leafNode> + <node name="lacp"> + <properties> + <help>Show LACP related info</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Show LACP details</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/bonding.py show_lacp_detail</command> + </leafNode> + </children> + </node> + <leafNode name="slaves"> + <properties> + <help>Show specified bonding interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/show-bond.py --slaves</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-bridge.xml.in b/op-mode-definitions/show-interfaces-bridge.xml.in new file mode 100644 index 0000000..dc81368 --- /dev/null +++ b/op-mode-definitions/show-interfaces-bridge.xml.in @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="bridge"> + <properties> + <help>Show specified Bridge interface information</help> + <completionHelp> + <path>interfaces bridge</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=bridge</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of the specified bridge interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4" --intf-type=bridge</command> + </leafNode> + </children> + </tagNode> + <node name="bridge"> + <properties> + <help>Show Bridge interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=bridge</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed bridge interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=bridge</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-dummy.xml.in b/op-mode-definitions/show-interfaces-dummy.xml.in new file mode 100644 index 0000000..b8ec7da --- /dev/null +++ b/op-mode-definitions/show-interfaces-dummy.xml.in @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="dummy"> + <properties> + <help>Show specified Dummy interface information</help> + <completionHelp> + <path>interfaces dummy</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=dummy</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of the specified dummy interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4" --intf-type=dummy</command> + </leafNode> + </children> + </tagNode> + <node name="dummy"> + <properties> + <help>Show Dummy interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=dummy</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed dummy interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=dummy</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-ethernet.xml.in b/op-mode-definitions/show-interfaces-ethernet.xml.in new file mode 100644 index 0000000..09f0b39 --- /dev/null +++ b/op-mode-definitions/show-interfaces-ethernet.xml.in @@ -0,0 +1,91 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="ethernet"> + <properties> + <help>Show specified Ethernet interface information</help> + <completionHelp> + <path>interfaces ethernet</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=ethernet</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of the specified ethernet interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4" --intf-type=ethernet</command> + </leafNode> + <leafNode name="identify"> + <properties> + <help>Visually identify specified ethernet interface</help> + </properties> + <command>echo "Blinking interface $4 for 30 seconds."; ethtool --identify "$4" 30</command> + </leafNode> + <node name="physical"> + <properties> + <help>Show physical device information for specified ethernet interface</help> + </properties> + <command>ethtool "$4"; ethtool --show-ring "$4"; ethtool --driver "$4"</command> + <children> + <leafNode name="offload"> + <properties> + <help>Show physical device offloading capabilities</help> + </properties> + <command>ethtool --show-features "$4" | sed -e 1d -e '/fixed/d' -e 's/^\t*//g' -e 's/://' | column -t -s' '</command> + </leafNode> + </children> + </node> + <leafNode name="statistics"> + <properties> + <help>Show physical device statistics for specified ethernet interface</help> + </properties> + <command>ethtool --statistics "$4"</command> + </leafNode> + <leafNode name="transceiver"> + <properties> + <help>Show transceiver information from modules (e.g SFP+, QSFP)</help> + </properties> + <command>ethtool --module-info "$4"</command> + </leafNode> + <tagNode name="vif"> + <properties> + <help>Show specified virtual network interface (vif) information</help> + <completionHelp> + <path>interfaces ethernet ${COMP_WORDS[3]} vif</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4.$6" --intf-type=ethernet</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of specified virtual network interface (vif) information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4.$6" --intf-type=ethernet</command> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <node name="ethernet"> + <properties> + <help>Show Ethernet interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=ethernet</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed ethernet interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=ethernet</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-geneve.xml.in b/op-mode-definitions/show-interfaces-geneve.xml.in new file mode 100644 index 0000000..d3d1880 --- /dev/null +++ b/op-mode-definitions/show-interfaces-geneve.xml.in @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="geneve"> + <properties> + <help>Show specified GENEVE interface information</help> + <completionHelp> + <path>interfaces geneve</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=geneve</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of the specified GENEVE interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4" --intf-type=geneve</command> + </leafNode> + </children> + </tagNode> + <node name="geneve"> + <properties> + <help>Show GENEVE interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=geneve</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed GENEVE interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=geneve</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-input.xml.in b/op-mode-definitions/show-interfaces-input.xml.in new file mode 100644 index 0000000..e5d4200 --- /dev/null +++ b/op-mode-definitions/show-interfaces-input.xml.in @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="input"> + <properties> + <help>Show specified Input interface information</help> + <completionHelp> + <path>interfaces input</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=input</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of the specified input interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4" --intf-type=input</command> + </leafNode> + </children> + </tagNode> + <node name="input"> + <properties> + <help>Show Input (ifb) interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=input</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed input interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=input</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-l2tpv3.xml.in b/op-mode-definitions/show-interfaces-l2tpv3.xml.in new file mode 100644 index 0000000..2d16517 --- /dev/null +++ b/op-mode-definitions/show-interfaces-l2tpv3.xml.in @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="l2tpv3"> + <properties> + <help>Show specified L2TPv3 interface information</help> + <completionHelp> + <path>interfaces l2tpv3</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=l2tpv3</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of the specified L2TPv3 interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4" --intf-type=l2tpv3</command> + </leafNode> + </children> + </tagNode> + <node name="l2tpv3"> + <properties> + <help>Show L2TPv3 interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=l2tpv3</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed L2TPv3 interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=l2tpv3</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-loopback.xml.in b/op-mode-definitions/show-interfaces-loopback.xml.in new file mode 100644 index 0000000..d341a63 --- /dev/null +++ b/op-mode-definitions/show-interfaces-loopback.xml.in @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="loopback"> + <properties> + <help>Show specified Loopback interface information</help> + <completionHelp> + <path>interfaces loopback</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=loopback</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of the specified Loopback interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4" --intf-type=loopback</command> + </leafNode> + </children> + </tagNode> + <node name="loopback"> + <properties> + <help>Show Loopback interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=loopback</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed Loopback interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=loopback</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-macsec.xml.in b/op-mode-definitions/show-interfaces-macsec.xml.in new file mode 100644 index 0000000..28264d2 --- /dev/null +++ b/op-mode-definitions/show-interfaces-macsec.xml.in @@ -0,0 +1,37 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <node name="macsec"> + <properties> + <help>Show MACsec interface information</help> + <completionHelp> + <path>interfaces macsec</path> + </completionHelp> + </properties> + <command>ip macsec show</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed MACsec interface information</help> + </properties> + <command>ip -s macsec show</command> + </leafNode> + </children> + </node> + <tagNode name="macsec"> + <properties> + <help>Show specified MACsec interface information</help> + <completionHelp> + <path>interfaces macsec</path> + </completionHelp> + </properties> + <command>ip macsec show $4</command> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-pppoe.xml.in b/op-mode-definitions/show-interfaces-pppoe.xml.in new file mode 100644 index 0000000..1c6e0b8 --- /dev/null +++ b/op-mode-definitions/show-interfaces-pppoe.xml.in @@ -0,0 +1,51 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="pppoe"> + <properties> + <help>Show specified PPPoE interface information</help> + <completionHelp> + <path>interfaces pppoe</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=pppoe</command> + <children> + <leafNode name="log"> + <properties> + <help>Show specified PPPoE interface log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit "ppp@$4".service</command> + </leafNode> + <leafNode name="statistics"> + <properties> + <help>Show specified PPPoE interface statistics</help> + <completionHelp> + <path>interfaces pppoe</path> + </completionHelp> + </properties> + <command>if [ -d "/sys/class/net/$4" ]; then /usr/sbin/pppstats "$4"; fi</command> + </leafNode> + </children> + </tagNode> + <node name="pppoe"> + <properties> + <help>Show PPPoE interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=pppoe</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed PPPoE interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=pppoe</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in b/op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in new file mode 100644 index 0000000..4ab2a5f --- /dev/null +++ b/op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="pseudo-ethernet"> + <properties> + <help>Show specified Pseudo-Ethernet/MACvlan interface information</help> + <completionHelp> + <path>interfaces pseudo-ethernet</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=pseudo-ethernet</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of the specified pseudo-ethernet/MACvlan interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4" --intf-type=pseudo-ethernet</command> + </leafNode> + </children> + </tagNode> + <node name="pseudo-ethernet"> + <properties> + <help>Show Pseudo-Ethernet/MACvlan interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=pseudo-ethernet</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed pseudo-ethernet/MACvlan interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=pseudo-ethernet</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-sstpc.xml.in b/op-mode-definitions/show-interfaces-sstpc.xml.in new file mode 100644 index 0000000..307276f --- /dev/null +++ b/op-mode-definitions/show-interfaces-sstpc.xml.in @@ -0,0 +1,51 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="sstpc"> + <properties> + <help>Show specified SSTP client interface information</help> + <completionHelp> + <path>interfaces sstpc</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=sstpc</command> + <children> + <leafNode name="log"> + <properties> + <help>Show specified SSTP client interface log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit "ppp@$4".service</command> + </leafNode> + <leafNode name="statistics"> + <properties> + <help>Show specified SSTP client interface statistics</help> + <completionHelp> + <path>interfaces sstpc</path> + </completionHelp> + </properties> + <command>if [ -d "/sys/class/net/$4" ]; then /usr/sbin/pppstats "$4"; fi</command> + </leafNode> + </children> + </tagNode> + <node name="sstpc"> + <properties> + <help>Show SSTP client interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=sstpc</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed SSTP client interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=sstpc</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-tunnel.xml.in b/op-mode-definitions/show-interfaces-tunnel.xml.in new file mode 100644 index 0000000..b99b0cb --- /dev/null +++ b/op-mode-definitions/show-interfaces-tunnel.xml.in @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="tunnel"> + <properties> + <help>Show specified Tunnel interface information</help> + <completionHelp> + <path>interfaces tunnel</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=tunnel</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of the specified tunnel interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4" --intf-type=tunnel</command> + </leafNode> + </children> + </tagNode> + <node name="tunnel"> + <properties> + <help>Show Tunnel interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=tunnel</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed tunnel interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=tunnel</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-virtual-ethernet.xml.in b/op-mode-definitions/show-interfaces-virtual-ethernet.xml.in new file mode 100644 index 0000000..18ae806 --- /dev/null +++ b/op-mode-definitions/show-interfaces-virtual-ethernet.xml.in @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="virtual-ethernet"> + <properties> + <help>Show specified virtual-ethernet interface information</help> + <completionHelp> + <path>interfaces virtual-ethernet</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=virtual-ethernet</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of the specified virtual-ethernet interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4" --intf-type=virtual-ethernet</command> + </leafNode> + </children> + </tagNode> + <node name="virtual-ethernet"> + <properties> + <help>Show virtual-ethernet interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=virtual-ethernet</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed virtual-ethernet interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=virtual-ethernet</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-vti.xml.in b/op-mode-definitions/show-interfaces-vti.xml.in new file mode 100644 index 0000000..ae5cfeb --- /dev/null +++ b/op-mode-definitions/show-interfaces-vti.xml.in @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="vti"> + <properties> + <help>Show specified VTI interface information</help> + <completionHelp> + <path>interfaces vti</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=vti</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of the specified vti interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4" --intf-type=vti</command> + </leafNode> + </children> + </tagNode> + <node name="vti"> + <properties> + <help>Show VTI interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=vti</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed vti interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=vti</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-vxlan.xml.in b/op-mode-definitions/show-interfaces-vxlan.xml.in new file mode 100644 index 0000000..fd729b9 --- /dev/null +++ b/op-mode-definitions/show-interfaces-vxlan.xml.in @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="vxlan"> + <properties> + <help>Show specified VXLAN interface information</help> + <completionHelp> + <path>interfaces vxlan</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=vxlan</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of the specified VXLAN interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4" --intf-type=vxlan</command> + </leafNode> + </children> + </tagNode> + <node name="vxlan"> + <properties> + <help>Show VXLAN interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=vxlan</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed VXLAN interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=vxlan</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-wireguard.xml.in b/op-mode-definitions/show-interfaces-wireguard.xml.in new file mode 100644 index 0000000..0e61ccd --- /dev/null +++ b/op-mode-definitions/show-interfaces-wireguard.xml.in @@ -0,0 +1,66 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="wireguard"> + <properties> + <help>Show specified WireGuard interface information</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type wireguard</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=wireguard</command> + <children> + <leafNode name="allowed-ips"> + <properties> + <help>Show all IP addresses allowed for the specified interface</help> + </properties> + <command>sudo wg show "$4" allowed-ips</command> + </leafNode> + <leafNode name="endpoints"> + <properties> + <help>Show all endpoints for the specified interface</help> + </properties> + <command>sudo wg show "$4" endpoints</command> + </leafNode> + <leafNode name="peers"> + <properties> + <help>Show all peer IDs for the specified interface</help> + </properties> + <command>sudo wg show "$4" peers</command> + </leafNode> + <leafNode name="public-key"> + <properties> + <help>Show interface public-key</help> + </properties> + <command>sudo wg show "$4" public-key</command> + </leafNode> + <leafNode name="summary"> + <properties> + <help>Shows current configuration and device information</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces_wireguard.py show_summary --intf-name="$4"</command> + </leafNode> + </children> + </tagNode> + <node name="wireguard"> + <properties> + <help>Show WireGuard interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=wireguard</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed Wireguard interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=wireguard</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-wireless.xml.in b/op-mode-definitions/show-interfaces-wireless.xml.in new file mode 100644 index 0000000..09c9a78 --- /dev/null +++ b/op-mode-definitions/show-interfaces-wireless.xml.in @@ -0,0 +1,82 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <node name="wireless"> + <properties> + <help>Show Wireless (WLAN) interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=wireless</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed wireless interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=wireless</command> + </leafNode> + <leafNode name="info"> + <properties> + <help>Show wireless interface configuration</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces_wireless.py show_info</command> + </leafNode> + </children> + </node> + <tagNode name="wireless"> + <properties> + <help>Show specified wireless interface information</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --type wireless</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=wireless</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show brief summary of the specified wireless interface</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4" --intf-type=wireless</command> + </leafNode> + <node name="scan"> + <properties> + <help>Scan for networks via specified wireless interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/interfaces_wireless.py show_scan --intf-name="$4"</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed scan results</help> + </properties> + <command>sudo /sbin/iw dev "$4" scan ap-force</command> + </leafNode> + </children> + </node> + <leafNode name="stations"> + <properties> + <help>Show specified Wireless interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces_wireless.py show_stations --intf-name="$4"</command> + </leafNode> + <tagNode name="vif"> + <properties> + <help>Show specified virtual network interface (vif) information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4.$6" --intf-type=wireless</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of specified virtual network interface (vif) information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4.$6" --intf-type=wireless</command> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-wwan.xml.in b/op-mode-definitions/show-interfaces-wwan.xml.in new file mode 100644 index 0000000..3682282 --- /dev/null +++ b/op-mode-definitions/show-interfaces-wwan.xml.in @@ -0,0 +1,103 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="wwan"> + <properties> + <help>Show specified Wireless Wire Area Network (WWAN) interface information</help> + <completionHelp> + <path>interfaces wwan</path> + <script>cd /sys/class/net; ls -d wwan*</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-name="$4" --intf-type=wirelessmodem</command> + <children> + <leafNode name="capabilities"> + <properties> + <help>Show WWAN module capabilities</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --capabilities</command> + </leafNode> + <leafNode name="firmware"> + <properties> + <help>Show WWAN module firmware</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --firmware</command> + </leafNode> + <leafNode name="imei"> + <properties> + <help>Show WWAN module IMEI/ESN/MEID</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --imei</command> + </leafNode> + <leafNode name="imsi"> + <properties> + <help>Show WWAN module IMSI</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --imsi</command> + </leafNode> + <leafNode name="model"> + <properties> + <help>Show WWAN module manufacturer</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --model</command> + </leafNode> + <leafNode name="msisdn"> + <properties> + <help>Show WWAN module MSISDN</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --msisdn</command> + </leafNode> + <leafNode name="revision"> + <properties> + <help>Show WWAN module revision</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --revision</command> + </leafNode> + <leafNode name="signal"> + <properties> + <help>Show WWAN module RF signal info</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --signal</command> + </leafNode> + <leafNode name="sim"> + <properties> + <help>Show WWAN module connected SIM card information</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --sim</command> + </leafNode> + <leafNode name="detail"> + <properties> + <help>Show WWAN module detailed information summary</help> + </properties> + <command>if cli-shell-api existsActive interfaces wwan $4; then mmcli --modem ${4#wwan}; else echo "Interface \"$4\" unconfigured!"; fi</command> + </leafNode> + <leafNode name="log"> + <properties> + <help>Show interface log for specified interface</help> + </properties> + <command>echo not implemented</command> + </leafNode> + </children> + </tagNode> + <node name="wwan"> + <properties> + <help>Show Wireless Modem (WWAN) interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=wwan</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed Wireless Modem (WWAN( interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=wwan</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces.xml.in b/op-mode-definitions/show-interfaces.xml.in new file mode 100644 index 0000000..0946664 --- /dev/null +++ b/op-mode-definitions/show-interfaces.xml.in @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <properties> + <help>Show network interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary_extended</command> + <children> + <leafNode name="counters"> + <properties> + <help>Show network interface counters</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_counters</command> + </leafNode> + <leafNode name="detail"> + <properties> + <help>Show detailed information of all interfaces</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show</command> + </leafNode> + <leafNode name="summary"> + <properties> + <help>Show summary information of all interfaces</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ip-access-paths-prefix-community-lists.xml.in b/op-mode-definitions/show-ip-access-paths-prefix-community-lists.xml.in new file mode 100644 index 0000000..d722251 --- /dev/null +++ b/op-mode-definitions/show-ip-access-paths-prefix-community-lists.xml.in @@ -0,0 +1,116 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ip"> + <properties> + <help>Show IPv4 routing information</help> + </properties> + <children> + <leafNode name="access-list"> + <properties> + <help>Show all IP access-lists</help> + </properties> + <command>vtysh -c "show ip access-list"</command> + </leafNode> + <tagNode name="access-list"> + <properties> + <help>Show all IP access-lists</help> + <completionHelp> + <path>policy access-list</path> + </completionHelp> + </properties> + <command>vtysh -c "show ip access-list $4"</command> + </tagNode> + <leafNode name="as-path-access-list"> + <properties> + <help>Show all as-path-access-lists</help> + </properties> + <command>vtysh -c "show ip as-path-access-list"</command> + </leafNode> + <tagNode name="as-path-access-list"> + <properties> + <help>Show all as-path-access-lists</help> + <completionHelp> + <path>policy as-path-list</path> + </completionHelp> + </properties> + <command>vtysh -c "show ip as-path-access-list $4"</command> + </tagNode> + <leafNode name="community-list"> + <properties> + <help>Show IP community-lists</help> + </properties> + <command>vtysh -c "show bgp community-list"</command> + </leafNode> + <tagNode name="community-list"> + <properties> + <help>Show IP community-lists</help> + <completionHelp> + <path>policy community-list</path> + </completionHelp> + </properties> + <command>vtysh -c "show bgp community-list $4 detail"</command> + </tagNode> + <leafNode name="extcommunity-list"> + <properties> + <help>Show extended IP community-lists</help> + </properties> + <command>vtysh -c "show bgp extcommunity-list"</command> + </leafNode> + <tagNode name="extcommunity-list"> + <properties> + <help>Show extended IP community-lists</help> + <completionHelp> + <path>policy extcommunity-list</path> + </completionHelp> + </properties> + <command>vtysh -c "show bgp extcommunity-list $4 detail"</command> + </tagNode> + <leafNode name="forwarding"> + <properties> + <help>Show IP forwarding status</help> + </properties> + <command>vtysh -c "show ip forwarding"</command> + </leafNode> + <leafNode name="large-community-list"> + <properties> + <help>Show IP large-community-lists</help> + </properties> + <command>vtysh -c "show bgp large-community-list"</command> + </leafNode> + <tagNode name="large-community-list"> + <properties> + <help>Show IP large-community-lists</help> + <completionHelp> + <path>policy large-community-list</path> + </completionHelp> + </properties> + <command>vtysh -c "show bgp large-community-list $4 detail"</command> + </tagNode> + <leafNode name="prefix-list"> + <properties> + <help>Show all IP prefix-lists</help> + </properties> + <command>vtysh -c "show ip prefix-list"</command> + </leafNode> + <tagNode name="prefix-list"> + <properties> + <help>Show all IP prefix-lists</help> + <completionHelp> + <path>policy prefix-list</path> + </completionHelp> + </properties> + <command>vtysh -c "show ip prefix-list $4"</command> + </tagNode> + <leafNode name="protocol"> + <properties> + <help>Show IP route-maps per protocol</help> + </properties> + <command>vtysh -c "show ip protocol"</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ip-bgp.xml.in b/op-mode-definitions/show-ip-bgp.xml.in new file mode 100644 index 0000000..ecef320 --- /dev/null +++ b/op-mode-definitions/show-ip-bgp.xml.in @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ip"> + <children> + <node name="bgp"> + <properties> + <help>Show Border Gateway Protocol (BGP) information</help> + </properties> + <command>vtysh -c "show ip bgp"</command> + <children> + #include <include/bgp/show-ip-bgp-common.xml.i> + <leafNode name="vrf"> + <properties> + <help>Show BGP VRF information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <tagNode name="vrf"> + <properties> + <help>Show BGP VRF related information</help> + <completionHelp> + <path>vrf name</path> + <list>all</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/show-ip-bgp-common.xml.i> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ip-igmp.xml.in b/op-mode-definitions/show-ip-igmp.xml.in new file mode 100644 index 0000000..1fd86ba --- /dev/null +++ b/op-mode-definitions/show-ip-igmp.xml.in @@ -0,0 +1,48 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ip"> + <children> + <node name="igmp"> + <properties> + <help>Show IGMP (Internet Group Management Protocol) information</help> + </properties> + <children> + <leafNode name="groups"> + <properties> + <help>IGMP groups information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="interface"> + <properties> + <help>IGMP interfaces information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="join"> + <properties> + <help>IGMP static join information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="sources"> + <properties> + <help>IGMP sources information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="statistics"> + <properties> + <help>IGMP statistics</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ip-multicast.xml.in b/op-mode-definitions/show-ip-multicast.xml.in new file mode 100644 index 0000000..00a4704 --- /dev/null +++ b/op-mode-definitions/show-ip-multicast.xml.in @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ip"> + <children> + <node name="multicast"> + <children> + <leafNode name="interface"> + <properties> + <help>Show multicast interfaces</help> + </properties> + <command>${vyos_op_scripts_dir}/igmp-proxy.py show_interface</command> + </leafNode> + <leafNode name="summary"> + <properties> + <help>IP multicast information</help> + </properties> + <command>vtysh -c "show ip multicast"</command> + </leafNode> + <leafNode name="route"> + <properties> + <help>IP multicast routing table</help> + </properties> + <command>vtysh -c "show ip mroute"</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ip-ospf.xml.in b/op-mode-definitions/show-ip-ospf.xml.in new file mode 100644 index 0000000..f3b9da9 --- /dev/null +++ b/op-mode-definitions/show-ip-ospf.xml.in @@ -0,0 +1,36 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ip"> + <properties> + <help>Show IPv4 routing information</help> + </properties> + <children> + <node name="ospf"> + <properties> + <help>Show IPv4 Open Shortest Path First (OSPF) routing information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/ospf/common.xml.i> + <tagNode name="vrf"> + <properties> + <help>Show OSPF routing protocol for given VRF</help> + <completionHelp> + <path>vrf name</path> + <list>all</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/ospf/common.xml.i> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ip-pim.xml.in b/op-mode-definitions/show-ip-pim.xml.in new file mode 100644 index 0000000..9deba1f --- /dev/null +++ b/op-mode-definitions/show-ip-pim.xml.in @@ -0,0 +1,156 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ip"> + <children> + <node name="pim"> + <properties> + <help>Show PIM (Protocol Independent Multicast) information</help> + </properties> + <children> + <leafNode name="assert"> + <properties> + <help>PIM interfaces assert</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="assert-internal"> + <properties> + <help>PIM interface internal assert state</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="assert-metric"> + <properties> + <help>PIM interface assert metric</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="assert-winner-metric"> + <properties> + <help>PIM interface assert winner metric</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="bsm-database"> + <properties> + <help>PIM cached bsm packets information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="bsr"> + <properties> + <help>PIM boot-strap router information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="bsrp-info"> + <properties> + <help>PIM cached group-rp mappings information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="channel"> + <properties> + <help>PIM downstream channel info</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="group-type"> + <properties> + <help>PIM multicast group type</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="interface"> + <properties> + <help>PIM interfaces information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="join"> + <properties> + <help>PIM join information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="local-membership"> + <properties> + <help>PIM interface local-membership</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="neighbor"> + <properties> + <help>PIM neighbor information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="nexthop"> + <properties> + <help>PIM cached nexthop rpf information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="rp-info"> + <properties> + <help>PIM rendezvous point information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="rpf"> + <properties> + <help>PIM reverse path forwarding information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="secondary"> + <properties> + <help>PIM neighbor addresses</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="state"> + <properties> + <help>PIM state information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="statistics"> + <properties> + <help>PIM statistics</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="upstream"> + <properties> + <help>PIM upstream information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="upstream-join-desired"> + <properties> + <help>PIM upstream join-desired</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="upstream-rpf"> + <properties> + <help>PIM upstream source reverse path forwarding</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="vxlan-groups"> + <properties> + <help>VXLAN BUM groups</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ip-ports.xml.in b/op-mode-definitions/show-ip-ports.xml.in new file mode 100644 index 0000000..a74b68f --- /dev/null +++ b/op-mode-definitions/show-ip-ports.xml.in @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ip"> + <children> + <leafNode name="ports"> + <properties> + <help>Show IP ports in use by various system services</help> + </properties> + <command>sudo /usr/bin/netstat -tulnp</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ip-rip.xml.in b/op-mode-definitions/show-ip-rip.xml.in new file mode 100644 index 0000000..768a86c --- /dev/null +++ b/op-mode-definitions/show-ip-rip.xml.in @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ip"> + <properties> + <help>Show IPv4 routing information</help> + </properties> + <children> + <node name="rip"> + <properties> + <help>Show Routing Information Protocol (RIP) information</help> + </properties> + <command>vtysh -c "show ip rip"</command> + <children> + <leafNode name="status"> + <properties> + <help>Show RIP protocol status</help> + </properties> + <command>vtysh -c "show ip rip status"</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ip-route.xml.in b/op-mode-definitions/show-ip-route.xml.in new file mode 100644 index 0000000..37279d3 --- /dev/null +++ b/op-mode-definitions/show-ip-route.xml.in @@ -0,0 +1,138 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ip"> + <properties> + <help>Show IPv4 routing information</help> + </properties> + <children> + <node name="route"> + <properties> + <help>Show IP routes</help> + </properties> + <command>vtysh -c "show ip route"</command> + <children> + #include <include/show-route-bgp.xml.i> + <node name="cache"> + <properties> + <help>Show kernel route cache</help> + </properties> + <command>ip -s route list cache</command> + </node> + <tagNode name="cache"> + <properties> + <help>Show kernel route cache for a given route</help> + <completionHelp> + <list><x.x.x.x> <x.x.x.x/x></list> + </completionHelp> + </properties> + <command>ip -s route list cache $5</command> + </tagNode> + #include <include/show-route-connected.xml.i> + <node name="forward"> + <properties> + <help>Show kernel route table</help> + </properties> + <command>ip route list</command> + </node> + <tagNode name="forward"> + <properties> + <help>Show kernel route table for a given route</help> + <completionHelp> + <list><x.x.x.x> <x.x.x.x/x></list> + </completionHelp> + </properties> + <command>ip -s route list $5</command> + </tagNode> + #include <include/show-route-isis.xml.i> + #include <include/show-route-openfabric.xml.i> + #include <include/show-route-kernel.xml.i> + #include <include/show-route-ospf.xml.i> + #include <include/show-route-rip.xml.i> + #include <include/show-route-static.xml.i> + #include <include/show-route-supernets-only.xml.i> + #include <include/show-route-table.xml.i> + #include <include/show-route-tag.xml.i> + <node name="summary"> + <properties> + <help>Summary of all routes</help> + </properties> + <command>${vyos_op_scripts_dir}/route.py show_summary --family inet</command> + <children> + <tagNode name="table"> + <properties> + <help>Summary of routes in a particular table</help> + </properties> + <command>${vyos_op_scripts_dir}/route.py show_summary --family inet --table $6</command> + </tagNode> + </children> + </node> + <tagNode name="vrf"> + <properties> + <help>Show IP routes in VRF</help> + <completionHelp> + <list>all</list> + <path>vrf name</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="summary"> + <properties> + <help>Summary of all routes in the VRF</help> + </properties> + <command>${vyos_op_scripts_dir}/route.py show_summary --family inet --vrf $5</command> + </node> + #include <include/show-route-bgp.xml.i> + #include <include/show-route-connected.xml.i> + #include <include/show-route-isis.xml.i> + #include <include/show-route-kernel.xml.i> + #include <include/show-route-ospf.xml.i> + #include <include/show-route-rip.xml.i> + #include <include/show-route-static.xml.i> + #include <include/show-route-supernets-only.xml.i> + #include <include/show-route-tag.xml.i> + <node name="node.tag"> + <properties> + <help>Show IP routes of specified IP address or prefix</help> + <completionHelp> + <list><x.x.x.x> <x.x.x.x/x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <leafNode name="longer-prefixes"> + <properties> + <help>Show longer prefixes of routes for specified prefix</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> + </node> + <tagNode name="route"> + <properties> + <help>Show IP routes of specified IP address or prefix</help> + <completionHelp> + <list><x.x.x.x> <x.x.x.x/x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <leafNode name="longer-prefixes"> + <properties> + <help>Show longer prefixes of routes for specified prefix</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ip.xml.in b/op-mode-definitions/show-ip.xml.in new file mode 100644 index 0000000..bac4df0 --- /dev/null +++ b/op-mode-definitions/show-ip.xml.in @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ip"> + <properties> + <help>Show IPv4 networking information</help> + </properties> + <children> + <node name="neighbors"> + <properties> + <help>Show IPv4 neighbor (ARP) table</help> + </properties> + <command>${vyos_op_scripts_dir}/neighbor.py show --family inet</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show IPv4 neighbor table for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --broadcast</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/neighbor.py show --family inet --interface "$5"</command> + </tagNode> + <tagNode name="state"> + <properties> + <help>Show IPv4 neighbors with specified state</help> + <completionHelp> + <list>reachable stale failed permanent</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/neighbor.py show --family inet --state "$5"</command> + </tagNode> + </children> + </node> + #include <include/show-nht.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ipv6-mld.xml.in b/op-mode-definitions/show-ipv6-mld.xml.in new file mode 100644 index 0000000..5c719f7 --- /dev/null +++ b/op-mode-definitions/show-ipv6-mld.xml.in @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ipv6"> + <children> + <node name="mld"> + <properties> + <help>Show MLD (Multicast Listener Discovery) information</help> + </properties> + <children> + <leafNode name="groups"> + <properties> + <help>MLD group information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="interface"> + <properties> + <help>MLD interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="joins"> + <properties> + <help>MLD joined groups and sources</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="statistics"> + <properties> + <help>MLD statistics</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ipv6-ospfv3.xml.in b/op-mode-definitions/show-ipv6-ospfv3.xml.in new file mode 100644 index 0000000..e1fcf47 --- /dev/null +++ b/op-mode-definitions/show-ipv6-ospfv3.xml.in @@ -0,0 +1,118 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ipv6"> + <properties> + <help>Show IPv6 routing information</help> + </properties> + <children> + <node name="ospfv3"> + <properties> + <help>Show IPv6 Open Shortest Path First (OSPF)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="area"> + <properties> + <help>Show Shortest Path First tree information</help> + </properties> + <command>vtysh -c "show ipv6 ospf6 spf tree"</command> + </node> + <tagNode name="area"> + <properties> + <help>Area ID (as an IPv4 notation)</help> + <completionHelp> + <path>protocols ospfv3 area</path> + </completionHelp> + </properties> + <command>vtysh -c "show ipv6 ospf6 area $5 spf tree"</command> + <children> + <tagNode name="router"> + <properties> + <help> Simulate view point (Router ID)</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>vtysh -c "show ipv6 ospf6 simulate spf-tree $7 $4 $5"</command> + </tagNode> + </children> + </tagNode> + #include <include/ospfv3/border-routers.xml.i> + #include <include/ospfv3/database.xml.i> + #include <include/ospf/graceful-restart.xml.i> + #include <include/ospfv3/interface.xml.i> + #include <include/ospfv3/linkstate.xml.i> + #include <include/ospfv3/neighbor.xml.i> + #include <include/ospfv3/redistribute.xml.i> + #include <include/ospfv3/route.xml.i> + <node name="vrf"> + <properties> + <help>Specify the VRF</help> + <completionHelp> + <list>all</list> + <path>vrf name</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + <tagNode name="vrf"> + <properties> + <help>VRF name</help> + <completionHelp> + <list>all</list> + <path>vrf name</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="area"> + <properties> + <help>Show Shortest Path First tree information</help> + </properties> + <command>vtysh -c "show ipv6 ospf6 vrf $5 spf tree"</command> + </node> + <tagNode name="area"> + <properties> + <help>Area ID (as an IPv4 notation)</help> + <completionHelp> + <path>protocols ospfv3 area</path> + </completionHelp> + </properties> + <command>vtysh -c "show ipv6 ospf6 vrf $5 area $7 spf tree"</command> + <children> + <tagNode name="router"> + <properties> + <help> Simulate view point (Router ID)</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>vtysh -c "show ipv6 ospf6 vrf $5 simulate spf-tree $9 $6 $7"</command> + </tagNode> + </children> + </tagNode> + #include <include/ospfv3/border-routers.xml.i> + #include <include/ospfv3/database.xml.i> + #include <include/ospf/graceful-restart.xml.i> + #include <include/ospfv3/interface.xml.i> + #include <include/ospfv3/linkstate.xml.i> + #include <include/ospfv3/neighbor.xml.i> + #include <include/ospfv3/redistribute.xml.i> + #include <include/ospfv3/route.xml.i> + </children> + </tagNode> + <leafNode name="vrfs"> + <properties> + <help>Show OSPFv3 VRFs</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ipv6-pim.xml.in b/op-mode-definitions/show-ipv6-pim.xml.in new file mode 100644 index 0000000..7cc3ce7 --- /dev/null +++ b/op-mode-definitions/show-ipv6-pim.xml.in @@ -0,0 +1,120 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ipv6"> + <children> + <node name="pim"> + <properties> + <help>Show PIM (Protocol Independent Multicast) information</help> + </properties> + <children> + <leafNode name="bsm-database"> + <properties> + <help>PIM cached bsm packets information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="bsr"> + <properties> + <help>PIM boot-strap router information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="bsrp-info"> + <properties> + <help>PIM cached group-rp mappings information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="channel"> + <properties> + <help>PIM downstream channel info</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="interface"> + <properties> + <help>PIM interfaces information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="join"> + <properties> + <help>PIM join information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="local-membership"> + <properties> + <help>PIM interface local-membership</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="neighbor"> + <properties> + <help>PIM neighbor information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="nexthop"> + <properties> + <help>PIM cached nexthop rpf information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="rp-info"> + <properties> + <help>PIM rendezvous point information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="rpf"> + <properties> + <help>PIM reverse path forwarding information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="secondary"> + <properties> + <help>PIM neighbor addresses</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="state"> + <properties> + <help>PIM state information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="statistics"> + <properties> + <help>PIM statistics</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="upstream"> + <properties> + <help>PIM upstream information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="upstream-join-desired"> + <properties> + <help>PIM upstream join-desired</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + <leafNode name="upstream-rpf"> + <properties> + <help>PIM upstream source reverse path forwarding</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ipv6-prefix-list.xml.in b/op-mode-definitions/show-ipv6-prefix-list.xml.in new file mode 100644 index 0000000..e003ad1 --- /dev/null +++ b/op-mode-definitions/show-ipv6-prefix-list.xml.in @@ -0,0 +1,92 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ipv6"> + <properties> + <help>Show IPv6 routing information</help> + </properties> + <children> + <node name="prefix-list"> + <properties> + <help>Show IPv6 prefix-lists</help> + </properties> + <command>vtysh -c "show ipv6 prefix-list"</command> + <children> + <node name="detail"> + <properties> + <help>Show detail of IPv6 prefix-lists</help> + </properties> + <command>vtysh -c "show ipv6 prefix-list detail"</command> + </node> + <tagNode name="detail"> + <properties> + <help>Show detail of specified IPv6 prefix-list</help> + </properties> + <command>vtysh -c "show ipv6 prefix-list detail $5"</command> + </tagNode> + <node name="summary"> + <properties> + <help>Show summary of IPv6 prefix-lists</help> + </properties> + <command>vtysh -c "show ipv6 prefix-list summary"</command> + </node> + <tagNode name="summary"> + <properties> + <help>Show summary of specified IPv6 prefix-list</help> + </properties> + <command>vtysh -c "show ipv6 prefix-list summary $5"</command> + </tagNode> + </children> + </node> + <tagNode name="prefix-list"> + <properties> + <help>Show specified IPv6 prefix-list</help> + <completionHelp> + <list>WORD</list> + </completionHelp> + </properties> + <command>vtysh -c "show ipv6 prefix-list $4"</command> + <children> + <node name="node.tag"> + <properties> + <help>Show select prefix of specified IPv6 prefix-list</help> + <completionHelp> + <list><h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <command>vtysh -c "show ipv6 prefix-list $4 $5"</command> + <children> + <node name="first-match"> + <properties> + <help>Show first-match from select prefix of named IPv6 prefix-list</help> + </properties> + <command>vtysh -c "show ipv6 prefix-list $4 $5 first-match"</command> + </node> + <node name="longer"> + <properties> + <help>Show longer match of select prefix from named IPv6 prefix-list</help> + </properties> + <command>vtysh -c "show ipv6 prefix-list $4 $5 longer"</command> + </node> + </children> + </node> + <node name="seq"> + <properties> + <help>Show specified sequence from specified IPv6 prefix-list</help> + </properties> + <command>vtysh -c "show ipv6 prefix-list $4 seq"</command> + </node> + <tagNode name="seq"> + <properties> + <help>Show specified sequence from specified IPv6 prefix-list</help> + </properties> + <command>vtysh -c "show ipv6 prefix-list $4 seq $6"</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ipv6-route.xml.in b/op-mode-definitions/show-ipv6-route.xml.in new file mode 100644 index 0000000..f68a949 --- /dev/null +++ b/op-mode-definitions/show-ipv6-route.xml.in @@ -0,0 +1,138 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ipv6"> + <properties> + <help>Show IPv6 routing information</help> + </properties> + <children> + <node name="route"> + <properties> + <help>Show IPv6 routes</help> + </properties> + <command>vtysh -c "show ipv6 route"</command> + <children> + #include <include/show-route-bgp.xml.i> + <node name="cache"> + <properties> + <help>Show kernel IPv6 route cache</help> + </properties> + <command>ip -s -f inet6 route list cache</command> + </node> + <tagNode name="cache"> + <properties> + <help>Show kernel IPv6 route cache for a given route</help> + <completionHelp> + <list><h:h:h:h:h:h:h:h> <h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <command>ip -s -f inet6 route list cache $5</command> + </tagNode> + #include <include/show-route-connected.xml.i> + <node name="forward"> + <properties> + <help>Show kernel IPv6 route table</help> + </properties> + <command>ip -f inet6 route list</command> + </node> + <tagNode name="forward"> + <properties> + <help>Show kernel IPv6 route table for a given route</help> + <completionHelp> + <list><h:h:h:h:h:h:h:h> <h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <command>ip -s -f inet6 route list $5</command> + </tagNode> + #include <include/show-route-isis.xml.i> + #include <include/show-route-openfabric.xml.i> + #include <include/show-route-kernel.xml.i> + #include <include/show-route-ospfv3.xml.i> + #include <include/show-route-ripng.xml.i> + #include <include/show-route-static.xml.i> + #include <include/show-route-table.xml.i> + #include <include/show-route-tag.xml.i> + <node name="summary"> + <properties> + <help>Summary of all routes</help> + </properties> + <command>${vyos_op_scripts_dir}/route.py show_summary --family inet6</command> + <children> + <tagNode name="table"> + <properties> + <help>Summary of routes in a particular table</help> + </properties> + <command>${vyos_op_scripts_dir}/route.py show_summary --family inet6 --table $6</command> + </tagNode> + </children> + </node> + <tagNode name="vrf"> + <properties> + <help>Show IPv6 routes in VRF</help> + <completionHelp> + <list>all</list> + <path>vrf name</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="summary"> + <properties> + <help>Summary of all routes in the VRF</help> + </properties> + <command>${vyos_op_scripts_dir}/route.py show_summary --family inet6 --vrf $5</command> + </node> + <node name="node.tag"> + <properties> + <help>Show IPv6 routes of given address or prefix</help> + <completionHelp> + <list><h:h:h:h:h:h:h:h> <h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="longer-prefixes"> + <properties> + <help>Show longer prefixes of routes for given prefix</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + </children> + </node> + #include <include/show-route-bgp.xml.i> + #include <include/show-route-connected.xml.i> + #include <include/show-route-isis.xml.i> + #include <include/show-route-kernel.xml.i> + #include <include/show-route-ospfv3.xml.i> + #include <include/show-route-ripng.xml.i> + #include <include/show-route-static.xml.i> + #include <include/show-route-supernets-only.xml.i> + #include <include/show-route-table.xml.i> + #include <include/show-route-tag.xml.i> + </children> + </tagNode> + </children> + </node> + <tagNode name="route"> + <properties> + <help>Show IPv6 routes of given address or prefix</help> + <completionHelp> + <list><h:h:h:h:h:h:h:h> <h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="longer-prefixes"> + <properties> + <help>Show longer prefixes of routes for given prefix</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ipv6.xml.in b/op-mode-definitions/show-ipv6.xml.in new file mode 100644 index 0000000..e10379a --- /dev/null +++ b/op-mode-definitions/show-ipv6.xml.in @@ -0,0 +1,50 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ipv6"> + <properties> + <help>Show IPv6 networking information</help> + </properties> + <children> + <node name="access-list"> + <properties> + <help>Show all IPv6 access-lists</help> + </properties> + <command>vtysh -c "show ipv6 access-list"</command> + </node> + <tagNode name="access-list"> + <properties> + <help>Show specified IPv6 access-list</help> + <completionHelp> + <list>WORD</list> + </completionHelp> + </properties> + <command>vtysh -c "show ipv6 access-list $4"</command> + </tagNode> + <node name="forwarding"> + <properties> + <help>Show IPv6 forwarding status</help> + </properties> + <command>vtysh -c "show ipv6 forwarding"</command> + </node> + #include <include/show-nht.xml.i> + <node name="ripng"> + <properties> + <help>Show RIPNG protocol information</help> + </properties> + <command>vtysh -c "show ipv6 ripng"</command> + <children> + <node name="status"> + <properties> + <help>Show RIPNG protocol status</help> + </properties> + <command>vtysh -c "show ipv6 ripng status"</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-isis.xml.in b/op-mode-definitions/show-isis.xml.in new file mode 100644 index 0000000..202e321 --- /dev/null +++ b/op-mode-definitions/show-isis.xml.in @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="isis"> + <properties> + <help>Show IS-IS routing protocol</help> + </properties> + <children> + #include <include/isis-common.xml.i> + <tagNode name="vrf"> + <properties> + <help>Show IS-IS routing protocol for given VRF</help> + <completionHelp> + <path>vrf name</path> + <list>all</list> + </completionHelp> + </properties> + <children> + #include <include/isis-common.xml.i> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-kernel-modules.xml.in b/op-mode-definitions/show-kernel-modules.xml.in new file mode 100644 index 0000000..28eb282 --- /dev/null +++ b/op-mode-definitions/show-kernel-modules.xml.in @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="kernel"> + <properties> + <help>Show kernel information</help> + </properties> + <children> + <node name="modules"> + <properties> + <help>Show kernel modules</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/kernel_modules.py show</command> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-license.xml.in b/op-mode-definitions/show-license.xml.in new file mode 100644 index 0000000..2ce1156 --- /dev/null +++ b/op-mode-definitions/show-license.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <leafNode name="license"> + <properties> + <help>Show VyOS license information</help> + </properties> + <command>less $_vyatta_less_options --prompt=".license, page %dt of %D" -- ${vyatta_sysconfdir}/LICENSE</command> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in new file mode 100644 index 0000000..c250468 --- /dev/null +++ b/op-mode-definitions/show-log.xml.in @@ -0,0 +1,947 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <properties> + <help>Show system information</help> + </properties> + <children> + <tagNode name="log"> + <properties> + <help>Show last number of messages in master logging buffer</help> + <completionHelp> + <list><1-9999></list> + </completionHelp> + </properties> + <command>if ${vyos_validators_dir}/numeric --range 1-9999 "$3"; then journalctl --no-hostname --boot --lines "$3"; fi</command> + </tagNode> + <node name="log"> + <properties> + <help>Show contents of current master logging buffer</help> + </properties> + <command>journalctl --no-hostname --boot</command> + <children> + <leafNode name="audit"> + <properties> + <help>Show audit logs</help> + </properties> + <command>cat /var/log/audit/audit.log</command> + </leafNode> + <leafNode name="all"> + <properties> + <help>Show contents of all master log files</help> + </properties> + <command>sudo bash -c 'eval $(lesspipe); less $_vyatta_less_options --prompt=".logm, file %i of %m., page %dt of %D" -- `printf "%s\n" /var/log/messages* | sort -nr`'</command> + </leafNode> + <leafNode name="authorization"> + <properties> + <help>Show listing of authorization attempts</help> + </properties> + <command>journalctl --no-hostname --boot --quiet SYSLOG_FACILITY=10 SYSLOG_FACILITY=4</command> + </leafNode> + <leafNode name="certbot"> + <properties> + <help>Show log for certbot</help> + </properties> + <command>if sudo test -f /var/log/letsencrypt/letsencrypt.log; then sudo cat /var/log/letsencrypt/letsencrypt.log; else echo "Cerbot log does not exist"; fi</command> + </leafNode> + <leafNode name="cluster"> + <properties> + <help>Show log for Cluster</help> + </properties> + <command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e heartbeat -e cl_status -e mach_down -e ha_log</command> + </leafNode> + <leafNode name="conntrack-sync"> + <properties> + <help>Show log for Conntrack-sync</help> + </properties> + <command>journalctl --no-hostname --boot --unit conntrackd.service</command> + </leafNode> + <leafNode name="console-server"> + <properties> + <help>Show log for console server</help> + </properties> + <command>journalctl --no-hostname --boot --unit conserver-server.service</command> + </leafNode> + <node name="ids"> + <properties> + <help>Show log for for Intrusion Detection System</help> + </properties> + <children> + <leafNode name="ddos-protection"> + <properties> + <help>Show log for DDOS protection</help> + </properties> + <command>journalctl --no-hostname --boot --unit fastnetmon.service</command> + </leafNode> + </children> + </node> + <node name="dhcp"> + <properties> + <help>Show log for Dynamic Host Control Protocol (DHCP)</help> + </properties> + <children> + <node name="server"> + <properties> + <help>Show log for DHCP server</help> + </properties> + <command>journalctl --no-hostname --boot --unit kea-dhcp4-server.service</command> + </node> + <node name="client"> + <properties> + <help>Show DHCP client logs</help> + </properties> + <command>journalctl --no-hostname --boot --unit "dhclient@*.service"</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show DHCP client log on specific interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --broadcast</script> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot --unit "dhclient@$6.service"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="dhcpv6"> + <properties> + <help>Show log for Dynamic Host Control Protocol IPv6 (DHCPv6)</help> + </properties> + <children> + <node name="server"> + <properties> + <help>Show log for DHCPv6 server</help> + </properties> + <command>journalctl --no-hostname --boot --unit kea-dhcp6-server.service</command> + </node> + <node name="client"> + <properties> + <help>Show DHCPv6 client logs</help> + </properties> + <command>journalctl --no-hostname --boot --unit "dhcp6c@*.service"</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show DHCPv6 client log on specific interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot --unit "dhcp6c@$6.service"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="firewall"> + <properties> + <help>Show log for Firewall</help> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "(ipv[46]|bri)-(FWD|INP|OUT|NAM)"</command> + <children> + <node name="bridge"> + <properties> + <help>Show firewall bridge log</help> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "bri-(FWD|INP|OUT|NAM)"</command> + <children> + <node name="forward"> + <properties> + <help>Show Bridge forward firewall log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep bri-FWD</command> + <children> + <node name="filter"> + <properties> + <help>Show Bridge firewall forward filter</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep bri-FWD-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall bridge forward filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[bri-FWD-filter-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="input"> + <properties> + <help>Show Bridge input firewall log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep bri-INP</command> + <children> + <node name="filter"> + <properties> + <help>Show Bridge firewall input filter</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep bri-INP-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall bridge input filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[bri-INP-filter-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="output"> + <properties> + <help>Show Bridge output firewall log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep bri-OUT</command> + <children> + <node name="filter"> + <properties> + <help>Show Bridge firewall output filter</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep bri-OUT-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall bridge output filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[bri-OUT-filter-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="prerouting"> + <properties> + <help>Show Bridge prerouting firewall log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep bri-PRE</command> + <children> + <node name="filter"> + <properties> + <help>Show Bridge firewall prerouting filter</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep bri-PRE-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall bridge prerouting filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[bri-PRE-filter-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + <tagNode name="name"> + <properties> + <help>Show custom Bridge firewall log</help> + <completionHelp> + <path>firewall bridge name</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | grep bri-NAM-$6</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall bridge name ${COMP_WORDS[5]} rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[bri-NAM-$6-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + <node name="ipv4"> + <properties> + <help>Show firewall IPv4 log</help> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "ipv4-(FWD|INP|OUT|NAM)"</command> + <children> + <node name="forward"> + <properties> + <help>Show firewall IPv4 forward log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv4-FWD</command> + <children> + <node name="filter"> + <properties> + <help>Show firewall IPv4 forward filter log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv4-FWD-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv4 forward filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv4-FWD-filter-$8-[ADRJCO]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="input"> + <properties> + <help>Show firewall IPv4 input log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv4-INP</command> + <children> + <node name="filter"> + <properties> + <help>Show firewall IPv4 input filter log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv4-INP-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv4 input filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv4-INP-filter-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + <tagNode name="name"> + <properties> + <help>Show custom IPv4 firewall log</help> + <completionHelp> + <path>firewall ipv4 name</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv4-NAM-$6</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv4 name ${COMP_WORDS[5]} rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv4-NAM-$6-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </tagNode> + <node name="output"> + <properties> + <help>Show firewall IPv4 output log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv4-OUT</command> + <children> + <node name="filter"> + <properties> + <help>Show firewall IPv4 output filter log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv4-OUT-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv4 output filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv4-OUT-filter-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="prerouting"> + <properties> + <help>Show firewall IPv4 prerouting log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv4-PRE</command> + <children> + <node name="raw"> + <properties> + <help>Show firewall IPv4 prerouting raw log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv4-PRE-raw</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv4 prerouting raw rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv4-PRE-raw-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="ipv6"> + <properties> + <help>Show firewall IPv6 log</help> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "ipv6-(FWD|INP|OUT|NAM)"</command> + <children> + <node name="forward"> + <properties> + <help>Show firewall IPv6 forward log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv6-FWD</command> + <children> + <node name="filter"> + <properties> + <help>Show firewall IPv6 forward filter log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv6-FWD-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv6 forward filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv6-FWD-filter-$8-[ADRJCO]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="input"> + <properties> + <help>Show firewall IPv6 input log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv6-INP</command> + <children> + <node name="filter"> + <properties> + <help>Show firewall IPv6 input filter log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv6-INP-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv6 input filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv6-INP-filter-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + <tagNode name="name"> + <properties> + <help>Show custom IPv6 firewall log</help> + <completionHelp> + <path>firewall ipv6 name</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv6-NAM-$6</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv6 name ${COMP_WORDS[5]} rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv6-NAM-$6-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </tagNode> + <node name="output"> + <properties> + <help>Show firewall IPv6 output log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv6-OUT</command> + <children> + <node name="filter"> + <properties> + <help>Show firewall IPv6 output filter log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv6-OUT-filter</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv6 output filter rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv6-OUT-filter-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="prerouting"> + <properties> + <help>Show firewall IPv6 prerouting log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv6-PRE</command> + <children> + <node name="raw"> + <properties> + <help>Show firewall IPv6 prerouting raw log</help> + </properties> + <command>journalctl --no-hostname --boot -k | grep ipv6-PRE-raw</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show log for a rule in the specified firewall</help> + <completionHelp> + <path>firewall ipv6 prerouting raw rule</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[ipv6-PRE-raw-$8-[ADRJC]\]"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + <leafNode name="flow-accounting"> + <properties> + <help>Show log for flow-accounting</help> + </properties> + <command>journalctl --no-hostname --boot --unit uacctd.service</command> + </leafNode> + <leafNode name="https"> + <properties> + <help>Show log for HTTPs</help> + </properties> + <command>journalctl --no-hostname --boot --unit nginx.service</command> + </leafNode> + <tagNode name="image"> + <properties> + <help>Show contents of master log file for image</help> + <completionHelp> + <script>compgen -f /lib/live/mount/persistence/boot/ | grep -v grub | sed -e s@/lib/live/mount/persistence/boot/@@</script> + </completionHelp> + </properties> + <command>less $_vyatta_less_options --prompt=".log, page %dt of %D" -- /lib/live/mount/persistence/boot/$4/rw/var/log/messages</command> + <children> + <leafNode name="all"> + <properties> + <help>Show contents of all master log files for image</help> + </properties> + <command>eval $(lesspipe); less $_vyatta_less_options --prompt=".log?m, file %i of %m., page %dt of %D" -- `printf "%s\n" /lib/live/mount/persistence/boot/$4/rw/var/log/messages* | sort -nr`</command> + </leafNode> + <leafNode name="authorization"> + <properties> + <help>Show listing of authorization attempts for image</help> + </properties> + <command>less $_vyatta_less_options --prompt=".log, page %dt of %D" -- /lib/live/mount/persistence/boot/$4/rw/var/log/auth.log</command> + </leafNode> + <tagNode name="tail"> + <properties> + <help>Show last changes to messages</help> + <completionHelp> + <list><NUMBER></list> + </completionHelp> + </properties> + <command>tail -n "$6" /lib/live/mount/persistence/boot/$4/rw/var/log/messages | ${VYATTA_PAGER:-cat}</command> + </tagNode> + </children> + </tagNode> + <leafNode name="ipoe-server"> + <properties> + <help>Show log for IPoE server</help> + </properties> + <command>journalctl --no-hostname --boot --unit accel-ppp@ipoe.service</command> + </leafNode> + <leafNode name="kernel"> + <properties> + <help>Show log for Linux Kernel</help> + </properties> + <command>journalctl --no-hostname --boot --dmesg</command> + </leafNode> + <leafNode name="lldp"> + <properties> + <help>Show log for Link Layer Discovery Protocol (LLDP)</help> + </properties> + <command>journalctl --no-hostname --boot --unit lldpd.service</command> + </leafNode> + <node name="nat"> + <properties> + <help>Show log for Network Address Translation (NAT)</help> + </properties> + <children> + <node name="destination"> + <properties> + <help>Show NAT destination log</help> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[DST-NAT-[0-9]+\]"</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show NAT destination log for specified rule</help> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[DST-NAT-$6\]"</command> + </tagNode> + </children> + </node> + <node name="source"> + <properties> + <help>Show NAT source log</help> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[SRC-NAT-[0-9]+(-MASQ)?\]"""</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show NAT source log for specified rule</help> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[SRC-NAT-$6(-MASQ)?\]"</command> + </tagNode> + </children> + </node> + <node name="static"> + <properties> + <help>Show NAT static log</help> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[STATIC-(SRC|DST)-NAT-[0-9]+\]"</command> + <children> + <tagNode name="rule"> + <properties> + <help>Show NAT static log for specified rule</help> + </properties> + <command>journalctl --no-hostname --boot -k | egrep "\[STATIC-(SRC|DST)-NAT-$6\]"</command> + </tagNode> + </children> + </node> + </children> + <command>journalctl --no-hostname --boot -k | egrep "\[(STATIC-)?(DST|SRC)-NAT-[0-9]+(-MASQ)?\]"</command> + </node> + <leafNode name="ndp-proxy"> + <properties> + <help>Show log for Neighbor Discovery Protocol (NDP) Proxy</help> + </properties> + <command>journalctl --no-hostname --boot --unit ndppd.service</command> + </leafNode> + <leafNode name="nhrp"> + <properties> + <help>Show log for Next Hop Resolution Protocol (NHRP)</help> + </properties> + <command>journalctl --no-hostname --boot --unit opennhrp.service</command> + </leafNode> + <leafNode name="ntp"> + <properties> + <help>Show log for Network Time Protocol (NTP)</help> + </properties> + <command>journalctl --no-hostname --boot --unit chrony.service</command> + </leafNode> + <node name="macsec"> + <properties> + <help>Show log for MACsec</help> + </properties> + <command>journalctl --no-hostname --boot --unit "wpa_supplicant-macsec@*.service"</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show MACsec log on specific interface</help> + <completionHelp> + <path>interfaces macsec</path> + </completionHelp> + </properties> + <command>SRC=$(cli-shell-api returnValue interfaces macsec "$5" source-interface); journalctl --no-hostname --boot --unit "wpa_supplicant-macsec@$SRC.service"</command> + </tagNode> + </children> + </node> + <node name="openvpn"> + <properties> + <help>Show log for OpenVPN</help> + </properties> + <command>journalctl --no-hostname --boot --unit openvpn@*.service</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show OpenVPN log on specific interface</help> + <completionHelp> + <path>interfaces openvpn</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot --unit openvpn@$5.service</command> + </tagNode> + </children> + </node> + <node name="pppoe"> + <properties> + <help>Show log for PPPoE interface</help> + </properties> + <command>journalctl --no-hostname --boot --unit "ppp@pppoe*.service"</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show PPPoE log on specific interface</help> + <completionHelp> + <path>interfaces pppoe</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot --unit "ppp@$5.service"</command> + </tagNode> + </children> + </node> + <leafNode name="pppoe-server"> + <properties> + <help>Show log for PPPoE server</help> + </properties> + <command>journalctl --no-hostname --boot --unit accel-ppp@pppoe.service</command> + </leafNode> + <node name="protocol"> + <properties> + <help>Show log for Routing Protocol</help> + </properties> + <children> + <leafNode name="ospf"> + <properties> + <help>Show log for OSPF</help> + </properties> + <command>journalctl --boot /usr/lib/frr/ospfd</command> + </leafNode> + <leafNode name="ospfv3"> + <properties> + <help>Show log for OSPF for IPv6</help> + </properties> + <command>journalctl --boot /usr/lib/frr/ospf6d</command> + </leafNode> + <leafNode name="bgp"> + <properties> + <help>Show log for BGP</help> + </properties> + <command>journalctl --boot /usr/lib/frr/bgpd</command> + </leafNode> + <leafNode name="rip"> + <properties> + <help>Show log for RIP</help> + </properties> + <command>journalctl --boot /usr/lib/frr/ripd</command> + </leafNode> + <leafNode name="ripng"> + <properties> + <help>Show log for RIPng</help> + </properties> + <command>journalctl --boot /usr/lib/frr/ripngd</command> + </leafNode> + <leafNode name="static"> + <properties> + <help>Show log for static route</help> + </properties> + <command>journalctl --boot /usr/lib/frr/staticd</command> + </leafNode> + <leafNode name="multicast"> + <properties> + <help>Show log for Multicast protocol</help> + </properties> + <command>journalctl --boot /usr/lib/frr/pimd</command> + </leafNode> + <leafNode name="isis"> + <properties> + <help>Show log for ISIS</help> + </properties> + <command>journalctl --boot /usr/lib/frr/isisd</command> + </leafNode> + <leafNode name="openfabric"> + <properties> + <help>Show log for OpenFabric</help> + </properties> + <command>journalctl --boot /usr/lib/frr/fabricd</command> + </leafNode> + <leafNode name="nhrp"> + <properties> + <help>Show log for NHRP</help> + </properties> + <command>journalctl --boot /usr/lib/frr/nhrpd</command> + </leafNode> + <leafNode name="bfd"> + <properties> + <help>Show log for BFD</help> + </properties> + <command>journalctl --boot /usr/lib/frr/bfdd</command> + </leafNode> + <leafNode name="mpls"> + <properties> + <help>Show log for MPLS</help> + </properties> + <command>journalctl --boot /usr/lib/frr/ldpd</command> + </leafNode> + </children> + </node> + <leafNode name="router-advert"> + <properties> + <help>Show log for Router Advertisement Daemon (radvd)</help> + </properties> + <command>journalctl --no-hostname --boot --unit radvd.service</command> + </leafNode> + <leafNode name="snmp"> + <properties> + <help>Show log for Simple Network Monitoring Protocol (SNMP)</help> + </properties> + <command>journalctl --no-hostname --boot --unit snmpd.service</command> + </leafNode> + <node name="ssh"> + <properties> + <help>Show log for Secure Shell (SSH)</help> + </properties> + <command>journalctl --no-hostname --boot --unit ssh.service</command> + <children> + <node name="dynamic-protection"> + <properties> + <help>Show SSH guard log</help> + </properties> + <command>journalctl --no-hostname --boot --unit sshguard.service</command> + </node> + </children> + </node> + <tagNode name="tail"> + <properties> + <help>Show last n changes to messages</help> + <completionHelp> + <list><NUMBER></list> + </completionHelp> + </properties> + <command>tail -n "$4" /var/log/messages | ${VYATTA_PAGER:-cat}</command> + </tagNode> + <node name="tail"> + <properties> + <help>Show last 10 lines of /var/log/messages file</help> + </properties> + <command>tail -n 10 /var/log/messages</command> + </node> + <leafNode name="vpn"> + <properties> + <help>Monitor last lines of ALL Virtual Private Network services</help> + </properties> + <command>journalctl --no-hostname --boot --unit strongswan.service --unit accel-ppp@*.service --unit ocserv.service</command> + </leafNode> + <leafNode name="ipsec"> + <properties> + <help>Show log for IPsec</help> + </properties> + <command>journalctl --no-hostname --boot --unit strongswan.service</command> + </leafNode> + <leafNode name="l2tp"> + <properties> + <help>Show log for L2TP</help> + </properties> + <command>journalctl --no-hostname --boot --unit accel-ppp@l2tp.service</command> + </leafNode> + <leafNode name="openconnect"> + <properties> + <help>Show log for OpenConnect</help> + </properties> + <command>journalctl --no-hostname --boot --unit ocserv.service</command> + </leafNode> + <leafNode name="pptp"> + <properties> + <help>Show log for PPTP</help> + </properties> + <command>journalctl --no-hostname --boot --unit accel-ppp@pptp.service</command> + </leafNode> + <leafNode name="sstp"> + <properties> + <help>Show log for Secure Socket Tunneling Protocol (SSTP) server</help> + </properties> + <command>journalctl --no-hostname --boot --unit accel-ppp@sstp.service</command> + </leafNode> + <node name="sstpc"> + <properties> + <help>Show log for Secure Socket Tunneling Protocol (SSTP) client</help> + </properties> + <command>journalctl --no-hostname --boot --unit "ppp@sstpc*.service"</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show SSTP client log on specific interface</help> + <completionHelp> + <path>interfaces sstpc</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot --unit "ppp@$5.service"</command> + </tagNode> + </children> + </node> + <leafNode name="vrrp"> + <properties> + <help>Show log for Virtual Router Redundancy Protocol (VRRP)</help> + </properties> + <command>journalctl --no-hostname --boot --unit keepalived.service</command> + </leafNode> + <node name="wireless"> + <properties> + <help>Show log for Wireless interface</help> + </properties> + <children> + <node name="wpa-supplicant"> + <properties> + <help>Show log for WPA supplicant</help> + </properties> + <command>if cli-shell-api existsActive interfaces wireless; then journalctl --no-hostname --boot --unit "wpa_supplicant@*.service"; else echo "No wireless interface configured!"; fi</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show log for specific wireless interface supplicant</help> + <completionHelp> + <path>interfaces wireless</path> + </completionHelp> + </properties> + <command>if [[ $(cli-shell-api returnActiveValue interfaces wireless $6 type) == "station" ]]; then journalctl --no-hostname --boot --unit "wpa_supplicant@$6.service"; else echo "Wireless interface $6 not configured as station!"; fi</command> + </tagNode> + </children> + </node> + <node name="hostapd"> + <properties> + <help>Show log for host access point daemon</help> + </properties> + <command>if cli-shell-api existsActive interfaces wireless; then journalctl --no-hostname --boot --unit "hostapd@*.service"; else echo "No wireless interface configured!"; fi</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show log for specific host access point daemon interface</help> + <completionHelp> + <path>interfaces wireless</path> + </completionHelp> + </properties> + <command>if [[ $(cli-shell-api returnActiveValue interfaces wireless $6 type) == "access-point" ]]; then journalctl --no-hostname --boot --unit "hostapd@$6.service"; else echo "Wireless interface $6 not configured as access-point!"; fi</command> + </tagNode> + </children> + </node> + </children> + </node> + <leafNode name="webproxy"> + <properties> + <help>Show log for Webproxy</help> + </properties> + <command>journalctl --no-hostname --boot --unit squid.service</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-login.xml.in b/op-mode-definitions/show-login.xml.in new file mode 100644 index 0000000..6d8c782 --- /dev/null +++ b/op-mode-definitions/show-login.xml.in @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="login"> + <properties> + <help>Show current login credentials</help> + </properties> + <command>${vyos_op_scripts_dir}/show_current_user.sh</command> + <children> + <leafNode name="groups"> + <properties> + <help>Show current login group information</help> + </properties> + <command>/usr/bin/id -Gn</command> + </leafNode> + <leafNode name="level"> + <properties> + <help>Show current login level</help> + </properties> + <command>if [ -n "$VYATTA_USER_LEVEL_DIR" ]; then basename $VYATTA_USER_LEVEL_DIR; fi</command> + </leafNode> + <leafNode name="user"> + <properties> + <help>Show current login user id</help> + </properties> + <command>/usr/bin/id -un</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-monitoring.xml.in b/op-mode-definitions/show-monitoring.xml.in new file mode 100644 index 0000000..2651b34 --- /dev/null +++ b/op-mode-definitions/show-monitoring.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <leafNode name="monitoring"> + <properties> + <help>Show currently monitored services</help> + </properties> + <command>vtysh -c "show debugging"</command> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-mpls.xml.in b/op-mode-definitions/show-mpls.xml.in new file mode 100644 index 0000000..86f6f1b --- /dev/null +++ b/op-mode-definitions/show-mpls.xml.in @@ -0,0 +1,218 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="mpls"> + <properties> + <help>Show Multiprotocol Label Switching (MPLS)</help> + </properties> + <children> + <node name="ldp"> + <properties> + <help>Label Distribution Protocol (LDP)</help> + </properties> + <children> + <node name="binding"> + <properties> + <help>Label Information Base</help> + </properties> + <command>vtysh -c "show mpls ldp binding"</command> + <children> + <node name="detail"> + <properties> + <help>Show detailed information</help> + </properties> + <command>vtysh -c "show mpls ldp binding detail"</command> + </node> + <tagNode name="neighbor"> + <properties> + <help>Display labels from LDP neighbor</help> + <completionHelp> + <list><x.x.x.x> <h:h:h:h:h:h:h:h></list> + <script>vtysh -c "show mpls ldp neighbor" | awk '{print $2}' | egrep -v "ID"</script> + </completionHelp> + </properties> + <command>vtysh -c "show mpls ldp binding neighbor $6"</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed information</help> + </properties> + <command>vtysh -c "show mpls ldp binding neighbor $6 detail"</command> + </leafNode> + <tagNode name="local-label"> + <properties> + <help>Match locally assigned label value</help> + </properties> + <command>vtysh -c "show mpls ldp binding neighbor $6 local-label $8"</command> + </tagNode> + <tagNode name="remote-label"> + <properties> + <help>Match remotely assigned label value</help> + </properties> + <command>vtysh -c "show mpls ldp binding neighbor $6 remote-label $8"</command> + </tagNode> + </children> + </tagNode> + <tagNode name="local-label"> + <properties> + <help>Match locally assigned label value</help> + </properties> + <command>vtysh -c "show mpls ldp binding local-label $6"</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed information</help> + </properties> + <command>vtysh -c "show mpls ldp binding local-label $6 detail"</command> + </leafNode> + <tagNode name="neighbor"> + <properties> + <help>Match LDP neighbor</help> + <completionHelp> + <list><x.x.x.x> <h:h:h:h:h:h:h:h></list> + <script>vtysh -c "show mpls ldp neighbor" | awk '{print $2}' | egrep -v "ID"</script> + </completionHelp> + </properties> + <command>vtysh -c "show mpls ldp binding local-label $6 neighbor $8"</command> + </tagNode> + <tagNode name="remote-label"> + <properties> + <help>Match remotely assigned label value</help> + </properties> + <command>vtysh -c "show mpls ldp binding local-label $6 remote-label $8"</command> + </tagNode> + </children> + </tagNode> + <tagNode name="remote-label"> + <properties> + <help>Match remotely assigned label value</help> + </properties> + <command>vtysh -c "show mpls ldp binding remote-label $6"</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed information</help> + </properties> + <command>vtysh -c "show mpls ldp binding remote-label $6 detail"</command> + </leafNode> + <tagNode name="neighbor"> + <properties> + <help>Match LDP neighbor</help> + <completionHelp> + <list><x.x.x.x> <h:h:h:h:h:h:h:h></list> + <script>vtysh -c "show mpls ldp neighbor" | awk '{print $2}' | egrep -v "ID"</script> + </completionHelp> + </properties> + <command>vtysh -c "show mpls ldp binding remote-label $6 neighbor $8"</command> + </tagNode> + <tagNode name="local-label"> + <properties> + <help>Match locally assigned label value</help> + </properties> + <command>vtysh -c "show mpls ldp binding remote-label $6 local-label $8"</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + <tagNode name="binding"> + <properties> + <help>LDP forwarding equivalence class</help> + <completionHelp> + <list><x.x.x.x/x> <h:h:h:h:h:h:h:h/h></list> + </completionHelp> + </properties> + <command>vtysh -c "show mpls ldp binding $5"</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed information</help> + </properties> + <command>vtysh -c "show mpls ldp binding $5 detail"</command> + </leafNode> + </children> + </tagNode> + <node name="discovery"> + <properties> + <help>Discovery hello information</help> + </properties> + <command>vtysh -c "show mpls ldp discovery"</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed information</help> + </properties> + <command>vtysh -c "show mpls ldp discovery detail"</command> + </leafNode> + </children> + </node> + <node name="interface"> + <properties> + <help>LDP interface information</help> + </properties> + <command>vtysh -c "show mpls ldp interface"</command> + </node> + <node name="neighbor"> + <properties> + <help>LDP neighbor information</help> + </properties> + <command>vtysh -c "show mpls ldp neighbor"</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed information</help> + </properties> + <command>vtysh -c "show mpls ldp neighbor detail"</command> + </leafNode> + <leafNode name="capabilities"> + <properties> + <help>Show neighbor capability information</help> + </properties> + <command>vtysh -c "show mpls ldp neighbor capabilities"</command> + </leafNode> + </children> + </node> + <tagNode name="neighbor"> + <properties> + <help>LDP neighbor</help> + <completionHelp> + <list><x.x.x.x> <h:h:h:h:h:h:h:h></list> + <script>vtysh -c "show mpls ldp neighbor" | awk '{print $2}' | egrep -v "ID"</script> + </completionHelp> + </properties> + <command>vtysh -c "show mpls ldp neighbor $5"</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed information</help> + </properties> + <command>vtysh -c "show mpls ldp neighbor $5 detail"</command> + </leafNode> + <leafNode name="capabilities"> + <properties> + <help>Show neighbor capability information</help> + </properties> + <command>vtysh -c "show mpls ldp neighbor $5 capabilities"</command> + </leafNode> + </children> + </tagNode> + </children> + </node> + <node name="pseudowire"> + <properties> + <help>Show MPLS pseudowire interfaces</help> + </properties> + <command>vtysh -c "show mpls pseudowires"</command> + </node> + <node name="table"> + <properties> + <help>Show MPLS table</help> + </properties> + <command>vtysh -c "show mpls table"</command> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-netns.xml.in b/op-mode-definitions/show-netns.xml.in new file mode 100644 index 0000000..8d5072d --- /dev/null +++ b/op-mode-definitions/show-netns.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="netns"> + <properties> + <help>Show network namespace information</help> + </properties> + <command>ip netns ls</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-openfabric.xml.in b/op-mode-definitions/show-openfabric.xml.in new file mode 100644 index 0000000..2f48986 --- /dev/null +++ b/op-mode-definitions/show-openfabric.xml.in @@ -0,0 +1,51 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="openfabric"> + <properties> + <help>Show OpenFabric routing protocol</help> + </properties> + <children> + <node name="database"> + <properties> + <help>Show OpenFabric link state database</help> + </properties> + <children> + #include <include/vtysh-generic-detail.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + <node name="interface"> + <properties> + <help>Show OpenFabric interfaces</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + #include <include/vtysh-generic-detail.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + #include <include/vtysh-generic-interface-tagNode.xml.i> + <node name="neighbor"> + <properties> + <help>Show OpenFabric neighbor adjacencies</help> + </properties> + <children> + #include <include/vtysh-generic-detail.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + <leafNode name="summary"> + <properties> + <help>Show OpenFabric information summary</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-poweroff.xml.in b/op-mode-definitions/show-poweroff.xml.in new file mode 100644 index 0000000..1fd2afc --- /dev/null +++ b/op-mode-definitions/show-poweroff.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <leafNode name="poweroff"> + <properties> + <help>Show scheduled poweroff</help> + </properties> + <command>${vyos_op_scripts_dir}/powerctrl.py --check</command> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-protocols.xml.in b/op-mode-definitions/show-protocols.xml.in new file mode 100644 index 0000000..8f98f3a --- /dev/null +++ b/op-mode-definitions/show-protocols.xml.in @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="protocols"> + <properties> + <help>Show protocol specific information</help> + </properties> + <children> + <node name="static"> + <properties> + <help>Show static protocol parameters</help> + </properties> + <children> + <node name="arp"> + <properties> + <help>Show Address Resolution Protocol (ARP) information</help> + </properties> + <command>/usr/sbin/arp -e -n</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show Address Resolution Protocol (ARP) cache for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --broadcast</script> + </completionHelp> + </properties> + <command>/usr/sbin/arp -e -n -i "$6"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-qos.xml.in b/op-mode-definitions/show-qos.xml.in new file mode 100644 index 0000000..8974e95 --- /dev/null +++ b/op-mode-definitions/show-qos.xml.in @@ -0,0 +1,80 @@ +<?xml version="1.0" ?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="qos"> + <properties> + <help>Show Quality of Service (QoS) information</help> + </properties> + <children> + <node name="cake"> + <properties> + <help>Show QoS CAKE information</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Show QoS CAKE for given interface</help> + <completionHelp> + <path>qos interface</path> + <list><interface></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/qos.py show_cake --ifname $5</command> + </tagNode> + </children> + </node> + <node name="shaper"> + <properties> + <help>Show QoS shaping information</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/qos.py show_shaper</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show QoS detailed information</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/qos.py show_shaper --detail</command> + </leafNode> + <tagNode name="interface"> + <properties> + <help>Show QoS shaping for given interface</help> + <completionHelp> + <path>qos interface</path> + <list><interface></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/qos.py show_shaper --ifname $5</command> + <children> + <tagNode name="class"> + <properties> + <help>Show QoS shaping for given class</help> + <completionHelp> + <list><class></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/qos.py show_shaper --ifname $5 --classn $7</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show QoS detailed information for given class</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/qos.py show_shaper --ifname $5 --classn $7 --detail</command> + </leafNode> + </children> + </tagNode> + <leafNode name="detail"> + <properties> + <help>Show QoS detailed information for given interface</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/qos.py show_shaper --ifname $5 --detail</command> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-raid.xml.in b/op-mode-definitions/show-raid.xml.in new file mode 100644 index 0000000..2ae3fad --- /dev/null +++ b/op-mode-definitions/show-raid.xml.in @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <tagNode name="raid"> + <properties> + <help>Show status of RAID set</help> + <completionHelp> + <script>${vyos_completion_dir}/list_raidset.sh</script> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_raid.sh $3</command> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-reboot.xml.in b/op-mode-definitions/show-reboot.xml.in new file mode 100644 index 0000000..c85966b --- /dev/null +++ b/op-mode-definitions/show-reboot.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <leafNode name="reboot"> + <properties> + <help>Show scheduled reboot</help> + </properties> + <command>${vyos_op_scripts_dir}/powerctrl.py --check</command> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-route-map.xml.in b/op-mode-definitions/show-route-map.xml.in new file mode 100644 index 0000000..633b2a4 --- /dev/null +++ b/op-mode-definitions/show-route-map.xml.in @@ -0,0 +1,22 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="route-map"> + <properties> + <help>Show route-map information</help> + </properties> + <command>vtysh -c "show route-map"</command> + </node> + <tagNode name="route-map"> + <properties> + <help>Show specified route-map information</help> + <completionHelp> + <path>policy route-map</path> + </completionHelp> + </properties> + <command>vtysh -c "show route-map $3"</command> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-secure-boot.xml.in b/op-mode-definitions/show-secure-boot.xml.in new file mode 100644 index 0000000..ff731ba --- /dev/null +++ b/op-mode-definitions/show-secure-boot.xml.in @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="secure-boot"> + <properties> + <help>Show Secure Boot state</help> + </properties> + <command>${vyos_op_scripts_dir}/secure_boot.py show</command> + <children> + <leafNode name="keys"> + <properties> + <help>Show enrolled certificates</help> + </properties> + <command>mokutil --list-enrolled</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-segment-routing.xml.in b/op-mode-definitions/show-segment-routing.xml.in new file mode 100644 index 0000000..ebdb51a --- /dev/null +++ b/op-mode-definitions/show-segment-routing.xml.in @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="segment-routing"> + <properties> + <help>Show Segment Routing</help> + </properties> + <children> + <node name="srv6"> + <properties> + <help>Segment Routing SRv6</help> + </properties> + <children> + <node name="locator"> + <properties> + <help>Locator Information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ssh.xml.in b/op-mode-definitions/show-ssh.xml.in new file mode 100644 index 0000000..ca8e669 --- /dev/null +++ b/op-mode-definitions/show-ssh.xml.in @@ -0,0 +1,34 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ssh"> + <properties> + <help>Show SSH server information</help> + </properties> + <children> + <node name="dynamic-protection"> + <properties> + <help>Show SSH server dynamic-protection blocked attackers</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ssh.py show_dynamic_protection</command> + </node> + <node name="fingerprints"> + <properties> + <help>Show SSH server public key fingerprints</help> + </properties> + <command>${vyos_op_scripts_dir}/ssh.py show_fingerprints</command> + <children> + <node name="ascii"> + <properties> + <help>Show visual ASCII art representation of the public key</help> + </properties> + <command>${vyos_op_scripts_dir}/ssh.py show_fingerprints --ascii</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-system.xml.in b/op-mode-definitions/show-system.xml.in new file mode 100644 index 0000000..6873b81 --- /dev/null +++ b/op-mode-definitions/show-system.xml.in @@ -0,0 +1,268 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="system"> + <properties> + <help>Show system information</help> + </properties> + <children> + <node name="commit"> + <properties> + <help>Show commit revision log</help> + </properties> + <command>${vyos_op_scripts_dir}/config_mgmt.py show_commit_log</command> + <children> + <tagNode name="diff"> + <properties> + <help>Show commit revision diff</help> + </properties> + <command>${vyos_op_scripts_dir}/config_mgmt.py show_commit_diff --rev "$5"</command> + </tagNode> + <tagNode name="file"> + <properties> + <help>Show commit revision file</help> + </properties> + <command>${vyos_op_scripts_dir}/config_mgmt.py show_commit_file --rev "$5"</command> + <children> + <tagNode name="compare"> + <properties> + <help>Compare config file revisions</help> + </properties> + <command>${vyos_op_scripts_dir}/config_mgmt.py show_commit_diff --rev "$5" --rev2 "$7"</command> + <children> + <leafNode name="commands"> + <properties> + <help>Compare config file revision commands</help> + </properties> + <command>${vyos_op_scripts_dir}/config_mgmt.py show_commit_diff --rev "$5" --rev2 "$7" --commands</command> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + <node name="connections"> + <properties> + <help>Show active network connections on the system</help> + </properties> + <command>netstat -an</command> + <children> + <node name="tcp"> + <properties> + <help>Show TCP connection information</help> + </properties> + <command>ss -t -r</command> + <children> + <leafNode name="all"> + <properties> + <help>Show all TCP connections</help> + </properties> + <command>ss -t -a</command> + </leafNode> + <leafNode name="numeric"> + <properties> + <help>Show TCP connection without resolving names</help> + </properties> + <command>ss -t -n</command> + </leafNode> + </children> + </node> + <node name="udp"> + <properties> + <help>Show UDP socket information</help> + </properties> + <command>ss -u -a -r</command> + <children> + <leafNode name="numeric"> + <properties> + <help>Show UDP socket information without resolving names</help> + </properties> + <command>ss -u -a -n</command> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="cpu"> + <properties> + <help>Show CPU information</help> + </properties> + <command>${vyos_op_scripts_dir}/cpu.py show</command> + </leafNode> + <leafNode name="kernel-messages"> + <properties> + <help>Show messages in kernel ring buffer</help> + </properties> + <command>sudo dmesg</command> + </leafNode> + <node name="login"> + <properties> + <help>Show user accounts</help> + </properties> + <children> + <node name="authentication"> + <properties> + <help>Show user account authentication information</help> + </properties> + <children> + <tagNode name="user"> + <properties> + <help>Show configured user</help> + <completionHelp> + <path>system login user</path> + </completionHelp> + </properties> + <children> + <node name="otp"> + <properties> + <help>Show OTP key information</help> + </properties> + <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="full"</command> + <children> + <leafNode name="full"> + <properties> + <help>Show full settings, including QR code and commands for VyOS</help> + </properties> + <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="full"</command> + </leafNode> + <leafNode name="key-b32"> + <properties> + <help>Show OTP authentication secret in Base32 (used in mobile apps)</help> + </properties> + <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="key-b32"</command> + </leafNode> + <leafNode name="qrcode"> + <properties> + <help>Show OTP authentication QR code</help> + </properties> + <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="qrcode"</command> + </leafNode> + <leafNode name="uri"> + <properties> + <help>Show OTP authentication otpauth URI</help> + </properties> + <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="uri"</command> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> + </node> + <node name="users"> + <properties> + <help>Show user account information</help> + </properties> + <command>${vyos_libexec_dir}/vyos-sudo.py ${vyos_op_scripts_dir}/show_users.py</command> + <children> + <leafNode name="all"> + <properties> + <help>Show information about all accounts</help> + </properties> + <command>${vyos_libexec_dir}/vyos-sudo.py ${vyos_op_scripts_dir}/show_users.py all</command> + </leafNode> + <leafNode name="locked"> + <properties> + <help>Show information about locked accounts</help> + </properties> + <command>${vyos_libexec_dir}/vyos-sudo.py ${vyos_op_scripts_dir}/show_users.py locked</command> + </leafNode> + <leafNode name="other"> + <properties> + <help>Show information about non VyOS user accounts</help> + </properties> + <command>${vyos_libexec_dir}/vyos-sudo.py ${vyos_op_scripts_dir}/show_users.py other</command> + </leafNode> + <leafNode name="vyos"> + <properties> + <help>Show information about VyOS user accounts</help> + </properties> + <command>${vyos_libexec_dir}/vyos-sudo.py ${vyos_op_scripts_dir}/show_users.py vyos</command> + </leafNode> + </children> + </node> + </children> + </node> + <node name="memory"> + <properties> + <help>Show system memory usage</help> + </properties> + <command>${vyos_op_scripts_dir}/memory.py show</command> + <children> + <leafNode name="cache"> + <properties> + <help>Show kernel cache information</help> + </properties> + <command>sudo slabtop -o</command> + </leafNode> + <leafNode name="detail"> + <properties> + <help>Show detailed system memory usage</help> + </properties> + <command>cat /proc/meminfo</command> + </leafNode> + <leafNode name="routing-daemons"> + <properties> + <help>Show memory usage of all routing protocols</help> + </properties> + <command>vtysh -c "show memory"</command> + </leafNode> + </children> + </node> + <node name="processes"> + <properties> + <help>Show system processes</help> + </properties> + <command>ps ax</command> + <children> + <leafNode name="extensive"> + <properties> + <help>Show extensive process info</help> + </properties> + <command>top -b -n1</command> + </leafNode> + <leafNode name="summary"> + <properties> + <help>Show summary of system processes</help> + </properties> + <command>${vyos_op_scripts_dir}/uptime.py show</command> + </leafNode> + <leafNode name="tree"> + <properties> + <help>Show process tree</help> + </properties> + <command>ps -ejH</command> + </leafNode> + </children> + </node> + <leafNode name="routing-daemons"> + <properties> + <help>Show FRRouting daemons</help> + </properties> + <command>vtysh -c "show daemons"</command> + </leafNode> + <leafNode name="storage"> + <properties> + <help>Show filesystem usage</help> + </properties> + <command>${vyos_op_scripts_dir}/storage.py show</command> + </leafNode> + <leafNode name="updates"> + <properties> + <help>Show system available updates</help> + </properties> + <command>${vyos_op_scripts_dir}/system.py show_update</command> + </leafNode> + <leafNode name="uptime"> + <properties> + <help>Show system uptime and load averages</help> + </properties> + <command>${vyos_op_scripts_dir}/uptime.py show</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-table.xml.in b/op-mode-definitions/show-table.xml.in new file mode 100644 index 0000000..c7998e3 --- /dev/null +++ b/op-mode-definitions/show-table.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <leafNode name="table"> + <properties> + <help>Show routing tables</help> + </properties> + <command>vtysh -c "show zebra router table summary"</command> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-techsupport_report.xml.in b/op-mode-definitions/show-techsupport_report.xml.in new file mode 100644 index 0000000..4fd6e5d --- /dev/null +++ b/op-mode-definitions/show-techsupport_report.xml.in @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="tech-support"> + <properties> + <help>Show tech-support report</help> + </properties> + <children> + <node name="report"> + <properties> + <help>Show consolidated tech-support report (contains private information)</help> + </properties> + <command>${vyos_op_scripts_dir}/show_techsupport_report.py</command> + <children> + <node name="machine-readable"> + <properties> + <help>Show consolidated tech-support report in JSON</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/tech_support.py show --raw</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-users.xml.in b/op-mode-definitions/show-users.xml.in new file mode 100644 index 0000000..a026e47 --- /dev/null +++ b/op-mode-definitions/show-users.xml.in @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="users"> + <properties> + <help>Show user information</help> + </properties> + <command>who -H</command> + <children> + <node name="recent"> + <properties> + <help>Show 10 recently logged in users</help> + </properties> + <command>last -aF -n 10 | sed -e 's/^wtmp begins/Displaying logins since/'</command> + </node> + <tagNode name="recent"> + <properties> + <help>Show specified number of recently logged in users</help> + <completionHelp> + <list>NUMBER</list> + </completionHelp> + </properties> + <command>last -aF -n $4 | sed -e 's/^wtmp begins/Displaying logins since/'</command> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-version.xml.in b/op-mode-definitions/show-version.xml.in new file mode 100644 index 0000000..36e68ff --- /dev/null +++ b/op-mode-definitions/show-version.xml.in @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="version"> + <properties> + <help>Show system version information</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/version.py show</command> + <children> + <leafNode name="funny"> + <properties> + <help>Show system version and some fun stuff</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/version.py show --funny</command> + </leafNode> + <leafNode name="all"> + <properties> + <help>Show system version and versions of all packages</help> + </properties> + <command>echo "Package versions:"; dpkg -l | cat</command> + </leafNode> + <leafNode name="frr"> + <properties> + <help>Show FRRouting version information</help> + </properties> + <command>vtysh -c "show version"</command> + </leafNode> + <leafNode name="kernel"> + <properties> + <help>Show Linux Kernel version information</help> + </properties> + <command>uname -r</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-virtual-server.xml.in b/op-mode-definitions/show-virtual-server.xml.in new file mode 100644 index 0000000..5dbd3c7 --- /dev/null +++ b/op-mode-definitions/show-virtual-server.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="virtual-server"> + <properties> + <help>Show virtual server information</help> + </properties> + <command>${vyos_op_scripts_dir}/show_virtual_server.py</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-vrf.xml.in b/op-mode-definitions/show-vrf.xml.in new file mode 100644 index 0000000..c186498 --- /dev/null +++ b/op-mode-definitions/show-vrf.xml.in @@ -0,0 +1,44 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="vrf"> + <properties> + <help>Show VRF (Virtual Routing and Forwarding) information</help> + </properties> + <command>${vyos_op_scripts_dir}/vrf.py show</command> + <children> + <leafNode name="vni"> + <properties> + <help>Show information on VRF/VXLAN VNI mapping</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> + <tagNode name="vrf"> + <properties> + <help>Show information on specific VRF instance</help> + <completionHelp> + <path>vrf name</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vrf.py show --name="$3"</command> + <children> + <leafNode name="processes"> + <properties> + <help>Shows all process ids associated with VRF</help> + </properties> + <command>ip vrf pids "$3"</command> + </leafNode> + <leafNode name="vni"> + <properties> + <help>Show VXLAN VNI association</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-zebra.xml.in b/op-mode-definitions/show-zebra.xml.in new file mode 100644 index 0000000..69991a1 --- /dev/null +++ b/op-mode-definitions/show-zebra.xml.in @@ -0,0 +1,54 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="zebra"> + <properties> + <help>Show Zebra routing information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + <node name="client"> + <properties> + <help>Client information </help> + </properties> + <children> + <node name="summary"> + <properties> + <help>Brief Summary</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + </children> + </node> + <node name="dplane"> + <properties> + <help>Zebra dataplane information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + <node name="router"> + <properties> + <help>Zebra Router Information</help> + </properties> + <children> + <node name="table"> + <properties> + <help>Table Information about this Zebra Router</help> + </properties> + <children> + <node name="summary"> + <properties> + <help>Summary Information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/snmp.xml.in b/op-mode-definitions/snmp.xml.in new file mode 100644 index 0000000..894005e --- /dev/null +++ b/op-mode-definitions/snmp.xml.in @@ -0,0 +1,111 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="snmp"> + <properties> + <help>Show status of SNMP on localhost</help> + </properties> + <children> + <tagNode name="community"> + <properties> + <help>Show status of SNMP community</help> + <completionHelp> + <script>${vyos_op_scripts_dir}/snmp.py --allowed</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/snmp.py --community="$4"</command> + <children> + <tagNode name="host"> + <properties> + <help>Show status of SNMP on remote host</help> + </properties> + <command>${vyos_op_scripts_dir}/snmp.py --community="$4" --host "$6"</command> + </tagNode> + </children> + </tagNode> + <node name="mib"> + <properties> + <help>Show SNMP MIB information</help> + </properties> + <children> + <node name="ifmib"> + <properties> + <help>Show all SNMP interfaces MIB information</help> + </properties> + <command>${vyos_op_scripts_dir}/snmp_ifmib.py</command> + <children> + <tagNode name="ifAlias"> + <properties> + <help>Show SNMP ifAlias for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/snmp_ifmib.py --ifalias="$6"</command> + </tagNode> + <tagNode name="ifDescr"> + <properties> + <help>Show SNMP ifDescr for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/snmp_ifmib.py --ifdescr="$6"</command> + </tagNode> + <tagNode name="ifIndex"> + <properties> + <help>Show SNMP ifDescr for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/snmp_ifmib.py --ifindex="$6"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="v3"> + <properties> + <help>Show SNMP v3 status on localhost</help> + </properties> + <command>${vyos_op_scripts_dir}/snmp_v3.py --all</command> + <children> + <leafNode name="certificates"> + <properties> + <help>Show TSM certificates</help> + </properties> + <command>${vyos_op_scripts_dir}/snmp_v3_showcerts.sh</command> + </leafNode> + <leafNode name="group"> + <properties> + <help>Show the list of configured groups</help> + </properties> + <command>${vyos_op_scripts_dir}/snmp_v3.py --group</command> + </leafNode> + <leafNode name="trap-target"> + <properties> + <help>Show the list of configured targets</help> + </properties> + <command>${vyos_op_scripts_dir}/snmp_v3.py --trap</command> + </leafNode> + <leafNode name="user"> + <properties> + <help>Show the list of configured users</help> + </properties> + <command>${vyos_op_scripts_dir}/snmp_v3.py --user</command> + </leafNode> + <leafNode name="view"> + <properties> + <help>Show the list of configured views</help> + </properties> + <command>${vyos_op_scripts_dir}/snmp_v3.py --view</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/sstp-server.xml.in b/op-mode-definitions/sstp-server.xml.in new file mode 100644 index 0000000..03dfc42 --- /dev/null +++ b/op-mode-definitions/sstp-server.xml.in @@ -0,0 +1,26 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="sstp-server"> + <properties> + <help>Show SSTP server information</help> + </properties> + <children> + <leafNode name="sessions"> + <properties> + <help>Show active SSTP server sessions</help> + </properties> + <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="sstp" --action="show sessions"</command> + </leafNode> + <leafNode name="statistics"> + <properties> + <help>Show SSTP server statistics</help> + </properties> + <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="sstp" --action="show stat"</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/suricata.xml.in b/op-mode-definitions/suricata.xml.in new file mode 100644 index 0000000..ff1f847 --- /dev/null +++ b/op-mode-definitions/suricata.xml.in @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="update"> + <children> + <node name="suricata"> + <properties> + <help>Update Suricata</help> + </properties> + <command>if test -f /run/suricata/suricata.yaml; then sudo suricata-update --suricata-conf /run/suricata/suricata.yaml; sudo systemctl restart suricata; else echo "Service Suricata not configured"; fi </command> + </node> + </children> + </node> + <node name="restart"> + <children> + <node name="suricata"> + <properties> + <help>Restart Suricata service</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name suricata</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/system-image.xml.in b/op-mode-definitions/system-image.xml.in new file mode 100644 index 0000000..44b055b --- /dev/null +++ b/op-mode-definitions/system-image.xml.in @@ -0,0 +1,210 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="add"> + <properties> + <help>Add an object</help> + </properties> + <children> + <node name="system"> + <properties> + <help>Add item to a system facility</help> + </properties> + <children> + <tagNode name="image"> + <properties> + <help>Add a new image to the system</help> + <completionHelp> + <list>/path/to/vyos-image.iso "http://example.com/vyos-image.iso" latest</list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/image_installer.py --action add --image-path "${4}"</command> + <children> + <tagNode name="vrf"> + <properties> + <help>Download image via specified VRF</help> + <completionHelp> + <path>vrf name</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/image_installer.py --action add --image-path "${4}" --vrf "${6}"</command> + <children> + <tagNode name="username"> + <properties> + <help>Username for authentication</help> + </properties> + <children> + <tagNode name="password"> + <properties> + <help>Password to use with authentication</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/image_installer.py --action add --image-path "${4}" --vrf "${6}" --username "${8}" --password "${10}"</command> + </tagNode> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="username"> + <properties> + <help>Username for authentication</help> + </properties> + <children> + <tagNode name="password"> + <properties> + <help>Password to use with authentication</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/image_installer.py --action add --image-path "${4}" --username "${6}" --password "${8}"</command> + </tagNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + <node name="set"> + <properties> + <help>Install a new system</help> + </properties> + <children> + <node name="system"> + <properties> + <help>Set system operational parameters</help> + </properties> + <children> + <tagNode name="boot-console"> + <properties> + <help>Set system console type at boot</help> + <completionHelp> + <script>sudo ${vyos_op_scripts_dir}/image_manager.py --action list_console_types</script> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/image_manager.py --action set_console_type --console-type "${4}"</command> + </tagNode> + <node name="image"> + <properties> + <help>Set system image parameters</help> + </properties> + <children> + <node name="default-boot"> + <properties> + <help>Set default image to boot.</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/image_manager.py --action set</command> + </node> + <tagNode name="default-boot"> + <properties> + <help>Set default image to boot.</help> + <completionHelp> + <script>sudo ${vyos_op_scripts_dir}/image_manager.py --action list</script> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/image_manager.py --action set --image-name "${5}"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="install"> + <properties> + <help>Install a new system</help> + </properties> + <children> + <node name="image"> + <properties> + <help>Install new system image to hard drive</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/image_installer.py --action install</command> + </node> + </children> + </node> + <node name="delete"> + <properties> + <help>Delete an object</help> + </properties> + <children> + <node name="system"> + <properties> + <help>Delete system objects</help> + </properties> + <children> + <node name="image"> + <properties> + <help>Remove an installed image from the system</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/image_manager.py --action delete</command> + </node> + <tagNode name="image"> + <properties> + <help>Remove an installed image from the system</help> + <completionHelp> + <script>sudo ${vyos_op_scripts_dir}/image_manager.py --action list</script> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/image_manager.py --action delete --image-name "${4}"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="rename"> + <properties> + <help>Rename an object</help> + </properties> + <children> + <node name="system"> + <properties> + <help>Rename a system object</help> + </properties> + <children> + <tagNode name="image"> + <properties> + <help>System image to rename</help> + <completionHelp> + <script>sudo ${vyos_op_scripts_dir}/image_manager.py --action list</script> + </completionHelp> + </properties> + <children> + <tagNode name="to"> + <properties> + <help>A new name for an image</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/image_manager.py --action rename --image-name "${4}" --image-new-name "${6}"</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + <node name="show"> + <properties> + <help>Rename an object</help> + </properties> + <children> + <node name="system"> + <properties> + <help>Show system information</help> + </properties> + <children> + <node name="image"> + <properties> + <help>Show installed VyOS images</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/image_info.py show_images_summary</command> + <children> + <node name="details"> + <properties> + <help>Show details about installed VyOS images</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/image_info.py show_images_details</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/telnet.xml.in b/op-mode-definitions/telnet.xml.in new file mode 100644 index 0000000..2cacc6a --- /dev/null +++ b/op-mode-definitions/telnet.xml.in @@ -0,0 +1,35 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="execute"> + <children> + <node name="telnet"> + <properties> + <help>Telnet to a node</help> + </properties> + <children> + <tagNode name="to"> + <properties> + <help>Telnet to a host</help> + <completionHelp> + <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>/usr/bin/telnet $4</command> + <children> + <tagNode name="port"> + <properties> + <help>Telnet to a host:port</help> + <completionHelp> + <list><0-65535></list> + </completionHelp> + </properties> + <command>/usr/bin/telnet $4 $6</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> + diff --git a/op-mode-definitions/terminal.xml.in b/op-mode-definitions/terminal.xml.in new file mode 100644 index 0000000..2a76de1 --- /dev/null +++ b/op-mode-definitions/terminal.xml.in @@ -0,0 +1,114 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="clear"> + <properties> + <help>Clear system information</help> + </properties> + <children> + <node name="console"> + <properties> + <help>Clear screen</help> + </properties> + <command>/usr/bin/clear</command> + </node> + </children> + </node> + <node name="reset"> + <properties> + <help>Reset a service</help> + </properties> + <children> + <node name="terminal"> + <properties> + <help>Reset terminal</help> + </properties> + <command>/usr/bin/reset</command> + </node> + </children> + </node> + <node name="set"> + <properties> + <help>Set operational options</help> + </properties> + <children> + <tagNode name="builtin"> + <properties> + <help>Bash builtin set command</help> + <completionHelp> + <list><OPTION></list> + </completionHelp> + </properties> + <command>builtin $3</command> + </tagNode> + <node name="console"> + <properties> + <help>Control console behaviors</help> + </properties> + <children> + <leafNode name="keymap"> + <properties> + <help>Reconfigure console keyboard layout</help> + </properties> + <command>sudo dpkg-reconfigure -f dialog keyboard-configuration && sudo systemctl restart keyboard-setup</command> + </leafNode> + </children> + </node> + <node name="terminal"> + <properties> + <help>Control terminal behaviors</help> + </properties> + <children> + <node name="key"> + <properties> + <help>Set key behaviors</help> + </properties> + <children> + <tagNode name="query-help"> + <properties> + <help>Enable/disable getting help using question mark (default enabled)</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/toggle_help_binding.sh $5</command> + </tagNode> + </children> + </node> + <node name="pager"> + <properties> + <help>Set terminal pager to default (less)</help> + </properties> + <command>VYATTA_PAGER=${_vyatta_default_pager}</command> + </node> + <tagNode name="pager"> + <properties> + <help>Set terminal pager</help> + <completionHelp> + <list><PROGRAM></list> + </completionHelp> + </properties> + <command>VYATTA_PAGER=$4</command> + </tagNode> + <tagNode name="length"> + <properties> + <help>Set terminal to given number of rows (0 disables paging)</help> + <completionHelp> + <list><NUMBER></list> + </completionHelp> + </properties> + <command>if [ "$4" -eq 0 ]; then VYATTA_PAGER=cat; else VYATTA_PAGER=${_vyatta_default_pager}; stty rows $4; fi</command> + </tagNode> + <tagNode name="width"> + <properties> + <help>Set terminal to given number of columns</help> + <completionHelp> + <list><NUMBER></list> + </completionHelp> + </properties> + <command>stty columns $4</command> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/traceroute.xml.in b/op-mode-definitions/traceroute.xml.in new file mode 100644 index 0000000..aba0f45 --- /dev/null +++ b/op-mode-definitions/traceroute.xml.in @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <tagNode name="traceroute"> + <properties> + <help>Trace network path to node</help> + <completionHelp> + <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/traceroute.py ${@:2}</command> + <children> + <leafNode name="node.tag"> + <properties> + <help>Traceroute options</help> + <completionHelp> + <script>${vyos_op_scripts_dir}/traceroute.py --get-options "${COMP_WORDS[@]}"</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/traceroute.py ${@:2}</command> + </leafNode> + </children> + </tagNode> +</interfaceDefinition> diff --git a/op-mode-definitions/traffic-dump.xml.in b/op-mode-definitions/traffic-dump.xml.in new file mode 100644 index 0000000..e86e697 --- /dev/null +++ b/op-mode-definitions/traffic-dump.xml.in @@ -0,0 +1,34 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="monitor"> + <children> + <node name="traffic"> + <properties> + <help>Monitor traffic dumps</help> + </properties> + <children> + <tagNode name="interface"> + <command>${vyos_op_scripts_dir}/tcpdump.py $4</command> + <properties> + <help>Monitor traffic dump from an interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_dumpable_interfaces.py</script> + </completionHelp> + </properties> + <children> + <leafNode name="node.tag"> + <properties> + <help>Traffic capture options</help> + <completionHelp> + <script>${vyos_op_scripts_dir}/tcpdump.py --get-options-nested "${COMP_WORDS[@]}"</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/tcpdump.py "${@:4}"</command> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/vpn-ipsec.xml.in b/op-mode-definitions/vpn-ipsec.xml.in new file mode 100644 index 0000000..0a8671a --- /dev/null +++ b/op-mode-definitions/vpn-ipsec.xml.in @@ -0,0 +1,296 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="reset"> + <children> + <node name="vpn"> + <properties> + <help>Reset Virtual Private Network (VPN) information</help> + </properties> + <children> + <node name="ipsec"> + <properties> + <help>Reset IPSec VPN sessions</help> + </properties> + <children> + <tagNode name="profile"> + <properties> + <help>Reset a specific tunnel for given DMVPN profile</help> + <completionHelp> + <path>vpn ipsec profile</path> + </completionHelp> + </properties> + <children> + <tagNode name="tunnel"> + <properties> + <help>Reset a specific tunnel for given DMVPN profile</help> + <completionHelp> + <script>sudo ${vyos_completion_dir}/list_ipsec_profile_tunnels.py --profile ${COMP_WORDS[4]}</script> + </completionHelp> + </properties> + <children> + <tagNode name="remote-host"> + <properties> + <help>Reset a specific tunnel for given DMVPN NBMA</help> + <completionHelp> + <list><x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_profile_dst --profile="$5" --tunnel="$7" --nbma-dst="$9"</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_profile_all --profile="$5" --tunnel="$7"</command> + </tagNode> + </children> + </tagNode> + <node name="remote-access"> + <properties> + <help>Reset remote access IPSec VPN connections</help> + </properties> + <children> + <node name="all"> + <properties> + <help>Reset all users current remote access IPSec VPN sessions</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_ra</command> + </node> + <tagNode name="user"> + <properties> + <help>Reset specified user current remote access IPsec VPN session(s)</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_ra --user="$6"</command> + </tagNode> + </children> + </node> + <node name="site-to-site"> + <properties> + <help>Reset site-to-site IPSec VPN connections</help> + </properties> + <children> + <node name="all"> + <properties> + <help>Reset all site-to-site IPSec VPN sessions</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_all_peers</command> + </node> + <tagNode name="peer"> + <properties> + <help>Reset all tunnels for given peer</help> + <completionHelp> + <path>vpn ipsec site-to-site peer</path> + </completionHelp> + </properties> + <children> + <tagNode name="tunnel"> + <properties> + <help>Reset a specific tunnel for given peer</help> + <completionHelp> + <path>vpn ipsec site-to-site peer ${COMP_WORDS[5]} tunnel</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_peer --peer="$6" --tunnel="$8"</command> + </tagNode> + <node name="vti"> + <properties> + <help>Reset the VTI tunnel for given peer</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_peer --peer="$6" --tunnel="vti"</command> + </node> + </children> + <command>sudo ${vyos_op_scripts_dir}/ipsec.py reset_peer --peer="$6"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="restart"> + <children> + <node name="ipsec"> + <properties> + <help>Restart the IPsec VPN process</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name ipsec</command> + </node> + </children> + </node> + <node name="show"> + <children> + <node name="vpn"> + <properties> + <help>Show Virtual Private Network (VPN) information</help> + </properties> + <children> + <node name="debug"> + <properties> + <help>Show VPN debugging information</help> + </properties> + <children> + <tagNode name="peer"> + <properties> + <help>Show debugging information for a peer</help> + <completionHelp> + <path>vpn ipsec site-to-site peer</path> + </completionHelp> + </properties> + <children> + <tagNode name="tunnel"> + <properties> + <help>Show debug information for peer tunnel</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/vpn_ipsec.py --action="vpn-debug" --name="$5" --tunnel="$7"</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/vpn_ipsec.py --action="vpn-debug" --name="$5" --tunnel="all"</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/vpn_ipsec.py --action="vpn-debug" --name="all"</command> + </node> + <node name="ike"> + <properties> + <help>Show Internet Key Exchange (IKE) information</help> + </properties> + <children> + <node name="sa"> + <properties> + <help>Show all currently active IKE Security Associations (SA)</help> + </properties> + <children> + <node name="nat-traversal"> + <properties> + <help>Show all currently active IKE Security Associations (SA) that are using NAT Traversal</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/vpn_ike_sa.py --nat="yes"</command> + </node> + <tagNode name="peer"> + <properties> + <help>Show all currently active IKE Security Associations (SA) for a peer</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/vpn_ike_sa.py --peer="$6"</command> + </tagNode> + </children> + <command>sudo ${vyos_op_scripts_dir}/vpn_ike_sa.py</command> + </node> + <node name="secrets"> + <properties> + <help>Show all the pre-shared key secrets</help> + </properties> + <command>${vyos_op_scripts_dir}/ipsec.py show_psk</command> + </node> + <node name="status"> + <properties> + <help>Show summary of IKE process information</help> + </properties> + <command>if systemctl is-active --quiet strongswan ; then systemctl status strongswan ; else echo "Process is not running" ; fi</command> + </node> + </children> + </node> + <node name="ipsec"> + <properties> + <help>Show Internet Protocol Security (IPsec) information</help> + </properties> + <children> + <node name="connections"> + <properties> + <help>Show VPN connections</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/ipsec.py show_connections</command> + </node> + <node name="policy"> + <properties> + <help>Show the in-kernel crypto policies</help> + </properties> + <command>sudo ip xfrm policy list</command> + </node> + <node name="remote-access"> + <properties> + <help>Show active VPN server sessions</help> + </properties> + <children> + <node name="detail"> + <properties> + <help>Show detail active IKEv2 RA sessions</help> + </properties> + <command>if systemctl is-active --quiet strongswan ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_ra_detail; else echo "IPsec process not running" ; fi</command> + </node> + <tagNode name="connection-id"> + <properties> + <help>Show detail active IKEv2 RA sessions by connection-id</help> + </properties> + <command>if systemctl is-active --quiet strongswan ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_ra_detail --conn-id="$6"; else echo "IPsec process not running" ; fi</command> + </tagNode> + <node name="summary"> + <properties> + <help>Show active IKEv2 RA sessions summary</help> + </properties> + <command>if systemctl is-active --quiet strongswan ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_ra_summary; else echo "IPsec process not running" ; fi</command> + </node> + <tagNode name="username"> + <properties> + <help>Show detail active IKEv2 RA sessions by username</help> + </properties> + <command>if systemctl is-active --quiet strongswan ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_ra_detail --username="$6"; else echo "IPsec process not running" ; fi</command> + </tagNode> + </children> + </node> + <node name="sa"> + <properties> + <help>Show all active IPsec Security Associations (SA)</help> + </properties> + <children> + <!-- + <node name="detail"> + <properties> + <help>Show Detail on all active IPsec Security Associations (SA)</help> + </properties> + <command></command> + </node> + <tagNode name="stats"> + <properties> + <help>Show statistics for all currently active IPsec Security Associations (SA)</help> + <valueHelp> + <format>txt</format> + <description>Show Statistics for SAs associated with a specific peer</description> + </valueHelp> + </properties> + <children> + <tagNode name="tunnel"> + <properties> + <help>Show Statistics for SAs associated with a specific peer</help> + </properties> + <command></command> + </tagNode> + </children> + <command></command> + </tagNode> + --> + <node name="detail"> + <properties> + <help>Show Verbose Detail on all active IPsec Security Associations (SA)</help> + </properties> + <command>if systemctl is-active --quiet strongswan ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_sa_detail ; else echo "IPsec process not running" ; fi</command> + </node> + </children> + <command>if systemctl is-active --quiet strongswan ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_sa ; else echo "IPsec process not running" ; fi</command> + </node> + <node name="state"> + <properties> + <help>Show the in-kernel crypto state</help> + </properties> + <command>sudo ip xfrm state list</command> + </node> + <node name="status"> + <properties> + <help>Show status of IPsec process</help> + </properties> + <command>if systemctl is-active --quiet strongswan >/dev/null ; then echo -e "IPsec Process Running: $(pgrep charon)\n$(sudo /usr/sbin/ipsec status)" ; else echo "IPsec process not running" ; fi</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/vrrp.xml.in b/op-mode-definitions/vrrp.xml.in new file mode 100644 index 0000000..158e709 --- /dev/null +++ b/op-mode-definitions/vrrp.xml.in @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="vrrp"> + <properties> + <help>Show VRRP (Virtual Router Redundancy Protocol) information</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/vrrp.py --summary</command> + <children> + <node name="statistics"> + <properties> + <help>Show VRRP statistics</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/vrrp.py --statistics</command> + </node> + <node name="detail"> + <properties> + <help>Show detailed VRRP state information</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/vrrp.py --data</command> + </node> + </children> + </node> + </children> + </node> + <node name="restart"> + <children> + <node name="vrrp"> + <properties> + <help>Restart VRRP (Virtual Router Redundancy Protocol) process</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name vrrp</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/wake-on-lan.xml.in b/op-mode-definitions/wake-on-lan.xml.in new file mode 100644 index 0000000..d4589c8 --- /dev/null +++ b/op-mode-definitions/wake-on-lan.xml.in @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="execute"> + <children> + <node name="wake-on-lan"> + <properties> + <help>Send Wake-On-LAN (WOL) Magic Packet</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Interface where the station is connected</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + <tagNode name="host"> + <properties> + <help>Station (MAC) address to wake up</help> + </properties> + <command>sudo /usr/sbin/etherwake -i "$4" "$6"</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/webproxy.xml.in b/op-mode-definitions/webproxy.xml.in new file mode 100644 index 0000000..ba13907 --- /dev/null +++ b/op-mode-definitions/webproxy.xml.in @@ -0,0 +1,106 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="monitor"> + <children> + <node name="log"> + <children> + <node name="webproxy"> + <properties> + <help>Monitor last lines of Webproxy log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit squid.service</command> + <children> + <leafNode name="access-log"> + <properties> + <help>Monitor the last lines of the Webproxy access log</help> + </properties> + <command>if [ -f /var/log/squid/access.log ]; then sudo tail --follow=name /var/log/squid/access.log; else echo "WebProxy access-log does not exist"; fi</command> + </leafNode> + <leafNode name="cache-log"> + <properties> + <help>Monitor the last lines of the Webproxy cache log</help> + </properties> + <command>if [ -f /var/log/squid/cache.log ]; then sudo tail --follow=name /var/log/squid/cache.log; else echo "WebProxy cache-log does not exist"; fi</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> + <node name="restart"> + <children> + <node name="webproxy"> + <properties> + <help>Restart WebProxy service</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name webproxy</command> + </node> + </children> + </node> + <node name="show"> + <children> + <node name="webproxy"> + <properties> + <help>Show WebProxy information</help> + </properties> + <children> + <!-- missing blacklist command --> + <node name="blacklist"> + <properties> + <help>Show webproxy blacklist information</help> + </properties> + <children> + <node name="categories"> + <properties> + <help>Show webproxy blacklist categories</help> + </properties> + <command>${vyos_completion_dir}/list_webproxy_category.sh</command> + </node> + </children> + </node> + <node name="log"> + <properties> + <help>Show contents of WebProxy access log</help> + </properties> + <command>if [ -e /var/log/squid/access.log ]; then sudo less $_vyatta_less_options --prompt="file %i of %m, page %dt of %D" -- `printf "%s\n" /var/log/squid/access.log* | sort -nr`; else echo "No WebProxy log"; fi</command> + </node> + <node name="update-log"> + <properties> + <help>Show update log for url-filter database</help> + </properties> + <command>if [ -e /opt/vyatta/etc/config/url-filtering/squidguard/updatestatus ]; then cat /opt/vyatta/etc/config/url-filtering/squidguard/updatestatus; else echo "Update log not found"; fi</command> + </node> + </children> + </node> + </children> + </node> + <node name="update"> + <children> + <node name="webproxy"> + <properties> + <help>Update WebProxy</help> + </properties> + <children> + <node name="blacklists"> + <properties> + <help>Update the webproxy blacklist database</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/webproxy_update_blacklist.sh --update-blacklist</command> + <children> + <tagNode name="vrf"> + <properties> + <help>Update webproxy blacklist database via specified VRF</help> + <completionHelp> + <path>vrf name</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/webproxy_update_blacklist.sh --update-blacklist --vrf "${5}" </command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 0000000..2d614e7 --- /dev/null +++ b/python/setup.py @@ -0,0 +1,32 @@ +import os +from setuptools import setup + +def packages(directory): + return [ + _[0].replace('/','.') + for _ in os.walk(directory) + if os.path.isfile(os.path.join(_[0], '__init__.py')) + ] + +setup( + name = "vyos", + version = "1.3.0", + author = "VyOS maintainers and contributors", + author_email = "maintainers@vyos.net", + description = ("VyOS configuration libraries."), + license = "LGPLv2+", + keywords = "vyos", + url = "http://www.vyos.io", + packages = packages('vyos'), + long_description="VyOS configuration libraries", + classifiers=[ + "Development Status :: 4 - Beta", + "Topic :: Utilities", + "License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)", + ], + entry_points={ + "console_scripts": [ + "config-mgmt = vyos.config_mgmt:run", + ], + }, +) diff --git a/python/vyos/__init__.py b/python/vyos/__init__.py new file mode 100644 index 0000000..e3e14fd --- /dev/null +++ b/python/vyos/__init__.py @@ -0,0 +1 @@ +from .base import ConfigError diff --git a/python/vyos/accel_ppp.py b/python/vyos/accel_ppp.py new file mode 100644 index 0000000..bae695f --- /dev/null +++ b/python/vyos/accel_ppp.py @@ -0,0 +1,70 @@ +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from vyos.utils.process import rc_cmd + +def get_server_statistics(accel_statistics, pattern, sep=':') -> dict: + import re + + stat_dict = {'sessions': {}} + + cpu = re.search(r'cpu(.*)', accel_statistics).group(0) + # Find all lines with pattern, for example 'sstp:' + data = re.search(rf'{pattern}(.*)', accel_statistics, re.DOTALL).group(0) + session_starting = re.search(r'starting(.*)', data).group(0) + session_active = re.search(r'active(.*)', data).group(0) + + for entry in {cpu, session_starting, session_active}: + if sep in entry: + key, value = entry.split(sep) + if key in ['starting', 'active', 'finishing']: + stat_dict['sessions'][key] = value.strip() + continue + if key == 'cpu': + stat_dict['cpu_load_percentage'] = int(re.sub(r'%', '', value.strip())) + continue + stat_dict[key] = value.strip() + return stat_dict + + +def accel_cmd(port: int, command: str) -> str: + _, output = rc_cmd(f'/usr/bin/accel-cmd -p{port} {command}') + return output + + +def accel_out_parse(accel_output: list[str]) -> list[dict[str, str]]: + """ Parse accel-cmd show sessions output """ + data_list: list[dict[str, str]] = list() + field_names: list[str] = list() + + field_names_unstripped: list[str] = accel_output.pop(0).split('|') + for field_name in field_names_unstripped: + field_names.append(field_name.strip()) + + while accel_output: + if '|' not in accel_output[0]: + accel_output.pop(0) + continue + + current_item: list[str] = accel_output.pop(0).split('|') + item_dict: dict[str, str] = {} + + for field_index in range(len(current_item)): + field_name: str = field_names[field_index] + field_value: str = current_item[field_index].strip() + item_dict[field_name] = field_value + + data_list.append(item_dict) + + return data_list diff --git a/python/vyos/accel_ppp_util.py b/python/vyos/accel_ppp_util.py new file mode 100644 index 0000000..ae75e66 --- /dev/null +++ b/python/vyos/accel_ppp_util.py @@ -0,0 +1,238 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +# The sole purpose of this module is to hold common functions used in +# all kinds of implementations to verify the CLI configuration. +# It is started by migrating the interfaces to the new get_config_dict() +# approach which will lead to a lot of code that can be reused. + +# NOTE: imports should be as local as possible to the function which +# makes use of it! + +from vyos import ConfigError +from vyos.base import Warning +from vyos.utils.dict import dict_search + +def get_pools_in_order(data: dict) -> list: + """Return a list of dictionaries representing pool data in the order + in which they should be allocated. Pool must be defined before we can + use it with 'next-pool' option. + + Args: + data: A dictionary of pool data, where the keys are pool names and the + values are dictionaries containing the 'subnet' key and the optional + 'next_pool' key. + + Returns: + list: A list of dictionaries + + Raises: + ValueError: If a 'next_pool' key references a pool name that + has not been defined. + ValueError: If a circular reference is found in the 'next_pool' keys. + + Example: + config_data = { + ... 'first-pool': { + ... 'next_pool': 'second-pool', + ... 'subnet': '192.0.2.0/25' + ... }, + ... 'second-pool': { + ... 'next_pool': 'third-pool', + ... 'subnet': '203.0.113.0/25' + ... }, + ... 'third-pool': { + ... 'subnet': '198.51.100.0/24' + ... }, + ... 'foo': { + ... 'subnet': '100.64.0.0/24', + ... 'next_pool': 'second-pool' + ... } + ... } + + % get_pools_in_order(config_data) + [{'third-pool': {'subnet': '198.51.100.0/24'}}, + {'second-pool': {'next_pool': 'third-pool', 'subnet': '203.0.113.0/25'}}, + {'first-pool': {'next_pool': 'second-pool', 'subnet': '192.0.2.0/25'}}, + {'foo': {'next_pool': 'second-pool', 'subnet': '100.64.0.0/24'}}] + """ + pools = [] + unresolved_pools = {} + + for pool, pool_config in data.items(): + if "next_pool" not in pool_config or not pool_config["next_pool"]: + pools.insert(0, {pool: pool_config}) + else: + unresolved_pools[pool] = pool_config + + while unresolved_pools: + resolved_pools = [] + + for pool, pool_config in unresolved_pools.items(): + next_pool_name = pool_config["next_pool"] + + if any(p for p in pools if next_pool_name in p): + index = next( + (i for i, p in enumerate(pools) if next_pool_name in p), None + ) + pools.insert(index + 1, {pool: pool_config}) + resolved_pools.append(pool) + elif next_pool_name in unresolved_pools: + # next pool not yet resolved + pass + else: + raise ConfigError( + f"Pool '{next_pool_name}' not defined in configuration data" + ) + + if not resolved_pools: + raise ConfigError("Circular reference in configuration data") + + for pool in resolved_pools: + unresolved_pools.pop(pool) + + return pools + + +def verify_accel_ppp_name_servers(config): + if "name_server_ipv4" in config: + if len(config["name_server_ipv4"]) > 2: + raise ConfigError( + "Not more then two IPv4 DNS name-servers " "can be configured" + ) + if "name_server_ipv6" in config: + if len(config["name_server_ipv6"]) > 3: + raise ConfigError( + "Not more then three IPv6 DNS name-servers " "can be configured" + ) + + +def verify_accel_ppp_wins_servers(config): + if 'wins_server' in config and len(config['wins_server']) > 2: + raise ConfigError( + 'Not more then two WINS name-servers can be configured') + + +def verify_accel_ppp_authentication(config, local_users=True): + """ + Common helper function which must be used by all Accel-PPP services based + on get_config_dict() + """ + # vertify auth settings + if local_users and dict_search("authentication.mode", config) == "local": + if ( + dict_search("authentication.local_users", config) is None + or dict_search("authentication.local_users", config) == {} + ): + raise ConfigError( + "Authentication mode local requires local users to be configured!" + ) + + for user in dict_search("authentication.local_users.username", config): + user_config = config["authentication"]["local_users"]["username"][user] + + if "password" not in user_config: + raise ConfigError(f'Password required for local user "{user}"') + + if "rate_limit" in user_config: + # if up/download is set, check that both have a value + if not {"upload", "download"} <= set(user_config["rate_limit"]): + raise ConfigError( + f'User "{user}" has rate-limit configured for only one ' + "direction but both upload and download must be given!" + ) + + elif dict_search("authentication.mode", config) == "radius": + if not dict_search("authentication.radius.server", config): + raise ConfigError("RADIUS authentication requires at least one server") + + for server in dict_search("authentication.radius.server", config): + radius_config = config["authentication"]["radius"]["server"][server] + if "key" not in radius_config: + raise ConfigError(f'Missing RADIUS secret key for server "{server}"') + + if dict_search("server_type", config) == 'ipoe' and dict_search( + "authentication.mode", config) == "local": + if not dict_search("authentication.interface", config): + raise ConfigError( + "Authentication mode local requires authentication interface to be configured!" + ) + for interface in dict_search("authentication.interface", config): + user_config = config["authentication"]["interface"][interface] + if "mac" not in user_config: + raise ConfigError( + f'Users MAC addreses are not configured for interface "{interface}"') + + if dict_search('authentication.radius.dynamic_author.server', config): + if not dict_search('authentication.radius.dynamic_author.key', config): + raise ConfigError('DAE/CoA server key required!') + + +def verify_accel_ppp_ip_pool(vpn_config): + """ + Common helper function which must be used by Accel-PPP + services (pptp, l2tp, sstp, pppoe) to verify client-ip-pool + and client-ipv6-pool + """ + if dict_search("client_ip_pool", vpn_config): + for pool_name, pool_config in vpn_config["client_ip_pool"].items(): + next_pool = dict_search(f"next_pool", pool_config) + if next_pool: + if next_pool not in vpn_config["client_ip_pool"]: + raise ConfigError( + f'Next pool "{next_pool}" does not exist') + if not dict_search(f"range", pool_config): + raise ConfigError( + f'Pool "{pool_name}" does not contain range but next-pool exists' + ) + if not dict_search("gateway_address", vpn_config): + Warning("IPv4 Server requires gateway-address to be configured!") + + default_pool = dict_search("default_pool", vpn_config) + if default_pool: + if not dict_search('client_ip_pool', + vpn_config) or default_pool not in dict_search( + 'client_ip_pool', vpn_config): + raise ConfigError(f'Default pool "{default_pool}" does not exists') + + if 'client_ipv6_pool' in vpn_config: + for ipv6_pool, ipv6_pool_config in vpn_config['client_ipv6_pool'].items(): + if 'delegate' in ipv6_pool_config and 'prefix' not in ipv6_pool_config: + raise ConfigError( + f'IPv6 delegate-prefix requires IPv6 prefix to be configured in "{ipv6_pool}"!') + + if dict_search('authentication.mode', vpn_config) in ['local', 'noauth']: + if not dict_search('client_ip_pool', vpn_config) and not dict_search( + 'client_ipv6_pool', vpn_config): + if dict_search('server_type', vpn_config) == 'ipoe': + if 'interface' in vpn_config: + for interface, interface_config in vpn_config['interface'].items(): + if dict_search('client_subnet', interface_config): + break + else: + raise ConfigError( + 'Local auth and noauth mode requires local client-ip-pool \ + or client-ipv6-pool or client-subnet to be configured!') + else: + raise ConfigError( + "Local auth mode requires local client-ip-pool \ + or client-ipv6-pool to be configured!") + + if dict_search('client_ip_pool', vpn_config) and not dict_search( + 'default_pool', vpn_config): + Warning("'default-pool' is not defined") + if dict_search('client_ipv6_pool', vpn_config) and not dict_search( + 'default_ipv6_pool', vpn_config): + Warning("'default-ipv6-pool' is not defined") diff --git a/python/vyos/airbag.py b/python/vyos/airbag.py new file mode 100644 index 0000000..3c7a144 --- /dev/null +++ b/python/vyos/airbag.py @@ -0,0 +1,173 @@ +# Copyright 2019-2020 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import sys +from datetime import datetime + +from vyos import debug +from vyos.logger import syslog +from vyos.version import get_full_version_data + + +def enable(log=True): + if log: + _intercepting_logger() + _intercepting_exceptions() + + +_noteworthy = [] + + +def noteworthy(msg): + """ + noteworthy can be use to take note things which we may not want to + report to the user may but be worth including in bug report + if something goes wrong later on + """ + _noteworthy.append(msg) + + +# emulate a file object +class _IO(object): + def __init__(self, std, log): + self.std = std + self.log = log + + def write(self, message): + self.std.write(message) + for line in message.split('\n'): + s = line.rstrip() + if s: + self.log(s) + + def flush(self): + self.std.flush() + + def close(self): + pass + + +# The function which will be used to report information +# to users when an exception is unhandled +def bug_report(dtype, value, trace): + from traceback import format_exception + + sys.stdout.flush() + sys.stderr.flush() + + information = get_full_version_data() + trace = '\n'.join(format_exception(dtype, value, trace)).replace('\n\n','\n') + note = '' + if _noteworthy: + note = 'noteworthy:\n' + note += '\n'.join(_noteworthy) + + information.update({ + 'date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'trace': trace, + 'instructions': INSTRUCTIONS, + 'note': note, + }) + + sys.stdout.write(INTRO.format(**information)) + sys.stdout.flush() + + sys.stderr.write(FAULT.format(**information)) + sys.stderr.flush() + + +# define an exception handler to be run when an exception +# reach the end of __main__ and was not intercepted +def _intercepter(dtype, value, trace): + bug_report(dtype, value, trace) + if debug.enabled('developer'): + import pdb + pdb.pm() + + +def _intercepting_logger(_singleton=[False]): + skip = _singleton.pop() + _singleton.append(True) + if skip: + return + + # log to syslog any message sent to stderr + sys.stderr = _IO(sys.stderr, syslog.critical) + + +# lists as default arguments in function is normally dangerous +# as they will keep any modification performed, unless this is +# what you want to do (in that case to only run the code once) +def _intercepting_exceptions(_singleton=[False]): + skip = _singleton.pop() + _singleton.append(True) + if skip: + return + + # install the handler to replace the default behaviour + # which just prints the exception trace on screen + sys.excepthook = _intercepter + + +# Messages to print +# if the key before the value has not time, syslog takes that as the source of the message + +FAULT = """\ +Report time: {date} +Image version: VyOS {version} +Release train: {release_train} + +Built by: {built_by} +Built on: {built_on} +Build UUID: {build_uuid} +Build commit ID: {build_git} + +Architecture: {system_arch} +Boot via: {boot_via} +System type: {system_type} + +Hardware vendor: {hardware_vendor} +Hardware model: {hardware_model} +Hardware S/N: {hardware_serial} +Hardware UUID: {hardware_uuid} + +{trace} +{note} +""" + +INTRO = """\ +VyOS had an issue completing a command. + +We are sorry that you encountered a problem while using VyOS. +There are a few things you can do to help us (and yourself): +{instructions} + +When reporting problems, please include as much information as possible: +- do not obfuscate any data (feel free to contact us privately if your + business policy requires it) +- and include all the information presented below + +""" + +INSTRUCTIONS = """\ +- Contact us using the online help desk if you have a subscription: + https://support.vyos.io/ +- Make sure you are running the latest version of VyOS available at: + https://vyos.net/get/ +- Consult the community forum to see how to handle this issue: + https://forum.vyos.io +- Join us on Slack where our users exchange help and advice: + https://vyos.slack.com +""".strip() diff --git a/python/vyos/base.py b/python/vyos/base.py new file mode 100644 index 0000000..ca96d96 --- /dev/null +++ b/python/vyos/base.py @@ -0,0 +1,72 @@ +# Copyright 2018-2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from textwrap import fill + + +class BaseWarning: + def __init__(self, header, message, **kwargs): + self.message = message + self.kwargs = kwargs + if 'width' not in kwargs: + self.width = 72 + if 'initial_indent' in kwargs: + del self.kwargs['initial_indent'] + if 'subsequent_indent' in kwargs: + del self.kwargs['subsequent_indent'] + self.textinitindent = header + self.standardindent = '' + + def print(self): + messages = self.message.split('\n') + isfirstmessage = True + initial_indent = self.textinitindent + print('') + for mes in messages: + mes = fill(mes, initial_indent=initial_indent, + subsequent_indent=self.standardindent, **self.kwargs) + if isfirstmessage: + isfirstmessage = False + initial_indent = self.standardindent + print(f'{mes}') + print('', flush=True) + + +class Warning(): + def __init__(self, message, **kwargs): + self.BaseWarn = BaseWarning('WARNING: ', message, **kwargs) + self.BaseWarn.print() + + +class DeprecationWarning(): + def __init__(self, message, **kwargs): + # Reformat the message and trim it to 72 characters in length + self.BaseWarn = BaseWarning('DEPRECATION WARNING: ', message, **kwargs) + self.BaseWarn.print() + + +class ConfigError(Exception): + def __init__(self, message): + # Reformat the message and trim it to 72 characters in length + message = fill(message, width=72) + # Call the base class constructor with the parameters it needs + super().__init__(message) + +class MigrationError(Exception): + def __init__(self, message): + # Reformat the message and trim it to 72 characters in length + message = fill(message, width=72) + # Call the base class constructor with the parameters it needs + super().__init__(message) diff --git a/python/vyos/certbot_util.py b/python/vyos/certbot_util.py new file mode 100644 index 0000000..bcb7838 --- /dev/null +++ b/python/vyos/certbot_util.py @@ -0,0 +1,58 @@ +# certbot_util -- adaptation of certbot_nginx name matching functions for VyOS +# https://github.com/certbot/certbot/blob/master/LICENSE.txt + +from certbot_nginx._internal import parser + +NAME_RANK = 0 +START_WILDCARD_RANK = 1 +END_WILDCARD_RANK = 2 +REGEX_RANK = 3 + +def _rank_matches_by_name(server_block_list, target_name): + """Returns a ranked list of server_blocks that match target_name. + Adapted from the function of the same name in + certbot_nginx.NginxConfigurator + """ + matches = [] + for server_block in server_block_list: + name_type, name = parser.get_best_match(target_name, + server_block['name']) + if name_type == 'exact': + matches.append({'vhost': server_block, + 'name': name, + 'rank': NAME_RANK}) + elif name_type == 'wildcard_start': + matches.append({'vhost': server_block, + 'name': name, + 'rank': START_WILDCARD_RANK}) + elif name_type == 'wildcard_end': + matches.append({'vhost': server_block, + 'name': name, + 'rank': END_WILDCARD_RANK}) + elif name_type == 'regex': + matches.append({'vhost': server_block, + 'name': name, + 'rank': REGEX_RANK}) + + return sorted(matches, key=lambda x: x['rank']) + +def _select_best_name_match(matches): + """Returns the best name match of a ranked list of server_blocks. + Adapted from the function of the same name in + certbot_nginx.NginxConfigurator + """ + if not matches: + return None + elif matches[0]['rank'] in [START_WILDCARD_RANK, END_WILDCARD_RANK]: + rank = matches[0]['rank'] + wildcards = [x for x in matches if x['rank'] == rank] + return max(wildcards, key=lambda x: len(x['name']))['vhost'] + else: + return matches[0]['vhost'] + +def choose_server_block(server_block_list, target_name): + matches = _rank_matches_by_name(server_block_list, target_name) + server_blocks = [x for x in [_select_best_name_match(matches)] + if x is not None] + return server_blocks + diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py new file mode 100644 index 0000000..9421553 --- /dev/null +++ b/python/vyos/component_version.py @@ -0,0 +1,204 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +""" +Functions for reading/writing component versions. + +The config file version string has the following form: + +VyOS 1.3/1.4: + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.0 + +VyOS 1.2: + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pppoe-server@2:pptp@1:qos@1:quagga@7:snmp@1:ssh@1:system@10:vrrp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.8 */ + +""" + +import os +import re +import sys +from dataclasses import dataclass +from dataclasses import replace +from typing import Optional + +from vyos.xml_ref import component_version +from vyos.utils.file import write_file +from vyos.version import get_version +from vyos.defaults import directories + +DEFAULT_CONFIG_PATH = os.path.join(directories['config'], 'config.boot') + +REGEX_WARN_VYOS = r'(// Warning: Do not remove the following line.)' +REGEX_WARN_VYATTA = r'(/\* Warning: Do not remove the following line. \*/)' +REGEX_COMPONENT_VERSION_VYOS = r'// vyos-config-version:\s+"([\w@:-]+)"\s*' +REGEX_COMPONENT_VERSION_VYATTA = r'/\* === vyatta-config-version:\s+"([\w@:-]+)"\s+=== \*/' +REGEX_RELEASE_VERSION_VYOS = r'// Release version:\s+(\S*)\s*' +REGEX_RELEASE_VERSION_VYATTA = r'/\* Release version:\s+(\S*)\s*\*/' + +CONFIG_FILE_VERSION = """\ +// Warning: Do not remove the following line. +// vyos-config-version: "{}" +// Release version: {} +""" + +warn_filter_vyos = re.compile(REGEX_WARN_VYOS) +warn_filter_vyatta = re.compile(REGEX_WARN_VYATTA) + +regex_filter = { 'vyos': dict(zip(['component', 'release'], + [re.compile(REGEX_COMPONENT_VERSION_VYOS), + re.compile(REGEX_RELEASE_VERSION_VYOS)])), + 'vyatta': dict(zip(['component', 'release'], + [re.compile(REGEX_COMPONENT_VERSION_VYATTA), + re.compile(REGEX_RELEASE_VERSION_VYATTA)])) } + +@dataclass +class VersionInfo: + component: Optional[dict[str,int]] = None + release: str = get_version() + vintage: str = 'vyos' + config_body: Optional[str] = None + footer_lines: Optional[list[str]] = None + + def component_is_none(self) -> bool: + return bool(self.component is None) + + def config_body_is_none(self) -> bool: + return bool(self.config_body is None) + + def update_footer(self): + f = CONFIG_FILE_VERSION.format(component_to_string(self.component), + self.release) + self.footer_lines = f.splitlines() + + def update_syntax(self): + self.vintage = 'vyos' + self.update_footer() + + def update_release(self, release: str): + self.release = release + self.update_footer() + + def update_component(self, key: str, version: int): + if not isinstance(version, int): + raise ValueError('version must be int') + if self.component is None: + self.component = {} + self.component[key] = version + self.component = dict(sorted(self.component.items(), key=lambda x: x[0])) + self.update_footer() + + def update_config_body(self, config_str: str): + self.config_body = config_str + + def write_string(self) -> str: + config_body = '' if self.config_body is None else self.config_body + footer_lines = [] if self.footer_lines is None else self.footer_lines + + return config_body + '\n' + '\n'.join(footer_lines) + '\n' + + def write(self, config_file): + string = self.write_string() + try: + write_file(config_file, string) + except Exception as e: + raise ValueError(e) from e + +def component_to_string(component: dict) -> str: + l = [f'{k}@{v}' for k, v in sorted(component.items(), key=lambda x: x[0])] + return ':'.join(l) + +def component_from_string(string: str) -> dict: + return {k: int(v) for k, v in re.findall(r'([\w,-]+)@(\d+)', string)} + +def version_info_from_file(config_file) -> VersionInfo: + """Return config file component and release version info.""" + version_info = VersionInfo() + try: + with open(config_file) as f: + config_str = f.read() + except OSError: + return None + + if len(parts := warn_filter_vyos.split(config_str)) > 1: + vintage = 'vyos' + elif len(parts := warn_filter_vyatta.split(config_str)) > 1: + vintage = 'vyatta' + else: + version_info.config_body = parts[0] if parts else None + return version_info + + version_info.vintage = vintage + version_info.config_body = parts[0] + version_lines = ''.join(parts[1:]).splitlines() + version_lines = [k for k in version_lines if k] + if len(version_lines) != 3: + raise ValueError(f'Malformed version strings: {version_lines}') + + m = regex_filter[vintage]['component'].match(version_lines[1]) + if not m: + raise ValueError(f'Malformed component string: {version_lines[1]}') + version_info.component = component_from_string(m.group(1)) + + m = regex_filter[vintage]['release'].match(version_lines[2]) + if not m: + raise ValueError(f'Malformed component string: {version_lines[2]}') + version_info.release = m.group(1) + + version_info.footer_lines = version_lines + + return version_info + +def version_info_from_system() -> VersionInfo: + """Return system component and release version info.""" + d = component_version() + sort_d = dict(sorted(d.items(), key=lambda x: x[0])) + version_info = VersionInfo( + component = sort_d, + release = get_version(), + vintage = 'vyos' + ) + + return version_info + +def version_info_copy(v: VersionInfo) -> VersionInfo: + """Make a copy of dataclass.""" + return replace(v) + +def version_info_prune_component(x: VersionInfo, y: VersionInfo) -> VersionInfo: + """In place pruning of component keys of x not in y.""" + if x.component is None or y.component is None: + return + x.component = { k: v for k,v in x.component.items() if k in y.component } + +def add_system_version(config_str: str = None, out_file: str = None): + """Wrap config string with system version and write to out_file. + + For convenience, calling with no argument will write system version + string to stdout, for use in bash scripts. + """ + version_info = version_info_from_system() + if config_str is not None: + version_info.update_config_body(config_str) + version_info.update_footer() + if out_file is not None: + version_info.write(out_file) + else: + sys.stdout.write(version_info.write_string()) diff --git a/python/vyos/compose_config.py b/python/vyos/compose_config.py new file mode 100644 index 0000000..79a8718 --- /dev/null +++ b/python/vyos/compose_config.py @@ -0,0 +1,88 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +"""This module allows iterating over function calls to modify an existing +config. +""" + +import traceback +from pathlib import Path +from typing import TypeAlias, Union, Callable + +from vyos.configtree import ConfigTree +from vyos.configtree import deep_copy as ct_deep_copy +from vyos.utils.system import load_as_module_source + +ConfigObj: TypeAlias = Union[str, ConfigTree] + +class ComposeConfigError(Exception): + """Raised when an error occurs modifying a config object. + """ + +class ComposeConfig: + """Apply function to config tree: for iteration over functions or files. + """ + def __init__(self, config_obj: ConfigObj, checkpoint_file=None): + if isinstance(config_obj, ConfigTree): + self.config_tree = config_obj + else: + self.config_tree = ConfigTree(config_obj) + + self.checkpoint = self.config_tree + self.checkpoint_file = checkpoint_file + + def apply_func(self, func: Callable): + """Apply the function to the config tree. + """ + if not callable(func): + raise ComposeConfigError(f'{func.__name__} is not callable') + + if self.checkpoint_file is not None: + self.checkpoint = ct_deep_copy(self.config_tree) + + try: + func(self.config_tree) + except Exception as e: + if self.checkpoint_file is not None: + self.config_tree = self.checkpoint + raise ComposeConfigError(e) from e + + def apply_file(self, func_file: str, func_name: str): + """Apply named function from file. + """ + try: + mod_name = Path(func_file).stem.replace('-', '_') + mod = load_as_module_source(mod_name, func_file) + func = getattr(mod, func_name) + except Exception as e: + raise ComposeConfigError(f'Error with {func_file}: {e}') from e + + try: + self.apply_func(func) + except ComposeConfigError as e: + msg = str(e) + tb = f'{traceback.format_exc()}' + raise ComposeConfigError(f'Error in {func_file}: {msg}\n{tb}') from e + + def to_string(self, with_version=False) -> str: + """Return the rendered config tree. + """ + return self.config_tree.to_string(no_version=not with_version) + + def write(self, config_file: str, with_version=False): + """Write the config tree to a file. + """ + config_str = self.to_string(with_version=with_version) + Path(config_file).write_text(config_str) diff --git a/python/vyos/config.py b/python/vyos/config.py new file mode 100644 index 0000000..1fab467 --- /dev/null +++ b/python/vyos/config.py @@ -0,0 +1,622 @@ +# Copyright 2017-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +""" +A library for reading VyOS running config data. + +This library is used internally by all config scripts of VyOS, +but its API should be considered stable and safe to use +in user scripts. + +Note that this module will not work outside VyOS. + +Node taxonomy +############# + +There are multiple types of config tree nodes in VyOS, each requires +its own set of operations. + +*Leaf nodes* (such as "address" in interfaces) can have values, but cannot +have children. +Leaf nodes can have one value, multiple values, or no values at all. + +For example, "system host-name" is a single-value leaf node, +"system name-server" is a multi-value leaf node (commonly abbreviated "multi node"), +and "system ip disable-forwarding" is a valueless leaf node. + +Non-leaf nodes cannot have values, but they can have child nodes. They are divided into +two classes depending on whether the names of their children are fixed or not. +For example, under "system", the names of all valid child nodes are predefined +("login", "name-server" etc.). + +To the contrary, children of the "system task-scheduler task" node can have arbitrary names. +Such nodes are called *tag nodes*. This terminology is confusing but we keep using it for lack +of a better word. No one remembers if the "tag" in "task Foo" is "task" or "Foo", +but the distinction is irrelevant in practice. + +Configuration modes +################### + +VyOS has two distinct modes: operational mode and configuration mode. When a user logins, +the CLI is in the operational mode. In this mode, only the running (effective) config is accessible for reading. + +When a user enters the "configure" command, a configuration session is setup. Every config session +has its *proposed* (or *session*) config built on top of the current running config. When changes are commited, if commit succeeds, +the proposed config is merged into the running config. + +In configuration mode, "base" functions like `exists`, `return_value` return values from the session config, +while functions prefixed "effective" return values from the running config. + +In operational mode, all functions return values from the running config. +""" + +import re +import json +from typing import Union + +import vyos.configtree +from vyos.xml_ref import multi_to_list +from vyos.xml_ref import from_source +from vyos.xml_ref import ext_dict_merge +from vyos.xml_ref import relative_defaults +from vyos.utils.dict import get_sub_dict +from vyos.utils.dict import mangle_dict_keys +from vyos.configsource import ConfigSource +from vyos.configsource import ConfigSourceSession + +class ConfigDict(dict): + _from_defaults = {} + _dict_kwargs = {} + def from_defaults(self, path: list[str]) -> bool: + return from_source(self._from_defaults, path) + @property + def kwargs(self) -> dict: + return self._dict_kwargs + +def config_dict_merge(src: dict, dest: Union[dict, ConfigDict]) -> ConfigDict: + if not isinstance(dest, ConfigDict): + dest = ConfigDict(dest) + return ext_dict_merge(src, dest) + +def config_dict_mangle_acme(name, cli_dict): + """ + Load CLI PKI dictionary and if an ACME certificate is used, load it's content + and place it into the CLI dictionary as it would be a "regular" CLI PKI based + certificate with private key + """ + from vyos.base import ConfigError + from vyos.defaults import directories + from vyos.utils.file import read_file + from vyos.pki import encode_certificate + from vyos.pki import encode_private_key + from vyos.pki import load_certificate + from vyos.pki import load_private_key + + try: + vyos_certbot_dir = directories['certbot'] + + if 'acme' in cli_dict: + tmp = read_file(f'{vyos_certbot_dir}/live/{name}/cert.pem') + tmp = load_certificate(tmp, wrap_tags=False) + cert_base64 = "".join(encode_certificate(tmp).strip().split("\n")[1:-1]) + + tmp = read_file(f'{vyos_certbot_dir}/live/{name}/privkey.pem') + tmp = load_private_key(tmp, wrap_tags=False) + key_base64 = "".join(encode_private_key(tmp).strip().split("\n")[1:-1]) + # install ACME based PEM keys into "regular" CLI config keys + cli_dict.update({'certificate' : cert_base64, 'private' : {'key' : key_base64}}) + except: + raise ConfigError(f'Unable to load ACME certificates for "{name}"!') + + return cli_dict + +class Config(object): + """ + The class of config access objects. + + Internally, in the current implementation, this object is *almost* stateless, + the only state it keeps is relative *config path* for convenient access to config + subtrees. + """ + def __init__(self, session_env=None, config_source=None): + if config_source is None: + self._config_source = ConfigSourceSession(session_env) + else: + if not isinstance(config_source, ConfigSource): + raise TypeError("config_source not of type ConfigSource") + self._config_source = config_source + + self._level = [] + self._dict_cache = {} + self.dependency_list = [] + (self._running_config, + self._session_config) = self._config_source.get_configtree_tuple() + + def get_config_tree(self, effective=False): + if effective: + return self._running_config + return self._session_config + + def _make_path(self, path): + # Backwards-compatibility stuff: original implementation used string paths + # libvyosconfig paths are lists, but since node names cannot contain whitespace, + # splitting at whitespace is reasonably safe. + # It may cause problems with exists() when it's used for checking values, + # since values may contain whitespace. + if isinstance(path, str): + path = re.split(r'\s+', path) + elif isinstance(path, list): + pass + else: + raise TypeError("Path must be a whitespace-separated string or a list") + return (self._level + path) + + def set_level(self, path): + """ + Set the *edit level*, that is, a relative config tree path. + Once set, all operations will be relative to this path, + for example, after ``set_level("system")``, calling + ``exists("name-server")`` is equivalent to calling + ``exists("system name-server"`` without ``set_level``. + + Args: + path (str|list): relative config path + """ + # Make sure there's always a space between default path (level) + # and path supplied as method argument + # XXX: for small strings in-place concatenation is not a problem + if isinstance(path, str): + if path: + self._level = re.split(r'\s+', path) + else: + self._level = [] + elif isinstance(path, list): + self._level = path.copy() + else: + raise TypeError("Level path must be either a whitespace-separated string or a list") + + def get_level(self): + """ + Gets the current edit level. + + Returns: + str: current edit level + """ + return(self._level.copy()) + + def exists(self, path): + """ + Checks if a node or value with given path exists in the proposed config. + + Args: + path (str): Configuration tree path + + Returns: + True if node or value exists in the proposed config, False otherwise + + Note: + This function should not be used outside of configuration sessions. + In operational mode scripts, use ``exists_effective``. + """ + if self._session_config is None: + return False + + # Assume the path is a node path first + if self._session_config.exists(self._make_path(path)): + return True + else: + # If that check fails, it may mean the path has a value at the end. + # libvyosconfig exists() works only for _nodes_, not _values_ + # libvyattacfg also worked for values, so we emulate that case here + if isinstance(path, str): + path = re.split(r'\s+', path) + path_without_value = path[:-1] + try: + # return_values() is safe to use with single-value nodes, + # it simply returns a single-item list in that case. + values = self._session_config.return_values(self._make_path(path_without_value)) + + # If we got this far, the node does exist and has values, + # so we need to check if it has the value in question among its values. + return (path[-1] in values) + except vyos.configtree.ConfigTreeError: + # Even the parent node doesn't exist at all + return False + + def session_changed(self): + """ + Returns: + True if the config session has uncommited changes, False otherwise. + """ + return self._config_source.session_changed() + + def in_session(self): + """ + Returns: + True if called from a configuration session, False otherwise. + """ + return self._config_source.in_session() + + def show_config(self, path=[], default=None, effective=False): + """ + Args: + path (str list): Configuration tree path, or empty + default (str): Default value to return + + Returns: + str: working configuration + """ + return self._config_source.show_config(path, default, effective) + + def get_cached_root_dict(self, effective=False): + cached = self._dict_cache.get(effective, {}) + if cached: + return cached + + if effective: + config = self._running_config + else: + config = self._session_config + + if config: + config_dict = json.loads(config.to_json()) + else: + config_dict = {} + + self._dict_cache[effective] = config_dict + + return config_dict + + def verify_mangling(self, key_mangling): + if not (isinstance(key_mangling, tuple) and \ + (len(key_mangling) == 2) and \ + isinstance(key_mangling[0], str) and \ + isinstance(key_mangling[1], str)): + raise ValueError("key_mangling must be a tuple of two strings") + + def get_config_dict(self, path=[], effective=False, key_mangling=None, + get_first_key=False, no_multi_convert=False, + no_tag_node_value_mangle=False, + with_defaults=False, + with_recursive_defaults=False, + with_pki=False): + """ + Args: + path (str list): Configuration tree path, can be empty + effective=False: effective or session config + key_mangling=None: mangle dict keys according to regex and replacement + get_first_key=False: if k = path[:-1], return sub-dict d[k] instead of {k: d[k]} + no_multi_convert=False: if convert, return single value of multi node as list + + Returns: a dict representation of the config under path + """ + kwargs = locals().copy() + del kwargs['self'] + del kwargs['no_multi_convert'] + del kwargs['with_defaults'] + del kwargs['with_recursive_defaults'] + del kwargs['with_pki'] + + lpath = self._make_path(path) + root_dict = self.get_cached_root_dict(effective) + conf_dict = get_sub_dict(root_dict, lpath, get_first_key=get_first_key) + + rpath = lpath if get_first_key else lpath[:-1] + + if not no_multi_convert: + conf_dict = multi_to_list(rpath, conf_dict) + + if key_mangling is not None: + self.verify_mangling(key_mangling) + conf_dict = mangle_dict_keys(conf_dict, + key_mangling[0], key_mangling[1], + abs_path=rpath, + no_tag_node_value_mangle=no_tag_node_value_mangle) + + if with_defaults or with_recursive_defaults: + defaults = self.get_config_defaults(**kwargs, + recursive=with_recursive_defaults) + conf_dict = config_dict_merge(defaults, conf_dict) + else: + conf_dict = ConfigDict(conf_dict) + + if with_pki and conf_dict: + pki_dict = self.get_config_dict(['pki'], key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True) + if pki_dict: + if 'certificate' in pki_dict: + for certificate in pki_dict['certificate']: + pki_dict['certificate'][certificate] = config_dict_mangle_acme( + certificate, pki_dict['certificate'][certificate]) + + conf_dict['pki'] = pki_dict + + interfaces_root = root_dict.get('interfaces', {}) + setattr(conf_dict, 'interfaces_root', interfaces_root) + + # save optional args for a call to get_config_defaults + setattr(conf_dict, '_dict_kwargs', kwargs) + + return conf_dict + + def get_config_defaults(self, path=[], effective=False, key_mangling=None, + no_tag_node_value_mangle=False, get_first_key=False, + recursive=False) -> dict: + lpath = self._make_path(path) + root_dict = self.get_cached_root_dict(effective) + conf_dict = get_sub_dict(root_dict, lpath, get_first_key) + + defaults = relative_defaults(lpath, conf_dict, + get_first_key=get_first_key, + recursive=recursive) + + rpath = lpath if get_first_key else lpath[:-1] + + if key_mangling is not None: + self.verify_mangling(key_mangling) + defaults = mangle_dict_keys(defaults, + key_mangling[0], key_mangling[1], + abs_path=rpath, + no_tag_node_value_mangle=no_tag_node_value_mangle) + + return defaults + + def merge_defaults(self, config_dict: ConfigDict, recursive=False): + if not isinstance(config_dict, ConfigDict): + raise TypeError('argument is not of type ConfigDict') + if not config_dict.kwargs: + raise ValueError('argument missing metadata') + + args = config_dict.kwargs + d = self.get_config_defaults(**args, recursive=recursive) + config_dict = config_dict_merge(d, config_dict) + return config_dict + + def is_multi(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node can have multiple values, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ + self._config_source.set_level(self.get_level) + return self._config_source.is_multi(path) + + def is_tag(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node is a tag node, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ + self._config_source.set_level(self.get_level) + return self._config_source.is_tag(path) + + def is_leaf(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node is a leaf node, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ + self._config_source.set_level(self.get_level) + return self._config_source.is_leaf(path) + + def return_value(self, path, default=None): + """ + Retrieve a value of single-value leaf node in the running or proposed config + + Args: + path (str): Configuration tree path + default (str): Default value to return if node does not exist + + Returns: + str: Node value, if it has any + None: if node is valueless *or* if it doesn't exist + + Note: + Due to the issue with treatment of valueless nodes by this function, + valueless nodes should be checked with ``exists`` instead. + + This function cannot be used outside a configuration session. + In operational mode scripts, use ``return_effective_value``. + """ + if self._session_config: + try: + value = self._session_config.return_value(self._make_path(path)) + except vyos.configtree.ConfigTreeError: + value = None + else: + value = None + + if not value: + return(default) + else: + return(value) + + def return_values(self, path, default=[]): + """ + Retrieve all values of a multi-value leaf node in the running or proposed config + + Args: + path (str): Configuration tree path + + Returns: + str list: Node values, if it has any + []: if node does not exist + + Note: + This function cannot be used outside a configuration session. + In operational mode scripts, use ``return_effective_values``. + """ + if self._session_config: + try: + values = self._session_config.return_values(self._make_path(path)) + except vyos.configtree.ConfigTreeError: + values = [] + else: + values = [] + + if not values: + return(default.copy()) + else: + return(values) + + def list_nodes(self, path, default=[]): + """ + Retrieve names of all children of a tag node in the running or proposed config + + Args: + path (str): Configuration tree path + + Returns: + string list: child node names + + """ + if self._session_config: + try: + nodes = self._session_config.list_nodes(self._make_path(path)) + except vyos.configtree.ConfigTreeError: + nodes = [] + else: + nodes = [] + + if not nodes: + return(default.copy()) + else: + return(nodes) + + def exists_effective(self, path): + """ + Checks if a node or value exists in the running (effective) config. + + Args: + path (str): Configuration tree path + + Returns: + True if node exists in the running config, False otherwise + + Note: + This function is safe to use in operational mode. In configuration mode, + it ignores uncommited changes. + """ + if self._running_config is None: + return False + + # Assume the path is a node path first + if self._running_config.exists(self._make_path(path)): + return True + else: + # If that check fails, it may mean the path has a value at the end. + # libvyosconfig exists() works only for _nodes_, not _values_ + # libvyattacfg also worked for values, so we emulate that case here + if isinstance(path, str): + path = re.split(r'\s+', path) + path_without_value = path[:-1] + try: + # return_values() is safe to use with single-value nodes, + # it simply returns a single-item list in that case. + values = self._running_config.return_values(self._make_path(path_without_value)) + + # If we got this far, the node does exist and has values, + # so we need to check if it has the value in question among its values. + return (path[-1] in values) + except vyos.configtree.ConfigTreeError: + # Even the parent node doesn't exist at all + return False + + + def return_effective_value(self, path, default=None): + """ + Retrieve a values of a single-value leaf node in a running (effective) config + + Args: + path (str): Configuration tree path + default (str): Default value to return if node does not exist + + Returns: + str: Node value + """ + if self._running_config: + try: + value = self._running_config.return_value(self._make_path(path)) + except vyos.configtree.ConfigTreeError: + value = None + else: + value = None + + if not value: + return(default) + else: + return(value) + + def return_effective_values(self, path, default=[]): + """ + Retrieve all values of a multi-value node in a running (effective) config + + Args: + path (str): Configuration tree path + + Returns: + str list: A list of values + """ + if self._running_config: + try: + values = self._running_config.return_values(self._make_path(path)) + except vyos.configtree.ConfigTreeError: + values = [] + else: + values = [] + + if not values: + return(default.copy()) + else: + return(values) + + def list_effective_nodes(self, path, default=[]): + """ + Retrieve names of all children of a tag node in the running config + + Args: + path (str): Configuration tree path + + Returns: + str list: child node names + """ + if self._running_config: + try: + nodes = self._running_config.list_nodes(self._make_path(path)) + except vyos.configtree.ConfigTreeError: + nodes = [] + else: + nodes = [] + + if not nodes: + return(default.copy()) + else: + return(nodes) diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py new file mode 100644 index 0000000..d518737 --- /dev/null +++ b/python/vyos/config_mgmt.py @@ -0,0 +1,761 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +import re +import sys +import gzip +import logging + +from typing import Optional +from typing import Tuple +from filecmp import cmp +from datetime import datetime +from textwrap import dedent +from pathlib import Path +from tabulate import tabulate +from shutil import copy, chown +from urllib.parse import urlsplit +from urllib.parse import urlunsplit + +from vyos.config import Config +from vyos.configtree import ConfigTree +from vyos.configtree import ConfigTreeError +from vyos.configtree import show_diff +from vyos.load_config import load +from vyos.load_config import LoadConfigError +from vyos.defaults import directories +from vyos.version import get_full_version_data +from vyos.utils.io import ask_yes_no +from vyos.utils.boot import boot_configuration_complete +from vyos.utils.process import is_systemd_service_active +from vyos.utils.process import rc_cmd + +SAVE_CONFIG = '/usr/libexec/vyos/vyos-save-config.py' +config_json = '/run/vyatta/config/config.json' + +# created by vyatta-cfg-postinst +commit_post_hook_dir = '/etc/commit/post-hooks.d' + +commit_hooks = {'commit_revision': '01vyos-commit-revision', + 'commit_archive': '02vyos-commit-archive'} + +DEFAULT_TIME_MINUTES = 10 +timer_name = 'commit-confirm' + +config_file = os.path.join(directories['config'], 'config.boot') +archive_dir = os.path.join(directories['config'], 'archive') +archive_config_file = os.path.join(archive_dir, 'config.boot') +commit_log_file = os.path.join(archive_dir, 'commits') +logrotate_conf = os.path.join(archive_dir, 'lr.conf') +logrotate_state = os.path.join(archive_dir, 'lr.state') +rollback_config = os.path.join(archive_dir, 'config.boot-rollback') +prerollback_config = os.path.join(archive_dir, 'config.boot-prerollback') +tmp_log_entry = '/tmp/commit-rev-entry' + +logger = logging.getLogger('config_mgmt') +logger.setLevel(logging.INFO) +ch = logging.StreamHandler() +formatter = logging.Formatter('%(funcName)s: %(levelname)s:%(message)s') +ch.setFormatter(formatter) +logger.addHandler(ch) + +def save_config(target, json_out=None): + if json_out is None: + cmd = f'{SAVE_CONFIG} {target}' + else: + cmd = f'{SAVE_CONFIG} {target} --write-json-file {json_out}' + rc, out = rc_cmd(cmd) + if rc != 0: + logger.critical(f'save config failed: {out}') + +def unsaved_commits(allow_missing_config=False) -> bool: + if get_full_version_data()['boot_via'] == 'livecd': + return False + if allow_missing_config and not os.path.exists(config_file): + return True + tmp_save = '/tmp/config.running' + save_config(tmp_save) + ret = not cmp(tmp_save, config_file, shallow=False) + os.unlink(tmp_save) + return ret + +def get_file_revision(rev: int): + revision = os.path.join(archive_dir, f'config.boot.{rev}.gz') + try: + with gzip.open(revision) as f: + r = f.read().decode() + except FileNotFoundError: + logger.warning(f'commit revision {rev} not available') + return '' + return r + +def get_config_tree_revision(rev: int): + c = get_file_revision(rev) + return ConfigTree(c) + +def is_node_revised(path: list = [], rev1: int = 1, rev2: int = 0) -> bool: + from vyos.configtree import DiffTree + left = get_config_tree_revision(rev1) + right = get_config_tree_revision(rev2) + diff_tree = DiffTree(left, right) + if diff_tree.add.exists(path) or diff_tree.sub.exists(path): + return True + return False + +class ConfigMgmtError(Exception): + pass + +class ConfigMgmt: + def __init__(self, session_env=None, config=None): + if session_env: + self._session_env = session_env + else: + self._session_env = None + + if config is None: + config = Config() + + d = config.get_config_dict(['system', 'config-management'], + key_mangling=('-', '_'), + get_first_key=True) + + self.max_revisions = int(d.get('commit_revisions', 0)) + self.num_revisions = 0 + self.locations = d.get('commit_archive', {}).get('location', []) + self.source_address = d.get('commit_archive', + {}).get('source_address', '') + if config.exists(['system', 'host-name']): + self.hostname = config.return_value(['system', 'host-name']) + if config.exists(['system', 'domain-name']): + tmp = config.return_value(['system', 'domain-name']) + self.hostname += f'.{tmp}' + else: + self.hostname = 'vyos' + + # upload only on existence of effective values, notably, on boot. + # one still needs session self.locations (above) for setting + # post-commit hook in conf_mode script + path = ['system', 'config-management', 'commit-archive', 'location'] + if config.exists_effective(path): + self.effective_locations = config.return_effective_values(path) + else: + self.effective_locations = [] + + # a call to compare without args is edit_level aware + edit_level = os.getenv('VYATTA_EDIT_LEVEL', '') + self.edit_path = [l for l in edit_level.split('/') if l] + + self.active_config = config._running_config + self.working_config = config._session_config + + # Console script functions + # + def commit_confirm(self, minutes: int=DEFAULT_TIME_MINUTES, + no_prompt: bool=False) -> Tuple[str,int]: + """Commit with reboot to saved config in 'minutes' minutes if + 'confirm' call is not issued. + """ + if is_systemd_service_active(f'{timer_name}.timer'): + msg = 'Another confirm is pending' + return msg, 1 + + if unsaved_commits(): + W = '\nYou should save previous commits before commit-confirm !\n' + else: + W = '' + + prompt_str = f''' +commit-confirm will automatically reboot in {minutes} minutes unless changes +are confirmed.\n +Proceed ?''' + prompt_str = W + prompt_str + if not no_prompt and not ask_yes_no(prompt_str, default=True): + msg = 'commit-confirm canceled' + return msg, 1 + + action = 'sg vyattacfg "/usr/bin/config-mgmt revert"' + cmd = f'sudo systemd-run --quiet --on-active={minutes}m --unit={timer_name} {action}' + rc, out = rc_cmd(cmd) + if rc != 0: + raise ConfigMgmtError(out) + + # start notify + cmd = f'sudo -b /usr/libexec/vyos/commit-confirm-notify.py {minutes}' + os.system(cmd) + + msg = f'Initialized commit-confirm; {minutes} minutes to confirm before reboot' + return msg, 0 + + def confirm(self) -> Tuple[str,int]: + """Do not reboot to saved config following 'commit-confirm'. + Update commit log and archive. + """ + if not is_systemd_service_active(f'{timer_name}.timer'): + msg = 'No confirm pending' + return msg, 0 + + cmd = f'sudo systemctl stop --quiet {timer_name}.timer' + rc, out = rc_cmd(cmd) + if rc != 0: + raise ConfigMgmtError(out) + + # kill notify + cmd = 'sudo pkill -f commit-confirm-notify.py' + rc, out = rc_cmd(cmd) + if rc != 0: + raise ConfigMgmtError(out) + + entry = self._read_tmp_log_entry() + + if self._archive_active_config(): + self._add_log_entry(**entry) + self._update_archive() + + msg = 'Reboot timer stopped' + return msg, 0 + + def revert(self) -> Tuple[str,int]: + """Reboot to saved config, dropping commits from 'commit-confirm'. + """ + _ = self._read_tmp_log_entry() + + # archived config will be reverted on boot + rc, out = rc_cmd('sudo systemctl reboot') + if rc != 0: + raise ConfigMgmtError(out) + + return '', 0 + + def rollback(self, rev: int, no_prompt: bool=False) -> Tuple[str,int]: + """Reboot to config revision 'rev'. + """ + msg = '' + + if not self._check_revision_number(rev): + msg = f'Invalid revision number {rev}: must be 0 < rev < {self.num_revisions}' + return msg, 1 + + prompt_str = 'Proceed with reboot ?' + if not no_prompt and not ask_yes_no(prompt_str, default=True): + msg = 'Canceling rollback' + return msg, 0 + + rc, out = rc_cmd(f'sudo cp {archive_config_file} {prerollback_config}') + if rc != 0: + raise ConfigMgmtError(out) + + path = os.path.join(archive_dir, f'config.boot.{rev}.gz') + with gzip.open(path) as f: + config = f.read() + try: + with open(rollback_config, 'wb') as f: + f.write(config) + copy(rollback_config, config_file) + except OSError as e: + raise ConfigMgmtError from e + + rc, out = rc_cmd('sudo systemctl reboot') + if rc != 0: + raise ConfigMgmtError(out) + + return msg, 0 + + def rollback_soft(self, rev: int): + """Rollback without reboot (rollback-soft) + """ + msg = '' + + if not self._check_revision_number(rev): + msg = f'Invalid revision number {rev}: must be 0 < rev < {self.num_revisions}' + return msg, 1 + + rollback_ct = self._get_config_tree_revision(rev) + try: + load(rollback_ct, switch='explicit') + print('Rollback diff has been applied.') + print('Use "compare" to review the changes or "commit" to apply them.') + except LoadConfigError as e: + raise ConfigMgmtError(e) from e + + return msg, 0 + + def compare(self, saved: bool=False, commands: bool=False, + rev1: Optional[int]=None, + rev2: Optional[int]=None) -> Tuple[str,int]: + """General compare function for config file revisions: + revision n vs. revision m; working version vs. active version; + or working version vs. saved version. + """ + ct1 = self.active_config + ct2 = self.working_config + msg = 'No changes between working and active configurations.\n' + if saved: + ct1 = self._get_saved_config_tree() + ct2 = self.working_config + msg = 'No changes between working and saved configurations.\n' + if rev1 is not None: + if not self._check_revision_number(rev1): + return f'Invalid revision number {rev1}', 1 + ct1 = self._get_config_tree_revision(rev1) + ct2 = self.working_config + msg = f'No changes between working and revision {rev1} configurations.\n' + if rev2 is not None: + if not self._check_revision_number(rev2): + return f'Invalid revision number {rev2}', 1 + # compare older to newer + ct2 = ct1 + ct1 = self._get_config_tree_revision(rev2) + msg = f'No changes between revisions {rev2} and {rev1} configurations.\n' + + out = '' + path = [] if commands else self.edit_path + try: + if commands: + out = show_diff(ct1, ct2, path=path, commands=True) + else: + out = show_diff(ct1, ct2, path=path) + except ConfigTreeError as e: + return e, 1 + + if out: + msg = out + + return msg, 0 + + def wrap_compare(self, options) -> Tuple[str,int]: + """Interface to vyatta-cfg-run: args collected as 'options' to parse + for compare. + """ + cmnds = False + r1 = None + r2 = None + if 'commands' in options: + cmnds=True + options.remove('commands') + for i in options: + if not i.isnumeric(): + options.remove(i) + if len(options) > 0: + r1 = int(options[0]) + if len(options) > 1: + r2 = int(options[1]) + + return self.compare(commands=cmnds, rev1=r1, rev2=r2) + + # Initialization and post-commit hooks for conf-mode + # + def initialize_revision(self): + """Initialize config archive, logrotate conf, and commit log. + """ + mask = os.umask(0o002) + os.makedirs(archive_dir, exist_ok=True) + json_dir = os.path.dirname(config_json) + try: + os.makedirs(json_dir, exist_ok=True) + chown(json_dir, group='vyattacfg') + except OSError as e: + logger.warning(f'cannot create {json_dir}: {e}') + + self._add_logrotate_conf() + + if (not os.path.exists(commit_log_file) or + self._get_number_of_revisions() == 0): + user = self._get_user() + via = 'init' + comment = '' + # add empty init config before boot-config load for revision + # and diff consistency + if self._archive_active_config(): + self._add_log_entry(user, via, comment) + self._update_archive() + + os.umask(mask) + + def commit_revision(self): + """Update commit log and rotate archived config.boot. + + commit_revision is called in post-commit-hooks, if + ['commit-archive', 'commit-revisions'] is configured. + """ + if os.getenv('IN_COMMIT_CONFIRM', ''): + self._new_log_entry(tmp_file=tmp_log_entry) + return + + if self._archive_active_config(): + self._add_log_entry() + self._update_archive() + + def commit_archive(self): + """Upload config to remote archive. + """ + from vyos.remote import upload + + hostname = self.hostname + t = datetime.now() + timestamp = t.strftime('%Y%m%d_%H%M%S') + remote_file = f'config.boot-{hostname}.{timestamp}' + source_address = self.source_address + + if self.effective_locations: + print("Archiving config...") + for location in self.effective_locations: + url = urlsplit(location) + _, _, netloc = url.netloc.rpartition("@") + redacted_location = urlunsplit(url._replace(netloc=netloc)) + print(f" {redacted_location}", end=" ", flush=True) + upload(archive_config_file, f'{location}/{remote_file}', + source_host=source_address) + + # op-mode functions + # + def get_raw_log_data(self) -> list: + """Return list of dicts of log data: + keys: [timestamp, user, commit_via, commit_comment] + """ + log = self._get_log_entries() + res_l = [] + for line in log: + d = self._get_log_entry(line) + res_l.append(d) + + return res_l + + @staticmethod + def format_log_data(data: list) -> str: + """Return formatted log data as str. + """ + res_l = [] + for l_no, l in enumerate(data): + time_d = datetime.fromtimestamp(int(l['timestamp'])) + time_str = time_d.strftime("%Y-%m-%d %H:%M:%S") + + res_l.append([l_no, time_str, + f"by {l['user']}", f"via {l['commit_via']}"]) + + if l['commit_comment'] != 'commit': # default comment + res_l.append([None, l['commit_comment']]) + + ret = tabulate(res_l, tablefmt="plain") + return ret + + @staticmethod + def format_log_data_brief(data: list) -> str: + """Return 'brief' form of log data as str. + + Slightly compacted format used in completion help for + 'rollback'. + """ + res_l = [] + for l_no, l in enumerate(data): + time_d = datetime.fromtimestamp(int(l['timestamp'])) + time_str = time_d.strftime("%Y-%m-%d %H:%M:%S") + + res_l.append(['\t', l_no, time_str, + f"{l['user']}", f"by {l['commit_via']}"]) + + ret = tabulate(res_l, tablefmt="plain") + return ret + + def show_commit_diff(self, rev: int, rev2: Optional[int]=None, + commands: bool=False) -> str: + """Show commit diff at revision number, compared to previous + revision, or to another revision. + """ + if rev2 is None: + out, _ = self.compare(commands=commands, rev1=rev, rev2=(rev+1)) + return out + + out, _ = self.compare(commands=commands, rev1=rev, rev2=rev2) + return out + + def show_commit_file(self, rev: int) -> str: + return self._get_file_revision(rev) + + # utility functions + # + + def _get_saved_config_tree(self): + with open(config_file) as f: + c = f.read() + return ConfigTree(c) + + def _get_file_revision(self, rev: int): + if rev not in range(0, self._get_number_of_revisions()): + raise ConfigMgmtError('revision not available') + revision = os.path.join(archive_dir, f'config.boot.{rev}.gz') + with gzip.open(revision) as f: + r = f.read().decode() + return r + + def _get_config_tree_revision(self, rev: int): + c = self._get_file_revision(rev) + return ConfigTree(c) + + def _add_logrotate_conf(self): + conf: str = dedent(f"""\ + {archive_config_file} {{ + su root vyattacfg + rotate {self.max_revisions} + start 0 + compress + copy + }} + """) + conf_file = Path(logrotate_conf) + conf_file.write_text(conf) + conf_file.chmod(0o644) + + def _archive_active_config(self) -> bool: + save_to_tmp = (boot_configuration_complete() or not + os.path.isfile(archive_config_file)) + mask = os.umask(0o113) + + ext = os.getpid() + cmp_saved = f'/tmp/config.boot.{ext}' + if save_to_tmp: + save_config(cmp_saved, json_out=config_json) + else: + copy(config_file, cmp_saved) + + # on boot, we need to manually create the config.json file; after + # boot, it is written by save_config, above + if not os.path.exists(config_json): + ct = self._get_saved_config_tree() + try: + with open(config_json, 'w') as f: + f.write(ct.to_json()) + chown(config_json, group='vyattacfg') + except OSError as e: + logger.warning(f'cannot create {config_json}: {e}') + + try: + if cmp(cmp_saved, archive_config_file, shallow=False): + os.unlink(cmp_saved) + os.umask(mask) + return False + except FileNotFoundError: + pass + + rc, out = rc_cmd(f'sudo mv {cmp_saved} {archive_config_file}') + os.umask(mask) + + if rc != 0: + logger.critical(f'mv file to archive failed: {out}') + return False + + return True + + @staticmethod + def _update_archive(): + cmd = f"sudo logrotate -f -s {logrotate_state} {logrotate_conf}" + rc, out = rc_cmd(cmd) + if rc != 0: + logger.critical(f'logrotate failure: {out}') + + @staticmethod + def _get_log_entries() -> list: + """Return lines of commit log as list of strings + """ + entries = [] + if os.path.exists(commit_log_file): + with open(commit_log_file) as f: + entries = f.readlines() + + return entries + + def _get_number_of_revisions(self) -> int: + l = self._get_log_entries() + return len(l) + + def _check_revision_number(self, rev: int) -> bool: + self.num_revisions = self._get_number_of_revisions() + if not 0 <= rev < self.num_revisions: + return False + return True + + @staticmethod + def _get_user() -> str: + import pwd + + try: + user = os.getlogin() + except OSError: + try: + user = pwd.getpwuid(os.geteuid())[0] + except KeyError: + user = 'unknown' + return user + + def _new_log_entry(self, user: str='', commit_via: str='', + commit_comment: str='', timestamp: Optional[int]=None, + tmp_file: str=None) -> Optional[str]: + # Format log entry and return str or write to file. + # + # Usage is within a post-commit hook, using env values. In case of + # commit-confirm, it can be written to a temporary file for + # inclusion on 'confirm'. + from time import time + + if timestamp is None: + timestamp = int(time()) + + if not user: + user = self._get_user() + if not commit_via: + commit_via = os.getenv('COMMIT_VIA', 'other') + if not commit_comment: + commit_comment = os.getenv('COMMIT_COMMENT', 'commit') + + # the commit log reserves '|' as field demarcation, so replace in + # comment if present; undo this in _get_log_entry, below + if re.search(r'\|', commit_comment): + commit_comment = commit_comment.replace('|', '%%') + + entry = f'|{timestamp}|{user}|{commit_via}|{commit_comment}|\n' + + mask = os.umask(0o113) + if tmp_file is not None: + try: + with open(tmp_file, 'w') as f: + f.write(entry) + except OSError as e: + logger.critical(f'write to {tmp_file} failed: {e}') + os.umask(mask) + return None + + os.umask(mask) + return entry + + @staticmethod + def _get_log_entry(line: str) -> dict: + log_fmt = re.compile(r'\|.*\|\n?$') + keys = ['user', 'commit_via', 'commit_comment', 'timestamp'] + if not log_fmt.match(line): + logger.critical(f'Invalid log format {line}') + return {} + + timestamp, user, commit_via, commit_comment = ( + tuple(line.strip().strip('|').split('|'))) + + commit_comment = commit_comment.replace('%%', '|') + d = dict(zip(keys, [user, commit_via, + commit_comment, timestamp])) + + return d + + def _read_tmp_log_entry(self) -> dict: + try: + with open(tmp_log_entry) as f: + entry = f.read() + os.unlink(tmp_log_entry) + except OSError as e: + logger.critical(f'error on file {tmp_log_entry}: {e}') + + return self._get_log_entry(entry) + + def _add_log_entry(self, user: str='', commit_via: str='', + commit_comment: str='', timestamp: Optional[int]=None): + mask = os.umask(0o113) + + entry = self._new_log_entry(user=user, commit_via=commit_via, + commit_comment=commit_comment, + timestamp=timestamp) + + log_entries = self._get_log_entries() + log_entries.insert(0, entry) + if len(log_entries) > self.max_revisions: + log_entries = log_entries[:-1] + + try: + with open(commit_log_file, 'w') as f: + f.writelines(log_entries) + except OSError as e: + logger.critical(e) + + os.umask(mask) + +# entry_point for console script +# +def run(): + from argparse import ArgumentParser, REMAINDER + + config_mgmt = ConfigMgmt() + + for s in list(commit_hooks): + if sys.argv[0].replace('-', '_').endswith(s): + func = getattr(config_mgmt, s) + try: + func() + except Exception as e: + print(f'{s}: {e}') + sys.exit(0) + + parser = ArgumentParser() + subparsers = parser.add_subparsers(dest='subcommand') + + commit_confirm = subparsers.add_parser('commit_confirm', + help="Commit with opt-out reboot to saved config") + commit_confirm.add_argument('-t', dest='minutes', type=int, + default=DEFAULT_TIME_MINUTES, + help="Minutes until reboot, unless 'confirm'") + commit_confirm.add_argument('-y', dest='no_prompt', action='store_true', + help="Execute without prompt") + + subparsers.add_parser('confirm', help="Confirm commit") + subparsers.add_parser('revert', help="Revert commit-confirm") + + rollback = subparsers.add_parser('rollback', + help="Rollback to earlier config") + rollback.add_argument('--rev', type=int, + help="Revision number for rollback") + rollback.add_argument('-y', dest='no_prompt', action='store_true', + help="Excute without prompt") + + rollback_soft = subparsers.add_parser('rollback_soft', + help="Rollback to earlier config") + rollback_soft.add_argument('--rev', type=int, + help="Revision number for rollback") + + compare = subparsers.add_parser('compare', + help="Compare config files") + + compare.add_argument('--saved', action='store_true', + help="Compare session config with saved config") + compare.add_argument('--commands', action='store_true', + help="Show difference between commands") + compare.add_argument('--rev1', type=int, default=None, + help="Compare revision with session config or other revision") + compare.add_argument('--rev2', type=int, default=None, + help="Compare revisions") + + wrap_compare = subparsers.add_parser('wrap_compare', + help="Wrapper interface for vyatta-cfg-run") + wrap_compare.add_argument('--options', nargs=REMAINDER) + + args = vars(parser.parse_args()) + + func = getattr(config_mgmt, args['subcommand']) + del args['subcommand'] + + res = '' + try: + res, rc = func(**args) + except ConfigMgmtError as e: + print(e) + sys.exit(1) + if res: + print(res) + sys.exit(rc) diff --git a/python/vyos/configdep.py b/python/vyos/configdep.py new file mode 100644 index 0000000..cf7c9d5 --- /dev/null +++ b/python/vyos/configdep.py @@ -0,0 +1,211 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +import json +import typing +from inspect import stack +from graphlib import TopologicalSorter, CycleError + +from vyos.utils.system import load_as_module +from vyos.configdict import dict_merge +from vyos.defaults import directories +from vyos.configsource import VyOSError +from vyos import ConfigError + +# https://peps.python.org/pep-0484/#forward-references +# for type 'Config' +if typing.TYPE_CHECKING: + from vyos.config import Config + +dependency_dir = os.path.join(directories['data'], + 'config-mode-dependencies') + +dependency_list: list[typing.Callable] = [] + +DEBUG = False + +def debug_print(s: str): + if DEBUG: + print(s) + +def canon_name(name: str) -> str: + return os.path.splitext(name)[0].replace('-', '_') + +def canon_name_of_path(path: str) -> str: + script = os.path.basename(path) + return canon_name(script) + +def caller_name() -> str: + filename = stack()[2].filename + return canon_name_of_path(filename) + +def name_of(f: typing.Callable) -> str: + return f.__name__ + +def names_of(l: list[typing.Callable]) -> list[str]: + return [name_of(f) for f in l] + +def remove_redundant(l: list[typing.Callable]) -> list[typing.Callable]: + names = set() + for e in reversed(l): + _ = l.remove(e) if name_of(e) in names else names.add(name_of(e)) + +def append_uniq(l: list[typing.Callable], e: typing.Callable): + """Append an element, removing earlier occurrences + + The list of dependencies is generally short and traversing the list on + each append is preferable to the cost of redundant script invocation. + """ + l.append(e) + remove_redundant(l) + +def read_dependency_dict(dependency_dir: str = dependency_dir) -> dict: + res = {} + for dep_file in os.listdir(dependency_dir): + if not dep_file.endswith('.json'): + continue + path = os.path.join(dependency_dir, dep_file) + with open(path) as f: + d = json.load(f) + if dep_file == 'vyos-1x.json': + res = dict_merge(res, d) + else: + res = dict_merge(d, res) + + return res + +def get_dependency_dict(config: 'Config') -> dict: + if hasattr(config, 'cached_dependency_dict'): + d = getattr(config, 'cached_dependency_dict') + else: + d = read_dependency_dict() + setattr(config, 'cached_dependency_dict', d) + return d + +def run_config_mode_script(target: str, config: 'Config'): + script = target + '.py' + path = os.path.join(directories['conf_mode'], script) + name = canon_name(script) + mod = load_as_module(name, path) + + config.set_level([]) + try: + c = mod.get_config(config) + mod.verify(c) + mod.generate(c) + mod.apply(c) + except (VyOSError, ConfigError) as e: + raise ConfigError(str(e)) from e + +def run_conditionally(target: str, tagnode: str, config: 'Config'): + tag_ext = f'_{tagnode}' if tagnode else '' + script_name = f'{target}{tag_ext}' + + scripts_called = getattr(config, 'scripts_called', []) + commit_scripts = getattr(config, 'commit_scripts', []) + + debug_print(f'scripts_called: {scripts_called}') + debug_print(f'commit_scripts: {commit_scripts}') + + if script_name in commit_scripts and script_name not in scripts_called: + debug_print(f'dependency {script_name} deferred to priority') + return + + run_config_mode_script(target, config) + +def def_closure(target: str, config: 'Config', + tagnode: typing.Optional[str] = None) -> typing.Callable: + def func_impl(): + tag_value = '' + if tagnode is not None: + os.environ['VYOS_TAGNODE_VALUE'] = tagnode + tag_value = tagnode + run_conditionally(target, tag_value, config) + + tag_ext = f'_{tagnode}' if tagnode is not None else '' + func_impl.__name__ = f'{target}{tag_ext}' + + return func_impl + +def set_dependents(case: str, config: 'Config', + tagnode: typing.Optional[str] = None): + global dependency_list + + dependency_list = config.dependency_list + + d = get_dependency_dict(config) + k = caller_name() + l = dependency_list + + for target in d[k][case]: + func = def_closure(target, config, tagnode) + append_uniq(l, func) + + debug_print(f'set_dependents: caller {k}, current dependents {names_of(l)}') + +def call_dependents(): + k = caller_name() + l = dependency_list + debug_print(f'call_dependents: caller {k}, remaining dependents {names_of(l)}') + while l: + f = l.pop(0) + debug_print(f'calling: {f.__name__}') + try: + f() + except ConfigError as e: + s = f'dependent {f.__name__}: {str(e)}' + raise ConfigError(s) from e + +def called_as_dependent() -> bool: + st = stack()[1:] + for f in st: + if f.filename == __file__: + return True + return False + +def graph_from_dependency_dict(d: dict) -> dict: + g = {} + for k in list(d): + g[k] = set() + # add the dependencies for every sub-case; should there be cases + # that are mutally exclusive in the future, the graphs will be + # distinguished + for el in list(d[k]): + g[k] |= set(d[k][el]) + + return g + +def is_acyclic(d: dict) -> bool: + g = graph_from_dependency_dict(d) + ts = TopologicalSorter(g) + try: + # get node iterator + order = ts.static_order() + # try iteration + _ = [*order] + except CycleError: + return False + + return True + +def check_dependency_graph(dependency_dir: str = dependency_dir, + supplement: str = None) -> bool: + d = read_dependency_dict(dependency_dir=dependency_dir) + if supplement is not None: + with open(supplement) as f: + d = dict_merge(json.load(f), d) + + return is_acyclic(d) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py new file mode 100644 index 0000000..5a353b1 --- /dev/null +++ b/python/vyos/configdict.py @@ -0,0 +1,666 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +""" +A library for retrieving value dicts from VyOS configs in a declarative fashion. +""" +import os +import json + +from vyos.utils.dict import dict_search +from vyos.utils.process import cmd + +def retrieve_config(path_hash, base_path, config): + """ + Retrieves a VyOS config as a dict according to a declarative description + + The description dict, passed in the first argument, must follow this format: + ``field_name : <path, type, [inner_options_dict]>``. + + Supported types are: ``str`` (for normal nodes), + ``list`` (returns a list of strings, for multi nodes), + ``bool`` (returns True if valueless node exists), + ``dict`` (for tag nodes, returns a dict indexed by node names, + according to description in the third item of the tuple). + + Args: + path_hash (dict): Declarative description of the config to retrieve + base_path (list): A base path to prepend to all option paths + config (vyos.config.Config): A VyOS config object + + Returns: + dict: config dict + """ + config_hash = {} + + for k in path_hash: + + if type(path_hash[k]) != tuple: + raise ValueError("In field {0}: expected a tuple, got a value {1}".format(k, str(path_hash[k]))) + if len(path_hash[k]) < 2: + raise ValueError("In field {0}: field description must be a tuple of at least two items, path (list) and type".format(k)) + + path = path_hash[k][0] + if type(path) != list: + raise ValueError("In field {0}: path must be a list, not a {1}".format(k, type(path))) + + typ = path_hash[k][1] + if type(typ) != type: + raise ValueError("In field {0}: type must be a type, not a {1}".format(k, type(typ))) + + path = base_path + path + + path_str = " ".join(path) + + if typ == str: + config_hash[k] = config.return_value(path_str) + elif typ == list: + config_hash[k] = config.return_values(path_str) + elif typ == bool: + config_hash[k] = config.exists(path_str) + elif typ == dict: + try: + inner_hash = path_hash[k][2] + except IndexError: + raise ValueError("The type of the \'{0}\' field is dict, but inner options hash is missing from the tuple".format(k)) + config_hash[k] = {} + nodes = config.list_nodes(path_str) + for node in nodes: + config_hash[k][node] = retrieve_config(inner_hash, path + [node], config) + + return config_hash + + +def dict_merge(source, destination): + """ Merge two dictionaries. Only keys which are not present in destination + will be copied from source, anything else will be kept untouched. Function + will return a new dict which has the merged key/value pairs. """ + from copy import deepcopy + tmp = deepcopy(destination) + + for key, value in source.items(): + if key not in tmp: + tmp[key] = value + elif isinstance(source[key], dict): + tmp[key] = dict_merge(source[key], tmp[key]) + + return tmp + +def list_diff(first, second): + """ Diff two dictionaries and return only unique items """ + second = set(second) + return [item for item in first if item not in second] + +def is_node_changed(conf, path): + """ + Check if any key under path has been changed and return True. + If nothing changed, return false + """ + from vyos.configdiff import get_config_diff + D = get_config_diff(conf, key_mangling=('-', '_')) + return D.is_node_changed(path) + +def leaf_node_changed(conf, path): + """ + Check if a leaf node was altered. If it has been altered - values has been + changed, or it was added/removed, we will return a list containing the old + value(s). If nothing has been changed, None is returned. + + NOTE: path must use the real CLI node name (e.g. with a hyphen!) + """ + from vyos.configdiff import get_config_diff + D = get_config_diff(conf, key_mangling=('-', '_')) + (new, old) = D.get_value_diff(path) + if new != old: + if isinstance(old, dict): + # valueLess nodes return {} if node is deleted + return True + if old is None and isinstance(new, dict): + # valueLess nodes return {} if node was added + return True + if old is None: + return [] + if isinstance(old, str): + return [old] + if isinstance(old, list): + if isinstance(new, str): + new = [new] + elif isinstance(new, type(None)): + new = [] + return list_diff(old, new) + + return None + +def node_changed(conf, path, key_mangling=None, recursive=False, expand_nodes=None) -> list: + """ + Check if node under path (or anything under path if recursive=True) was changed. By default + we only check if a node or subnode (recursive) was deleted from path. If expand_nodes + is set to Diff.ADD we can also check if something was added to the path. + + If nothing changed, an empty list is returned. + """ + from vyos.configdiff import get_config_diff + from vyos.configdiff import Diff + # to prevent circular dependencies we assign the default here + if not expand_nodes: expand_nodes = Diff.DELETE + D = get_config_diff(conf, key_mangling) + # get_child_nodes_diff() will return dict_keys() + tmp = D.get_child_nodes_diff(path, expand_nodes=expand_nodes, recursive=recursive) + output = [] + if expand_nodes & Diff.DELETE: + output.extend(list(tmp['delete'].keys())) + if expand_nodes & Diff.ADD: + output.extend(list(tmp['add'].keys())) + + # remove duplicate keys from list, this happens when a node (e.g. description) is altered + output = list(dict.fromkeys(output)) + return output + +def get_removed_vlans(conf, path, dict): + """ + Common function to parse a dictionary retrieved via get_config_dict() and + determine any added/removed VLAN interfaces - be it 802.1q or Q-in-Q. + """ + from vyos.configdiff import get_config_diff, Diff + + # Check vif, vif-s/vif-c VLAN interfaces for removal + D = get_config_diff(conf, key_mangling=('-', '_')) + D.set_level(conf.get_level()) + + # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 + keys = D.get_child_nodes_diff(path + ['vif'], expand_nodes=Diff.DELETE)['delete'].keys() + if keys: dict['vif_remove'] = [*keys] + + # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 + keys = D.get_child_nodes_diff(path + ['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys() + if keys: dict['vif_s_remove'] = [*keys] + + for vif in dict.get('vif_s', {}).keys(): + keys = D.get_child_nodes_diff(path + ['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys() + if keys: dict['vif_s'][vif]['vif_c_remove'] = [*keys] + + return dict + +def is_member(conf, interface, intftype=None): + """ + Checks if passed interface is member of other interface of specified type. + intftype is optional, if not passed it will search all known types + (currently bridge and bonding) + + Returns: dict + empty -> Interface is not a member + key -> Interface is a member of this interface + """ + ret_val = {} + intftypes = ['bonding', 'bridge'] + + if intftype not in intftypes + [None]: + raise ValueError(( + f'unknown interface type "{intftype}" or it cannot ' + f'have member interfaces')) + + intftype = intftypes if intftype == None else [intftype] + + for iftype in intftype: + base = ['interfaces', iftype] + for intf in conf.list_nodes(base): + member = base + [intf, 'member', 'interface', interface] + if conf.exists(member): + tmp = conf.get_config_dict(member, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + ret_val.update({intf : tmp}) + + return ret_val + +def is_mirror_intf(conf, interface, direction=None): + """ + Check whether the passed interface is used for port mirroring. Direction + is optional, if not passed it will search all known direction + (currently ingress and egress) + + Returns: + None -> Interface is not a monitor interface + Array() -> This interface is a monitor interface of interfaces + """ + from vyos.ifconfig import Section + + directions = ['ingress', 'egress'] + if direction not in directions + [None]: + raise ValueError(f'Unknown interface mirror direction "{direction}"') + + direction = directions if direction == None else [direction] + + ret_val = None + base = ['interfaces'] + + for dir in direction: + for iftype in conf.list_nodes(base): + iftype_base = base + [iftype] + for intf in conf.list_nodes(iftype_base): + mirror = iftype_base + [intf, 'mirror', dir, interface] + if conf.exists(mirror): + path = ['interfaces', Section.section(intf), intf] + tmp = conf.get_config_dict(path, key_mangling=('-', '_'), + get_first_key=True) + ret_val = {intf : tmp} + + return ret_val + +def has_address_configured(conf, intf): + """ + Checks if interface has an address configured. + Checks the following config nodes: + 'address', 'ipv6 address eui64', 'ipv6 address autoconf' + + Returns True if interface has address configured, False if it doesn't. + """ + from vyos.ifconfig import Section + ret = False + + old_level = conf.get_level() + conf.set_level([]) + + intfpath = ['interfaces', Section.get_config_path(intf)] + if (conf.exists([intfpath, 'address']) or + conf.exists([intfpath, 'ipv6', 'address', 'autoconf']) or + conf.exists([intfpath, 'ipv6', 'address', 'eui64'])): + ret = True + + conf.set_level(old_level) + return ret + +def has_vrf_configured(conf, intf): + """ + Checks if interface has a VRF configured. + + Returns True if interface has VRF configured, False if it doesn't. + """ + from vyos.ifconfig import Section + ret = False + + old_level = conf.get_level() + conf.set_level([]) + + if conf.exists(['interfaces', Section.get_config_path(intf), 'vrf']): + ret = True + + conf.set_level(old_level) + return ret + +def has_vlan_subinterface_configured(conf, intf): + """ + Checks if interface has an VLAN subinterface configured. + Checks the following config nodes: + 'vif', 'vif-s' + + Return True if interface has VLAN subinterface configured. + """ + from vyos.ifconfig import Section + ret = False + + intfpath = ['interfaces', Section.section(intf), intf] + if (conf.exists(intfpath + ['vif']) or conf.exists(intfpath + ['vif-s'])): + ret = True + + return ret + +def is_source_interface(conf, interface, intftype=None): + """ + Checks if passed interface is configured as source-interface of other + interfaces of specified type. intftype is optional, if not passed it will + search all known types (currently pppoe, macsec, pseudo-ethernet, tunnel + and vxlan) + + Returns: + None -> Interface is not a member + interface name -> Interface is a member of this interface + False -> interface type cannot have members + """ + ret_val = None + intftypes = ['macsec', 'pppoe', 'pseudo-ethernet', 'tunnel', 'vxlan'] + if not intftype: + intftype = intftypes + + if isinstance(intftype, str): + intftype = [intftype] + elif not isinstance(intftype, list): + raise ValueError(f'Interface type "{type(intftype)}" must be either str or list!') + + if not all(x in intftypes for x in intftype): + raise ValueError(f'unknown interface type "{intftype}" or it can not ' + 'have a source-interface') + + for it in intftype: + base = ['interfaces', it] + for intf in conf.list_nodes(base): + src_intf = base + [intf, 'source-interface'] + if conf.exists(src_intf) and interface in conf.return_values(src_intf): + ret_val = intf + break + + return ret_val + +def get_dhcp_interfaces(conf, vrf=None): + """ Common helper functions to retrieve all interfaces from current CLI + sessions that have DHCP configured. """ + dhcp_interfaces = {} + dict = conf.get_config_dict(['interfaces'], get_first_key=True) + if not dict: + return dhcp_interfaces + + def check_dhcp(config): + ifname = config['ifname'] + tmp = {} + if 'address' in config and 'dhcp' in config['address']: + options = {} + if dict_search('dhcp_options.default_route_distance', config) != None: + options.update({'dhcp_options' : config['dhcp_options']}) + if 'vrf' in config: + if vrf == config['vrf']: tmp.update({ifname : options}) + else: + if vrf is None: tmp.update({ifname : options}) + + return tmp + + for section, interface in dict.items(): + for ifname in interface: + # always reset config level, as get_interface_dict() will alter it + conf.set_level([]) + # we already have a dict representation of the config from get_config_dict(), + # but with the extended information from get_interface_dict() we also + # get the DHCP client default-route-distance default option if not specified. + _, ifconfig = get_interface_dict(conf, ['interfaces', section], ifname) + + tmp = check_dhcp(ifconfig) + dhcp_interfaces.update(tmp) + # check per VLAN interfaces + for vif, vif_config in ifconfig.get('vif', {}).items(): + tmp = check_dhcp(vif_config) + dhcp_interfaces.update(tmp) + # check QinQ VLAN interfaces + for vif_s, vif_s_config in ifconfig.get('vif_s', {}).items(): + tmp = check_dhcp(vif_s_config) + dhcp_interfaces.update(tmp) + for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): + tmp = check_dhcp(vif_c_config) + dhcp_interfaces.update(tmp) + + return dhcp_interfaces + +def get_pppoe_interfaces(conf, vrf=None): + """ Common helper functions to retrieve all interfaces from current CLI + sessions that have DHCP configured. """ + pppoe_interfaces = {} + conf.set_level([]) + for ifname in conf.list_nodes(['interfaces', 'pppoe']): + # always reset config level, as get_interface_dict() will alter it + conf.set_level([]) + # we already have a dict representation of the config from get_config_dict(), + # but with the extended information from get_interface_dict() we also + # get the DHCP client default-route-distance default option if not specified. + _, ifconfig = get_interface_dict(conf, ['interfaces', 'pppoe'], ifname) + + options = {} + if 'default_route_distance' in ifconfig: + options.update({'default_route_distance' : ifconfig['default_route_distance']}) + if 'no_default_route' in ifconfig: + options.update({'no_default_route' : {}}) + if 'vrf' in ifconfig: + if vrf == ifconfig['vrf']: pppoe_interfaces.update({ifname : options}) + else: + if vrf is None: pppoe_interfaces.update({ifname : options}) + + return pppoe_interfaces + +def get_interface_dict(config, base, ifname='', recursive_defaults=True, with_pki=False): + """ + Common utility function to retrieve and mangle the interfaces configuration + from the CLI input nodes. All interfaces have a common base where value + retrival is identical. This function must be used whenever possible when + working on the interfaces node! + + Return a dictionary with the necessary interface config keys. + """ + if not ifname: + from vyos import ConfigError + # determine tagNode instance + if 'VYOS_TAGNODE_VALUE' not in os.environ: + raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') + ifname = os.environ['VYOS_TAGNODE_VALUE'] + + # Check if interface has been removed. We must use exists() as + # get_config_dict() will always return {} - even when an empty interface + # node like the following exists. + # +macsec macsec1 { + # +} + if not config.exists(base + [ifname]): + dict = config.get_config_dict(base + [ifname], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + dict.update({'deleted' : {}}) + else: + # Get config_dict with default values + dict = config.get_config_dict(base + [ifname], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_defaults=True, + with_recursive_defaults=recursive_defaults, + with_pki=with_pki) + + # If interface does not request an IPv4 DHCP address there is no need + # to keep the dhcp-options key + if 'address' not in dict or 'dhcp' not in dict['address']: + if 'dhcp_options' in dict: + del dict['dhcp_options'] + + # Add interface instance name into dictionary + dict.update({'ifname': ifname}) + + # Check if QoS policy applied on this interface - See ifconfig.interface.set_mirror_redirect() + if config.exists(['qos', 'interface', ifname]): + dict.update({'traffic_policy': {}}) + + address = leaf_node_changed(config, base + [ifname, 'address']) + if address: dict.update({'address_old' : address}) + + # Check if we are a member of a bridge device + bridge = is_member(config, ifname, 'bridge') + if bridge: dict.update({'is_bridge_member' : bridge}) + + # Check if it is a monitor interface + mirror = is_mirror_intf(config, ifname) + if mirror: dict.update({'is_mirror_intf' : mirror}) + + # Check if we are a member of a bond device + bond = is_member(config, ifname, 'bonding') + if bond: dict.update({'is_bond_member' : bond}) + + # Check if any DHCP options changed which require a client restat + dhcp = is_node_changed(config, base + [ifname, 'dhcp-options']) + if dhcp: dict.update({'dhcp_options_changed' : {}}) + + # Changine interface VRF assignemnts require a DHCP restart, too + dhcp = is_node_changed(config, base + [ifname, 'vrf']) + if dhcp: dict.update({'dhcp_options_changed' : {}}) + + # Some interfaces come with a source_interface which must also not be part + # of any other bond or bridge interface as it is exclusivly assigned as the + # Kernels "lower" interface to this new "virtual/upper" interface. + if 'source_interface' in dict: + # Check if source interface is member of another bridge + tmp = is_member(config, dict['source_interface'], 'bridge') + if tmp: dict.update({'source_interface_is_bridge_member' : tmp}) + + # Check if source interface is member of another bridge + tmp = is_member(config, dict['source_interface'], 'bonding') + if tmp: dict.update({'source_interface_is_bond_member' : tmp}) + + mac = leaf_node_changed(config, base + [ifname, 'mac']) + if mac: dict.update({'mac_old' : mac}) + + eui64 = leaf_node_changed(config, base + [ifname, 'ipv6', 'address', 'eui64']) + if eui64: + tmp = dict_search('ipv6.address', dict) + if not tmp: + dict.update({'ipv6': {'address': {'eui64_old': eui64}}}) + else: + dict['ipv6']['address'].update({'eui64_old': eui64}) + + for vif, vif_config in dict.get('vif', {}).items(): + # Add subinterface name to dictionary + dict['vif'][vif].update({'ifname' : f'{ifname}.{vif}'}) + + if config.exists(['qos', 'interface', f'{ifname}.{vif}']): + dict['vif'][vif].update({'traffic_policy': {}}) + + if 'deleted' not in dict: + address = leaf_node_changed(config, base + [ifname, 'vif', vif, 'address']) + if address: dict['vif'][vif].update({'address_old' : address}) + + # If interface does not request an IPv4 DHCP address there is no need + # to keep the dhcp-options key + if 'address' not in dict['vif'][vif] or 'dhcp' not in dict['vif'][vif]['address']: + if 'dhcp_options' in dict['vif'][vif]: + del dict['vif'][vif]['dhcp_options'] + + # Check if we are a member of a bridge device + bridge = is_member(config, f'{ifname}.{vif}', 'bridge') + if bridge: dict['vif'][vif].update({'is_bridge_member' : bridge}) + + # Check if any DHCP options changed which require a client restat + dhcp = is_node_changed(config, base + [ifname, 'vif', vif, 'dhcp-options']) + if dhcp: dict['vif'][vif].update({'dhcp_options_changed' : {}}) + + for vif_s, vif_s_config in dict.get('vif_s', {}).items(): + # Add subinterface name to dictionary + dict['vif_s'][vif_s].update({'ifname' : f'{ifname}.{vif_s}'}) + + if config.exists(['qos', 'interface', f'{ifname}.{vif_s}']): + dict['vif_s'][vif_s].update({'traffic_policy': {}}) + + if 'deleted' not in dict: + address = leaf_node_changed(config, base + [ifname, 'vif-s', vif_s, 'address']) + if address: dict['vif_s'][vif_s].update({'address_old' : address}) + + # If interface does not request an IPv4 DHCP address there is no need + # to keep the dhcp-options key + if 'address' not in dict['vif_s'][vif_s] or 'dhcp' not in \ + dict['vif_s'][vif_s]['address']: + if 'dhcp_options' in dict['vif_s'][vif_s]: + del dict['vif_s'][vif_s]['dhcp_options'] + + # Check if we are a member of a bridge device + bridge = is_member(config, f'{ifname}.{vif_s}', 'bridge') + if bridge: dict['vif_s'][vif_s].update({'is_bridge_member' : bridge}) + + # Check if any DHCP options changed which require a client restat + dhcp = is_node_changed(config, base + [ifname, 'vif-s', vif_s, 'dhcp-options']) + if dhcp: dict['vif_s'][vif_s].update({'dhcp_options_changed' : {}}) + + for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): + # Add subinterface name to dictionary + dict['vif_s'][vif_s]['vif_c'][vif_c].update({'ifname' : f'{ifname}.{vif_s}.{vif_c}'}) + + if config.exists(['qos', 'interface', f'{ifname}.{vif_s}.{vif_c}']): + dict['vif_s'][vif_s]['vif_c'][vif_c].update({'traffic_policy': {}}) + + if 'deleted' not in dict: + address = leaf_node_changed(config, base + [ifname, 'vif-s', vif_s, 'vif-c', vif_c, 'address']) + if address: dict['vif_s'][vif_s]['vif_c'][vif_c].update( + {'address_old' : address}) + + # If interface does not request an IPv4 DHCP address there is no need + # to keep the dhcp-options key + if 'address' not in dict['vif_s'][vif_s]['vif_c'][vif_c] or 'dhcp' \ + not in dict['vif_s'][vif_s]['vif_c'][vif_c]['address']: + if 'dhcp_options' in dict['vif_s'][vif_s]['vif_c'][vif_c]: + del dict['vif_s'][vif_s]['vif_c'][vif_c]['dhcp_options'] + + # Check if we are a member of a bridge device + bridge = is_member(config, f'{ifname}.{vif_s}.{vif_c}', 'bridge') + if bridge: dict['vif_s'][vif_s]['vif_c'][vif_c].update( + {'is_bridge_member' : bridge}) + + # Check if any DHCP options changed which require a client restat + dhcp = is_node_changed(config, base + [ifname, 'vif-s', vif_s, 'vif-c', vif_c, 'dhcp-options']) + if dhcp: dict['vif_s'][vif_s]['vif_c'][vif_c].update({'dhcp_options_changed' : {}}) + + # Check vif, vif-s/vif-c VLAN interfaces for removal + dict = get_removed_vlans(config, base + [ifname], dict) + return ifname, dict + +def get_vlan_ids(interface): + """ + Get the VLAN ID of the interface bound to the bridge + """ + vlan_ids = set() + + bridge_status = cmd('bridge -j vlan show', shell=True) + vlan_filter_status = json.loads(bridge_status) + + if vlan_filter_status is not None: + for interface_status in vlan_filter_status: + ifname = interface_status['ifname'] + if interface == ifname: + vlans_status = interface_status['vlans'] + for vlan_status in vlans_status: + vlan_id = vlan_status['vlan'] + vlan_ids.add(vlan_id) + + return vlan_ids + +def get_accel_dict(config, base, chap_secrets, with_pki=False): + """ + Common utility function to retrieve and mangle the Accel-PPP configuration + from different CLI input nodes. All Accel-PPP services have a common base + where value retrival is identical. This function must be used whenever + possible when working with Accel-PPP services! + + Return a dictionary with the necessary interface config keys. + """ + from vyos.utils.cpu import get_core_count + from vyos.template import is_ipv4 + + dict = config.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True, + with_pki=with_pki) + + # set CPUs cores to process requests + dict.update({'thread_count' : get_core_count()}) + # we need to store the path to the secrets file + dict.update({'chap_secrets_file' : chap_secrets}) + + # We can only have two IPv4 and three IPv6 nameservers - also they are + # configured in a different way in the configuration, this is why we split + # the configuration + if 'name_server' in dict: + ns_v4 = [] + ns_v6 = [] + for ns in dict['name_server']: + if is_ipv4(ns): ns_v4.append(ns) + else: ns_v6.append(ns) + + dict.update({'name_server_ipv4' : ns_v4, 'name_server_ipv6' : ns_v6}) + del dict['name_server'] + + # Check option "disable-accounting" per server and replace default value from '1813' to '0' + for server in (dict_search('authentication.radius.server', dict) or []): + if 'disable_accounting' in dict['authentication']['radius']['server'][server]: + dict['authentication']['radius']['server'][server]['acct_port'] = '0' + + return dict diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py new file mode 100644 index 0000000..b6d4a55 --- /dev/null +++ b/python/vyos/configdiff.py @@ -0,0 +1,436 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from enum import IntFlag +from enum import auto +from itertools import chain + +from vyos.config import Config +from vyos.configtree import DiffTree +from vyos.configdict import dict_merge +from vyos.utils.dict import get_sub_dict +from vyos.utils.dict import mangle_dict_keys +from vyos.utils.dict import dict_search_args +from vyos.utils.dict import dict_to_key_paths +from vyos.xml_ref import get_defaults +from vyos.xml_ref import owner +from vyos.xml_ref import priority + +class ConfigDiffError(Exception): + """ + Raised on config dict access errors, for example, calling get_value on + a non-leaf node. + """ + pass + +def enum_to_key(e): + return e.name.lower() + +class Diff(IntFlag): + MERGE = auto() + DELETE = auto() + ADD = auto() + STABLE = auto() + +ALL = Diff.MERGE | Diff.DELETE | Diff.ADD | Diff.STABLE + +requires_effective = [enum_to_key(Diff.DELETE)] +target_defaults = [enum_to_key(Diff.MERGE)] + +def _key_sets_from_dicts(session_dict, effective_dict): + session_keys = list(session_dict) + effective_keys = list(effective_dict) + + ret = {} + stable_keys = [k for k in session_keys if k in effective_keys] + + ret[enum_to_key(Diff.MERGE)] = session_keys + ret[enum_to_key(Diff.DELETE)] = [k for k in effective_keys if k not in stable_keys] + ret[enum_to_key(Diff.ADD)] = [k for k in session_keys if k not in stable_keys] + ret[enum_to_key(Diff.STABLE)] = stable_keys + + return ret + +def _dict_from_key_set(key_set, d): + # This will always be applied to a key_set obtained from a get_sub_dict, + # hence there is no possibility of KeyError, as get_sub_dict guarantees + # a return type of dict + ret = {k: d[k] for k in key_set} + + return ret + +def get_config_diff(config, key_mangling=None): + """ + Check type and return ConfigDiff instance. + """ + if not config or not isinstance(config, Config): + raise TypeError("argument must me a Config instance") + if key_mangling and not (isinstance(key_mangling, tuple) and \ + (len(key_mangling) == 2) and \ + isinstance(key_mangling[0], str) and \ + isinstance(key_mangling[1], str)): + raise ValueError("key_mangling must be a tuple of two strings") + + if hasattr(config, 'cached_diff_tree'): + diff_t = getattr(config, 'cached_diff_tree') + else: + diff_t = DiffTree(config._running_config, config._session_config) + setattr(config, 'cached_diff_tree', diff_t) + + if hasattr(config, 'cached_diff_dict'): + diff_d = getattr(config, 'cached_diff_dict') + else: + diff_d = diff_t.dict + setattr(config, 'cached_diff_dict', diff_d) + + return ConfigDiff(config, key_mangling, diff_tree=diff_t, + diff_dict=diff_d) + +def get_commit_scripts(config) -> list: + """Return the list of config scripts to be executed by commit + + Return a list of the scripts to be called by commit for the proposed + config. The list is ordered by priority for reference, however, the + actual order of execution by the commit algorithm is not reflected + (delete vs. add queue), nor needed for current use. + """ + if not config or not isinstance(config, Config): + raise TypeError("argument must me a Config instance") + + if hasattr(config, 'commit_scripts'): + return getattr(config, 'commit_scripts') + + D = get_config_diff(config) + d = D._diff_dict + s = set() + for p in chain(dict_to_key_paths(d['sub']), dict_to_key_paths(d['add'])): + p_owner = owner(p, with_tag=True) + if not p_owner: + continue + p_priority = priority(p) + if not p_priority: + # default priority in legacy commit-algorithm + p_priority = 0 + p_priority = int(p_priority) + s.add((p_priority, p_owner)) + + res = [x[1] for x in sorted(s, key=lambda x: x[0])] + setattr(config, 'commit_scripts', res) + + return res + +class ConfigDiff(object): + """ + The class of config changes as represented by comparison between the + session config dict and the effective config dict. + """ + def __init__(self, config, key_mangling=None, diff_tree=None, diff_dict=None): + self._level = config.get_level() + self._session_config_dict = config.get_cached_root_dict(effective=False) + self._effective_config_dict = config.get_cached_root_dict(effective=True) + self._key_mangling = key_mangling + + self._diff_tree = diff_tree + self._diff_dict = diff_dict + + # mirrored from Config; allow path arguments relative to level + def _make_path(self, path): + if isinstance(path, str): + path = path.split() + elif isinstance(path, list): + pass + else: + raise TypeError("Path must be a whitespace-separated string or a list") + + ret = self._level + path + return ret + + def set_level(self, path): + """ + Set the *edit level*, that is, a relative config dict path. + Once set, all operations will be relative to this path, + for example, after ``set_level("system")``, calling + ``get_value("name-server")`` is equivalent to calling + ``get_value("system name-server")`` without ``set_level``. + + Args: + path (str|list): relative config path + """ + if isinstance(path, str): + if path: + self._level = path.split() + else: + self._level = [] + elif isinstance(path, list): + self._level = path.copy() + else: + raise TypeError("Level path must be either a whitespace-separated string or a list") + + def get_level(self): + """ + Gets the current edit level. + + Returns: + str: current edit level + """ + ret = self._level.copy() + return ret + + def _mangle_dict_keys(self, config_dict): + config_dict = mangle_dict_keys(config_dict, self._key_mangling[0], + self._key_mangling[1]) + return config_dict + + def is_node_changed(self, path=[]): + if self._diff_tree is None: + raise NotImplementedError("diff_tree class not available") + + if (self._diff_tree.add.exists(self._make_path(path)) or + self._diff_tree.sub.exists(self._make_path(path))): + return True + return False + + def node_changed_presence(self, path=[]) -> bool: + if self._diff_tree is None: + raise NotImplementedError("diff_tree class not available") + + path = self._make_path(path) + before = self._diff_tree.left.exists(path) + after = self._diff_tree.right.exists(path) + return (before and not after) or (not before and after) + + def node_changed_children(self, path=[]) -> list: + if self._diff_tree is None: + raise NotImplementedError("diff_tree class not available") + + path = self._make_path(path) + add = self._diff_tree.add + sub = self._diff_tree.sub + children = set() + if add.exists(path): + children.update(add.list_nodes(path)) + if sub.exists(path): + children.update(sub.list_nodes(path)) + + return list(children) + + def get_child_nodes_diff_str(self, path=[]): + ret = {'add': {}, 'change': {}, 'delete': {}} + + diff = self.get_child_nodes_diff(path, + expand_nodes=Diff.ADD | Diff.DELETE | Diff.MERGE | Diff.STABLE, + no_defaults=True) + + def parse_dict(diff_dict, diff_type, prefix=[]): + for k, v in diff_dict.items(): + if isinstance(v, dict): + parse_dict(v, diff_type, prefix + [k]) + else: + path_str = ' '.join(prefix + [k]) + if diff_type == 'add' or diff_type == 'delete': + if isinstance(v, list): + v = ', '.join(v) + ret[diff_type][path_str] = v + elif diff_type == 'merge': + old_value = dict_search_args(diff['stable'], *prefix, k) + if old_value and old_value != v: + ret['change'][path_str] = [old_value, v] + + parse_dict(diff['merge'], 'merge') + parse_dict(diff['add'], 'add') + parse_dict(diff['delete'], 'delete') + + return ret + + def get_child_nodes_diff(self, path=[], expand_nodes=Diff(0), no_defaults=False, + recursive=False): + """ + Args: + path (str|list): config path + expand_nodes=Diff(0): bit mask of enum indicating for which nodes + to provide full dict; for example, Diff.MERGE + will expand dict['merge'] into dict under + value + no_detaults=False: if expand_nodes & Diff.MERGE, do not merge default + values to ret['merge'] + recursive: if true, use config_tree diff algorithm provided by + diff_tree class + + Returns: dict of lists, representing differences between session + and effective config, under path + dict['merge'] = session config values + dict['delete'] = effective config values, not in session + dict['add'] = session config values, not in effective + dict['stable'] = config values in both session and effective + """ + session_dict = get_sub_dict(self._session_config_dict, + self._make_path(path), get_first_key=True) + + if recursive: + if self._diff_tree is None: + raise NotImplementedError("diff_tree class not available") + else: + add = get_sub_dict(self._diff_dict, ['add'], get_first_key=True) + sub = get_sub_dict(self._diff_dict, ['sub'], get_first_key=True) + inter = get_sub_dict(self._diff_dict, ['inter'], get_first_key=True) + ret = {} + ret[enum_to_key(Diff.MERGE)] = session_dict + ret[enum_to_key(Diff.DELETE)] = get_sub_dict(sub, self._make_path(path), + get_first_key=True) + ret[enum_to_key(Diff.ADD)] = get_sub_dict(add, self._make_path(path), + get_first_key=True) + ret[enum_to_key(Diff.STABLE)] = get_sub_dict(inter, self._make_path(path), + get_first_key=True) + for e in Diff: + k = enum_to_key(e) + if not (e & expand_nodes): + ret[k] = list(ret[k]) + else: + if self._key_mangling: + ret[k] = self._mangle_dict_keys(ret[k]) + if k in target_defaults and not no_defaults: + default_values = get_defaults(self._make_path(path), + get_first_key=True, + recursive=True) + ret[k] = dict_merge(default_values, ret[k]) + return ret + + effective_dict = get_sub_dict(self._effective_config_dict, + self._make_path(path), get_first_key=True) + + ret = _key_sets_from_dicts(session_dict, effective_dict) + + if not expand_nodes: + return ret + + for e in Diff: + if expand_nodes & e: + k = enum_to_key(e) + if k in requires_effective: + ret[k] = _dict_from_key_set(ret[k], effective_dict) + else: + ret[k] = _dict_from_key_set(ret[k], session_dict) + + if self._key_mangling: + ret[k] = self._mangle_dict_keys(ret[k]) + + if k in target_defaults and not no_defaults: + default_values = get_defaults(self._make_path(path), + get_first_key=True, + recursive=True) + ret[k] = dict_merge(default_values, ret[k]) + + return ret + + def get_node_diff(self, path=[], expand_nodes=Diff(0), no_defaults=False, + recursive=False): + """ + Args: + path (str|list): config path + expand_nodes=Diff(0): bit mask of enum indicating for which nodes + to provide full dict; for example, Diff.MERGE + will expand dict['merge'] into dict under + value + no_detaults=False: if expand_nodes & Diff.MERGE, do not merge default + values to ret['merge'] + recursive: if true, use config_tree diff algorithm provided by + diff_tree class + + Returns: dict of lists, representing differences between session + and effective config, at path + dict['merge'] = session config values + dict['delete'] = effective config values, not in session + dict['add'] = session config values, not in effective + dict['stable'] = config values in both session and effective + """ + session_dict = get_sub_dict(self._session_config_dict, self._make_path(path)) + + if recursive: + if self._diff_tree is None: + raise NotImplementedError("diff_tree class not available") + else: + add = get_sub_dict(self._diff_dict, ['add'], get_first_key=True) + sub = get_sub_dict(self._diff_dict, ['sub'], get_first_key=True) + inter = get_sub_dict(self._diff_dict, ['inter'], get_first_key=True) + ret = {} + ret[enum_to_key(Diff.MERGE)] = session_dict + ret[enum_to_key(Diff.DELETE)] = get_sub_dict(sub, self._make_path(path)) + ret[enum_to_key(Diff.ADD)] = get_sub_dict(add, self._make_path(path)) + ret[enum_to_key(Diff.STABLE)] = get_sub_dict(inter, self._make_path(path)) + for e in Diff: + k = enum_to_key(e) + if not (e & expand_nodes): + ret[k] = list(ret[k]) + else: + if self._key_mangling: + ret[k] = self._mangle_dict_keys(ret[k]) + if k in target_defaults and not no_defaults: + default_values = get_defaults(self._make_path(path), + get_first_key=True, + recursive=True) + ret[k] = dict_merge(default_values, ret[k]) + return ret + + effective_dict = get_sub_dict(self._effective_config_dict, self._make_path(path)) + + ret = _key_sets_from_dicts(session_dict, effective_dict) + + if not expand_nodes: + return ret + + for e in Diff: + if expand_nodes & e: + k = enum_to_key(e) + if k in requires_effective: + ret[k] = _dict_from_key_set(ret[k], effective_dict) + else: + ret[k] = _dict_from_key_set(ret[k], session_dict) + + if self._key_mangling: + ret[k] = self._mangle_dict_keys(ret[k]) + + if k in target_defaults and not no_defaults: + default_values = get_defaults(self._make_path(path), + get_first_key=True, + recursive=True) + ret[k] = dict_merge(default_values, ret[k]) + + return ret + + def get_value_diff(self, path=[]): + """ + Args: + path (str|list): config path + + Returns: (new, old) tuple of values in session config/effective config + """ + # one should properly use is_leaf as check; for the moment we will + # deduce from type, which will not catch call on non-leaf node if None + new_value_dict = get_sub_dict(self._session_config_dict, self._make_path(path)) + old_value_dict = get_sub_dict(self._effective_config_dict, self._make_path(path)) + + new_value = None + old_value = None + if new_value_dict: + new_value = next(iter(new_value_dict.values())) + if old_value_dict: + old_value = next(iter(old_value_dict.values())) + + if new_value and isinstance(new_value, dict): + raise ConfigDiffError("get_value_changed called on non-leaf node") + if old_value and isinstance(old_value, dict): + raise ConfigDiffError("get_value_changed called on non-leaf node") + + return new_value, old_value diff --git a/python/vyos/configquery.py b/python/vyos/configquery.py new file mode 100644 index 0000000..5d6ca9b --- /dev/null +++ b/python/vyos/configquery.py @@ -0,0 +1,192 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +''' +A small library that allows querying existence or value(s) of config +settings from op mode, and execution of arbitrary op mode commands. +''' + +import os +import json +import subprocess + +from vyos.utils.process import STDOUT +from vyos.utils.process import popen + +from vyos.utils.boot import boot_configuration_complete +from vyos.config import Config +from vyos.configsource import ConfigSourceSession, ConfigSourceString +from vyos.defaults import directories +from vyos.configtree import ConfigTree +from vyos.utils.dict import embed_dict +from vyos.utils.dict import get_sub_dict +from vyos.utils.dict import mangle_dict_keys +from vyos.utils.error import cli_shell_api_err +from vyos.xml_ref import multi_to_list +from vyos.xml_ref import is_tag +from vyos.base import Warning + +config_file = os.path.join(directories['config'], 'config.boot') + +class ConfigQueryError(Exception): + pass + +class GenericConfigQuery: + def __init__(self): + pass + + def exists(self, path: list): + raise NotImplementedError + + def value(self, path: list): + raise NotImplementedError + + def values(self, path: list): + raise NotImplementedError + +class GenericOpRun: + def __init__(self): + pass + + def run(self, path: list, **kwargs): + raise NotImplementedError + +class CliShellApiConfigQuery(GenericConfigQuery): + def __init__(self): + super().__init__() + + def exists(self, path: list): + cmd = ' '.join(path) + (_, err) = popen(f'cli-shell-api existsActive {cmd}') + if err: + return False + return True + + def value(self, path: list): + cmd = ' '.join(path) + (out, err) = popen(f'cli-shell-api returnActiveValue {cmd}') + if err: + raise ConfigQueryError('No value for given path') + return out + + def values(self, path: list): + cmd = ' '.join(path) + (out, err) = popen(f'cli-shell-api returnActiveValues {cmd}') + if err: + raise ConfigQueryError('No values for given path') + return out + +class ConfigTreeQuery(GenericConfigQuery): + def __init__(self): + super().__init__() + + if boot_configuration_complete(): + config_source = ConfigSourceSession() + self.config = Config(config_source=config_source) + else: + try: + with open(config_file) as f: + config_string = f.read() + except OSError as err: + config_string = '' + + config_source = ConfigSourceString(running_config_text=config_string, + session_config_text=config_string) + self.config = Config(config_source=config_source) + + def exists(self, path: list): + return self.config.exists(path) + + def value(self, path: list): + return self.config.return_value(path) + + def values(self, path: list): + return self.config.return_values(path) + + def list_nodes(self, path: list): + return self.config.list_nodes(path) + + def get_config_dict(self, path=[], effective=False, key_mangling=None, + get_first_key=False, no_multi_convert=False, + no_tag_node_value_mangle=False): + return self.config.get_config_dict(path, effective=effective, + key_mangling=key_mangling, get_first_key=get_first_key, + no_multi_convert=no_multi_convert, + no_tag_node_value_mangle=no_tag_node_value_mangle) + +class VbashOpRun(GenericOpRun): + def __init__(self): + super().__init__() + + def run(self, path: list, **kwargs): + cmd = ' '.join(path) + (out, err) = popen(f'/opt/vyatta/bin/vyatta-op-cmd-wrapper {cmd}', stderr=STDOUT, **kwargs) + if err: + raise ConfigQueryError(out) + return out + +def query_context(config_query_class=CliShellApiConfigQuery, + op_run_class=VbashOpRun): + query = config_query_class() + run = op_run_class() + return query, run + +def verify_mangling(key_mangling): + if not (isinstance(key_mangling, tuple) and + len(key_mangling) == 2 and + isinstance(key_mangling[0], str) and + isinstance(key_mangling[1], str)): + raise ValueError("key_mangling must be a tuple of two strings") + +def op_mode_run(cmd): + """ low-level to avoid overhead """ + p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + out = p.stdout.read() + p.wait() + return p.returncode, out.decode() + +def op_mode_config_dict(path=None, key_mangling=None, + no_tag_node_value_mangle=False, + no_multi_convert=False, get_first_key=False): + + if path is None: + path = [] + command = ['/bin/cli-shell-api', '--show-active-only', 'showConfig'] + + rc, out = op_mode_run(command + path) + if rc == cli_shell_api_err.VYOS_EMPTY_CONFIG: + out = '' + if rc == cli_shell_api_err.VYOS_INVALID_PATH: + Warning(out) + return {} + + ct = ConfigTree(out) + d = json.loads(ct.to_json()) + # cli-shell-api output includes last path component if tag node + if is_tag(path): + config_dict = embed_dict(path[:-1], d) + else: + config_dict = embed_dict(path, d) + + if not no_multi_convert: + config_dict = multi_to_list([], config_dict) + + if key_mangling is not None: + verify_mangling(key_mangling) + config_dict = mangle_dict_keys(config_dict, + key_mangling[0], key_mangling[1], + no_tag_node_value_mangle=no_tag_node_value_mangle) + + return get_sub_dict(config_dict, path, get_first_key=get_first_key) diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py new file mode 100644 index 0000000..7d51b94 --- /dev/null +++ b/python/vyos/configsession.py @@ -0,0 +1,287 @@ +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or modify it under the terms of +# the GNU Lesser General Public License as published by the Free Software Foundation; +# either version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License along with this library; +# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# configsession -- the write API for the VyOS running config + +import os +import re +import sys +import subprocess + +from vyos.defaults import directories +from vyos.utils.process import is_systemd_service_running +from vyos.utils.dict import dict_to_paths + +CLI_SHELL_API = '/bin/cli-shell-api' +SET = '/opt/vyatta/sbin/my_set' +DELETE = '/opt/vyatta/sbin/my_delete' +COMMENT = '/opt/vyatta/sbin/my_comment' +COMMIT = '/opt/vyatta/sbin/my_commit' +DISCARD = '/opt/vyatta/sbin/my_discard' +SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig'] +LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile'] +MIGRATE_LOAD_CONFIG = ['/usr/libexec/vyos/vyos-load-config.py'] +SAVE_CONFIG = ['/usr/libexec/vyos/vyos-save-config.py'] +INSTALL_IMAGE = ['/usr/libexec/vyos/op_mode/image_installer.py', + '--action', 'add', '--no-prompt', '--image-path'] +IMPORT_PKI = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'import'] +IMPORT_PKI_NO_PROMPT = ['/usr/libexec/vyos/op_mode/pki.py', + '--action', 'import', '--no-prompt'] +REMOVE_IMAGE = ['/usr/libexec/vyos/op_mode/image_manager.py', + '--action', 'delete', '--no-prompt', '--image-name'] +SET_DEFAULT_IMAGE = ['/usr/libexec/vyos/op_mode/image_manager.py', + '--action', 'set', '--no-prompt', '--image-name'] +GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate'] +SHOW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'show'] +RESET = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'reset'] +REBOOT = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'reboot'] +POWEROFF = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'poweroff'] +OP_CMD_ADD = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'add'] +OP_CMD_DELETE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'delete'] + +# Default "commit via" string +APP = "vyos-http-api" + +# When started as a service rather than from a user shell, +# the process lacks the VyOS-specific environment that comes +# from bash configs, so we have to inject it +# XXX: maybe it's better to do via a systemd environment file +def inject_vyos_env(env): + env['VYATTA_CFG_GROUP_NAME'] = 'vyattacfg' + env['VYATTA_USER_LEVEL_DIR'] = '/opt/vyatta/etc/shell/level/admin' + env['VYATTA_PROCESS_CLIENT'] = 'gui2_rest' + env['VYOS_HEADLESS_CLIENT'] = 'vyos_http_api' + env['vyatta_bindir']= '/opt/vyatta/bin' + env['vyatta_cfg_templates'] = '/opt/vyatta/share/vyatta-cfg/templates' + env['vyatta_configdir'] = directories['vyos_configdir'] + env['vyatta_datadir'] = '/opt/vyatta/share' + env['vyatta_datarootdir'] = '/opt/vyatta/share' + env['vyatta_libdir'] = '/opt/vyatta/lib' + env['vyatta_libexecdir'] = '/opt/vyatta/libexec' + env['vyatta_op_templates'] = '/opt/vyatta/share/vyatta-op/templates' + env['vyatta_prefix'] = '/opt/vyatta' + env['vyatta_sbindir'] = '/opt/vyatta/sbin' + env['vyatta_sysconfdir'] = '/opt/vyatta/etc' + env['vyos_bin_dir'] = '/usr/bin' + env['vyos_cfg_templates'] = '/opt/vyatta/share/vyatta-cfg/templates' + env['vyos_completion_dir'] = '/usr/libexec/vyos/completion' + env['vyos_configdir'] = directories['vyos_configdir'] + env['vyos_conf_scripts_dir'] = '/usr/libexec/vyos/conf_mode' + env['vyos_datadir'] = '/opt/vyatta/share' + env['vyos_datarootdir']= '/opt/vyatta/share' + env['vyos_libdir'] = '/opt/vyatta/lib' + env['vyos_libexec_dir'] = '/usr/libexec/vyos' + env['vyos_op_scripts_dir'] = '/usr/libexec/vyos/op_mode' + env['vyos_op_templates'] = '/opt/vyatta/share/vyatta-op/templates' + env['vyos_prefix'] = '/opt/vyatta' + env['vyos_sbin_dir'] = '/usr/sbin' + env['vyos_validators_dir'] = '/usr/libexec/vyos/validators' + + # if running the vyos-configd daemon, inject the vyshim env var + if is_systemd_service_running('vyos-configd.service'): + env['vyshim'] = '/usr/sbin/vyshim' + + return env + + +class ConfigSessionError(Exception): + pass + + +class ConfigSession(object): + """ + The write API of VyOS. + """ + def __init__(self, session_id, app=APP): + """ + Creates a new config session. + + Args: + session_id (str): Session identifier + app (str): Application name, purely informational + + Note: + The session identifier MUST be globally unique within the system. + The best practice is to only have one ConfigSession object per process + and used the PID for the session identifier. + """ + + env_str = subprocess.check_output([CLI_SHELL_API, 'getSessionEnv', str(session_id)]) + self.__session_id = session_id + + # Extract actual variables from the chunk of shell it outputs + # XXX: it's better to extend cli-shell-api to provide easily readable output + env_list = re.findall(r'([A-Z_]+)=([^;\s]+)', env_str.decode()) + + session_env = os.environ + session_env = inject_vyos_env(session_env) + for k, v in env_list: + session_env[k] = v + + self.__session_env = session_env + self.__session_env["COMMIT_VIA"] = app + + self.__run_command([CLI_SHELL_API, 'setupSession']) + + def __del__(self): + try: + output = subprocess.check_output([CLI_SHELL_API, 'teardownSession'], env=self.__session_env).decode().strip() + if output: + print("cli-shell-api teardownSession output for sesion {0}: {1}".format(self.__session_id, output), file=sys.stderr) + except Exception as e: + print("Could not tear down session {0}: {1}".format(self.__session_id, e), file=sys.stderr) + + def __run_command(self, cmd_list): + p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=self.__session_env) + (stdout_data, stderr_data) = p.communicate() + output = stdout_data.decode() + result = p.wait() + if result != 0: + raise ConfigSessionError(output) + return output + + def get_session_env(self): + return self.__session_env + + def set(self, path, value=None): + if not value: + value = [] + else: + value = [value] + self.__run_command([SET] + path + value) + + def set_section(self, path: list, d: dict): + try: + for p in dict_to_paths(d): + self.set(path + p) + except (ValueError, ConfigSessionError) as e: + raise ConfigSessionError(e) + + def delete(self, path, value=None): + if not value: + value = [] + else: + value = [value] + self.__run_command([DELETE] + path + value) + + def load_section(self, path: list, d: dict): + try: + self.delete(path) + if d: + for p in dict_to_paths(d): + self.set(path + p) + except (ValueError, ConfigSessionError) as e: + raise ConfigSessionError(e) + + def set_section_tree(self, d: dict): + try: + if d: + for p in dict_to_paths(d): + self.set(p) + except (ValueError, ConfigSessionError) as e: + raise ConfigSessionError(e) + + def load_section_tree(self, mask: dict, d: dict): + try: + if mask: + for p in dict_to_paths(mask): + self.delete(p) + if d: + for p in dict_to_paths(d): + self.set(p) + except (ValueError, ConfigSessionError) as e: + raise ConfigSessionError(e) + + def comment(self, path, value=None): + if not value: + value = [""] + else: + value = [value] + self.__run_command([COMMENT] + path + value) + + def commit(self): + out = self.__run_command([COMMIT]) + return out + + def discard(self): + self.__run_command([DISCARD]) + + def show_config(self, path, format='raw'): + config_data = self.__run_command(SHOW_CONFIG + path) + + if format == 'raw': + return config_data + + def load_config(self, file_path): + out = self.__run_command(LOAD_CONFIG + [file_path]) + return out + + def migrate_and_load_config(self, file_path): + out = self.__run_command(MIGRATE_LOAD_CONFIG + [file_path]) + return out + + def save_config(self, file_path): + out = self.__run_command(SAVE_CONFIG + [file_path]) + return out + + def install_image(self, url): + out = self.__run_command(INSTALL_IMAGE + [url]) + return out + + def remove_image(self, name): + out = self.__run_command(REMOVE_IMAGE + [name]) + return out + + def import_pki(self, path): + out = self.__run_command(IMPORT_PKI + path) + return out + + def import_pki_no_prompt(self, path): + out = self.__run_command(IMPORT_PKI_NO_PROMPT + path) + return out + + def set_default_image(self, name): + out = self.__run_command(SET_DEFAULT_IMAGE + [name]) + return out + + def generate(self, path): + out = self.__run_command(GENERATE + path) + return out + + def show(self, path): + out = self.__run_command(SHOW + path) + return out + + def reboot(self, path): + out = self.__run_command(REBOOT + path) + return out + + def reset(self, path): + out = self.__run_command(RESET + path) + return out + + def poweroff(self, path): + out = self.__run_command(POWEROFF + path) + return out + + def add_container_image(self, name): + out = self.__run_command(OP_CMD_ADD + ['container', 'image'] + [name]) + return out + + def delete_container_image(self, name): + out = self.__run_command(OP_CMD_DELETE + ['container', 'image'] + [name]) + return out + + def show_container_image(self): + out = self.__run_command(SHOW + ['container', 'image']) + return out diff --git a/python/vyos/configsource.py b/python/vyos/configsource.py new file mode 100644 index 0000000..59e5ac8 --- /dev/null +++ b/python/vyos/configsource.py @@ -0,0 +1,321 @@ + +# Copyright 2020-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +import re +import subprocess + +from vyos.configtree import ConfigTree +from vyos.utils.boot import boot_configuration_complete + +class VyOSError(Exception): + """ + Raised on config access errors. + """ + pass + +class ConfigSourceError(Exception): + ''' + Raised on error in ConfigSource subclass init. + ''' + pass + +class ConfigSource: + def __init__(self): + self._running_config: ConfigTree = None + self._session_config: ConfigTree = None + + def get_configtree_tuple(self): + return self._running_config, self._session_config + + def session_changed(self): + """ + Returns: + True if the config session has uncommited changes, False otherwise. + """ + raise NotImplementedError(f"function not available for {type(self)}") + + def in_session(self): + """ + Returns: + True if called from a configuration session, False otherwise. + """ + raise NotImplementedError(f"function not available for {type(self)}") + + def show_config(self, path=[], default=None, effective=False): + """ + Args: + path (str|list): Configuration tree path, or empty + default (str): Default value to return + + Returns: + str: working configuration + """ + raise NotImplementedError(f"function not available for {type(self)}") + + def is_multi(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node can have multiple values, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ + raise NotImplementedError(f"function not available for {type(self)}") + + def is_tag(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node is a tag node, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ + raise NotImplementedError(f"function not available for {type(self)}") + + def is_leaf(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node is a leaf node, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ + raise NotImplementedError(f"function not available for {type(self)}") + +class ConfigSourceSession(ConfigSource): + def __init__(self, session_env=None): + super().__init__() + self._cli_shell_api = "/bin/cli-shell-api" + self._level = [] + if session_env: + self.__session_env = session_env + else: + self.__session_env = None + + # Running config can be obtained either from op or conf mode, it always succeeds + # once the config system is initialized during boot; + # before initialization, set to empty string + if boot_configuration_complete(): + try: + running_config_text = self._run([self._cli_shell_api, '--show-active-only', '--show-show-defaults', '--show-ignore-edit', 'showConfig']) + except VyOSError: + running_config_text = '' + else: + running_config_text = '' + + # Session config ("active") only exists in conf mode. + # In op mode, we'll just use the same running config for both active and session configs. + if self.in_session(): + try: + session_config_text = self._run([self._cli_shell_api, '--show-working-only', '--show-show-defaults', '--show-ignore-edit', 'showConfig']) + except VyOSError: + session_config_text = '' + else: + session_config_text = running_config_text + + if running_config_text: + self._running_config = ConfigTree(running_config_text) + else: + self._running_config = None + + if session_config_text: + self._session_config = ConfigTree(session_config_text) + else: + self._session_config = None + + def _make_command(self, op, path): + args = path.split() + cmd = [self._cli_shell_api, op] + args + return cmd + + def _run(self, cmd): + if self.__session_env: + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=self.__session_env) + else: + p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + out = p.stdout.read() + p.wait() + p.communicate() + if p.returncode != 0: + raise VyOSError() + else: + return out.decode() + + def set_level(self, path): + """ + Set the *edit level*, that is, a relative config tree path. + Once set, all operations will be relative to this path, + for example, after ``set_level("system")``, calling + ``exists("name-server")`` is equivalent to calling + ``exists("system name-server"`` without ``set_level``. + + Args: + path (str|list): relative config path + """ + # Make sure there's always a space between default path (level) + # and path supplied as method argument + # XXX: for small strings in-place concatenation is not a problem + if isinstance(path, str): + if path: + self._level = re.split(r'\s+', path) + else: + self._level = [] + elif isinstance(path, list): + self._level = path.copy() + else: + raise TypeError("Level path must be either a whitespace-separated string or a list") + + def session_changed(self): + """ + Returns: + True if the config session has uncommited changes, False otherwise. + """ + try: + self._run(self._make_command('sessionChanged', '')) + return True + except VyOSError: + return False + + def in_session(self): + """ + Returns: + True if called from a configuration session, False otherwise. + """ + if os.getenv('VYOS_CONFIGD', ''): + return False + try: + self._run(self._make_command('inSession', '')) + return True + except VyOSError: + return False + + def show_config(self, path=[], default=None, effective=False): + """ + Args: + path (str|list): Configuration tree path, or empty + default (str): Default value to return + + Returns: + str: working configuration + """ + + # show_config should be independent of CLI edit level. + # Set the CLI edit environment to the top level, and + # restore original on exit. + save_env = self.__session_env + + env_str = self._run(self._make_command('getEditResetEnv', '')) + env_list = re.findall(r'([A-Z_]+)=\'([^;\s]+)\'', env_str) + root_env = os.environ + for k, v in env_list: + root_env[k] = v + + self.__session_env = root_env + + # FIXUP: by default, showConfig will give you a diff + # if there are uncommitted changes. + # The config parser obviously cannot work with diffs, + # so we need to supress diff production using appropriate + # options for getting either running (active) + # or proposed (working) config. + if effective: + path = ['--show-active-only'] + path + else: + path = ['--show-working-only'] + path + + if isinstance(path, list): + path = " ".join(path) + try: + out = self._run(self._make_command('showConfig', path)) + self.__session_env = save_env + return out + except VyOSError: + self.__session_env = save_env + return(default) + + def is_multi(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node can have multiple values, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ + try: + path = " ".join(self._level) + " " + path + self._run(self._make_command('isMulti', path)) + return True + except VyOSError: + return False + + def is_tag(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node is a tag node, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ + try: + path = " ".join(self._level) + " " + path + self._run(self._make_command('isTag', path)) + return True + except VyOSError: + return False + + def is_leaf(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node is a leaf node, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ + try: + path = " ".join(self._level) + " " + path + self._run(self._make_command('isLeaf', path)) + return True + except VyOSError: + return False + +class ConfigSourceString(ConfigSource): + def __init__(self, running_config_text=None, session_config_text=None): + super().__init__() + + try: + self._running_config = ConfigTree(running_config_text) if running_config_text else None + self._session_config = ConfigTree(session_config_text) if session_config_text else None + except ValueError: + raise ConfigSourceError(f"Init error in {type(self)}") diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py new file mode 100644 index 0000000..bd77ab8 --- /dev/null +++ b/python/vyos/configtree.py @@ -0,0 +1,498 @@ +# configtree -- a standalone VyOS config file manipulation library (Python bindings) +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or modify it under the terms of +# the GNU Lesser General Public License as published by the Free Software Foundation; +# either version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License along with this library; +# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import os +import re +import json +import logging + +from ctypes import cdll, c_char_p, c_void_p, c_int, c_bool + +LIBPATH = '/usr/lib/libvyosconfig.so.0' + +def replace_backslash(s, search, replace): + """Modify quoted strings containing backslashes not of escape sequences""" + def replace_method(match): + result = match.group().replace(search, replace) + return result + p = re.compile(r'("[^"]*[\\][^"]*"\n|\'[^\']*[\\][^\']*\'\n)') + return p.sub(replace_method, s) + +def escape_backslash(string: str) -> str: + """Escape single backslashes in quoted strings""" + result = replace_backslash(string, '\\', '\\\\') + return result + +def unescape_backslash(string: str) -> str: + """Unescape backslashes in quoted strings""" + result = replace_backslash(string, '\\\\', '\\') + return result + +def extract_version(s): + """ Extract the version string from the config string """ + t = re.split('(^//)', s, maxsplit=1, flags=re.MULTILINE) + return (s, ''.join(t[1:])) + +def check_path(path): + # Necessary type checking + if not isinstance(path, list): + raise TypeError("Expected a list, got a {}".format(type(path))) + else: + pass + + +class ConfigTreeError(Exception): + pass + + +class ConfigTree(object): + def __init__(self, config_string=None, address=None, libpath=LIBPATH): + if config_string is None and address is None: + raise TypeError("ConfigTree() requires one of 'config_string' or 'address'") + self.__config = None + self.__lib = cdll.LoadLibrary(libpath) + + # Import functions + self.__from_string = self.__lib.from_string + self.__from_string.argtypes = [c_char_p] + self.__from_string.restype = c_void_p + + self.__get_error = self.__lib.get_error + self.__get_error.argtypes = [] + self.__get_error.restype = c_char_p + + self.__to_string = self.__lib.to_string + self.__to_string.argtypes = [c_void_p, c_bool] + self.__to_string.restype = c_char_p + + self.__to_commands = self.__lib.to_commands + self.__to_commands.argtypes = [c_void_p, c_char_p] + self.__to_commands.restype = c_char_p + + self.__to_json = self.__lib.to_json + self.__to_json.argtypes = [c_void_p] + self.__to_json.restype = c_char_p + + self.__to_json_ast = self.__lib.to_json_ast + self.__to_json_ast.argtypes = [c_void_p] + self.__to_json_ast.restype = c_char_p + + self.__set_add_value = self.__lib.set_add_value + self.__set_add_value.argtypes = [c_void_p, c_char_p, c_char_p] + self.__set_add_value.restype = c_int + + self.__delete_value = self.__lib.delete_value + self.__delete_value.argtypes = [c_void_p, c_char_p, c_char_p] + self.__delete_value.restype = c_int + + self.__delete = self.__lib.delete_node + self.__delete.argtypes = [c_void_p, c_char_p] + self.__delete.restype = c_int + + self.__rename = self.__lib.rename_node + self.__rename.argtypes = [c_void_p, c_char_p, c_char_p] + self.__rename.restype = c_int + + self.__copy = self.__lib.copy_node + self.__copy.argtypes = [c_void_p, c_char_p, c_char_p] + self.__copy.restype = c_int + + self.__set_replace_value = self.__lib.set_replace_value + self.__set_replace_value.argtypes = [c_void_p, c_char_p, c_char_p] + self.__set_replace_value.restype = c_int + + self.__set_valueless = self.__lib.set_valueless + self.__set_valueless.argtypes = [c_void_p, c_char_p] + self.__set_valueless.restype = c_int + + self.__exists = self.__lib.exists + self.__exists.argtypes = [c_void_p, c_char_p] + self.__exists.restype = c_int + + self.__list_nodes = self.__lib.list_nodes + self.__list_nodes.argtypes = [c_void_p, c_char_p] + self.__list_nodes.restype = c_char_p + + self.__return_value = self.__lib.return_value + self.__return_value.argtypes = [c_void_p, c_char_p] + self.__return_value.restype = c_char_p + + self.__return_values = self.__lib.return_values + self.__return_values.argtypes = [c_void_p, c_char_p] + self.__return_values.restype = c_char_p + + self.__is_tag = self.__lib.is_tag + self.__is_tag.argtypes = [c_void_p, c_char_p] + self.__is_tag.restype = c_int + + self.__set_tag = self.__lib.set_tag + self.__set_tag.argtypes = [c_void_p, c_char_p] + self.__set_tag.restype = c_int + + self.__get_subtree = self.__lib.get_subtree + self.__get_subtree.argtypes = [c_void_p, c_char_p] + self.__get_subtree.restype = c_void_p + + self.__destroy = self.__lib.destroy + self.__destroy.argtypes = [c_void_p] + + if address is None: + config_section, version_section = extract_version(config_string) + config_section = escape_backslash(config_section) + config = self.__from_string(config_section.encode()) + if config is None: + msg = self.__get_error().decode() + raise ValueError("Failed to parse config: {0}".format(msg)) + else: + self.__config = config + self.__version = version_section + else: + self.__config = address + self.__version = '' + + self.__migration = os.environ.get('VYOS_MIGRATION') + if self.__migration: + self.migration_log = logging.getLogger('vyos.migrate') + + def __del__(self): + if self.__config is not None: + self.__destroy(self.__config) + + def __str__(self): + return self.to_string() + + def _get_config(self): + return self.__config + + def get_version_string(self): + return self.__version + + def to_string(self, ordered_values=False, no_version=False): + config_string = self.__to_string(self.__config, ordered_values).decode() + config_string = unescape_backslash(config_string) + if no_version: + return config_string + config_string = "{0}\n{1}".format(config_string, self.__version) + return config_string + + def to_commands(self, op="set"): + commands = self.__to_commands(self.__config, op.encode()).decode() + commands = unescape_backslash(commands) + return commands + + def to_json(self): + return self.__to_json(self.__config).decode() + + def to_json_ast(self): + return self.__to_json_ast(self.__config).decode() + + def set(self, path, value=None, replace=True): + """Set new entry in VyOS configuration. + path: configuration path e.g. 'system dns forwarding listen-address' + value: value to be added to node, e.g. '172.18.254.201' + replace: True: current occurance will be replaced + False: new value will be appended to current occurances - use + this for adding values to a multi node + """ + + check_path(path) + path_str = " ".join(map(str, path)).encode() + + if value is None: + self.__set_valueless(self.__config, path_str) + else: + if replace: + self.__set_replace_value(self.__config, path_str, str(value).encode()) + else: + self.__set_add_value(self.__config, path_str, str(value).encode()) + + if self.__migration: + self.migration_log.info(f"- op: set path: {path} value: {value} replace: {replace}") + + def delete(self, path): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res = self.__delete(self.__config, path_str) + if (res != 0): + raise ConfigTreeError(f"Path doesn't exist: {path}") + + if self.__migration: + self.migration_log.info(f"- op: delete path: {path}") + + def delete_value(self, path, value): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res = self.__delete_value(self.__config, path_str, value.encode()) + if (res != 0): + if res == 1: + raise ConfigTreeError(f"Path doesn't exist: {path}") + elif res == 2: + raise ConfigTreeError(f"Value doesn't exist: '{value}'") + else: + raise ConfigTreeError() + + if self.__migration: + self.migration_log.info(f"- op: delete_value path: {path} value: {value}") + + def rename(self, path, new_name): + check_path(path) + path_str = " ".join(map(str, path)).encode() + newname_str = new_name.encode() + + # Check if a node with intended new name already exists + new_path = path[:-1] + [new_name] + if self.exists(new_path): + raise ConfigTreeError() + res = self.__rename(self.__config, path_str, newname_str) + if (res != 0): + raise ConfigTreeError("Path [{}] doesn't exist".format(path)) + + if self.__migration: + self.migration_log.info(f"- op: rename old_path: {path} new_path: {new_path}") + + def copy(self, old_path, new_path): + check_path(old_path) + check_path(new_path) + oldpath_str = " ".join(map(str, old_path)).encode() + newpath_str = " ".join(map(str, new_path)).encode() + + # Check if a node with intended new name already exists + if self.exists(new_path): + raise ConfigTreeError() + res = self.__copy(self.__config, oldpath_str, newpath_str) + if (res != 0): + msg = self.__get_error().decode() + raise ConfigTreeError(msg) + + if self.__migration: + self.migration_log.info(f"- op: copy old_path: {old_path} new_path: {new_path}") + + def exists(self, path): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res = self.__exists(self.__config, path_str) + if (res == 0): + return False + else: + return True + + def list_nodes(self, path, path_must_exist=True): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res_json = self.__list_nodes(self.__config, path_str).decode() + res = json.loads(res_json) + + if res is None: + if path_must_exist: + raise ConfigTreeError("Path [{}] doesn't exist".format(path_str)) + else: + return [] + else: + return res + + def return_value(self, path): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res_json = self.__return_value(self.__config, path_str).decode() + res = json.loads(res_json) + + if res is None: + raise ConfigTreeError("Path [{}] doesn't exist".format(path_str)) + else: + return res + + def return_values(self, path): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res_json = self.__return_values(self.__config, path_str).decode() + res = json.loads(res_json) + + if res is None: + raise ConfigTreeError("Path [{}] doesn't exist".format(path_str)) + else: + return res + + def is_tag(self, path): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res = self.__is_tag(self.__config, path_str) + if (res >= 1): + return True + else: + return False + + def set_tag(self, path): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res = self.__set_tag(self.__config, path_str) + if (res == 0): + return True + else: + raise ConfigTreeError("Path [{}] doesn't exist".format(path_str)) + + def get_subtree(self, path, with_node=False): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res = self.__get_subtree(self.__config, path_str, with_node) + subt = ConfigTree(address=res) + return subt + +def show_diff(left, right, path=[], commands=False, libpath=LIBPATH): + if left is None: + left = ConfigTree(config_string='\n') + if right is None: + right = ConfigTree(config_string='\n') + if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)): + raise TypeError("Arguments must be instances of ConfigTree") + if path: + if (not left.exists(path)) and (not right.exists(path)): + raise ConfigTreeError(f"Path {path} doesn't exist") + + check_path(path) + path_str = " ".join(map(str, path)).encode() + + __lib = cdll.LoadLibrary(libpath) + __show_diff = __lib.show_diff + __show_diff.argtypes = [c_bool, c_char_p, c_void_p, c_void_p] + __show_diff.restype = c_char_p + __get_error = __lib.get_error + __get_error.argtypes = [] + __get_error.restype = c_char_p + + res = __show_diff(commands, path_str, left._get_config(), right._get_config()) + res = res.decode() + if res == "#1@": + msg = __get_error().decode() + raise ConfigTreeError(msg) + + res = unescape_backslash(res) + return res + +def union(left, right, libpath=LIBPATH): + if left is None: + left = ConfigTree(config_string='\n') + if right is None: + right = ConfigTree(config_string='\n') + if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)): + raise TypeError("Arguments must be instances of ConfigTree") + + __lib = cdll.LoadLibrary(libpath) + __tree_union = __lib.tree_union + __tree_union.argtypes = [c_void_p, c_void_p] + __tree_union.restype = c_void_p + __get_error = __lib.get_error + __get_error.argtypes = [] + __get_error.restype = c_char_p + + res = __tree_union( left._get_config(), right._get_config()) + tree = ConfigTree(address=res) + + return tree + +def mask_inclusive(left, right, libpath=LIBPATH): + if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)): + raise TypeError("Arguments must be instances of ConfigTree") + + try: + __lib = cdll.LoadLibrary(libpath) + __mask_tree = __lib.mask_tree + __mask_tree.argtypes = [c_void_p, c_void_p] + __mask_tree.restype = c_void_p + __get_error = __lib.get_error + __get_error.argtypes = [] + __get_error.restype = c_char_p + + res = __mask_tree(left._get_config(), right._get_config()) + except Exception as e: + raise ConfigTreeError(e) + if not res: + msg = __get_error().decode() + raise ConfigTreeError(msg) + + tree = ConfigTree(address=res) + + return tree + +def reference_tree_to_json(from_dir, to_file, libpath=LIBPATH): + try: + __lib = cdll.LoadLibrary(libpath) + __reference_tree_to_json = __lib.reference_tree_to_json + __reference_tree_to_json.argtypes = [c_char_p, c_char_p] + __get_error = __lib.get_error + __get_error.argtypes = [] + __get_error.restype = c_char_p + res = __reference_tree_to_json(from_dir.encode(), to_file.encode()) + except Exception as e: + raise ConfigTreeError(e) + if res == 1: + msg = __get_error().decode() + raise ConfigTreeError(msg) + +class DiffTree: + def __init__(self, left, right, path=[], libpath=LIBPATH): + if left is None: + left = ConfigTree(config_string='\n') + if right is None: + right = ConfigTree(config_string='\n') + if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)): + raise TypeError("Arguments must be instances of ConfigTree") + if path: + if not left.exists(path): + raise ConfigTreeError(f"Path {path} doesn't exist in lhs tree") + if not right.exists(path): + raise ConfigTreeError(f"Path {path} doesn't exist in rhs tree") + + self.left = left + self.right = right + + self.__lib = cdll.LoadLibrary(libpath) + + self.__diff_tree = self.__lib.diff_tree + self.__diff_tree.argtypes = [c_char_p, c_void_p, c_void_p] + self.__diff_tree.restype = c_void_p + + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res = self.__diff_tree(path_str, left._get_config(), right._get_config()) + + # full diff config_tree and python dict representation + self.full = ConfigTree(address=res) + self.dict = json.loads(self.full.to_json()) + + # config_tree sub-trees + self.add = self.full.get_subtree(['add']) + self.sub = self.full.get_subtree(['sub']) + self.inter = self.full.get_subtree(['inter']) + self.delete = self.full.get_subtree(['del']) + + def to_commands(self): + add = self.add.to_commands() + delete = self.delete.to_commands(op="delete") + return delete + "\n" + add + +def deep_copy(config_tree: ConfigTree) -> ConfigTree: + """An inelegant, but reasonably fast, copy; replace with backend copy + """ + D = DiffTree(None, config_tree) + return D.add diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py new file mode 100644 index 0000000..92996f2 --- /dev/null +++ b/python/vyos/configverify.py @@ -0,0 +1,539 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +# The sole purpose of this module is to hold common functions used in +# all kinds of implementations to verify the CLI configuration. +# It is started by migrating the interfaces to the new get_config_dict() +# approach which will lead to a lot of code that can be reused. + +# NOTE: imports should be as local as possible to the function which +# makes use of it! + +from vyos import ConfigError +from vyos.utils.dict import dict_search +# pattern re-used in ipsec migration script +dynamic_interface_pattern = r'(ppp|pppoe|sstpc|l2tp|ipoe)[0-9]+' + +def verify_mtu(config): + """ + Common helper function used by interface implementations to perform + recurring validation if the specified MTU can be used by the underlaying + hardware. + """ + from vyos.ifconfig import Interface + if 'mtu' in config: + mtu = int(config['mtu']) + + tmp = Interface(config['ifname']) + # Not all interfaces support min/max MTU + # https://vyos.dev/T5011 + try: + min_mtu = tmp.get_min_mtu() + max_mtu = tmp.get_max_mtu() + except: # Fallback to defaults + min_mtu = 68 + max_mtu = 9000 + + if mtu < min_mtu: + raise ConfigError(f'Interface MTU too low, ' \ + f'minimum supported MTU is {min_mtu}!') + if mtu > max_mtu: + raise ConfigError(f'Interface MTU too high, ' \ + f'maximum supported MTU is {max_mtu}!') + +def verify_mtu_parent(config, parent): + if 'mtu' not in config or 'mtu' not in parent: + return + + mtu = int(config['mtu']) + parent_mtu = int(parent['mtu']) + if mtu > parent_mtu: + raise ConfigError(f'Interface MTU "{mtu}" too high, ' \ + f'parent interface MTU is "{parent_mtu}"!') + +def verify_mtu_ipv6(config): + """ + Common helper function used by interface implementations to perform + recurring validation if the specified MTU can be used when IPv6 is + configured on the interface. IPv6 requires a 1280 bytes MTU. + """ + from vyos.template import is_ipv6 + if 'mtu' in config: + # IPv6 minimum required link mtu + min_mtu = 1280 + if int(config['mtu']) < min_mtu: + interface = config['ifname'] + error_msg = f'IPv6 address will be configured on interface "{interface}",\n' \ + f'the required minimum MTU is "{min_mtu}"!' + + if 'address' in config: + for address in config['address']: + if address in ['dhcpv6'] or is_ipv6(address): + raise ConfigError(error_msg) + + tmp = dict_search('ipv6.address.no_default_link_local', config) + if tmp == None: raise ConfigError('link-local ' + error_msg) + + tmp = dict_search('ipv6.address.autoconf', config) + if tmp != None: raise ConfigError(error_msg) + + tmp = dict_search('ipv6.address.eui64', config) + if tmp != None: raise ConfigError(error_msg) + +def verify_vrf(config): + """ + Common helper function used by interface implementations to perform + recurring validation of VRF configuration. + """ + from vyos.utils.network import interface_exists + if 'vrf' in config: + vrfs = config['vrf'] + if isinstance(vrfs, str): + vrfs = [vrfs] + + for vrf in vrfs: + if vrf == 'default': + continue + if not interface_exists(vrf): + raise ConfigError(f'VRF "{vrf}" does not exist!') + + if 'is_bridge_member' in config: + raise ConfigError( + 'Interface "{ifname}" cannot be both a member of VRF "{vrf}" ' + 'and bridge "{is_bridge_member}"!'.format(**config)) + +def verify_bond_bridge_member(config): + """ + Checks if interface has a VRF configured and is also part of a bond or + bridge, which is not allowed! + """ + if 'vrf' in config: + ifname = config['ifname'] + if 'is_bond_member' in config: + raise ConfigError(f'Can not add interface "{ifname}" to bond, it has a VRF assigned!') + if 'is_bridge_member' in config: + raise ConfigError(f'Can not add interface "{ifname}" to bridge, it has a VRF assigned!') + +def verify_tunnel(config): + """ + This helper is used to verify the common part of the tunnel + """ + from vyos.template import is_ipv4 + from vyos.template import is_ipv6 + + if 'encapsulation' not in config: + raise ConfigError('Must configure the tunnel encapsulation for '\ + '{ifname}!'.format(**config)) + + if 'source_address' not in config and 'source_interface' not in config: + raise ConfigError('source-address or source-interface required for tunnel!') + + if 'remote' not in config and config['encapsulation'] != 'gre': + raise ConfigError('remote ip address is mandatory for tunnel') + + if config['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre', 'ip6gretap', 'ip6erspan']: + error_ipv6 = 'Encapsulation mode requires IPv6' + if 'source_address' in config and not is_ipv6(config['source_address']): + raise ConfigError(f'{error_ipv6} source-address') + + if 'remote' in config and not is_ipv6(config['remote']): + raise ConfigError(f'{error_ipv6} remote') + else: + error_ipv4 = 'Encapsulation mode requires IPv4' + if 'source_address' in config and not is_ipv4(config['source_address']): + raise ConfigError(f'{error_ipv4} source-address') + + if 'remote' in config and not is_ipv4(config['remote']): + raise ConfigError(f'{error_ipv4} remote address') + + if config['encapsulation'] in ['sit', 'gretap', 'ip6gretap']: + if 'source_interface' in config: + encapsulation = config['encapsulation'] + raise ConfigError(f'Option source-interface can not be used with ' \ + f'encapsulation "{encapsulation}"!') + elif config['encapsulation'] == 'gre': + if 'source_address' in config and is_ipv6(config['source_address']): + raise ConfigError('Can not use local IPv6 address is for mGRE tunnels') + +def verify_mirror_redirect(config): + """ + Common helper function used by interface implementations to perform + recurring validation of mirror and redirect interface configuration via tc(8) + + It makes no sense to mirror traffic back at yourself! + """ + from vyos.utils.network import interface_exists + if {'mirror', 'redirect'} <= set(config): + raise ConfigError('Mirror and redirect can not be enabled at the same time!') + + if 'mirror' in config: + for direction, mirror_interface in config['mirror'].items(): + if not interface_exists(mirror_interface): + raise ConfigError(f'Requested mirror interface "{mirror_interface}" '\ + 'does not exist!') + + if mirror_interface == config['ifname']: + raise ConfigError(f'Can not mirror "{direction}" traffic back '\ + 'the originating interface!') + + if 'redirect' in config: + redirect_ifname = config['redirect'] + if not interface_exists(redirect_ifname): + raise ConfigError(f'Requested redirect interface "{redirect_ifname}" '\ + 'does not exist!') + + if ('mirror' in config or 'redirect' in config) and dict_search('traffic_policy.in', config) is not None: + # XXX: support combination of limiting and redirect/mirror - this is an + # artificial limitation + raise ConfigError('Can not use ingress policy together with mirror or redirect!') + +def verify_authentication(config): + """ + Common helper function used by interface implementations to perform + recurring validation of authentication for either PPPoE or WWAN interfaces. + + If authentication CLI option is defined, both username and password must + be set! + """ + if 'authentication' not in config: + return + if not {'username', 'password'} <= set(config['authentication']): + raise ConfigError('Authentication requires both username and ' \ + 'password to be set!') + +def verify_address(config): + """ + Common helper function used by interface implementations to perform + recurring validation of IP address assignment when interface is part + of a bridge or bond. + """ + if {'is_bridge_member', 'address'} <= set(config): + interface = config['ifname'] + bridge_name = next(iter(config['is_bridge_member'])) + raise ConfigError(f'Cannot assign address to interface "{interface}" ' + f'as it is a member of bridge "{bridge_name}"!') + +def verify_bridge_delete(config): + """ + Common helper function used by interface implementations to + perform recurring validation of IP address assignmenr + when interface also is part of a bridge. + """ + if 'is_bridge_member' in config: + interface = config['ifname'] + bridge_name = next(iter(config['is_bridge_member'])) + raise ConfigError(f'Interface "{interface}" cannot be deleted as it ' + f'is a member of bridge "{bridge_name}"!') + +def verify_interface_exists(config, ifname, state_required=False, warning_only=False): + """ + Common helper function used by interface implementations to perform + recurring validation if an interface actually exists. We first probe + if the interface is defined on the CLI, if it's not found we try if + it exists at the OS level. + """ + from vyos.base import Warning + from vyos.utils.dict import dict_search_recursive + from vyos.utils.network import interface_exists + + if not state_required: + # Check if interface is present in CLI config + tmp = getattr(config, 'interfaces_root', {}) + if bool(list(dict_search_recursive(tmp, ifname))): + return True + + # Interface not found on CLI, try Linux Kernel + if interface_exists(ifname): + return True + + message = f'Interface "{ifname}" does not exist!' + if warning_only: + Warning(message) + return False + raise ConfigError(message) + +def verify_source_interface(config): + """ + Common helper function used by interface implementations to + perform recurring validation of the existence of a source-interface + required by e.g. peth/MACvlan, MACsec ... + """ + import re + from vyos.utils.network import interface_exists + + ifname = config['ifname'] + if 'source_interface' not in config: + raise ConfigError(f'Physical source-interface required for "{ifname}"!') + + src_ifname = config['source_interface'] + # We do not allow sourcing other interfaces (e.g. tunnel) from dynamic interfaces + tmp = re.compile(dynamic_interface_pattern) + if tmp.match(src_ifname): + raise ConfigError(f'Can not source "{ifname}" from dynamic interface "{src_ifname}"!') + + if not interface_exists(src_ifname): + raise ConfigError(f'Specified source-interface {src_ifname} does not exist') + + if 'source_interface_is_bridge_member' in config: + bridge_name = next(iter(config['source_interface_is_bridge_member'])) + raise ConfigError(f'Invalid source-interface "{src_ifname}". Interface ' + f'is already a member of bridge "{bridge_name}"!') + + if 'source_interface_is_bond_member' in config: + bond_name = next(iter(config['source_interface_is_bond_member'])) + raise ConfigError(f'Invalid source-interface "{src_ifname}". Interface ' + f'is already a member of bond "{bond_name}"!') + + if 'is_source_interface' in config: + tmp = config['is_source_interface'] + raise ConfigError(f'Can not use source-interface "{src_ifname}", it already ' \ + f'belongs to interface "{tmp}"!') + +def verify_dhcpv6(config): + """ + Common helper function used by interface implementations to perform + recurring validation of DHCPv6 options which are mutually exclusive. + """ + if 'dhcpv6_options' in config: + if {'parameters_only', 'temporary'} <= set(config['dhcpv6_options']): + raise ConfigError('DHCPv6 temporary and parameters-only options ' + 'are mutually exclusive!') + + # It is not allowed to have duplicate SLA-IDs as those identify an + # assigned IPv6 subnet from a delegated prefix + for pd in (dict_search('dhcpv6_options.pd', config) or []): + sla_ids = [] + interfaces = dict_search(f'dhcpv6_options.pd.{pd}.interface', config) + + if not interfaces: + raise ConfigError('DHCPv6-PD requires an interface where to assign ' + 'the delegated prefix!') + + for count, interface in enumerate(interfaces): + if 'sla_id' in interfaces[interface]: + sla_ids.append(interfaces[interface]['sla_id']) + else: + sla_ids.append(str(count)) + + # Check for duplicates + duplicates = [x for n, x in enumerate(sla_ids) if x in sla_ids[:n]] + if duplicates: + raise ConfigError('Site-Level Aggregation Identifier (SLA-ID) ' + 'must be unique per prefix-delegation!') + +def verify_vlan_config(config): + """ + Common helper function used by interface implementations to perform + recurring validation of interface VLANs + """ + + # VLAN and Q-in-Q IDs are not allowed to overlap + if 'vif' in config and 'vif_s' in config: + duplicate = list(set(config['vif']) & set(config['vif_s'])) + if duplicate: + raise ConfigError(f'Duplicate VLAN id "{duplicate[0]}" used for vif and vif-s interfaces!') + + parent_ifname = config['ifname'] + # 802.1q VLANs + for vlan_id in config.get('vif', {}): + vlan = config['vif'][vlan_id] + vlan['ifname'] = f'{parent_ifname}.{vlan_id}' + + verify_dhcpv6(vlan) + verify_address(vlan) + verify_vrf(vlan) + verify_mirror_redirect(vlan) + verify_mtu_parent(vlan, config) + + # 802.1ad (Q-in-Q) VLANs + for s_vlan_id in config.get('vif_s', {}): + s_vlan = config['vif_s'][s_vlan_id] + s_vlan['ifname'] = f'{parent_ifname}.{s_vlan_id}' + + verify_dhcpv6(s_vlan) + verify_address(s_vlan) + verify_vrf(s_vlan) + verify_mirror_redirect(s_vlan) + verify_mtu_parent(s_vlan, config) + + for c_vlan_id in s_vlan.get('vif_c', {}): + c_vlan = s_vlan['vif_c'][c_vlan_id] + c_vlan['ifname'] = f'{parent_ifname}.{s_vlan_id}.{c_vlan_id}' + + verify_dhcpv6(c_vlan) + verify_address(c_vlan) + verify_vrf(c_vlan) + verify_mirror_redirect(c_vlan) + verify_mtu_parent(c_vlan, config) + verify_mtu_parent(c_vlan, s_vlan) + + +def verify_diffie_hellman_length(file, min_keysize): + """ Verify Diffie-Hellamn keypair length given via file. It must be greater + then or equal to min_keysize """ + import os + import re + from vyos.utils.process import cmd + + try: + keysize = str(min_keysize) + except: + return False + + if os.path.exists(file): + out = cmd(f'openssl dhparam -inform PEM -in {file} -text') + prog = re.compile('\d+\s+bit') + if prog.search(out): + bits = prog.search(out)[0].split()[0] + if int(bits) >= int(min_keysize): + return True + + return False + +def verify_common_route_maps(config): + """ + Common helper function used by routing protocol implementations to perform + recurring validation if the specified route-map for either zebra to kernel + installation exists (this is the top-level route_map key) or when a route + is redistributed with a route-map that it exists! + """ + # XXX: This function is called in combination with a previous call to: + # tmp = conf.get_config_dict(['policy']) - see protocols_ospf.py as example. + # We should NOT call this with the key_mangling option as this would rename + # route-map hypens '-' to underscores '_' and one could no longer distinguish + # what should have been the "proper" route-map name, as foo-bar and foo_bar + # are two entire different route-map instances! + for route_map in ['route-map', 'route_map']: + if route_map not in config: + continue + tmp = config[route_map] + # Check if the specified route-map exists, if not error out + if dict_search(f'policy.route-map.{tmp}', config) == None: + raise ConfigError(f'Specified route-map "{tmp}" does not exist!') + + if 'redistribute' in config: + for protocol, protocol_config in config['redistribute'].items(): + if 'route_map' in protocol_config: + verify_route_map(protocol_config['route_map'], config) + +def verify_route_map(route_map_name, config): + """ + Common helper function used by routing protocol implementations to perform + recurring validation if a specified route-map exists! + """ + # Check if the specified route-map exists, if not error out + if dict_search(f'policy.route-map.{route_map_name}', config) == None: + raise ConfigError(f'Specified route-map "{route_map_name}" does not exist!') + +def verify_prefix_list(prefix_list, config, version=''): + """ + Common helper function used by routing protocol implementations to perform + recurring validation if a specified prefix-list exists! + """ + # Check if the specified prefix-list exists, if not error out + if dict_search(f'policy.prefix-list{version}.{prefix_list}', config) == None: + raise ConfigError(f'Specified prefix-list{version} "{prefix_list}" does not exist!') + +def verify_access_list(access_list, config, version=''): + """ + Common helper function used by routing protocol implementations to perform + recurring validation if a specified prefix-list exists! + """ + # Check if the specified ACL exists, if not error out + if dict_search(f'policy.access-list{version}.{access_list}', config) == None: + raise ConfigError(f'Specified access-list{version} "{access_list}" does not exist!') + +def verify_pki_certificate(config: dict, cert_name: str, no_password_protected: bool=False): + """ + Common helper function user by PKI consumers to perform recurring + validation functions for PEM based certificates + """ + if 'pki' not in config: + raise ConfigError('PKI is not configured!') + + if 'certificate' not in config['pki']: + raise ConfigError('PKI does not contain any certificates!') + + if cert_name not in config['pki']['certificate']: + raise ConfigError(f'Certificate "{cert_name}" not found in configuration!') + + pki_cert = config['pki']['certificate'][cert_name] + if 'certificate' not in pki_cert: + raise ConfigError(f'PEM certificate for "{cert_name}" missing in configuration!') + + if 'private' not in pki_cert or 'key' not in pki_cert['private']: + raise ConfigError(f'PEM private key for "{cert_name}" missing in configuration!') + + if no_password_protected and 'password_protected' in pki_cert['private']: + raise ConfigError('Password protected PEM private key is not supported!') + +def verify_pki_ca_certificate(config: dict, ca_name: str): + """ + Common helper function user by PKI consumers to perform recurring + validation functions for PEM based CA certificates + """ + if 'pki' not in config: + raise ConfigError('PKI is not configured!') + + if 'ca' not in config['pki']: + raise ConfigError('PKI does not contain any CA certificates!') + + if ca_name not in config['pki']['ca']: + raise ConfigError(f'CA Certificate "{ca_name}" not found in configuration!') + + pki_cert = config['pki']['ca'][ca_name] + if 'certificate' not in pki_cert: + raise ConfigError(f'PEM CA certificate for "{cert_name}" missing in configuration!') + +def verify_pki_dh_parameters(config: dict, dh_name: str, min_key_size: int=0): + """ + Common helper function user by PKI consumers to perform recurring + validation functions on DH parameters + """ + from vyos.pki import load_dh_parameters + + if 'pki' not in config: + raise ConfigError('PKI is not configured!') + + if 'dh' not in config['pki']: + raise ConfigError('PKI does not contain any DH parameters!') + + if dh_name not in config['pki']['dh']: + raise ConfigError(f'DH parameter "{dh_name}" not found in configuration!') + + if min_key_size: + pki_dh = config['pki']['dh'][dh_name] + dh_params = load_dh_parameters(pki_dh['parameters']) + dh_numbers = dh_params.parameter_numbers() + dh_bits = dh_numbers.p.bit_length() + if dh_bits < min_key_size: + raise ConfigError(f'Minimum DH key-size is {min_key_size} bits!') + +def verify_eapol(config: dict): + """ + Common helper function used by interface implementations to perform + recurring validation of EAPoL configuration. + """ + if 'eapol' not in config: + return + + if 'certificate' not in config['eapol']: + raise ConfigError('Certificate must be specified when using EAPoL!') + + verify_pki_certificate(config, config['eapol']['certificate'], no_password_protected=True) + + if 'ca_certificate' in config['eapol']: + for ca_cert in config['eapol']['ca_certificate']: + verify_pki_ca_certificate(config, ca_cert) diff --git a/python/vyos/debug.py b/python/vyos/debug.py new file mode 100644 index 0000000..6ce42b1 --- /dev/null +++ b/python/vyos/debug.py @@ -0,0 +1,205 @@ +# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys +from datetime import datetime + +def message(message, flag='', destination=sys.stdout): + """ + print a debug message line on stdout if debugging is enabled for the flag + also log it to a file if the flag 'log' is enabled + + message: the message to print + flag: which flag must be set for it to print + destination: which file like object to write to (default: sys.stdout) + + returns if any message was logged or not + """ + enable = enabled(flag) + if enable: + destination.write(_format(flag,message)) + + # the log flag is special as it logs all the commands + # executed to a log + logfile = _logfile('log', '/tmp/developer-log') + if not logfile: + return enable + + try: + # at boot the file is created as root:vyattacfg + # at runtime the file is created as user:vyattacfg + # but the helper scripts are not run as this so it + # need the default permission to be 666 (an not 660) + mask = os.umask(0o111) + + with open(logfile, 'a') as f: + f.write(_timed(_format('log', message))) + finally: + os.umask(mask) + + return enable + + +def enabled(flag): + """ + a flag can be set by touching the file in /tmp or /config + + The current flags are: + - developer: the code will drop into PBD on un-handled exception + - log: the code will log all command to a file + - ifconfig: when modifying an interface, + prints command with result and sysfs access on stdout for interface + - command: print command run with result + + Having the flag setup on the filesystem is required to have + debuging at boot time, however, setting the flag via environment + does not require a seek to the filesystem and is more efficient + it can be done on the shell on via .bashrc for the user + + The function returns an empty string if the flag was not set otherwise + the function returns either the file or environment name used to set it up + """ + + # this is to force all new flags to be registered here to be + # documented both here and a reminder to update readthedocs :-) + if flag not in ['developer', 'log', 'ifconfig', 'command']: + return '' + + return _fromenv(flag) or _fromfile(flag) + + +def _timed(message): + now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + return f'{now} {message}' + + +def _remove_invisible(string): + for char in ('\0', '\a', '\b', '\f', '\v'): + string = string.replace(char, '') + return string + + +def _format(flag, message): + """ + format a log message + """ + message = _remove_invisible(message) + return f'DEBUG/{flag.upper():<7} {message}\n' + + +def _fromenv(flag): + """ + check if debugging is set for this flag via environment + + For a given debug flag named "test" + The presence of the environment VYOS_TEST_DEBUG (uppercase) enables it + + return empty string if not + return content of env value it is + """ + + flagname = f'VYOS_{flag.upper()}_DEBUG' + flagenv = os.environ.get(flagname, None) + + if flagenv is None: + return '' + return flagenv + + +def _fromfile(flag): + """ + Check if debug exist for a given debug flag name + + Check is a debug flag was set by the user. the flag can be set either: + - in /tmp for a non-persistent presence between reboot + - in /config for always on (an existence at boot time) + + For a given debug flag named "test" + The presence of the file vyos.test.debug (all lowercase) enables it + + The function returns an empty string if the flag was not set otherwise + the function returns the full flagname + """ + + for folder in ('/tmp', '/config'): + flagfile = f'{folder}/vyos.{flag}.debug' + if os.path.isfile(flagfile): + return flagfile + + return '' + + +def _contentenv(flag): + return os.environ.get(f'VYOS_{flag.upper()}_DEBUG', '').strip() + + +def _contentfile(flag, default=''): + """ + Check if debug exist for a given debug flag name + + Check is a debug flag was set by the user. the flag can be set either: + - in /tmp for a non-persistent presence between reboot + - in /config for always on (an existence at boot time) + + For a given debug flag named "test" + The presence of the file vyos.test.debug (all lowercase) enables it + + The function returns an empty string if the flag was not set otherwise + the function returns the full flagname + """ + + for folder in ('/tmp', '/config'): + flagfile = f'{folder}/vyos.{flag}.debug' + if not os.path.isfile(flagfile): + continue + with open(flagfile) as f: + content = f.readline().strip() + return content or default + + return '' + + +def _logfile(flag, default): + """ + return the name of the file to use for logging when the flag 'log' is set + if it could not be established or the location is invalid it returns + an empty string + """ + + # For log we return the location of the log file + log_location = _contentenv(flag) or _contentfile(flag, default) + + # it was not set + if not log_location: + return '' + + # Make sure that the logs can only be in /tmp, /var/log, or /tmp + if not log_location.startswith('/tmp/') and \ + not log_location.startswith('/config/') and \ + not log_location.startswith('/var/log/'): + return default + # Do not allow to escape the folders + if '..' in log_location: + return default + + if not os.path.exists(log_location): + return log_location + + # this permission is unique the the config and var folder + stat = os.stat(log_location).st_mode + if stat != 0o100666: + return default + return log_location diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py new file mode 100644 index 0000000..dec619d --- /dev/null +++ b/python/vyos/defaults.py @@ -0,0 +1,63 @@ +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os + +base_dir = '/usr/libexec/vyos/' + +directories = { + 'base' : base_dir, + 'data' : '/usr/share/vyos/', + 'conf_mode' : f'{base_dir}/conf_mode', + 'op_mode' : f'{base_dir}/op_mode', + 'services' : f'{base_dir}/services', + 'config' : '/opt/vyatta/etc/config', + 'migrate' : '/opt/vyatta/etc/config-migrate/migrate', + 'activate' : f'{base_dir}/activate', + 'log' : '/var/log/vyatta', + 'templates' : '/usr/share/vyos/templates/', + 'certbot' : '/config/auth/letsencrypt', + 'api_schema': f'{base_dir}/services/api/graphql/graphql/schema/', + 'api_client_op': f'{base_dir}/services/api/graphql/graphql/client_op/', + 'api_templates': f'{base_dir}/services/api/graphql/session/templates/', + 'vyos_udev_dir' : '/run/udev/vyos', + 'isc_dhclient_dir' : '/run/dhclient', + 'dhcp6_client_dir' : '/run/dhcp6c', + 'vyos_configdir' : '/opt/vyatta/config', + 'completion_dir' : f'{base_dir}/completion' +} + +config_status = '/tmp/vyos-config-status' +api_config_state = '/run/http-api-state' + +cfg_group = 'vyattacfg' + +cfg_vintage = 'vyos' + +commit_lock = os.path.join(directories['vyos_configdir'], '.lock') + +component_version_json = os.path.join(directories['data'], 'component-versions.json') + +config_default = os.path.join(directories['data'], 'config.boot.default') + +rt_symbolic_names = { + # Standard routing tables for Linux & reserved IDs for VyOS + 'default': 253, # Confusingly, a final fallthru, not the default. + 'main': 254, # The actual global table used by iproute2 unless told otherwise. + 'local': 255, # Special kernel loopback table. +} + +rt_global_vrf = rt_symbolic_names['main'] +rt_global_table = rt_symbolic_names['main'] diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py new file mode 100644 index 0000000..21272cc --- /dev/null +++ b/python/vyos/ethtool.py @@ -0,0 +1,200 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import re + +from json import loads +from vyos.utils.network import interface_exists +from vyos.utils.process import popen + +# These drivers do not support using ethtool to change the speed, duplex, or +# flow control settings +_drivers_without_speed_duplex_flow = ['vmxnet3', 'virtio_net', 'xen_netfront', + 'iavf', 'ice', 'i40e', 'hv_netvsc', 'veth', 'ixgbevf', + 'tun'] + +class Ethtool: + """ + Class is used to retrive and cache information about an ethernet adapter + """ + # dictionary containing driver featurs, it will be populated on demand and + # the content will look like: + # [{'esp-hw-offload': {'active': False, 'fixed': True, 'requested': False}, + # 'esp-tx-csum-hw-offload': {'active': False, + # 'fixed': True, + # 'requested': False}, + # 'fcoe-mtu': {'active': False, 'fixed': True, 'requested': False}, + # 'generic-receive-offload': {'active': True, + # 'fixed': False, + # 'requested': True}, + # 'generic-segmentation-offload': {'active': True, + # 'fixed': False, + # 'requested': True}, + # 'highdma': {'active': True, 'fixed': False, 'requested': True}, + # 'ifname': 'eth0', + # 'l2-fwd-offload': {'active': False, 'fixed': True, 'requested': False}, + # 'large-receive-offload': {'active': False, + # 'fixed': False, + # 'requested': False}, + # ... + _features = { } + # dictionary containing available interface speed and duplex settings + # { + # '10' : {'full': '', 'half': ''}, + # '100' : {'full': '', 'half': ''}, + # '1000': {'full': ''} + # } + _ring_buffer = None + _driver_name = None + _flow_control = None + + def __init__(self, ifname): + # Get driver used for interface + if not interface_exists(ifname): + raise ValueError(f'Interface "{ifname}" does not exist!') + + out, _ = popen(f'ethtool --driver {ifname}') + driver = re.search(r'driver:\s(\w+)', out) + if driver: + self._driver_name = driver.group(1) + + # Build a dictinary of supported link-speed and dupley settings. + # [ { + # "ifname": "eth0", + # "supported-ports": [ "TP" ], + # "supported-link-modes": [ "10baseT/Half","10baseT/Full","100baseT/Half","100baseT/Full","1000baseT/Full" ], + # "supported-pause-frame-use": "Symmetric", + # "supports-auto-negotiation": true, + # "supported-fec-modes": [ ], + # "advertised-link-modes": [ "10baseT/Half","10baseT/Full","100baseT/Half","100baseT/Full","1000baseT/Full" ], + # "advertised-pause-frame-use": "Symmetric", + # "advertised-auto-negotiation": true, + # "advertised-fec-modes": [ ], + # "speed": 1000, + # "duplex": "Full", + # "auto-negotiation": false, + # "port": "Twisted Pair", + # "phyad": 1, + # "transceiver": "internal", + # "supports-wake-on": "pumbg", + # "wake-on": "g", + # "current-message-level": 7, + # "link-detected": true + # } ] + out, _ = popen(f'ethtool --json {ifname}') + self._base_settings = loads(out)[0] + + # Now populate driver features + out, _ = popen(f'ethtool --json --show-features {ifname}') + self._features = loads(out)[0] + + # Get information about NIC ring buffers + out, _ = popen(f'ethtool --json --show-ring {ifname}') + self._ring_buffer = loads(out)[0] + + # Get current flow control settings, but this is not supported by + # all NICs (e.g. vmxnet3 does not support is) + out, err = popen(f'ethtool --json --show-pause {ifname}') + if not bool(err): + self._flow_control = loads(out)[0] + + def check_auto_negotiation_supported(self): + """ Check if the NIC supports changing auto-negotiation """ + return self._base_settings['supports-auto-negotiation'] + + def get_auto_negotiation(self): + return self._base_settings['supports-auto-negotiation'] and self._base_settings['auto-negotiation'] + + def get_driver_name(self): + return self._driver_name + + def _get_generic(self, feature): + """ + Generic method to read self._features and return a tuple for feature + enabled and feature is fixed. + + In case of a missing key, return "fixed = True and enabled = False" + """ + active = False + fixed = True + if feature in self._features: + active = bool(self._features[feature]['active']) + fixed = bool(self._features[feature]['fixed']) + return active, fixed + + def get_generic_receive_offload(self): + return self._get_generic('generic-receive-offload') + + def get_generic_segmentation_offload(self): + return self._get_generic('generic-segmentation-offload') + + def get_hw_tc_offload(self): + return self._get_generic('hw-tc-offload') + + def get_large_receive_offload(self): + return self._get_generic('large-receive-offload') + + def get_scatter_gather(self): + return self._get_generic('scatter-gather') + + def get_tcp_segmentation_offload(self): + return self._get_generic('tcp-segmentation-offload') + + def get_ring_buffer_max(self, rx_tx): + # Configuration of RX/TX ring-buffers is not supported on every device, + # thus when it's impossible return None + if rx_tx not in ['rx', 'tx']: + ValueError('Ring-buffer type must be either "rx" or "tx"') + return str(self._ring_buffer.get(f'{rx_tx}-max', None)) + + def get_ring_buffer(self, rx_tx): + # Configuration of RX/TX ring-buffers is not supported on every device, + # thus when it's impossible return None + if rx_tx not in ['rx', 'tx']: + ValueError('Ring-buffer type must be either "rx" or "tx"') + return str(self._ring_buffer.get(rx_tx, None)) + + def check_speed_duplex(self, speed, duplex): + """ Check if the passed speed and duplex combination is supported by + the underlaying network adapter. """ + if isinstance(speed, int): + speed = str(speed) + if speed != 'auto' and not speed.isdigit(): + raise ValueError(f'Value "{speed}" for speed is invalid!') + if duplex not in ['auto', 'full', 'half']: + raise ValueError(f'Value "{duplex}" for duplex is invalid!') + + if speed == 'auto' and duplex == 'auto': + return True + + if self.get_driver_name() in _drivers_without_speed_duplex_flow: + return False + + # ['10baset/half', '10baset/full', '100baset/half', '100baset/full', '1000baset/full'] + tmp = [x.lower() for x in self._base_settings['supported-link-modes']] + if f'{speed}baset/{duplex}' in tmp: + return True + return False + + def check_flow_control(self): + """ Check if the NIC supports flow-control """ + return bool(self._flow_control) + + def get_flow_control(self): + if self._flow_control == None: + raise ValueError('Interface does not support changing '\ + 'flow-control settings!') + + return 'on' if bool(self._flow_control['autonegotiate']) else 'off' diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py new file mode 100644 index 0000000..64fed81 --- /dev/null +++ b/python/vyos/firewall.py @@ -0,0 +1,785 @@ +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import csv +import gzip +import os +import re + +from pathlib import Path +from socket import AF_INET +from socket import AF_INET6 +from socket import getaddrinfo +from time import strftime + +from vyos.remote import download +from vyos.template import is_ipv4 +from vyos.template import render +from vyos.utils.dict import dict_search_args +from vyos.utils.dict import dict_search_recursive +from vyos.utils.process import cmd +from vyos.utils.process import run +from vyos.utils.network import get_vrf_tableid +from vyos.defaults import rt_global_table +from vyos.defaults import rt_global_vrf + +# Conntrack +def conntrack_required(conf): + required_nodes = ['nat', 'nat66', 'load-balancing wan'] + + for path in required_nodes: + if conf.exists(path): + return True + + firewall = conf.get_config_dict(['firewall'], key_mangling=('-', '_'), + no_tag_node_value_mangle=True, get_first_key=True) + + for rules, path in dict_search_recursive(firewall, 'rule'): + if any(('state' in rule_conf or 'connection_status' in rule_conf or 'offload_target' in rule_conf) for rule_conf in rules.values()): + return True + + return False + +# Domain Resolver + +def fqdn_config_parse(firewall): + firewall['ip_fqdn'] = {} + firewall['ip6_fqdn'] = {} + + for domain, path in dict_search_recursive(firewall, 'fqdn'): + hook_name = path[1] + priority = path[2] + + fw_name = path[2] + rule = path[4] + suffix = path[5][0] + set_name = f'{hook_name}_{priority}_{rule}_{suffix}' + + if (path[0] == 'ipv4') and (path[1] == 'forward' or path[1] == 'input' or path[1] == 'output' or path[1] == 'name'): + firewall['ip_fqdn'][set_name] = domain + elif (path[0] == 'ipv6') and (path[1] == 'forward' or path[1] == 'input' or path[1] == 'output' or path[1] == 'name'): + if path[1] == 'name': + set_name = f'name6_{priority}_{rule}_{suffix}' + firewall['ip6_fqdn'][set_name] = domain + +def fqdn_resolve(fqdn, ipv6=False): + try: + res = getaddrinfo(fqdn, None, AF_INET6 if ipv6 else AF_INET) + return set(item[4][0] for item in res) + except: + return None + +# End Domain Resolver + +def find_nftables_rule(table, chain, rule_matches=[]): + # Find rule in table/chain that matches all criteria and return the handle + results = cmd(f'sudo nft --handle list chain {table} {chain}').split("\n") + for line in results: + if all(rule_match in line for rule_match in rule_matches): + handle_search = re.search('handle (\d+)', line) + if handle_search: + return handle_search[1] + return None + +def remove_nftables_rule(table, chain, handle): + cmd(f'sudo nft delete rule {table} {chain} handle {handle}') + +# Functions below used by template generation + +def nft_action(vyos_action): + if vyos_action == 'accept': + return 'return' + return vyos_action + +def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name): + output = [] + + if ip_name == 'ip6': + def_suffix = '6' + family = 'ipv6' + else: + def_suffix = '' + family = 'bri' if ip_name == 'bri' else 'ipv4' + + if 'state' in rule_conf and rule_conf['state']: + states = ",".join([s for s in rule_conf['state']]) + + if states: + output.append(f'ct state {{{states}}}') + + if 'conntrack_helper' in rule_conf: + helper_map = {'h323': ['RAS', 'Q.931'], 'nfs': ['rpc'], 'sqlnet': ['tns']} + helper_out = [] + + for helper in rule_conf['conntrack_helper']: + if helper in helper_map: + helper_out.extend(helper_map[helper]) + else: + helper_out.append(helper) + + if helper_out: + helper_str = ','.join(f'"{s}"' for s in helper_out) + output.append(f'ct helper {{{helper_str}}}') + + if 'connection_status' in rule_conf and rule_conf['connection_status']: + status = rule_conf['connection_status'] + if status['nat'] == 'destination': + nat_status = 'dnat' + output.append(f'ct status {nat_status}') + if status['nat'] == 'source': + nat_status = 'snat' + output.append(f'ct status {nat_status}') + + if 'protocol' in rule_conf and rule_conf['protocol'] != 'all': + proto = rule_conf['protocol'] + operator = '' + if proto[0] == '!': + operator = '!=' + proto = proto[1:] + if proto == 'tcp_udp': + proto = '{tcp, udp}' + output.append(f'meta l4proto {operator} {proto}') + + if 'ethernet_type' in rule_conf: + ether_type_mapping = { + '802.1q': '8021q', + '802.1ad': '8021ad', + 'ipv6': 'ip6', + 'ipv4': 'ip', + 'arp': 'arp' + } + ether_type = rule_conf['ethernet_type'] + operator = '!=' if ether_type.startswith('!') else '' + ether_type = ether_type.lstrip('!') + ether_type = ether_type_mapping.get(ether_type, ether_type) + output.append(f'ether type {operator} {ether_type}') + + for side in ['destination', 'source']: + if side in rule_conf: + prefix = side[0] + side_conf = rule_conf[side] + address_mask = side_conf.get('address_mask', None) + + if 'address' in side_conf: + suffix = side_conf['address'] + operator = '' + exclude = suffix[0] == '!' + if exclude: + operator = '!= ' + suffix = suffix[1:] + if address_mask: + operator = '!=' if exclude else '==' + operator = f'& {address_mask} {operator} ' + + if suffix.find('-') != -1: + # Range + start, end = suffix.split('-') + if is_ipv4(start): + output.append(f'ip {prefix}addr {operator}{suffix}') + else: + output.append(f'ip6 {prefix}addr {operator}{suffix}') + else: + if is_ipv4(suffix): + output.append(f'ip {prefix}addr {operator}{suffix}') + else: + output.append(f'ip6 {prefix}addr {operator}{suffix}') + + if 'fqdn' in side_conf: + fqdn = side_conf['fqdn'] + hook_name = '' + operator = '' + if fqdn[0] == '!': + operator = '!=' + if hook == 'FWD': + hook_name = 'forward' + if hook == 'INP': + hook_name = 'input' + if hook == 'OUT': + hook_name = 'output' + if hook == 'PRE': + hook_name = 'prerouting' + if hook == 'NAM': + hook_name = f'name{def_suffix}' + output.append(f'{ip_name} {prefix}addr {operator} @FQDN_{hook_name}_{fw_name}_{rule_id}_{prefix}') + + if dict_search_args(side_conf, 'geoip', 'country_code'): + operator = '' + hook_name = '' + if dict_search_args(side_conf, 'geoip', 'inverse_match') != None: + operator = '!=' + if hook == 'FWD': + hook_name = 'forward' + if hook == 'INP': + hook_name = 'input' + if hook == 'OUT': + hook_name = 'output' + if hook == 'PRE': + hook_name = 'prerouting' + if hook == 'NAM': + hook_name = f'name' + output.append(f'{ip_name} {prefix}addr {operator} @GEOIP_CC{def_suffix}_{hook_name}_{fw_name}_{rule_id}') + + if 'mac_address' in side_conf: + suffix = side_conf["mac_address"] + if suffix[0] == '!': + suffix = f'!= {suffix[1:]}' + output.append(f'ether {prefix}addr {suffix}') + + if 'port' in side_conf: + proto = rule_conf['protocol'] + port = side_conf['port'].split(',') + + ports = [] + negated_ports = [] + + for p in port: + if p[0] == '!': + negated_ports.append(p[1:]) + else: + ports.append(p) + + if proto == 'tcp_udp': + proto = 'th' + + if ports: + ports_str = ','.join(ports) + output.append(f'{proto} {prefix}port {{{ports_str}}}') + + if negated_ports: + negated_ports_str = ','.join(negated_ports) + output.append(f'{proto} {prefix}port != {{{negated_ports_str}}}') + + if 'group' in side_conf: + group = side_conf['group'] + for ipvx_address_group in ['address_group', 'ipv4_address_group', 'ipv6_address_group']: + if ipvx_address_group in group: + group_name = group[ipvx_address_group] + operator = '' + exclude = group_name[0] == "!" + if exclude: + operator = '!=' + group_name = group_name[1:] + if address_mask: + operator = '!=' if exclude else '==' + operator = f'& {address_mask} {operator}' + # for bridge, change ip_name + if ip_name == 'bri': + ip_name = 'ip' if ipvx_address_group == 'ipv4_address_group' else 'ip6' + def_suffix = '6' if ipvx_address_group == 'ipv6_address_group' else '' + output.append(f'{ip_name} {prefix}addr {operator} @A{def_suffix}_{group_name}') + for ipvx_network_group in ['network_group', 'ipv4_network_group', 'ipv6_network_group']: + if ipvx_network_group in group: + group_name = group[ipvx_network_group] + operator = '' + if group_name[0] == "!": + operator = '!=' + group_name = group_name[1:] + # for bridge, change ip_name + if ip_name == 'bri': + ip_name = 'ip' if ipvx_network_group == 'ipv4_network_group' else 'ip6' + def_suffix = '6' if ipvx_network_group == 'ipv6_network_group' else '' + output.append(f'{ip_name} {prefix}addr {operator} @N{def_suffix}_{group_name}') + if 'dynamic_address_group' in group: + group_name = group['dynamic_address_group'] + operator = '' + if group_name[0] == "!": + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_name} {prefix}addr {operator} @DA{def_suffix}_{group_name}') + # Generate firewall group domain-group + elif 'domain_group' in group: + group_name = group['domain_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_name} {prefix}addr {operator} @D_{group_name}') + if 'mac_group' in group: + group_name = group['mac_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'ether {prefix}addr {operator} @M_{group_name}') + if 'port_group' in group: + proto = rule_conf['protocol'] + group_name = group['port_group'] + + if proto == 'tcp_udp': + proto = 'th' + + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + + output.append(f'{proto} {prefix}port {operator} @P_{group_name}') + + if dict_search_args(rule_conf, 'action') == 'synproxy': + output.append('ct state invalid,untracked') + + if 'hop_limit' in rule_conf: + operators = {'eq': '==', 'gt': '>', 'lt': '<'} + for op, operator in operators.items(): + if op in rule_conf['hop_limit']: + value = rule_conf['hop_limit'][op] + output.append(f'ip6 hoplimit {operator} {value}') + + if 'inbound_interface' in rule_conf: + operator = '' + if 'name' in rule_conf['inbound_interface']: + iiface = rule_conf['inbound_interface']['name'] + if iiface[0] == '!': + operator = '!=' + iiface = iiface[1:] + output.append(f'iifname {operator} {{{iiface}}}') + elif 'group' in rule_conf['inbound_interface']: + iiface = rule_conf['inbound_interface']['group'] + if iiface[0] == '!': + operator = '!=' + iiface = iiface[1:] + output.append(f'iifname {operator} @I_{iiface}') + + if 'outbound_interface' in rule_conf: + operator = '' + if 'name' in rule_conf['outbound_interface']: + oiface = rule_conf['outbound_interface']['name'] + if oiface[0] == '!': + operator = '!=' + oiface = oiface[1:] + output.append(f'oifname {operator} {{{oiface}}}') + elif 'group' in rule_conf['outbound_interface']: + oiface = rule_conf['outbound_interface']['group'] + if oiface[0] == '!': + operator = '!=' + oiface = oiface[1:] + output.append(f'oifname {operator} @I_{oiface}') + + if 'ttl' in rule_conf: + operators = {'eq': '==', 'gt': '>', 'lt': '<'} + for op, operator in operators.items(): + if op in rule_conf['ttl']: + value = rule_conf['ttl'][op] + output.append(f'ip ttl {operator} {value}') + + for icmp in ['icmp', 'icmpv6']: + if icmp in rule_conf: + if 'type_name' in rule_conf[icmp]: + output.append(icmp + ' type ' + rule_conf[icmp]['type_name']) + else: + if 'code' in rule_conf[icmp]: + output.append(icmp + ' code ' + rule_conf[icmp]['code']) + if 'type' in rule_conf[icmp]: + output.append(icmp + ' type ' + rule_conf[icmp]['type']) + + + if 'packet_length' in rule_conf: + lengths_str = ','.join(rule_conf['packet_length']) + output.append(f'ip{def_suffix} length {{{lengths_str}}}') + + if 'packet_length_exclude' in rule_conf: + negated_lengths_str = ','.join(rule_conf['packet_length_exclude']) + output.append(f'ip{def_suffix} length != {{{negated_lengths_str}}}') + + if 'packet_type' in rule_conf: + output.append(f'pkttype ' + rule_conf['packet_type']) + + if 'dscp' in rule_conf: + dscp_str = ','.join(rule_conf['dscp']) + output.append(f'ip{def_suffix} dscp {{{dscp_str}}}') + + if 'dscp_exclude' in rule_conf: + negated_dscp_str = ','.join(rule_conf['dscp_exclude']) + output.append(f'ip{def_suffix} dscp != {{{negated_dscp_str}}}') + + if 'ipsec' in rule_conf: + if 'match_ipsec_in' in rule_conf['ipsec']: + output.append('meta ipsec == 1') + if 'match_none_in' in rule_conf['ipsec']: + output.append('meta ipsec == 0') + if 'match_ipsec_out' in rule_conf['ipsec']: + output.append('rt ipsec exists') + if 'match_none_out' in rule_conf['ipsec']: + output.append('rt ipsec missing') + + if 'fragment' in rule_conf: + # Checking for fragmentation after priority -400 is not possible, + # so we use a priority -450 hook to set a mark + if 'match_frag' in rule_conf['fragment']: + output.append('meta mark 0xffff1') + if 'match_non_frag' in rule_conf['fragment']: + output.append('meta mark != 0xffff1') + + if 'limit' in rule_conf: + if 'rate' in rule_conf['limit']: + output.append(f'limit rate {rule_conf["limit"]["rate"]}') + if 'burst' in rule_conf['limit']: + output.append(f'burst {rule_conf["limit"]["burst"]} packets') + + if 'recent' in rule_conf: + count = rule_conf['recent']['count'] + time = rule_conf['recent']['time'] + output.append(f'add @RECENT{def_suffix}_{hook}_{fw_name}_{rule_id} {{ {ip_name} saddr limit rate over {count}/{time} burst {count} packets }}') + + if 'gre' in rule_conf: + gre_key = dict_search_args(rule_conf, 'gre', 'key') + + gre_flags = dict_search_args(rule_conf, 'gre', 'flags') + output.append(parse_gre_flags(gre_flags or {}, force_keyed=gre_key is not None)) + + gre_proto_alias_map = { + '802.1q': '8021q', + '802.1ad': '8021ad', + 'gretap': '0x6558', + } + + gre_proto = dict_search_args(rule_conf, 'gre', 'inner_proto') + if gre_proto is not None: + gre_proto = gre_proto_alias_map.get(gre_proto, gre_proto) + output.append(f'gre protocol {gre_proto}') + + gre_ver = dict_search_args(rule_conf, 'gre', 'version') + if gre_ver == 'gre': + output.append('gre version 0') + elif gre_ver == 'pptp': + output.append('gre version 1') + + if gre_key: + # The offset of the key within the packet shifts depending on the C-flag. + # nftables cannot handle complex enough expressions to match multiple + # offsets based on bitfields elsewhere. + # We enforce a specific match for the checksum flag in validation, so the + # gre_flags dict will always have a 'checksum' key when gre_key is populated. + if not gre_flags['checksum']: + # No "unset" child node means C is set, we offset key lookup +32 bits + output.append(f'@th,64,32 == {gre_key}') + else: + output.append(f'@th,32,32 == {gre_key}') + + if 'time' in rule_conf: + output.append(parse_time(rule_conf['time'])) + + tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') + if tcp_flags: + output.append(parse_tcp_flags(tcp_flags)) + + # TCP MSS + tcp_mss = dict_search_args(rule_conf, 'tcp', 'mss') + if tcp_mss: + output.append(f'tcp option maxseg size {tcp_mss}') + + if 'connection_mark' in rule_conf: + conn_mark_str = ','.join(rule_conf['connection_mark']) + output.append(f'ct mark {{{conn_mark_str}}}') + + if 'mark' in rule_conf: + mark = rule_conf['mark'] + operator = '' + if mark[0] == '!': + operator = '!=' + mark = mark[1:] + output.append(f'meta mark {operator} {{{mark}}}') + + if 'vlan' in rule_conf: + if 'id' in rule_conf['vlan']: + output.append(f'vlan id {rule_conf["vlan"]["id"]}') + if 'priority' in rule_conf['vlan']: + output.append(f'vlan pcp {rule_conf["vlan"]["priority"]}') + if 'ethernet_type' in rule_conf['vlan']: + ether_type_mapping = { + '802.1q': '8021q', + '802.1ad': '8021ad', + 'ipv6': 'ip6', + 'ipv4': 'ip', + 'arp': 'arp' + } + ether_type = rule_conf['vlan']['ethernet_type'] + operator = '!=' if ether_type.startswith('!') else '' + ether_type = ether_type.lstrip('!') + ether_type = ether_type_mapping.get(ether_type, ether_type) + output.append(f'vlan type {operator} {ether_type}') + + if 'log' in rule_conf: + action = rule_conf['action'] if 'action' in rule_conf else 'accept' + #output.append(f'log prefix "[{fw_name[:19]}-{rule_id}-{action[:1].upper()}]"') + output.append(f'log prefix "[{family}-{hook}-{fw_name}-{rule_id}-{action[:1].upper()}]"') + ##{family}-{hook}-{fw_name}-{rule_id} + if 'log_options' in rule_conf: + + if 'level' in rule_conf['log_options']: + log_level = rule_conf['log_options']['level'] + output.append(f'log level {log_level}') + + if 'group' in rule_conf['log_options']: + log_group = rule_conf['log_options']['group'] + output.append(f'log group {log_group}') + + if 'queue_threshold' in rule_conf['log_options']: + queue_threshold = rule_conf['log_options']['queue_threshold'] + output.append(f'queue-threshold {queue_threshold}') + + if 'snapshot_length' in rule_conf['log_options']: + log_snaplen = rule_conf['log_options']['snapshot_length'] + output.append(f'snaplen {log_snaplen}') + + output.append('counter') + + if 'add_address_to_group' in rule_conf: + for side in ['destination_address', 'source_address']: + if side in rule_conf['add_address_to_group']: + prefix = side[0] + side_conf = rule_conf['add_address_to_group'][side] + dyn_group = side_conf['address_group'] + if 'timeout' in side_conf: + timeout_value = side_conf['timeout'] + output.append(f'set update ip{def_suffix} {prefix}addr timeout {timeout_value} @DA{def_suffix}_{dyn_group}') + else: + output.append(f'set update ip{def_suffix} saddr @DA{def_suffix}_{dyn_group}') + + set_table = False + if 'set' in rule_conf: + # Parse set command used in policy route: + if 'connection_mark' in rule_conf['set']: + conn_mark = rule_conf['set']['connection_mark'] + output.append(f'ct mark set {conn_mark}') + if 'dscp' in rule_conf['set']: + dscp = rule_conf['set']['dscp'] + output.append(f'ip{def_suffix} dscp set {dscp}') + if 'mark' in rule_conf['set']: + mark = rule_conf['set']['mark'] + output.append(f'meta mark set {mark}') + if 'vrf' in rule_conf['set']: + set_table = True + vrf_name = rule_conf['set']['vrf'] + if vrf_name == 'default': + table = rt_global_vrf + else: + # NOTE: VRF->table ID lookup depends on the VRF iface already existing. + table = get_vrf_tableid(vrf_name) + if 'table' in rule_conf['set']: + set_table = True + table = rule_conf['set']['table'] + if table == 'main': + table = rt_global_table + if set_table: + mark = 0x7FFFFFFF - int(table) + output.append(f'meta mark set {mark}') + if 'tcp_mss' in rule_conf['set']: + mss = rule_conf['set']['tcp_mss'] + output.append(f'tcp option maxseg size set {mss}') + + if 'action' in rule_conf: + if rule_conf['action'] == 'offload': + offload_target = rule_conf['offload_target'] + output.append(f'flow add @VYOS_FLOWTABLE_{offload_target}') + else: + output.append(f'{rule_conf["action"]}') + + if 'jump' in rule_conf['action']: + target = rule_conf['jump_target'] + output.append(f'NAME{def_suffix}_{target}') + + if 'queue' in rule_conf['action']: + if 'queue' in rule_conf: + target = rule_conf['queue'] + output.append(f'num {target}') + + if 'queue_options' in rule_conf: + queue_opts = ','.join(rule_conf['queue_options']) + output.append(f'{queue_opts}') + + # Synproxy + if 'synproxy' in rule_conf: + synproxy_mss = dict_search_args(rule_conf, 'synproxy', 'tcp', 'mss') + if synproxy_mss: + output.append(f'mss {synproxy_mss}') + synproxy_ws = dict_search_args(rule_conf, 'synproxy', 'tcp', 'window_scale') + if synproxy_ws: + output.append(f'wscale {synproxy_ws} timestamp sack-perm') + + else: + if set_table: + output.append('return') + + output.append(f'comment "{family}-{hook}-{fw_name}-{rule_id}"') + return " ".join(output) + +def parse_gre_flags(flags, force_keyed=False): + flag_map = { # nft does not have symbolic names for these. + 'checksum': 1<<0, + 'routing': 1<<1, + 'key': 1<<2, + 'sequence': 1<<3, + 'strict_routing': 1<<4, + } + + include = 0 + exclude = 0 + for fl_name, fl_state in flags.items(): + if not fl_state: + include |= flag_map[fl_name] + else: # 'unset' child tag + exclude |= flag_map[fl_name] + + if force_keyed: + # Implied by a key-match. + include |= flag_map['key'] + + if include == 0 and exclude == 0: + return '' # Don't bother extracting and matching no bits + + return f'gre flags & {include + exclude} == {include}' + +def parse_tcp_flags(flags): + include = [flag for flag in flags if flag != 'not'] + exclude = list(flags['not']) if 'not' in flags else [] + return f'tcp flags & ({"|".join(include + exclude)}) == {"|".join(include) if include else "0x0"}' + +def parse_time(time): + out = [] + if 'startdate' in time: + start = time['startdate'] + if 'T' not in start and 'starttime' in time: + start += f' {time["starttime"]}' + out.append(f'time >= "{start}"') + if 'starttime' in time and 'startdate' not in time: + out.append(f'hour >= "{time["starttime"]}"') + if 'stopdate' in time: + stop = time['stopdate'] + if 'T' not in stop and 'stoptime' in time: + stop += f' {time["stoptime"]}' + out.append(f'time < "{stop}"') + if 'stoptime' in time and 'stopdate' not in time: + out.append(f'hour < "{time["stoptime"]}"') + if 'weekdays' in time: + days = time['weekdays'].split(",") + out_days = [f'"{day}"' for day in days if day[0] != '!'] + out.append(f'day {{{",".join(out_days)}}}') + return " ".join(out) + +# GeoIP + +nftables_geoip_conf = '/run/nftables-geoip.conf' +geoip_database = '/usr/share/vyos-geoip/dbip-country-lite.csv.gz' +geoip_lock_file = '/run/vyos-geoip.lock' + +def geoip_load_data(codes=[]): + data = None + + if not os.path.exists(geoip_database): + return [] + + try: + with gzip.open(geoip_database, mode='rt') as csv_fh: + reader = csv.reader(csv_fh) + out = [] + for start, end, code in reader: + if code.lower() in codes: + out.append([start, end, code.lower()]) + return out + except: + print('Error: Failed to open GeoIP database') + return [] + +def geoip_download_data(): + url = 'https://download.db-ip.com/free/dbip-country-lite-{}.csv.gz'.format(strftime("%Y-%m")) + try: + dirname = os.path.dirname(geoip_database) + if not os.path.exists(dirname): + os.mkdir(dirname) + + download(geoip_database, url) + print("Downloaded GeoIP database") + return True + except: + print("Error: Failed to download GeoIP database") + return False + +class GeoIPLock(object): + def __init__(self, file): + self.file = file + + def __enter__(self): + if os.path.exists(self.file): + return False + + Path(self.file).touch() + return True + + def __exit__(self, exc_type, exc_value, tb): + os.unlink(self.file) + +def geoip_update(firewall, force=False): + with GeoIPLock(geoip_lock_file) as lock: + if not lock: + print("Script is already running") + return False + + if not firewall: + print("Firewall is not configured") + return True + + if not os.path.exists(geoip_database): + if not geoip_download_data(): + return False + elif force: + geoip_download_data() + + ipv4_codes = {} + ipv6_codes = {} + + ipv4_sets = {} + ipv6_sets = {} + + # Map country codes to set names + for codes, path in dict_search_recursive(firewall, 'country_code'): + set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' + if ( path[0] == 'ipv4'): + for code in codes: + ipv4_codes.setdefault(code, []).append(set_name) + elif ( path[0] == 'ipv6' ): + set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}' + for code in codes: + ipv6_codes.setdefault(code, []).append(set_name) + + if not ipv4_codes and not ipv6_codes: + if force: + print("GeoIP not in use by firewall") + return True + + geoip_data = geoip_load_data([*ipv4_codes, *ipv6_codes]) + + # Iterate IP blocks to assign to sets + for start, end, code in geoip_data: + ipv4 = is_ipv4(start) + if code in ipv4_codes and ipv4: + ip_range = f'{start}-{end}' if start != end else start + for setname in ipv4_codes[code]: + ipv4_sets.setdefault(setname, []).append(ip_range) + if code in ipv6_codes and not ipv4: + ip_range = f'{start}-{end}' if start != end else start + for setname in ipv6_codes[code]: + ipv6_sets.setdefault(setname, []).append(ip_range) + + render(nftables_geoip_conf, 'firewall/nftables-geoip-update.j2', { + 'ipv4_sets': ipv4_sets, + 'ipv6_sets': ipv6_sets + }) + + result = run(f'nft --file {nftables_geoip_conf}') + if result != 0: + print('Error: GeoIP failed to update firewall') + return False + + return True diff --git a/python/vyos/frr.py b/python/vyos/frr.py new file mode 100644 index 0000000..6fb8180 --- /dev/null +++ b/python/vyos/frr.py @@ -0,0 +1,551 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +r""" +A Library for interracting with the FRR daemon suite. +It supports simple configuration manipulation and loading using the official tools +supplied with FRR (vtysh and frr-reload) + +All configuration management and manipulation is done using strings and regex. + + +Example Usage +##### + +# Reading configuration from frr: +``` +>>> original_config = get_configuration() +>>> repr(original_config) +'!\nfrr version 7.3.1\nfrr defaults traditional\nhostname debian\n...... +``` + + +# Modify a configuration section: +``` +>>> new_bgp_section = 'router bgp 65000\n neighbor 192.0.2.1 remote-as 65000\n' +>>> modified_config = replace_section(original_config, new_bgp_section, replace_re=r'router bgp \d+') +>>> repr(modified_config) +'............router bgp 65000\n neighbor 192.0.2.1 remote-as 65000\n...........' +``` + +Remove a configuration section: +``` +>>> modified_config = remove_section(original_config, r'router ospf') +``` + +Test the new configuration: +``` +>>> try: +>>> mark_configuration(modified configuration) +>>> except ConfigurationNotValid as e: +>>> print('resulting configuration is not valid') +>>> sys.exit(1) +``` + +Apply the new configuration: +``` +>>> try: +>>> replace_configuration(modified_config) +>>> except CommitError as e: +>>> print('Exception while commiting the supplied configuration') +>>> print(e) +>>> exit(1) +``` +""" + +import tempfile +import re + +from vyos import ConfigError +from vyos.utils.process import cmd +from vyos.utils.process import popen +from vyos.utils.process import STDOUT + +import logging +from logging.handlers import SysLogHandler +import os +import sys + +LOG = logging.getLogger(__name__) +DEBUG = False + +ch = SysLogHandler(address='/dev/log') +ch2 = logging.StreamHandler(stream=sys.stdout) +LOG.addHandler(ch) +LOG.addHandler(ch2) + +_frr_daemons = ['zebra', 'staticd', 'bgpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd', + 'isisd', 'pimd', 'pim6d', 'ldpd', 'eigrpd', 'babeld', 'bfdd', 'fabricd'] + +path_vtysh = '/usr/bin/vtysh' +path_frr_reload = '/usr/lib/frr/frr-reload.py' +path_config = '/run/frr' + +default_add_before = r'(ip prefix-list .*|route-map .*|line vty|end)' + + +class FrrError(Exception): + pass + + +class ConfigurationNotValid(FrrError): + """ + The configuratioin supplied to vtysh is not valid + """ + pass + + +class CommitError(FrrError): + """ + Commiting the supplied configuration failed to commit by a unknown reason + see commit error and/or run mark_configuration on the specified configuration + to se error generated + + used by: reload_configuration() + """ + pass + + +class ConfigSectionNotFound(FrrError): + """ + Removal of configuration failed because it is not existing in the supplied configuration + """ + pass + +def init_debugging(): + global DEBUG + + DEBUG = os.path.exists('/tmp/vyos.frr.debug') + if DEBUG: + LOG.setLevel(logging.DEBUG) + +def get_configuration(daemon=None, marked=False): + """ Get current running FRR configuration + daemon: Collect only configuration for the specified FRR daemon, + supplying daemon=None retrieves the complete configuration + marked: Mark the configuration with "end" tags + + return: string containing the running configuration from frr + + """ + if daemon and daemon not in _frr_daemons: + raise ValueError(f'The specified daemon type is not supported {repr(daemon)}') + + cmd = f"{path_vtysh} -c 'show run'" + if daemon: + cmd += f' -d {daemon}' + + output, code = popen(cmd, stderr=STDOUT) + if code: + raise OSError(code, output) + + config = output.replace('\r', '') + # Remove first header lines from FRR config + config = config.split("\n", 3)[-1] + # Mark the configuration with end tags + if marked: + config = mark_configuration(config) + + return config + + +def mark_configuration(config): + """ Add end marks and Test the configuration for syntax faults + If the configuration is valid a marked version of the configuration is returned, + or else it failes with a ConfigurationNotValid Exception + + config: The configuration string to mark/test + return: The marked configuration from FRR + """ + output, code = popen(f"{path_vtysh} -m -f -", stderr=STDOUT, input=config) + + if code == 2: + raise ConfigurationNotValid(str(output)) + elif code: + raise OSError(code, output) + + config = output.replace('\r', '') + return config + + +def reload_configuration(config, daemon=None): + """ Execute frr-reload with the new configuration + This will try to reapply the supplied configuration inside FRR. + The configuration needs to be a complete configuration from the integrated config or + from a daemon. + + config: The configuration to apply + daemon: Apply the conigutaion to the specified FRR daemon, + supplying daemon=None applies to the integrated configuration + return: None + """ + if daemon and daemon not in _frr_daemons: + raise ValueError(f'The specified daemon type is not supported {repr(daemon)}') + + f = tempfile.NamedTemporaryFile('w') + f.write(config) + f.flush() + + LOG.debug(f'reload_configuration: Reloading config using temporary file: {f.name}') + cmd = f'{path_frr_reload} --reload' + if daemon: + cmd += f' --daemon {daemon}' + + if DEBUG: + cmd += f' --debug --stdout' + + cmd += f' {f.name}' + + LOG.debug(f'reload_configuration: Executing command against frr-reload: "{cmd}"') + output, code = popen(cmd, stderr=STDOUT) + f.close() + + for i, e in enumerate(output.split('\n')): + LOG.debug(f'frr-reload output: {i:3} {e}') + + if code == 1: + raise ConfigError(output) + elif code: + raise OSError(code, output) + + return output + + +def save_configuration(): + """ T3217: Save FRR configuration to /run/frr/config/frr.conf """ + return cmd(f'{path_vtysh} -n -w') + + +def execute(command): + """ Run commands inside vtysh + command: str containing commands to execute inside a vtysh session + """ + if not isinstance(command, str): + raise ValueError(f'command needs to be a string: {repr(command)}') + + cmd = f"{path_vtysh} -c '{command}'" + + output, code = popen(cmd, stderr=STDOUT) + if code: + raise OSError(code, output) + + config = output.replace('\r', '') + return config + + +def configure(lines, daemon=False): + """ run commands inside config mode vtysh + lines: list or str conaining commands to execute inside a configure session + only one command executed on each configure() + Executing commands inside a subcontext uses the list to describe the context + ex: ['router bgp 6500', 'neighbor 192.0.2.1 remote-as 65000'] + return: None + """ + if isinstance(lines, str): + lines = [lines] + elif not isinstance(lines, list): + raise ValueError('lines needs to be string or list of commands') + + if daemon and daemon not in _frr_daemons: + raise ValueError(f'The specified daemon type is not supported {repr(daemon)}') + + cmd = f'{path_vtysh}' + if daemon: + cmd += f' -d {daemon}' + + cmd += " -c 'configure terminal'" + for x in lines: + cmd += f" -c '{x}'" + + output, code = popen(cmd, stderr=STDOUT) + if code == 1: + raise ConfigurationNotValid(f'Configuration FRR failed: {repr(output)}') + elif code: + raise OSError(code, output) + + config = output.replace('\r', '') + return config + + +def _replace_section(config, replacement, replace_re, before_re): + r"""Replace a section of FRR config + config: full original configuration + replacement: replacement configuration section + replace_re: The regex to replace + example: ^router bgp \d+$.?*^!$ + this will replace everything between ^router bgp X$ and ^!$ + before_re: When replace_re is not existant, the config will be added before this tag + example: ^line vty$ + + return: modified configuration as a text file + """ + # DEPRECATED, this is replaced by a new implementation + # Check if block is configured, remove the existing instance else add a new one + if re.findall(replace_re, config, flags=re.MULTILINE | re.DOTALL): + # Section is in the configration, replace it + return re.sub(replace_re, replacement, config, count=1, + flags=re.MULTILINE | re.DOTALL) + if before_re: + if not re.findall(before_re, config, flags=re.MULTILINE | re.DOTALL): + raise ConfigSectionNotFound(f"Config section {before_re} not found in config") + + # If no section is in the configuration, add it before the line vty line + return re.sub(before_re, rf'{replacement}\n\g<1>', config, count=1, + flags=re.MULTILINE | re.DOTALL) + + raise ConfigSectionNotFound(f"Config section {replacement} not found in config") + + +def replace_section(config, replacement, from_re, to_re=r'!', before_re=r'line vty'): + r"""Replace a section of FRR config + config: full original configuration + replacement: replacement configuration section + from_re: Regex for the start of section matching + example: 'router bgp \d+' + to_re: Regex for stop of section matching + default: '!' + example: '!' or 'end' + before_re: When from_re/to_re does not return a match, the config will + be added before this tag + default: ^line vty$ + + startline and endline tags will be automatically added to the resulting from_re/to_re and before_re regex'es + """ + # DEPRECATED, this is replaced by a new implementation + return _replace_section(config, replacement, replace_re=rf'^{from_re}$.*?^{to_re}$', before_re=rf'^({before_re})$') + + +def remove_section(config, from_re, to_re='!'): + # DEPRECATED, this is replaced by a new implementation + return _replace_section(config, '', replace_re=rf'^{from_re}$.*?^{to_re}$', before_re=None) + + +def _find_first_block(config, start_pattern, stop_pattern, start_at=0): + '''Find start and stop line numbers for a config block + config: (list) A list conaining the configuration that is searched + start_pattern: (raw-str) The pattern searched for a a start of block tag + stop_pattern: (raw-str) The pattern searched for to signify the end of the block + start_at: (int) The index to start searching at in the <config> + + Returns: + None: No complete block could be found + set(int, int): A complete block found between the line numbers returned in the set + + The object <config> is searched from the start for the regex <start_pattern> until the first match is found. + On a successful match it continues the search for the regex <stop_pattern> until it is found. + After a successful run a set is returned containing the start and stop line numbers. + ''' + LOG.debug(f'_find_first_block: find start={repr(start_pattern)} stop={repr(stop_pattern)} start_at={start_at}') + _start = None + for i, element in enumerate(config[start_at:], start=start_at): + # LOG.debug(f'_find_first_block: running line {i:3} "{element}"') + if not _start: + if not re.match(start_pattern, element): + LOG.debug(f'_find_first_block: no match {i:3} "{element}"') + continue + _start = i + LOG.debug(f'_find_first_block: Found start {i:3} "{element}"') + continue + + if not re.match(stop_pattern, element): + LOG.debug(f'_find_first_block: no match {i:3} "{element}"') + continue + + LOG.debug(f'_find_first_block: Found stop {i:3} "{element}"') + return (_start, i) + + LOG.debug('_find_first_block: exit start={repr(start_pattern)} stop={repr(stop_pattern)} start_at={start_at}') + return None + + +def _find_first_element(config, pattern, start_at=0): + '''Find the first element that matches the current pattern in config + config: (list) A list containing the configuration that is searched + start_pattern: (raw-str) The pattern searched for + start_at: (int) The index to start searching at in the <config> + + return: Line index of the line containing the searched pattern + + TODO: for now it returns -1 on a no-match because 0 also returns as False + TODO: that means that we can not use False matching to tell if its + ''' + LOG.debug(f'_find_first_element: find start="{pattern}" start_at={start_at}') + for i, element in enumerate(config[start_at:], start=0): + if re.match(pattern + '$', element): + LOG.debug(f'_find_first_element: Found stop {i:3} "{element}"') + return i + LOG.debug(f'_find_first_element: no match {i:3} "{element}"') + LOG.debug(f'_find_first_element: Did not find any match, exiting') + return -1 + + +def _find_elements(config, pattern, start_at=0): + '''Find all instances of pattern and return a list containing all element indexes + config: (list) A list containing the configuration that is searched + start_pattern: (raw-str) The pattern searched for + start_at: (int) The index to start searching at in the <config> + + return: A list of line indexes containing the searched pattern + TODO: refactor this to return a generator instead + ''' + return [i for i, element in enumerate(config[start_at:], start=0) if re.match(pattern + '$', element)] + + +class FRRConfig: + '''Main FRR Configuration manipulation object + Using this object the user could load, manipulate and commit the configuration to FRR + ''' + def __init__(self, config=[]): + self.imported_config = '' + + if isinstance(config, list): + self.config = config.copy() + self.original_config = config.copy() + elif isinstance(config, str): + self.config = config.split('\n') + self.original_config = self.config.copy() + else: + raise ValueError( + 'The config element needs to be a string or list type object') + + if config: + LOG.debug(f'__init__: frr library initiated with initial config') + for i, e in enumerate(self.config): + LOG.debug(f'__init__: initial {i:3} {e}') + + def load_configuration(self, daemon=None): + '''Load the running configuration from FRR into the config object + daemon: str with name of the FRR Daemon to load configuration from or + None to load the consolidated config + + Using this overwrites the current loaded config objects and replaces the original loaded config + ''' + init_debugging() + + self.imported_config = get_configuration(daemon=daemon) + if daemon: + LOG.debug(f'load_configuration: Configuration loaded from FRR daemon {daemon}') + else: + LOG.debug(f'load_configuration: Configuration loaded from FRR integrated config') + + self.original_config = self.imported_config.split('\n') + self.config = self.original_config.copy() + + for i, e in enumerate(self.imported_config.split('\n')): + LOG.debug(f'load_configuration: loaded {i:3} {e}') + return + + def test_configuration(self): + '''Test the current configuration against FRR + This will exception if FRR failes to load the current configuration object + ''' + LOG.debug('test_configation: Testing configuration') + mark_configuration('\n'.join(self.config)) + + def commit_configuration(self, daemon=None): + ''' + Commit the current configuration to FRR daemon: str with name of the + FRR daemon to commit to or None to use the consolidated config. + + Configuration is automatically saved after apply + ''' + LOG.debug('commit_configuration: Commiting configuration') + for i, e in enumerate(self.config): + LOG.debug(f'commit_configuration: new_config {i:3} {e}') + + # https://github.com/FRRouting/frr/issues/10132 + # https://github.com/FRRouting/frr/issues/10133 + count = 0 + count_max = 5 + emsg = '' + while count < count_max: + count += 1 + try: + reload_configuration('\n'.join(self.config), daemon=daemon) + break + except ConfigError as e: + emsg = str(e) + except: + # we just need to re-try the commit of the configuration + # for the listed FRR issues above + pass + if count >= count_max: + if emsg: + raise ConfigError(emsg) + raise ConfigurationNotValid(f'Config commit retry counter ({count_max}) exceeded for {daemon} daemon!') + + # Save configuration to /run/frr/config/frr.conf + save_configuration() + + + def modify_section(self, start_pattern, replacement='!', stop_pattern=r'\S+', remove_stop_mark=False, count=0): + if isinstance(replacement, str): + replacement = replacement.split('\n') + elif not isinstance(replacement, list): + return ValueError("The replacement element needs to be a string or list type object") + LOG.debug(f'modify_section: starting search for {repr(start_pattern)} until {repr(stop_pattern)}') + + _count = 0 + _next_start = 0 + while True: + if count and count <= _count: + # Break out of the loop after specified amount of matches + LOG.debug(f'modify_section: reached limit ({_count}), exiting loop at line {_next_start}') + break + # While searching, always assume that the user wants to search for the exact pattern he entered + # To be more specific the user needs a override, eg. a "pattern.*" + _w = _find_first_block( + self.config, start_pattern+'$', stop_pattern, start_at=_next_start) + if not _w: + # Reached the end, no more elements to remove + LOG.debug(f'modify_section: No more config sections found, exiting') + break + start_element, end_element = _w + LOG.debug(f'modify_section: found match between {start_element} and {end_element}') + for i, e in enumerate(self.config[start_element:end_element+1 if remove_stop_mark else end_element], + start=start_element): + LOG.debug(f'modify_section: remove {i:3} {e}') + del self.config[start_element:end_element + + 1 if remove_stop_mark else end_element] + if replacement: + # Append the replacement config at the current position + for i, e in enumerate(replacement, start=start_element): + LOG.debug(f'modify_section: add {i:3} {e}') + self.config[start_element:start_element] = replacement + _count += 1 + _next_start = start_element + len(replacement) + + return _count + + def add_before(self, before_pattern, addition): + '''Add config block before this element in the configuration''' + if isinstance(addition, str): + addition = addition.split('\n') + elif not isinstance(addition, list): + return ValueError("The replacement element needs to be a string or list type object") + + start = _find_first_element(self.config, before_pattern) + if start < 0: + return False + for i, e in enumerate(addition, start=start): + LOG.debug(f'add_before: add {i:3} {e}') + self.config[start:start] = addition + return True + + def __str__(self): + return '\n'.join(self.config) + + def __repr__(self): + return f'frr({repr(str(self))})' diff --git a/python/vyos/hostsd_client.py b/python/vyos/hostsd_client.py new file mode 100644 index 0000000..f31ef51 --- /dev/null +++ b/python/vyos/hostsd_client.py @@ -0,0 +1,131 @@ +import json +import zmq + +SOCKET_PATH = "ipc:///run/vyos-hostsd/vyos-hostsd.sock" + +class VyOSHostsdError(Exception): + pass + +class Client(object): + def __init__(self): + try: + context = zmq.Context() + self.__socket = context.socket(zmq.REQ) + self.__socket.RCVTIMEO = 10000 #ms + self.__socket.setsockopt(zmq.LINGER, 0) + self.__socket.connect(SOCKET_PATH) + except zmq.error.Again: + raise VyOSHostsdError("Could not connect to vyos-hostsd") + + def _communicate(self, msg): + try: + request = json.dumps(msg).encode() + self.__socket.send(request) + + reply_msg = self.__socket.recv().decode() + reply = json.loads(reply_msg) + if 'error' in reply: + raise VyOSHostsdError(reply['error']) + else: + return reply["data"] + except zmq.error.Again: + raise VyOSHostsdError("Could not connect to vyos-hostsd") + + def add_name_servers(self, data): + msg = {'type': 'name_servers', 'op': 'add', 'data': data} + self._communicate(msg) + + def delete_name_servers(self, data): + msg = {'type': 'name_servers', 'op': 'delete', 'data': data} + self._communicate(msg) + + def get_name_servers(self, tag_regex): + msg = {'type': 'name_servers', 'op': 'get', 'tag_regex': tag_regex} + return self._communicate(msg) + + def add_name_server_tags_recursor(self, data): + msg = {'type': 'name_server_tags_recursor', 'op': 'add', 'data': data} + self._communicate(msg) + + def delete_name_server_tags_recursor(self, data): + msg = {'type': 'name_server_tags_recursor', 'op': 'delete', 'data': data} + self._communicate(msg) + + def get_name_server_tags_recursor(self): + msg = {'type': 'name_server_tags_recursor', 'op': 'get'} + return self._communicate(msg) + + def add_name_server_tags_system(self, data): + msg = {'type': 'name_server_tags_system', 'op': 'add', 'data': data} + self._communicate(msg) + + def delete_name_server_tags_system(self, data): + msg = {'type': 'name_server_tags_system', 'op': 'delete', 'data': data} + self._communicate(msg) + + def get_name_server_tags_system(self): + msg = {'type': 'name_server_tags_system', 'op': 'get'} + return self._communicate(msg) + + def add_forward_zones(self, data): + msg = {'type': 'forward_zones', 'op': 'add', 'data': data} + self._communicate(msg) + + def delete_forward_zones(self, data): + msg = {'type': 'forward_zones', 'op': 'delete', 'data': data} + self._communicate(msg) + + def get_forward_zones(self): + msg = {'type': 'forward_zones', 'op': 'get'} + return self._communicate(msg) + + def add_authoritative_zones(self, data): + msg = {'type': 'authoritative_zones', 'op': 'add', 'data': data} + self._communicate(msg) + + def delete_authoritative_zones(self, data): + msg = {'type': 'authoritative_zones', 'op': 'delete', 'data': data} + self._communicate(msg) + + def get_authoritative_zones(self): + msg = {'type': 'authoritative_zones', 'op': 'get'} + return self._communicate(msg) + + def add_search_domains(self, data): + msg = {'type': 'search_domains', 'op': 'add', 'data': data} + self._communicate(msg) + + def delete_search_domains(self, data): + msg = {'type': 'search_domains', 'op': 'delete', 'data': data} + self._communicate(msg) + + def get_search_domains(self, tag_regex): + msg = {'type': 'search_domains', 'op': 'get', 'tag_regex': tag_regex} + return self._communicate(msg) + + def add_hosts(self, data): + msg = {'type': 'hosts', 'op': 'add', 'data': data} + self._communicate(msg) + + def delete_hosts(self, data): + msg = {'type': 'hosts', 'op': 'delete', 'data': data} + self._communicate(msg) + + def get_hosts(self, tag_regex): + msg = {'type': 'hosts', 'op': 'get', 'tag_regex': tag_regex} + return self._communicate(msg) + + def set_host_name(self, host_name, domain_name): + msg = { + 'type': 'host_name', + 'op': 'set', + 'data': { + 'host_name': host_name, + 'domain_name': domain_name, + } + } + self._communicate(msg) + + def apply(self): + msg = {'op': 'apply'} + return self._communicate(msg) diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py new file mode 100644 index 0000000..206b2bb --- /dev/null +++ b/python/vyos/ifconfig/__init__.py @@ -0,0 +1,41 @@ +# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig.section import Section +from vyos.ifconfig.control import Control +from vyos.ifconfig.interface import Interface +from vyos.ifconfig.operational import Operational +from vyos.ifconfig.vrrp import VRRP + +from vyos.ifconfig.bond import BondIf +from vyos.ifconfig.bridge import BridgeIf +from vyos.ifconfig.dummy import DummyIf +from vyos.ifconfig.ethernet import EthernetIf +from vyos.ifconfig.geneve import GeneveIf +from vyos.ifconfig.loopback import LoopbackIf +from vyos.ifconfig.macvlan import MACVLANIf +from vyos.ifconfig.input import InputIf +from vyos.ifconfig.vxlan import VXLANIf +from vyos.ifconfig.wireguard import WireGuardIf +from vyos.ifconfig.vtun import VTunIf +from vyos.ifconfig.vti import VTIIf +from vyos.ifconfig.pppoe import PPPoEIf +from vyos.ifconfig.tunnel import TunnelIf +from vyos.ifconfig.wireless import WiFiIf +from vyos.ifconfig.l2tpv3 import L2TPv3If +from vyos.ifconfig.macsec import MACsecIf +from vyos.ifconfig.veth import VethIf +from vyos.ifconfig.wwan import WWANIf +from vyos.ifconfig.sstpc import SSTPCIf diff --git a/python/vyos/ifconfig/afi.py b/python/vyos/ifconfig/afi.py new file mode 100644 index 0000000..fd263d2 --- /dev/null +++ b/python/vyos/ifconfig/afi.py @@ -0,0 +1,19 @@ +# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +# https://www.iana.org/assignments/address-family-numbers/address-family-numbers.xhtml + +IP4 = 1 +IP6 = 2 diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py new file mode 100644 index 0000000..8ba4817 --- /dev/null +++ b/python/vyos/ifconfig/bond.py @@ -0,0 +1,509 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os + +from vyos.ifconfig.interface import Interface +from vyos.utils.dict import dict_search +from vyos.utils.assertion import assert_list +from vyos.utils.assertion import assert_mac +from vyos.utils.assertion import assert_positive + +@Interface.register +class BondIf(Interface): + """ + The Linux bonding driver provides a method for aggregating multiple network + interfaces into a single logical "bonded" interface. The behavior of the + bonded interfaces depends upon the mode; generally speaking, modes provide + either hot standby or load balancing services. Additionally, link integrity + monitoring may be performed. + """ + + iftype = 'bond' + definition = { + **Interface.definition, + ** { + 'section': 'bonding', + 'prefixes': ['bond', ], + 'broadcast': True, + 'bridgeable': True, + }, + } + + _sysfs_set = {**Interface._sysfs_set, **{ + 'bond_hash_policy': { + 'validate': lambda v: assert_list(v, ['layer2', 'layer2+3', 'layer3+4', 'encap2+3', 'encap3+4']), + 'location': '/sys/class/net/{ifname}/bonding/xmit_hash_policy', + }, + 'bond_min_links': { + 'validate': assert_positive, + 'location': '/sys/class/net/{ifname}/bonding/min_links', + }, + 'bond_lacp_rate': { + 'validate': lambda v: assert_list(v, ['slow', 'fast']), + 'location': '/sys/class/net/{ifname}/bonding/lacp_rate', + }, + 'bond_system_mac': { + 'validate': lambda v: assert_mac(v, test_all_zero=False), + 'location': '/sys/class/net/{ifname}/bonding/ad_actor_system', + }, + 'bond_miimon': { + 'validate': assert_positive, + 'location': '/sys/class/net/{ifname}/bonding/miimon' + }, + 'bond_arp_interval': { + 'validate': assert_positive, + 'location': '/sys/class/net/{ifname}/bonding/arp_interval' + }, + 'bond_arp_ip_target': { + # XXX: no validation of the IP + 'location': '/sys/class/net/{ifname}/bonding/arp_ip_target', + }, + 'bond_add_port': { + 'location': '/sys/class/net/{ifname}/bonding/slaves', + }, + 'bond_del_port': { + 'location': '/sys/class/net/{ifname}/bonding/slaves', + }, + 'bond_primary': { + 'convert': lambda name: name if name else '\0', + 'location': '/sys/class/net/{ifname}/bonding/primary', + }, + 'bond_mode': { + 'validate': lambda v: assert_list(v, ['balance-rr', 'active-backup', 'balance-xor', 'broadcast', '802.3ad', 'balance-tlb', 'balance-alb']), + 'location': '/sys/class/net/{ifname}/bonding/mode', + }, + }} + + _sysfs_get = {**Interface._sysfs_get, **{ + 'bond_arp_ip_target': { + 'location': '/sys/class/net/{ifname}/bonding/arp_ip_target', + }, + 'bond_mode': { + 'location': '/sys/class/net/{ifname}/bonding/mode', + } + }} + + @staticmethod + def get_inherit_bond_options() -> list: + """ + Returns list of option + which are inherited from bond interface to member interfaces + :return: List of interface options + :rtype: list + """ + options = [ + 'mtu' + ] + return options + + def remove(self): + """ + Remove interface from operating system. Removing the interface + deconfigures all assigned IP addresses and clear possible DHCP(v6) + client processes. + Example: + >>> from vyos.ifconfig import Interface + >>> i = Interface('eth0') + >>> i.remove() + """ + # when a bond member gets deleted, all members are placed in A/D state + # even when they are enabled inside CLI. This will make the config + # and system look async. + slave_list = [] + for s in self.get_slaves(): + slave = { + 'ifname': s, + 'state': Interface(s).get_admin_state() + } + slave_list.append(slave) + + # remove bond master which places members in disabled state + super().remove() + + # replicate previous interface state before bond destruction back to + # physical interface + for slave in slave_list: + i = Interface(slave['ifname']) + i.set_admin_state(slave['state']) + + def set_hash_policy(self, mode): + """ + Selects the transmit hash policy to use for slave selection in + balance-xor, 802.3ad, and tlb modes. Possible values are: layer2, + layer2+3, layer3+4, encap2+3, encap3+4. + + The default value is layer2 + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').set_hash_policy('layer2+3') + """ + self.set_interface('bond_hash_policy', mode) + + def set_min_links(self, number): + """ + Specifies the minimum number of links that must be active before + asserting carrier. It is similar to the Cisco EtherChannel min-links + feature. This allows setting the minimum number of member ports that + must be up (link-up state) before marking the bond device as up + (carrier on). This is useful for situations where higher level services + such as clustering want to ensure a minimum number of low bandwidth + links are active before switchover. This option only affect 802.3ad + mode. + + The default value is 0. This will cause carrier to be asserted (for + 802.3ad mode) whenever there is an active aggregator, regardless of the + number of available links in that aggregator. Note that, because an + aggregator cannot be active without at least one available link, + setting this option to 0 or to 1 has the exact same effect. + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').set_min_links('0') + """ + self.set_interface('bond_min_links', number) + + def set_lacp_rate(self, slow_fast): + """ + Option specifying the rate in which we'll ask our link partner + to transmit LACPDU packets in 802.3ad mode. Possible values + are: + + slow or 0 + Request partner to transmit LACPDUs every 30 seconds + + fast or 1 + Request partner to transmit LACPDUs every 1 second + + The default is slow. + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').set_lacp_rate('slow') + """ + self.set_interface('bond_lacp_rate', slow_fast) + + def set_miimon_interval(self, interval): + """ + Specifies the MII link monitoring frequency in milliseconds. This + determines how often the link state of each slave is inspected for link + failures. A value of zero disables MII link monitoring. A value of 100 + is a good starting point. + + The default value is 0. + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').set_miimon_interval('100') + """ + return self.set_interface('bond_miimon', interval) + + def set_arp_interval(self, interval): + """ + Specifies the ARP link monitoring frequency in milliseconds. + + The ARP monitor works by periodically checking the slave devices + to determine whether they have sent or received traffic recently + (the precise criteria depends upon the bonding mode, and the + state of the slave). Regular traffic is generated via ARP probes + issued for the addresses specified by the arp_ip_target option. + + If ARP monitoring is used in an etherchannel compatible mode + (modes 0 and 2), the switch should be configured in a mode that + evenly distributes packets across all links. If the switch is + configured to distribute the packets in an XOR fashion, all + replies from the ARP targets will be received on the same link + which could cause the other team members to fail. + + value of 0 disables ARP monitoring. The default value is 0. + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').set_arp_interval('100') + """ + return self.set_interface('bond_arp_interval', interval) + + def get_arp_ip_target(self): + """ + Specifies the IP addresses to use as ARP monitoring peers when + arp_interval is > 0. These are the targets of the ARP request sent to + determine the health of the link to the targets. Specify these values + in ddd.ddd.ddd.ddd format. Multiple IP addresses must be separated by + a comma. At least one IP address must be given for ARP monitoring to + function. The maximum number of targets that can be specified is 16. + + The default value is no IP addresses. + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').get_arp_ip_target() + '192.0.2.1' + """ + # As this function might also be called from update() of a VLAN interface + # we must check if the bond_arp_ip_target retrieval worked or not - as this + # can not be set for a bond vif interface + try: + return self.get_interface('bond_arp_ip_target') + except FileNotFoundError: + return '' + + def set_arp_ip_target(self, target): + """ + Specifies the IP addresses to use as ARP monitoring peers when + arp_interval is > 0. These are the targets of the ARP request sent to + determine the health of the link to the targets. Specify these values + in ddd.ddd.ddd.ddd format. Multiple IP addresses must be separated by + a comma. At least one IP address must be given for ARP monitoring to + function. The maximum number of targets that can be specified is 16. + + The default value is no IP addresses. + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').set_arp_ip_target('192.0.2.1') + >>> BondIf('bond0').get_arp_ip_target() + '192.0.2.1' + """ + return self.set_interface('bond_arp_ip_target', target) + + def add_port(self, interface): + """ + Enslave physical interface to bond. + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').add_port('eth0') + >>> BondIf('bond0').add_port('eth1') + """ + + # From drivers/net/bonding/bond_main.c: + # ... + # bond_set_slave_link_state(new_slave, + # BOND_LINK_UP, + # BOND_SLAVE_NOTIFY_NOW); + # ... + # + # The kernel will ALWAYS place new bond members in "up" state regardless + # what the CLI will tell us! + + # Physical interface must be in admin down state before they can be + # enslaved. If this is not the case an error will be shown: + # bond0: eth0 is up - this may be due to an out of date ifenslave + slave = Interface(interface) + slave_state = slave.get_admin_state() + if slave_state == 'up': + slave.set_admin_state('down') + + ret = self.set_interface('bond_add_port', f'+{interface}') + # The kernel will ALWAYS place new bond members in "up" state regardless + # what the LI is configured for - thus we place the interface in its + # desired state + slave.set_admin_state(slave_state) + return ret + + def del_port(self, interface): + """ + Remove physical port from bond + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').del_port('eth1') + """ + return self.set_interface('bond_del_port', f'-{interface}') + + def get_slaves(self): + """ + Return a list with all configured slave interfaces on this bond. + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').get_slaves() + ['eth1', 'eth2'] + """ + enslaved_ifs = [] + # retrieve real enslaved interfaces from OS kernel + sysfs_bond = '/sys/class/net/{}'.format(self.config['ifname']) + if os.path.isdir(sysfs_bond): + for directory in os.listdir(sysfs_bond): + if 'lower_' in directory: + enslaved_ifs.append(directory.replace('lower_', '')) + + return enslaved_ifs + + def get_mode(self): + """ + Return bond operation mode. + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').get_mode() + '802.3ad' + """ + mode = self.get_interface('bond_mode') + # mode is now "802.3ad 4", we are only interested in "802.3ad" + return mode.split()[0] + + def set_primary(self, interface): + """ + A string (eth0, eth2, etc) specifying which slave is the primary + device. The specified device will always be the active slave while it + is available. Only when the primary is off-line will alternate devices + be used. This is useful when one slave is preferred over another, e.g., + when one slave has higher throughput than another. + + The primary option is only valid for active-backup, balance-tlb and + balance-alb mode. + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').set_primary('eth2') + """ + return self.set_interface('bond_primary', interface) + + def set_mode(self, mode): + """ + Specifies one of the bonding policies. The default is balance-rr + (round robin). + + Possible values are: balance-rr, active-backup, balance-xor, + broadcast, 802.3ad, balance-tlb, balance-alb + + NOTE: the bonding mode can not be changed when the bond itself has + slaves + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').set_mode('802.3ad') + """ + return self.set_interface('bond_mode', mode) + + def set_system_mac(self, mac): + """ + In an AD system, this specifies the mac-address for the actor in + protocol packet exchanges (LACPDUs). The value cannot be NULL or + multicast. It is preferred to have the local-admin bit set for this + mac but driver does not enforce it. If the value is not given then + system defaults to using the masters' mac address as actors' system + address. + + This parameter has effect only in 802.3ad mode and is available through + SysFs interface. + + Example: + >>> from vyos.ifconfig import BondIf + >>> BondIf('bond0').set_system_mac('00:50:ab:cd:ef:01') + """ + return self.set_interface('bond_system_mac', mac) + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # use ref-counting function to place an interface into admin down state. + # set_admin_state_up() must be called the same amount of times else the + # interface won't come up. This can/should be used to prevent link flapping + # when changing interface parameters require the interface to be down. + # We will disable it once before reconfiguration and enable it afterwards. + if 'shutdown_required' in config: + self.set_admin_state('down') + + # Specifies the MII link monitoring frequency in milliseconds + value = config.get('mii_mon_interval') + self.set_miimon_interval(value) + + # Bonding transmit hash policy + value = config.get('hash_policy') + if value: self.set_hash_policy(value) + + # Minimum number of member interfaces + value = config.get('min_links') + if value: self.set_min_links(value) + + # Some interface options can only be changed if the interface is + # administratively down + if self.get_admin_state() == 'down': + # Remove ALL bond member interfaces + for interface in self.get_slaves(): + self.del_port(interface) + + # Restore correct interface status based on config + if dict_search(f'member.interface.{interface}.disable', config) is not None or \ + dict_search(f'member.interface_remove.{interface}.disable', config) is not None: + Interface(interface).set_admin_state('down') + else: + Interface(interface).set_admin_state('up') + + # Bonding policy/mode - default value, always present + self.set_mode(config['mode']) + + # LACPDU transmission rate - default value + if config['mode'] == '802.3ad': + self.set_lacp_rate(config.get('lacp_rate')) + + if config['mode'] not in ['802.3ad', 'balance-tlb', 'balance-alb']: + tmp = dict_search('arp_monitor.interval', config) + value = tmp if (tmp != None) else '0' + self.set_arp_interval(value) + + # ARP monitor targets need to be synchronized between sysfs and CLI. + # Unfortunately an address can't be send twice to sysfs as this will + # result in the following exception: OSError: [Errno 22] Invalid argument. + # + # We remove ALL addresses prior to adding new ones, this will remove + # addresses manually added by the user too - but as we are limited to 16 adresses + # from the kernel side this looks valid to me. We won't run into an error + # when a user added manual adresses which would result in having more + # then 16 adresses in total. + arp_tgt_addr = list(map(str, self.get_arp_ip_target().split())) + for addr in arp_tgt_addr: + self.set_arp_ip_target('-' + addr) + + # Add configured ARP target addresses + value = dict_search('arp_monitor.target', config) + if isinstance(value, str): + value = [value] + if value: + for addr in value: + self.set_arp_ip_target('+' + addr) + + # Add (enslave) interfaces to bond + value = dict_search('member.interface', config) + for interface in (value or []): + # if we've come here we already verified the interface + # does not have an addresses configured so just flush + # any remaining ones + Interface(interface).flush_addrs() + self.add_port(interface) + + # Add system mac address for 802.3ad - default address is all zero + # mode is always present (defaultValue) + if config['mode'] == '802.3ad': + mac = '00:00:00:00:00:00' + if 'system_mac' in config: + mac = config['system_mac'] + self.set_system_mac(mac) + + # Primary device interface - must be set after 'mode' + value = config.get('primary') + if value: self.set_primary(value) + + # call base class first + super().update(config) + + # enable/disable EAPoL (Extensible Authentication Protocol over Local Area Network) + self.set_eapol() diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py new file mode 100644 index 0000000..917f962 --- /dev/null +++ b/python/vyos/ifconfig/bridge.py @@ -0,0 +1,413 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig.interface import Interface +from vyos.utils.assertion import assert_boolean +from vyos.utils.assertion import assert_list +from vyos.utils.assertion import assert_positive +from vyos.utils.dict import dict_search +from vyos.utils.network import interface_exists +from vyos.configdict import get_vlan_ids +from vyos.configdict import list_diff + +@Interface.register +class BridgeIf(Interface): + """ + A bridge is a way to connect two Ethernet segments together in a protocol + independent way. Packets are forwarded based on Ethernet address, rather + than IP address (like a router). Since forwarding is done at Layer 2, all + protocols can go transparently through a bridge. + + The Linux bridge code implements a subset of the ANSI/IEEE 802.1d standard. + """ + iftype = 'bridge' + definition = { + **Interface.definition, + **{ + 'section': 'bridge', + 'prefixes': ['br', ], + 'broadcast': True, + 'vlan': True, + }, + } + + _sysfs_get = { + **Interface._sysfs_get,**{ + 'vlan_filter': { + 'location': '/sys/class/net/{ifname}/bridge/vlan_filtering' + } + } + } + + _sysfs_set = {**Interface._sysfs_set, **{ + 'ageing_time': { + 'validate': assert_positive, + 'convert': lambda t: int(t) * 100, + 'location': '/sys/class/net/{ifname}/bridge/ageing_time', + }, + 'forward_delay': { + 'validate': assert_positive, + 'convert': lambda t: int(t) * 100, + 'location': '/sys/class/net/{ifname}/bridge/forward_delay', + }, + 'hello_time': { + 'validate': assert_positive, + 'convert': lambda t: int(t) * 100, + 'location': '/sys/class/net/{ifname}/bridge/hello_time', + }, + 'max_age': { + 'validate': assert_positive, + 'convert': lambda t: int(t) * 100, + 'location': '/sys/class/net/{ifname}/bridge/max_age', + }, + 'priority': { + 'validate': assert_positive, + 'location': '/sys/class/net/{ifname}/bridge/priority', + }, + 'stp': { + 'validate': assert_boolean, + 'location': '/sys/class/net/{ifname}/bridge/stp_state', + }, + 'vlan_filter': { + 'validate': assert_boolean, + 'location': '/sys/class/net/{ifname}/bridge/vlan_filtering', + }, + 'vlan_protocol': { + 'validate': lambda v: assert_list(v, ['0x88a8', '0x8100']), + 'location': '/sys/class/net/{ifname}/bridge/vlan_protocol', + }, + 'multicast_querier': { + 'validate': assert_boolean, + 'location': '/sys/class/net/{ifname}/bridge/multicast_querier', + }, + 'multicast_snooping': { + 'validate': assert_boolean, + 'location': '/sys/class/net/{ifname}/bridge/multicast_snooping', + }, + }} + + _command_set = {**Interface._command_set, **{ + 'add_port': { + 'shellcmd': 'ip link set dev {value} master {ifname}', + }, + 'del_port': { + 'shellcmd': 'ip link set dev {value} nomaster', + }, + }} + + def get_vlan_filter(self): + """ + Get the status of the bridge VLAN filter + """ + + return self.get_interface('vlan_filter') + + + def set_ageing_time(self, time): + """ + Set bridge interface MAC address aging time in seconds. Internal kernel + representation is in centiseconds. Kernel default is 300 seconds. + + Example: + >>> from vyos.ifconfig import BridgeIf + >>> BridgeIf('br0').ageing_time(2) + """ + self.set_interface('ageing_time', time) + + def set_forward_delay(self, time): + """ + Set bridge forwarding delay in seconds. Internal Kernel representation + is in centiseconds. + + Example: + >>> from vyos.ifconfig import BridgeIf + >>> BridgeIf('br0').forward_delay(15) + """ + self.set_interface('forward_delay', time) + + def set_hello_time(self, time): + """ + Set bridge hello time in seconds. Internal Kernel representation + is in centiseconds. + + Example: + >>> from vyos.ifconfig import BridgeIf + >>> BridgeIf('br0').set_hello_time(2) + """ + self.set_interface('hello_time', time) + + def set_max_age(self, time): + """ + Set bridge max message age in seconds. Internal Kernel representation + is in centiseconds. + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').set_max_age(30) + """ + self.set_interface('max_age', time) + + def set_priority(self, priority): + """ + Set bridge max aging time in seconds. + + Example: + >>> from vyos.ifconfig import BridgeIf + >>> BridgeIf('br0').set_priority(8192) + """ + self.set_interface('priority', priority) + + def set_stp(self, state): + """ + Set bridge STP (Spanning Tree) state. 0 -> STP disabled, 1 -> STP enabled + + Example: + >>> from vyos.ifconfig import BridgeIf + >>> BridgeIf('br0').set_stp(1) + """ + self.set_interface('stp', state) + + def set_vlan_filter(self, state): + """ + Set bridge Vlan Filter state. 0 -> Vlan Filter disabled, 1 -> Vlan Filter enabled + + Example: + >>> from vyos.ifconfig import BridgeIf + >>> BridgeIf('br0').set_vlan_filter(1) + """ + self.set_interface('vlan_filter', state) + + # VLAN of bridge parent interface is always 1 + # VLAN 1 is the default VLAN for all unlabeled packets + cmd = f'bridge vlan add dev {self.ifname} vid 1 pvid untagged self' + self._cmd(cmd) + + def set_multicast_querier(self, enable): + """ + Sets whether the bridge actively runs a multicast querier or not. When a + bridge receives a 'multicast host membership' query from another network + host, that host is tracked based on the time that the query was received + plus the multicast query interval time. + + Use enable=1 to enable or enable=0 to disable + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').set_multicast_querier(1) + """ + self.set_interface('multicast_querier', enable) + + def set_multicast_snooping(self, enable): + """ + Enable or disable multicast snooping on the bridge. + + Use enable=1 to enable or enable=0 to disable + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').set_multicast_snooping(1) + """ + self.set_interface('multicast_snooping', enable) + + def add_port(self, interface): + """ + Add physical interface to bridge (member port) + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').add_port('eth0') + >>> BridgeIf('br0').add_port('eth1') + """ + # Bridge port handling of wireless interfaces is done by hostapd. + if 'wlan' in interface: + return + + try: + return self.set_interface('add_port', interface) + except: + from vyos import ConfigError + raise ConfigError('Error: Device does not allow enslaving to a bridge.') + + def del_port(self, interface): + """ + Remove member port from bridge instance. + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').del_port('eth1') + """ + return self.set_interface('del_port', interface) + + def set_vlan_protocol(self, protocol): + """ + Set protocol used for VLAN filtering. + The valid values are 0x8100(802.1q) or 0x88A8(802.1ad). + + Example: + >>> from vyos.ifconfig import Interface + >>> BridgeIf('br0').del_port('eth1') + """ + + if protocol not in ['802.1q', '802.1ad']: + raise ValueError() + + map = { + '802.1ad': '0x88a8', + '802.1q' : '0x8100' + } + + return self.set_interface('vlan_protocol', map[protocol]) + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # Set ageing time + value = config.get('aging') + self.set_ageing_time(value) + + # set bridge forward delay + value = config.get('forwarding_delay') + self.set_forward_delay(value) + + # set hello time + value = config.get('hello_time') + self.set_hello_time(value) + + # set max message age + value = config.get('max_age') + self.set_max_age(value) + + # set bridge priority + value = config.get('priority') + self.set_priority(value) + + # enable/disable spanning tree + value = '1' if 'stp' in config else '0' + self.set_stp(value) + + # enable or disable multicast snooping + tmp = dict_search('igmp.snooping', config) + value = '1' if (tmp != None) else '0' + self.set_multicast_snooping(value) + + # enable or disable IGMP querier + tmp = dict_search('igmp.querier', config) + value = '1' if (tmp != None) else '0' + self.set_multicast_querier(value) + + # remove interface from bridge + tmp = dict_search('member.interface_remove', config) + for member in (tmp or []): + if interface_exists(member): + self.del_port(member) + + # enable/disable VLAN Filter + tmp = '1' if 'enable_vlan' in config else '0' + self.set_vlan_filter(tmp) + + tmp = config.get('protocol') + self.set_vlan_protocol(tmp) + + # add VLAN interfaces to local 'parent' bridge to allow forwarding + if 'enable_vlan' in config: + for vlan in config.get('vif_remove', {}): + # Remove old VLANs from the bridge + cmd = f'bridge vlan del dev {self.ifname} vid {vlan} self' + self._cmd(cmd) + + for vlan in config.get('vif', {}): + cmd = f'bridge vlan add dev {self.ifname} vid {vlan} self' + self._cmd(cmd) + + # VLAN of bridge parent interface is always 1. VLAN 1 is the default + # VLAN for all unlabeled packets + cmd = f'bridge vlan add dev {self.ifname} vid 1 pvid untagged self' + self._cmd(cmd) + + tmp = dict_search('member.interface', config) + if tmp: + for interface, interface_config in tmp.items(): + # if interface does yet not exist bail out early and + # add it later + if not interface_exists(interface): + continue + + # Bridge lower "physical" interface + lower = Interface(interface) + + # If we've come that far we already verified the interface does + # not have any addresses configured by CLI so just flush any + # remaining ones + lower.flush_addrs() + + # enslave interface port to bridge + self.add_port(interface) + + if not interface.startswith('wlan'): + # always set private-vlan/port isolation - this can not be + # done when lower link is a wifi link, as it will trigger: + # RTNETLINK answers: Operation not supported + tmp = dict_search('isolated', interface_config) + value = 'on' if (tmp != None) else 'off' + lower.set_port_isolation(value) + + # set bridge port path cost + if 'cost' in interface_config: + lower.set_path_cost(interface_config['cost']) + + # set bridge port path priority + if 'priority' in interface_config: + lower.set_path_priority(interface_config['priority']) + + if 'enable_vlan' in config: + add_vlan = [] + native_vlan_id = None + allowed_vlan_ids= [] + cur_vlan_ids = get_vlan_ids(interface) + + if 'native_vlan' in interface_config: + vlan_id = interface_config['native_vlan'] + add_vlan.append(vlan_id) + native_vlan_id = vlan_id + + if 'allowed_vlan' in interface_config: + for vlan in interface_config['allowed_vlan']: + vlan_range = vlan.split('-') + if len(vlan_range) == 2: + for vlan_add in range(int(vlan_range[0]),int(vlan_range[1]) + 1): + add_vlan.append(str(vlan_add)) + allowed_vlan_ids.append(str(vlan_add)) + else: + add_vlan.append(vlan) + allowed_vlan_ids.append(vlan) + + # Remove redundant VLANs from the system + for vlan in list_diff(cur_vlan_ids, add_vlan): + cmd = f'bridge vlan del dev {interface} vid {vlan} master' + self._cmd(cmd) + + for vlan in allowed_vlan_ids: + cmd = f'bridge vlan add dev {interface} vid {vlan} master' + self._cmd(cmd) + + # Setting native VLAN to system + if native_vlan_id: + cmd = f'bridge vlan add dev {interface} vid {native_vlan_id} pvid untagged master' + self._cmd(cmd) + + super().update(config) diff --git a/python/vyos/ifconfig/control.py b/python/vyos/ifconfig/control.py new file mode 100644 index 0000000..7402da5 --- /dev/null +++ b/python/vyos/ifconfig/control.py @@ -0,0 +1,196 @@ +# Copyright 2019-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os + +from inspect import signature +from inspect import _empty + +from vyos.ifconfig.section import Section +from vyos.utils.process import popen +from vyos.utils.process import cmd +from vyos.utils.file import read_file +from vyos.utils.file import write_file +from vyos import debug + +class Control(Section): + _command_get = {} + _command_set = {} + _signature = {} + + def __init__(self, **kargs): + # some commands (such as operation comands - show interfaces, etc.) + # need to query the interface statistics. If the interface + # code is used and the debugging is enabled, the screen output + # will include both the command but also the debugging for that command + # to prevent this, debugging can be explicitely disabled + + # if debug is not explicitely disabled the the config, enable it + self.debug = '' + if kargs.get('debug', True) and debug.enabled('ifconfig'): + self.debug = 'ifconfig' + + def _debug_msg (self, message): + return debug.message(message, self.debug) + + def _popen(self, command): + return popen(command, self.debug) + + def _cmd(self, command): + import re + if 'netns' in self.config: + # This command must be executed from default netns 'ip link set dev X netns X' + # exclude set netns cmd from netns to avoid: + # failed to run command: ip netns exec ns01 ip link set dev veth20 netns ns01 + pattern = r'ip link set dev (\S+) netns (\S+)' + matches = re.search(pattern, command) + if matches and matches.group(2) == self.config['netns']: + # Command already includes netns and matches desired namespace: + command = command + else: + command = f'ip netns exec {self.config["netns"]} {command}' + return cmd(command, self.debug) + + def _get_command(self, config, name): + """ + Using the defined names, set data write to sysfs. + """ + cmd = self._command_get[name]['shellcmd'].format(**config) + return self._command_get[name].get('format', lambda _: _)(self._cmd(cmd)) + + def _values(self, name, validate, value): + """ + looks at the validation function "validate" + for the interface sysfs or command and + returns a dict with the right options to call it + """ + if name not in self._signature: + self._signature[name] = signature(validate) + + values = {} + + for k in self._signature[name].parameters: + default = self._signature[name].parameters[k].default + if default is not _empty: + continue + if k == 'self': + values[k] = self + elif k == 'ifname': + values[k] = self.ifname + else: + values[k] = value + + return values + + def _set_command(self, config, name, value): + """ + Using the defined names, set data write to sysfs. + """ + # the code can pass int as int + value = str(value) + + validate = self._command_set[name].get('validate', None) + if validate: + try: + validate(**self._values(name, validate, value)) + except Exception as e: + raise e.__class__(f'Could not set {name}. {e}') + + convert = self._command_set[name].get('convert', None) + if convert: + value = convert(value) + + possible = self._command_set[name].get('possible', None) + if possible and not possible(config['ifname'], value): + return False + + config = {**config, **{'value': value}} + + cmd = self._command_set[name]['shellcmd'].format(**config) + return self._command_set[name].get('format', lambda _: _)(self._cmd(cmd)) + + _sysfs_get = {} + _sysfs_set = {} + + def _read_sysfs(self, filename): + """ + Provide a single primitive w/ error checking for reading from sysfs. + """ + value = None + if os.path.exists(filename): + value = read_file(filename) + self._debug_msg("read '{}' < '{}'".format(value, filename)) + return value + + def _write_sysfs(self, filename, value): + """ + Provide a single primitive w/ error checking for writing to sysfs. + """ + if os.path.isfile(filename): + write_file(filename, str(value)) + self._debug_msg("write '{}' > '{}'".format(value, filename)) + return True + return False + + def _get_sysfs(self, config, name): + """ + Using the defined names, get data write from sysfs. + """ + filename = self._sysfs_get[name]['location'].format(**config) + if not filename: + return None + return self._read_sysfs(filename) + + def _set_sysfs(self, config, name, value): + """ + Using the defined names, set data write to sysfs. + """ + # the code can pass int as int + value = str(value) + + validate = self._sysfs_set[name].get('validate', None) + if validate: + try: + validate(**self._values(name, validate, value)) + except Exception as e: + raise e.__class__(f'Could not set {name}. {e}') + + config = {**config, **{'value': value}} + + convert = self._sysfs_set[name].get('convert', None) + if convert: + value = convert(value) + + commited = self._write_sysfs( + self._sysfs_set[name]['location'].format(**config), value) + if not commited: + errmsg = self._sysfs_set.get('errormsg', '') + if errmsg: + raise TypeError(errmsg.format(**config)) + return commited + + def get_interface(self, name): + if name in self._sysfs_get: + return self._get_sysfs(self.config, name) + if name in self._command_get: + return self._get_command(self.config, name) + raise KeyError(f'{name} is not a attribute of the interface we can get') + + def set_interface(self, name, value): + if name in self._sysfs_set: + return self._set_sysfs(self.config, name, value) + if name in self._command_set: + return self._set_command(self.config, name, value) + raise KeyError(f'{name} is not a attribute of the interface we can set') diff --git a/python/vyos/ifconfig/dummy.py b/python/vyos/ifconfig/dummy.py new file mode 100644 index 0000000..d457699 --- /dev/null +++ b/python/vyos/ifconfig/dummy.py @@ -0,0 +1,33 @@ +# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig.interface import Interface + +@Interface.register +class DummyIf(Interface): + """ + A dummy interface is entirely virtual like, for example, the loopback + interface. The purpose of a dummy interface is to provide a device to route + packets through without actually transmitting them. + """ + + iftype = 'dummy' + definition = { + **Interface.definition, + **{ + 'section': 'dummy', + 'prefixes': ['dum', ], + }, + } diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py new file mode 100644 index 0000000..61da7b7 --- /dev/null +++ b/python/vyos/ifconfig/ethernet.py @@ -0,0 +1,457 @@ +# Copyright 2019-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os + +from glob import glob + +from vyos.base import Warning +from vyos.ethtool import Ethtool +from vyos.ifconfig import Section +from vyos.ifconfig.interface import Interface +from vyos.utils.dict import dict_search +from vyos.utils.file import read_file +from vyos.utils.process import run +from vyos.utils.assertion import assert_list + +@Interface.register +class EthernetIf(Interface): + """ + Abstraction of a Linux Ethernet Interface + """ + iftype = 'ethernet' + definition = { + **Interface.definition, + **{ + 'section': 'ethernet', + 'prefixes': ['lan', 'eth', 'eno', 'ens', 'enp', 'enx'], + 'bondable': True, + 'broadcast': True, + 'bridgeable': True, + 'eternal': '(lan|eth|eno|ens|enp|enx)[0-9]+$', + } + } + + @staticmethod + def feature(ifname, option, value): + run(f'ethtool --features {ifname} {option} {value}') + return False + + _command_set = {**Interface._command_set, **{ + 'gro': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'possible': lambda i, v: EthernetIf.feature(i, 'gro', v), + }, + 'gso': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'possible': lambda i, v: EthernetIf.feature(i, 'gso', v), + }, + 'hw-tc-offload': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'possible': lambda i, v: EthernetIf.feature(i, 'hw-tc-offload', v), + }, + 'lro': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'possible': lambda i, v: EthernetIf.feature(i, 'lro', v), + }, + 'sg': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'possible': lambda i, v: EthernetIf.feature(i, 'sg', v), + }, + 'tso': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'possible': lambda i, v: EthernetIf.feature(i, 'tso', v), + }, + }} + + @staticmethod + def get_bond_member_allowed_options() -> list: + """ + Return list of options which are allowed for changing, + when interface is a bond member + :return: List of interface options + :rtype: list + """ + bond_allowed_sections = [ + 'description', + 'disable', + 'disable_flow_control', + 'disable_link_detect', + 'duplex', + 'eapol.ca_certificate', + 'eapol.certificate', + 'eapol.passphrase', + 'mirror.egress', + 'mirror.ingress', + 'offload.gro', + 'offload.gso', + 'offload.lro', + 'offload.rfs', + 'offload.rps', + 'offload.sg', + 'offload.tso', + 'redirect', + 'ring_buffer.rx', + 'ring_buffer.tx', + 'speed', + 'hw_id' + ] + return bond_allowed_sections + + def __init__(self, ifname, **kargs): + super().__init__(ifname, **kargs) + self.ethtool = Ethtool(ifname) + + def remove(self): + """ + Remove interface from config. Removing the interface deconfigures all + assigned IP addresses. + Example: + >>> from vyos.ifconfig import WWANIf + >>> i = EthernetIf('eth0') + >>> i.remove() + """ + + if self.exists(self.ifname): + # interface is placed in A/D state when removed from config! It + # will remain visible for the operating system. + self.set_admin_state('down') + + # Remove all VLAN subinterfaces - filter with the VLAN dot + for vlan in [x for x in Section.interfaces(self.iftype) if x.startswith(f'{self.ifname}.')]: + Interface(vlan).remove() + + super().remove() + + def set_flow_control(self, enable): + """ + Changes the pause parameters of the specified Ethernet device. + + @param enable: true -> enable pause frames, false -> disable pause frames + + Example: + >>> from vyos.ifconfig import EthernetIf + >>> i = EthernetIf('eth0') + >>> i.set_flow_control(True) + """ + ifname = self.config['ifname'] + + if enable not in ['on', 'off']: + raise ValueError("Value out of range") + + if not self.ethtool.check_flow_control(): + self._debug_msg(f'NIC driver does not support changing flow control settings!') + return False + + current = self.ethtool.get_flow_control() + if current != enable: + # Assemble command executed on system. Unfortunately there is no way + # to change this setting via sysfs + cmd = f'ethtool --pause {ifname} autoneg {enable} tx {enable} rx {enable}' + output, code = self._popen(cmd) + if code: + Warning(f'could not change "{ifname}" flow control setting!') + return output + return None + + def set_speed_duplex(self, speed, duplex): + """ + Set link speed in Mbit/s and duplex. + + @speed can be any link speed in MBit/s, e.g. 10, 100, 1000 auto + @duplex can be half, full, auto + + Example: + >>> from vyos.ifconfig import EthernetIf + >>> i = EthernetIf('eth0') + >>> i.set_speed_duplex('auto', 'auto') + """ + ifname = self.config['ifname'] + + if speed not in ['auto', '10', '100', '1000', '2500', '5000', '10000', + '25000', '40000', '50000', '100000', '400000']: + raise ValueError("Value out of range (speed)") + + if duplex not in ['auto', 'full', 'half']: + raise ValueError("Value out of range (duplex)") + + if not self.ethtool.check_speed_duplex(speed, duplex): + Warning(f'changing speed/duplex setting on "{ifname}" is unsupported!') + return + + if not self.ethtool.check_auto_negotiation_supported(): + Warning(f'changing auto-negotiation setting on "{ifname}" is unsupported!') + return + + # Get current speed and duplex settings: + ifname = self.config['ifname'] + if self.ethtool.get_auto_negotiation(): + if speed == 'auto' and duplex == 'auto': + # bail out early as nothing is to change + return + else: + # XXX: read in current speed and duplex settings + # There are some "nice" NICs like AX88179 which do not support + # reading the speed thus we simply fallback to the supplied speed + # to not cause any change here and raise an exception. + cur_speed = read_file(f'/sys/class/net/{ifname}/speed', speed) + cur_duplex = read_file(f'/sys/class/net/{ifname}/duplex', duplex) + if (cur_speed == speed) and (cur_duplex == duplex): + # bail out early as nothing is to change + return + + cmd = f'ethtool --change {ifname}' + try: + if speed == 'auto' or duplex == 'auto': + cmd += ' autoneg on' + else: + cmd += f' speed {speed} duplex {duplex} autoneg off' + return self._cmd(cmd) + except PermissionError: + # Some NICs do not tell that they don't suppport settings speed/duplex, + # but they do not actually support it either. + # In that case it's probably better to ignore the error + # than end up with a broken config. + print('Warning: could not set speed/duplex settings: operation not permitted!') + + def set_gro(self, state): + """ + Enable Generic Receive Offload. State can be either True or False. + + Example: + >>> from vyos.ifconfig import EthernetIf + >>> i = EthernetIf('eth0') + >>> i.set_gro(True) + """ + if not isinstance(state, bool): + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_generic_receive_offload() + if enabled != state: + if not fixed: + return self.set_interface('gro', 'on' if state else 'off') + else: + print('Adapter does not support changing generic-receive-offload settings!') + return False + + def set_gso(self, state): + """ + Enable Generic Segmentation offload. State can be either True or False. + Example: + >>> from vyos.ifconfig import EthernetIf + >>> i = EthernetIf('eth0') + >>> i.set_gso(True) + """ + if not isinstance(state, bool): + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_generic_segmentation_offload() + if enabled != state: + if not fixed: + return self.set_interface('gso', 'on' if state else 'off') + else: + print('Adapter does not support changing generic-segmentation-offload settings!') + return False + + def set_hw_tc_offload(self, state): + """ + Enable hardware TC flow offload. State can be either True or False. + Example: + >>> from vyos.ifconfig import EthernetIf + >>> i = EthernetIf('eth0') + >>> i.set_hw_tc_offload(True) + """ + if not isinstance(state, bool): + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_hw_tc_offload() + if enabled != state: + if not fixed: + return self.set_interface('hw-tc-offload', 'on' if state else 'off') + else: + print('Adapter does not support changing hw-tc-offload settings!') + return False + + def set_lro(self, state): + """ + Enable Large Receive offload. State can be either True or False. + Example: + >>> from vyos.ifconfig import EthernetIf + >>> i = EthernetIf('eth0') + >>> i.set_lro(True) + """ + if not isinstance(state, bool): + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_large_receive_offload() + if enabled != state: + if not fixed: + return self.set_interface('lro', 'on' if state else 'off') + else: + print('Adapter does not support changing large-receive-offload settings!') + return False + + def set_rps(self, state): + if not isinstance(state, bool): + raise ValueError('Value out of range') + + rps_cpus = 0 + queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*')) + if state: + # Enable RPS on all available CPUs except CPU0 which we will not + # utilize so the system has one spare core when it's under high + # preasure to server other means. Linux sysfs excepts a bitmask + # representation of the CPUs which should participate on RPS, we + # can enable more CPUs that are physically present on the system, + # Linux will clip that internally! + rps_cpus = (1 << os.cpu_count()) -1 + + # XXX: we should probably reserve one core when the system is under + # high preasure so we can still have a core left for housekeeping. + # This is done by masking out the lowst bit so CPU0 is spared from + # receive packet steering. + rps_cpus &= ~1 + + for i in range(0, queues): + self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', f'{rps_cpus:x}') + + # send bitmask representation as hex string without leading '0x' + return True + + def set_rfs(self, state): + rfs_flow = 0 + queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*')) + if state: + global_rfs_flow = 32768 + rfs_flow = int(global_rfs_flow/queues) + + for i in range(0, queues): + self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_flow_cnt', rfs_flow) + + return True + + def set_sg(self, state): + """ + Enable Scatter-Gather support. State can be either True or False. + + Example: + >>> from vyos.ifconfig import EthernetIf + >>> i = EthernetIf('eth0') + >>> i.set_sg(True) + """ + if not isinstance(state, bool): + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_scatter_gather() + if enabled != state: + if not fixed: + return self.set_interface('sg', 'on' if state else 'off') + else: + print('Adapter does not support changing scatter-gather settings!') + return False + + def set_tso(self, state): + """ + Enable TCP segmentation offloading. State can be either True or False. + + Example: + >>> from vyos.ifconfig import EthernetIf + >>> i = EthernetIf('eth0') + >>> i.set_tso(False) + """ + if not isinstance(state, bool): + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_tcp_segmentation_offload() + if enabled != state: + if not fixed: + return self.set_interface('tso', 'on' if state else 'off') + else: + print('Adapter does not support changing tcp-segmentation-offload settings!') + return False + + def set_ring_buffer(self, rx_tx, size): + """ + Example: + >>> from vyos.ifconfig import EthernetIf + >>> i = EthernetIf('eth0') + >>> i.set_ring_buffer('rx', '4096') + """ + current_size = self.ethtool.get_ring_buffer(rx_tx) + if current_size == size: + # bail out early if nothing is about to change + return None + + ifname = self.config['ifname'] + cmd = f'ethtool --set-ring {ifname} {rx_tx} {size}' + output, code = self._popen(cmd) + # ethtool error codes: + # 80 - value already setted + # 81 - does not possible to set value + if code and code != 80: + print(f'could not set "{rx_tx}" ring-buffer for {ifname}') + return output + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # disable ethernet flow control (pause frames) + value = 'off' if 'disable_flow_control' in config else 'on' + self.set_flow_control(value) + + # GRO (generic receive offload) + self.set_gro(dict_search('offload.gro', config) != None) + + # GSO (generic segmentation offload) + self.set_gso(dict_search('offload.gso', config) != None) + + # GSO (generic segmentation offload) + self.set_hw_tc_offload(dict_search('offload.hw_tc_offload', config) != None) + + # LRO (large receive offload) + self.set_lro(dict_search('offload.lro', config) != None) + + # RPS - Receive Packet Steering + self.set_rps(dict_search('offload.rps', config) != None) + + # RFS - Receive Flow Steering + self.set_rfs(dict_search('offload.rfs', config) != None) + + # scatter-gather option + self.set_sg(dict_search('offload.sg', config) != None) + + # TSO (TCP segmentation offloading) + self.set_tso(dict_search('offload.tso', config) != None) + + # Set physical interface speed and duplex + if 'speed_duplex_changed' in config: + if {'speed', 'duplex'} <= set(config): + speed = config.get('speed') + duplex = config.get('duplex') + self.set_speed_duplex(speed, duplex) + + # Set interface ring buffer + if 'ring_buffer' in config: + for rx_tx, size in config['ring_buffer'].items(): + self.set_ring_buffer(rx_tx, size) + + # call base class last + super().update(config) + + # enable/disable EAPoL (Extensible Authentication Protocol over Local Area Network) + self.set_eapol() diff --git a/python/vyos/ifconfig/geneve.py b/python/vyos/ifconfig/geneve.py new file mode 100644 index 0000000..fbb261a --- /dev/null +++ b/python/vyos/ifconfig/geneve.py @@ -0,0 +1,65 @@ +# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig import Interface +from vyos.utils.dict import dict_search + +@Interface.register +class GeneveIf(Interface): + """ + Geneve: Generic Network Virtualization Encapsulation + + For more information please refer to: + https://tools.ietf.org/html/draft-gross-geneve-00 + https://www.redhat.com/en/blog/what-geneve + https://developers.redhat.com/blog/2019/05/17/an-introduction-to-linux-virtual-interfaces-tunnels/#geneve + https://lwn.net/Articles/644938/ + """ + iftype = 'geneve' + definition = { + **Interface.definition, + **{ + 'section': 'geneve', + 'prefixes': ['gnv', ], + 'bridgeable': True, + } + } + + def _create(self): + # This table represents a mapping from VyOS internal config dict to + # arguments used by iproute2. For more information please refer to: + # - https://man7.org/linux/man-pages/man8/ip-link.8.html + mapping = { + 'parameters.ip.df' : 'df', + 'parameters.ip.tos' : 'tos', + 'parameters.ip.ttl' : 'ttl', + 'parameters.ip.innerproto' : 'innerprotoinherit', + 'parameters.ipv6.flowlabel' : 'flowlabel', + } + + cmd = 'ip link add name {ifname} type {type} id {vni} remote {remote}' + for vyos_key, iproute2_key in mapping.items(): + # dict_search will return an empty dict "{}" for valueless nodes like + # "parameters.nolearning" - thus we need to test the nodes existence + # by using isinstance() + tmp = dict_search(vyos_key, self.config) + if isinstance(tmp, dict): + cmd += f' {iproute2_key}' + elif tmp != None: + cmd += f' {iproute2_key} {tmp}' + + self._cmd(cmd.format(**self.config)) + # interface is always A/D down. It needs to be enabled explicitly + self.set_admin_state('down') diff --git a/python/vyos/ifconfig/input.py b/python/vyos/ifconfig/input.py new file mode 100644 index 0000000..3e5f579 --- /dev/null +++ b/python/vyos/ifconfig/input.py @@ -0,0 +1,36 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig.interface import Interface + +@Interface.register +class InputIf(Interface): + """ + The Intermediate Functional Block (ifb) pseudo network interface acts as a + QoS concentrator for multiple different sources of traffic. Packets from + or to other interfaces have to be redirected to it using the mirred action + in order to be handled, regularly routed traffic will be dropped. This way, + a single stack of qdiscs, classes and filters can be shared between + multiple interfaces. + """ + + iftype = 'ifb' + definition = { + **Interface.definition, + **{ + 'section': 'input', + 'prefixes': ['ifb', ], + }, + } diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py new file mode 100644 index 0000000..002d3da --- /dev/null +++ b/python/vyos/ifconfig/interface.py @@ -0,0 +1,1974 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +import re +import json +import jmespath + +from copy import deepcopy +from glob import glob + +from ipaddress import IPv4Network +from netifaces import ifaddresses +# this is not the same as socket.AF_INET/INET6 +from netifaces import AF_INET +from netifaces import AF_INET6 + +from vyos import ConfigError +from vyos.configdict import list_diff +from vyos.configdict import dict_merge +from vyos.configdict import get_vlan_ids +from vyos.defaults import directories +from vyos.pki import find_chain +from vyos.pki import encode_certificate +from vyos.pki import load_certificate +from vyos.pki import wrap_private_key +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 +from vyos.template import render +from vyos.utils.network import mac2eui64 +from vyos.utils.dict import dict_search +from vyos.utils.network import get_interface_config +from vyos.utils.network import get_interface_namespace +from vyos.utils.network import get_vrf_tableid +from vyos.utils.network import is_netns_interface +from vyos.utils.process import is_systemd_service_active +from vyos.utils.process import run +from vyos.utils.file import read_file +from vyos.utils.file import write_file +from vyos.utils.network import is_intf_addr_assigned +from vyos.utils.network import is_ipv6_link_local +from vyos.utils.assertion import assert_boolean +from vyos.utils.assertion import assert_list +from vyos.utils.assertion import assert_mac +from vyos.utils.assertion import assert_mtu +from vyos.utils.assertion import assert_positive +from vyos.utils.assertion import assert_range +from vyos.ifconfig.control import Control +from vyos.ifconfig.vrrp import VRRP +from vyos.ifconfig.operational import Operational +from vyos.ifconfig import Section + +from netaddr import EUI +from netaddr import mac_unix_expanded + +link_local_prefix = 'fe80::/64' + +class Interface(Control): + # This is the class which will be used to create + # self.operational, it allows subclasses, such as + # WireGuard to modify their display behaviour + OperationalClass = Operational + + options = ['debug', 'create'] + required = [] + default = { + 'debug': True, + 'create': True, + } + definition = { + 'section': '', + 'prefixes': [], + 'vlan': False, + 'bondable': False, + 'broadcast': False, + 'bridgeable': False, + 'eternal': '', + } + + _command_get = { + 'admin_state': { + 'shellcmd': 'ip -json link show dev {ifname}', + 'format': lambda j: 'up' if 'UP' in jmespath.search('[*].flags | [0]', json.loads(j)) else 'down', + }, + 'alias': { + 'shellcmd': 'ip -json -detail link list dev {ifname}', + 'format': lambda j: jmespath.search('[*].ifalias | [0]', json.loads(j)) or '', + }, + 'mac': { + 'shellcmd': 'ip -json -detail link list dev {ifname}', + 'format': lambda j: jmespath.search('[*].address | [0]', json.loads(j)), + }, + 'min_mtu': { + 'shellcmd': 'ip -json -detail link list dev {ifname}', + 'format': lambda j: jmespath.search('[*].min_mtu | [0]', json.loads(j)), + }, + 'max_mtu': { + 'shellcmd': 'ip -json -detail link list dev {ifname}', + 'format': lambda j: jmespath.search('[*].max_mtu | [0]', json.loads(j)), + }, + 'mtu': { + 'shellcmd': 'ip -json -detail link list dev {ifname}', + 'format': lambda j: jmespath.search('[*].mtu | [0]', json.loads(j)), + }, + 'oper_state': { + 'shellcmd': 'ip -json -detail link list dev {ifname}', + 'format': lambda j: jmespath.search('[*].operstate | [0]', json.loads(j)), + }, + 'vrf': { + 'shellcmd': 'ip -json -detail link list dev {ifname}', + 'format': lambda j: jmespath.search('[?linkinfo.info_slave_kind == `vrf`].master | [0]', json.loads(j)), + }, + } + + _command_set = { + 'admin_state': { + 'validate': lambda v: assert_list(v, ['up', 'down']), + 'shellcmd': 'ip link set dev {ifname} {value}', + }, + 'alias': { + 'convert': lambda name: name if name else '', + 'shellcmd': 'ip link set dev {ifname} alias "{value}"', + }, + 'bridge_port_isolation': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'shellcmd': 'bridge link set dev {ifname} isolated {value}', + }, + 'mac': { + 'validate': assert_mac, + 'shellcmd': 'ip link set dev {ifname} address {value}', + }, + 'mtu': { + 'validate': assert_mtu, + 'shellcmd': 'ip link set dev {ifname} mtu {value}', + }, + 'vrf': { + 'convert': lambda v: f'master {v}' if v else 'nomaster', + 'shellcmd': 'ip link set dev {ifname} {value}', + }, + } + + _sysfs_set = { + 'arp_cache_tmo': { + 'location': '/proc/sys/net/ipv4/neigh/{ifname}/base_reachable_time_ms', + }, + 'arp_filter': { + 'validate': assert_boolean, + 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_filter', + }, + 'arp_accept': { + 'validate': lambda arp: assert_range(arp,0,2), + 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_accept', + }, + 'arp_announce': { + 'validate': assert_boolean, + 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_announce', + }, + 'arp_ignore': { + 'validate': assert_boolean, + 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_ignore', + }, + 'ipv4_forwarding': { + 'validate': assert_boolean, + 'location': '/proc/sys/net/ipv4/conf/{ifname}/forwarding', + }, + 'ipv4_directed_broadcast': { + 'validate': assert_boolean, + 'location': '/proc/sys/net/ipv4/conf/{ifname}/bc_forwarding', + }, + 'ipv6_accept_ra': { + 'validate': lambda ara: assert_range(ara,0,3), + 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra', + }, + 'ipv6_autoconf': { + 'validate': lambda aco: assert_range(aco,0,2), + 'location': '/proc/sys/net/ipv6/conf/{ifname}/autoconf', + }, + 'ipv6_forwarding': { + 'validate': lambda fwd: assert_range(fwd,0,2), + 'location': '/proc/sys/net/ipv6/conf/{ifname}/forwarding', + }, + 'ipv6_accept_dad': { + 'validate': lambda dad: assert_range(dad,0,3), + 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_dad', + }, + 'ipv6_dad_transmits': { + 'validate': assert_positive, + 'location': '/proc/sys/net/ipv6/conf/{ifname}/dad_transmits', + }, + 'ipv6_cache_tmo': { + 'location': '/proc/sys/net/ipv6/neigh/{ifname}/base_reachable_time_ms', + }, + 'path_cost': { + # XXX: we should set a maximum + 'validate': assert_positive, + 'location': '/sys/class/net/{ifname}/brport/path_cost', + 'errormsg': '{ifname} is not a bridge port member' + }, + 'path_priority': { + # XXX: we should set a maximum + 'validate': assert_positive, + 'location': '/sys/class/net/{ifname}/brport/priority', + 'errormsg': '{ifname} is not a bridge port member' + }, + 'proxy_arp': { + 'validate': assert_boolean, + 'location': '/proc/sys/net/ipv4/conf/{ifname}/proxy_arp', + }, + 'proxy_arp_pvlan': { + 'validate': assert_boolean, + 'location': '/proc/sys/net/ipv4/conf/{ifname}/proxy_arp_pvlan', + }, + # link_detect vs link_filter name weirdness + 'link_detect': { + 'validate': lambda link: assert_range(link,0,3), + 'location': '/proc/sys/net/ipv4/conf/{ifname}/link_filter', + }, + 'per_client_thread': { + 'validate': assert_boolean, + 'location': '/sys/class/net/{ifname}/threaded', + }, + } + + _sysfs_get = { + 'arp_cache_tmo': { + 'location': '/proc/sys/net/ipv4/neigh/{ifname}/base_reachable_time_ms', + }, + 'arp_filter': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_filter', + }, + 'arp_accept': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_accept', + }, + 'arp_announce': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_announce', + }, + 'arp_ignore': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_ignore', + }, + 'ipv4_forwarding': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/forwarding', + }, + 'ipv4_directed_broadcast': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/bc_forwarding', + }, + 'ipv6_accept_ra': { + 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra', + }, + 'ipv6_autoconf': { + 'location': '/proc/sys/net/ipv6/conf/{ifname}/autoconf', + }, + 'ipv6_forwarding': { + 'location': '/proc/sys/net/ipv6/conf/{ifname}/forwarding', + }, + 'ipv6_accept_dad': { + 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_dad', + }, + 'ipv6_dad_transmits': { + 'location': '/proc/sys/net/ipv6/conf/{ifname}/dad_transmits', + }, + 'ipv6_cache_tmo': { + 'location': '/proc/sys/net/ipv6/neigh/{ifname}/base_reachable_time_ms', + }, + 'proxy_arp': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/proxy_arp', + }, + 'proxy_arp_pvlan': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/proxy_arp_pvlan', + }, + 'link_detect': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/link_filter', + }, + 'per_client_thread': { + 'validate': assert_boolean, + 'location': '/sys/class/net/{ifname}/threaded', + }, + } + + @classmethod + def exists(cls, ifname: str, netns: str=None) -> bool: + cmd = f'ip link show dev {ifname}' + if netns: + cmd = f'ip netns exec {netns} {cmd}' + return run(cmd) == 0 + + @classmethod + def get_config(cls): + """ + Some but not all interfaces require a configuration when they are added + using iproute2. This method will provide the configuration dictionary + used by this class. + """ + return deepcopy(cls.default) + + def __init__(self, ifname, **kargs): + """ + This is the base interface class which supports basic IP/MAC address + operations as well as DHCP(v6). Other interface which represent e.g. + and ethernet bridge are implemented as derived classes adding all + additional functionality. + + For creation you will need to provide the interface type, otherwise + the existing interface is used + + DEBUG: + This class has embedded debugging (print) which can be enabled by + creating the following file: + vyos@vyos# touch /tmp/vyos.ifconfig.debug + + Example: + >>> from vyos.ifconfig import Interface + >>> i = Interface('eth0') + """ + self.config = deepcopy(kargs) + self.config['ifname'] = self.ifname = ifname + + self._admin_state_down_cnt = 0 + + # we must have updated config before initialising the Interface + super().__init__(**kargs) + + if not self.exists(ifname): + # Any instance of Interface, such as Interface('eth0') can be used + # safely to access the generic function in this class as 'type' is + # unset, the class can not be created + if not self.iftype: + raise Exception(f'interface "{ifname}" not found') + self.config['type'] = self.iftype + + # Should an Instance of a child class (EthernetIf, DummyIf, ..) + # be required, then create should be set to False to not accidentally create it. + # In case a subclass does not define it, we use get to set the default to True + if self.config.get('create',True): + for k in self.required: + if k not in kargs: + name = self.default['type'] + raise ConfigError(f'missing required option {k} for {name} {ifname} creation') + + self._create() + # If we can not connect to the interface then let the caller know + # as the class could not be correctly initialised + else: + raise Exception(f'interface "{ifname}" not found!') + + # temporary list of assigned IP addresses + self._addr = [] + + self.operational = self.OperationalClass(ifname) + self.vrrp = VRRP(ifname) + + def _create(self): + # Do not create interface that already exist or exists in netns + netns = self.config.get('netns', None) + if self.exists(f'{self.ifname}', netns=netns): + return + + cmd = 'ip link add dev {ifname} type {type}'.format(**self.config) + if 'netns' in self.config: cmd = f'ip netns exec {netns} {cmd}' + self._cmd(cmd) + + def remove(self): + """ + Remove interface from operating system. Removing the interface + deconfigures all assigned IP addresses and clear possible DHCP(v6) + client processes. + + Example: + >>> from vyos.ifconfig import Interface + >>> i = Interface('eth0') + >>> i.remove() + """ + # Stop WPA supplicant if EAPoL was in use + if is_systemd_service_active(f'wpa_supplicant-wired@{self.ifname}'): + self._cmd(f'systemctl stop wpa_supplicant-wired@{self.ifname}') + + # remove all assigned IP addresses from interface - this is a bit redundant + # as the kernel will remove all addresses on interface deletion, but we + # can not delete ALL interfaces, see below + self.flush_addrs() + + # remove interface from conntrack VRF interface map + self._del_interface_from_ct_iface_map() + + # --------------------------------------------------------------------- + # Any class can define an eternal regex in its definition + # interface matching the regex will not be deleted + + eternal = self.definition['eternal'] + if not eternal: + self._delete() + elif not re.match(eternal, self.ifname): + self._delete() + + def _delete(self): + # NOTE (Improvement): + # after interface removal no other commands should be allowed + # to be called and instead should raise an Exception: + cmd = 'ip link del dev {ifname}'.format(**self.config) + # for delete we can't get data from self.config{'netns'} + netns = get_interface_namespace(self.ifname) + if netns: cmd = f'ip netns exec {netns} {cmd}' + return self._cmd(cmd) + + def _nft_check_and_run(self, nft_command): + # Check if deleting is possible first to avoid raising errors + _, err = self._popen(f'nft --check {nft_command}') + if not err: + # Remove map element + self._cmd(f'nft {nft_command}') + + def _del_interface_from_ct_iface_map(self): + nft_command = f'delete element inet vrf_zones ct_iface_map {{ "{self.ifname}" }}' + self._nft_check_and_run(nft_command) + + def _add_interface_to_ct_iface_map(self, vrf_table_id: int): + nft_command = f'add element inet vrf_zones ct_iface_map {{ "{self.ifname}" : {vrf_table_id} }}' + self._nft_check_and_run(nft_command) + + def get_min_mtu(self): + """ + Get hardware minimum supported MTU + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').get_min_mtu() + '60' + """ + return int(self.get_interface('min_mtu')) + + def get_max_mtu(self): + """ + Get hardware maximum supported MTU + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').get_max_mtu() + '9000' + """ + return int(self.get_interface('max_mtu')) + + def get_mtu(self): + """ + Get/set interface mtu in bytes. + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').get_mtu() + '1500' + """ + return int(self.get_interface('mtu')) + + def set_mtu(self, mtu): + """ + Get/set interface mtu in bytes. + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_mtu(1400) + >>> Interface('eth0').get_mtu() + '1400' + """ + tmp = self.get_interface('mtu') + if str(tmp) == mtu: + return None + return self.set_interface('mtu', mtu) + + def get_mac(self): + """ + Get current interface MAC (Media Access Contrl) address used. + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').get_mac() + '00:50:ab:cd:ef:00' + """ + return self.get_interface('mac') + + def get_mac_synthetic(self): + """ + Get a synthetic MAC address. This is a common method which can be called + from derived classes to overwrite the get_mac() call in a generic way. + + NOTE: Tunnel interfaces have no "MAC" address by default. The content + of the 'address' file in /sys/class/net/device contains the + local-ip thus we generate a random MAC address instead + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').get_mac() + '00:50:ab:cd:ef:00' + """ + from hashlib import sha256 + + # Get processor ID number + cpu_id = self._cmd('sudo dmidecode -t 4 | grep ID | head -n1 | sed "s/.*ID://;s/ //g"') + + # XXX: T3894 - it seems not all systems have eth0 - get a list of all + # available Ethernet interfaces on the system (without VLAN subinterfaces) + # and then take the first one. + all_eth_ifs = Section.interfaces('ethernet', vlan=False) + first_mac = Interface(all_eth_ifs[0]).get_mac() + + sha = sha256() + # Calculate SHA256 sum based on the CPU ID number, eth0 mac address and + # this interface identifier - this is as predictable as an interface + # MAC address and thus can be used in the same way + sha.update(cpu_id.encode()) + sha.update(first_mac.encode()) + sha.update(self.ifname.encode()) + # take the most significant 48 bits from the SHA256 string + tmp = sha.hexdigest()[:12] + # Convert pseudo random string into EUI format which now represents a + # MAC address + tmp = EUI(tmp).value + # set locally administered bit in MAC address + tmp |= 0xf20000000000 + # convert integer to "real" MAC address representation + mac = EUI(hex(tmp).split('x')[-1]) + # change dialect to use : as delimiter instead of - + mac.dialect = mac_unix_expanded + return str(mac) + + def set_mac(self, mac): + """ + Set interface MAC (Media Access Contrl) address to given value. + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_mac('00:50:ab:cd:ef:01') + """ + + # If MAC is unchanged, bail out early + if mac == self.get_mac(): + return None + + # MAC address can only be changed if interface is in 'down' state + prev_state = self.get_admin_state() + if prev_state == 'up': + self.set_admin_state('down') + + self.set_interface('mac', mac) + + # Turn an interface to the 'up' state if it was changed to 'down' by this fucntion + if prev_state == 'up': + self.set_admin_state('up') + + def del_netns(self, netns: str) -> bool: + """ Remove interface from given network namespace """ + # If network namespace does not exist then there is nothing to delete + if not os.path.exists(f'/run/netns/{netns}'): + return False + + # Check if interface exists in network namespace + if is_netns_interface(self.ifname, netns): + self._cmd(f'ip netns exec {netns} ip link del dev {self.ifname}') + return True + return False + + def set_netns(self, netns: str) -> bool: + """ + Add interface from given network namespace + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('dum0').set_netns('foo') + """ + self._cmd(f'ip link set dev {self.ifname} netns {netns}') + return True + + def get_vrf(self): + """ + Get VRF from interface + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').get_vrf() + """ + return self.get_interface('vrf') + + def set_vrf(self, vrf: str) -> bool: + """ + Add/Remove interface from given VRF instance. + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_vrf('foo') + >>> Interface('eth0').set_vrf() + """ + + # Don't allow for netns yet + if 'netns' in self.config: + return False + + tmp = self.get_interface('vrf') + if tmp == vrf: + return False + + # Get current VRF table ID + old_vrf_tableid = get_vrf_tableid(self.ifname) + self.set_interface('vrf', vrf) + + if vrf: + # Get routing table ID number for VRF + vrf_table_id = get_vrf_tableid(vrf) + # Add map element with interface and zone ID + if vrf_table_id: + # delete old table ID from nftables if it has changed, e.g. interface moved to a different VRF + if old_vrf_tableid and old_vrf_tableid != int(vrf_table_id): + self._del_interface_from_ct_iface_map() + self._add_interface_to_ct_iface_map(vrf_table_id) + else: + self._del_interface_from_ct_iface_map() + + return True + + def set_arp_cache_tmo(self, tmo): + """ + Set ARP cache timeout value in seconds. Internal Kernel representation + is in milliseconds. + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_arp_cache_tmo(40) + """ + tmo = str(int(tmo) * 1000) + tmp = self.get_interface('arp_cache_tmo') + if tmp == tmo: + return None + return self.set_interface('arp_cache_tmo', tmo) + + def set_ipv6_cache_tmo(self, tmo): + """ + Set IPv6 cache timeout value in seconds. Internal Kernel representation + is in milliseconds. + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_ipv6_cache_tmo(40) + """ + tmo = str(int(tmo) * 1000) + tmp = self.get_interface('ipv6_cache_tmo') + if tmp == tmo: + return None + return self.set_interface('ipv6_cache_tmo', tmo) + + def _cleanup_mss_rules(self, table, ifname): + commands = [] + results = self._cmd(f'nft -a list chain {table} VYOS_TCP_MSS').split("\n") + for line in results: + if f'oifname "{ifname}"' in line: + handle_search = re.search('handle (\d+)', line) + if handle_search: + self._cmd(f'nft delete rule {table} VYOS_TCP_MSS handle {handle_search[1]}') + + def set_tcp_ipv4_mss(self, mss): + """ + Set IPv4 TCP MSS value advertised when TCP SYN packets leave this + interface. Value is in bytes. + + A value of 0 will disable the MSS adjustment + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_tcp_ipv4_mss(1340) + """ + # Don't allow for netns yet + if 'netns' in self.config: + return None + + self._cleanup_mss_rules('raw', self.ifname) + nft_prefix = 'nft add rule raw VYOS_TCP_MSS' + base_cmd = f'oifname "{self.ifname}" tcp flags & (syn|rst) == syn' + if mss == 'clamp-mss-to-pmtu': + self._cmd(f"{nft_prefix} '{base_cmd} tcp option maxseg size set rt mtu'") + elif int(mss) > 0: + low_mss = str(int(mss) + 1) + self._cmd(f"{nft_prefix} '{base_cmd} tcp option maxseg size {low_mss}-65535 tcp option maxseg size set {mss}'") + + def set_tcp_ipv6_mss(self, mss): + """ + Set IPv6 TCP MSS value advertised when TCP SYN packets leave this + interface. Value is in bytes. + + A value of 0 will disable the MSS adjustment + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_tcp_mss(1320) + """ + # Don't allow for netns yet + if 'netns' in self.config: + return None + + self._cleanup_mss_rules('ip6 raw', self.ifname) + nft_prefix = 'nft add rule ip6 raw VYOS_TCP_MSS' + base_cmd = f'oifname "{self.ifname}" tcp flags & (syn|rst) == syn' + if mss == 'clamp-mss-to-pmtu': + self._cmd(f"{nft_prefix} '{base_cmd} tcp option maxseg size set rt mtu'") + elif int(mss) > 0: + low_mss = str(int(mss) + 1) + self._cmd(f"{nft_prefix} '{base_cmd} tcp option maxseg size {low_mss}-65535 tcp option maxseg size set {mss}'") + + def set_arp_filter(self, arp_filter): + """ + Filter ARP requests + + 1 - Allows you to have multiple network interfaces on the same + subnet, and have the ARPs for each interface be answered + based on whether or not the kernel would route a packet from + the ARP'd IP out that interface (therefore you must use source + based routing for this to work). In other words it allows control + of which cards (usually 1) will respond to an arp request. + + 0 - (default) The kernel can respond to arp requests with addresses + from other interfaces. This may seem wrong but it usually makes + sense, because it increases the chance of successful communication. + IP addresses are owned by the complete host on Linux, not by + particular interfaces. Only for more complex setups like load- + balancing, does this behaviour cause problems. + """ + tmp = self.get_interface('arp_filter') + if tmp == arp_filter: + return None + return self.set_interface('arp_filter', arp_filter) + + def set_arp_accept(self, arp_accept): + """ + Define behavior for gratuitous ARP frames who's IP is not + already present in the ARP table: + 0 - don't create new entries in the ARP table + 1 - create new entries in the ARP table + + Both replies and requests type gratuitous arp will trigger the + ARP table to be updated, if this setting is on. + + If the ARP table already contains the IP address of the + gratuitous arp frame, the arp table will be updated regardless + if this setting is on or off. + """ + tmp = self.get_interface('arp_accept') + if tmp == arp_accept: + return None + return self.set_interface('arp_accept', arp_accept) + + def set_arp_announce(self, arp_announce): + """ + Define different restriction levels for announcing the local + source IP address from IP packets in ARP requests sent on + interface: + 0 - (default) Use any local address, configured on any interface + 1 - Try to avoid local addresses that are not in the target's + subnet for this interface. This mode is useful when target + hosts reachable via this interface require the source IP + address in ARP requests to be part of their logical network + configured on the receiving interface. When we generate the + request we will check all our subnets that include the + target IP and will preserve the source address if it is from + such subnet. + + Increasing the restriction level gives more chance for + receiving answer from the resolved target while decreasing + the level announces more valid sender's information. + """ + tmp = self.get_interface('arp_announce') + if tmp == arp_announce: + return None + return self.set_interface('arp_announce', arp_announce) + + def set_arp_ignore(self, arp_ignore): + """ + Define different modes for sending replies in response to received ARP + requests that resolve local target IP addresses: + + 0 - (default): reply for any local target IP address, configured + on any interface + 1 - reply only if the target IP address is local address + configured on the incoming interface + """ + tmp = self.get_interface('arp_ignore') + if tmp == arp_ignore: + return None + return self.set_interface('arp_ignore', arp_ignore) + + def set_ipv4_forwarding(self, forwarding): + """ Configure IPv4 forwarding. """ + tmp = self.get_interface('ipv4_forwarding') + if tmp == forwarding: + return None + return self.set_interface('ipv4_forwarding', forwarding) + + def set_ipv4_directed_broadcast(self, forwarding): + """ Configure IPv4 directed broadcast forwarding. """ + tmp = self.get_interface('ipv4_directed_broadcast') + if tmp == forwarding: + return None + return self.set_interface('ipv4_directed_broadcast', forwarding) + + def _cleanup_ipv4_source_validation_rules(self, ifname): + results = self._cmd(f'nft -a list chain ip raw vyos_rpfilter').split("\n") + for line in results: + if f'iifname "{ifname}"' in line: + handle_search = re.search('handle (\d+)', line) + if handle_search: + self._cmd(f'nft delete rule ip raw vyos_rpfilter handle {handle_search[1]}') + + def set_ipv4_source_validation(self, mode): + """ + Set IPv4 reverse path validation + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_ipv4_source_validation('strict') + """ + # Don't allow for netns yet + if 'netns' in self.config: + return None + + self._cleanup_ipv4_source_validation_rules(self.ifname) + nft_prefix = f'nft insert rule ip raw vyos_rpfilter iifname "{self.ifname}"' + if mode in ['strict', 'loose']: + self._cmd(f"{nft_prefix} counter return") + if mode == 'strict': + self._cmd(f"{nft_prefix} fib saddr . iif oif 0 counter drop") + elif mode == 'loose': + self._cmd(f"{nft_prefix} fib saddr oif 0 counter drop") + + def _cleanup_ipv6_source_validation_rules(self, ifname): + results = self._cmd(f'nft -a list chain ip6 raw vyos_rpfilter').split("\n") + for line in results: + if f'iifname "{ifname}"' in line: + handle_search = re.search('handle (\d+)', line) + if handle_search: + self._cmd(f'nft delete rule ip6 raw vyos_rpfilter handle {handle_search[1]}') + + def set_ipv6_source_validation(self, mode): + """ + Set IPv6 reverse path validation + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_ipv6_source_validation('strict') + """ + # Don't allow for netns yet + if 'netns' in self.config: + return None + + self._cleanup_ipv6_source_validation_rules(self.ifname) + nft_prefix = f'nft insert rule ip6 raw vyos_rpfilter iifname "{self.ifname}"' + if mode in ['strict', 'loose']: + self._cmd(f"{nft_prefix} counter return") + if mode == 'strict': + self._cmd(f"{nft_prefix} fib saddr . iif oif 0 counter drop") + elif mode == 'loose': + self._cmd(f"{nft_prefix} fib saddr oif 0 counter drop") + + def set_ipv6_accept_ra(self, accept_ra): + """ + Accept Router Advertisements; autoconfigure using them. + + It also determines whether or not to transmit Router Solicitations. + If and only if the functional setting is to accept Router + Advertisements, Router Solicitations will be transmitted. + + 0 - Do not accept Router Advertisements. + 1 - (default) Accept Router Advertisements if forwarding is disabled. + 2 - Overrule forwarding behaviour. Accept Router Advertisements even if + forwarding is enabled. + """ + tmp = self.get_interface('ipv6_accept_ra') + if tmp == accept_ra: + return None + return self.set_interface('ipv6_accept_ra', accept_ra) + + def set_ipv6_autoconf(self, autoconf): + """ + Autoconfigure addresses using Prefix Information in Router + Advertisements. + """ + tmp = self.get_interface('ipv6_autoconf') + if tmp == autoconf: + return None + return self.set_interface('ipv6_autoconf', autoconf) + + def add_ipv6_eui64_address(self, prefix): + """ + Extended Unique Identifier (EUI), as per RFC2373, allows a host to + assign itself a unique IPv6 address based on a given IPv6 prefix. + + Calculate the EUI64 from the interface's MAC, then assign it + with the given prefix to the interface. + """ + # T2863: only add a link-local IPv6 address if the interface returns + # a MAC address. This is not the case on e.g. WireGuard interfaces. + mac = self.get_mac() + if mac: + eui64 = mac2eui64(mac, prefix) + prefixlen = prefix.split('/')[1] + self.add_addr(f'{eui64}/{prefixlen}') + + def del_ipv6_eui64_address(self, prefix): + """ + Delete the address based on the interface's MAC-based EUI64 + combined with the prefix address. + """ + if is_ipv6(prefix): + eui64 = mac2eui64(self.get_mac(), prefix) + prefixlen = prefix.split('/')[1] + self.del_addr(f'{eui64}/{prefixlen}') + + def set_ipv6_forwarding(self, forwarding): + """ + Configure IPv6 interface-specific Host/Router behaviour. + + False: + + By default, Host behaviour is assumed. This means: + + 1. IsRouter flag is not set in Neighbour Advertisements. + 2. If accept_ra is TRUE (default), transmit Router + Solicitations. + 3. If accept_ra is TRUE (default), accept Router + Advertisements (and do autoconfiguration). + 4. If accept_redirects is TRUE (default), accept Redirects. + + True: + + If local forwarding is enabled, Router behaviour is assumed. + This means exactly the reverse from the above: + + 1. IsRouter flag is set in Neighbour Advertisements. + 2. Router Solicitations are not sent unless accept_ra is 2. + 3. Router Advertisements are ignored unless accept_ra is 2. + 4. Redirects are ignored. + """ + tmp = self.get_interface('ipv6_forwarding') + if tmp == forwarding: + return None + return self.set_interface('ipv6_forwarding', forwarding) + + def set_ipv6_dad_accept(self, dad): + """Whether to accept DAD (Duplicate Address Detection)""" + tmp = self.get_interface('ipv6_accept_dad') + if tmp == dad: + return None + return self.set_interface('ipv6_accept_dad', dad) + + def set_ipv6_dad_messages(self, dad): + """ + The amount of Duplicate Address Detection probes to send. + Default: 1 + """ + tmp = self.get_interface('ipv6_dad_transmits') + if tmp == dad: + return None + return self.set_interface('ipv6_dad_transmits', dad) + + def set_link_detect(self, link_filter): + """ + Configure kernel response in packets received on interfaces that are 'down' + + 0 - Allow packets to be received for the address on this interface + even if interface is disabled or no carrier. + + 1 - Ignore packets received if interface associated with the incoming + address is down. + + 2 - Ignore packets received if interface associated with the incoming + address is down or has no carrier. + + Default value is 0. Note that some distributions enable it in startup + scripts. + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_link_detect(1) + """ + tmp = self.get_interface('link_detect') + if tmp == link_filter: + return None + return self.set_interface('link_detect', link_filter) + + def get_alias(self): + """ + Get interface alias name used by e.g. SNMP + + Example: + >>> Interface('eth0').get_alias() + 'interface description as set by user' + """ + return self.get_interface('alias') + + def set_alias(self, ifalias=''): + """ + Set interface alias name used by e.g. SNMP + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_alias('VyOS upstream interface') + + to clear alias e.g. delete it use: + + >>> Interface('eth0').set_ifalias('') + """ + tmp = self.get_interface('alias') + if tmp == ifalias: + return None + self.set_interface('alias', ifalias) + + def get_admin_state(self): + """ + Get interface administrative state. Function will return 'up' or 'down' + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').get_admin_state() + 'up' + """ + return self.get_interface('admin_state') + + def set_admin_state(self, state): + """ + Set interface administrative state to be 'up' or 'down' + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_admin_state('down') + >>> Interface('eth0').get_admin_state() + 'down' + """ + if state == 'up': + self._admin_state_down_cnt -= 1 + if self._admin_state_down_cnt < 1: + return self.set_interface('admin_state', state) + else: + self._admin_state_down_cnt += 1 + return self.set_interface('admin_state', state) + + def set_path_cost(self, cost): + """ + Set interface path cost, only relevant for STP enabled interfaces + + Example: + + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_path_cost(4) + """ + self.set_interface('path_cost', cost) + + def set_path_priority(self, priority): + """ + Set interface path priority, only relevant for STP enabled interfaces + + Example: + + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_path_priority(4) + """ + self.set_interface('path_priority', priority) + + def set_port_isolation(self, on_or_off): + """ + Controls whether a given port will be isolated, which means it will be + able to communicate with non-isolated ports only. By default this flag + is off. + + Use enable=1 to enable or enable=0 to disable + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth1').set_port_isolation('on') + """ + self.set_interface('bridge_port_isolation', on_or_off) + + def set_proxy_arp(self, enable): + """ + Set per interface proxy ARP configuration + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_proxy_arp(1) + """ + tmp = self.get_interface('proxy_arp') + if tmp == enable: + return None + self.set_interface('proxy_arp', enable) + + def set_proxy_arp_pvlan(self, enable): + """ + Private VLAN proxy arp. + Basically allow proxy arp replies back to the same interface + (from which the ARP request/solicitation was received). + + This is done to support (ethernet) switch features, like RFC + 3069, where the individual ports are NOT allowed to + communicate with each other, but they are allowed to talk to + the upstream router. As described in RFC 3069, it is possible + to allow these hosts to communicate through the upstream + router by proxy_arp'ing. Don't need to be used together with + proxy_arp. + + This technology is known by different names: + In RFC 3069 it is called VLAN Aggregation. + Cisco and Allied Telesyn call it Private VLAN. + Hewlett-Packard call it Source-Port filtering or port-isolation. + Ericsson call it MAC-Forced Forwarding (RFC Draft). + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_proxy_arp_pvlan(1) + """ + tmp = self.get_interface('proxy_arp_pvlan') + if tmp == enable: + return None + self.set_interface('proxy_arp_pvlan', enable) + + def get_addr_v4(self): + """ + Retrieve assigned IPv4 addresses from given interface. + This is done using the netifaces and ipaddress python modules. + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').get_addr_v4() + ['172.16.33.30/24'] + """ + ipv4 = [] + if AF_INET in ifaddresses(self.config['ifname']): + for v4_addr in ifaddresses(self.config['ifname'])[AF_INET]: + # we need to manually assemble a list of IPv4 address/prefix + prefix = '/' + \ + str(IPv4Network('0.0.0.0/' + v4_addr['netmask']).prefixlen) + ipv4.append(v4_addr['addr'] + prefix) + return ipv4 + + def get_addr_v6(self): + """ + Retrieve assigned IPv6 addresses from given interface. + This is done using the netifaces and ipaddress python modules. + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').get_addr_v6() + ['fe80::20c:29ff:fe11:a174/64'] + """ + ipv6 = [] + if AF_INET6 in ifaddresses(self.config['ifname']): + for v6_addr in ifaddresses(self.config['ifname'])[AF_INET6]: + # Note that currently expanded netmasks are not supported. That means + # 2001:db00::0/24 is a valid argument while 2001:db00::0/ffff:ff00:: not. + # see https://docs.python.org/3/library/ipaddress.html + prefix = '/' + v6_addr['netmask'].split('/')[-1] + + # we alsoneed to remove the interface suffix on link local + # addresses + v6_addr['addr'] = v6_addr['addr'].split('%')[0] + ipv6.append(v6_addr['addr'] + prefix) + return ipv6 + + def get_addr(self): + """ + Retrieve assigned IPv4 and IPv6 addresses from given interface. + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').get_addr() + ['172.16.33.30/24', 'fe80::20c:29ff:fe11:a174/64'] + """ + return self.get_addr_v4() + self.get_addr_v6() + + def add_addr(self, addr): + """ + Add IP(v6) address to interface. Address is only added if it is not + already assigned to that interface. Address format must be validated + and compressed/normalized before calling this function. + + addr: can be an IPv4 address, IPv6 address, dhcp or dhcpv6! + IPv4: add IPv4 address to interface + IPv6: add IPv6 address to interface + dhcp: start dhclient (IPv4) on interface + dhcpv6: start WIDE DHCPv6 (IPv6) on interface + + Returns False if address is already assigned and wasn't re-added. + Example: + >>> from vyos.ifconfig import Interface + >>> j = Interface('eth0') + >>> j.add_addr('192.0.2.1/24') + >>> j.add_addr('2001:db8::ffff/64') + >>> j.get_addr() + ['192.0.2.1/24', '2001:db8::ffff/64'] + """ + # XXX: normalize/compress with ipaddress if calling functions don't? + # is subnet mask always passed, and in the same way? + + # do not add same address twice + if addr in self._addr: + return False + + # get interface network namespace if specified + netns = self.config.get('netns', None) + + # add to interface + if addr == 'dhcp': + self.set_dhcp(True) + elif addr == 'dhcpv6': + self.set_dhcpv6(True) + elif not is_intf_addr_assigned(self.ifname, addr, netns=netns): + netns_cmd = f'ip netns exec {netns}' if netns else '' + tmp = f'{netns_cmd} ip addr add {addr} dev {self.ifname}' + # Add broadcast address for IPv4 + if is_ipv4(addr): tmp += ' brd +' + + self._cmd(tmp) + else: + return False + + # add to cache + self._addr.append(addr) + + return True + + def del_addr(self, addr): + """ + Delete IP(v6) address from interface. Address is only deleted if it is + assigned to that interface. Address format must be exactly the same as + was used when adding the address. + + addr: can be an IPv4 address, IPv6 address, dhcp or dhcpv6! + IPv4: delete IPv4 address from interface + IPv6: delete IPv6 address from interface + dhcp: stop dhclient (IPv4) on interface + dhcpv6: stop dhclient (IPv6) on interface + + Returns False if address isn't already assigned and wasn't deleted. + Example: + >>> from vyos.ifconfig import Interface + >>> j = Interface('eth0') + >>> j.add_addr('2001:db8::ffff/64') + >>> j.add_addr('192.0.2.1/24') + >>> j.get_addr() + ['192.0.2.1/24', '2001:db8::ffff/64'] + >>> j.del_addr('192.0.2.1/24') + >>> j.get_addr() + ['2001:db8::ffff/64'] + """ + if not addr: + raise ValueError() + + # get interface network namespace if specified + netns = self.config.get('netns', None) + + # remove from interface + if addr == 'dhcp': + self.set_dhcp(False) + elif addr == 'dhcpv6': + self.set_dhcpv6(False) + elif is_intf_addr_assigned(self.ifname, addr, netns=netns): + netns_cmd = f'ip netns exec {netns}' if netns else '' + self._cmd(f'{netns_cmd} ip addr del {addr} dev {self.ifname}') + else: + return False + + # remove from cache + if addr in self._addr: + self._addr.remove(addr) + + return True + + def flush_addrs(self): + """ + Flush all addresses from an interface, including DHCP. + + Will raise an exception on error. + """ + # stop DHCP(v6) if running + self.set_dhcp(False) + self.set_dhcpv6(False) + + netns = get_interface_namespace(self.ifname) + netns_cmd = f'ip netns exec {netns}' if netns else '' + cmd = f'{netns_cmd} ip addr flush dev {self.ifname}' + # flush all addresses + self._cmd(cmd) + + def add_to_bridge(self, bridge_dict): + """ + Adds the interface to the bridge with the passed port config. + + Returns False if bridge doesn't exist. + """ + + # drop all interface addresses first + self.flush_addrs() + + ifname = self.ifname + + for bridge, bridge_config in bridge_dict.items(): + # add interface to bridge - use Section.klass to get BridgeIf class + Section.klass(bridge)(bridge, create=True).add_port(self.ifname) + + # set bridge port path cost + if 'cost' in bridge_config: + self.set_path_cost(bridge_config['cost']) + + # set bridge port path priority + if 'priority' in bridge_config: + self.set_path_cost(bridge_config['priority']) + + bridge_vlan_filter = Section.klass(bridge)(bridge, create=True).get_vlan_filter() + + if int(bridge_vlan_filter): + cur_vlan_ids = get_vlan_ids(ifname) + add_vlan = [] + native_vlan_id = None + allowed_vlan_ids= [] + + if 'native_vlan' in bridge_config: + vlan_id = bridge_config['native_vlan'] + add_vlan.append(vlan_id) + native_vlan_id = vlan_id + + if 'allowed_vlan' in bridge_config: + for vlan in bridge_config['allowed_vlan']: + vlan_range = vlan.split('-') + if len(vlan_range) == 2: + for vlan_add in range(int(vlan_range[0]),int(vlan_range[1]) + 1): + add_vlan.append(str(vlan_add)) + allowed_vlan_ids.append(str(vlan_add)) + else: + add_vlan.append(vlan) + allowed_vlan_ids.append(vlan) + + # Remove redundant VLANs from the system + for vlan in list_diff(cur_vlan_ids, add_vlan): + cmd = f'bridge vlan del dev {ifname} vid {vlan} master' + self._cmd(cmd) + + for vlan in allowed_vlan_ids: + cmd = f'bridge vlan add dev {ifname} vid {vlan} master' + self._cmd(cmd) + # Setting native VLAN to system + if native_vlan_id: + cmd = f'bridge vlan add dev {ifname} vid {native_vlan_id} pvid untagged master' + self._cmd(cmd) + + def set_dhcp(self, enable): + """ + Enable/Disable DHCP client on a given interface. + """ + if enable not in [True, False]: + raise ValueError() + + ifname = self.ifname + config_base = directories['isc_dhclient_dir'] + '/dhclient' + dhclient_config_file = f'{config_base}_{ifname}.conf' + dhclient_lease_file = f'{config_base}_{ifname}.leases' + systemd_override_file = f'/run/systemd/system/dhclient@{ifname}.service.d/10-override.conf' + systemd_service = f'dhclient@{ifname}.service' + + # Rendered client configuration files require the apsolute config path + self.config['isc_dhclient_dir'] = directories['isc_dhclient_dir'] + + # 'up' check is mandatory b/c even if the interface is A/D, as soon as + # the DHCP client is started the interface will be placed in u/u state. + # This is not what we intended to do when disabling an interface. + if enable and 'disable' not in self.config: + if dict_search('dhcp_options.host_name', self.config) == None: + # read configured system hostname. + # maybe change to vyos-hostsd client ??? + hostname = 'vyos' + hostname_file = '/etc/hostname' + if os.path.isfile(hostname_file): + hostname = read_file(hostname_file) + tmp = {'dhcp_options' : { 'host_name' : hostname}} + self.config = dict_merge(tmp, self.config) + + render(systemd_override_file, 'dhcp-client/override.conf.j2', self.config) + render(dhclient_config_file, 'dhcp-client/ipv4.j2', self.config) + + # Reload systemd unit definitons as some options are dynamically generated + self._cmd('systemctl daemon-reload') + + # When the DHCP client is restarted a brief outage will occur, as + # the old lease is released a new one is acquired (T4203). We will + # only restart DHCP client if it's option changed, or if it's not + # running, but it should be running (e.g. on system startup) + if 'dhcp_options_changed' in self.config or not is_systemd_service_active(systemd_service): + return self._cmd(f'systemctl restart {systemd_service}') + else: + if is_systemd_service_active(systemd_service): + self._cmd(f'systemctl stop {systemd_service}') + # cleanup old config files + for file in [dhclient_config_file, systemd_override_file, dhclient_lease_file]: + if os.path.isfile(file): + os.remove(file) + + return None + + def set_dhcpv6(self, enable): + """ + Enable/Disable DHCPv6 client on a given interface. + """ + if enable not in [True, False]: + raise ValueError() + + ifname = self.ifname + config_base = directories['dhcp6_client_dir'] + config_file = f'{config_base}/dhcp6c.{ifname}.conf' + script_file = f'/etc/wide-dhcpv6/dhcp6c.{ifname}.script' # can not live under /run b/c of noexec mount option + systemd_override_file = f'/run/systemd/system/dhcp6c@{ifname}.service.d/10-override.conf' + systemd_service = f'dhcp6c@{ifname}.service' + + # Rendered client configuration files require additional settings + config = deepcopy(self.config) + config['dhcp6_client_dir'] = directories['dhcp6_client_dir'] + config['dhcp6_script_file'] = script_file + + if enable and 'disable' not in config: + render(systemd_override_file, 'dhcp-client/ipv6.override.conf.j2', config) + render(config_file, 'dhcp-client/ipv6.j2', config) + render(script_file, 'dhcp-client/dhcp6c-script.j2', config, permission=0o755) + + # Reload systemd unit definitons as some options are dynamically generated + self._cmd('systemctl daemon-reload') + + # We must ignore any return codes. This is required to enable + # DHCPv6-PD for interfaces which are yet not up and running. + return self._popen(f'systemctl restart {systemd_service}') + else: + if is_systemd_service_active(systemd_service): + self._cmd(f'systemctl stop {systemd_service}') + if os.path.isfile(config_file): + os.remove(config_file) + if os.path.isfile(script_file): + os.remove(script_file) + + return None + + def set_mirror_redirect(self): + # Please refer to the document for details + # - https://man7.org/linux/man-pages/man8/tc.8.html + # - https://man7.org/linux/man-pages/man8/tc-mirred.8.html + # Depening if we are the source or the target interface of the port + # mirror we need to setup some variables. + + # Don't allow for netns yet + if 'netns' in self.config: + return None + + source_if = self.config['ifname'] + + mirror_config = None + if 'mirror' in self.config: + mirror_config = self.config['mirror'] + if 'is_mirror_intf' in self.config: + source_if = next(iter(self.config['is_mirror_intf'])) + mirror_config = self.config['is_mirror_intf'][source_if].get('mirror', None) + + redirect_config = None + + # clear existing ingess - ignore errors (e.g. "Error: Cannot find specified + # qdisc on specified device") - we simply cleanup all stuff here + if not 'traffic_policy' in self.config: + self._popen(f'tc qdisc del dev {source_if} parent ffff: 2>/dev/null'); + self._popen(f'tc qdisc del dev {source_if} parent 1: 2>/dev/null'); + + # Apply interface mirror policy + if mirror_config: + for direction, target_if in mirror_config.items(): + if direction == 'ingress': + handle = 'ffff: ingress' + parent = 'ffff:' + elif direction == 'egress': + handle = '1: root prio' + parent = '1:' + + # Mirror egress traffic + mirror_cmd = f'tc qdisc add dev {source_if} handle {handle}; ' + # Export the mirrored traffic to the interface + mirror_cmd += f'tc filter add dev {source_if} parent {parent} protocol '\ + f'all prio 10 u32 match u32 0 0 flowid 1:1 action mirred '\ + f'egress mirror dev {target_if}' + _, err = self._popen(mirror_cmd) + if err: print('tc qdisc(filter for mirror port failed') + + # Apply interface traffic redirection policy + elif 'redirect' in self.config: + _, err = self._popen(f'tc qdisc add dev {source_if} handle ffff: ingress') + if err: print(f'tc qdisc add for redirect failed!') + + target_if = self.config['redirect'] + _, err = self._popen(f'tc filter add dev {source_if} parent ffff: protocol '\ + f'all prio 10 u32 match u32 0 0 flowid 1:1 action mirred '\ + f'egress redirect dev {target_if}') + if err: print('tc filter add for redirect failed') + + def set_per_client_thread(self, enable): + """ + Per-device control to enable/disable the threaded mode for all the napi + instances of the given network device, without the need for a device up/down. + + User sets it to 1 or 0 to enable or disable threaded mode. + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('wg1').set_per_client_thread(1) + """ + # In the case of a "virtual" interface like wireguard, the sysfs + # node is only created once there is a peer configured. We can now + # add a verify() code-path for this or make this dynamic without + # nagging the user + tmp = self._sysfs_get['per_client_thread']['location'] + if not os.path.exists(tmp): + return None + + tmp = self.get_interface('per_client_thread') + if tmp == enable: + return None + self.set_interface('per_client_thread', enable) + + def set_eapol(self) -> None: + """ Take care about EAPoL supplicant daemon """ + + # XXX: wpa_supplicant works on the source interface + cfg_dir = '/run/wpa_supplicant' + wpa_supplicant_conf = f'{cfg_dir}/{self.ifname}.conf' + eapol_action='stop' + + if 'eapol' in self.config: + # The default is a fallback to hw_id which is not present for any interface + # other then an ethernet interface. Thus we emulate hw_id by reading back the + # Kernel assigned MAC address + if 'hw_id' not in self.config: + self.config['hw_id'] = read_file(f'/sys/class/net/{self.ifname}/address') + render(wpa_supplicant_conf, 'ethernet/wpa_supplicant.conf.j2', self.config) + + cert_file_path = os.path.join(cfg_dir, f'{self.ifname}_cert.pem') + cert_key_path = os.path.join(cfg_dir, f'{self.ifname}_cert.key') + + cert_name = self.config['eapol']['certificate'] + pki_cert = self.config['pki']['certificate'][cert_name] + + loaded_pki_cert = load_certificate(pki_cert['certificate']) + loaded_ca_certs = {load_certificate(c['certificate']) + for c in self.config['pki']['ca'].values()} if 'ca' in self.config['pki'] else {} + + cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs) + + write_file(cert_file_path, + '\n'.join(encode_certificate(c) for c in cert_full_chain)) + write_file(cert_key_path, wrap_private_key(pki_cert['private']['key'])) + + if 'ca_certificate' in self.config['eapol']: + ca_cert_file_path = os.path.join(cfg_dir, f'{self.ifname}_ca.pem') + ca_chains = [] + + for ca_cert_name in self.config['eapol']['ca_certificate']: + pki_ca_cert = self.config['pki']['ca'][ca_cert_name] + loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) + ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + ca_chains.append( + '\n'.join(encode_certificate(c) for c in ca_full_chain)) + + write_file(ca_cert_file_path, '\n'.join(ca_chains)) + + eapol_action='reload-or-restart' + + # start/stop WPA supplicant service + self._cmd(f'systemctl {eapol_action} wpa_supplicant-wired@{self.ifname}') + + if 'eapol' not in self.config: + # delete configuration on interface removal + if os.path.isfile(wpa_supplicant_conf): + os.unlink(wpa_supplicant_conf) + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + if self.debug: + import pprint + pprint.pprint(config) + + # Cache the configuration - it will be reused inside e.g. DHCP handler + # XXX: maybe pass the option via __init__ in the future and rename this + # method to apply()? + self.config = config + + # Change interface MAC address - re-set to real hardware address (hw-id) + # if custom mac is removed. Skip if bond member. + if 'is_bond_member' not in config: + mac = config.get('hw_id') + if 'mac' in config: + mac = config.get('mac') + if mac: + self.set_mac(mac) + + # If interface is connected to NETNS we don't have to check all other + # settings like MTU/IPv6/sysctl values, etc. + # Since the interface is pushed onto a separate logical stack + # Configure NETNS + if dict_search('netns', config) != None: + if not is_netns_interface(self.ifname, self.config['netns']): + self.set_netns(config.get('netns', '')) + else: + self.del_netns(config.get('netns', '')) + + # Update interface description + self.set_alias(config.get('description', '')) + + # Ignore link state changes + value = '2' if 'disable_link_detect' in config else '1' + self.set_link_detect(value) + + # Configure assigned interface IP addresses. No longer + # configured addresses will be removed first + new_addr = config.get('address', []) + + # always ensure DHCP client is stopped (when not configured explicitly) + if 'dhcp' not in new_addr: + self.del_addr('dhcp') + + # always ensure DHCPv6 client is stopped (when not configured as client + # for IPv6 address or prefix delegation) + dhcpv6pd = dict_search('dhcpv6_options.pd', config) + dhcpv6pd = dhcpv6pd != None and len(dhcpv6pd) != 0 + if 'dhcpv6' not in new_addr and not dhcpv6pd: + self.del_addr('dhcpv6') + + # determine IP addresses which are assigned to the interface and build a + # list of addresses which are no longer in the dict so they can be removed + if 'address_old' in config: + for addr in list_diff(config['address_old'], new_addr): + # we will delete all interface specific IP addresses if they are not + # explicitly configured on the CLI + if is_ipv6_link_local(addr): + eui64 = mac2eui64(self.get_mac(), link_local_prefix) + if addr != f'{eui64}/64': + self.del_addr(addr) + else: + self.del_addr(addr) + + # start DHCPv6 client when only PD was configured + if dhcpv6pd: + self.set_dhcpv6(True) + + # XXX: Bind interface to given VRF or unbind it if vrf is not set. Unbinding + # will call 'ip link set dev eth0 nomaster' which will also drop the + # interface out of any bridge or bond - thus this is checked before. + if 'is_bond_member' in config: + bond_if = next(iter(config['is_bond_member'])) + tmp = get_interface_config(config['ifname']) + if 'master' in tmp and tmp['master'] != bond_if: + self.set_vrf('') + + elif 'is_bridge_member' in config: + bridge_if = next(iter(config['is_bridge_member'])) + tmp = get_interface_config(config['ifname']) + if 'master' in tmp and tmp['master'] != bridge_if: + self.set_vrf('') + else: + self.set_vrf(config.get('vrf', '')) + + # Add this section after vrf T4331 + for addr in new_addr: + self.add_addr(addr) + + # Configure MSS value for IPv4 TCP connections + tmp = dict_search('ip.adjust_mss', config) + value = tmp if (tmp != None) else '0' + self.set_tcp_ipv4_mss(value) + + # Configure ARP cache timeout in milliseconds - has default value + tmp = dict_search('ip.arp_cache_timeout', config) + value = tmp if (tmp != None) else '30' + self.set_arp_cache_tmo(value) + + # Configure ARP filter configuration + tmp = dict_search('ip.disable_arp_filter', config) + value = '0' if (tmp != None) else '1' + self.set_arp_filter(value) + + # Configure ARP accept + tmp = dict_search('ip.enable_arp_accept', config) + value = '1' if (tmp != None) else '0' + self.set_arp_accept(value) + + # Configure ARP announce + tmp = dict_search('ip.enable_arp_announce', config) + value = '1' if (tmp != None) else '0' + self.set_arp_announce(value) + + # Configure ARP ignore + tmp = dict_search('ip.enable_arp_ignore', config) + value = '1' if (tmp != None) else '0' + self.set_arp_ignore(value) + + # Enable proxy-arp on this interface + tmp = dict_search('ip.enable_proxy_arp', config) + value = '1' if (tmp != None) else '0' + self.set_proxy_arp(value) + + # Enable private VLAN proxy ARP on this interface + tmp = dict_search('ip.proxy_arp_pvlan', config) + value = '1' if (tmp != None) else '0' + self.set_proxy_arp_pvlan(value) + + # IPv4 forwarding + tmp = dict_search('ip.disable_forwarding', config) + value = '0' if (tmp != None) else '1' + self.set_ipv4_forwarding(value) + + # IPv4 directed broadcast forwarding + tmp = dict_search('ip.enable_directed_broadcast', config) + value = '1' if (tmp != None) else '0' + self.set_ipv4_directed_broadcast(value) + + # IPv4 source-validation + tmp = dict_search('ip.source_validation', config) + value = tmp if (tmp != None) else '0' + self.set_ipv4_source_validation(value) + + # IPv6 source-validation + tmp = dict_search('ipv6.source_validation', config) + value = tmp if (tmp != None) else '0' + self.set_ipv6_source_validation(value) + + # MTU - Maximum Transfer Unit has a default value. It must ALWAYS be set + # before mangling any IPv6 option. If MTU is less then 1280 IPv6 will be + # automatically disabled by the kernel. Also MTU must be increased before + # configuring any IPv6 address on the interface. + if 'mtu' in config and dict_search('dhcp_options.mtu', config) == None: + self.set_mtu(config.get('mtu')) + + # Configure MSS value for IPv6 TCP connections + tmp = dict_search('ipv6.adjust_mss', config) + value = tmp if (tmp != None) else '0' + self.set_tcp_ipv6_mss(value) + + # IPv6 forwarding + tmp = dict_search('ipv6.disable_forwarding', config) + value = '0' if (tmp != None) else '1' + self.set_ipv6_forwarding(value) + + # IPv6 router advertisements + tmp = dict_search('ipv6.address.autoconf', config) + value = '2' if (tmp != None) else '1' + if 'dhcpv6' in new_addr: + value = '2' + self.set_ipv6_accept_ra(value) + + # IPv6 address autoconfiguration + tmp = dict_search('ipv6.address.autoconf', config) + value = '1' if (tmp != None) else '0' + self.set_ipv6_autoconf(value) + + # Whether to accept IPv6 DAD (Duplicate Address Detection) packets + tmp = dict_search('ipv6.accept_dad', config) + # Not all interface types got this CLI option, but if they do, there + # is an XML defaultValue available + if (tmp != None): self.set_ipv6_dad_accept(tmp) + + # IPv6 DAD tries + tmp = dict_search('ipv6.dup_addr_detect_transmits', config) + # Not all interface types got this CLI option, but if they do, there + # is an XML defaultValue available + if (tmp != None): self.set_ipv6_dad_messages(tmp) + + # Delete old IPv6 EUI64 addresses before changing MAC + for addr in (dict_search('ipv6.address.eui64_old', config) or []): + self.del_ipv6_eui64_address(addr) + + # Manage IPv6 link-local addresses + if dict_search('ipv6.address.no_default_link_local', config) != None: + self.del_ipv6_eui64_address(link_local_prefix) + else: + self.add_ipv6_eui64_address(link_local_prefix) + + # Add IPv6 EUI-based addresses + tmp = dict_search('ipv6.address.eui64', config) + if tmp: + for addr in tmp: + self.add_ipv6_eui64_address(addr) + + # Configure IPv6 base time in milliseconds - has default value + tmp = dict_search('ipv6.base_reachable_time', config) + value = tmp if (tmp != None) else '30' + self.set_ipv6_cache_tmo(value) + + # re-add ourselves to any bridge we might have fallen out of + if 'is_bridge_member' in config: + tmp = config.get('is_bridge_member') + self.add_to_bridge(tmp) + + # configure interface mirror or redirection target + self.set_mirror_redirect() + + # enable/disable NAPI threading mode + tmp = dict_search('per_client_thread', config) + value = '1' if (tmp != None) else '0' + self.set_per_client_thread(value) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) + + # remove no longer required 802.1ad (Q-in-Q VLANs) + ifname = config['ifname'] + for vif_s_id in config.get('vif_s_remove', {}): + vif_s_ifname = f'{ifname}.{vif_s_id}' + VLANIf(vif_s_ifname).remove() + + # create/update 802.1ad (Q-in-Q VLANs) + for vif_s_id, vif_s_config in config.get('vif_s', {}).items(): + tmp = deepcopy(VLANIf.get_config()) + tmp['protocol'] = vif_s_config['protocol'] + tmp['source_interface'] = ifname + tmp['vlan_id'] = vif_s_id + + # It is not possible to change the VLAN encapsulation protocol + # "on-the-fly". For this "quirk" we need to actively delete and + # re-create the VIF-S interface. + vif_s_ifname = f'{ifname}.{vif_s_id}' + if self.exists(vif_s_ifname): + cur_cfg = get_interface_config(vif_s_ifname) + protocol = dict_search('linkinfo.info_data.protocol', cur_cfg).lower() + if protocol != vif_s_config['protocol']: + VLANIf(vif_s_ifname).remove() + + s_vlan = VLANIf(vif_s_ifname, **tmp) + s_vlan.update(vif_s_config) + + # remove no longer required client VLAN (vif-c) + for vif_c_id in vif_s_config.get('vif_c_remove', {}): + vif_c_ifname = f'{vif_s_ifname}.{vif_c_id}' + VLANIf(vif_c_ifname).remove() + + # create/update client VLAN (vif-c) interface + for vif_c_id, vif_c_config in vif_s_config.get('vif_c', {}).items(): + tmp = deepcopy(VLANIf.get_config()) + tmp['source_interface'] = vif_s_ifname + tmp['vlan_id'] = vif_c_id + + vif_c_ifname = f'{vif_s_ifname}.{vif_c_id}' + c_vlan = VLANIf(vif_c_ifname, **tmp) + c_vlan.update(vif_c_config) + + # remove no longer required 802.1q VLAN interfaces + for vif_id in config.get('vif_remove', {}): + vif_ifname = f'{ifname}.{vif_id}' + VLANIf(vif_ifname).remove() + + # create/update 802.1q VLAN interfaces + for vif_id, vif_config in config.get('vif', {}).items(): + vif_ifname = f'{ifname}.{vif_id}' + tmp = deepcopy(VLANIf.get_config()) + tmp['source_interface'] = ifname + tmp['vlan_id'] = vif_id + + # We need to ensure that the string format is consistent, and we need to exclude redundant spaces. + sep = ' ' + if 'egress_qos' in vif_config: + # Unwrap strings into arrays + egress_qos_array = vif_config['egress_qos'].split() + # The split array is spliced according to the fixed format + tmp['egress_qos'] = sep.join(egress_qos_array) + + if 'ingress_qos' in vif_config: + # Unwrap strings into arrays + ingress_qos_array = vif_config['ingress_qos'].split() + # The split array is spliced according to the fixed format + tmp['ingress_qos'] = sep.join(ingress_qos_array) + + # Since setting the QoS control parameters in the later stage will + # not completely delete the old settings, + # we still need to delete the VLAN encapsulation interface in order to + # ensure that the changed settings are effective. + cur_cfg = get_interface_config(vif_ifname) + qos_str = '' + tmp2 = dict_search('linkinfo.info_data.ingress_qos', cur_cfg) + if 'ingress_qos' in tmp and tmp2: + for item in tmp2: + from_key = item['from'] + to_key = item['to'] + qos_str += f'{from_key}:{to_key} ' + if qos_str != tmp['ingress_qos']: + if self.exists(vif_ifname): + VLANIf(vif_ifname).remove() + + qos_str = '' + tmp2 = dict_search('linkinfo.info_data.egress_qos', cur_cfg) + if 'egress_qos' in tmp and tmp2: + for item in tmp2: + from_key = item['from'] + to_key = item['to'] + qos_str += f'{from_key}:{to_key} ' + if qos_str != tmp['egress_qos']: + if self.exists(vif_ifname): + VLANIf(vif_ifname).remove() + + vlan = VLANIf(vif_ifname, **tmp) + vlan.update(vif_config) + + +class VLANIf(Interface): + """ Specific class which abstracts 802.1q and 802.1ad (Q-in-Q) VLAN interfaces """ + iftype = 'vlan' + + def _create(self): + # bail out early if interface already exists + if self.exists(f'{self.ifname}'): + return + + # If source_interface or vlan_id was not explicitly defined (e.g. when + # calling VLANIf('eth0.1').remove() we can define source_interface and + # vlan_id here, as it's quiet obvious that it would be eth0 in that case. + if 'source_interface' not in self.config: + self.config['source_interface'] = '.'.join(self.ifname.split('.')[:-1]) + if 'vlan_id' not in self.config: + self.config['vlan_id'] = self.ifname.split('.')[-1] + + cmd = 'ip link add link {source_interface} name {ifname} type vlan id {vlan_id}' + if 'protocol' in self.config: + cmd += ' protocol {protocol}' + if 'ingress_qos' in self.config: + cmd += ' ingress-qos-map {ingress_qos}' + if 'egress_qos' in self.config: + cmd += ' egress-qos-map {egress_qos}' + + self._cmd(cmd.format(**self.config)) + + # interface is always A/D down. It needs to be enabled explicitly + self.set_admin_state('down') + + def set_admin_state(self, state): + """ + Set interface administrative state to be 'up' or 'down' + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0.10').set_admin_state('down') + >>> Interface('eth0.10').get_admin_state() + 'down' + """ + # A VLAN interface can only be placed in admin up state when + # the lower interface is up, too + lower_interface = glob(f'/sys/class/net/{self.ifname}/lower*/flags')[0] + with open(lower_interface, 'r') as f: + flags = f.read() + # If parent is not up - bail out as we can not bring up the VLAN. + # Flags are defined in kernel source include/uapi/linux/if.h + if not int(flags, 16) & 1: + return None + + return super().set_admin_state(state) diff --git a/python/vyos/ifconfig/l2tpv3.py b/python/vyos/ifconfig/l2tpv3.py new file mode 100644 index 0000000..c1f2803 --- /dev/null +++ b/python/vyos/ifconfig/l2tpv3.py @@ -0,0 +1,113 @@ +# Copyright 2019-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from time import sleep +from time import time + +from vyos.utils.process import run +from vyos.ifconfig.interface import Interface + +def wait_for_add_l2tpv3(timeout=10, sleep_interval=1, cmd=None): + ''' + In some cases, we need to wait until local address is assigned. + And only then can the l2tpv3 tunnel be configured. + For example when ipv6 address in tentative state + or we wait for some routing daemon for remote address. + ''' + start_time = time() + test_command = cmd + while True: + if (start_time + timeout) < time(): + return None + result = run(test_command) + if result == 0: + return True + sleep(sleep_interval) + +@Interface.register +class L2TPv3If(Interface): + """ + The Linux bonding driver provides a method for aggregating multiple network + interfaces into a single logical "bonded" interface. The behavior of the + bonded interfaces depends upon the mode; generally speaking, modes provide + either hot standby or load balancing services. Additionally, link integrity + monitoring may be performed. + """ + iftype = 'l2tp' + definition = { + **Interface.definition, + **{ + 'section': 'l2tpeth', + 'prefixes': ['l2tpeth', ], + 'bridgeable': True, + } + } + + def _create(self): + # create tunnel interface + cmd = 'ip l2tp add tunnel tunnel_id {tunnel_id}' + cmd += ' peer_tunnel_id {peer_tunnel_id}' + cmd += ' udp_sport {source_port}' + cmd += ' udp_dport {destination_port}' + cmd += ' encap {encapsulation}' + cmd += ' local {source_address}' + cmd += ' remote {remote}' + c = cmd.format(**self.config) + # wait until the local/remote address is available, but no more 10 sec. + wait_for_add_l2tpv3(cmd=c) + + # setup session + cmd = 'ip l2tp add session name {ifname}' + cmd += ' tunnel_id {tunnel_id}' + cmd += ' session_id {session_id}' + cmd += ' peer_session_id {peer_session_id}' + self._cmd(cmd.format(**self.config)) + + # No need for interface shut down. There exist no function to permanently enable tunnel. + # But you can disable interface permanently with shutdown/disable command. + self.set_admin_state('up') + + def remove(self): + """ + Remove interface from operating system. Removing the interface + deconfigures all assigned IP addresses. + Example: + >>> from vyos.ifconfig import L2TPv3If + >>> i = L2TPv3If('l2tpeth0') + >>> i.remove() + """ + + if self.exists(self.ifname): + self.set_admin_state('down') + + # remove all assigned IP addresses from interface - this is a bit redundant + # as the kernel will remove all addresses on interface deletion + self.flush_addrs() + + # remove interface from conntrack VRF interface map, here explicitly and do not + # rely on the base class implementation as the interface will + # vanish as soon as the l2tp session is deleted + self._del_interface_from_ct_iface_map() + + if {'tunnel_id', 'session_id'} <= set(self.config): + cmd = 'ip l2tp del session tunnel_id {tunnel_id}' + cmd += ' session_id {session_id}' + self._cmd(cmd.format(**self.config)) + + if 'tunnel_id' in self.config: + cmd = 'ip l2tp del tunnel tunnel_id {tunnel_id}' + self._cmd(cmd.format(**self.config)) + + # No need to call the baseclass as the interface is now already gone diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py new file mode 100644 index 0000000..e1d0418 --- /dev/null +++ b/python/vyos/ifconfig/loopback.py @@ -0,0 +1,70 @@ +# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig.interface import Interface + +@Interface.register +class LoopbackIf(Interface): + """ + The loopback device is a special, virtual network interface that your router + uses to communicate with itself. + """ + _persistent_addresses = ['127.0.0.1/8', '::1/128'] + iftype = 'loopback' + definition = { + **Interface.definition, + **{ + 'section': 'loopback', + 'prefixes': ['lo', ], + 'bridgeable': True, + } + } + + def remove(self): + """ + Loopback interface can not be deleted from operating system. We can + only remove all assigned IP addresses. + + Example: + >>> from vyos.ifconfig import Interface + >>> i = LoopbackIf('lo').remove() + """ + # remove all assigned IP addresses from interface + for addr in self.get_addr(): + if addr in self._persistent_addresses: + # Do not allow deletion of the default loopback addresses as + # this will cause weird system behavior like snmp/ssh no longer + # operating as expected, see https://vyos.dev/T2034. + continue + + self.del_addr(addr) + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + address = config.get('address', []) + # We must ensure that the loopback addresses are never deleted from the system + for tmp in self._persistent_addresses: + if tmp not in address: + address.append(tmp) + + # Update IP address entry in our dictionary + config.update({'address' : address}) + + # call base class + super().update(config) diff --git a/python/vyos/ifconfig/macsec.py b/python/vyos/ifconfig/macsec.py new file mode 100644 index 0000000..3839058 --- /dev/null +++ b/python/vyos/ifconfig/macsec.py @@ -0,0 +1,74 @@ +# Copyright 2020-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig.interface import Interface + +@Interface.register +class MACsecIf(Interface): + """ + MACsec is an IEEE standard (IEEE 802.1AE) for MAC security, introduced in + 2006. It defines a way to establish a protocol independent connection + between two hosts with data confidentiality, authenticity and/or integrity, + using GCM-AES-128. MACsec operates on the Ethernet layer and as such is a + layer 2 protocol, which means it's designed to secure traffic within a + layer 2 network, including DHCP or ARP requests. It does not compete with + other security solutions such as IPsec (layer 3) or TLS (layer 4), as all + those solutions are used for their own specific use cases. + """ + iftype = 'macsec' + definition = { + **Interface.definition, + **{ + 'section': 'macsec', + 'prefixes': ['macsec', ], + }, + } + + def _create(self): + """ + Create MACsec interface in OS kernel. Interface is administrative + down by default. + """ + + # create tunnel interface + cmd = 'ip link add link {source_interface} {ifname} type {type}'.format(**self.config) + cmd += f' cipher {self.config["security"]["cipher"]}' + + if 'encrypt' in self.config["security"]: + cmd += ' encrypt on' + + self._cmd(cmd) + + # Check if using static keys + if 'static' in self.config["security"]: + # Set static TX key + cmd = 'ip macsec add {ifname} tx sa 0 pn 1 on key 00'.format(**self.config) + cmd += f' {self.config["security"]["static"]["key"]}' + self._cmd(cmd) + + for peer, peer_config in self.config["security"]["static"]["peer"].items(): + if 'disable' in peer_config: + continue + + # Create the address + cmd = 'ip macsec add {ifname} rx port 1 address'.format(**self.config) + cmd += f' {peer_config["mac"]}' + self._cmd(cmd) + # Add the encryption key to the address + cmd += f' sa 0 pn 1 on key 01 {peer_config["key"]}' + self._cmd(cmd) + + # interface is always A/D down. It needs to be enabled explicitly + self.set_admin_state('down') diff --git a/python/vyos/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py new file mode 100644 index 0000000..2266879 --- /dev/null +++ b/python/vyos/ifconfig/macvlan.py @@ -0,0 +1,47 @@ +# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig.interface import Interface + +@Interface.register +class MACVLANIf(Interface): + """ + Abstraction of a Linux MACvlan interface + """ + iftype = 'macvlan' + definition = { + **Interface.definition, + **{ + 'section': 'pseudo-ethernet', + 'prefixes': ['peth', ], + }, + } + + def _create(self): + """ + Create MACvlan interface in OS kernel. Interface is administrative + down by default. + """ + # please do not change the order when assembling the command + cmd = 'ip link add {ifname} link {source_interface} type {type} mode {mode}' + self._cmd(cmd.format(**self.config)) + + # interface is always A/D down. It needs to be enabled explicitly + self.set_admin_state('down') + + def set_mode(self, mode): + ifname = self.config['ifname'] + cmd = f'ip link set dev {ifname} type macvlan mode {mode}' + return self._cmd(cmd) diff --git a/python/vyos/ifconfig/operational.py b/python/vyos/ifconfig/operational.py new file mode 100644 index 0000000..dc27421 --- /dev/null +++ b/python/vyos/ifconfig/operational.py @@ -0,0 +1,180 @@ +# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os + +from time import time +from datetime import datetime +from functools import reduce +from tabulate import tabulate + +from vyos.ifconfig import Control + +class Operational(Control): + """ + A class able to load Interface statistics + """ + + cache_magic = 'XYZZYX' + + _stat_names = { + 'rx': ['bytes', 'packets', 'errors', 'dropped', 'overrun', 'mcast'], + 'tx': ['bytes', 'packets', 'errors', 'dropped', 'carrier', 'collisions'], + } + + _stats_dir = { + 'rx': ['rx_bytes', 'rx_packets', 'rx_errors', 'rx_dropped', 'rx_over_errors', 'multicast'], + 'tx': ['tx_bytes', 'tx_packets', 'tx_errors', 'tx_dropped', 'tx_carrier_errors', 'collisions'], + } + + # a list made of the content of _stats_dir['rx'] + _stats_dir['tx'] + _stats_all = reduce(lambda x, y: x+y, _stats_dir.values()) + + # this is not an interface but will be able to be controlled like one + _sysfs_get = { + 'oper_state':{ + 'location': '/sys/class/net/{ifname}/operstate', + }, + } + + + @classmethod + def cachefile (cls, ifname): + # the file where we are saving the counters + return f'/var/run/vyatta/{ifname}.stats' + + + def __init__(self, ifname): + """ + Operational provide access to the counters of an interface + It behave like an interface when it comes to access sysfs + + interface is an instance of the interface for which we want + to look at (a subclass of Interface, such as EthernetIf) + """ + + # add a self.config to minic Interface behaviour and make + # coding similar. Perhaps part of class Interface could be + # moved into a shared base class. + self.config = { + 'ifname': ifname, + 'create': False, + 'debug': False, + } + super().__init__(**self.config) + self.ifname = ifname + + # adds all the counters of an interface + for stat in self._stats_all: + self._sysfs_get[stat] = { + 'location': '/sys/class/net/{ifname}/statistics/'+stat, + } + + def get_state(self): + """ + Get interface operational state + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').operational.get_sate() + 'up' + """ + # https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-net + # "unknown", "notpresent", "down", "lowerlayerdown", "testing", "dormant", "up" + return self.get_interface('oper_state') + + @classmethod + def strtime (cls, epoc): + """ + represent an epoc/unix date in the format used by operation commands + """ + return datetime.fromtimestamp(epoc).strftime("%a %b %d %R:%S %Z %Y") + + def save_counters(self, stats): + """ + record the provided stats to a file keeping vyatta compatibility + """ + + with open(self.cachefile(self.ifname), 'w') as f: + f.write(self.cache_magic) + f.write('\n') + f.write(str(int(time()))) + f.write('\n') + for k,v in stats.items(): + if v: + f.write(f'{k},{v}\n') + + def load_counters(self): + """ + load the stats from a file keeping vyatta compatibility + return a dict() with the value for each interface counter for the cache + """ + ifname = self.config['ifname'] + + stats = {} + no_stats = {} + for name in self._stats_all: + stats[name] = 0 + no_stats[name] = 0 + + try: + with open(self.cachefile(self.ifname),'r') as f: + magic = f.readline().strip() + if magic != self.cache_magic: + print(f'bad magic {ifname}') + return no_stats + stats['timestamp'] = f.readline().strip() + for line in f: + k, v = line.split(',') + stats[k] = int(v) + return stats + except IOError: + return no_stats + + def clear_counters(self): + stats = self.get_stats() + for counter, value in stats.items(): + stats[counter] = value + self.save_counters(stats) + + def reset_counters(self): + try: + os.remove(self.cachefile(self.ifname)) + except FileNotFoundError: + pass + + def get_stats(self): + """ return a dict() with the value for each interface counter """ + stats = {} + for counter in self._stats_all: + stats[counter] = int(self.get_interface(counter)) + return stats + + def formated_stats(self, indent=4): + tabs = [] + stats = self.get_stats() + for rtx in self._stats_dir: + tabs.append([f'{rtx.upper()}:', ] + [_ for _ in self._stat_names[rtx]]) + tabs.append(['', ] + [stats[_] for _ in self._stats_dir[rtx]]) + + s = tabulate( + tabs, + stralign="right", + numalign="right", + tablefmt="plain" + ) + + p = ' '*indent + return f'{p}' + s.replace('\n', f'\n{p}') diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py new file mode 100644 index 0000000..febf145 --- /dev/null +++ b/python/vyos/ifconfig/pppoe.py @@ -0,0 +1,150 @@ +# Copyright 2020-2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig.interface import Interface +from vyos.utils.assertion import assert_range +from vyos.utils.network import get_interface_config + +@Interface.register +class PPPoEIf(Interface): + iftype = 'pppoe' + definition = { + **Interface.definition, + **{ + 'section': 'pppoe', + 'prefixes': ['pppoe', ], + }, + } + + _sysfs_get = { + **Interface._sysfs_get,**{ + 'accept_ra_defrtr': { + 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra_defrtr', + } + } + } + + _sysfs_set = {**Interface._sysfs_set, **{ + 'accept_ra_defrtr': { + 'validate': lambda value: assert_range(value, 0, 2), + 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra_defrtr', + }, + }} + + def _remove_routes(self, vrf=None): + # Always delete default routes when interface is removed + vrf_cmd = '' + if vrf: + vrf_cmd = f'-c "vrf {vrf}"' + self._cmd(f'vtysh -c "conf t" {vrf_cmd} -c "no ip route 0.0.0.0/0 {self.ifname} tag 210"') + self._cmd(f'vtysh -c "conf t" {vrf_cmd} -c "no ipv6 route ::/0 {self.ifname} tag 210"') + + def remove(self): + """ + Remove interface from operating system. Removing the interface + deconfigures all assigned IP addresses and clear possible DHCP(v6) + client processes. + Example: + >>> from vyos.ifconfig import Interface + >>> i = Interface('pppoe0') + >>> i.remove() + """ + vrf = None + tmp = get_interface_config(self.ifname) + if 'master' in tmp: + vrf = tmp['master'] + self._remove_routes(vrf) + + # remove bond master which places members in disabled state + super().remove() + + def _create(self): + # we can not create this interface as it is managed outside + pass + + def _delete(self): + # we can not create this interface as it is managed outside + pass + + def del_addr(self, addr): + # we can not create this interface as it is managed outside + pass + + def get_mac(self): + """ Get a synthetic MAC address. """ + return self.get_mac_synthetic() + + def set_accept_ra_defrtr(self, enable): + """ + Learn default router in Router Advertisement. + 1: enabled + 0: disable + + Example: + >>> from vyos.ifconfig import PPPoEIf + >>> PPPoEIf('pppoe1').set_accept_ra_defrtr(0) + """ + tmp = self.get_interface('accept_ra_defrtr') + if tmp == enable: + return None + self.set_interface('accept_ra_defrtr', enable) + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # Cache the configuration - it will be reused inside e.g. DHCP handler + # XXX: maybe pass the option via __init__ in the future and rename this + # method to apply()? + # + # We need to copy this from super().update() as we utilize self.set_dhcpv6() + # before this is done by the base class. + self._config = config + + # remove old routes from an e.g. old VRF assignment + if 'shutdown_required': + vrf = None + tmp = get_interface_config(self.ifname) + if 'master' in tmp: + vrf = tmp['master'] + self._remove_routes(vrf) + + # DHCPv6 PD handling is a bit different on PPPoE interfaces, as we do + # not require an 'address dhcpv6' CLI option as with other interfaces + if 'dhcpv6_options' in config and 'pd' in config['dhcpv6_options']: + self.set_dhcpv6(True) + else: + self.set_dhcpv6(False) + + super().update(config) + + # generate proper configuration string when VRFs are in use + vrf = '' + if 'vrf' in config: + tmp = config['vrf'] + vrf = f'-c "vrf {tmp}"' + + # learn default router in Router Advertisement. + tmp = '0' if 'no_default_route' in config else '1' + self.set_accept_ra_defrtr(tmp) + + if 'no_default_route' not in config: + # Set default route(s) pointing to PPPoE interface + distance = config['default_route_distance'] + self._cmd(f'vtysh -c "conf t" {vrf} -c "ip route 0.0.0.0/0 {self.ifname} tag 210 {distance}"') + if 'ipv6' in config: + self._cmd(f'vtysh -c "conf t" {vrf} -c "ipv6 route ::/0 {self.ifname} tag 210 {distance}"') diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py new file mode 100644 index 0000000..50273cf --- /dev/null +++ b/python/vyos/ifconfig/section.py @@ -0,0 +1,195 @@ +# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import re +import netifaces + + +class Section: + # the known interface prefixes + _prefixes = {} + _classes = [] + + # class need to define: definition['prefixes'] + # the interface prefixes declared by a class used to name interface with + # prefix[0-9]*(\.[0-9]+)?(\.[0-9]+)?, such as lo, eth0 or eth0.1.2 + + @classmethod + def register(cls, klass): + """ + A function to use as decorator the interfaces classes + It register the prefix for the interface (eth, dum, vxlan, ...) + with the class which can handle it (EthernetIf, DummyIf,VXLANIf, ...) + """ + if not klass.definition.get('prefixes',[]): + raise RuntimeError(f'valid interface prefixes not defined for {klass.__name__}') + + cls._classes.append(klass) + + for ifprefix in klass.definition['prefixes']: + if ifprefix in cls._prefixes: + raise RuntimeError(f'only one class can be registered for prefix "{ifprefix}" type') + cls._prefixes[ifprefix] = klass + + return klass + + @classmethod + def _basename(cls, name, vlan, vrrp): + """ + remove the number at the end of interface name + name: name of the interface + vlan: if vlan is True, do not stop at the vlan number + """ + if vrrp: + name = re.sub(r'\d(\d|v|\.)*$', '', name) + elif vlan: + name = re.sub(r'\d(\d|\.)*$', '', name) + else: + name = re.sub(r'\d+$', '', name) + return name + + @classmethod + def section(cls, name, vlan=True, vrrp=True): + """ + return the name of a section an interface should be under + name: name of the interface (eth0, dum1, ...) + vlan: should we try try to remove the VLAN from the number + """ + name = cls._basename(name, vlan, vrrp) + + if name in cls._prefixes: + return cls._prefixes[name].definition['section'] + return '' + + @classmethod + def sections(cls): + """ + return all the sections we found under 'set interfaces' + """ + return list(set([cls._prefixes[_].definition['section'] for _ in cls._prefixes])) + + @classmethod + def klass(cls, name, vlan=True, vrrp=True): + name = cls._basename(name, vlan, vrrp) + if name in cls._prefixes: + return cls._prefixes[name] + raise ValueError(f'No type found for interface name: {name}') + + @classmethod + def _intf_under_section (cls,section='',vlan=True): + """ + return a generator with the name of the configured interface + which are under a section + """ + interfaces = netifaces.interfaces() + + for ifname in interfaces: + ifsection = cls.section(ifname) + if not ifsection and not ifname.startswith('vrrp'): + continue + + if section and ifsection != section: + continue + + if vlan == False and '.' in ifname: + continue + + yield ifname + + @classmethod + def _sort_interfaces(cls, generator): + """ + return a list of the sorted interface by number, vlan, qinq + """ + def key(ifname): + value = 0 + parts = re.split(r'([^0-9]+)([0-9]+)[.]?([0-9]+)?[.]?([0-9]+)?', ifname) + length = len(parts) + name = parts[1] if length >= 3 else parts[0] + # the +1 makes sure eth0.0.0 after eth0.0 + number = int(parts[2]) + 1 if length >= 4 and parts[2] is not None else 0 + vlan = int(parts[3]) + 1 if length >= 5 and parts[3] is not None else 0 + qinq = int(parts[4]) + 1 if length >= 6 and parts[4] is not None else 0 + + # so that "lo" (or short names) are handled (as "loa") + for n in (name + 'aaa')[:3]: + value *= 100 + value += (ord(n) - ord('a')) + value += number + # vlan are 16 bits, so this can not overflow + value = (value << 16) + vlan + value = (value << 16) + qinq + return value + + l = list(generator) + l.sort(key=key) + return l + + @classmethod + def interfaces(cls, section='', vlan=True): + """ + return a list of the name of the configured interface which are under a section + if no section is provided, then it returns all configured interfaces. + If vlan is True, also Vlan subinterfaces will be returned + """ + + return cls._sort_interfaces(cls._intf_under_section(section, vlan)) + + @classmethod + def _intf_with_feature(cls, feature=''): + """ + return a generator with the name of the configured interface which have + a particular feature set in their definition such as: + bondable, broadcast, bridgeable, ... + """ + for klass in cls._classes: + if klass.definition[feature]: + yield klass.definition['section'] + + @classmethod + def feature(cls, feature=''): + """ + return list with the name of the configured interface which have + a particular feature set in their definition such as: + bondable, broadcast, bridgeable, ... + """ + return list(cls._intf_with_feature(feature)) + + @classmethod + def reserved(cls): + """ + return list with the interface name prefixes + eth, lo, vxlan, dum, ... + """ + return list(cls._prefixes.keys()) + + @classmethod + def get_config_path(cls, name): + """ + get config path to interface with .vif or .vif-s.vif-c + example: eth0.1.2 -> 'ethernet eth0 vif-s 1 vif-c 2' + Returns False if interface name is invalid (not found in sections) + """ + sect = cls.section(name) + if sect: + splinterface = name.split('.') + intfpath = f'{sect} {splinterface[0]}' + if len(splinterface) == 2: + intfpath += f' vif {splinterface[1]}' + elif len(splinterface) == 3: + intfpath += f' vif-s {splinterface[1]} vif-c {splinterface[2]}' + return intfpath + else: + return False diff --git a/python/vyos/ifconfig/sstpc.py b/python/vyos/ifconfig/sstpc.py new file mode 100644 index 0000000..50fc6ee --- /dev/null +++ b/python/vyos/ifconfig/sstpc.py @@ -0,0 +1,40 @@ +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig.interface import Interface + +@Interface.register +class SSTPCIf(Interface): + iftype = 'sstpc' + definition = { + **Interface.definition, + **{ + 'section': 'sstpc', + 'prefixes': ['sstpc', ], + 'eternal': 'sstpc[0-9]+$', + }, + } + + def _create(self): + # we can not create this interface as it is managed outside + pass + + def _delete(self): + # we can not create this interface as it is managed outside + pass + + def get_mac(self): + """ Get a synthetic MAC address. """ + return self.get_mac_synthetic() diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py new file mode 100644 index 0000000..9ba7b31 --- /dev/null +++ b/python/vyos/ifconfig/tunnel.py @@ -0,0 +1,178 @@ +# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +# https://developers.redhat.com/blog/2019/05/17/an-introduction-to-linux-virtual-interfaces-tunnels/ +# https://community.hetzner.com/tutorials/linux-setup-gre-tunnel + +from vyos.ifconfig.interface import Interface +from vyos.utils.dict import dict_search +from vyos.utils.assertion import assert_list + +def enable_to_on(value): + if value == 'enable': + return 'on' + if value == 'disable': + return 'off' + raise ValueError(f'expect enable or disable but got "{value}"') + +@Interface.register +class TunnelIf(Interface): + """ + Tunnel: private base class for tunnels + https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/tunnel.c + https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/ip6tunnel.c + """ + definition = { + **Interface.definition, + **{ + 'section': 'tunnel', + 'prefixes': ['tun',], + 'bridgeable': True, + }, + } + + # This table represents a mapping from VyOS internal config dict to + # arguments used by iproute2. For more information please refer to: + # - https://man7.org/linux/man-pages/man8/ip-link.8.html + # - https://man7.org/linux/man-pages/man8/ip-tunnel.8.html + mapping = { + 'source_address' : 'local', + 'source_interface' : 'dev', + 'remote' : 'remote', + 'parameters.ip.key' : 'key', + 'parameters.ip.tos' : 'tos', + 'parameters.ip.ttl' : 'ttl', + } + mapping_ipv4 = { + 'parameters.ip.key' : 'key', + 'parameters.ip.no_pmtu_discovery' : 'nopmtudisc', + 'parameters.ip.ignore_df' : 'ignore-df', + 'parameters.ip.tos' : 'tos', + 'parameters.ip.ttl' : 'ttl', + 'parameters.erspan.direction' : 'erspan_dir', + 'parameters.erspan.hw_id' : 'erspan_hwid', + 'parameters.erspan.index' : 'erspan', + 'parameters.erspan.version' : 'erspan_ver', + } + mapping_ipv6 = { + 'parameters.ipv6.encaplimit' : 'encaplimit', + 'parameters.ipv6.flowlabel' : 'flowlabel', + 'parameters.ipv6.hoplimit' : 'hoplimit', + 'parameters.ipv6.tclass' : 'tclass', + } + + # TODO: This is surely used for more than tunnels + # TODO: could be refactored elsewhere + _command_set = { + **Interface._command_set, + **{ + 'multicast': { + 'validate': lambda v: assert_list(v, ['enable', 'disable']), + 'convert': enable_to_on, + 'shellcmd': 'ip link set dev {ifname} multicast {value}', + }, + } + } + + def __init__(self, ifname, **kargs): + # T3357: we do not have the 'encapsulation' in kargs when calling this + # class from op-mode like "show interfaces tunnel" + if 'encapsulation' in kargs: + self.iftype = kargs['encapsulation'] + # The gretap interface has the possibility to act as L2 bridge + if self.iftype in ['gretap', 'ip6gretap']: + # no multicast, ttl or tos for gretap + self.definition = { + **TunnelIf.definition, + **{ + 'bridgeable': True, + }, + } + + super().__init__(ifname, **kargs) + + def _create(self): + if self.config['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']: + mapping = { **self.mapping, **self.mapping_ipv6 } + else: + mapping = { **self.mapping, **self.mapping_ipv4 } + + cmd = 'ip tunnel add {ifname} mode {encapsulation}' + if self.iftype in ['gretap', 'ip6gretap', 'erspan', 'ip6erspan']: + cmd = 'ip link add name {ifname} type {encapsulation}' + # ERSPAN requires the serialisation of packets + if self.iftype in ['erspan', 'ip6erspan']: + cmd += ' seq' + + for vyos_key, iproute2_key in mapping.items(): + # dict_search will return an empty dict "{}" for valueless nodes like + # "parameters.nolearning" - thus we need to test the nodes existence + # by using isinstance() + tmp = dict_search(vyos_key, self.config) + if isinstance(tmp, dict): + cmd += f' {iproute2_key}' + elif tmp != None: + cmd += f' {iproute2_key} {tmp}' + + self._cmd(cmd.format(**self.config)) + + self.set_admin_state('down') + + def _change_options(self): + # gretap interfaces do not support changing any parameter + if self.iftype in ['gretap', 'ip6gretap', 'erspan', 'ip6erspan']: + return + + if self.config['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']: + mapping = { **self.mapping, **self.mapping_ipv6 } + else: + mapping = { **self.mapping, **self.mapping_ipv4 } + + cmd = 'ip tunnel change {ifname} mode {encapsulation}' + for vyos_key, iproute2_key in mapping.items(): + # dict_search will return an empty dict "{}" for valueless nodes like + # "parameters.nolearning" - thus we need to test the nodes existence + # by using isinstance() + tmp = dict_search(vyos_key, self.config) + if isinstance(tmp, dict): + cmd += f' {iproute2_key}' + elif tmp != None: + cmd += f' {iproute2_key} {tmp}' + + self._cmd(cmd.format(**self.config)) + + def get_mac(self): + """ Get a synthetic MAC address. """ + return self.get_mac_synthetic() + + def set_multicast(self, enable): + """ Change the MULTICAST flag on the device """ + return self.set_interface('multicast', enable) + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + # Adjust iproute2 tunnel parameters if necessary + self._change_options() + + # IP Multicast + tmp = dict_search('enable_multicast', config) + value = 'enable' if (tmp != None) else 'disable' + self.set_multicast(value) + + # call base class first + super().update(config) diff --git a/python/vyos/ifconfig/veth.py b/python/vyos/ifconfig/veth.py new file mode 100644 index 0000000..aafbf22 --- /dev/null +++ b/python/vyos/ifconfig/veth.py @@ -0,0 +1,54 @@ +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig.interface import Interface + + +@Interface.register +class VethIf(Interface): + """ + Abstraction of a Linux veth interface + """ + iftype = 'veth' + definition = { + **Interface.definition, + **{ + 'section': 'virtual-ethernet', + 'prefixes': ['veth', ], + 'bridgeable': True, + }, + } + + def _create(self): + """ + Create veth interface in OS kernel. Interface is administrative + down by default. + """ + # check before create, as we have 2 veth interfaces in our CLI + # interface virtual-ethernet veth0 peer-name 'veth1' + # interface virtual-ethernet veth1 peer-name 'veth0' + # + # but iproute2 creates the pair with one command: + # ip link add vet0 type veth peer name veth1 + if self.exists(self.config['peer_name']): + return + + # create virtual-ethernet interface + cmd = 'ip link add {ifname} type {type}'.format(**self.config) + cmd += f' peer name {self.config["peer_name"]}' + self._cmd(cmd) + + # interface is always A/D down. It needs to be enabled explicitly + self.set_admin_state('down') diff --git a/python/vyos/ifconfig/vrrp.py b/python/vyos/ifconfig/vrrp.py new file mode 100644 index 0000000..ee9336d --- /dev/null +++ b/python/vyos/ifconfig/vrrp.py @@ -0,0 +1,156 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +import json +import signal + +from time import time +from tabulate import tabulate + +from vyos.configquery import ConfigTreeQuery +from vyos.utils.convert import seconds_to_human +from vyos.utils.file import read_file +from vyos.utils.file import wait_for_file_write_complete +from vyos.utils.process import process_running + +class VRRPError(Exception): + pass + +class VRRPNoData(VRRPError): + pass + +class VRRP(object): + _vrrp_prefix = '00:00:5E:00:01:' + location = { + 'pid': '/run/keepalived/keepalived.pid', + 'fifo': '/run/keepalived/keepalived_notify_fifo', + 'state': '/tmp/keepalived.data', + 'stats': '/tmp/keepalived.stats', + 'json': '/tmp/keepalived.json', + 'daemon': '/etc/default/keepalived', + 'config': '/run/keepalived/keepalived.conf', + } + + _signal = { + 'state': signal.SIGUSR1, + 'stats': signal.SIGUSR2, + 'json': signal.SIGRTMIN + 2, + } + + _name = { + 'state': 'information', + 'stats': 'statistics', + 'json': 'data', + } + + state = { + 0: 'INIT', + 1: 'BACKUP', + 2: 'MASTER', + 3: 'FAULT', + # UNKNOWN + } + + def __init__(self,ifname): + self.ifname = ifname + + def enabled(self): + return self.ifname in self.active_interfaces() + + @classmethod + def active_interfaces(cls): + if not os.path.exists(cls.location['pid']): + return [] + data = cls.collect('json') + return [group['data']['ifp_ifname'] for group in json.loads(data)] + + @classmethod + def decode_state(cls, code): + return cls.state.get(code,'UNKNOWN') + + # used in conf mode + @classmethod + def is_running(cls): + if not os.path.exists(cls.location['pid']): + return False + return process_running(cls.location['pid']) + + @classmethod + def collect(cls, what): + fname = cls.location[what] + try: + # send signal to generate the configuration file + pid = read_file(cls.location['pid']) + wait_for_file_write_complete(fname, + pre_hook=(lambda: os.kill(int(pid), cls._signal[what])), + timeout=30) + + return read_file(fname) + except OSError: + # raised by vyos.utils.file.read_file + raise VRRPNoData("VRRP data is not available (wait time exceeded)") + except FileNotFoundError: + raise VRRPNoData("VRRP data is not available (process not running or no active groups)") + except Exception: + name = cls._name[what] + raise VRRPError(f'VRRP {name} is not available') + finally: + if os.path.exists(fname): + os.remove(fname) + + @classmethod + def disabled(cls): + disabled = [] + base = ['high-availability', 'vrrp'] + conf = ConfigTreeQuery() + if conf.exists(base): + # Read VRRP configuration directly from CLI + vrrp_config_dict = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) + + # add disabled groups to the list + if 'group' in vrrp_config_dict: + for group, group_config in vrrp_config_dict['group'].items(): + if 'disable' not in group_config: + continue + disabled.append([group, group_config['interface'], group_config['vrid'], 'DISABLED', '']) + + # return list with disabled instances + return disabled + + @classmethod + def format(cls, data): + headers = ["Name", "Interface", "VRID", "State", "Priority", "Last Transition"] + groups = [] + + data = json.loads(data) + for group in data: + data = group['data'] + + name = data['iname'] + intf = data['ifp_ifname'] + vrid = data['vrid'] + state = cls.decode_state(data["state"]) + priority = data['effective_priority'] + + since = int(time() - float(data['last_transition'])) + last = seconds_to_human(since) + + groups.append([name, intf, vrid, state, priority, last]) + + # add to the active list disabled instances + groups.extend(cls.disabled()) + return(tabulate(groups, headers)) diff --git a/python/vyos/ifconfig/vti.py b/python/vyos/ifconfig/vti.py new file mode 100644 index 0000000..251cbeb --- /dev/null +++ b/python/vyos/ifconfig/vti.py @@ -0,0 +1,80 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig.interface import Interface +from vyos.utils.dict import dict_search +from vyos.utils.vti_updown_db import vti_updown_db_exists, open_vti_updown_db_readonly + +@Interface.register +class VTIIf(Interface): + iftype = 'vti' + definition = { + **Interface.definition, + **{ + 'section': 'vti', + 'prefixes': ['vti', ], + }, + } + + def __init__(self, ifname, **kwargs): + self.bypass_vti_updown_db = kwargs.pop("bypass_vti_updown_db", False) + super().__init__(ifname, **kwargs) + + def _create(self): + # This table represents a mapping from VyOS internal config dict to + # arguments used by iproute2. For more information please refer to: + # - https://man7.org/linux/man-pages/man8/ip-link.8.html + # - https://man7.org/linux/man-pages/man8/ip-tunnel.8.html + mapping = { + 'source_interface' : 'dev', + } + if_id = self.ifname.lstrip('vti') + # The key defaults to 0 and will match any policies which similarly do + # not have a lookup key configuration - thus we shift the key by one + # to also support a vti0 interface + if_id = str(int(if_id) +1) + cmd = f'ip link add {self.ifname} type xfrm if_id {if_id}' + for vyos_key, iproute2_key in mapping.items(): + # dict_search will return an empty dict "{}" for valueless nodes like + # "parameters.nolearning" - thus we need to test the nodes existence + # by using isinstance() + tmp = dict_search(vyos_key, self.config) + if isinstance(tmp, dict): + cmd += f' {iproute2_key}' + elif tmp != None: + cmd += f' {iproute2_key} {tmp}' + + self._cmd(cmd.format(**self.config)) + + # interface is always A/D down. It needs to be enabled explicitly + self.set_interface('admin_state', 'down') + + def set_admin_state(self, state): + """ + Set interface administrative state to be 'up' or 'down'. + + The interface will only be brought 'up' if ith is attached to an + active ipsec site-to-site connection or remote access connection. + """ + if state == 'down' or self.bypass_vti_updown_db: + super().set_admin_state(state) + elif vti_updown_db_exists(): + with open_vti_updown_db_readonly() as db: + if db.wantsInterfaceUp(self.ifname): + super().set_admin_state(state) + + def get_mac(self): + """ Get a synthetic MAC address. """ + return self.get_mac_synthetic() diff --git a/python/vyos/ifconfig/vtun.py b/python/vyos/ifconfig/vtun.py new file mode 100644 index 0000000..6fb414e --- /dev/null +++ b/python/vyos/ifconfig/vtun.py @@ -0,0 +1,49 @@ +# Copyright 2020-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig.interface import Interface + +@Interface.register +class VTunIf(Interface): + iftype = 'vtun' + definition = { + **Interface.definition, + **{ + 'section': 'openvpn', + 'prefixes': ['vtun', ], + 'bridgeable': True, + }, + } + + def _create(self): + """ Depending on OpenVPN operation mode the interface is created + immediately (e.g. Server mode) or once the connection to the server is + established (client mode). The latter will only be brought up once the + server can be reached, thus we might need to create this interface in + advance for the service to be operational. """ + try: + cmd = 'openvpn --mktun --dev-type {device_type} --dev {ifname}'.format(**self.config) + return self._cmd(cmd) + except PermissionError: + # interface created by OpenVPN daemon in the meantime ... + pass + + def add_addr(self, addr): + # IP addresses are managed by OpenVPN daemon + pass + + def del_addr(self, addr): + # IP addresses are managed by OpenVPN daemon + pass diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py new file mode 100644 index 0000000..1023c58 --- /dev/null +++ b/python/vyos/ifconfig/vxlan.py @@ -0,0 +1,211 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configdict import list_diff +from vyos.ifconfig import Interface +from vyos.utils.assertion import assert_list +from vyos.utils.dict import dict_search +from vyos.utils.network import get_interface_config +from vyos.utils.network import get_vxlan_vlan_tunnels +from vyos.utils.network import get_vxlan_vni_filter + +@Interface.register +class VXLANIf(Interface): + """ + The VXLAN protocol is a tunnelling protocol designed to solve the + problem of limited VLAN IDs (4096) in IEEE 802.1q. With VXLAN the + size of the identifier is expanded to 24 bits (16777216). + + VXLAN is described by IETF RFC 7348, and has been implemented by a + number of vendors. The protocol runs over UDP using a single + destination port. This document describes the Linux kernel tunnel + device, there is also a separate implementation of VXLAN for + Openvswitch. + + Unlike most tunnels, a VXLAN is a 1 to N network, not just point to + point. A VXLAN device can learn the IP address of the other endpoint + either dynamically in a manner similar to a learning bridge, or make + use of statically-configured forwarding entries. + + For more information please refer to: + https://www.kernel.org/doc/Documentation/networking/vxlan.txt + """ + + iftype = 'vxlan' + definition = { + **Interface.definition, + **{ + 'section': 'vxlan', + 'prefixes': ['vxlan', ], + 'bridgeable': True, + } + } + + _command_set = {**Interface._command_set, **{ + 'neigh_suppress': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'shellcmd': 'bridge link set dev {ifname} neigh_suppress {value} learning off', + }, + 'vlan_tunnel': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'shellcmd': 'bridge link set dev {ifname} vlan_tunnel {value}', + }, + }} + + def _create(self): + # This table represents a mapping from VyOS internal config dict to + # arguments used by iproute2. For more information please refer to: + # - https://man7.org/linux/man-pages/man8/ip-link.8.html + mapping = { + 'group' : 'group', + 'gpe' : 'gpe', + 'parameters.external' : 'external', + 'parameters.ip.df' : 'df', + 'parameters.ip.tos' : 'tos', + 'parameters.ip.ttl' : 'ttl', + 'parameters.ipv6.flowlabel' : 'flowlabel', + 'parameters.nolearning' : 'nolearning', + 'parameters.vni_filter' : 'vnifilter', + 'remote' : 'remote', + 'source_address' : 'local', + 'source_interface' : 'dev', + 'vni' : 'id', + } + + # IPv6 flowlabels can only be used on IPv6 tunnels, thus we need to + # ensure that at least the first remote IP address is passed to the + # tunnel creation command. Subsequent tunnel remote addresses can later + # be added to the FDB + remote_list = None + if 'remote' in self.config: + # skip first element as this is already configured as remote + remote_list = self.config['remote'][1:] + self.config['remote'] = self.config['remote'][0] + + cmd = 'ip link add {ifname} type {type} dstport {port}' + for vyos_key, iproute2_key in mapping.items(): + # dict_search will return an empty dict "{}" for valueless nodes like + # "parameters.nolearning" - thus we need to test the nodes existence + # by using isinstance() + tmp = dict_search(vyos_key, self.config) + if isinstance(tmp, dict): + cmd += f' {iproute2_key}' + elif tmp != None: + cmd += f' {iproute2_key} {tmp}' + + self._cmd(cmd.format(**self.config)) + # interface is always A/D down. It needs to be enabled explicitly + self.set_admin_state('down') + + # VXLAN tunnel is always recreated on any change - see interfaces_vxlan.py + if remote_list: + for remote in remote_list: + cmd = f'bridge fdb append to 00:00:00:00:00:00 dst {remote} ' \ + 'port {port} dev {ifname}' + self._cmd(cmd.format(**self.config)) + + def set_neigh_suppress(self, state): + """ + Controls whether neigh discovery (arp and nd) proxy and suppression + is enabled on the port. By default this flag is off. + """ + + # Determine current OS Kernel neigh_suppress setting - only adjust when needed + tmp = get_interface_config(self.ifname) + cur_state = 'on' if dict_search(f'linkinfo.info_slave_data.neigh_suppress', tmp) == True else 'off' + new_state = 'on' if state else 'off' + if cur_state != new_state: + self.set_interface('neigh_suppress', state) + + def set_vlan_vni_mapping(self, state): + """ + Controls whether vlan to tunnel mapping is enabled on the port. + By default this flag is off. + """ + def range_to_dict(vlan_to_vni): + """ Converts dict of ranges to dict """ + result_dict = {} + for vlan, vlan_conf in vlan_to_vni.items(): + vni = vlan_conf['vni'] + vlan_range, vni_range = vlan.split('-'), vni.split('-') + if len(vlan_range) > 1: + vlan_range = range(int(vlan_range[0]), int(vlan_range[1]) + 1) + vni_range = range(int(vni_range[0]), int(vni_range[1]) + 1) + dict_to_add = {str(k): {'vni': str(v)} for k, v in zip(vlan_range, vni_range)} + result_dict.update(dict_to_add) + return result_dict + + if not isinstance(state, bool): + raise ValueError('Value out of range') + + if 'vlan_to_vni_removed' in self.config: + cur_vni_filter = None + if dict_search('parameters.vni_filter', self.config) != None: + cur_vni_filter = get_vxlan_vni_filter(self.ifname) + + for vlan, vlan_config in range_to_dict(self.config['vlan_to_vni_removed']).items(): + # If VNI filtering is enabled, remove matching VNI filter + if cur_vni_filter != None: + vni = vlan_config['vni'] + if vni in cur_vni_filter: + self._cmd(f'bridge vni delete dev {self.ifname} vni {vni}') + self._cmd(f'bridge vlan del dev {self.ifname} vid {vlan}') + + # Determine current OS Kernel vlan_tunnel setting - only adjust when needed + tmp = get_interface_config(self.ifname) + cur_state = 'on' if dict_search(f'linkinfo.info_slave_data.vlan_tunnel', tmp) == True else 'off' + new_state = 'on' if state else 'off' + if cur_state != new_state: + self.set_interface('vlan_tunnel', new_state) + + if 'vlan_to_vni' in self.config: + # Determine current OS Kernel configured VLANs + vlan_vni_mapping = range_to_dict(self.config['vlan_to_vni']) + os_configured_vlan_ids = get_vxlan_vlan_tunnels(self.ifname) + add_vlan = list_diff(list(vlan_vni_mapping.keys()), os_configured_vlan_ids) + + for vlan, vlan_config in vlan_vni_mapping.items(): + # VLAN mapping already exists - skip + if vlan not in add_vlan: + continue + + vni = vlan_config['vni'] + # The following commands must be run one after another, + # they can not be combined with linux 6.1 and iproute2 6.1 + self._cmd(f'bridge vlan add dev {self.ifname} vid {vlan}') + self._cmd(f'bridge vlan add dev {self.ifname} vid {vlan} tunnel_info id {vni}') + + # If VNI filtering is enabled, install matching VNI filter + if dict_search('parameters.vni_filter', self.config) != None: + self._cmd(f'bridge vni add dev {self.ifname} vni {vni}') + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # call base class last + super().update(config) + + # Enable/Disable VLAN tunnel mapping + # This is only possible after the interface was assigned to the bridge + self.set_vlan_vni_mapping(dict_search('vlan_to_vni', config) != None) + + # Enable/Disable neighbor suppression and learning, there is no need to + # explicitly "disable" it, as VXLAN interface will be recreated if anything + # under "parameters" changes. + if dict_search('parameters.neighbor_suppress', config) != None: + self.set_neigh_suppress('on') diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py new file mode 100644 index 0000000..9030b13 --- /dev/null +++ b/python/vyos/ifconfig/wireguard.py @@ -0,0 +1,243 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +import time + +from datetime import timedelta +from tempfile import NamedTemporaryFile + +from hurry.filesize import size +from hurry.filesize import alternative + +from vyos.ifconfig import Interface +from vyos.ifconfig import Operational +from vyos.template import is_ipv6 + + +class WireGuardOperational(Operational): + def _dump(self): + """Dump wireguard data in a python friendly way.""" + last_device = None + output = {} + + # Dump wireguard connection data + _f = self._cmd('wg show all dump') + for line in _f.split('\n'): + if not line: + # Skip empty lines and last line + continue + items = line.split('\t') + + if last_device != items[0]: + # We are currently entering a new node + device, private_key, public_key, listen_port, fw_mark = items + last_device = device + + output[device] = { + 'private_key': None if private_key == '(none)' else private_key, + 'public_key': None if public_key == '(none)' else public_key, + 'listen_port': int(listen_port), + 'fw_mark': None if fw_mark == 'off' else int(fw_mark), + 'peers': {}, + } + else: + # We are entering a peer + ( + device, + public_key, + preshared_key, + endpoint, + allowed_ips, + latest_handshake, + transfer_rx, + transfer_tx, + persistent_keepalive, + ) = items + if allowed_ips == '(none)': + allowed_ips = [] + else: + allowed_ips = allowed_ips.split('\t') + output[device]['peers'][public_key] = { + 'preshared_key': None if preshared_key == '(none)' else preshared_key, + 'endpoint': None if endpoint == '(none)' else endpoint, + 'allowed_ips': allowed_ips, + 'latest_handshake': None if latest_handshake == '0' else int(latest_handshake), + 'transfer_rx': int(transfer_rx), + 'transfer_tx': int(transfer_tx), + 'persistent_keepalive': None if persistent_keepalive == 'off' else int(persistent_keepalive), + } + return output + + def show_interface(self): + from vyos.config import Config + + c = Config() + + wgdump = self._dump().get(self.config['ifname'], None) + + c.set_level(['interfaces', 'wireguard', self.config['ifname']]) + description = c.return_effective_value(['description']) + ips = c.return_effective_values(['address']) + + answer = 'interface: {}\n'.format(self.config['ifname']) + if description: + answer += ' description: {}\n'.format(description) + if ips: + answer += ' address: {}\n'.format(', '.join(ips)) + + answer += ' public key: {}\n'.format(wgdump['public_key']) + answer += ' private key: (hidden)\n' + answer += ' listening port: {}\n'.format(wgdump['listen_port']) + answer += '\n' + + for peer in c.list_effective_nodes(['peer']): + if wgdump['peers']: + pubkey = c.return_effective_value(['peer', peer, 'public-key']) + if pubkey in wgdump['peers']: + wgpeer = wgdump['peers'][pubkey] + + answer += ' peer: {}\n'.format(peer) + answer += ' public key: {}\n'.format(pubkey) + + """ figure out if the tunnel is recently active or not """ + status = 'inactive' + if wgpeer['latest_handshake'] is None: + """ no handshake ever """ + status = 'inactive' + else: + if int(wgpeer['latest_handshake']) > 0: + delta = timedelta( + seconds=int(time.time() - wgpeer['latest_handshake']) + ) + answer += ' latest handshake: {}\n'.format(delta) + if time.time() - int(wgpeer['latest_handshake']) < (60 * 5): + """ Five minutes and the tunnel is still active """ + status = 'active' + else: + """ it's been longer than 5 minutes """ + status = 'inactive' + elif int(wgpeer['latest_handshake']) == 0: + """ no handshake ever """ + status = 'inactive' + answer += ' status: {}\n'.format(status) + + if wgpeer['endpoint'] is not None: + answer += ' endpoint: {}\n'.format(wgpeer['endpoint']) + + if wgpeer['allowed_ips'] is not None: + answer += ' allowed ips: {}\n'.format( + ','.join(wgpeer['allowed_ips']).replace(',', ', ') + ) + + if wgpeer['transfer_rx'] > 0 or wgpeer['transfer_tx'] > 0: + rx_size = size(wgpeer['transfer_rx'], system=alternative) + tx_size = size(wgpeer['transfer_tx'], system=alternative) + answer += ' transfer: {} received, {} sent\n'.format( + rx_size, tx_size + ) + + if wgpeer['persistent_keepalive'] is not None: + answer += ' persistent keepalive: every {} seconds\n'.format( + wgpeer['persistent_keepalive'] + ) + answer += '\n' + return answer + + +@Interface.register +class WireGuardIf(Interface): + OperationalClass = WireGuardOperational + iftype = 'wireguard' + definition = { + **Interface.definition, + **{ + 'section': 'wireguard', + 'prefixes': [ + 'wg', + ], + 'bridgeable': False, + }, + } + + def get_mac(self): + """Get a synthetic MAC address.""" + return self.get_mac_synthetic() + + def update(self, config): + """General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface.""" + + tmp_file = NamedTemporaryFile('w') + tmp_file.write(config['private_key']) + tmp_file.flush() + + # Wireguard base command is identical for every peer + base_cmd = 'wg set {ifname}' + if 'port' in config: + base_cmd += ' listen-port {port}' + if 'fwmark' in config: + base_cmd += ' fwmark {fwmark}' + + base_cmd += f' private-key {tmp_file.name}' + base_cmd = base_cmd.format(**config) + if 'peer' in config: + for peer, peer_config in config['peer'].items(): + # T4702: No need to configure this peer when it was explicitly + # marked as disabled - also active sessions are terminated as + # the public key was already removed when entering this method! + if 'disable' in peer_config: + continue + + # start of with a fresh 'wg' command + cmd = base_cmd + ' peer {public_key}' + + # If no PSK is given remove it by using /dev/null - passing keys via + # the shell (usually bash) is considered insecure, thus we use a file + no_psk_file = '/dev/null' + psk_file = no_psk_file + if 'preshared_key' in peer_config: + psk_file = '/tmp/tmp.wireguard.psk' + with open(psk_file, 'w') as f: + f.write(peer_config['preshared_key']) + cmd += f' preshared-key {psk_file}' + + # Persistent keepalive is optional + if 'persistent_keepalive' in peer_config: + cmd += ' persistent-keepalive {persistent_keepalive}' + + # Multiple allowed-ip ranges can be defined - ensure we are always + # dealing with a list + if isinstance(peer_config['allowed_ips'], str): + peer_config['allowed_ips'] = [peer_config['allowed_ips']] + cmd += ' allowed-ips ' + ','.join(peer_config['allowed_ips']) + + # Endpoint configuration is optional + if {'address', 'port'} <= set(peer_config): + if is_ipv6(peer_config['address']): + cmd += ' endpoint [{address}]:{port}' + else: + cmd += ' endpoint {address}:{port}' + + self._cmd(cmd.format(**peer_config)) + + # PSK key file is not required to be stored persistently as its backed by CLI + if psk_file != no_psk_file and os.path.exists(psk_file): + os.remove(psk_file) + + # call base class + super().update(config) diff --git a/python/vyos/ifconfig/wireless.py b/python/vyos/ifconfig/wireless.py new file mode 100644 index 0000000..88eaa77 --- /dev/null +++ b/python/vyos/ifconfig/wireless.py @@ -0,0 +1,65 @@ +# Copyright 2020-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig.interface import Interface + +@Interface.register +class WiFiIf(Interface): + """ + Handle WIFI/WLAN interfaces. + """ + iftype = 'wifi' + definition = { + **Interface.definition, + **{ + 'section': 'wireless', + 'prefixes': ['wlan', ], + 'bridgeable': True, + } + } + def _create(self): + # all interfaces will be added in monitor mode + cmd = 'iw phy {physical_device} interface add {ifname} type monitor' + self._cmd(cmd.format(**self.config)) + + # wireless interface is administratively down by default + self.set_admin_state('down') + + def _delete(self): + cmd = 'iw dev {ifname} del' \ + .format(**self.config) + self._cmd(cmd) + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # We can not call add_to_bridge() until wpa_supplicant is running, thus + # we will remove the key from the config dict and react to this special + # case in this derived class. + # re-add ourselves to any bridge we might have fallen out of + bridge_member = None + if 'is_bridge_member' in config: + bridge_member = config['is_bridge_member'] + del config['is_bridge_member'] + + # call base class first + super().update(config) + + # re-add ourselves to any bridge we might have fallen out of + if bridge_member: + self.add_to_bridge(bridge_member) diff --git a/python/vyos/ifconfig/wwan.py b/python/vyos/ifconfig/wwan.py new file mode 100644 index 0000000..845c9be --- /dev/null +++ b/python/vyos/ifconfig/wwan.py @@ -0,0 +1,45 @@ +# Copyright 2021 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig.interface import Interface + +@Interface.register +class WWANIf(Interface): + iftype = 'wwan' + definition = { + **Interface.definition, + **{ + 'section': 'wwan', + 'prefixes': ['wwan', ], + 'eternal': 'wwan[0-9]+$', + }, + } + + def remove(self): + """ + Remove interface from config. Removing the interface deconfigures all + assigned IP addresses. + Example: + >>> from vyos.ifconfig import WWANIf + >>> i = WWANIf('wwan0') + >>> i.remove() + """ + + if self.exists(self.ifname): + # interface is placed in A/D state when removed from config! It + # will remain visible for the operating system. + self.set_admin_state('down') + + super().remove() diff --git a/python/vyos/iflag.py b/python/vyos/iflag.py new file mode 100644 index 0000000..3ce73c1 --- /dev/null +++ b/python/vyos/iflag.py @@ -0,0 +1,36 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from enum import IntEnum + +class IFlag(IntEnum): + """ net/if.h interface flags """ + + IFF_UP = 0x1 #: Interface up/down status + IFF_BROADCAST = 0x2 #: Broadcast address valid + IFF_DEBUG = 0x4, #: Debugging + IFF_LOOPBACK = 0x8 #: Is loopback network + IFF_POINTOPOINT = 0x10 #: Is point-to-point link + IFF_NOTRAILERS = 0x20 #: Avoid use of trailers + IFF_RUNNING = 0x40 #: Resources allocated + IFF_NOARP = 0x80 #: No address resolution protocol + IFF_PROMISC = 0x100 #: Promiscuous mode + IFF_ALLMULTI = 0x200 #: Receive all multicast + IFF_MASTER = 0x400 #: Load balancer master + IFF_SLAVE = 0x800 #: Load balancer slave + IFF_MULTICAST = 0x1000 #: Supports multicast + IFF_PORTSEL = 0x2000 #: Media type adjustable + IFF_AUTOMEDIA = 0x4000 #: Automatic media type enabled + IFF_DYNAMIC = 0x8000 #: Is a dial-up device with dynamic address diff --git a/python/vyos/initialsetup.py b/python/vyos/initialsetup.py new file mode 100644 index 0000000..cb6b9e4 --- /dev/null +++ b/python/vyos/initialsetup.py @@ -0,0 +1,72 @@ +# initialsetup -- functions for setting common values in config file, +# for use in installation and first boot scripts +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or modify it under the terms of +# the GNU Lesser General Public License as published by the Free Software Foundation; +# either version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License along with this library; +# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from vyos.utils.auth import make_password_hash +from vyos.utils.auth import split_ssh_public_key + +def set_interface_address(config, intf, addr, intf_type="ethernet"): + config.set(["interfaces", intf_type, intf, "address"], value=addr) + config.set_tag(["interfaces", intf_type]) + +def set_host_name(config, hostname): + config.set(["system", "host-name"], value=hostname) + +def set_name_servers(config, servers): + for s in servers: + config.set(["system", "name-server"], replace=False, value=s) + +def set_default_gateway(config, gateway): + config.set(["protocols", "static", "route", "0.0.0.0/0", "next-hop", gateway]) + config.set_tag(["protocols", "static", "route"]) + config.set_tag(["protocols", "static", "route", "0.0.0.0/0", "next-hop"]) + +def set_user_password(config, user, password): + # Make a password hash + hash = make_password_hash(password) + + config.set(["system", "login", "user", user, "authentication", "encrypted-password"], value=hash) + config.set(["system", "login", "user", user, "authentication", "plaintext-password"], value="") + +def disable_user_password(config, user): + config.set(["system", "login", "user", user, "authentication", "encrypted-password"], value="!") + config.set(["system", "login", "user", user, "authentication", "plaintext-password"], value="") + +def set_user_level(config, user, level): + config.set(["system", "login", "user", user, "level"], value=level) + +def set_user_ssh_key(config, user, key_string): + key = split_ssh_public_key(key_string, defaultname=user) + + config.set(["system", "login", "user", user, "authentication", "public-keys", key["name"], "key"], value=key["data"]) + config.set(["system", "login", "user", user, "authentication", "public-keys", key["name"], "type"], value=key["type"]) + config.set_tag(["system", "login", "user", user, "authentication", "public-keys"]) + +def create_user(config, user, password=None, key=None, level="admin"): + config.set(["system", "login", "user", user]) + config.set_tag(["system", "login", "user", user]) + + if not key and not password: + raise ValueError("Must set at least password or SSH public key") + + if password: + set_user_password(config, user, password) + else: + disable_user_password(config, user) + + if key: + set_user_ssh_key(config, user, key) + + set_user_level(config, user, level) diff --git a/python/vyos/ioctl.py b/python/vyos/ioctl.py new file mode 100644 index 0000000..51574c1 --- /dev/null +++ b/python/vyos/ioctl.py @@ -0,0 +1,35 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +import socket +import fcntl +import struct + +SIOCGIFFLAGS = 0x8913 + +def get_terminal_size(): + """ pull the terminal size """ + """ rows,cols = vyos.ioctl.get_terminal_size() """ + columns, rows = os.get_terminal_size(0) + return (rows,columns) + +def get_interface_flags(intf): + """ Pull the SIOCGIFFLAGS """ + nullif = '\0'*256 + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + raw = fcntl.ioctl(sock.fileno(), SIOCGIFFLAGS, intf + nullif) + flags, = struct.unpack('H', raw[16:18]) + return flags diff --git a/python/vyos/ipsec.py b/python/vyos/ipsec.py new file mode 100644 index 0000000..28f7756 --- /dev/null +++ b/python/vyos/ipsec.py @@ -0,0 +1,247 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Package to communicate with Strongswan VICI + + +class ViciInitiateError(Exception): + """ + VICI can't initiate a session. + """ + + pass + + +class ViciCommandError(Exception): + """ + VICI can't execute a command by any reason. + """ + + pass + + +def get_vici_sas(): + from vici import Session as vici_session + + try: + session = vici_session() + except Exception: + raise ViciInitiateError('IPsec not initialized') + try: + sas = list(session.list_sas()) + return sas + except Exception: + raise ViciCommandError('Failed to get SAs') + + +def get_vici_connections(): + from vici import Session as vici_session + + try: + session = vici_session() + except Exception: + raise ViciInitiateError('IPsec not initialized') + try: + connections = list(session.list_conns()) + return connections + except Exception: + raise ViciCommandError('Failed to get connections') + + +def get_vici_sas_by_name(ike_name: str, tunnel: str) -> list: + """ + Find installed SAs by IKE_SA name and/or CHILD_SA name + and return list with SASs info. + If tunnel is not None return a list contained only + CHILD_SAs wich names equal tunnel value. + :param ike_name: IKE SA name + :type ike_name: str + :param tunnel: CHILD SA name + :type tunnel: str + :return: list of Ordinary Dicts with SASs + :rtype: list + """ + from vici import Session as vici_session + + try: + session = vici_session() + except Exception: + raise ViciInitiateError('IPsec not initialized') + vici_dict = {} + if ike_name: + vici_dict['ike'] = ike_name + if tunnel: + vici_dict['child'] = tunnel + try: + sas = list(session.list_sas(vici_dict)) + return sas + except Exception: + raise ViciCommandError('Failed to get SAs') + + +def get_vici_connection_by_name(ike_name: str) -> list: + """ + Find loaded SAs by IKE_SA name and return list with SASs info + :param ike_name: IKE SA name + :type ike_name: str + :return: list of Ordinary Dicts with SASs + :rtype: list + """ + from vici import Session as vici_session + + try: + session = vici_session() + except Exception: + raise ViciInitiateError('IPsec is not initialized') + vici_dict = {} + if ike_name: + vici_dict['ike'] = ike_name + try: + sas = list(session.list_conns(vici_dict)) + return sas + except Exception: + raise ViciCommandError('Failed to get SAs') + + +def terminate_vici_ikeid_list(ike_id_list: list) -> None: + """ + Terminate IKE SAs by their id that contained in the list + :param ike_id_list: list of IKE SA id + :type ike_id_list: list + """ + from vici import Session as vici_session + + try: + session = vici_session() + except Exception: + raise ViciInitiateError('IPsec is not initialized') + try: + for ikeid in ike_id_list: + session_generator = session.terminate({'ike-id': ikeid, 'timeout': '-1'}) + # a dummy `for` loop is required because of requirements + # from vici. Without a full iteration on the output, the + # command to vici may not be executed completely + for _ in session_generator: + pass + except Exception: + raise ViciCommandError(f'Failed to terminate SA for IKE ids {ike_id_list}') + + +def terminate_vici_by_name(ike_name: str, child_name: str) -> None: + """ + Terminate IKE SAs by name if CHILD SA name is None. + Terminate CHILD SAs by name if CHILD SA name is specified + :param ike_name: IKE SA name + :type ike_name: str + :param child_name: CHILD SA name + :type child_name: str + """ + from vici import Session as vici_session + + try: + session = vici_session() + except Exception: + raise ViciInitiateError('IPsec is not initialized') + try: + vici_dict: dict = {} + if ike_name: + vici_dict['ike'] = ike_name + if child_name: + vici_dict['child'] = child_name + session_generator = session.terminate(vici_dict) + # a dummy `for` loop is required because of requirements + # from vici. Without a full iteration on the output, the + # command to vici may not be executed completely + for _ in session_generator: + pass + except Exception: + if child_name: + raise ViciCommandError(f'Failed to terminate SA for IPSEC {child_name}') + else: + raise ViciCommandError(f'Failed to terminate SA for IKE {ike_name}') + + +def vici_initiate_all_child_sa_by_ike(ike_sa_name: str, child_sa_list: list) -> bool: + """ + Initiate IKE SA with scpecified CHILD_SAs in list + + Args: + ike_sa_name (str): an IKE SA connection name + child_sa_list (list): a list of child SA names + + Returns: + bool: a result of initiation command + """ + from vici import Session as vici_session + + try: + session = vici_session() + except Exception: + raise ViciInitiateError('IPsec is not initialized') + + try: + for child_sa_name in child_sa_list: + session_generator = session.initiate( + {'ike': ike_sa_name, 'child': child_sa_name, 'timeout': '-1'} + ) + # a dummy `for` loop is required because of requirements + # from vici. Without a full iteration on the output, the + # command to vici may not be executed completely + for _ in session_generator: + pass + return True + except Exception: + raise ViciCommandError(f'Failed to initiate SA for IKE {ike_sa_name}') + + +def vici_initiate( + ike_sa_name: str, child_sa_name: str, src_addr: str, dst_addr: str +) -> bool: + """Initiate IKE SA with one child_sa connection with specific peer + + Args: + ike_sa_name (str): an IKE SA connection name + child_sa_name (str): a child SA profile name + src_addr (str): source address + dst_addr (str): remote address + + Returns: + bool: a result of initiation command + """ + from vici import Session as vici_session + + try: + session = vici_session() + except Exception: + raise ViciInitiateError('IPsec is not initialized') + + try: + session_generator = session.initiate( + { + 'ike': ike_sa_name, + 'child': child_sa_name, + 'timeout': '-1', + 'my-host': src_addr, + 'other-host': dst_addr, + } + ) + # a dummy `for` loop is required because of requirements + # from vici. Without a full iteration on the output, the + # command to vici may not be executed completely + for _ in session_generator: + pass + return True + except Exception: + raise ViciCommandError(f'Failed to initiate SA for IKE {ike_sa_name}') diff --git a/python/vyos/kea.py b/python/vyos/kea.py new file mode 100644 index 0000000..addfdba --- /dev/null +++ b/python/vyos/kea.py @@ -0,0 +1,364 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import json +import os +import socket + +from vyos.template import is_ipv6 +from vyos.template import isc_static_route +from vyos.template import netmask_from_cidr +from vyos.utils.dict import dict_search_args +from vyos.utils.file import file_permissions +from vyos.utils.process import run + +kea4_options = { + 'name_server': 'domain-name-servers', + 'domain_name': 'domain-name', + 'domain_search': 'domain-search', + 'ntp_server': 'ntp-servers', + 'pop_server': 'pop-server', + 'smtp_server': 'smtp-server', + 'time_server': 'time-servers', + 'wins_server': 'netbios-name-servers', + 'default_router': 'routers', + 'server_identifier': 'dhcp-server-identifier', + 'tftp_server_name': 'tftp-server-name', + 'bootfile_size': 'boot-size', + 'time_offset': 'time-offset', + 'wpad_url': 'wpad-url', + 'ipv6_only_preferred': 'v6-only-preferred', + 'captive_portal': 'v4-captive-portal' +} + +kea6_options = { + 'info_refresh_time': 'information-refresh-time', + 'name_server': 'dns-servers', + 'domain_search': 'domain-search', + 'nis_domain': 'nis-domain-name', + 'nis_server': 'nis-servers', + 'nisplus_domain': 'nisp-domain-name', + 'nisplus_server': 'nisp-servers', + 'sntp_server': 'sntp-servers', + 'captive_portal': 'v6-captive-portal' +} + +kea_ctrl_socket = '/run/kea/dhcp{inet}-ctrl-socket' + +def kea_parse_options(config): + options = [] + + for node, option_name in kea4_options.items(): + if node not in config: + continue + + value = ", ".join(config[node]) if isinstance(config[node], list) else config[node] + options.append({'name': option_name, 'data': value}) + + if 'client_prefix_length' in config: + options.append({'name': 'subnet-mask', 'data': netmask_from_cidr('0.0.0.0/' + config['client_prefix_length'])}) + + if 'ip_forwarding' in config: + options.append({'name': 'ip-forwarding', 'data': "true"}) + + if 'static_route' in config: + default_route = '' + + if 'default_router' in config: + default_route = isc_static_route('0.0.0.0/0', config['default_router']) + + routes = [isc_static_route(route, route_options['next_hop']) for route, route_options in config['static_route'].items()] + + options.append({'name': 'rfc3442-static-route', 'data': ", ".join(routes if not default_route else routes + [default_route])}) + options.append({'name': 'windows-static-route', 'data': ", ".join(routes)}) + + if 'time_zone' in config: + with open("/usr/share/zoneinfo/" + config['time_zone'], "rb") as f: + tz_string = f.read().split(b"\n")[-2].decode("utf-8") + + options.append({'name': 'pcode', 'data': tz_string}) + options.append({'name': 'tcode', 'data': config['time_zone']}) + + unifi_controller = dict_search_args(config, 'vendor_option', 'ubiquiti', 'unifi_controller') + if unifi_controller: + options.append({ + 'name': 'unifi-controller', + 'data': unifi_controller, + 'space': 'ubnt' + }) + + return options + +def kea_parse_subnet(subnet, config): + out = {'subnet': subnet, 'id': int(config['subnet_id'])} + options = [] + + if 'option' in config: + out['option-data'] = kea_parse_options(config['option']) + + if 'bootfile_name' in config['option']: + out['boot-file-name'] = config['option']['bootfile_name'] + + if 'bootfile_server' in config['option']: + out['next-server'] = config['option']['bootfile_server'] + + if 'ignore_client_id' in config: + out['match-client-id'] = False + + if 'lease' in config: + out['valid-lifetime'] = int(config['lease']) + out['max-valid-lifetime'] = int(config['lease']) + + if 'range' in config: + pools = [] + for num, range_config in config['range'].items(): + start, stop = range_config['start'], range_config['stop'] + pool = { + 'pool': f'{start} - {stop}' + } + + if 'option' in range_config: + pool['option-data'] = kea_parse_options(range_config['option']) + + if 'bootfile_name' in range_config['option']: + pool['boot-file-name'] = range_config['option']['bootfile_name'] + + if 'bootfile_server' in range_config['option']: + pool['next-server'] = range_config['option']['bootfile_server'] + + pools.append(pool) + out['pools'] = pools + + if 'static_mapping' in config: + reservations = [] + for host, host_config in config['static_mapping'].items(): + if 'disable' in host_config: + continue + + reservation = { + 'hostname': host, + } + + if 'mac' in host_config: + reservation['hw-address'] = host_config['mac'] + + if 'duid' in host_config: + reservation['duid'] = host_config['duid'] + + if 'ip_address' in host_config: + reservation['ip-address'] = host_config['ip_address'] + + if 'option' in host_config: + reservation['option-data'] = kea_parse_options(host_config['option']) + + if 'bootfile_name' in host_config['option']: + reservation['boot-file-name'] = host_config['option']['bootfile_name'] + + if 'bootfile_server' in host_config['option']: + reservation['next-server'] = host_config['option']['bootfile_server'] + + reservations.append(reservation) + out['reservations'] = reservations + + return out + +def kea6_parse_options(config): + options = [] + + for node, option_name in kea6_options.items(): + if node not in config: + continue + + value = ", ".join(config[node]) if isinstance(config[node], list) else config[node] + options.append({'name': option_name, 'data': value}) + + if 'sip_server' in config: + sip_servers = config['sip_server'] + + addrs = [] + hosts = [] + + for server in sip_servers: + if is_ipv6(server): + addrs.append(server) + else: + hosts.append(server) + + if addrs: + options.append({'name': 'sip-server-addr', 'data': ", ".join(addrs)}) + + if hosts: + options.append({'name': 'sip-server-dns', 'data': ", ".join(hosts)}) + + cisco_tftp = dict_search_args(config, 'vendor_option', 'cisco', 'tftp-server') + if cisco_tftp: + options.append({'name': 'tftp-servers', 'code': 2, 'space': 'cisco', 'data': cisco_tftp}) + + return options + +def kea6_parse_subnet(subnet, config): + out = {'subnet': subnet, 'id': int(config['subnet_id'])} + + if 'option' in config: + out['option-data'] = kea6_parse_options(config['option']) + + if 'interface' in config: + out['interface'] = config['interface'] + + if 'range' in config: + pools = [] + for num, range_config in config['range'].items(): + pool = {} + + if 'prefix' in range_config: + pool['pool'] = range_config['prefix'] + + if 'start' in range_config: + start = range_config['start'] + stop = range_config['stop'] + pool['pool'] = f'{start} - {stop}' + + if 'option' in range_config: + pool['option-data'] = kea6_parse_options(range_config['option']) + + pools.append(pool) + + out['pools'] = pools + + if 'prefix_delegation' in config: + pd_pools = [] + + if 'prefix' in config['prefix_delegation']: + for prefix, pd_conf in config['prefix_delegation']['prefix'].items(): + pd_pool = { + 'prefix': prefix, + 'prefix-len': int(pd_conf['prefix_length']), + 'delegated-len': int(pd_conf['delegated_length']) + } + + if 'excluded_prefix' in pd_conf: + pd_pool['excluded-prefix'] = pd_conf['excluded_prefix'] + pd_pool['excluded-prefix-len'] = int(pd_conf['excluded_prefix_length']) + + pd_pools.append(pd_pool) + + out['pd-pools'] = pd_pools + + if 'lease_time' in config: + if 'default' in config['lease_time']: + out['valid-lifetime'] = int(config['lease_time']['default']) + if 'maximum' in config['lease_time']: + out['max-valid-lifetime'] = int(config['lease_time']['maximum']) + if 'minimum' in config['lease_time']: + out['min-valid-lifetime'] = int(config['lease_time']['minimum']) + + if 'static_mapping' in config: + reservations = [] + for host, host_config in config['static_mapping'].items(): + if 'disable' in host_config: + continue + + reservation = { + 'hostname': host + } + + if 'mac' in host_config: + reservation['hw-address'] = host_config['mac'] + + if 'duid' in host_config: + reservation['duid'] = host_config['duid'] + + if 'ipv6_address' in host_config: + reservation['ip-addresses'] = [ host_config['ipv6_address'] ] + + if 'ipv6_prefix' in host_config: + reservation['prefixes'] = [ host_config['ipv6_prefix'] ] + + if 'option' in host_config: + reservation['option-data'] = kea6_parse_options(host_config['option']) + + reservations.append(reservation) + + out['reservations'] = reservations + + return out + +def _ctrl_socket_command(inet, command, args=None): + path = kea_ctrl_socket.format(inet=inet) + + if not os.path.exists(path): + return None + + if file_permissions(path) != '0775': + run(f'sudo chmod 775 {path}') + + with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock: + sock.connect(path) + + payload = {'command': command} + if args: + payload['arguments'] = args + + sock.send(bytes(json.dumps(payload), 'utf-8')) + result = b'' + while True: + data = sock.recv(4096) + result += data + if len(data) < 4096: + break + + return json.loads(result.decode('utf-8')) + +def kea_get_leases(inet): + leases = _ctrl_socket_command(inet, f'lease{inet}-get-all') + + if not leases or 'result' not in leases or leases['result'] != 0: + return [] + + return leases['arguments']['leases'] + +def kea_delete_lease(inet, ip_address): + args = {'ip-address': ip_address} + + result = _ctrl_socket_command(inet, f'lease{inet}-del', args) + + if result and 'result' in result: + return result['result'] == 0 + + return False + +def kea_get_active_config(inet): + config = _ctrl_socket_command(inet, 'config-get') + + if not config or 'result' not in config or config['result'] != 0: + return None + + return config + +def kea_get_pool_from_subnet_id(config, inet, subnet_id): + shared_networks = dict_search_args(config, 'arguments', f'Dhcp{inet}', 'shared-networks') + + if not shared_networks: + return None + + for network in shared_networks: + if f'subnet{inet}' not in network: + continue + + for subnet in network[f'subnet{inet}']: + if 'id' in subnet and int(subnet['id']) == int(subnet_id): + return network['name'] + + return None diff --git a/python/vyos/limericks.py b/python/vyos/limericks.py new file mode 100644 index 0000000..3c67448 --- /dev/null +++ b/python/vyos/limericks.py @@ -0,0 +1,72 @@ +# Copyright 2015, 2018 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import random + +limericks = [ + +""" +A programmer whose name was Searle +once wrote a long program in Perl. +Despite very few quirks, +no one got how it works. +Not even the interpreter perl(1). +""", + +""" +There was a young lady of Maine +who set up IPsec VPN. +Problems didn't arise +till other vendors' device +had to add she to that VPN. +""", + +""" +One day a programmer from York +started his own Vyatta fork. +Though he was a huge geek, +it still took him a week +to get the damn build scripts to work. +""", + +""" +A network admin from Hong Kong +knew MPPE cipher's not strong. +But he was behind NAT, +so he put up with that, +sad network admin from Hong Kong. +""", + +""" +A network admin named Drake +greeted friends with a three-way handshake +and refused to proceed +if they didn't complete it, +that standards-compliant guy Drake. +""", + +""" +A network admin from Nantucket +used hierarchy token buckets. +Bandwidth limits he set +slowed down his net, +users drove him away from Nantucket. +""" + +] + + +def get_random(): + return limericks[random.randint(0, len(limericks) - 1)] diff --git a/python/vyos/load_config.py b/python/vyos/load_config.py new file mode 100644 index 0000000..b910a2f --- /dev/null +++ b/python/vyos/load_config.py @@ -0,0 +1,181 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +"""This module abstracts the loading of a config file into the running +config. It provides several varieties of loading a config file, from the +legacy version to the developing versions, as a means of offering +alternatives for competing use cases, and a base for profiling the +performance of each. +""" + +import sys +from pathlib import Path +from tempfile import NamedTemporaryFile +from typing import Union, Literal, TypeAlias, get_type_hints, get_args + +from vyos.config import Config +from vyos.configtree import ConfigTree, DiffTree +from vyos.configsource import ConfigSourceSession, VyOSError +from vyos.migrate import ConfigMigrate, ConfigMigrateError +from vyos.utils.process import popen, DEVNULL + +Variety: TypeAlias = Literal['explicit', 'batch', 'tree', 'legacy'] +ConfigObj: TypeAlias = Union[str, ConfigTree] + +thismod = sys.modules[__name__] + +class LoadConfigError(Exception): + """Raised when an error occurs loading a config file. + """ + +# utility functions + +def get_running_config(config: Config) -> ConfigTree: + return config.get_config_tree(effective=True) + +def get_proposed_config(config_file: str = None) -> ConfigTree: + config_str = Path(config_file).read_text() + return ConfigTree(config_str) + +def check_session(strict: bool, switch: Variety) -> None: + """Check if we are in a config session, with no uncommitted changes, if + strict. This is not needed for legacy load, as these checks are + implicit. + """ + + if switch == 'legacy': + return + + context = ConfigSourceSession() + + if not context.in_session(): + raise LoadConfigError('not in a config session') + + if strict and context.session_changed(): + raise LoadConfigError('commit or discard changes before loading config') + +# methods to call for each variety + +# explicit +def diff_to_commands(ctree: ConfigTree, ntree: ConfigTree) -> list: + """Calculate the diff between the current and proposed config.""" + # Calculate the diff between the current and new config tree + commands = DiffTree(ctree, ntree).to_commands() + # on an empty set of 'add' or 'delete' commands, to_commands + # returns '\n'; prune below + command_list = commands.splitlines() + command_list = [c for c in command_list if c] + return command_list + +def set_commands(cmds: list) -> None: + """Set commands in the config session.""" + if not cmds: + print('no commands to set') + return + error_out = [] + for op in cmds: + out, rc = popen(f'/opt/vyatta/sbin/my_{op}', shell=True, stderr=DEVNULL) + if rc != 0: + error_out.append(out) + continue + if error_out: + out = '\n'.join(error_out) + raise LoadConfigError(out) + +# legacy +class LoadConfig(ConfigSourceSession): + """A subclass for calling 'loadFile'. + """ + def load_config(self, file_name): + return self._run(['/bin/cli-shell-api','loadFile', file_name]) + +# end methods to call for each variety + +def migrate(config_obj: ConfigObj) -> ConfigObj: + """Migrate a config object to the current version. + """ + if isinstance(config_obj, ConfigTree): + config_file = NamedTemporaryFile(delete=False).name + Path(config_file).write_text(config_obj.to_string()) + else: + config_file = config_obj + + config_migrate = ConfigMigrate(config_file) + try: + config_migrate.run() + except ConfigMigrateError as e: + raise LoadConfigError(e) from e + else: + if isinstance(config_obj, ConfigTree): + return ConfigTree(Path(config_file).read_text()) + return config_file + finally: + if isinstance(config_obj, ConfigTree): + Path(config_file).unlink() + +def load_explicit(config_obj: ConfigObj): + """Explicit load from file or configtree. + """ + config = Config() + ctree = get_running_config(config) + if isinstance(config_obj, ConfigTree): + ntree = config_obj + else: + ntree = get_proposed_config(config_obj) + # Calculate the diff between the current and proposed config + cmds = diff_to_commands(ctree, ntree) + # Set the commands in the config session + set_commands(cmds) + +def load_batch(config_obj: ConfigObj): + # requires legacy backend patch + raise NotImplementedError('batch loading not implemented') + +def load_tree(config_obj: ConfigObj): + # requires vyconf backend patch + raise NotImplementedError('tree loading not implemented') + +def load_legacy(config_obj: ConfigObj): + """Legacy load from file or configtree. + """ + if isinstance(config_obj, ConfigTree): + config_file = NamedTemporaryFile(delete=False).name + Path(config_file).write_text(config_obj.to_string()) + else: + config_file = config_obj + + config = LoadConfig() + + try: + config.load_config(config_file) + except VyOSError as e: + raise LoadConfigError(e) from e + finally: + if isinstance(config_obj, ConfigTree): + Path(config_file).unlink() + +def load(config_obj: ConfigObj, strict: bool = True, + switch: Variety = 'legacy'): + type_hints = get_type_hints(load) + switch_choice = get_args(type_hints['switch']) + if switch not in switch_choice: + raise ValueError(f'invalid switch: {switch}') + + check_session(strict, switch) + + config_obj = migrate(config_obj) + + func = getattr(thismod, f'load_{switch}') + func(config_obj) diff --git a/python/vyos/logger.py b/python/vyos/logger.py new file mode 100644 index 0000000..f7cc964 --- /dev/null +++ b/python/vyos/logger.py @@ -0,0 +1,143 @@ +# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# A wrapper class around logging to make it easier to use + +# for a syslog logger: +# from vyos.logger import syslog +# syslog.critical('message') + +# for a stderr logger: +# from vyos.logger import stderr +# stderr.critical('message') + +# for a custom logger (syslog and file): +# from vyos.logger import getLogger +# combined = getLogger(__name__, syslog=True, stream=sys.stdout, filename='/tmp/test') +# combined.critical('message') + +import sys +import logging +import logging.handlers as handlers + +TIMED = '%(asctime)s: %(message)s' +SHORT = '%(filename)s: %(message)s' +CLEAR = '%(levelname) %(asctime)s %(filename)s: %(message)s' + +_levels = { + 'CRITICAL': logging.CRITICAL, + 'ERROR': logging.CRITICAL, + 'WARNING': logging.WARNING, + 'INFO': logging.INFO, + 'DEBUG': logging.DEBUG, + 'NOTSET': logging.NOTSET, +} + +# prevent recreation of already created logger +_created = {} + +def getLogger(name=None, **kwargs): + if name in _created: + if len(kwargs) == 0: + return _created[name] + raise ValueError('a logger with the name "{name} already exists') + + logger = logging.getLogger(name) + logger.setLevel(_levels[kwargs.get('level', 'DEBUG')]) + + if 'address' in kwargs or kwargs.get('syslog', False): + logger.addHandler(_syslog(**kwargs)) + if 'stream' in kwargs: + logger.addHandler(_stream(**kwargs)) + if 'filename' in kwargs: + logger.addHandler(_file(**kwargs)) + + _created[name] = logger + return logger + + +def _syslog(**kwargs): + formating = kwargs.get('format', SHORT) + handler = handlers.SysLogHandler( + address=kwargs.get('address', '/dev/log'), + facility=kwargs.get('facility', 'syslog'), + ) + handler.setFormatter(logging.Formatter(formating)) + return handler + + +def _stream(**kwargs): + formating = kwargs.get('format', CLEAR) + handler = logging.StreamHandler( + stream=kwargs.get('stream', sys.stderr), + ) + handler.setFormatter(logging.Formatter(formating)) + return handler + + +def _file(**kwargs): + formating = kwargs.get('format', CLEAR) + handler = handlers.RotatingFileHandler( + filename=kwargs.get('filename', 1048576), + maxBytes=kwargs.get('maxBytes', 1048576), + backupCount=kwargs.get('backupCount', 3), + ) + handler.setFormatter(logging.Formatter(formating)) + return handler + + +# exported pre-built logger, please keep in mind that the names +# must be unique otherwise the logger are shared + +# a logger for stderr +stderr = getLogger( + 'VyOS Syslog', + format=SHORT, + stream=sys.stderr, + address='/dev/log' +) + +# a logger to syslog +syslog = getLogger( + 'VyOS StdErr', + format='%(message)s', + address='/dev/log' +) + + +# testing +if __name__ == '__main__': + # from vyos.logger import getLogger + formating = '%(asctime)s (%(filename)s) %(levelname)s: %(message)s' + + # syslog logger + # syslog=True if no 'address' field is provided + syslog = getLogger(__name__ + '.1', syslog=True, format=formating) + syslog.info('syslog test') + + # steam logger + stream = getLogger(__name__ + '.2', stream=sys.stdout, level='ERROR') + stream.info('steam test') + + # file logger + filelog = getLogger(__name__ + '.3', filename='/tmp/test') + filelog.info('file test') + + # create a combined logger + getLogger('VyOS', syslog=True, stream=sys.stdout, filename='/tmp/test') + + # recover the created logger from name + combined = getLogger('VyOS') + combined.info('combined test') diff --git a/python/vyos/migrate.py b/python/vyos/migrate.py new file mode 100644 index 0000000..9d16136 --- /dev/null +++ b/python/vyos/migrate.py @@ -0,0 +1,283 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +import re +import json +import logging +from pathlib import Path +from grp import getgrnam + +from vyos.component_version import VersionInfo +from vyos.component_version import version_info_from_system +from vyos.component_version import version_info_from_file +from vyos.component_version import version_info_copy +from vyos.component_version import version_info_prune_component +from vyos.compose_config import ComposeConfig +from vyos.compose_config import ComposeConfigError +from vyos.configtree import ConfigTree +from vyos.defaults import directories as default_dir +from vyos.defaults import component_version_json + + +log_file = Path(default_dir['config']).joinpath('vyos-migrate.log') + +class ConfigMigrateError(Exception): + """Raised on error in config migration.""" + +class ConfigMigrate: + # pylint: disable=too-many-instance-attributes + # the number is reasonable in this case + def __init__(self, config_file: str, force=False, + output_file: str = None, checkpoint_file: str = None): + self.config_file: str = config_file + self.force: bool = force + self.system_version: VersionInfo = version_info_from_system() + self.file_version: VersionInfo = version_info_from_file(self.config_file) + self.compose = None + self.output_file = output_file + self.checkpoint_file = checkpoint_file + self.logger = None + self.config_modified = True + + if self.file_version is None: + raise ConfigMigrateError(f'failed to read config file {self.config_file}') + + def migration_needed(self) -> bool: + return self.system_version.component != self.file_version.component + + def release_update_needed(self) -> bool: + return self.system_version.release != self.file_version.release + + def syntax_update_needed(self) -> bool: + return self.system_version.vintage != self.file_version.vintage + + def update_release(self): + """ + Update config file release version. + """ + self.file_version.update_release(self.system_version.release) + + def update_syntax(self): + """ + Update config file syntax. + """ + self.file_version.update_syntax() + + @staticmethod + def normalize_config_body(version_info: VersionInfo): + """ + This is an interim workaround for the cosmetic issue of node + ordering when composing operations on the internal config_tree: + ordering is performed on parsing, hence was maintained in the old + system which would parse/write on each application of a migration + script (~200). Here, we will take the cost of one extra parsing to + reorder before save, for easier review. + """ + if not version_info.config_body_is_none(): + ct = ConfigTree(version_info.config_body) + version_info.update_config_body(ct.to_string()) + + def write_config(self): + if self.output_file is not None: + config_file = self.output_file + else: + config_file = self.config_file + + try: + self.file_version.write(config_file) + except ValueError as e: + raise ConfigMigrateError(f'failed to write {config_file}: {e}') from e + + def init_logger(self): + self.logger = logging.getLogger(__name__) + self.logger.setLevel(logging.DEBUG) + + fh = ConfigMigrate.group_perm_file_handler(log_file, + group='vyattacfg', + mode='w') + fh.setLevel(logging.INFO) + fh_formatter = logging.Formatter('%(message)s') + fh.setFormatter(fh_formatter) + self.logger.addHandler(fh) + ch = logging.StreamHandler() + ch.setLevel(logging.WARNING) + ch_formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s') + ch.setFormatter(ch_formatter) + self.logger.addHandler(ch) + + @staticmethod + def group_perm_file_handler(filename, group=None, mode='a'): + # pylint: disable=consider-using-with + if group is None: + return logging.FileHandler(filename, mode) + gid = getgrnam(group).gr_gid + if not os.path.exists(filename): + open(filename, 'a').close() + os.chown(filename, -1, gid) + os.chmod(filename, 0o664) + return logging.FileHandler(filename, mode) + + @staticmethod + def sort_function(): + """ + Define sort function for migration files as tuples (n, m) for file + n-to-m. + """ + numbers = re.compile(r'(\d+)') + def func(p: Path): + parts = numbers.split(p.stem) + return list(map(int, parts[1::2])) + return func + + @staticmethod + def file_ext(file_path: Path) -> str: + """ + Return an identifier from file name for checkpoint file extension. + """ + return f'{file_path.parent.stem}_{file_path.stem}' + + def run_migration_scripts(self): + """ + Call migration files iteratively. + """ + os.environ['VYOS_MIGRATION'] = '1' + + self.init_logger() + self.logger.info("List of applied migration modules:") + + components = list(self.system_version.component) + components.sort() + + # T4382: 'bgp' needs to follow 'quagga': + if 'bgp' in components and 'quagga' in components: + components.insert(components.index('quagga'), + components.pop(components.index('bgp'))) + + revision: VersionInfo = version_info_copy(self.file_version) + # prune retired, for example, zone-policy + version_info_prune_component(revision, self.system_version) + + migrate_dir = Path(default_dir['migrate']) + sort_func = ConfigMigrate.sort_function() + + for key in components: + p = migrate_dir.joinpath(key) + script_list = list(p.glob('*-to-*')) + script_list = sorted(script_list, key=sort_func) + + if not self.file_version.component_is_none() and not self.force: + start = self.file_version.component.get(key, 0) + script_list = list(filter(lambda x, st=start: sort_func(x)[0] >= st, + script_list)) + + if not script_list: # no applicable migration scripts + revision.update_component(key, self.system_version.component[key]) + continue + + for file in script_list: + f = file.as_posix() + self.logger.info(f'applying {f}') + try: + self.compose.apply_file(f, func_name='migrate') + except ComposeConfigError as e: + self.logger.error(e) + if self.checkpoint_file: + check = f'{self.checkpoint_file}_{ConfigMigrate.file_ext(file)}' + revision.update_config_body(self.compose.to_string()) + ConfigMigrate.normalize_config_body(revision) + revision.write(check) + break + else: + revision.update_component(key, sort_func(file)[1]) + + revision.update_config_body(self.compose.to_string()) + ConfigMigrate.normalize_config_body(revision) + self.file_version = version_info_copy(revision) + + if revision.component != self.system_version.component: + raise ConfigMigrateError(f'incomplete migration: check {log_file} for error') + + del os.environ['VYOS_MIGRATION'] + + def save_json_record(self): + """ + Write component versions to a json file + """ + version_file = component_version_json + + try: + with open(version_file, 'w') as f: + f.write(json.dumps(self.system_version.component, + indent=2, sort_keys=True)) + except OSError: + pass + + def load_config(self): + """ + Instantiate a ComposeConfig object with the config string. + """ + + self.compose = ComposeConfig(self.file_version.config_body, self.checkpoint_file) + + def run(self): + """ + If migration needed, run migration scripts and update config file. + If only release version update needed, update release version. + """ + # save system component versions in json file for reference + self.save_json_record() + + if not self.migration_needed(): + if self.release_update_needed(): + self.update_release() + self.write_config() + else: + self.config_modified = False + return + + if self.syntax_update_needed(): + self.update_syntax() + self.write_config() + + self.load_config() + + self.run_migration_scripts() + + self.update_release() + self.write_config() + + def run_script(self, test_script: str): + """ + Run a single migration script. For testing this simply provides the + body for loading and writing the result; the component string is not + updated. + """ + + self.load_config() + self.init_logger() + + os.environ['VYOS_MIGRATION'] = '1' + + try: + self.compose.apply_file(test_script, func_name='migrate') + except ComposeConfigError as e: + self.logger.error(f'config-migration error in {test_script}: {e}') + else: + self.file_version.update_config_body(self.compose.to_string()) + + del os.environ['VYOS_MIGRATION'] + + self.write_config() diff --git a/python/vyos/nat.py b/python/vyos/nat.py new file mode 100644 index 0000000..5fab3c2 --- /dev/null +++ b/python/vyos/nat.py @@ -0,0 +1,317 @@ +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from vyos.template import is_ip_network +from vyos.utils.dict import dict_search_args +from vyos.template import bracketize_ipv6 + + +def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False): + output = [] + ip_prefix = 'ip6' if ipv6 else 'ip' + log_prefix = ('DST' if nat_type == 'destination' else 'SRC') + f'-NAT-{rule_id}' + log_suffix = '' + + if ipv6: + log_prefix = log_prefix.replace("NAT-", "NAT66-") + + ignore_type_addr = False + translation_str = '' + + if 'inbound_interface' in rule_conf: + operator = '' + if 'name' in rule_conf['inbound_interface']: + iiface = rule_conf['inbound_interface']['name'] + if iiface[0] == '!': + operator = '!=' + iiface = iiface[1:] + output.append(f'iifname {operator} {{{iiface}}}') + else: + iiface = rule_conf['inbound_interface']['group'] + if iiface[0] == '!': + operator = '!=' + iiface = iiface[1:] + output.append(f'iifname {operator} @I_{iiface}') + + if 'outbound_interface' in rule_conf: + operator = '' + if 'name' in rule_conf['outbound_interface']: + oiface = rule_conf['outbound_interface']['name'] + if oiface[0] == '!': + operator = '!=' + oiface = oiface[1:] + output.append(f'oifname {operator} {{{oiface}}}') + else: + oiface = rule_conf['outbound_interface']['group'] + if oiface[0] == '!': + operator = '!=' + oiface = oiface[1:] + output.append(f'oifname {operator} @I_{oiface}') + + if 'protocol' in rule_conf and rule_conf['protocol'] != 'all': + protocol = rule_conf['protocol'] + if protocol == 'tcp_udp': + protocol = '{ tcp, udp }' + output.append(f'meta l4proto {protocol}') + + if 'packet_type' in rule_conf: + output.append(f'pkttype ' + rule_conf['packet_type']) + + if 'exclude' in rule_conf: + translation_str = 'return' + log_suffix = '-EXCL' + elif 'translation' in rule_conf: + addr = dict_search_args(rule_conf, 'translation', 'address') + port = dict_search_args(rule_conf, 'translation', 'port') + if 'redirect' in rule_conf['translation']: + translation_output = [f'redirect'] + redirect_port = dict_search_args(rule_conf, 'translation', 'redirect', 'port') + if redirect_port: + translation_output.append(f'to {redirect_port}') + else: + + translation_prefix = nat_type[:1] + translation_output = [f'{translation_prefix}nat'] + + if addr and is_ip_network(addr): + if not ipv6: + map_addr = dict_search_args(rule_conf, nat_type, 'address') + if map_addr: + if port: + translation_output.append(f'{ip_prefix} prefix to {ip_prefix} {translation_prefix}addr map {{ {map_addr} : {addr} . {port} }}') + else: + translation_output.append(f'{ip_prefix} prefix to {ip_prefix} {translation_prefix}addr map {{ {map_addr} : {addr} }}') + ignore_type_addr = True + else: + translation_output.append(f'prefix to {addr}') + else: + translation_output.append(f'prefix to {addr}') + elif addr == 'masquerade': + if port: + addr = f'{addr} to ' + translation_output = [addr] + log_suffix = '-MASQ' + else: + translation_output.append('to') + if addr: + addr = bracketize_ipv6(addr) + translation_output.append(addr) + + options = [] + addr_mapping = dict_search_args(rule_conf, 'translation', 'options', 'address_mapping') + port_mapping = dict_search_args(rule_conf, 'translation', 'options', 'port_mapping') + if addr_mapping == 'persistent': + options.append('persistent') + if port_mapping and port_mapping != 'none': + options.append(port_mapping) + + if ((not addr) or (addr and not is_ip_network(addr))) and port: + translation_str = " ".join(translation_output) + (f':{port}') + else: + translation_str = " ".join(translation_output) + + if options: + translation_str += f' {",".join(options)}' + + if not ipv6 and 'backend' in rule_conf['load_balance']: + hash_input_items = [] + current_prob = 0 + nat_map = [] + + for trans_addr, addr in rule_conf['load_balance']['backend'].items(): + item_prob = int(addr['weight']) + upper_limit = current_prob + item_prob - 1 + hash_val = str(current_prob) + '-' + str(upper_limit) + element = hash_val + " : " + trans_addr + nat_map.append(element) + current_prob = current_prob + item_prob + + elements = ' , '.join(nat_map) + + if 'hash' in rule_conf['load_balance'] and 'random' in rule_conf['load_balance']['hash']: + translation_str += ' numgen random mod 100 map ' + '{ ' + f'{elements}' + ' }' + else: + for input_param in rule_conf['load_balance']['hash']: + if input_param == 'source-address': + param = 'ip saddr' + elif input_param == 'destination-address': + param = 'ip daddr' + elif input_param == 'source-port': + prot = rule_conf['protocol'] + param = f'{prot} sport' + elif input_param == 'destination-port': + prot = rule_conf['protocol'] + param = f'{prot} dport' + hash_input_items.append(param) + hash_input = ' . '.join(hash_input_items) + translation_str += f' jhash ' + f'{hash_input}' + ' mod 100 map ' + '{ ' + f'{elements}' + ' }' + + for target in ['source', 'destination']: + if target not in rule_conf: + continue + + side_conf = rule_conf[target] + prefix = target[:1] + + addr = dict_search_args(side_conf, 'address') + if addr and not (ignore_type_addr and target == nat_type): + operator = '' + if addr[:1] == '!': + operator = '!=' + addr = addr[1:] + output.append(f'{ip_prefix} {prefix}addr {operator} {addr}') + + addr_prefix = dict_search_args(side_conf, 'prefix') + if addr_prefix and ipv6: + operator = '' + if addr_prefix[:1] == '!': + operator = '!=' + addr_prefix = addr_prefix[1:] + output.append(f'ip6 {prefix}addr {operator} {addr_prefix}') + + port = dict_search_args(side_conf, 'port') + if port: + protocol = rule_conf['protocol'] + if protocol == 'tcp_udp': + protocol = 'th' + operator = '' + if port[:1] == '!': + operator = '!=' + port = port[1:] + output.append(f'{protocol} {prefix}port {operator} {{ {port} }}') + + if 'group' in side_conf: + group = side_conf['group'] + if 'address_group' in group and not (ignore_type_addr and target == nat_type): + group_name = group['address_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + if ipv6: + output.append(f'{ip_prefix} {prefix}addr {operator} @A6_{group_name}') + else: + output.append(f'{ip_prefix} {prefix}addr {operator} @A_{group_name}') + # Generate firewall group domain-group + elif 'domain_group' in group and not (ignore_type_addr and target == nat_type): + group_name = group['domain_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_prefix} {prefix}addr {operator} @D_{group_name}') + elif 'network_group' in group and not (ignore_type_addr and target == nat_type): + group_name = group['network_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + if ipv6: + output.append(f'{ip_prefix} {prefix}addr {operator} @N6_{group_name}') + else: + output.append(f'{ip_prefix} {prefix}addr {operator} @N_{group_name}') + if 'mac_group' in group: + group_name = group['mac_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'ether {prefix}addr {operator} @M_{group_name}') + if 'port_group' in group: + proto = rule_conf['protocol'] + group_name = group['port_group'] + + if proto == 'tcp_udp': + proto = 'th' + + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + + output.append(f'{proto} {prefix}port {operator} @P_{group_name}') + + output.append('counter') + + if 'log' in rule_conf: + output.append(f'log prefix "[{log_prefix}{log_suffix}]"') + + if translation_str: + output.append(translation_str) + + output.append(f'comment "{log_prefix}"') + + return " ".join(output) + +def parse_nat_static_rule(rule_conf, rule_id, nat_type): + output = [] + log_prefix = ('STATIC-DST' if nat_type == 'destination' else 'STATIC-SRC') + f'-NAT-{rule_id}' + log_suffix = '' + + ignore_type_addr = False + translation_str = '' + + if 'inbound_interface' in rule_conf: + ifname = rule_conf['inbound_interface'] + ifprefix = 'i' if nat_type == 'destination' else 'o' + if ifname != 'any': + output.append(f'{ifprefix}ifname "{ifname}"') + + if 'exclude' in rule_conf: + translation_str = 'return' + log_suffix = '-EXCL' + elif 'translation' in rule_conf: + translation_prefix = nat_type[:1] + translation_output = [f'{translation_prefix}nat'] + addr = dict_search_args(rule_conf, 'translation', 'address') + map_addr = dict_search_args(rule_conf, 'destination', 'address') + + if nat_type == 'source': + addr, map_addr = map_addr, addr # Swap + + if addr and is_ip_network(addr): + translation_output.append(f'ip prefix to ip {translation_prefix}addr map {{ {map_addr} : {addr} }}') + ignore_type_addr = True + elif addr: + translation_output.append(f'to {addr}') + + options = [] + addr_mapping = dict_search_args(rule_conf, 'translation', 'options', 'address_mapping') + port_mapping = dict_search_args(rule_conf, 'translation', 'options', 'port_mapping') + if addr_mapping == 'persistent': + options.append('persistent') + if port_mapping and port_mapping != 'none': + options.append(port_mapping) + + if options: + translation_output.append(",".join(options)) + + translation_str = " ".join(translation_output) + + prefix = nat_type[:1] + addr = dict_search_args(rule_conf, 'translation' if nat_type == 'source' else nat_type, 'address') + if addr and not ignore_type_addr: + output.append(f'ip {prefix}addr {addr}') + + output.append('counter') + + if 'log' in rule_conf: + output.append(f'log prefix "[{log_prefix}{log_suffix}]"') + + if translation_str: + output.append(translation_str) + + output.append(f'comment "{log_prefix}"') + + return " ".join(output) diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py new file mode 100644 index 0000000..066c805 --- /dev/null +++ b/python/vyos/opmode.py @@ -0,0 +1,285 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import re +import sys +import typing +from humps import decamelize + + +class Error(Exception): + """ Any error that makes requested operation impossible to complete + for reasons unrelated to the user input or script logic. + + This is the base class, scripts should not use it directly + and should raise more specific errors instead, + whenever possible. + """ + pass + +class UnconfiguredSubsystem(Error): + """ Requested operation is valid, but cannot be completed + because corresponding subsystem is not configured + and thus is not running. + """ + pass + +class UnconfiguredObject(UnconfiguredSubsystem): + """ Requested operation is valid but cannot be completed + because its parameter refers to an object that does not exist + in the system configuration. + """ + pass + +class DataUnavailable(Error): + """ Requested operation is valid, but cannot be completed + because data for it is not available. + This error MAY be treated as temporary because such issues + are often caused by transient events such as service restarts. + """ + pass + +class PermissionDenied(Error): + """ Requested operation is valid, but the caller has no permission + to perform it. + """ + pass + +class InsufficientResources(Error): + """ Requested operation and its arguments are valid but the system + does not have enough resources (such as drive space or memory) + to complete it. + """ + pass + +class UnsupportedOperation(Error): + """ Requested operation is technically valid but is not implemented yet. """ + pass + +class IncorrectValue(Error): + """ Requested operation is valid, but an argument provided has an + incorrect value, preventing successful completion. + """ + pass + +class CommitInProgress(Error): + """ Requested operation is valid, but not possible at the time due + to a commit being in progress. + """ + pass + +class InternalError(Error): + """ Any situation when VyOS detects that it could not perform + an operation correctly due to logic errors in its own code + or errors in underlying software. + """ + pass + + +def _is_op_mode_function_name(name): + if re.match(r"^(show|clear|reset|restart|add|update|delete|generate|set|renew|release|execute)", name): + return True + else: + return False + +def _capture_output(name): + if re.match(r"^(show|generate)", name): + return True + else: + return False + +def _get_op_mode_functions(module): + from inspect import getmembers, isfunction + + # Get all functions in that module + funcs = getmembers(module, isfunction) + + # getmembers returns (name, func) tuples + funcs = list(filter(lambda ft: _is_op_mode_function_name(ft[0]), funcs)) + + funcs_dict = {} + for (name, thunk) in funcs: + funcs_dict[name] = thunk + + return funcs_dict + +def _is_optional_type(t): + # Optional[t] is internally an alias for Union[t, NoneType] + # and there's no easy way to get union members it seems + if (type(t) == typing._UnionGenericAlias): + if (len(t.__args__) == 2): + if t.__args__[1] == type(None): + return True + + return False + +def _get_arg_type(t): + """ Returns the type itself if it's a primitive type, + or the "real" type of typing.Optional + + Doesn't work with anything else at the moment! + """ + if _is_optional_type(t): + return t.__args__[0] + else: + return t + +def _is_literal_type(t): + if _is_optional_type(t): + t = _get_arg_type(t) + + if typing.get_origin(t) == typing.Literal: + return True + + return False + +def _get_literal_values(t): + """ Returns the tuple of allowed values for a Literal type + """ + if not _is_literal_type(t): + return tuple() + if _is_optional_type(t): + t = _get_arg_type(t) + + return typing.get_args(t) + +def _normalize_field_name(name): + # Convert the name to string if it is not + # (in some cases they may be numbers) + name = str(name) + + # Replace all separators with underscores + name = re.sub(r'(\s|[\(\)\[\]\{\}\-\.\,:\"\'\`])+', '_', name) + + # Replace specific characters with textual descriptions + name = re.sub(r'@', '_at_', name) + name = re.sub(r'%', '_percentage_', name) + name = re.sub(r'~', '_tilde_', name) + + # Force all letters to lowercase + name = name.lower() + + # Remove leading and trailing underscores, if any + name = re.sub(r'(^(_+)(?=[^_])|_+$)', '', name) + + # Ensure there are only single underscores + name = re.sub(r'_+', '_', name) + + return name + +def _normalize_dict_field_names(old_dict): + new_dict = {} + + for key in old_dict: + new_key = _normalize_field_name(key) + new_dict[new_key] = _normalize_field_names(old_dict[key]) + + # Sanity check + if len(old_dict) != len(new_dict): + raise InternalError("Dictionary fields do not allow unique normalization") + else: + return new_dict + +def _normalize_field_names(value): + if isinstance(value, dict): + return _normalize_dict_field_names(value) + elif isinstance(value, list): + return list(map(lambda v: _normalize_field_names(v), value)) + else: + return value + +def run(module): + from argparse import ArgumentParser + + functions = _get_op_mode_functions(module) + + parser = ArgumentParser() + subparsers = parser.add_subparsers(dest="subcommand") + + for function_name in functions: + subparser = subparsers.add_parser(function_name, help=functions[function_name].__doc__) + + type_hints = typing.get_type_hints(functions[function_name]) + if 'return' in type_hints: + del type_hints['return'] + for opt in type_hints: + th = type_hints[opt] + + # Function argument names use underscores as separators + # but command-line options should use hyphens + # Without this, we'd get options like "--foo_bar" + opt = re.sub(r'_', '-', opt) + + if _get_arg_type(th) == bool: + subparser.add_argument(f"--{opt}", action='store_true') + else: + if _is_optional_type(th): + if _is_literal_type(th): + subparser.add_argument(f"--{opt}", + choices=list(_get_literal_values(th)), + default=None) + else: + subparser.add_argument(f"--{opt}", + type=_get_arg_type(th), default=None) + else: + if _is_literal_type(th): + subparser.add_argument(f"--{opt}", + choices=list(_get_literal_values(th)), + required=True) + else: + subparser.add_argument(f"--{opt}", + type=_get_arg_type(th), required=True) + + # Get options as a dict rather than a namespace, + # so that we can modify it and pack for passing to functions + args = vars(parser.parse_args()) + + if not args["subcommand"]: + print("Subcommand required!") + parser.print_usage() + sys.exit(1) + + function_name = args["subcommand"] + func = functions[function_name] + + # Remove the subcommand from the arguments, + # it would cause an extra argument error when we pass the dict to a function + del args["subcommand"] + + # Show and generate commands must always get the "raw" argument, + # but other commands (clear/reset/restart/add/delete) should not, + # because they produce no output and it makes no sense for them. + if ("raw" not in args) and _capture_output(function_name): + args["raw"] = False + + if _capture_output(function_name): + # Show and generate commands are slightly special: + # they may return human-formatted output + # or a raw dict that we need to serialize in JSON for printing + res = func(**args) + if not args["raw"]: + return res + else: + if not isinstance(res, dict) and not isinstance(res, list): + raise InternalError(f"Bare literal is not an acceptable raw output, must be a list or an object.\ + The output was:{res}") + res = decamelize(res) + res = _normalize_field_names(res) + from json import dumps + return dumps(res, indent=4) + else: + # Other functions should not return anything, + # although they may print their own warnings or status messages + func(**args) diff --git a/python/vyos/pki.py b/python/vyos/pki.py new file mode 100644 index 0000000..5a0e2dd --- /dev/null +++ b/python/vyos/pki.py @@ -0,0 +1,453 @@ +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import datetime +import ipaddress + +from cryptography import x509 +from cryptography.exceptions import InvalidSignature +from cryptography.x509.extensions import ExtensionNotFound +from cryptography.x509.oid import NameOID +from cryptography.x509.oid import ExtendedKeyUsageOID +from cryptography.x509.oid import ExtensionOID +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import dh +from cryptography.hazmat.primitives.asymmetric import dsa +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric import rsa + +CERT_BEGIN='-----BEGIN CERTIFICATE-----\n' +CERT_END='\n-----END CERTIFICATE-----' +KEY_BEGIN='-----BEGIN PRIVATE KEY-----\n' +KEY_END='\n-----END PRIVATE KEY-----' +KEY_ENC_BEGIN='-----BEGIN ENCRYPTED PRIVATE KEY-----\n' +KEY_ENC_END='\n-----END ENCRYPTED PRIVATE KEY-----' +KEY_PUB_BEGIN='-----BEGIN PUBLIC KEY-----\n' +KEY_PUB_END='\n-----END PUBLIC KEY-----' +CRL_BEGIN='-----BEGIN X509 CRL-----\n' +CRL_END='\n-----END X509 CRL-----' +CSR_BEGIN='-----BEGIN CERTIFICATE REQUEST-----\n' +CSR_END='\n-----END CERTIFICATE REQUEST-----' +DH_BEGIN='-----BEGIN DH PARAMETERS-----\n' +DH_END='\n-----END DH PARAMETERS-----' +OVPN_BEGIN = '-----BEGIN OpenVPN Static key V{0}-----\n' +OVPN_END = '\n-----END OpenVPN Static key V{0}-----' +OPENSSH_KEY_BEGIN='-----BEGIN OPENSSH PRIVATE KEY-----\n' +OPENSSH_KEY_END='\n-----END OPENSSH PRIVATE KEY-----' + +# Print functions + +encoding_map = { + 'PEM': serialization.Encoding.PEM, + 'OpenSSH': serialization.Encoding.OpenSSH +} + +public_format_map = { + 'SubjectPublicKeyInfo': serialization.PublicFormat.SubjectPublicKeyInfo, + 'OpenSSH': serialization.PublicFormat.OpenSSH +} + +private_format_map = { + 'PKCS8': serialization.PrivateFormat.PKCS8, + 'OpenSSH': serialization.PrivateFormat.OpenSSH +} + +hash_map = { + 'sha256': hashes.SHA256, + 'sha384': hashes.SHA384, + 'sha512': hashes.SHA512, +} + +def get_certificate_fingerprint(cert, hash): + hash_algorithm = hash_map[hash]() + fp = cert.fingerprint(hash_algorithm) + + return fp.hex(':').upper() + +def encode_certificate(cert): + return cert.public_bytes(encoding=serialization.Encoding.PEM).decode('utf-8') + +def encode_public_key(cert, encoding='PEM', key_format='SubjectPublicKeyInfo'): + if encoding not in encoding_map: + encoding = 'PEM' + if key_format not in public_format_map: + key_format = 'SubjectPublicKeyInfo' + return cert.public_bytes( + encoding=encoding_map[encoding], + format=public_format_map[key_format]).decode('utf-8') + +def encode_private_key(private_key, encoding='PEM', key_format='PKCS8', passphrase=None): + if encoding not in encoding_map: + encoding = 'PEM' + if key_format not in private_format_map: + key_format = 'PKCS8' + encryption = serialization.NoEncryption() if not passphrase else serialization.BestAvailableEncryption(bytes(passphrase, 'utf-8')) + return private_key.private_bytes( + encoding=encoding_map[encoding], + format=private_format_map[key_format], + encryption_algorithm=encryption).decode('utf-8') + +def encode_dh_parameters(dh_parameters): + return dh_parameters.parameter_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.ParameterFormat.PKCS3).decode('utf-8') + +# EC Helper + +def get_elliptic_curve(size): + curve_func = None + name = f'SECP{size}R1' + if hasattr(ec, name): + curve_func = getattr(ec, name) + else: + curve_func = ec.SECP256R1() # Default to SECP256R1 + return curve_func() + +# Creation functions + +def create_private_key(key_type, key_size=None): + private_key = None + if key_type == 'rsa': + private_key = rsa.generate_private_key(public_exponent=65537, key_size=key_size) + elif key_type == 'dsa': + private_key = dsa.generate_private_key(key_size=key_size) + elif key_type == 'ec': + curve = get_elliptic_curve(key_size) + private_key = ec.generate_private_key(curve) + return private_key + +def create_certificate_request(subject, private_key, subject_alt_names=[]): + subject_obj = x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, subject['country']), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, subject['state']), + x509.NameAttribute(NameOID.LOCALITY_NAME, subject['locality']), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, subject['organization']), + x509.NameAttribute(NameOID.COMMON_NAME, subject['common_name'])]) + + builder = x509.CertificateSigningRequestBuilder() \ + .subject_name(subject_obj) + + if subject_alt_names: + alt_names = [] + for obj in subject_alt_names: + if isinstance(obj, ipaddress.IPv4Address) or isinstance(obj, ipaddress.IPv6Address): + alt_names.append(x509.IPAddress(obj)) + elif isinstance(obj, str): + alt_names.append(x509.RFC822Name(obj) if '@' in obj else x509.DNSName(obj)) + if alt_names: + builder = builder.add_extension(x509.SubjectAlternativeName(alt_names), critical=False) + + return builder.sign(private_key, hashes.SHA256()) + +def add_key_identifier(ca_cert): + try: + ski_ext = ca_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) + return x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ski_ext.value) + except: + return x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_cert.public_key()) + +def create_certificate(cert_req, ca_cert, ca_private_key, valid_days=365, cert_type='server', is_ca=False, is_sub_ca=False): + ext_key_usage = [] + if is_ca: + ext_key_usage = [ExtendedKeyUsageOID.CLIENT_AUTH, ExtendedKeyUsageOID.SERVER_AUTH] + elif cert_type == 'client': + ext_key_usage = [ExtendedKeyUsageOID.CLIENT_AUTH] + elif cert_type == 'server': + ext_key_usage = [ExtendedKeyUsageOID.SERVER_AUTH] + + builder = x509.CertificateBuilder() \ + .subject_name(cert_req.subject) \ + .issuer_name(ca_cert.subject) \ + .public_key(cert_req.public_key()) \ + .serial_number(x509.random_serial_number()) \ + .not_valid_before(datetime.datetime.utcnow()) \ + .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=int(valid_days))) + + builder = builder.add_extension(x509.BasicConstraints(ca=is_ca, path_length=0 if is_sub_ca else None), critical=True) + builder = builder.add_extension(x509.KeyUsage( + digital_signature=True, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=is_ca, + crl_sign=is_ca, + encipher_only=False, + decipher_only=False), critical=True) + builder = builder.add_extension(x509.ExtendedKeyUsage(ext_key_usage), critical=False) + builder = builder.add_extension(x509.SubjectKeyIdentifier.from_public_key(cert_req.public_key()), critical=False) + + if not is_ca or is_sub_ca: + builder = builder.add_extension(add_key_identifier(ca_cert), critical=False) + + for ext in cert_req.extensions: + builder = builder.add_extension(ext.value, critical=False) + + return builder.sign(ca_private_key, hashes.SHA256()) + +def create_certificate_revocation_list(ca_cert, ca_private_key, serial_numbers=[]): + if not serial_numbers: + return False + + builder = x509.CertificateRevocationListBuilder() \ + .issuer_name(ca_cert.subject) \ + .last_update(datetime.datetime.today()) \ + .next_update(datetime.datetime.today() + datetime.timedelta(1, 0, 0)) + + for serial_number in serial_numbers: + revoked_cert = x509.RevokedCertificateBuilder() \ + .serial_number(serial_number) \ + .revocation_date(datetime.datetime.today()) \ + .build() + builder = builder.add_revoked_certificate(revoked_cert) + + return builder.sign(private_key=ca_private_key, algorithm=hashes.SHA256()) + +def create_dh_parameters(bits=2048): + if not bits or bits < 512: + print("Invalid DH parameter key size") + return False + + return dh.generate_parameters(generator=2, key_size=int(bits)) + +# Wrap functions + +def wrap_public_key(raw_data): + return KEY_PUB_BEGIN + raw_data + KEY_PUB_END + +def wrap_private_key(raw_data, passphrase=None): + return (KEY_ENC_BEGIN if passphrase else KEY_BEGIN) + raw_data + (KEY_ENC_END if passphrase else KEY_END) + +def wrap_openssh_public_key(raw_data, type): + return f'{type} {raw_data}' + +def wrap_openssh_private_key(raw_data): + return OPENSSH_KEY_BEGIN + raw_data + OPENSSH_KEY_END + +def wrap_certificate_request(raw_data): + return CSR_BEGIN + raw_data + CSR_END + +def wrap_certificate(raw_data): + return CERT_BEGIN + raw_data + CERT_END + +def wrap_crl(raw_data): + return CRL_BEGIN + raw_data + CRL_END + +def wrap_dh_parameters(raw_data): + return DH_BEGIN + raw_data + DH_END + +def wrap_openvpn_key(raw_data, version='1'): + return OVPN_BEGIN.format(version) + raw_data + OVPN_END.format(version) + +# Load functions +def load_public_key(raw_data, wrap_tags=True): + if wrap_tags: + raw_data = wrap_public_key(raw_data) + + try: + return serialization.load_pem_public_key(bytes(raw_data, 'utf-8')) + except ValueError: + return False + +def load_private_key(raw_data, passphrase=None, wrap_tags=True): + if wrap_tags: + raw_data = wrap_private_key(raw_data, passphrase) + + if passphrase is not None: + passphrase = bytes(passphrase, 'utf-8') + + try: + return serialization.load_pem_private_key(bytes(raw_data, 'utf-8'), password=passphrase) + except (ValueError, TypeError): + return False + +def load_openssh_public_key(raw_data, type): + try: + return serialization.load_ssh_public_key(bytes(f'{type} {raw_data}', 'utf-8')) + except ValueError: + return False + +def load_openssh_private_key(raw_data, passphrase=None, wrap_tags=True): + if wrap_tags: + raw_data = wrap_openssh_private_key(raw_data) + + try: + return serialization.load_ssh_private_key(bytes(raw_data, 'utf-8'), password=passphrase) + except ValueError: + return False + +def load_certificate_request(raw_data, wrap_tags=True): + if wrap_tags: + raw_data = wrap_certificate_request(raw_data) + + try: + return x509.load_pem_x509_csr(bytes(raw_data, 'utf-8')) + except ValueError: + return False + +def load_certificate(raw_data, wrap_tags=True): + if wrap_tags: + raw_data = wrap_certificate(raw_data) + + try: + return x509.load_pem_x509_certificate(bytes(raw_data, 'utf-8')) + except ValueError: + return False + +def load_crl(raw_data, wrap_tags=True): + if wrap_tags: + raw_data = wrap_crl(raw_data) + + try: + return x509.load_pem_x509_crl(bytes(raw_data, 'utf-8')) + except ValueError: + return False + +def load_dh_parameters(raw_data, wrap_tags=True): + if wrap_tags: + raw_data = wrap_dh_parameters(raw_data) + + try: + return serialization.load_pem_parameters(bytes(raw_data, 'utf-8')) + except ValueError: + return False + +# Verify + +def is_ca_certificate(cert): + if not cert: + return False + + try: + ext = cert.extensions.get_extension_for_oid(ExtensionOID.BASIC_CONSTRAINTS) + return ext.value.ca + except ExtensionNotFound: + return False + +def verify_certificate(cert, ca_cert): + # Verify certificate was signed by specified CA + if ca_cert.subject != cert.issuer: + return False + + ca_public_key = ca_cert.public_key() + try: + if isinstance(ca_public_key, rsa.RSAPublicKeyWithSerialization): + ca_public_key.verify( + cert.signature, + cert.tbs_certificate_bytes, + padding=padding.PKCS1v15(), + algorithm=cert.signature_hash_algorithm) + elif isinstance(ca_public_key, dsa.DSAPublicKeyWithSerialization): + ca_public_key.verify( + cert.signature, + cert.tbs_certificate_bytes, + algorithm=cert.signature_hash_algorithm) + elif isinstance(ca_public_key, ec.EllipticCurvePublicKeyWithSerialization): + ca_public_key.verify( + cert.signature, + cert.tbs_certificate_bytes, + signature_algorithm=ec.ECDSA(cert.signature_hash_algorithm)) + else: + return False # We cannot verify it + return True + except InvalidSignature: + return False + +def verify_crl(crl, ca_cert): + # Verify CRL was signed by specified CA + if ca_cert.subject != crl.issuer: + return False + + ca_public_key = ca_cert.public_key() + try: + if isinstance(ca_public_key, rsa.RSAPublicKeyWithSerialization): + ca_public_key.verify( + crl.signature, + crl.tbs_certlist_bytes, + padding=padding.PKCS1v15(), + algorithm=crl.signature_hash_algorithm) + elif isinstance(ca_public_key, dsa.DSAPublicKeyWithSerialization): + ca_public_key.verify( + crl.signature, + crl.tbs_certlist_bytes, + algorithm=crl.signature_hash_algorithm) + elif isinstance(ca_public_key, ec.EllipticCurvePublicKeyWithSerialization): + ca_public_key.verify( + crl.signature, + crl.tbs_certlist_bytes, + signature_algorithm=ec.ECDSA(crl.signature_hash_algorithm)) + else: + return False # We cannot verify it + return True + except InvalidSignature: + return False + +def verify_ca_chain(sorted_names, pki_node): + if len(sorted_names) == 1: # Single cert, no chain + return True + + for name in sorted_names: + cert = load_certificate(pki_node[name]['certificate']) + verified = False + for ca_name in sorted_names: + if name == ca_name: + continue + ca_cert = load_certificate(pki_node[ca_name]['certificate']) + if verify_certificate(cert, ca_cert): + verified = True + break + if not verified and name != sorted_names[-1]: + # Only permit top-most certificate to fail verify (e.g. signed by public CA not explicitly in chain) + return False + return True + +# Certificate chain + +def find_parent(cert, ca_certs): + for ca_cert in ca_certs: + if verify_certificate(cert, ca_cert): + return ca_cert + return None + +def find_chain(cert, ca_certs): + remaining = ca_certs.copy() + chain = [cert] + + while remaining: + parent = find_parent(chain[-1], remaining) + if parent is None: + # No parent in the list of remaining certificates or there's a circular dependency + break + elif parent == chain[-1]: + # Self-signed: must be root CA (end of chain) + break + else: + remaining.remove(parent) + chain.append(parent) + + return chain + +def sort_ca_chain(ca_names, pki_node): + def ca_cmp(ca_name1, ca_name2, pki_node): + cert1 = load_certificate(pki_node[ca_name1]['certificate']) + cert2 = load_certificate(pki_node[ca_name2]['certificate']) + + if verify_certificate(cert1, cert2): # cert1 is child of cert2 + return -1 + return 1 + + from functools import cmp_to_key + return sorted(ca_names, key=cmp_to_key(lambda cert1, cert2: ca_cmp(cert1, cert2, pki_node))) diff --git a/python/vyos/priority.py b/python/vyos/priority.py new file mode 100644 index 0000000..ab4e6d4 --- /dev/null +++ b/python/vyos/priority.py @@ -0,0 +1,75 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from pathlib import Path +from typing import List + +from vyos.xml_ref import load_reference +from vyos.base import Warning as Warn + +def priority_data(d: dict) -> list: + def func(d, path, res, hier): + for k,v in d.items(): + if not 'node_data' in v: + continue + subpath = path + [k] + hier_prio = hier + data = v.get('node_data') + o = data.get('owner') + p = data.get('priority') + # a few interface-definitions have priority preceding owner + # attribute, instead of within properties; pass in descent + if p is not None and o is None: + hier_prio = p + if o is not None and p is None: + p = hier_prio + if o is not None and p is not None: + o = Path(o.split()[0]).name + p = int(p) + res.append((subpath, o, p)) + if isinstance(v, dict): + func(v, subpath, res, hier_prio) + return res + ret = func(d, [], [], 0) + ret = sorted(ret, key=lambda x: x[0]) + ret = sorted(ret, key=lambda x: x[2]) + return ret + +def get_priority_data() -> list: + xml = load_reference() + return priority_data(xml.ref) + +def priority_sort(sections: List[list[str]] = None, + owners: List[str] = None, + reverse=False) -> List: + if sections is not None: + index = 0 + collection: List = sections + elif owners is not None: + index = 1 + collection = owners + else: + raise ValueError('one of sections or owners is required') + + l = get_priority_data() + m = [item for item in l if item[index] in collection] + n = sorted(m, key=lambda x: x[2], reverse=reverse) + o = [item[index] for item in n] + # sections are unhashable; use comprehension + missed = [j for j in collection if j not in o] + if missed: + Warn(f'No priority available for elements {missed}') + + return o diff --git a/python/vyos/progressbar.py b/python/vyos/progressbar.py new file mode 100644 index 0000000..8d10426 --- /dev/null +++ b/python/vyos/progressbar.py @@ -0,0 +1,77 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import math +import os +import signal +import subprocess + +from vyos.utils.io import is_dumb_terminal +from vyos.utils.io import print_error + +class Progressbar: + def __init__(self, step=None): + self.total = 0.0 + self.step = step + # Silently ignore all calls if terminal capabilities are lacking. + # This will also prevent the output from littering Ansible logs, + # as `ansible.netcommon.network_cli' coaxes the terminal into believing + # it is interactive. + self._dumb = is_dumb_terminal() + def __enter__(self): + if not self._dumb: + # Recalculate terminal width with every window resize. + signal.signal(signal.SIGWINCH, lambda signum, frame: self._update_cols()) + # Disable line wrapping to prevent the staircase effect. + subprocess.run(['tput', 'rmam'], check=False) + self._update_cols() + # Print an empty progressbar with entry. + self.progress(0, 1) + return self + def __exit__(self, exc_type, kexc_val, exc_tb): + if not self._dumb: + # Revert to the default SIGWINCH handler (ie nothing). + signal.signal(signal.SIGWINCH, signal.SIG_DFL) + # Reenable line wrapping. + subprocess.run(['tput', 'smam'], check=False) + def _update_cols(self): + # `os.get_terminal_size()' is fast enough for our purposes. + self.col = max(os.get_terminal_size().columns - 15, 20) + def increment(self): + """ + Stateful progressbar taking the step fraction at init and no input at + callback (for FTP) + """ + if self.step: + if self.total < 1.0: + self.total += self.step + if self.total >= 1.0: + self.total = 1.0 + # Ignore superfluous calls caused by fuzzy FTP size calculations. + self.step = None + self.progress(self.total, 1.0) + def progress(self, done, total): + """ + Stateless progressbar taking no input at init and current progress with + final size at callback (for SSH) + """ + if done <= total and not self._dumb: + length = math.ceil(self.col * done / total) + percentage = str(math.ceil(100 * done / total)).rjust(3) + # Carriage return at the end will make sure the line will get overwritten. + print_error(f'[{length * "#"}{(self.col - length) * "_"}] {percentage}%', end='\r') + # Print a newline to make sure the full progressbar doesn't get overwritten by the next line. + if done == total: + print_error() diff --git a/python/vyos/qos/__init__.py b/python/vyos/qos/__init__.py new file mode 100644 index 0000000..a2980cc --- /dev/null +++ b/python/vyos/qos/__init__.py @@ -0,0 +1,28 @@ +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.qos.base import QoSBase +from vyos.qos.cake import CAKE +from vyos.qos.droptail import DropTail +from vyos.qos.fairqueue import FairQueue +from vyos.qos.fqcodel import FQCodel +from vyos.qos.limiter import Limiter +from vyos.qos.netem import NetEm +from vyos.qos.priority import Priority +from vyos.qos.randomdetect import RandomDetect +from vyos.qos.ratelimiter import RateLimiter +from vyos.qos.roundrobin import RoundRobin +from vyos.qos.trafficshaper import TrafficShaper +from vyos.qos.trafficshaper import TrafficShaperHFSC diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py new file mode 100644 index 0000000..98e486e --- /dev/null +++ b/python/vyos/qos/base.py @@ -0,0 +1,440 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +import jmespath + +from vyos.base import Warning +from vyos.utils.process import cmd +from vyos.utils.dict import dict_search +from vyos.utils.file import read_file + +from vyos.utils.network import get_protocol_by_name + + +class QoSBase: + _debug = False + _direction = ['egress'] + _parent = 0xffff + _dsfields = { + "default": 0x0, + "lowdelay": 0x10, + "throughput": 0x08, + "reliability": 0x04, + "mincost": 0x02, + "priority": 0x20, + "immediate": 0x40, + "flash": 0x60, + "flash-override": 0x80, + "critical": 0x0A, + "internet": 0xC0, + "network": 0xE0, + "AF11": 0x28, + "AF12": 0x30, + "AF13": 0x38, + "AF21": 0x48, + "AF22": 0x50, + "AF23": 0x58, + "AF31": 0x68, + "AF32": 0x70, + "AF33": 0x78, + "AF41": 0x88, + "AF42": 0x90, + "AF43": 0x98, + "CS1": 0x20, + "CS2": 0x40, + "CS3": 0x60, + "CS4": 0x80, + "CS5": 0xA0, + "CS6": 0xC0, + "CS7": 0xE0, + "EF": 0xB8 + } + qostype = None + + def __init__(self, interface): + if os.path.exists('/tmp/vyos.qos.debug'): + self._debug = True + self._interface = interface + + def _cmd(self, command): + if self._debug: + print(f'DEBUG/QoS: {command}') + return cmd(command) + + def get_direction(self) -> list: + return self._direction + + def _get_class_max_id(self, config) -> int: + if 'class' in config: + tmp = list(config['class'].keys()) + tmp.sort(key=lambda ii: int(ii)) + return tmp[-1] + return None + + def _get_dsfield(self, value): + if value in self._dsfields: + return self._dsfields[value] + else: + return value + + def _calc_random_detect_queue_params(self, avg_pkt, max_thr, limit=None, min_thr=None, + mark_probability=None, precedence=0): + params = dict() + avg_pkt = int(avg_pkt) + max_thr = int(max_thr) + mark_probability = int(mark_probability) + limit = int(limit) if limit else 4 * max_thr + min_thr = int(min_thr) if min_thr else ((9 + precedence) * max_thr) // 18 + + params['avg_pkt'] = avg_pkt + params['limit'] = limit * avg_pkt + params['min_val'] = min_thr * avg_pkt + params['max_val'] = max_thr * avg_pkt + params['burst'] = (2 * min_thr + max_thr) // 3 + params['probability'] = 1 / mark_probability + + return params + + def _build_base_qdisc(self, config : dict, cls_id : int): + """ + Add/replace qdisc for every class (also default is a class). This is + a genetic method which need an implementation "per" queue-type. + + This matches the old mapping as defined in Perl here: + https://github.com/vyos/vyatta-cfg-qos/blob/equuleus/lib/Vyatta/Qos/ShaperClass.pm#L223-L229 + """ + queue_type = dict_search('queue_type', config) + default_tc = f'tc qdisc replace dev {self._interface} parent {self._parent}:{cls_id:x}' + + if queue_type == 'priority': + handle = 0x4000 + cls_id + default_tc += f' handle {handle:x}: prio' + self._cmd(default_tc) + + queue_limit = dict_search('queue_limit', config) + for ii in range(1, 4): + tmp = f'tc qdisc replace dev {self._interface} parent {handle:x}:{ii:x} pfifo' + if queue_limit: tmp += f' limit {queue_limit}' + self._cmd(tmp) + + elif queue_type == 'fair-queue': + default_tc += f' sfq' + + tmp = dict_search('queue_limit', config) + if tmp: default_tc += f' limit {tmp}' + + self._cmd(default_tc) + + elif queue_type == 'fq-codel': + default_tc += f' fq_codel' + tmp = dict_search('codel_quantum', config) + if tmp: default_tc += f' quantum {tmp}' + + tmp = dict_search('flows', config) + if tmp: default_tc += f' flows {tmp}' + + tmp = dict_search('interval', config) + if tmp: default_tc += f' interval {tmp}ms' + + tmp = dict_search('queue_limit', config) + if tmp: default_tc += f' limit {tmp}' + + tmp = dict_search('target', config) + if tmp: default_tc += f' target {tmp}ms' + + default_tc += f' noecn' + + self._cmd(default_tc) + + elif queue_type == 'random-detect': + default_tc += f' red' + + qparams = self._calc_random_detect_queue_params( + avg_pkt=dict_search('average_packet', config), + max_thr=dict_search('maximum_threshold', config), + limit=dict_search('queue_limit', config), + min_thr=dict_search('minimum_threshold', config), + mark_probability=dict_search('mark_probability', config) + ) + + default_tc += f' limit {qparams["limit"]} avpkt {qparams["avg_pkt"]}' + default_tc += f' max {qparams["max_val"]} min {qparams["min_val"]}' + default_tc += f' burst {qparams["burst"]} probability {qparams["probability"]}' + + self._cmd(default_tc) + + elif queue_type == 'drop-tail': + default_tc += f' pfifo' + + tmp = dict_search('queue_limit', config) + if tmp: default_tc += f' limit {tmp}' + + self._cmd(default_tc) + + def _rate_convert(self, rate) -> int: + rates = { + 'bit' : 1, + 'kbit' : 1000, + 'mbit' : 1000000, + 'gbit' : 1000000000, + 'tbit' : 1000000000000, + } + + if rate == 'auto' or rate.endswith('%'): + speed = 1000 + default_speed = speed + # Not all interfaces have valid entries in the speed file. PPPoE + # interfaces have the appropriate speed file, but you can not read it: + # cat: /sys/class/net/pppoe7/speed: Invalid argument + try: + speed = read_file(f'/sys/class/net/{self._interface}/speed') + if not speed.isnumeric(): + Warning('Interface speed cannot be determined (assuming 1000 Mbit/s)') + if int(speed) < 1: + speed = default_speed + if rate.endswith('%'): + percent = rate.rstrip('%') + speed = int(speed) * int(percent) // 100 + except: + pass + + return int(speed) *1000000 # convert to MBit/s + + rate_numeric = int(''.join([n for n in rate if n.isdigit()])) + rate_scale = ''.join([n for n in rate if not n.isdigit()]) + + if int(rate_numeric) <= 0: + raise ValueError(f'{rate_numeric} is not a valid bandwidth <= 0') + + if rate_scale: + return int(rate_numeric * rates[rate_scale]) + else: + # No suffix implies Kbps just as Cisco IOS + return int(rate_numeric * 1000) + + def update(self, config, direction, priority=None): + """ method must be called from derived class after it has completed qdisc setup """ + if self._debug: + import pprint + pprint.pprint(config) + + if 'class' in config: + for cls, cls_config in config['class'].items(): + self._build_base_qdisc(cls_config, int(cls)) + + # every match criteria has it's tc instance + filter_cmd_base = f'tc filter add dev {self._interface} parent {self._parent:x}:' + + if priority: + filter_cmd_base += f' prio {cls}' + elif 'priority' in cls_config: + prio = cls_config['priority'] + filter_cmd_base += f' prio {prio}' + + filter_cmd_base += ' protocol all' + + if 'match' in cls_config: + has_filter = False + for index, (match, match_config) in enumerate(cls_config['match'].items(), start=1): + filter_cmd = filter_cmd_base + if not has_filter: + for key in ['mark', 'vif', 'ip', 'ipv6']: + if key in match_config: + has_filter = True + break + + if self.qostype == 'shaper' and 'prio ' not in filter_cmd: + filter_cmd += f' prio {index}' + if 'mark' in match_config: + mark = match_config['mark'] + filter_cmd += f' handle {mark} fw' + if 'vif' in match_config: + vif = match_config['vif'] + filter_cmd += f' basic match "meta(vlan mask 0xfff eq {vif})"' + + for af in ['ip', 'ipv6']: + tc_af = af + if af == 'ipv6': + tc_af = 'ip6' + + if af in match_config: + filter_cmd += ' u32' + + tmp = dict_search(f'{af}.source.address', match_config) + if tmp: filter_cmd += f' match {tc_af} src {tmp}' + + tmp = dict_search(f'{af}.source.port', match_config) + if tmp: filter_cmd += f' match {tc_af} sport {tmp} 0xffff' + + tmp = dict_search(f'{af}.destination.address', match_config) + if tmp: filter_cmd += f' match {tc_af} dst {tmp}' + + tmp = dict_search(f'{af}.destination.port', match_config) + if tmp: filter_cmd += f' match {tc_af} dport {tmp} 0xffff' + + tmp = dict_search(f'{af}.protocol', match_config) + if tmp: + tmp = get_protocol_by_name(tmp) + filter_cmd += f' match {tc_af} protocol {tmp} 0xff' + + tmp = dict_search(f'{af}.dscp', match_config) + if tmp: + tmp = self._get_dsfield(tmp) + if af == 'ip': + filter_cmd += f' match {tc_af} dsfield {tmp} 0xff' + elif af == 'ipv6': + filter_cmd += f' match u16 {tmp} 0x0ff0 at 0' + + # Will match against total length of an IPv4 packet and + # payload length of an IPv6 packet. + # + # IPv4 : match u16 0x0000 ~MAXLEN at 2 + # IPv6 : match u16 0x0000 ~MAXLEN at 4 + tmp = dict_search(f'{af}.max_length', match_config) + if tmp: + # We need the 16 bit two's complement of the maximum + # packet length + tmp = hex(0xffff & ~int(tmp)) + + if af == 'ip': + filter_cmd += f' match u16 0x0000 {tmp} at 2' + elif af == 'ipv6': + filter_cmd += f' match u16 0x0000 {tmp} at 4' + + # We match against specific TCP flags - we assume the IPv4 + # header length is 20 bytes and assume the IPv6 packet is + # not using extension headers (hence a ip header length of 40 bytes) + # TCP Flags are set on byte 13 of the TCP header. + # IPv4 : match u8 X X at 33 + # IPv6 : match u8 X X at 53 + # with X = 0x02 for SYN and X = 0x10 for ACK + tmp = dict_search(f'{af}.tcp', match_config) + if tmp: + mask = 0 + if 'ack' in tmp: + mask |= 0x10 + if 'syn' in tmp: + mask |= 0x02 + mask = hex(mask) + + if af == 'ip': + filter_cmd += f' match u8 {mask} {mask} at 33' + elif af == 'ipv6': + filter_cmd += f' match u8 {mask} {mask} at 53' + + cls = int(cls) + filter_cmd += f' flowid {self._parent:x}:{cls:x}' + self._cmd(filter_cmd) + + vlan_expression = "match.*.vif" + match_vlan = jmespath.search(vlan_expression, cls_config) + + if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in cls_config) \ + and has_filter: + # For "vif" "basic match" is used instead of "action police" T5961 + if not match_vlan: + filter_cmd += f' action police' + + if 'exceed' in cls_config: + action = cls_config['exceed'] + filter_cmd += f' conform-exceed {action}' + if 'not_exceed' in cls_config: + action = cls_config['not_exceed'] + filter_cmd += f'/{action}' + + if 'bandwidth' in cls_config: + rate = self._rate_convert(cls_config['bandwidth']) + filter_cmd += f' rate {rate}' + + if 'burst' in cls_config: + burst = cls_config['burst'] + filter_cmd += f' burst {burst}' + + if 'mtu' in cls_config: + mtu = cls_config['mtu'] + filter_cmd += f' mtu {mtu}' + + cls = int(cls) + filter_cmd += f' flowid {self._parent:x}:{cls:x}' + self._cmd(filter_cmd) + + # The police block allows limiting of the byte or packet rate of + # traffic matched by the filter it is attached to. + # https://man7.org/linux/man-pages/man8/tc-police.8.html + + # T5295: We do not handle rate via tc filter directly, + # but rather set the tc filter to direct traffic to the correct tc class flow. + # + # if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in cls_config): + # filter_cmd += f' action police' + # + # if 'exceed' in cls_config: + # action = cls_config['exceed'] + # filter_cmd += f' conform-exceed {action}' + # if 'not_exceed' in cls_config: + # action = cls_config['not_exceed'] + # filter_cmd += f'/{action}' + # + # if 'bandwidth' in cls_config: + # rate = self._rate_convert(cls_config['bandwidth']) + # filter_cmd += f' rate {rate}' + # + # if 'burst' in cls_config: + # burst = cls_config['burst'] + # filter_cmd += f' burst {burst}' + + if 'default' in config: + default_cls_id = 1 + if 'class' in config: + class_id_max = self._get_class_max_id(config) + default_cls_id = int(class_id_max) +1 + self._build_base_qdisc(config['default'], default_cls_id) + + if self.qostype == 'limiter': + if 'default' in config: + filter_cmd = f'tc filter replace dev {self._interface} parent {self._parent:x}: ' + filter_cmd += 'prio 255 protocol all basic' + + # The police block allows limiting of the byte or packet rate of + # traffic matched by the filter it is attached to. + # https://man7.org/linux/man-pages/man8/tc-police.8.html + if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in + config['default']): + filter_cmd += f' action police' + + if 'exceed' in config['default']: + action = config['default']['exceed'] + filter_cmd += f' conform-exceed {action}' + if 'not_exceed' in config['default']: + action = config['default']['not_exceed'] + filter_cmd += f'/{action}' + + if 'bandwidth' in config['default']: + rate = self._rate_convert(config['default']['bandwidth']) + filter_cmd += f' rate {rate}' + + if 'burst' in config['default']: + burst = config['default']['burst'] + filter_cmd += f' burst {burst}' + + if 'mtu' in config['default']: + mtu = config['default']['mtu'] + filter_cmd += f' mtu {mtu}' + + if 'class' in config: + filter_cmd += f' flowid {self._parent:x}:{default_cls_id:x}' + + self._cmd(filter_cmd) diff --git a/python/vyos/qos/cake.py b/python/vyos/qos/cake.py new file mode 100644 index 0000000..1ee7d0f --- /dev/null +++ b/python/vyos/qos/cake.py @@ -0,0 +1,57 @@ +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.qos.base import QoSBase + +class CAKE(QoSBase): + _direction = ['egress'] + + # https://man7.org/linux/man-pages/man8/tc-cake.8.html + def update(self, config, direction): + tmp = f'tc qdisc add dev {self._interface} root handle 1: cake {direction}' + if 'bandwidth' in config: + bandwidth = self._rate_convert(config['bandwidth']) + tmp += f' bandwidth {bandwidth}' + + if 'rtt' in config: + rtt = config['rtt'] + tmp += f' rtt {rtt}ms' + + if 'flow_isolation' in config: + if 'blind' in config['flow_isolation']: + tmp += f' flowblind' + if 'dst_host' in config['flow_isolation']: + tmp += f' dsthost' + if 'dual_dst_host' in config['flow_isolation']: + tmp += f' dual-dsthost' + if 'dual_src_host' in config['flow_isolation']: + tmp += f' dual-srchost' + if 'triple_isolate' in config['flow_isolation']: + tmp += f' triple-isolate' + if 'flow' in config['flow_isolation']: + tmp += f' flows' + if 'host' in config['flow_isolation']: + tmp += f' hosts' + if 'nat' in config['flow_isolation']: + tmp += f' nat' + if 'src_host' in config['flow_isolation']: + tmp += f' srchost ' + else: + tmp += f' nonat' + + self._cmd(tmp) + + # call base class + super().update(config, direction) diff --git a/python/vyos/qos/droptail.py b/python/vyos/qos/droptail.py new file mode 100644 index 0000000..427d43d --- /dev/null +++ b/python/vyos/qos/droptail.py @@ -0,0 +1,28 @@ +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.qos.base import QoSBase + +class DropTail(QoSBase): + # https://man7.org/linux/man-pages/man8/tc-pfifo.8.html + def update(self, config, direction): + tmp = f'tc qdisc add dev {self._interface} root pfifo' + if 'queue_limit' in config: + limit = config["queue_limit"] + tmp += f' limit {limit}' + self._cmd(tmp) + + # call base class + super().update(config, direction) diff --git a/python/vyos/qos/fairqueue.py b/python/vyos/qos/fairqueue.py new file mode 100644 index 0000000..f41d098 --- /dev/null +++ b/python/vyos/qos/fairqueue.py @@ -0,0 +1,31 @@ +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.qos.base import QoSBase + +class FairQueue(QoSBase): + # https://man7.org/linux/man-pages/man8/tc-sfq.8.html + def update(self, config, direction): + tmp = f'tc qdisc add dev {self._interface} root sfq' + + if 'hash_interval' in config: + tmp += f' perturb {config["hash_interval"]}' + if 'queue_limit' in config: + tmp += f' limit {config["queue_limit"]}' + + self._cmd(tmp) + + # call base class + super().update(config, direction) diff --git a/python/vyos/qos/fqcodel.py b/python/vyos/qos/fqcodel.py new file mode 100644 index 0000000..cd2340a --- /dev/null +++ b/python/vyos/qos/fqcodel.py @@ -0,0 +1,40 @@ +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.qos.base import QoSBase + +class FQCodel(QoSBase): + # https://man7.org/linux/man-pages/man8/tc-fq_codel.8.html + def update(self, config, direction): + tmp = f'tc qdisc add dev {self._interface} root fq_codel' + + if 'codel_quantum' in config: + tmp += f' quantum {config["codel_quantum"]}' + if 'flows' in config: + tmp += f' flows {config["flows"]}' + if 'interval' in config: + interval = int(config['interval']) * 1000 + tmp += f' interval {interval}' + if 'queue_limit' in config: + tmp += f' limit {config["queue_limit"]}' + if 'target' in config: + target = int(config['target']) * 1000 + tmp += f' target {target}' + + tmp += f' noecn' + self._cmd(tmp) + + # call base class + super().update(config, direction) diff --git a/python/vyos/qos/limiter.py b/python/vyos/qos/limiter.py new file mode 100644 index 0000000..3f5c111 --- /dev/null +++ b/python/vyos/qos/limiter.py @@ -0,0 +1,28 @@ +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.qos.base import QoSBase + +class Limiter(QoSBase): + _direction = ['ingress'] + qostype = 'limiter' + + def update(self, config, direction): + tmp = f'tc qdisc add dev {self._interface} handle {self._parent:x}: {direction}' + self._cmd(tmp) + + # base class must be called last + super().update(config, direction) + diff --git a/python/vyos/qos/netem.py b/python/vyos/qos/netem.py new file mode 100644 index 0000000..8bdef30 --- /dev/null +++ b/python/vyos/qos/netem.py @@ -0,0 +1,53 @@ +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.qos.base import QoSBase + +class NetEm(QoSBase): + # https://man7.org/linux/man-pages/man8/tc-netem.8.html + def update(self, config, direction): + tmp = f'tc qdisc add dev {self._interface} root netem' + if 'bandwidth' in config: + rate = self._rate_convert(config["bandwidth"]) + tmp += f' rate {rate}' + + if 'queue_limit' in config: + limit = config["queue_limit"] + tmp += f' limit {limit}' + + if 'delay' in config: + delay = config["delay"] + tmp += f' delay {delay}ms' + + if 'loss' in config: + drop = config["loss"] + tmp += f' drop {drop}%' + + if 'corruption' in config: + corrupt = config["corruption"] + tmp += f' corrupt {corrupt}%' + + if 'reordering' in config: + reorder = config["reordering"] + tmp += f' reorder {reorder}%' + + if 'duplicate' in config: + duplicate = config["duplicate"] + tmp += f' duplicate {duplicate}%' + + self._cmd(tmp) + + # call base class + super().update(config, direction) diff --git a/python/vyos/qos/priority.py b/python/vyos/qos/priority.py new file mode 100644 index 0000000..7f0a670 --- /dev/null +++ b/python/vyos/qos/priority.py @@ -0,0 +1,40 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.qos.base import QoSBase + +class Priority(QoSBase): + _parent = 1 + + # https://man7.org/linux/man-pages/man8/tc-prio.8.html + def update(self, config, direction): + if 'class' in config: + class_id_max = self._get_class_max_id(config) + bands = int(class_id_max) +1 + + tmp = f'tc qdisc add dev {self._interface} root handle {self._parent:x}: prio bands {bands} priomap ' \ + f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' \ + f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' \ + f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' \ + f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' + self._cmd(tmp) + + for cls in config['class']: + cls = int(cls) + tmp = f'tc qdisc add dev {self._interface} parent {self._parent:x}:{cls:x} pfifo' + self._cmd(tmp) + + # base class must be called last + super().update(config, direction, priority=True) diff --git a/python/vyos/qos/randomdetect.py b/python/vyos/qos/randomdetect.py new file mode 100644 index 0000000..a3a39da --- /dev/null +++ b/python/vyos/qos/randomdetect.py @@ -0,0 +1,46 @@ +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.qos.base import QoSBase + +class RandomDetect(QoSBase): + _parent = 1 + + # https://man7.org/linux/man-pages/man8/tc.8.html + def update(self, config, direction): + + # # Generalized Random Early Detection + handle = self._parent + tmp = f'tc qdisc add dev {self._interface} root handle {self._parent}:0 gred setup DPs 8 default 0 grio' + self._cmd(tmp) + bandwidth = self._rate_convert(config['bandwidth']) + + # set VQ (virtual queue) parameters + for precedence, precedence_config in config['precedence'].items(): + precedence = int(precedence) + qparams = self._calc_random_detect_queue_params( + avg_pkt=precedence_config.get('average_packet'), + max_thr=precedence_config.get('maximum_threshold'), + limit=precedence_config.get('queue_limit'), + min_thr=precedence_config.get('minimum_threshold'), + mark_probability=precedence_config.get('mark_probability'), + precedence=precedence + ) + tmp = f'tc qdisc change dev {self._interface} handle {handle}:0 gred limit {qparams["limit"]} min {qparams["min_val"]} max {qparams["max_val"]} avpkt {qparams["avg_pkt"]} ' + tmp += f'burst {qparams["burst"]} bandwidth {bandwidth} probability {qparams["probability"]} DP {precedence} prio {8 - precedence:x}' + self._cmd(tmp) + + # call base class + super().update(config, direction) diff --git a/python/vyos/qos/ratelimiter.py b/python/vyos/qos/ratelimiter.py new file mode 100644 index 0000000..a4f80a1 --- /dev/null +++ b/python/vyos/qos/ratelimiter.py @@ -0,0 +1,37 @@ +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.qos.base import QoSBase + +class RateLimiter(QoSBase): + # https://man7.org/linux/man-pages/man8/tc-tbf.8.html + def update(self, config, direction): + # call base class + super().update(config, direction) + + tmp = f'tc qdisc add dev {self._interface} root tbf' + if 'bandwidth' in config: + rate = self._rate_convert(config['bandwidth']) + tmp += f' rate {rate}' + + if 'burst' in config: + burst = config['burst'] + tmp += f' burst {burst}' + + if 'latency' in config: + latency = config['latency'] + tmp += f' latency {latency}ms' + + self._cmd(tmp) diff --git a/python/vyos/qos/roundrobin.py b/python/vyos/qos/roundrobin.py new file mode 100644 index 0000000..80814dd --- /dev/null +++ b/python/vyos/qos/roundrobin.py @@ -0,0 +1,44 @@ +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.qos.base import QoSBase + +class RoundRobin(QoSBase): + _parent = 1 + + # https://man7.org/linux/man-pages/man8/tc-drr.8.html + def update(self, config, direction): + tmp = f'tc qdisc add dev {self._interface} root handle 1: drr' + self._cmd(tmp) + + if 'class' in config: + for cls in config['class']: + cls = int(cls) + tmp = f'tc class replace dev {self._interface} parent 1:1 classid 1:{cls:x} drr' + self._cmd(tmp) + + tmp = f'tc qdisc replace dev {self._interface} parent 1:{cls:x} pfifo' + self._cmd(tmp) + + if 'default' in config: + class_id_max = self._get_class_max_id(config) + default_cls_id = int(class_id_max) +1 + + # class ID via CLI is in range 1-4095, thus 1000 hex = 4096 + tmp = f'tc class replace dev {self._interface} parent 1:1 classid 1:{default_cls_id:x} drr' + self._cmd(tmp) + + # call base class + super().update(config, direction, priority=True) diff --git a/python/vyos/qos/trafficshaper.py b/python/vyos/qos/trafficshaper.py new file mode 100644 index 0000000..8b0333c --- /dev/null +++ b/python/vyos/qos/trafficshaper.py @@ -0,0 +1,216 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from math import ceil +from vyos.qos.base import QoSBase + +# Kernel limits on quantum (bytes) +MAXQUANTUM = 200000 +MINQUANTUM = 1000 + +class TrafficShaper(QoSBase): + _parent = 1 + qostype = 'shaper' + + # https://man7.org/linux/man-pages/man8/tc-htb.8.html + def update(self, config, direction): + class_id_max = 0 + if 'class' in config: + tmp = list(config['class']) + # Convert strings to integers + tmp = [int(x) for x in tmp] + class_id_max = max(tmp) + + r2q = 10 + # bandwidth is a mandatory CLI node + speed = self._rate_convert(config['bandwidth']) + speed_bps = int(speed) // 8 + + # need a bigger r2q if going fast than 16 mbits/sec + if (speed_bps // r2q) >= MAXQUANTUM: # integer division + r2q = ceil(speed_bps / MAXQUANTUM) + else: + # if there is a slow class then may need smaller value + if 'class' in config: + min_speed = speed_bps + for cls, cls_options in config['class'].items(): + # find class with the lowest bandwidth used + if 'bandwidth' in cls_options: + bw_bps = int(self._rate_convert(cls_options['bandwidth'])) // 8 # bandwidth in bytes per second + if bw_bps < min_speed: + min_speed = bw_bps + + while (r2q > 1) and (min_speed // r2q) < MINQUANTUM: + tmp = r2q -1 + if (speed_bps // tmp) >= MAXQUANTUM: + break + r2q = tmp + + + default_minor_id = int(class_id_max) +1 + tmp = f'tc qdisc replace dev {self._interface} root handle {self._parent:x}: htb r2q {r2q} default {default_minor_id:x}' # default is in hex + self._cmd(tmp) + + tmp = f'tc class replace dev {self._interface} parent {self._parent:x}: classid {self._parent:x}:1 htb rate {speed}' + self._cmd(tmp) + + if 'class' in config: + for cls, cls_config in config['class'].items(): + # class id is used later on and passed as hex, thus this needs to be an int + cls = int(cls) + + # bandwidth is a mandatory CLI node + # T5296 if bandwidth 'auto' or 'xx%' get value from config shaper total "bandwidth" + # i.e from set shaper test bandwidth '300mbit' + # without it, it tries to get value from qos.base /sys/class/net/{self._interface}/speed + if cls_config['bandwidth'] == 'auto': + rate = self._rate_convert(config['bandwidth']) + elif cls_config['bandwidth'].endswith('%'): + percent = cls_config['bandwidth'].rstrip('%') + rate = self._rate_convert(config['bandwidth']) * int(percent) // 100 + else: + rate = self._rate_convert(cls_config['bandwidth']) + + burst = cls_config['burst'] + quantum = cls_config['codel_quantum'] + + tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{cls:x} htb rate {rate} burst {burst} quantum {quantum}' + if 'priority' in cls_config: + priority = cls_config['priority'] + tmp += f' prio {priority}' + + if 'ceiling' in cls_config: + f_ceil = self._rate_convert(cls_config['ceiling']) + tmp += f' ceil {f_ceil}' + self._cmd(tmp) + + tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{cls:x} sfq' + self._cmd(tmp) + + if 'default' in config: + if config['default']['bandwidth'].endswith('%'): + percent = config['default']['bandwidth'].rstrip('%') + rate = self._rate_convert(config['bandwidth']) * int(percent) // 100 + else: + rate = self._rate_convert(config['default']['bandwidth']) + burst = config['default']['burst'] + quantum = config['default']['codel_quantum'] + tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{default_minor_id:x} htb rate {rate} burst {burst} quantum {quantum}' + if 'priority' in config['default']: + priority = config['default']['priority'] + tmp += f' prio {priority}' + if 'ceiling' in config['default']: + if config['default']['ceiling'].endswith('%'): + percent = config['default']['ceiling'].rstrip('%') + f_ceil = self._rate_convert(config['bandwidth']) * int(percent) // 100 + else: + f_ceil = self._rate_convert(config['default']['ceiling']) + tmp += f' ceil {f_ceil}' + self._cmd(tmp) + + tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{default_minor_id:x} sfq' + self._cmd(tmp) + + # call base class + super().update(config, direction) + +class TrafficShaperHFSC(QoSBase): + _parent = 1 + qostype = 'shaper_hfsc' + + # https://man7.org/linux/man-pages/man8/tc-hfsc.8.html + def update(self, config, direction): + class_id_max = 0 + if 'class' in config: + tmp = list(config['class']) + tmp.sort() + class_id_max = tmp[-1] + + r2q = 10 + # bandwidth is a mandatory CLI node + speed = self._rate_convert(config['bandwidth']) + speed_bps = int(speed) // 8 + + # need a bigger r2q if going fast than 16 mbits/sec + if (speed_bps // r2q) >= MAXQUANTUM: # integer division + r2q = ceil(speed_bps // MAXQUANTUM) + else: + # if there is a slow class then may need smaller value + if 'class' in config: + min_speed = speed_bps + for cls, cls_options in config['class'].items(): + # find class with the lowest bandwidth used + if 'bandwidth' in cls_options: + bw_bps = int(self._rate_convert(cls_options['bandwidth'])) // 8 # bandwidth in bytes per second + if bw_bps < min_speed: + min_speed = bw_bps + + while (r2q > 1) and (min_speed // r2q) < MINQUANTUM: + tmp = r2q -1 + if (speed_bps // tmp) >= MAXQUANTUM: + break + r2q = tmp + + default_minor_id = int(class_id_max) +1 + tmp = f'tc qdisc replace dev {self._interface} root handle {self._parent:x}: hfsc default {default_minor_id:x}' # default is in hex + self._cmd(tmp) + + tmp = f'tc class replace dev {self._interface} parent {self._parent:x}: classid {self._parent:x}:1 hfsc sc rate {speed} ul rate {speed}' + self._cmd(tmp) + + if 'class' in config: + for cls, cls_config in config['class'].items(): + # class id is used later on and passed as hex, thus this needs to be an int + cls = int(cls) + # ls m1 + if cls_config.get('linkshare', {}).get('m1').endswith('%'): + percent = cls_config['linkshare']['m1'].rstrip('%') + m_one_rate = self._rate_convert(config['bandwidth']) * int(percent) // 100 + else: + m_one_rate = cls_config['linkshare']['m1'] + # ls m2 + if cls_config.get('linkshare', {}).get('m2').endswith('%'): + percent = cls_config['linkshare']['m2'].rstrip('%') + m_two_rate = self._rate_convert(config['bandwidth']) * int(percent) // 100 + else: + m_two_rate = self._rate_convert(cls_config['linkshare']['m2']) + + tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{cls:x} hfsc ls m1 {m_one_rate} m2 {m_two_rate} ' + self._cmd(tmp) + + tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{cls:x} sfq perturb 10' + self._cmd(tmp) + + if 'default' in config: + # ls m1 + if config.get('default', {}).get('linkshare', {}).get('m1').endswith('%'): + percent = config['default']['linkshare']['m1'].rstrip('%') + m_one_rate = self._rate_convert(config['default']['linkshare']['m1']) * int(percent) // 100 + else: + m_one_rate = config['default']['linkshare']['m1'] + # ls m2 + if config.get('default', {}).get('linkshare', {}).get('m2').endswith('%'): + percent = config['default']['linkshare']['m2'].rstrip('%') + m_two_rate = self._rate_convert(config['default']['linkshare']['m2']) * int(percent) // 100 + else: + m_two_rate = self._rate_convert(config['default']['linkshare']['m2']) + tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{default_minor_id:x} hfsc ls m1 {m_one_rate} m2 {m_two_rate} ' + self._cmd(tmp) + + tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{default_minor_id:x} sfq perturb 10' + self._cmd(tmp) + + # call base class + super().update(config, direction) diff --git a/python/vyos/raid.py b/python/vyos/raid.py new file mode 100644 index 0000000..7fb7948 --- /dev/null +++ b/python/vyos/raid.py @@ -0,0 +1,71 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.utils.disk import device_from_id +from vyos.utils.process import cmd + +def raid_sets(): + """ + Returns a list of RAID sets + """ + with open('/proc/mdstat') as f: + return [line.split()[0].rstrip(':') for line in f if line.startswith('md')] + +def raid_set_members(raid_set_name: str): + """ + Returns a list of members of a RAID set + """ + with open('/proc/mdstat') as f: + for line in f: + if line.startswith(raid_set_name): + return [l.split('[')[0] for l in line.split()[4:]] + return [] + +def partitions(): + """ + Returns a list of partitions + """ + with open('/proc/partitions') as f: + p = [l.strip().split()[-1] for l in list(f) if l.strip()] + p.remove('name') + return p + +def add_raid_member(raid_set_name: str, member: str, by_id: bool = False): + """ + Add a member to an existing RAID set + """ + if by_id: + member = device_from_id(member) + if raid_set_name not in raid_sets(): + raise ValueError(f"RAID set {raid_set_name} does not exist") + if member not in partitions(): + raise ValueError(f"Partition {member} does not exist") + if member in raid_set_members(raid_set_name): + raise ValueError(f"Partition {member} is already a member of RAID set {raid_set_name}") + cmd(f'mdadm --add /dev/{raid_set_name} /dev/{member}') + disk = cmd(f'lsblk -ndo PKNAME /dev/{member}') + cmd(f'grub-install /dev/{disk}') + +def delete_raid_member(raid_set_name: str, member: str, by_id: bool = False): + """ + Delete a member from an existing RAID set + """ + if by_id: + member = device_from_id(member) + if raid_set_name not in raid_sets(): + raise ValueError(f"RAID set {raid_set_name} does not exist") + if member not in raid_set_members(raid_set_name): + raise ValueError(f"Partition {member} is not a member of RAID set {raid_set_name}") + cmd(f'mdadm --remove /dev/{raid_set_name} /dev/{member}') diff --git a/python/vyos/range_regex.py b/python/vyos/range_regex.py new file mode 100644 index 0000000..81e9d2e --- /dev/null +++ b/python/vyos/range_regex.py @@ -0,0 +1,141 @@ +'''Copyright (c) 2013, Dmitry Voronin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +''' + +# coding=utf8 + +# Split range to ranges that has its unique pattern. +# Example for 12-345: +# +# 12- 19: 1[2-9] +# 20- 99: [2-9]\d +# 100-299: [1-2]\d{2} +# 300-339: 3[0-3]\d +# 340-345: 34[0-5] + +def range_to_regex(inpt_range): + if isinstance(inpt_range, str): + range_list = inpt_range.split('-') + # Check input arguments + if len(range_list) == 2: + # The first element in range must be higher then the second + if int(range_list[0]) < int(range_list[1]): + return regex_for_range(int(range_list[0]), int(range_list[1])) + + return None + +def bounded_regex_for_range(min_, max_): + return r'\b({})\b'.format(regex_for_range(min_, max_)) + +def regex_for_range(min_, max_): + """ + > regex_for_range(12, 345) + '1[2-9]|[2-9]\d|[1-2]\d{2}|3[0-3]\d|34[0-5]' + """ + positive_subpatterns = [] + negative_subpatterns = [] + + if min_ < 0: + min__ = 1 + if max_ < 0: + min__ = abs(max_) + max__ = abs(min_) + + negative_subpatterns = split_to_patterns(min__, max__) + min_ = 0 + + if max_ >= 0: + positive_subpatterns = split_to_patterns(min_, max_) + + negative_only_subpatterns = ['-' + val for val in negative_subpatterns if val not in positive_subpatterns] + positive_only_subpatterns = [val for val in positive_subpatterns if val not in negative_subpatterns] + intersected_subpatterns = ['-?' + val for val in negative_subpatterns if val in positive_subpatterns] + + subpatterns = negative_only_subpatterns + intersected_subpatterns + positive_only_subpatterns + return '|'.join(subpatterns) + + +def split_to_patterns(min_, max_): + subpatterns = [] + + start = min_ + for stop in split_to_ranges(min_, max_): + subpatterns.append(range_to_pattern(start, stop)) + start = stop + 1 + + return subpatterns + + +def split_to_ranges(min_, max_): + stops = {max_} + + nines_count = 1 + stop = fill_by_nines(min_, nines_count) + while min_ <= stop < max_: + stops.add(stop) + + nines_count += 1 + stop = fill_by_nines(min_, nines_count) + + zeros_count = 1 + stop = fill_by_zeros(max_ + 1, zeros_count) - 1 + while min_ < stop <= max_: + stops.add(stop) + + zeros_count += 1 + stop = fill_by_zeros(max_ + 1, zeros_count) - 1 + + stops = list(stops) + stops.sort() + + return stops + + +def fill_by_nines(integer, nines_count): + return int(str(integer)[:-nines_count] + '9' * nines_count) + + +def fill_by_zeros(integer, zeros_count): + return integer - integer % 10 ** zeros_count + + +def range_to_pattern(start, stop): + pattern = '' + any_digit_count = 0 + + for start_digit, stop_digit in zip(str(start), str(stop)): + if start_digit == stop_digit: + pattern += start_digit + elif start_digit != '0' or stop_digit != '9': + pattern += '[{}-{}]'.format(start_digit, stop_digit) + else: + any_digit_count += 1 + + if any_digit_count: + pattern += r'\d' + + if any_digit_count > 1: + pattern += '{{{}}}'.format(any_digit_count) + + return pattern diff --git a/python/vyos/remote.py b/python/vyos/remote.py new file mode 100644 index 0000000..d87fd24 --- /dev/null +++ b/python/vyos/remote.py @@ -0,0 +1,479 @@ +# Copyright 2021 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +import pwd +import shutil +import socket +import ssl +import stat +import sys +import tempfile +import urllib.parse + +from contextlib import contextmanager +from pathlib import Path + +from ftplib import FTP +from ftplib import FTP_TLS + +from paramiko import SSHClient, SSHException +from paramiko import MissingHostKeyPolicy + +from requests import Session +from requests.adapters import HTTPAdapter +from requests.packages.urllib3 import PoolManager + +from vyos.progressbar import Progressbar +from vyos.utils.io import ask_yes_no +from vyos.utils.io import is_interactive +from vyos.utils.io import print_error +from vyos.utils.misc import begin +from vyos.utils.process import cmd, rc_cmd +from vyos.version import get_version +from vyos.base import Warning + +CHUNK_SIZE = 8192 + +class InteractivePolicy(MissingHostKeyPolicy): + """ + Paramiko policy for interactively querying the user on whether to proceed + with SSH connections to unknown hosts. + """ + def missing_host_key(self, client, hostname, key): + print_error(f"Host '{hostname}' not found in known hosts.") + print_error('Fingerprint: ' + key.get_fingerprint().hex()) + if not sys.stdin.isatty(): + return + if not ask_yes_no('Do you wish to continue?'): + raise SSHException(f"Cannot connect to unknown host '{hostname}'.") + if client._host_keys_filename is None: + Warning('no \'known_hosts\' file; create to store keys permanently') + return + if ask_yes_no('Do you wish to permanently add this host/key pair to known_hosts file?'): + client._host_keys.add(hostname, key.get_name(), key) + client.save_host_keys(client._host_keys_filename) + +class SourceAdapter(HTTPAdapter): + """ + urllib3 transport adapter for setting source addresses per session. + """ + def __init__(self, source_pair, *args, **kwargs): + # A source pair is a tuple of a source host string and source port respectively. + # Supply '' and 0 respectively for default values. + self._source_pair = source_pair + super(SourceAdapter, self).__init__(*args, **kwargs) + + def init_poolmanager(self, connections, maxsize, block=False): + self.poolmanager = PoolManager( + num_pools=connections, maxsize=maxsize, + block=block, source_address=self._source_pair) + +@contextmanager +def umask(mask: int): + """ + Context manager that temporarily sets the process umask. + """ + import os + oldmask = os.umask(mask) + try: + yield + finally: + os.umask(oldmask) + +def check_storage(path, size): + """ + Check whether `path` has enough storage space for a transfer of `size` bytes. + """ + path = os.path.abspath(os.path.expanduser(path)) + directory = path if os.path.isdir(path) else (os.path.dirname(os.path.expanduser(path)) or os.getcwd()) + # `size` can be None or 0 to indicate unknown size. + if not size: + print_error('Warning: Cannot determine size of remote file. Bravely continuing regardless.') + return + + if size < 1024 * 1024: + print_error(f'The file is {size / 1024.0:.3f} KiB.') + else: + print_error(f'The file is {size / (1024.0 * 1024.0):.3f} MiB.') + + # Will throw `FileNotFoundError' if `directory' is absent. + if size > shutil.disk_usage(directory).free: + raise OSError(f'Not enough disk space available in "{directory}".') + + +class FtpC: + def __init__(self, + url, + progressbar=False, + check_space=False, + source_host='', + source_port=0, + timeout=10): + self.secure = url.scheme == 'ftps' + self.hostname = url.hostname + self.path = url.path + self.username = url.username or os.getenv('REMOTE_USERNAME', 'anonymous') + self.password = url.password or os.getenv('REMOTE_PASSWORD', '') + self.port = url.port or 21 + self.source = (source_host, source_port) + self.progressbar = progressbar + self.check_space = check_space + self.timeout = timeout + + def _establish(self): + if self.secure: + return FTP_TLS(source_address=self.source, + context=ssl.create_default_context(), + timeout=self.timeout) + else: + return FTP(source_address=self.source, timeout=self.timeout) + + def download(self, location: str): + # Open the file upfront before establishing connection. + with open(location, 'wb') as f, self._establish() as conn: + conn.connect(self.hostname, self.port) + conn.login(self.username, self.password) + # Set secure connection over TLS. + if self.secure: + conn.prot_p() + # Almost all FTP servers support the `SIZE' command. + size = conn.size(self.path) + if self.check_space: + check_storage(location, size) + # No progressbar if we can't determine the size or if the file is too small. + if self.progressbar and size and size > CHUNK_SIZE: + with Progressbar(CHUNK_SIZE / size) as p: + callback = lambda block: begin(f.write(block), p.increment()) + conn.retrbinary('RETR ' + self.path, callback, CHUNK_SIZE) + else: + conn.retrbinary('RETR ' + self.path, f.write, CHUNK_SIZE) + + def upload(self, location: str): + size = os.path.getsize(location) + with open(location, 'rb') as f, self._establish() as conn: + conn.connect(self.hostname, self.port) + conn.login(self.username, self.password) + if self.secure: + conn.prot_p() + if self.progressbar and size and size > CHUNK_SIZE: + with Progressbar(CHUNK_SIZE / size) as p: + conn.storbinary('STOR ' + self.path, f, CHUNK_SIZE, lambda block: p.increment()) + else: + conn.storbinary('STOR ' + self.path, f, CHUNK_SIZE) + +class SshC: + known_hosts = os.path.expanduser('~/.ssh/known_hosts') + def __init__(self, + url, + progressbar=False, + check_space=False, + source_host='', + source_port=0, + timeout=10.0): + self.hostname = url.hostname + self.path = url.path + self.username = url.username or os.getenv('REMOTE_USERNAME') + self.password = url.password or os.getenv('REMOTE_PASSWORD') + self.port = url.port or 22 + self.source = (source_host, source_port) + self.progressbar = progressbar + self.check_space = check_space + self.timeout = timeout + + def _establish(self): + ssh = SSHClient() + ssh.load_system_host_keys() + # Try to load from a user-local known hosts file if one exists. + if os.path.exists(self.known_hosts): + ssh.load_host_keys(self.known_hosts) + ssh.set_missing_host_key_policy(InteractivePolicy()) + # `socket.create_connection()` automatically picks a NIC and an IPv4/IPv6 address family + # for us on dual-stack systems. + sock = socket.create_connection((self.hostname, self.port), self.timeout, self.source) + ssh.connect(self.hostname, self.port, self.username, self.password, sock=sock) + return ssh + + def download(self, location: str): + with self._establish() as ssh, ssh.open_sftp() as sftp: + if self.check_space: + check_storage(location, sftp.stat(self.path).st_size) + if self.progressbar: + with Progressbar() as p: + sftp.get(self.path, location, callback=p.progress) + else: + sftp.get(self.path, location) + + def upload(self, location: str): + with self._establish() as ssh, ssh.open_sftp() as sftp: + try: + # If the remote path is a directory, use the original filename. + if stat.S_ISDIR(sftp.stat(self.path).st_mode): + path = os.path.join(self.path, os.path.basename(location)) + # A file exists at this destination. We're simply going to clobber it. + else: + path = self.path + # This path doesn't point at any existing file. We can freely use this filename. + except IOError: + path = self.path + finally: + if self.progressbar: + with Progressbar() as p: + sftp.put(location, path, callback=p.progress) + else: + sftp.put(location, path) + + +class HttpC: + def __init__(self, + url, + progressbar=False, + check_space=False, + source_host='', + source_port=0, + timeout=10.0): + self.urlstring = urllib.parse.urlunsplit(url) + self.progressbar = progressbar + self.check_space = check_space + self.source_pair = (source_host, source_port) + self.username = url.username or os.getenv('REMOTE_USERNAME') + self.password = url.password or os.getenv('REMOTE_PASSWORD') + self.timeout = timeout + + def _establish(self): + session = Session() + session.mount(self.urlstring, SourceAdapter(self.source_pair)) + session.headers.update({'User-Agent': 'VyOS/' + get_version()}) + if self.username: + session.auth = self.username, self.password + return session + + def download(self, location: str): + with self._establish() as s: + # We ask for uncompressed downloads so that we don't have to deal with decoding. + # Not only would it potentially mess up with the progress bar but + # `shutil.copyfileobj(request.raw, file)` does not handle automatic decoding. + s.headers.update({'Accept-Encoding': 'identity'}) + with s.head(self.urlstring, + allow_redirects=True, + timeout=self.timeout) as r: + # Abort early if the destination is inaccessible. + r.raise_for_status() + # If the request got redirected, keep the last URL we ended up with. + final_urlstring = r.url + if r.history and self.progressbar: + print_error('Redirecting to ' + final_urlstring) + # Check for the prospective file size. + try: + size = int(r.headers['Content-Length']) + # In case the server does not supply the header. + except KeyError: + size = None + if self.check_space: + check_storage(location, size) + with s.get(final_urlstring, stream=True, + timeout=self.timeout) as r, open(location, 'wb') as f: + if self.progressbar and size: + with Progressbar(CHUNK_SIZE / size) as p: + for chunk in iter(lambda: begin(p.increment(), r.raw.read(CHUNK_SIZE)), b''): + f.write(chunk) + else: + # We'll try to stream the download directly with `copyfileobj()` so that large + # files (like entire VyOS images) don't occupy much memory. + shutil.copyfileobj(r.raw, f) + + def upload(self, location: str): + # Does not yet support progressbars. + with self._establish() as s, open(location, 'rb') as f: + s.post(self.urlstring, + data=f, + allow_redirects=True, + timeout=self.timeout) + + +class TftpC: + # We simply allow `curl` to take over because + # 1. TFTP is rather simple. + # 2. Since there's no concept authentication, we don't need to deal with keys/passwords. + # 3. It would be a waste to import, audit and maintain a third-party library for TFTP. + # 4. I'd rather not implement the entire protocol here, no matter how simple it is. + def __init__(self, + url, + progressbar=False, + check_space=False, + source_host=None, + source_port=0, + timeout=10): + source_option = f'--interface {source_host} --local-port {source_port}' if source_host else '' + progress_flag = '--progress-bar' if progressbar else '-s' + self.command = f'curl {source_option} {progress_flag} --connect-timeout {timeout}' + self.urlstring = urllib.parse.urlunsplit(url) + + def download(self, location: str): + with open(location, 'wb') as f: + f.write(cmd(f'{self.command} "{self.urlstring}"').encode()) + + def upload(self, location: str): + with open(location, 'rb') as f: + cmd(f'{self.command} -T - "{self.urlstring}"', input=f.read()) + +class GitC: + def __init__(self, + url, + progressbar=False, + check_space=False, + source_host=None, + source_port=0, + timeout=10, + ): + self.command = 'git' + self.url = url + self.urlstring = urllib.parse.urlunsplit(url) + if self.urlstring.startswith("git+"): + self.urlstring = self.urlstring.replace("git+", "", 1) + + def download(self, location: str): + raise NotImplementedError("not supported") + + @umask(0o077) + def upload(self, location: str): + scheme = self.url.scheme + _, _, scheme = scheme.partition("+") + netloc = self.url.netloc + url = Path(self.url.path).parent + with tempfile.TemporaryDirectory(prefix="git-commit-archive-") as directory: + # Determine username, fullname, email for Git commit + pwd_entry = pwd.getpwuid(os.getuid()) + user = pwd_entry.pw_name + name = pwd_entry.pw_gecos.split(",")[0] or user + fqdn = socket.getfqdn() + email = f"{user}@{fqdn}" + + # environment vars for our git commands + env = { + "GIT_TERMINAL_PROMPT": "0", + "GIT_AUTHOR_NAME": name, + "GIT_AUTHOR_EMAIL": email, + "GIT_COMMITTER_NAME": name, + "GIT_COMMITTER_EMAIL": email, + } + + # build ssh command for git + ssh_command = ["ssh"] + + # if we are not interactive, we use StrictHostKeyChecking=yes to avoid any prompts + if not sys.stdout.isatty(): + ssh_command += ["-o", "StrictHostKeyChecking=yes"] + + env["GIT_SSH_COMMAND"] = " ".join(ssh_command) + + # git clone + path_repository = Path(directory) / "repository" + scheme = f"{scheme}://" if scheme else "" + rc, out = rc_cmd( + [self.command, "clone", f"{scheme}{netloc}{url}", str(path_repository), "--depth=1"], + env=env, + shell=False, + ) + if rc: + raise Exception(out) + + # git add + filename = Path(Path(self.url.path).name).stem + dst = path_repository / filename + shutil.copy2(location, dst) + rc, out = rc_cmd( + [self.command, "-C", str(path_repository), "add", filename], + env=env, + shell=False, + ) + + # git commit -m + commit_message = os.environ.get("COMMIT_COMMENT", "commit") + rc, out = rc_cmd( + [self.command, "-C", str(path_repository), "commit", "-m", commit_message], + env=env, + shell=False, + ) + + # git push + rc, out = rc_cmd( + [self.command, "-C", str(path_repository), "push"], + env=env, + shell=False, + ) + if rc: + raise Exception(out) + + +def urlc(urlstring, *args, **kwargs): + """ + Dynamically dispatch the appropriate protocol class. + """ + url_classes = { + "http": HttpC, + "https": HttpC, + "ftp": FtpC, + "ftps": FtpC, + "sftp": SshC, + "ssh": SshC, + "scp": SshC, + "tftp": TftpC, + "git": GitC, + } + url = urllib.parse.urlsplit(urlstring) + scheme, _, _ = url.scheme.partition("+") + try: + return url_classes[scheme](url, *args, **kwargs) + except KeyError: + raise ValueError(f'Unsupported URL scheme: "{scheme}"') + +def download(local_path, urlstring, progressbar=False, check_space=False, + source_host='', source_port=0, timeout=10.0, raise_error=False): + try: + progressbar = progressbar and is_interactive() + urlc(urlstring, progressbar, check_space, source_host, source_port, timeout).download(local_path) + except Exception as err: + if raise_error: + raise + print_error(f'Unable to download "{urlstring}": {err}') + sys.exit(1) + except KeyboardInterrupt: + print_error('\nDownload aborted by user.') + sys.exit(1) + +def upload(local_path, urlstring, progressbar=False, + source_host='', source_port=0, timeout=10.0): + try: + progressbar = progressbar and is_interactive() + urlc(urlstring, progressbar, False, source_host, source_port, timeout).upload(local_path) + except Exception as err: + print_error(f'Unable to upload "{urlstring}": {err}') + sys.exit(1) + except KeyboardInterrupt: + print_error('\nUpload aborted by user.') + sys.exit(1) + +def get_remote_config(urlstring, source_host='', source_port=0): + """ + Quietly download a file and return it as a string. + """ + temp = tempfile.NamedTemporaryFile(delete=False).name + try: + download(temp, urlstring, False, False, source_host, source_port) + with open(temp, 'r') as f: + return f.read() + finally: + os.remove(temp) diff --git a/python/vyos/snmpv3_hashgen.py b/python/vyos/snmpv3_hashgen.py new file mode 100644 index 0000000..324c327 --- /dev/null +++ b/python/vyos/snmpv3_hashgen.py @@ -0,0 +1,50 @@ +# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Documentation / Inspiration +# - https://tools.ietf.org/html/rfc3414#appendix-A.3 +# - https://github.com/TheMysteriousX/SNMPv3-Hash-Generator + +key_length = 1048576 + +def random(l): + # os.urandom(8) returns 8 bytes of random data + import os + from binascii import hexlify + return hexlify(os.urandom(l)).decode('utf-8') + +def expand(s, l): + """ repead input string (s) as long as we reach the desired length in bytes """ + from itertools import repeat + reps = l // len(s) + 1 # approximation; worst case: overrun = l + len(s) + return ''.join(list(repeat(s, reps)))[:l].encode('utf-8') + +def plaintext_to_md5(passphrase, engine): + """ Convert input plaintext passphrase to MD5 hashed version usable by net-snmp """ + from hashlib import md5 + tmp = expand(passphrase, key_length) + hash = md5(tmp).digest() + engine = bytearray.fromhex(engine) + out = b''.join([hash, engine, hash]) + return md5(out).digest().hex() + +def plaintext_to_sha1(passphrase, engine): + """ Convert input plaintext passphrase to SHA1hashed version usable by net-snmp """ + from hashlib import sha1 + tmp = expand(passphrase, key_length) + hash = sha1(tmp).digest() + engine = bytearray.fromhex(engine) + out = b''.join([hash, engine, hash]) + return sha1(out).digest().hex() diff --git a/python/vyos/system/__init__.py b/python/vyos/system/__init__.py new file mode 100644 index 0000000..0c91330 --- /dev/null +++ b/python/vyos/system/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +__all_: list[str] = ['disk', 'grub', 'image'] +# define image-tools version +SYSTEM_CFG_VER = 1 diff --git a/python/vyos/system/compat.py b/python/vyos/system/compat.py new file mode 100644 index 0000000..d35bdde --- /dev/null +++ b/python/vyos/system/compat.py @@ -0,0 +1,337 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from pathlib import Path +from re import compile, MULTILINE, DOTALL +from functools import wraps +from copy import deepcopy +from typing import Union + +from vyos.system import disk, grub, image, SYSTEM_CFG_VER +from vyos.template import render + +TMPL_GRUB_COMPAT: str = 'grub/grub_compat.j2' + +# define regexes and variables +REGEX_VERSION = r'^menuentry "[^\n]*{\n[^}]*\s+linux /boot/(?P<version>\S+)/[^}]*}' +REGEX_MENUENTRY = r'^menuentry "[^\n]*{\n[^}]*\s+linux /boot/(?P<version>\S+)/vmlinuz (?P<options>[^\n]+)\n[^}]*}' +REGEX_CONSOLE = r'^.*console=(?P<console_type>[^\s\d]+)(?P<console_num>[\d]+)(,(?P<console_speed>[\d]+))?.*$' +REGEX_SANIT_CONSOLE = r'\ ?console=[^\s\d]+[\d]+(,\d+)?\ ?' +REGEX_SANIT_INIT = r'\ ?init=\S*\ ?' +REGEX_SANIT_QUIET = r'\ ?quiet\ ?' +PW_RESET_OPTION = 'init=/opt/vyatta/sbin/standalone_root_pw_reset' + + +class DowngradingImageTools(Exception): + """Raised when attempting to add an image with an earlier version + of image-tools than the current system, as indicated by the value + of SYSTEM_CFG_VER or absence thereof.""" + pass + + +def mode(): + if grub.get_cfg_ver() >= SYSTEM_CFG_VER: + return False + + return True + + +def find_versions(menu_entries: list) -> list: + """Find unique VyOS versions from menu entries + + Args: + menu_entries (list): a list with menu entries + + Returns: + list: List of installed versions + """ + versions = [] + for vyos_ver in menu_entries: + versions.append(vyos_ver.get('version')) + # remove duplicates + versions = list(set(versions)) + return versions + + +def filter_unparsed(grub_path: str) -> str: + """Find currently installed VyOS version + + Args: + grub_path (str): a path to the grub.cfg file + + Returns: + str: unparsed grub.cfg items + """ + config_text = Path(grub_path).read_text() + regex_filter = compile(REGEX_VERSION, MULTILINE | DOTALL) + filtered = regex_filter.sub('', config_text) + regex_filter = compile(grub.REGEX_GRUB_VARS, MULTILINE) + filtered = regex_filter.sub('', filtered) + regex_filter = compile(grub.REGEX_GRUB_MODULES, MULTILINE) + filtered = regex_filter.sub('', filtered) + # strip extra new lines + filtered = filtered.strip() + return filtered + + +def get_search_root(unparsed: str) -> str: + unparsed_lines = unparsed.splitlines() + search_root = next((x for x in unparsed_lines if 'search' in x), '') + return search_root + + +def sanitize_boot_opts(boot_opts: str) -> str: + """Sanitize boot options from console and init + + Args: + boot_opts (str): boot options + + Returns: + str: sanitized boot options + """ + regex_filter = compile(REGEX_SANIT_CONSOLE) + boot_opts = regex_filter.sub('', boot_opts) + regex_filter = compile(REGEX_SANIT_INIT) + boot_opts = regex_filter.sub('', boot_opts) + # legacy tools add 'quiet' on add system image; this is not desired + regex_filter = compile(REGEX_SANIT_QUIET) + boot_opts = regex_filter.sub(' ', boot_opts) + + return boot_opts + + +def parse_entry(entry: tuple) -> dict: + """Parse GRUB menuentry + + Args: + entry (tuple): tuple of (version, options) + + Returns: + dict: dictionary with parsed options + """ + # save version to dict + entry_dict = {'version': entry[0]} + # detect boot mode type + if PW_RESET_OPTION in entry[1]: + entry_dict['bootmode'] = 'pw_reset' + else: + entry_dict['bootmode'] = 'normal' + # find console type and number + regex_filter = compile(REGEX_CONSOLE) + entry_dict.update(regex_filter.match(entry[1]).groupdict()) + speed = entry_dict.get('console_speed', None) + entry_dict['console_speed'] = speed if speed is not None else '115200' + entry_dict['boot_opts'] = sanitize_boot_opts(entry[1]) + + return entry_dict + + +def parse_menuentries(grub_path: str) -> list: + """Parse all GRUB menuentries + + Args: + grub_path (str): a path to GRUB config file + + Returns: + list: list with menu items (each item is a dict) + """ + menuentries = [] + # read configuration file + config_text = Path(grub_path).read_text() + # parse menuentries to tuples (version, options) + regex_filter = compile(REGEX_MENUENTRY, MULTILINE) + filter_results = regex_filter.findall(config_text) + # parse each entry + for entry in filter_results: + menuentries.append(parse_entry(entry)) + + return menuentries + + +def prune_vyos_versions(root_dir: str = '') -> None: + """Delete vyos-versions files of registered images subsequently deleted + or renamed by legacy image-tools + + Args: + root_dir (str): an optional path to the root directory + """ + if not root_dir: + root_dir = disk.find_persistence() + + version_files = Path(f'{root_dir}/{grub.GRUB_DIR_VYOS_VERS}').glob('*.cfg') + + for file in version_files: + version = Path(file).stem + if not Path(f'{root_dir}/boot/{version}').is_dir(): + grub.version_del(version, root_dir) + + +def update_cfg_ver(root_dir:str = '') -> int: + """Get minumum version of image-tools across all installed images + + Args: + root_dir (str): an optional path to the root directory + + Returns: + int: minimum version of image-tools + """ + if not root_dir: + root_dir = disk.find_persistence() + + prune_vyos_versions(root_dir) + + images_details = image.get_images_details() + cfg_version = min(d['tools_version'] for d in images_details) + + return cfg_version + + +def get_default(data: dict, root_dir: str = '') -> Union[int, None]: + """Translate default version to menuentry index + + Args: + data (dict): boot data + root_dir (str): an optional path to the root directory + + Returns: + int: index of default version in menu_entries or None + """ + if not root_dir: + root_dir = disk.find_persistence() + + grub_cfg_main = f'{root_dir}/{grub.GRUB_CFG_MAIN}' + + menu_entries = data.get('versions', []) + console_type = data.get('console_type', 'tty') + console_num = data.get('console_num', '0') + image_name = image.get_default_image() + + sublist = list(filter(lambda x: (x.get('version') == image_name and + x.get('console_type') == console_type and + x.get('bootmode') == 'normal'), + menu_entries)) + + if sublist: + return menu_entries.index(sublist[0]) + + return None + + +def update_version_list(root_dir: str = '') -> list[dict]: + """Update list of dicts of installed version boot data + + Args: + root_dir (str): an optional path to the root directory + + Returns: + list: list of dicts of installed version boot data + """ + if not root_dir: + root_dir = disk.find_persistence() + + grub_cfg_main = f'{root_dir}/{grub.GRUB_CFG_MAIN}' + + # get list of versions in menuentries + menu_entries = parse_menuentries(grub_cfg_main) + menu_versions = find_versions(menu_entries) + + # remove deprecated console-type ttyUSB + menu_entries = list(filter(lambda x: x.get('console_type') != 'ttyUSB', + menu_entries)) + + # get list of versions added/removed by image-tools + current_versions = grub.version_list(root_dir) + + remove = list(set(menu_versions) - set(current_versions)) + for ver in remove: + menu_entries = list(filter(lambda x: x.get('version') != ver, + menu_entries)) + + # reset boot_opts in case of config update + for entry in menu_entries: + entry['boot_opts'] = grub.get_boot_opts(entry['version']) + + add = list(set(current_versions) - set(menu_versions)) + for ver in add: + last = menu_entries[0].get('version') + new = deepcopy(list(filter(lambda x: x.get('version') == last, + menu_entries))) + for e in new: + boot_opts = grub.get_boot_opts(ver) + e.update({'version': ver, 'boot_opts': boot_opts}) + + menu_entries = new + menu_entries + + return menu_entries + + +def grub_cfg_fields(root_dir: str = '') -> dict: + """Gather fields for rendering grub.cfg + + Args: + root_dir (str): an optional path to the root directory + + Returns: + dict: dictionary for rendering TMPL_GRUB_COMPAT + """ + if not root_dir: + root_dir = disk.find_persistence() + + grub_cfg_main = f'{root_dir}/{grub.GRUB_CFG_MAIN}' + grub_vars = f'{root_dir}/{grub.CFG_VYOS_VARS}' + + fields = grub.vars_read(grub_vars) + # 'default' and 'timeout' from legacy grub.cfg resets 'default' to + # index, rather than uuid + fields |= grub.vars_read(grub_cfg_main) + + fields['tools_version'] = SYSTEM_CFG_VER + menu_entries = update_version_list(root_dir) + fields['versions'] = menu_entries + + default = get_default(fields, root_dir) + if default is not None: + fields['default'] = default + + modules = grub.modules_read(grub_cfg_main) + fields['modules'] = modules + + unparsed = filter_unparsed(grub_cfg_main).splitlines() + search_root = next((x for x in unparsed if 'search' in x), '') + fields['search_root'] = search_root + + return fields + + +def render_grub_cfg(root_dir: str = '') -> None: + """Render grub.cfg for legacy compatibility""" + if not root_dir: + root_dir = disk.find_persistence() + + grub_cfg_main = f'{root_dir}/{grub.GRUB_CFG_MAIN}' + + fields = grub_cfg_fields(root_dir) + render(grub_cfg_main, TMPL_GRUB_COMPAT, fields) + + +def grub_cfg_update(func): + """Decorator to update grub.cfg after function call""" + @wraps(func) + def wrapper(*args, **kwargs): + ret = func(*args, **kwargs) + if mode(): + render_grub_cfg() + return ret + return wrapper diff --git a/python/vyos/system/disk.py b/python/vyos/system/disk.py new file mode 100644 index 0000000..c8908cd --- /dev/null +++ b/python/vyos/system/disk.py @@ -0,0 +1,242 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from json import loads as json_loads +from os import sync +from dataclasses import dataclass +from time import sleep + +from psutil import disk_partitions + +from vyos.utils.process import run, cmd + + +@dataclass +class DiskDetails: + """Disk details""" + name: str + partition: dict[str, str] + + +def disk_cleanup(drive_path: str) -> None: + """Clean up disk partition table (MBR and GPT) + Remove partition and device signatures. + Zeroize primary and secondary headers - first and last 17408 bytes + (512 bytes * 34 LBA) on a drive + + Args: + drive_path (str): path to a drive that needs to be cleaned + """ + partitions: list[str] = partition_list(drive_path) + for partition in partitions: + run(f'wipefs -af {partition}') + run(f'wipefs -af {drive_path}') + run(f'sgdisk -Z {drive_path}') + + +def find_persistence() -> str: + """Find a mountpoint for persistence storage + + Returns: + str: Path where 'persistance' pertition is mounted, Empty if not found + """ + mounted_partitions = disk_partitions() + for partition in mounted_partitions: + if partition.mountpoint.endswith('/persistence'): + return partition.mountpoint + return '' + + +def parttable_create(drive_path: str, root_size: int) -> None: + """Create a hybrid MBR/GPT partition table + 0-2047 first sectors are free + 2048-4095 sectors - BIOS Boot Partition + 4096 + 256 MB - EFI system partition + Everything else till the end of a drive - Linux partition + + Args: + drive_path (str): path to a drive + """ + if not root_size: + root_size_text: str = '+100%' + else: + root_size_text: str = str(root_size) + command = f'sgdisk -a1 -n1:2048:4095 -t1:EF02 -n2:4096:+256M -t2:EF00 \ + -n3:0:+{root_size_text}K -t3:8300 {drive_path}' + + run(command) + # update partitons in kernel + sync() + run(f'partx -u {drive_path}') + + partitions: list[str] = partition_list(drive_path) + + disk: DiskDetails = DiskDetails( + name = drive_path, + partition = { + 'efi': next(x for x in partitions if x.endswith('2')), + 'root': next(x for x in partitions if x.endswith('3')) + } + ) + + return disk + + +def partition_list(drive_path: str) -> list[str]: + """Get a list of partitions on a drive + + Args: + drive_path (str): path to a drive + + Returns: + list[str]: a list of partition paths + """ + lsblk: str = cmd(f'lsblk -Jp {drive_path}') + drive_info: dict = json_loads(lsblk) + device: list = drive_info.get('blockdevices') + children: list[str] = device[0].get('children', []) if device else [] + partitions: list[str] = [child.get('name') for child in children] + return partitions + + +def partition_parent(partition_path: str) -> str: + """Get a parent device for a partition + + Args: + partition (str): path to a partition + + Returns: + str: path to a parent device + """ + parent: str = cmd(f'lsblk -ndpo pkname {partition_path}') + return parent + + +def from_partition(partition_path: str) -> DiskDetails: + drive_path: str = partition_parent(partition_path) + partitions: list[str] = partition_list(drive_path) + + disk: DiskDetails = DiskDetails( + name = drive_path, + partition = { + 'efi': next(x for x in partitions if x.endswith('2')), + 'root': next(x for x in partitions if x.endswith('3')) + } + ) + + return disk + +def filesystem_create(partition: str, fstype: str) -> None: + """Create a filesystem on a partition + + Args: + partition (str): path to a partition (for example: '/dev/sda1') + fstype (str): filesystem type ('efi' or 'ext4') + """ + if fstype == 'efi': + command = 'mkfs -t fat -n EFI' + run(f'{command} {partition}') + if fstype == 'ext4': + command = 'mkfs -t ext4 -L persistence' + run(f'{command} {partition}') + + +def partition_mount(partition: str, + path: str, + fsype: str = '', + overlay_params: dict[str, str] = {}) -> bool: + """Mount a partition into a path + + Args: + partition (str): path to a partition (for example: '/dev/sda1') + path (str): a path where to mount + fsype (str): optionally, set fstype ('squashfs', 'overlay', 'iso9660') + overlay_params (dict): optionally, set overlay parameters. + Defaults to None. + + Returns: + bool: True on success + """ + if fsype in ['squashfs', 'iso9660']: + command: str = f'mount -o loop,ro -t {fsype} {partition} {path}' + if fsype == 'overlay' and overlay_params: + command: str = f'mount -t overlay -o noatime,\ + upperdir={overlay_params["upperdir"]},\ + lowerdir={overlay_params["lowerdir"]},\ + workdir={overlay_params["workdir"]} overlay {path}' + + else: + command = f'mount {partition} {path}' + + rc = run(command) + if rc == 0: + return True + + return False + + +def partition_umount(partition: str = '', path: str = '') -> None: + """Umount a partition by a partition name or a path + + Args: + partition (str): path to a partition (for example: '/dev/sda1') + path (str): a path where a partition is mounted + """ + if partition: + command = f'umount {partition}' + run(command) + if path: + command = f'umount {path}' + run(command) + + +def find_device(mountpoint: str) -> str: + """Find a device by mountpoint + + Returns: + str: Path to device, Empty if not found + """ + mounted_partitions = disk_partitions(all=True) + for partition in mounted_partitions: + if partition.mountpoint == mountpoint: + return partition.mountpoint + return '' + + +def wait_for_umount(mountpoint: str = '') -> None: + """Wait (within reason) for umount to complete + """ + i = 0 + while find_device(mountpoint): + i += 1 + if i == 5: + print(f'Warning: {mountpoint} still mounted') + break + sleep(1) + + +def disks_size() -> dict[str, int]: + """Get a dictionary with physical disks and their sizes + + Returns: + dict[str, int]: a dictionary with name: size mapping + """ + disks_size: dict[str, int] = {} + lsblk: str = cmd('lsblk -Jbp') + blk_list = json_loads(lsblk) + for device in blk_list.get('blockdevices'): + if device['type'] == 'disk': + disks_size.update({device['name']: device['size']}) + return disks_size diff --git a/python/vyos/system/grub.py b/python/vyos/system/grub.py new file mode 100644 index 0000000..de8303e --- /dev/null +++ b/python/vyos/system/grub.py @@ -0,0 +1,464 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import platform + +from pathlib import Path +from re import MULTILINE, compile as re_compile +from shutil import copy2 +from uuid import uuid5, NAMESPACE_URL, UUID + +from vyos.template import render +from vyos.utils.process import cmd, rc_cmd +from vyos.system import disk + +# Define variables +GRUB_DIR_MAIN: str = '/boot/grub' +GRUB_CFG_MAIN: str = f'{GRUB_DIR_MAIN}/grub.cfg' +GRUB_DIR_VYOS: str = f'{GRUB_DIR_MAIN}/grub.cfg.d' +CFG_VYOS_HEADER: str = f'{GRUB_DIR_VYOS}/00-vyos-header.cfg' +CFG_VYOS_MODULES: str = f'{GRUB_DIR_VYOS}/10-vyos-modules-autoload.cfg' +CFG_VYOS_VARS: str = f'{GRUB_DIR_VYOS}/20-vyos-defaults-autoload.cfg' +CFG_VYOS_COMMON: str = f'{GRUB_DIR_VYOS}/25-vyos-common-autoload.cfg' +CFG_VYOS_PLATFORM: str = f'{GRUB_DIR_VYOS}/30-vyos-platform-autoload.cfg' +CFG_VYOS_MENU: str = f'{GRUB_DIR_VYOS}/40-vyos-menu-autoload.cfg' +CFG_VYOS_OPTIONS: str = f'{GRUB_DIR_VYOS}/50-vyos-options.cfg' +GRUB_DIR_VYOS_VERS: str = f'{GRUB_DIR_VYOS}/vyos-versions' + +TMPL_VYOS_VERSION: str = 'grub/grub_vyos_version.j2' +TMPL_GRUB_VARS: str = 'grub/grub_vars.j2' +TMPL_GRUB_MAIN: str = 'grub/grub_main.j2' +TMPL_GRUB_MENU: str = 'grub/grub_menu.j2' +TMPL_GRUB_MODULES: str = 'grub/grub_modules.j2' +TMPL_GRUB_OPTS: str = 'grub/grub_options.j2' +TMPL_GRUB_COMMON: str = 'grub/grub_common.j2' + +# default boot options +BOOT_OPTS_STEM: str = 'boot=live rootdelay=5 noautologin net.ifnames=0 biosdevname=0 vyos-union=/boot/' + +# prepare regexes +REGEX_GRUB_VARS: str = r'^set (?P<variable_name>\w+)=[\'"]?(?P<variable_value>.*)(?<![\'"])[\'"]?$' +REGEX_GRUB_MODULES: str = r'^insmod (?P<module_name>.+)$' +REGEX_KERNEL_CMDLINE: str = r'^BOOT_IMAGE=/(?P<boot_type>boot|live)/((?P<image_version>.+)/)?vmlinuz.*$' +REGEX_GRUB_BOOT_OPTS: str = r'^\s*set boot_opts="(?P<boot_opts>[^$]+)"$' + + +def install(drive_path: str, boot_dir: str, efi_dir: str, id: str = 'VyOS', chroot : str = "") -> None: + """Install GRUB for both BIOS and EFI modes (hybrid boot) + + Args: + drive_path (str): path to a drive where GRUB must be installed + boot_dir (str): a path to '/boot' directory + efi_dir (str): a path to '/boot/efi' directory + """ + + if chroot: + chroot_cmd = f"chroot {chroot}" + else: + chroot_cmd = "" + + efi_installation_arch = "x86_64" + if platform.machine() == "aarch64": + efi_installation_arch = "arm64" + elif platform.machine() == "x86_64": + cmd( + f'{chroot_cmd} grub-install --no-floppy --target=i386-pc \ + --boot-directory={boot_dir} {drive_path} --force' + ) + + cmd( + f'{chroot_cmd} grub-install --no-floppy --recheck --target={efi_installation_arch}-efi \ + --force-extra-removable --boot-directory={boot_dir} \ + --efi-directory={efi_dir} --bootloader-id="{id}" \ + --uefi-secure-boot' + ) + + +def gen_version_uuid(version_name: str) -> str: + """Generate unique ID from version name + + Use UUID5 / NAMESPACE_URL with prefix `uuid5-` + + Args: + version_name (str): version name + + Returns: + str: generated unique ID + """ + ver_uuid: UUID = uuid5(NAMESPACE_URL, version_name) + ver_id: str = f'uuid5-{ver_uuid}' + return ver_id + + +def version_add(version_name: str, + root_dir: str = '', + boot_opts: str = '', + boot_opts_config = None) -> None: + """Add a new VyOS version to GRUB loader configuration + + Args: + vyos_version (str): VyOS version name + root_dir (str): an optional path to the root directory. + Defaults to empty. + boot_opts (str): an optional boot options for Linux kernel. + Defaults to empty. + """ + if not root_dir: + root_dir = disk.find_persistence() + version_config: str = f'{root_dir}/{GRUB_DIR_VYOS_VERS}/{version_name}.cfg' + render( + version_config, TMPL_VYOS_VERSION, { + 'version_name': version_name, + 'version_uuid': gen_version_uuid(version_name), + 'boot_opts_default': BOOT_OPTS_STEM + version_name, + 'boot_opts': boot_opts, + 'boot_opts_config': boot_opts_config + }) + + +def version_del(vyos_version: str, root_dir: str = '') -> None: + """Delete a VyOS version from GRUB loader configuration + + Args: + vyos_version (str): VyOS version name + root_dir (str): an optional path to the root directory. + Defaults to empty. + """ + if not root_dir: + root_dir = disk.find_persistence() + version_config: str = f'{root_dir}/{GRUB_DIR_VYOS_VERS}/{vyos_version}.cfg' + Path(version_config).unlink(missing_ok=True) + + +def version_list(root_dir: str = '') -> list[str]: + """Generate a list with installed VyOS versions + + Args: + root_dir (str): an optional path to the root directory. + Defaults to empty. + + Returns: + list: A list with versions names + + N.B. coreutils stat reports st_birthtime, but not available in + Path.stat()/os.stat() + """ + if not root_dir: + root_dir = disk.find_persistence() + versions_files = Path(f'{root_dir}/{GRUB_DIR_VYOS_VERS}').glob('*.cfg') + versions_order: dict[str, int] = {} + for file in versions_files: + p = Path(root_dir).joinpath('boot').joinpath(file.stem) + command = f'stat -c %W {p.as_posix()}' + rc, out = rc_cmd(command) + if rc == 0: + versions_order[file.stem] = int(out) + versions_order = sorted(versions_order, key=versions_order.get, reverse=True) + versions_list: list[str] = list(versions_order) + + return versions_list + + +def read_env(env_file: str = '') -> dict[str, str]: + """Read GRUB environment + + Args: + env_file (str, optional): a path to grub environment file. + Defaults to empty. + + Returns: + dict: dictionary with GRUB environment + """ + if not env_file: + root_dir: str = disk.find_persistence() + env_file = f'{root_dir}/{GRUB_DIR_MAIN}/grubenv' + + env_content: str = cmd(f'grub-editenv {env_file} list').splitlines() + regex_filter = re_compile(r'^(?P<variable_name>.*)=(?P<variable_value>.*)$') + env_dict: dict[str, str] = {} + for env_item in env_content: + search_result = regex_filter.fullmatch(env_item) + if search_result: + search_result_dict: dict[str, str] = search_result.groupdict() + variable_name: str = search_result_dict.get('variable_name', '') + variable_value: str = search_result_dict.get('variable_value', '') + if variable_name and variable_value: + env_dict.update({variable_name: variable_value}) + return env_dict + + +def get_cfg_ver(root_dir: str = '') -> int: + """Get current version of GRUB configuration + + Args: + root_dir (str, optional): an optional path to the root directory. + Defaults to empty. + + Returns: + int: a configuration version + """ + if not root_dir: + root_dir = disk.find_persistence() + + cfg_ver: str = vars_read(f'{root_dir}/{CFG_VYOS_HEADER}').get( + 'VYOS_CFG_VER') + if cfg_ver: + cfg_ver_int: int = int(cfg_ver) + else: + cfg_ver_int: int = 0 + return cfg_ver_int + + +def write_cfg_ver(cfg_ver: int, root_dir: str = '') -> None: + """Write version number of GRUB configuration + + Args: + cfg_ver (int): a version number to write + root_dir (str, optional): an optional path to the root directory. + Defaults to empty. + + Returns: + int: a configuration version + """ + if not root_dir: + root_dir = disk.find_persistence() + + vars_file: str = f'{root_dir}/{CFG_VYOS_HEADER}' + vars_current: dict[str, str] = vars_read(vars_file) + vars_current['VYOS_CFG_VER'] = str(cfg_ver) + vars_write(vars_file, vars_current) + + +def vars_read(grub_cfg: str) -> dict[str, str]: + """Read variables from a GRUB configuration file + + Args: + grub_cfg (str): a path to the GRUB config file + + Returns: + dict: a dictionary with variables and values + """ + vars_dict: dict[str, str] = {} + regex_filter = re_compile(REGEX_GRUB_VARS) + try: + config_text: list[str] = Path(grub_cfg).read_text().splitlines() + except FileNotFoundError: + return vars_dict + for line in config_text: + search_result = regex_filter.fullmatch(line) + if search_result: + search_dict = search_result.groupdict() + variable_name: str = search_dict.get('variable_name', '') + variable_value: str = search_dict.get('variable_value', '') + if variable_name and variable_value: + vars_dict.update({variable_name: variable_value}) + return vars_dict + + +def modules_read(grub_cfg: str) -> list[str]: + """Read modules list from a GRUB configuration file + + Args: + grub_cfg (str): a path to the GRUB config file + + Returns: + list: a list with modules to load + """ + mods_list: list[str] = [] + regex_filter = re_compile(REGEX_GRUB_MODULES, MULTILINE) + try: + config_text = Path(grub_cfg).read_text() + except FileNotFoundError: + return mods_list + mods_list = regex_filter.findall(config_text) + + return mods_list + + +def modules_write(grub_cfg: str, mods_list: list[str]) -> None: + """Write modules list to a GRUB configuration file (overwrite everything) + + Args: + grub_cfg (str): a path to GRUB configuration file + mods_list (list): a list with modules to load + """ + render(grub_cfg, TMPL_GRUB_MODULES, {'mods_list': mods_list}) + + +def vars_write(grub_cfg: str, grub_vars: dict[str, str]) -> None: + """Write variables to a GRUB configuration file (overwrite everything) + + Args: + grub_cfg (str): a path to GRUB configuration file + grub_vars (dict): a dictionary with new variables + """ + render(grub_cfg, TMPL_GRUB_VARS, {'vars': grub_vars}) + +def get_boot_opts(version_name: str, root_dir: str = '') -> str: + """Read boot_opts setting from version file; return default setting on + any failure. + + Args: + version_name (str): version name + root_dir (str, optional): an optional path to the root directory. + Defaults to empty. + """ + if not root_dir: + root_dir = disk.find_persistence() + + boot_opts_default: str = BOOT_OPTS_STEM + version_name + boot_opts: str = '' + regex_filter = re_compile(REGEX_GRUB_BOOT_OPTS) + version_config: str = f'{root_dir}/{GRUB_DIR_VYOS_VERS}/{version_name}.cfg' + try: + config_text: list[str] = Path(version_config).read_text().splitlines() + except FileNotFoundError: + return boot_opts_default + for line in config_text: + search_result = regex_filter.fullmatch(line) + if search_result: + search_dict = search_result.groupdict() + boot_opts = search_dict.get('boot_opts', '') + break + + if not boot_opts: + boot_opts = boot_opts_default + + return boot_opts + +def set_default(version_name: str, root_dir: str = '') -> None: + """Set version as default boot entry + + Args: + version_name (str): version name + root_dir (str, optional): an optional path to the root directory. + Defaults to empty. + """ + if not root_dir: + root_dir = disk.find_persistence() + + vars_file = f'{root_dir}/{CFG_VYOS_VARS}' + vars_current = vars_read(vars_file) + vars_current['default'] = gen_version_uuid(version_name) + vars_write(vars_file, vars_current) + + +def common_write(root_dir: str = '', grub_common: dict[str, str] = {}) -> None: + """Write common GRUB configuration file (overwrite everything) + + Args: + root_dir (str, optional): an optional path to the root directory. + Defaults to empty. + """ + if not root_dir: + root_dir = disk.find_persistence() + common_config = f'{root_dir}/{CFG_VYOS_COMMON}' + render(common_config, TMPL_GRUB_COMMON, grub_common) + + +def create_structure(root_dir: str = '') -> None: + """Create GRUB directories structure + + Args: + root_dir (str, optional): an optional path to the root directory. + Defaults to ''. + """ + if not root_dir: + root_dir = disk.find_persistence() + + Path(f'{root_dir}/{GRUB_DIR_VYOS_VERS}').mkdir(parents=True, exist_ok=True) + + +def set_console_type(console_type: str, root_dir: str = '') -> None: + """Write default console type to GRUB configuration + + Args: + console_type (str): a default console type + root_dir (str, optional): an optional path to the root directory. + Defaults to empty. + """ + if not root_dir: + root_dir = disk.find_persistence() + + vars_file: str = f'{root_dir}/{CFG_VYOS_VARS}' + vars_current: dict[str, str] = vars_read(vars_file) + vars_current['console_type'] = str(console_type) + vars_write(vars_file, vars_current) + +def set_console_speed(console_speed: str, root_dir: str = '') -> None: + """Write default console speed to GRUB configuration + + Args: + console_speed (str): default console speed + root_dir (str, optional): an optional path to the root directory. + Defaults to empty. + """ + if not root_dir: + root_dir = disk.find_persistence() + + vars_file: str = f'{root_dir}/{CFG_VYOS_VARS}' + vars_current: dict[str, str] = vars_read(vars_file) + vars_current['console_speed'] = str(console_speed) + vars_write(vars_file, vars_current) + +def set_kernel_cmdline_options(cmdline_options: str, version_name: str, + root_dir: str = '') -> None: + """Write additional cmdline options to GRUB configuration + + Args: + cmdline_options (str): cmdline options to add to default boot line + version_name (str): image version name + root_dir (str, optional): an optional path to the root directory. + """ + if not root_dir: + root_dir = disk.find_persistence() + + version_add(version_name=version_name, root_dir=root_dir, + boot_opts_config=cmdline_options) + + +def sort_inodes(dir_path: str) -> None: + """Sort inodes for files inside a folder + Regenerate inodes for each file to get the same order for both inodes + and file names + + GRUB iterates files by inodes, not alphabetically. Therefore, if we + want to read them in proper order, we need to sort inodes for all + config files in a folder. + + Args: + dir_path (str): a path to directory + """ + dir_content: list[Path] = sorted(Path(dir_path).iterdir()) + temp_list_old: list[Path] = [] + temp_list_new: list[Path] = [] + + # create a copy of all files, to get new inodes + for item in dir_content: + # skip directories + if item.is_dir(): + continue + # create a new copy of file with a temporary name + copy_path = Path(f'{item.as_posix()}_tmp') + copy2(item, Path(copy_path)) + temp_list_old.append(item) + temp_list_new.append(copy_path) + + # delete old files and rename new ones + for item in temp_list_old: + item.unlink() + for item in temp_list_new: + new_name = Path(f'{item.as_posix()[0:-4]}') + item.rename(new_name) diff --git a/python/vyos/system/grub_util.py b/python/vyos/system/grub_util.py new file mode 100644 index 0000000..4a3d879 --- /dev/null +++ b/python/vyos/system/grub_util.py @@ -0,0 +1,70 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.system import disk, grub, image, compat + +@compat.grub_cfg_update +def set_console_speed(console_speed: str, root_dir: str = '') -> None: + """Write default console speed to GRUB configuration + + Args: + console_speed (str): default console speed + root_dir (str, optional): an optional path to the root directory. + Defaults to empty. + """ + if not root_dir: + root_dir = disk.find_persistence() + + grub.set_console_speed(console_speed, root_dir) + +@image.if_not_live_boot +def update_console_speed(console_speed: str, root_dir: str = '') -> None: + """Update console_speed if different from current value""" + + if not root_dir: + root_dir = disk.find_persistence() + + vars_file: str = f'{root_dir}/{grub.CFG_VYOS_VARS}' + vars_current: dict[str, str] = grub.vars_read(vars_file) + console_speed_current = vars_current.get('console_speed', None) + if console_speed != console_speed_current: + set_console_speed(console_speed, root_dir) + +@compat.grub_cfg_update +def set_kernel_cmdline_options(cmdline_options: str, version: str = '', + root_dir: str = '') -> None: + """Write Kernel CLI cmdline options to GRUB configuration""" + if not root_dir: + root_dir = disk.find_persistence() + + if not version: + version = image.get_running_image() + + grub.set_kernel_cmdline_options(cmdline_options, version, root_dir) + +@image.if_not_live_boot +def update_kernel_cmdline_options(cmdline_options: str, + root_dir: str = '') -> None: + """Update Kernel custom cmdline options""" + if not root_dir: + root_dir = disk.find_persistence() + + version = image.get_running_image() + + boot_opts_current = grub.get_boot_opts(version, root_dir) + boot_opts_proposed = grub.BOOT_OPTS_STEM + f'{version} {cmdline_options}' + + if boot_opts_proposed != boot_opts_current: + set_kernel_cmdline_options(cmdline_options, version, root_dir) diff --git a/python/vyos/system/image.py b/python/vyos/system/image.py new file mode 100644 index 0000000..aae52e7 --- /dev/null +++ b/python/vyos/system/image.py @@ -0,0 +1,283 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from pathlib import Path +from re import compile as re_compile +from functools import wraps +from tempfile import TemporaryDirectory +from typing import TypedDict +from json import loads + +from vyos.defaults import directories +from vyos.system import disk, grub + +# Define variables +GRUB_DIR_MAIN: str = '/boot/grub' +GRUB_DIR_VYOS: str = f'{GRUB_DIR_MAIN}/grub.cfg.d' +CFG_VYOS_VARS: str = f'{GRUB_DIR_VYOS}/20-vyos-defaults-autoload.cfg' +GRUB_DIR_VYOS_VERS: str = f'{GRUB_DIR_VYOS}/vyos-versions' +# prepare regexes +REGEX_KERNEL_CMDLINE: str = r'^BOOT_IMAGE=/(?P<boot_type>boot|live)/((?P<image_version>.+)/)?vmlinuz.*$' +REGEX_SYSTEM_CFG_VER: str = r'(\r\n|\r|\n)SYSTEM_CFG_VER\s*=\s*(?P<cfg_ver>\d+)(\r\n|\r|\n)' + + +# structures definitions +class ImageDetails(TypedDict): + name: str + version: str + tools_version: int + disk_ro: int + disk_rw: int + disk_total: int + + +class BootDetails(TypedDict): + image_default: str + image_running: str + images_available: list[str] + console_type: str + console_num: int + + +def bootmode_detect() -> str: + """Detect system boot mode + + Returns: + str: 'bios' or 'efi' + """ + if Path('/sys/firmware/efi/').exists(): + return 'efi' + else: + return 'bios' + + +def get_image_version(mount_path: str) -> str: + """Extract version name from rootfs mounted at mount_path + + Args: + mount_path (str): mount path of rootfs + + Returns: + str: version name + """ + version_file: str = Path( + f'{mount_path}/opt/vyatta/etc/version').read_text() + version_name: str = version_file.lstrip('Version: ').strip() + + return version_name + + +def get_image_tools_version(mount_path: str) -> int: + """Extract image-tools version from rootfs mounted at mount_path + + Args: + mount_path (str): mount path of rootfs + + Returns: + str: image-tools version + """ + try: + version_file: str = Path( + f'{mount_path}/usr/lib/python3/dist-packages/vyos/system/__init__.py').read_text() + except FileNotFoundError: + system_cfg_ver: int = 0 + else: + res = re_compile(REGEX_SYSTEM_CFG_VER).search(version_file) + system_cfg_ver: int = int(res.groupdict().get('cfg_ver', 0)) + + return system_cfg_ver + + +def get_versions(image_name: str, root_dir: str = '') -> dict[str, str]: + """Return versions of image and image-tools + + Args: + image_name (str): a name of an image + root_dir (str, optional): an optional path to the root directory. + Defaults to ''. + + Returns: + dict[str, int]: a dictionary with versions of image and image-tools + """ + if not root_dir: + root_dir = disk.find_persistence() + + squashfs_file: str = next( + Path(f'{root_dir}/boot/{image_name}').glob('*.squashfs')).as_posix() + with TemporaryDirectory() as squashfs_mounted: + disk.partition_mount(squashfs_file, squashfs_mounted, 'squashfs') + + image_version: str = get_image_version(squashfs_mounted) + image_tools_version: int = get_image_tools_version(squashfs_mounted) + + disk.partition_umount(squashfs_file) + + versions: dict[str, int] = { + 'image': image_version, + 'image-tools': image_tools_version + } + + return versions + + +def get_details(image_name: str, root_dir: str = '') -> ImageDetails: + """Return information about image + + Args: + image_name (str): a name of an image + root_dir (str, optional): an optional path to the root directory. + Defaults to ''. + + Returns: + ImageDetails: a dictionary with details about an image (name, size) + """ + if not root_dir: + root_dir = disk.find_persistence() + + versions = get_versions(image_name, root_dir) + image_version: str = versions.get('image', '') + image_tools_version: int = versions.get('image-tools', 0) + + image_path: Path = Path(f'{root_dir}/boot/{image_name}') + image_path_rw: Path = Path(f'{root_dir}/boot/{image_name}/rw') + + image_disk_ro: int = int() + for item in image_path.iterdir(): + if not item.is_symlink(): + image_disk_ro += item.stat().st_size + + image_disk_rw: int = int() + for item in image_path_rw.rglob('*'): + if not item.is_symlink(): + image_disk_rw += item.stat().st_size + + image_details: ImageDetails = { + 'name': image_name, + 'version': image_version, + 'tools_version': image_tools_version, + 'disk_ro': image_disk_ro, + 'disk_rw': image_disk_rw, + 'disk_total': image_disk_ro + image_disk_rw + } + + return image_details + + +def get_images_details() -> list[ImageDetails]: + """Return information about all images + + Returns: + list[ImageDetails]: a list of dictionaries with details about images + """ + images: list[str] = grub.version_list() + images_details: list[ImageDetails] = list() + for image_name in images: + images_details.append(get_details(image_name)) + + return images_details + + +def get_running_image() -> str: + """Find currently running image name + + Returns: + str: image name + """ + running_image: str = '' + regex_filter = re_compile(REGEX_KERNEL_CMDLINE) + cmdline: str = Path('/proc/cmdline').read_text() + running_image_result = regex_filter.match(cmdline) + if running_image_result: + running_image: str = running_image_result.groupdict().get( + 'image_version', '') + # we need to have a fallback for live systems: + # explicit read from version file + if not running_image: + json_data: str = Path(directories['data']).joinpath('version.json').read_text() + dict_data: dict = loads(json_data) + running_image: str = dict_data['version'] + + return running_image + + +def get_default_image(root_dir: str = '') -> str: + """Get default boot entry + + Args: + root_dir (str, optional): an optional path to the root directory. + Defaults to empty. + Returns: + str: a version name + """ + if not root_dir: + root_dir = disk.find_persistence() + + vars_file: str = f'{root_dir}/{CFG_VYOS_VARS}' + vars_current: dict[str, str] = grub.vars_read(vars_file) + default_uuid: str = vars_current.get('default', '') + if default_uuid: + images_list: list[str] = grub.version_list(root_dir) + for image_name in images_list: + if default_uuid == grub.gen_version_uuid(image_name): + return image_name + return '' + else: + return '' + + +def validate_name(image_name: str) -> bool: + """Validate image name + + Args: + image_name (str): suggested image name + + Returns: + bool: validation result + """ + regex_filter = re_compile(r'^[\w\.+-]{1,64}$') + if regex_filter.match(image_name): + return True + return False + + +def is_live_boot() -> bool: + """Detect live booted system + + Returns: + bool: True if the system currently booted in live mode + """ + regex_filter = re_compile(REGEX_KERNEL_CMDLINE) + cmdline: str = Path('/proc/cmdline').read_text() + running_image_result = regex_filter.match(cmdline) + if running_image_result: + boot_type: str = running_image_result.groupdict().get('boot_type', '') + if boot_type == 'boot': + return False + return True + +def if_not_live_boot(func): + """Decorator to call function only if not live boot""" + @wraps(func) + def wrapper(*args, **kwargs): + if not is_live_boot(): + ret = func(*args, **kwargs) + return ret + return None + return wrapper + +def is_running_as_container() -> bool: + if Path('/.dockerenv').exists(): + return True + return False diff --git a/python/vyos/system/raid.py b/python/vyos/system/raid.py new file mode 100644 index 0000000..5b33d34 --- /dev/null +++ b/python/vyos/system/raid.py @@ -0,0 +1,122 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +"""RAID related functions""" + +from pathlib import Path +from shutil import copy +from dataclasses import dataclass + +from vyos.utils.process import cmd, run +from vyos.system import disk + + +@dataclass +class RaidDetails: + """RAID type""" + name: str + level: str + members: list[str] + disks: list[disk.DiskDetails] + + +def raid_create(raid_members: list[str], + raid_name: str = 'md0', + raid_level: str = 'raid1') -> None: + """Create a RAID array + + Args: + raid_name (str): a name of array (data, backup, test, etc.) + raid_members (list[str]): a list of array members + raid_level (str, optional): an array level. Defaults to 'raid1'. + """ + raid_devices_num: int = len(raid_members) + raid_members_str: str = ' '.join(raid_members) + for part in raid_members: + drive: str = disk.partition_parent(part) + # set partition type GUID for raid member; cf. + # https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs + command: str = f'sgdisk --typecode=3:A19D880F-05FC-4D3B-A006-743F0F84911E {drive}' + cmd(command) + command: str = f'mdadm --create /dev/{raid_name} -R --metadata=1.0 \ + --raid-devices={raid_devices_num} --level={raid_level} \ + {raid_members_str}' + + cmd(command) + + raid = RaidDetails( + name = f'/dev/{raid_name}', + level = raid_level, + members = raid_members, + disks = [disk.from_partition(m) for m in raid_members] + ) + + return raid + +def clear(): + """Deactivate all RAID arrays""" + command: str = 'mdadm --examine --scan' + raid_config = cmd(command) + if not raid_config: + return + command: str = 'mdadm --run /dev/md?*' + run(command) + command: str = 'mdadm --assemble --scan --auto=yes --symlink=no' + run(command) + command: str = 'mdadm --stop --scan' + run(command) + + +def update_initramfs() -> None: + """Update initramfs""" + mdadm_script = '/etc/initramfs-tools/scripts/local-top/mdadm' + copy('/usr/share/initramfs-tools/scripts/local-block/mdadm', mdadm_script) + p = Path(mdadm_script) + p.write_text(p.read_text().replace('$((COUNT + 1))', '20')) + command: str = 'update-initramfs -u' + cmd(command) + +def update_default(target_dir: str) -> None: + """Update /etc/default/mdadm to start MD monitoring daemon at boot + """ + source_mdadm_config = '/etc/default/mdadm' + target_mdadm_config = Path(target_dir).joinpath('/etc/default/mdadm') + target_mdadm_config_dir = Path(target_mdadm_config).parent + Path.mkdir(target_mdadm_config_dir, parents=True, exist_ok=True) + s = Path(source_mdadm_config).read_text().replace('START_DAEMON=false', + 'START_DAEMON=true') + Path(target_mdadm_config).write_text(s) + +def get_uuid(device: str) -> str: + """Get UUID of a device""" + command: str = f'tune2fs -l {device}' + l = cmd(command).splitlines() + uuid = next((x for x in l if x.startswith('Filesystem UUID')), '') + return uuid.split(':')[1].strip() if uuid else '' + +def get_uuids(raid_details: RaidDetails) -> tuple[str]: + """Get UUIDs of RAID members + + Args: + raid_name (str): a name of array (data, backup, test, etc.) + + Returns: + tuple[str]: root_disk uuid, root_md uuid + """ + raid_name: str = raid_details.name + root_partition: str = raid_details.members[0] + uuid_root_disk: str = get_uuid(root_partition) + uuid_root_md: str = get_uuid(raid_name) + return uuid_root_disk, uuid_root_md diff --git a/python/vyos/template.py b/python/vyos/template.py new file mode 100644 index 0000000..be9f781 --- /dev/null +++ b/python/vyos/template.py @@ -0,0 +1,990 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import functools +import os + +from jinja2 import Environment +from jinja2 import FileSystemLoader +from jinja2 import ChainableUndefined +from vyos.defaults import directories +from vyos.utils.dict import dict_search_args +from vyos.utils.file import makedir +from vyos.utils.permission import chmod +from vyos.utils.permission import chown + +# We use a mutable global variable for the default template directory +# to make it possible to call scripts from this repository +# outside of live VyOS systems. +# If something (like the image build scripts) +# want to call a script, they can modify the default location +# to the repository path. +DEFAULT_TEMPLATE_DIR = directories["templates"] + +# Holds template filters registered via register_filter() +_FILTERS = {} +_TESTS = {} + +# reuse Environments with identical settings to improve performance +@functools.lru_cache(maxsize=2) +def _get_environment(location=None): + from os import getenv + + if location is None: + loc_loader=FileSystemLoader(DEFAULT_TEMPLATE_DIR) + else: + loc_loader=FileSystemLoader(location) + env = Environment( + # Don't check if template files were modified upon re-rendering + auto_reload=False, + # Cache up to this number of templates for quick re-rendering + cache_size=100, + loader=loc_loader, + trim_blocks=True, + undefined=ChainableUndefined, + extensions=['jinja2.ext.loopcontrols'] + ) + env.filters.update(_FILTERS) + env.tests.update(_TESTS) + return env + + +def register_filter(name, func=None): + """Register a function to be available as filter in templates under given name. + + It can also be used as a decorator, see below in this module for examples. + + :raise RuntimeError: + when trying to register a filter after a template has been rendered already + :raise ValueError: when trying to register a name which was taken already + """ + if func is None: + return functools.partial(register_filter, name) + if _get_environment.cache_info().currsize: + raise RuntimeError( + "Filters can only be registered before rendering the first template" + ) + if name in _FILTERS: + raise ValueError(f"A filter with name {name!r} was registered already") + _FILTERS[name] = func + return func + +def register_test(name, func=None): + """Register a function to be available as test in templates under given name. + + It can also be used as a decorator, see below in this module for examples. + + :raise RuntimeError: + when trying to register a test after a template has been rendered already + :raise ValueError: when trying to register a name which was taken already + """ + if func is None: + return functools.partial(register_test, name) + if _get_environment.cache_info().currsize: + raise RuntimeError( + "Tests can only be registered before rendering the first template" + ) + if name in _TESTS: + raise ValueError(f"A test with name {name!r} was registered already") + _TESTS[name] = func + return func + + +def render_to_string(template, content, formater=None, location=None): + """Render a template from the template directory, raise on any errors. + + :param template: the path to the template relative to the template folder + :param content: the dictionary of variables to put into rendering context + :param formater: + if given, it has to be a callable the rendered string is passed through + + The parsed template files are cached, so rendering the same file multiple times + does not cause as too much overhead. + If used everywhere, it could be changed to load the template from Python + environment variables from an importable Python module generated when the Debian + package is build (recovering the load time and overhead caused by having the + file out of the code). + """ + template = _get_environment(location).get_template(template) + rendered = template.render(content) + if formater is not None: + rendered = formater(rendered) + return rendered + + +def render( + destination, + template, + content, + formater=None, + permission=None, + user=None, + group=None, + location=None, +): + """Render a template from the template directory to a file, raise on any errors. + + :param destination: path to the file to save the rendered template in + :param permission: permission bitmask to set for the output file + :param user: user to own the output file + :param group: group to own the output file + + All other parameters are as for :func:`render_to_string`. + """ + # Create the directory if it does not exist + folder = os.path.dirname(destination) + makedir(folder, user, group) + + # As we are opening the file with 'w', we are performing the rendering before + # calling open() to not accidentally erase the file if rendering fails + rendered = render_to_string(template, content, formater, location) + + # Write to file + with open(destination, "w") as file: + chmod(file.fileno(), permission) + chown(file.fileno(), user, group) + file.write(rendered) + + +################################## +# Custom template filters follow # +################################## +@register_filter('force_to_list') +def force_to_list(value): + """ Convert scalars to single-item lists and leave lists untouched """ + if isinstance(value, list): + return value + else: + return [value] + +@register_filter('seconds_to_human') +def seconds_to_human(seconds, separator=""): + """ Convert seconds to human-readable values like 1d6h15m23s """ + from vyos.utils.convert import seconds_to_human + return seconds_to_human(seconds, separator=separator) + +@register_filter('bytes_to_human') +def bytes_to_human(bytes, initial_exponent=0, precision=2): + """ Convert bytes to human-readable values like 1.44M """ + from vyos.utils.convert import bytes_to_human + return bytes_to_human(bytes, initial_exponent=initial_exponent, precision=precision) + +@register_filter('human_to_bytes') +def human_to_bytes(value): + """ Convert a data amount with a unit suffix to bytes, like 2K to 2048 """ + from vyos.utils.convert import human_to_bytes + return human_to_bytes(value) + +@register_filter('ip_from_cidr') +def ip_from_cidr(prefix): + """ Take an IPv4/IPv6 CIDR host and strip cidr mask. + Example: + 192.0.2.1/24 -> 192.0.2.1, 2001:db8::1/64 -> 2001:db8::1 + """ + from ipaddress import ip_interface + return str(ip_interface(prefix).ip) + +@register_filter('address_from_cidr') +def address_from_cidr(prefix): + """ Take an IPv4/IPv6 CIDR prefix and convert the network to an "address". + Example: + 192.0.2.0/24 -> 192.0.2.0, 2001:db8::/48 -> 2001:db8:: + """ + from ipaddress import ip_network + return str(ip_network(prefix).network_address) + +@register_filter('bracketize_ipv6') +def bracketize_ipv6(address): + """ Place a passed IPv6 address into [] brackets, do nothing for IPv4 """ + if is_ipv6(address): + return f'[{address}]' + return address + +@register_filter('dot_colon_to_dash') +def dot_colon_to_dash(text): + """ Replace dot and colon to dash for string + Example: + 192.0.2.1 => 192-0-2-1, 2001:db8::1 => 2001-db8--1 + """ + text = text.replace(":", "-") + text = text.replace(".", "-") + return text + +@register_filter('generate_uuid4') +def generate_uuid4(text): + """ Generate random unique ID + Example: + % uuid4() + UUID('958ddf6a-ef14-4e81-8cfb-afb12456d1c5') + """ + from uuid import uuid4 + return uuid4() + +@register_filter('netmask_from_cidr') +def netmask_from_cidr(prefix): + """ Take CIDR prefix and convert the prefix length to a "subnet mask". + Example: + - 192.0.2.0/24 -> 255.255.255.0 + - 2001:db8::/48 -> ffff:ffff:ffff:: + """ + from ipaddress import ip_network + return str(ip_network(prefix).netmask) + +@register_filter('netmask_from_ipv4') +def netmask_from_ipv4(address): + """ Take IP address and search all attached interface IP addresses for the + given one. After address has been found, return the associated netmask. + + Example: + - 172.18.201.10 -> 255.255.255.128 + """ + from netifaces import interfaces + from netifaces import ifaddresses + from netifaces import AF_INET + for interface in interfaces(): + tmp = ifaddresses(interface) + if AF_INET in tmp: + for af_addr in tmp[AF_INET]: + if 'addr' in af_addr: + if af_addr['addr'] == address: + return af_addr['netmask'] + + raise ValueError + +@register_filter('is_ip_network') +def is_ip_network(addr): + """ Take IP(v4/v6) address and validate if the passed argument is a network + or a host address. + + Example: + - 192.0.2.0 -> False + - 192.0.2.10/24 -> False + - 192.0.2.0/24 -> True + - 2001:db8:: -> False + - 2001:db8::100 -> False + - 2001:db8::/48 -> True + - 2001:db8:1000::/64 -> True + """ + try: + from ipaddress import ip_network + # input variables must contain a / to indicate its CIDR notation + if len(addr.split('/')) != 2: + raise ValueError() + ip_network(addr) + return True + except: + return False + +@register_filter('network_from_ipv4') +def network_from_ipv4(address): + """ Take IP address and search all attached interface IP addresses for the + given one. After address has been found, return the associated network + address. + + Example: + - 172.18.201.10 has mask 255.255.255.128 -> network is 172.18.201.0 + """ + netmask = netmask_from_ipv4(address) + from ipaddress import ip_interface + cidr_prefix = ip_interface(f'{address}/{netmask}').network + return address_from_cidr(cidr_prefix) + +@register_filter('is_interface') +def is_interface(interface): + """ Check if parameter is a valid local interface name """ + from vyos.utils.network import interface_exists + return interface_exists(interface) + +@register_filter('is_ip') +def is_ip(addr): + """ Check addr if it is an IPv4 or IPv6 address """ + return is_ipv4(addr) or is_ipv6(addr) + +@register_filter('is_ipv4') +def is_ipv4(text): + """ Filter IP address, return True on IPv4 address, False otherwise """ + from ipaddress import ip_interface + try: return ip_interface(text).version == 4 + except: return False + +@register_filter('is_ipv6') +def is_ipv6(text): + """ Filter IP address, return True on IPv6 address, False otherwise """ + from ipaddress import ip_interface + try: return ip_interface(text).version == 6 + except: return False + +@register_filter('first_host_address') +def first_host_address(prefix): + """ Return first usable (host) IP address from given prefix. + Example: + - 10.0.0.0/24 -> 10.0.0.1 + - 2001:db8::/64 -> 2001:db8:: + """ + from ipaddress import ip_interface + tmp = ip_interface(prefix).network + return str(tmp.network_address +1) + +@register_filter('last_host_address') +def last_host_address(text): + """ Return first usable IP address from given prefix. + Example: + - 10.0.0.0/24 -> 10.0.0.254 + - 2001:db8::/64 -> 2001:db8::ffff:ffff:ffff:ffff + """ + from ipaddress import ip_interface + from ipaddress import IPv4Network + from ipaddress import IPv6Network + + addr = ip_interface(text) + if addr.version == 4: + return str(IPv4Network(addr).broadcast_address - 1) + + return str(IPv6Network(addr).broadcast_address) + +@register_filter('inc_ip') +def inc_ip(address, increment): + """ Increment given IP address by 'increment' + + Example (inc by 2): + - 10.0.0.0/24 -> 10.0.0.2 + - 2001:db8::/64 -> 2001:db8::2 + """ + from ipaddress import ip_interface + return str(ip_interface(address).ip + int(increment)) + +@register_filter('dec_ip') +def dec_ip(address, decrement): + """ Decrement given IP address by 'decrement' + + Example (inc by 2): + - 10.0.0.0/24 -> 10.0.0.2 + - 2001:db8::/64 -> 2001:db8::2 + """ + from ipaddress import ip_interface + return str(ip_interface(address).ip - int(decrement)) + +@register_filter('compare_netmask') +def compare_netmask(netmask1, netmask2): + """ + Compare two IP netmask if they have the exact same size. + + compare_netmask('10.0.0.0/8', '20.0.0.0/8') -> True + compare_netmask('10.0.0.0/8', '20.0.0.0/16') -> False + """ + from ipaddress import ip_network + try: + return ip_network(netmask1).netmask == ip_network(netmask2).netmask + except: + return False + +@register_filter('isc_static_route') +def isc_static_route(subnet, router): + # https://ercpe.de/blog/pushing-static-routes-with-isc-dhcp-server + # Option format is: + # <netmask>, <network-byte1>, <network-byte2>, <network-byte3>, <router-byte1>, <router-byte2>, <router-byte3> + # where bytes with the value 0 are omitted. + from ipaddress import ip_network + net = ip_network(subnet) + # add netmask + string = str(net.prefixlen) + ',' + # add network bytes + if net.prefixlen: + width = net.prefixlen // 8 + if net.prefixlen % 8: + width += 1 + string += ','.join(map(str,tuple(net.network_address.packed)[:width])) + ',' + + # add router bytes + string += ','.join(router.split('.')) + + return string + +@register_filter('is_file') +def is_file(filename): + if os.path.exists(filename): + return os.path.isfile(filename) + return False + +@register_filter('get_dhcp_router') +def get_dhcp_router(interface): + """ Static routes can point to a router received by a DHCP reply. This + helper is used to get the current default router from the DHCP reply. + + Returns False of no router is found, returns the IP address as string if + a router is found. + """ + lease_file = directories['isc_dhclient_dir'] + f'/dhclient_{interface}.leases' + if not os.path.exists(lease_file): + return None + + from vyos.utils.file import read_file + for line in read_file(lease_file).splitlines(): + if 'option routers' in line: + (_, _, address) = line.split() + return address.rstrip(';') + +@register_filter('natural_sort') +def natural_sort(iterable): + import re + from jinja2.runtime import Undefined + + if isinstance(iterable, Undefined) or iterable is None: + return list() + + def convert(text): + return int(text) if text.isdigit() else text.lower() + def alphanum_key(key): + return [convert(c) for c in re.split('([0-9]+)', str(key))] + + return sorted(iterable, key=alphanum_key) + +@register_filter('get_ipv4') +def get_ipv4(interface): + """ Get interface IPv4 addresses""" + from vyos.ifconfig import Interface + return Interface(interface).get_addr_v4() + +@register_filter('get_ipv6') +def get_ipv6(interface): + """ Get interface IPv6 addresses""" + from vyos.ifconfig import Interface + return Interface(interface).get_addr_v6() + +@register_filter('get_ip') +def get_ip(interface): + """ Get interface IP addresses""" + from vyos.ifconfig import Interface + return Interface(interface).get_addr() + +def get_first_ike_dh_group(ike_group): + if ike_group and 'proposal' in ike_group: + for priority, proposal in ike_group['proposal'].items(): + if 'dh_group' in proposal: + return 'dh-group' + proposal['dh_group'] + return 'dh-group2' # Fallback on dh-group2 + +@register_filter('get_esp_ike_cipher') +def get_esp_ike_cipher(group_config, ike_group=None): + pfs_lut = { + 'dh-group1' : 'modp768', + 'dh-group2' : 'modp1024', + 'dh-group5' : 'modp1536', + 'dh-group14' : 'modp2048', + 'dh-group15' : 'modp3072', + 'dh-group16' : 'modp4096', + 'dh-group17' : 'modp6144', + 'dh-group18' : 'modp8192', + 'dh-group19' : 'ecp256', + 'dh-group20' : 'ecp384', + 'dh-group21' : 'ecp521', + 'dh-group22' : 'modp1024s160', + 'dh-group23' : 'modp2048s224', + 'dh-group24' : 'modp2048s256', + 'dh-group25' : 'ecp192', + 'dh-group26' : 'ecp224', + 'dh-group27' : 'ecp224bp', + 'dh-group28' : 'ecp256bp', + 'dh-group29' : 'ecp384bp', + 'dh-group30' : 'ecp512bp', + 'dh-group31' : 'curve25519', + 'dh-group32' : 'curve448' + } + + ciphers = [] + if 'proposal' in group_config: + for priority, proposal in group_config['proposal'].items(): + # both encryption and hash need to be specified for a proposal + if not {'encryption', 'hash'} <= set(proposal): + continue + + tmp = '{encryption}-{hash}'.format(**proposal) + if 'prf' in proposal: + tmp += '-' + proposal['prf'] + if 'dh_group' in proposal: + tmp += '-' + pfs_lut[ 'dh-group' + proposal['dh_group'] ] + elif 'pfs' in group_config and group_config['pfs'] != 'disable': + group = group_config['pfs'] + if group_config['pfs'] == 'enable': + group = get_first_ike_dh_group(ike_group) + tmp += '-' + pfs_lut[group] + + ciphers.append(tmp) + return ciphers + +@register_filter('get_uuid') +def get_uuid(seed): + """ Get interface IP addresses""" + if seed: + from hashlib import md5 + from uuid import UUID + tmp = md5() + tmp.update(seed.encode('utf-8')) + return str(UUID(tmp.hexdigest())) + else: + from uuid import uuid1 + return uuid1() + +openvpn_translate = { + 'des': 'des-cbc', + '3des': 'des-ede3-cbc', + 'bf128': 'bf-cbc', + 'bf256': 'bf-cbc', + 'aes128gcm': 'aes-128-gcm', + 'aes128': 'aes-128-cbc', + 'aes192gcm': 'aes-192-gcm', + 'aes192': 'aes-192-cbc', + 'aes256gcm': 'aes-256-gcm', + 'aes256': 'aes-256-cbc' +} + +@register_filter('openvpn_cipher') +def get_openvpn_cipher(cipher): + if cipher in openvpn_translate: + return openvpn_translate[cipher].upper() + return cipher.upper() + +@register_filter('openvpn_data_ciphers') +def get_openvpn_data_ciphers(ciphers): + out = [] + for cipher in ciphers: + if cipher in openvpn_translate: + out.append(openvpn_translate[cipher]) + else: + out.append(cipher) + return ':'.join(out).upper() + +@register_filter('snmp_auth_oid') +def snmp_auth_oid(type): + if type not in ['md5', 'sha', 'aes', 'des', 'none']: + raise ValueError() + + OIDs = { + 'md5' : '.1.3.6.1.6.3.10.1.1.2', + 'sha' : '.1.3.6.1.6.3.10.1.1.3', + 'aes' : '.1.3.6.1.6.3.10.1.2.4', + 'des' : '.1.3.6.1.6.3.10.1.2.2', + 'none': '.1.3.6.1.6.3.10.1.2.1' + } + return OIDs[type] + +@register_filter('nft_action') +def nft_action(vyos_action): + if vyos_action == 'accept': + return 'return' + return vyos_action + +@register_filter('nft_rule') +def nft_rule(rule_conf, fw_hook, fw_name, rule_id, ip_name='ip'): + from vyos.firewall import parse_rule + return parse_rule(rule_conf, fw_hook, fw_name, rule_id, ip_name) + +@register_filter('nft_default_rule') +def nft_default_rule(fw_conf, fw_name, family): + output = ['counter'] + default_action = fw_conf['default_action'] + #family = 'ipv6' if ipv6 else 'ipv4' + + if 'default_log' in fw_conf: + action_suffix = default_action[:1].upper() + output.append(f'log prefix "[{family}-{fw_name[:19]}-default-{action_suffix}]"') + + #output.append(nft_action(default_action)) + output.append(f'{default_action}') + if 'default_jump_target' in fw_conf: + target = fw_conf['default_jump_target'] + def_suffix = '6' if family == 'ipv6' else '' + output.append(f'NAME{def_suffix}_{target}') + + output.append(f'comment "{fw_name} default-action {default_action}"') + return " ".join(output) + +@register_filter('nft_state_policy') +def nft_state_policy(conf, state): + out = [f'ct state {state}'] + + if 'log' in conf: + log_state = state[:3].upper() + log_action = (conf['action'] if 'action' in conf else 'accept')[:1].upper() + out.append(f'log prefix "[STATE-POLICY-{log_state}-{log_action}]"') + + if 'log_level' in conf: + log_level = conf['log_level'] + out.append(f'level {log_level}') + + out.append('counter') + + if 'action' in conf: + out.append(conf['action']) + + return " ".join(out) + +@register_filter('nft_intra_zone_action') +def nft_intra_zone_action(zone_conf, ipv6=False): + if 'intra_zone_filtering' in zone_conf: + intra_zone = zone_conf['intra_zone_filtering'] + fw_name = 'ipv6_name' if ipv6 else 'name' + name_prefix = 'NAME6_' if ipv6 else 'NAME_' + + if 'action' in intra_zone: + if intra_zone['action'] == 'accept': + return 'return' + return intra_zone['action'] + elif dict_search_args(intra_zone, 'firewall', fw_name): + name = dict_search_args(intra_zone, 'firewall', fw_name) + return f'jump {name_prefix}{name}' + return 'return' + +@register_filter('nft_nested_group') +def nft_nested_group(out_list, includes, groups, key): + if not vyos_defined(out_list): + out_list = [] + + def add_includes(name): + if key in groups[name]: + for item in groups[name][key]: + if item in out_list: + continue + out_list.append(item) + + if 'include' in groups[name]: + for name_inc in groups[name]['include']: + add_includes(name_inc) + + for name in includes: + add_includes(name) + return out_list + +@register_filter('nat_rule') +def nat_rule(rule_conf, rule_id, nat_type, ipv6=False): + from vyos.nat import parse_nat_rule + return parse_nat_rule(rule_conf, rule_id, nat_type, ipv6) + +@register_filter('nat_static_rule') +def nat_static_rule(rule_conf, rule_id, nat_type): + from vyos.nat import parse_nat_static_rule + return parse_nat_static_rule(rule_conf, rule_id, nat_type) + +@register_filter('conntrack_rule') +def conntrack_rule(rule_conf, rule_id, action, ipv6=False): + ip_prefix = 'ip6' if ipv6 else 'ip' + def_suffix = '6' if ipv6 else '' + output = [] + + if 'inbound_interface' in rule_conf: + ifname = rule_conf['inbound_interface'] + if ifname != 'any': + output.append(f'iifname {ifname}') + + if 'protocol' in rule_conf: + if action != 'timeout': + proto = rule_conf['protocol'] + else: + for protocol, protocol_config in rule_conf['protocol'].items(): + proto = protocol + if proto != 'all': + output.append(f'meta l4proto {proto}') + + tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') + if tcp_flags and action != 'timeout': + from vyos.firewall import parse_tcp_flags + output.append(parse_tcp_flags(tcp_flags)) + + for side in ['source', 'destination']: + if side in rule_conf: + side_conf = rule_conf[side] + prefix = side[0] + + if 'address' in side_conf: + address = side_conf['address'] + operator = '' + if address[0] == '!': + operator = '!=' + address = address[1:] + output.append(f'{ip_prefix} {prefix}addr {operator} {address}') + + if 'port' in side_conf: + port = side_conf['port'] + operator = '' + if port[0] == '!': + operator = '!=' + port = port[1:] + output.append(f'th {prefix}port {operator} {port}') + + if 'group' in side_conf: + group = side_conf['group'] + + if 'address_group' in group: + group_name = group['address_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_prefix} {prefix}addr {operator} @A{def_suffix}_{group_name}') + # Generate firewall group domain-group + elif 'domain_group' in group: + group_name = group['domain_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_prefix} {prefix}addr {operator} @D_{group_name}') + elif 'network_group' in group: + group_name = group['network_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_prefix} {prefix}addr {operator} @N{def_suffix}_{group_name}') + if 'port_group' in group: + group_name = group['port_group'] + + if proto == 'tcp_udp': + proto = 'th' + + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + + output.append(f'{proto} {prefix}port {operator} @P_{group_name}') + + if action == 'ignore': + output.append('counter notrack') + output.append(f'comment "ignore-{rule_id}"') + else: + output.append(f'counter ct timeout set ct-timeout-{rule_id}') + output.append(f'comment "timeout-{rule_id}"') + + return " ".join(output) + +@register_filter('conntrack_ct_policy') +def conntrack_ct_policy(protocol_conf): + output = [] + for item in protocol_conf: + item_value = protocol_conf[item] + output.append(f'{item}: {item_value}') + + return ", ".join(output) + +@register_filter('range_to_regex') +def range_to_regex(num_range): + """Convert range of numbers or list of ranges + to regex + + % range_to_regex('11-12') + '(1[1-2])' + % range_to_regex(['11-12', '14-15']) + '(1[1-2]|1[4-5])' + """ + from vyos.range_regex import range_to_regex + if isinstance(num_range, list): + data = [] + for entry in num_range: + if '-' not in entry: + data.append(entry) + else: + data.append(range_to_regex(entry)) + return f'({"|".join(data)})' + + if '-' not in num_range: + return num_range + + regex = range_to_regex(num_range) + return f'({regex})' + +@register_filter('kea_address_json') +def kea_address_json(addresses): + from json import dumps + from vyos.utils.network import is_addr_assigned + + out = [] + + for address in addresses: + ifname = is_addr_assigned(address, return_ifname=True, include_vrf=True) + + if not ifname: + continue + + out.append(f'{ifname}/{address}') + + return dumps(out) + +@register_filter('kea_high_availability_json') +def kea_high_availability_json(config): + from json import dumps + + source_addr = config['source_address'] + remote_addr = config['remote'] + ha_mode = 'hot-standby' if config['mode'] == 'active-passive' else 'load-balancing' + ha_role = config['status'] + + if ha_role == 'primary': + peer1_role = 'primary' + peer2_role = 'standby' if ha_mode == 'hot-standby' else 'secondary' + else: + peer1_role = 'standby' if ha_mode == 'hot-standby' else 'secondary' + peer2_role = 'primary' + + data = { + 'this-server-name': os.uname()[1], + 'mode': ha_mode, + 'heartbeat-delay': 10000, + 'max-response-delay': 10000, + 'max-ack-delay': 5000, + 'max-unacked-clients': 0, + 'peers': [ + { + 'name': os.uname()[1], + 'url': f'http://{source_addr}:647/', + 'role': peer1_role, + 'auto-failover': True + }, + { + 'name': config['name'], + 'url': f'http://{remote_addr}:647/', + 'role': peer2_role, + 'auto-failover': True + }] + } + + if 'ca_cert_file' in config: + data['trust-anchor'] = config['ca_cert_file'] + + if 'cert_file' in config: + data['cert-file'] = config['cert_file'] + + if 'cert_key_file' in config: + data['key-file'] = config['cert_key_file'] + + return dumps(data) + +@register_filter('kea_shared_network_json') +def kea_shared_network_json(shared_networks): + from vyos.kea import kea_parse_options + from vyos.kea import kea_parse_subnet + from json import dumps + out = [] + + for name, config in shared_networks.items(): + if 'disable' in config: + continue + + network = { + 'name': name, + 'authoritative': ('authoritative' in config), + 'subnet4': [] + } + + if 'option' in config: + network['option-data'] = kea_parse_options(config['option']) + + if 'bootfile_name' in config['option']: + network['boot-file-name'] = config['option']['bootfile_name'] + + if 'bootfile_server' in config['option']: + network['next-server'] = config['option']['bootfile_server'] + + if 'subnet' in config: + for subnet, subnet_config in config['subnet'].items(): + if 'disable' in subnet_config: + continue + network['subnet4'].append(kea_parse_subnet(subnet, subnet_config)) + + out.append(network) + + return dumps(out, indent=4) + +@register_filter('kea6_shared_network_json') +def kea6_shared_network_json(shared_networks): + from vyos.kea import kea6_parse_options + from vyos.kea import kea6_parse_subnet + from json import dumps + out = [] + + for name, config in shared_networks.items(): + if 'disable' in config: + continue + + network = { + 'name': name, + 'subnet6': [] + } + + if 'option' in config: + network['option-data'] = kea6_parse_options(config['option']) + + if 'interface' in config: + network['interface'] = config['interface'] + + if 'subnet' in config: + for subnet, subnet_config in config['subnet'].items(): + network['subnet6'].append(kea6_parse_subnet(subnet, subnet_config)) + + out.append(network) + + return dumps(out, indent=4) + +@register_test('vyos_defined') +def vyos_defined(value, test_value=None, var_type=None): + """ + Jinja2 plugin to test if a variable is defined and not none - vyos_defined + will test value if defined and is not none and return true or false. + + If test_value is supplied, the value must also pass == test_value to return true. + If var_type is supplied, the value must also be of the specified class/type + + Examples: + 1. Test if var is defined and not none: + {% if foo is vyos_defined %} + ... + {% endif %} + + 2. Test if variable is defined, not none and has value "something" + {% if bar is vyos_defined("something") %} + ... + {% endif %} + + Parameters + ---------- + value : any + Value to test from ansible + test_value : any, optional + Value to test in addition of defined and not none, by default None + var_type : ['float', 'int', 'str', 'list', 'dict', 'tuple', 'bool'], optional + Type or Class to test for + + Returns + ------- + boolean + True if variable matches criteria, False in other cases. + + Implementation inspired and re-used from https://github.com/aristanetworks/ansible-avd/ + """ + + from jinja2 import Undefined + + if isinstance(value, Undefined) or value is None: + # Invalid value - return false + return False + elif test_value is not None and value != test_value: + # Valid value but not matching the optional argument + return False + elif str(var_type).lower() in ['float', 'int', 'str', 'list', 'dict', 'tuple', 'bool'] and str(var_type).lower() != type(value).__name__: + # Invalid class - return false + return False + else: + # Valid value and is matching optional argument if provided - return true + return True diff --git a/python/vyos/tpm.py b/python/vyos/tpm.py new file mode 100644 index 0000000..a24f149 --- /dev/null +++ b/python/vyos/tpm.py @@ -0,0 +1,96 @@ +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import tempfile + +from vyos.utils.process import rc_cmd + +default_pcrs = ['0','2','4','7'] +tpm_handle = 0x81000000 + +def init_tpm(clear=False): + """ + Initialize TPM + """ + code, output = rc_cmd('tpm2_startup' + (' -c' if clear else '')) + if code != 0: + raise Exception('init_tpm: Failed to initialize TPM') + +def clear_tpm_key(): + """ + Clear existing key on TPM + """ + code, output = rc_cmd(f'tpm2_evictcontrol -C o -c {tpm_handle}') + if code != 0: + raise Exception('clear_tpm_key: Failed to clear TPM key') + +def read_tpm_key(index=0, pcrs=default_pcrs): + """ + Read existing key on TPM + """ + with tempfile.TemporaryDirectory() as tpm_dir: + pcr_str = ",".join(pcrs) + + tpm_key_file = os.path.join(tpm_dir, 'tpm_key.key') + code, output = rc_cmd(f'tpm2_unseal -c {tpm_handle + index} -p pcr:sha256:{pcr_str} -o {tpm_key_file}') + if code != 0: + raise Exception('read_tpm_key: Failed to read key from TPM') + + with open(tpm_key_file, 'rb') as f: + tpm_key = f.read() + + return tpm_key + +def write_tpm_key(key, index=0, pcrs=default_pcrs): + """ + Saves key to TPM + """ + with tempfile.TemporaryDirectory() as tpm_dir: + pcr_str = ",".join(pcrs) + + policy_file = os.path.join(tpm_dir, 'policy.digest') + code, output = rc_cmd(f'tpm2_createpolicy --policy-pcr -l sha256:{pcr_str} -L {policy_file}') + if code != 0: + raise Exception('write_tpm_key: Failed to create policy digest') + + primary_context_file = os.path.join(tpm_dir, 'primary.ctx') + code, output = rc_cmd(f'tpm2_createprimary -C e -g sha256 -G rsa -c {primary_context_file}') + if code != 0: + raise Exception('write_tpm_key: Failed to create primary key') + + key_file = os.path.join(tpm_dir, 'crypt.key') + with open(key_file, 'wb') as f: + f.write(key) + + public_obj = os.path.join(tpm_dir, 'obj.pub') + private_obj = os.path.join(tpm_dir, 'obj.key') + code, output = rc_cmd( + f'tpm2_create -g sha256 \ + -u {public_obj} -r {private_obj} \ + -C {primary_context_file} -L {policy_file} -i {key_file}') + + if code != 0: + raise Exception('write_tpm_key: Failed to create object') + + load_context_file = os.path.join(tpm_dir, 'load.ctx') + code, output = rc_cmd(f'tpm2_load -C {primary_context_file} -u {public_obj} -r {private_obj} -c {load_context_file}') + + if code != 0: + raise Exception('write_tpm_key: Failed to load object') + + code, output = rc_cmd(f'tpm2_evictcontrol -c {load_context_file} -C o {tpm_handle + index}') + + if code != 0: + raise Exception('write_tpm_key: Failed to write object to TPM') diff --git a/python/vyos/utils/__init__.py b/python/vyos/utils/__init__.py new file mode 100644 index 0000000..3759b21 --- /dev/null +++ b/python/vyos/utils/__init__.py @@ -0,0 +1,33 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.utils import assertion +from vyos.utils import auth +from vyos.utils import boot +from vyos.utils import commit +from vyos.utils import configfs +from vyos.utils import convert +from vyos.utils import cpu +from vyos.utils import dict +from vyos.utils import file +from vyos.utils import io +from vyos.utils import kernel +from vyos.utils import list +from vyos.utils import locking +from vyos.utils import misc +from vyos.utils import network +from vyos.utils import permission +from vyos.utils import process +from vyos.utils import system diff --git a/python/vyos/utils/assertion.py b/python/vyos/utils/assertion.py new file mode 100644 index 0000000..c7fa220 --- /dev/null +++ b/python/vyos/utils/assertion.py @@ -0,0 +1,81 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +def assert_boolean(b): + if int(b) not in (0, 1): + raise ValueError(f'Value {b} out of range') + +def assert_range(value, lower=0, count=3): + if int(value, 16) not in range(lower, lower+count): + raise ValueError("Value out of range") + +def assert_list(s, l): + if s not in l: + o = ' or '.join([f'"{n}"' for n in l]) + raise ValueError(f'state must be {o}, got {s}') + +def assert_number(n): + if not str(n).isnumeric(): + raise ValueError(f'{n} must be a number') + +def assert_positive(n, smaller=0): + assert_number(n) + if int(n) < smaller: + raise ValueError(f'{n} is smaller than {smaller}') + +def assert_mtu(mtu, ifname): + assert_number(mtu) + + import json + from vyos.utils.process import cmd + out = cmd(f'ip -j -d link show dev {ifname}') + # [{"ifindex":2,"ifname":"eth0","flags":["BROADCAST","MULTICAST","UP","LOWER_UP"],"mtu":1500,"qdisc":"pfifo_fast","operstate":"UP","linkmode":"DEFAULT","group":"default","txqlen":1000,"link_type":"ether","address":"08:00:27:d9:5b:04","broadcast":"ff:ff:ff:ff:ff:ff","promiscuity":0,"min_mtu":46,"max_mtu":16110,"inet6_addr_gen_mode":"none","num_tx_queues":1,"num_rx_queues":1,"gso_max_size":65536,"gso_max_segs":65535}] + parsed = json.loads(out)[0] + min_mtu = int(parsed.get('min_mtu', '0')) + # cur_mtu = parsed.get('mtu',0), + max_mtu = int(parsed.get('max_mtu', '0')) + cur_mtu = int(mtu) + + if (min_mtu and cur_mtu < min_mtu) or cur_mtu < 68: + raise ValueError(f'MTU is too small for interface "{ifname}": {mtu} < {min_mtu}') + if (max_mtu and cur_mtu > max_mtu) or cur_mtu > 65536: + raise ValueError(f'MTU is too small for interface "{ifname}": {mtu} > {max_mtu}') + +def assert_mac(m, test_all_zero=True): + split = m.split(':') + size = len(split) + + # a mac address consits out of 6 octets + if size != 6: + raise ValueError(f'wrong number of MAC octets ({size}): {m}') + + octets = [] + try: + for octet in split: + octets.append(int(octet, 16)) + except ValueError: + raise ValueError(f'invalid hex number "{octet}" in : {m}') + + # validate against the first mac address byte if it's a multicast + # address + if octets[0] & 1: + raise ValueError(f'{m} is a multicast MAC address') + + # overall mac address is not allowed to be 00:00:00:00:00:00 + if test_all_zero and sum(octets) == 0: + raise ValueError('00:00:00:00:00:00 is not a valid MAC address') + + if octets[:5] == (0, 0, 94, 0, 1): + raise ValueError(f'{m} is a VRRP MAC address') diff --git a/python/vyos/utils/auth.py b/python/vyos/utils/auth.py new file mode 100644 index 0000000..a0b3e1c --- /dev/null +++ b/python/vyos/utils/auth.py @@ -0,0 +1,51 @@ +# authutils -- miscelanneous functions for handling passwords and publis keys +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or modify it under the terms of +# the GNU Lesser General Public License as published by the Free Software Foundation; +# either version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License along with this library; +# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import re + +from vyos.utils.process import cmd + +def make_password_hash(password): + """ Makes a password hash for /etc/shadow using mkpasswd """ + + mkpassword = 'mkpasswd --method=sha-512 --stdin' + return cmd(mkpassword, input=password, timeout=5) + +def split_ssh_public_key(key_string, defaultname=""): + """ Splits an SSH public key into its components """ + + key_string = key_string.strip() + parts = re.split(r'\s+', key_string) + + if len(parts) == 3: + key_type, key_data, key_name = parts[0], parts[1], parts[2] + else: + key_type, key_data, key_name = parts[0], parts[1], defaultname + + if key_type not in ['ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519']: + raise ValueError("Bad key type \'{0}\', must be one of must be one of ssh-rsa, ssh-dss, ecdsa-sha2-nistp<256|384|521> or ssh-ed25519".format(key_type)) + + return({"type": key_type, "data": key_data, "name": key_name}) + +def get_current_user() -> str: + import os + current_user = 'nobody' + # During CLI "owner" script execution we use SUDO_USER + if 'SUDO_USER' in os.environ: + current_user = os.environ['SUDO_USER'] + # During op-mode or config-mode interactive CLI we use USER + elif 'USER' in os.environ: + current_user = os.environ['USER'] + return current_user diff --git a/python/vyos/utils/boot.py b/python/vyos/utils/boot.py new file mode 100644 index 0000000..708bef1 --- /dev/null +++ b/python/vyos/utils/boot.py @@ -0,0 +1,39 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os + +def boot_configuration_complete() -> bool: + """ Check if the boot config loader has completed + """ + from vyos.defaults import config_status + if os.path.isfile(config_status): + return True + return False + +def boot_configuration_success() -> bool: + from vyos.defaults import config_status + try: + with open(config_status) as f: + res = f.read().strip() + except FileNotFoundError: + return False + if int(res) == 0: + return True + return False + +def is_uefi_system() -> bool: + efi_fw_dir = '/sys/firmware/efi' + return os.path.exists(efi_fw_dir) and os.path.isdir(efi_fw_dir) diff --git a/python/vyos/utils/commit.py b/python/vyos/utils/commit.py new file mode 100644 index 0000000..105aed8 --- /dev/null +++ b/python/vyos/utils/commit.py @@ -0,0 +1,60 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +def commit_in_progress(): + """ Not to be used in normal op mode scripts! """ + + # The CStore backend locks the config by opening a file + # The file is not removed after commit, so just checking + # if it exists is insufficient, we need to know if it's open by anyone + + # There are two ways to check if any other process keeps a file open. + # The first one is to try opening it and see if the OS objects. + # That's faster but prone to race conditions and can be intrusive. + # The other one is to actually check if any process keeps it open. + # It's non-intrusive but needs root permissions, else you can't check + # processes of other users. + # + # Since this will be used in scripts that modify the config outside of the CLI + # framework, those knowingly have root permissions. + # For everything else, we add a safeguard. + from psutil import process_iter + from psutil import NoSuchProcess + from getpass import getuser + from vyos.defaults import commit_lock + + if getuser() != 'root': + raise OSError('This functions needs to be run as root to return correct results!') + + for proc in process_iter(): + try: + files = proc.open_files() + if files: + for f in files: + if f.path == commit_lock: + return True + except NoSuchProcess as err: + # Process died before we could examine it + pass + # Default case + return False + + +def wait_for_commit_lock(): + """ Not to be used in normal op mode scripts! """ + from time import sleep + # Very synchronous approach to multiprocessing + while commit_in_progress(): + sleep(1) diff --git a/python/vyos/utils/config.py b/python/vyos/utils/config.py new file mode 100644 index 0000000..3304701 --- /dev/null +++ b/python/vyos/utils/config.py @@ -0,0 +1,39 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +from vyos.defaults import directories + +config_file = os.path.join(directories['config'], 'config.boot') + +def read_saved_value(path: list): + if not isinstance(path, list) or not path: + return '' + from vyos.configtree import ConfigTree + try: + with open(config_file) as f: + config_string = f.read() + ct = ConfigTree(config_string) + except Exception: + return '' + if not ct.exists(path): + return '' + res = ct.return_values(path) + if len(res) == 1: + return res[0] + res = ct.list_nodes(path) + if len(res) == 1: + return ' '.join(res) + return res diff --git a/python/vyos/utils/configfs.py b/python/vyos/utils/configfs.py new file mode 100644 index 0000000..8617f01 --- /dev/null +++ b/python/vyos/utils/configfs.py @@ -0,0 +1,37 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os + +def delete_cli_node(cli_path: list): + from shutil import rmtree + for config_dir in ['VYATTA_TEMP_CONFIG_DIR', 'VYATTA_CHANGES_ONLY_DIR']: + tmp = os.path.join(os.environ[config_dir], '/'.join(cli_path)) + # delete CLI node + if os.path.exists(tmp): + rmtree(tmp) + +def add_cli_node(cli_path: list, value: str=None): + from vyos.utils.auth import get_current_user + from vyos.utils.file import write_file + + current_user = get_current_user() + for config_dir in ['VYATTA_TEMP_CONFIG_DIR', 'VYATTA_CHANGES_ONLY_DIR']: + # store new value + tmp = os.path.join(os.environ[config_dir], '/'.join(cli_path)) + write_file(f'{tmp}/node.val', value, user=current_user, group='vyattacfg', mode=0o664) + # mark CLI node as modified + if config_dir == 'VYATTA_CHANGES_ONLY_DIR': + write_file(f'{tmp}/.modified', '', user=current_user, group='vyattacfg', mode=0o664) diff --git a/python/vyos/utils/convert.py b/python/vyos/utils/convert.py new file mode 100644 index 0000000..dd4266f --- /dev/null +++ b/python/vyos/utils/convert.py @@ -0,0 +1,237 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. +import re + +# Define the number of seconds in each time unit +time_units = { + 'y': 60 * 60 * 24 * 365.25, # year + 'w': 60 * 60 * 24 * 7, # week + 'd': 60 * 60 * 24, # day + 'h': 60 * 60, # hour + 'm': 60, # minute + 's': 1 # second +} + + +def human_to_seconds(time_str): + """ Converts a human-readable interval such as 1w4d18h35m59s + to number of seconds + """ + + time_patterns = { + 'y': r'(\d+)\s*y', + 'w': r'(\d+)\s*w', + 'd': r'(\d+)\s*d', + 'h': r'(\d+)\s*h', + 'm': r'(\d+)\s*m', + 's': r'(\d+)\s*s' + } + + total_seconds = 0 + + for unit, pattern in time_patterns.items(): + match = re.search(pattern, time_str) + if match: + value = int(match.group(1)) + total_seconds += value * time_units[unit] + + return int(total_seconds) + + +def seconds_to_human(s, separator=""): + """ Converts number of seconds passed to a human-readable + interval such as 1w4d18h35m59s + """ + s = int(s) + result = [] + + years = s // time_units['y'] + if years > 0: + result.append(f'{int(years)}y') + s = int(s % time_units['y']) + + weeks = s // time_units['w'] + if weeks > 0: + result.append(f'{weeks}w') + s = s % time_units['w'] + + days = s // time_units['d'] + if days > 0: + result.append(f'{days}d') + s = s % time_units['d'] + + hours = s // time_units['h'] + if hours > 0: + result.append(f'{hours}h') + s = s % time_units['h'] + + minutes = s // time_units['m'] + if minutes > 0: + result.append(f'{minutes}m') + s = s % 60 + + seconds = s + if seconds > 0: + result.append(f'{seconds}s') + + return separator.join(result) + + +def bytes_to_human(bytes, initial_exponent=0, precision=2, + int_below_exponent=0): + """ Converts a value in bytes to a human-readable size string like 640 KB + + The initial_exponent parameter is the exponent of 2, + e.g. 10 (1024) for kilobytes, 20 (1024 * 1024) for megabytes. + """ + + if bytes == 0: + return "0 B" + + from math import log2 + + bytes = bytes * (2**initial_exponent) + + # log2 is a float, while range checking requires an int + exponent = int(log2(bytes)) + if exponent < int_below_exponent: + precision = 0 + + if exponent < 10: + value = bytes + suffix = "B" + elif exponent in range(10, 20): + value = bytes / 1024 + suffix = "KB" + elif exponent in range(20, 30): + value = bytes / 1024**2 + suffix = "MB" + elif exponent in range(30, 40): + value = bytes / 1024**3 + suffix = "GB" + else: + value = bytes / 1024**4 + suffix = "TB" + # Add a new case when the first machine with petabyte RAM + # hits the market. + + size_string = "{0:.{1}f} {2}".format(value, precision, suffix) + return size_string + +def human_to_bytes(value): + """ Converts a data amount with a unit suffix to bytes, like 2K to 2048 """ + + from re import match as re_match + + res = re_match(r'^\s*(\d+(?:\.\d+)?)\s*([a-zA-Z]+)\s*$', value) + + if not res: + raise ValueError(f"'{value}' is not a valid data amount") + else: + amount = float(res.group(1)) + unit = res.group(2).lower() + + if unit == 'b': + res = amount + elif (unit == 'k') or (unit == 'kb'): + res = amount * 1024 + elif (unit == 'm') or (unit == 'mb'): + res = amount * 1024**2 + elif (unit == 'g') or (unit == 'gb'): + res = amount * 1024**3 + elif (unit == 't') or (unit == 'tb'): + res = amount * 1024**4 + else: + raise ValueError(f"Unsupported data unit '{unit}'") + + # There cannot be fractional bytes, so we convert them to integer. + # However, truncating causes problems with conversion back to human unit, + # so we round instead -- that seems to work well enough. + return round(res) + +def mac_to_eui64(mac, prefix=None): + """ + Convert a MAC address to a EUI64 address or, with prefix provided, a full + IPv6 address. + Thankfully copied from https://gist.github.com/wido/f5e32576bb57b5cc6f934e177a37a0d3 + """ + import re + from ipaddress import ip_network + # http://tools.ietf.org/html/rfc4291#section-2.5.1 + eui64 = re.sub(r'[.:-]', '', mac).lower() + eui64 = eui64[0:6] + 'fffe' + eui64[6:] + eui64 = hex(int(eui64[0:2], 16) ^ 2)[2:].zfill(2) + eui64[2:] + + if prefix is None: + return ':'.join(re.findall(r'.{4}', eui64)) + else: + try: + net = ip_network(prefix, strict=False) + euil = int('0x{0}'.format(eui64), 16) + return str(net[euil]) + except: # pylint: disable=bare-except + return + + +def convert_data(data) -> dict | list | tuple | str | int | float | bool | None: + """Filter and convert multiple types of data to types usable in CLI/API + + WARNING: Must not be used for anything except formatting output for API or CLI + + On the output allowed everything supported in JSON. + + Args: + data (Any): input data + + Returns: + dict | list | tuple | str | int | float | bool | None: converted data + """ + from base64 import b64encode + + # return original data for types which do not require conversion + if isinstance(data, str | int | float | bool | None): + return data + + if isinstance(data, list): + list_tmp = [] + for item in data: + list_tmp.append(convert_data(item)) + return list_tmp + + if isinstance(data, tuple): + list_tmp = list(data) + tuple_tmp = tuple(convert_data(list_tmp)) + return tuple_tmp + + if isinstance(data, bytes | bytearray): + try: + return data.decode() + except UnicodeDecodeError: + return b64encode(data).decode() + + if isinstance(data, set | frozenset): + list_tmp = convert_data(list(data)) + return list_tmp + + if isinstance(data, dict): + dict_tmp = {} + for key, value in data.items(): + dict_tmp[key] = convert_data(value) + return dict_tmp + + # do not return anything for other types + # which cannot be converted to JSON + # for example: complex | range | memoryview + return diff --git a/python/vyos/utils/cpu.py b/python/vyos/utils/cpu.py new file mode 100644 index 0000000..3bea5ac --- /dev/null +++ b/python/vyos/utils/cpu.py @@ -0,0 +1,101 @@ +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +""" +Retrieves (or at least attempts to retrieve) the total number of real CPU cores +installed in a Linux system. + +The issue of core count is complicated by existence of SMT, e.g. Intel's Hyper Threading. +GNU nproc returns the number of LOGICAL cores, +which is 2x of the real cores if SMT is enabled. + +The idea is to find all physical CPUs and add up their core counts. +It has special cases for x86_64 and MAY work correctly on other architectures, +but nothing is certain. +""" + +import re + +def _read_cpuinfo(): + with open('/proc/cpuinfo', 'r') as f: + lines = f.read().strip() + return re.split(r'\n+', lines) + +def _split_line(l): + l = l.strip() + parts = re.split(r'\s*:\s*', l) + return (parts[0], ":".join(parts[1:])) + +def _find_cpus(cpuinfo_lines): + # Make a dict because it's more convenient to work with later, + # when we need to find physicall distinct CPUs there. + cpus = {} + + cpu_number = 0 + + for l in cpuinfo_lines: + key, value = _split_line(l) + if key == 'processor': + cpu_number = value + cpus[cpu_number] = {} + else: + cpus[cpu_number][key] = value + + return cpus + +def _find_physical_cpus(): + cpus = _find_cpus(_read_cpuinfo()) + + phys_cpus = {} + + for num in cpus: + if 'physical id' in cpus[num]: + # On at least some architectures, CPUs in different sockets + # have different 'physical id' field, e.g. on x86_64. + phys_id = cpus[num]['physical id'] + if phys_id not in phys_cpus: + phys_cpus[phys_id] = cpus[num] + else: + # On other architectures, e.g. on ARM, there's no such field. + # We just assume they are different CPUs, + # whether single core ones or cores of physical CPUs. + phys_cpus[num] = cpus[num] + + return phys_cpus + +def get_cpus(): + """ Returns a list of /proc/cpuinfo entries that belong to different CPUs. + """ + cpus_dict = _find_physical_cpus() + return list(cpus_dict.values()) + +def get_core_count(): + """ Returns the total number of physical CPU cores + (even if Hyper-Threading or another SMT is enabled and has inflated + the number of cores in /proc/cpuinfo) + """ + physical_cpus = _find_physical_cpus() + + core_count = 0 + + for num in physical_cpus: + # Some architectures, e.g. x86_64, include a field for core count. + # Since we found unique physical CPU entries, we can sum their core counts. + if 'cpu cores' in physical_cpus[num]: + core_count += int(physical_cpus[num]['cpu cores']) + else: + core_count += 1 + + return core_count diff --git a/python/vyos/utils/dict.py b/python/vyos/utils/dict.py new file mode 100644 index 0000000..1a7a6b9 --- /dev/null +++ b/python/vyos/utils/dict.py @@ -0,0 +1,374 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +def colon_separated_to_dict(data_string, uniquekeys=False): + """ Converts a string containing newline-separated entries + of colon-separated key-value pairs into a dict. + + Such files are common in Linux /proc filesystem + + Args: + data_string (str): data string + uniquekeys (bool): whether to insist that keys are unique or not + + Returns: dict + + Raises: + ValueError: if uniquekeys=True and the data string has + duplicate keys. + + Note: + If uniquekeys=True, then dict entries are always strings, + otherwise they are always lists of strings. + """ + import re + key_value_re = re.compile(r'([^:]+)\s*\:\s*(.*)') + + data_raw = re.split('\n', data_string) + + data = {} + + for l in data_raw: + l = l.strip() + if l: + match = re.match(key_value_re, l) + if match and (len(match.groups()) == 2): + key = match.groups()[0].strip() + value = match.groups()[1].strip() + else: + raise ValueError(f"""Line "{l}" could not be parsed a colon-separated pair """, l) + if key in data.keys(): + if uniquekeys: + raise ValueError("Data string has duplicate keys: {0}".format(key)) + else: + data[key].append(value) + else: + if uniquekeys: + data[key] = value + else: + data[key] = [value] + else: + pass + + return data + +def mangle_dict_keys(data, regex, replacement, abs_path=None, no_tag_node_value_mangle=False): + """ Mangles dict keys according to a regex and replacement character. + Some libraries like Jinja2 do not like certain characters in dict keys. + This function can be used for replacing all offending characters + with something acceptable. + + Args: + data (dict): Original dict to mangle + regex, replacement (str): arguments to re.sub(regex, replacement, ...) + abs_path (list): if data is a config dict and no_tag_node_value_mangle is True + then abs_path should be the absolute config path to the first + keys of data, non-inclusive + no_tag_node_value_mangle (bool): do not mangle keys of tag node values + + Returns: dict + """ + import re + from vyos.xml_ref import is_tag_value + + if abs_path is None: + abs_path = [] + + new_dict = type(data)() + + for k in data.keys(): + if no_tag_node_value_mangle and is_tag_value(abs_path + [k]): + new_key = k + else: + new_key = re.sub(regex, replacement, k) + + value = data[k] + + if isinstance(value, dict): + new_dict[new_key] = mangle_dict_keys(value, regex, replacement, + abs_path=abs_path + [k], + no_tag_node_value_mangle=no_tag_node_value_mangle) + else: + new_dict[new_key] = value + + return new_dict + +def _get_sub_dict(d, lpath): + k = lpath[0] + if k not in d.keys(): + return {} + c = {k: d[k]} + lpath = lpath[1:] + if not lpath: + return c + elif not isinstance(c[k], dict): + return {} + return _get_sub_dict(c[k], lpath) + +def get_sub_dict(source, lpath, get_first_key=False): + """ Returns the sub-dict of a nested dict, defined by path of keys. + + Args: + source (dict): Source dict to extract from + lpath (list[str]): sequence of keys + + Returns: source, if lpath is empty, else + {key : source[..]..[key]} for key the last element of lpath, if exists + {} otherwise + """ + if not isinstance(source, dict): + raise TypeError("source must be of type dict") + if not isinstance(lpath, list): + raise TypeError("path must be of type list") + if not lpath: + return source + + ret = _get_sub_dict(source, lpath) + + if get_first_key and lpath and ret: + tmp = next(iter(ret.values())) + if not isinstance(tmp, dict): + raise TypeError("Data under node is not of type dict") + ret = tmp + + return ret + +def dict_search(path, dict_object): + """ Traverse Python dictionary (dict_object) delimited by dot (.). + Return value of key if found, None otherwise. + + This is faster implementation then jmespath.search('foo.bar', dict_object)""" + if not isinstance(dict_object, dict) or not path: + return None + + parts = path.split('.') + inside = parts[:-1] + if not inside: + if path not in dict_object: + return None + return dict_object[path] + c = dict_object + for p in parts[:-1]: + c = c.get(p, {}) + return c.get(parts[-1], None) + +def dict_search_args(dict_object, *path): + # Traverse dictionary using variable arguments + # Added due to above function not allowing for '.' in the key names + # Example: dict_search_args(some_dict, 'key', 'subkey', 'subsubkey', ...) + if not isinstance(dict_object, dict) or not path: + return None + + for item in path: + if item not in dict_object: + return None + dict_object = dict_object[item] + return dict_object + +def dict_search_recursive(dict_object, key, path=[]): + """ Traverse a dictionary recurisvely and return the value of the key + we are looking for. + + Thankfully copied from https://stackoverflow.com/a/19871956 + + Modified to yield optional path to found keys + """ + if isinstance(dict_object, list): + for i in dict_object: + new_path = path + [i] + for x in dict_search_recursive(i, key, new_path): + yield x + elif isinstance(dict_object, dict): + if key in dict_object: + new_path = path + [key] + yield dict_object[key], new_path + for k, j in dict_object.items(): + new_path = path + [k] + for x in dict_search_recursive(j, key, new_path): + yield x + + +def dict_set(key_path, value, dict_object): + """ Set value to Python dictionary (dict_object) using path to key delimited by dot (.). + The key will be added if it does not exist. + """ + path_list = key_path.split(".") + dynamic_dict = dict_object + if len(path_list) > 0: + for i in range(0, len(path_list)-1): + dynamic_dict = dynamic_dict[path_list[i]] + dynamic_dict[path_list[len(path_list)-1]] = value + +def dict_delete(key_path, dict_object): + """ Delete key in Python dictionary (dict_object) using path to key delimited by dot (.). + """ + path_dict = dict_object + path_list = key_path.split('.') + inside = path_list[:-1] + if not inside: + del dict_object[path_list] + else: + for key in path_list[:-1]: + path_dict = path_dict[key] + del path_dict[path_list[len(path_list)-1]] + +def dict_to_list(d, save_key_to=None): + """ Convert a dict to a list of dicts. + + Optionally, save the original key of the dict inside + dicts stores in that list. + """ + def save_key(i, k): + if isinstance(i, dict): + i[save_key_to] = k + return + elif isinstance(i, list): + for _i in i: + save_key(_i, k) + else: + raise ValueError(f"Cannot save the key: the item is {type(i)}, not a dict") + + collect = [] + + for k,_ in d.items(): + item = d[k] + if save_key_to is not None: + save_key(item, k) + if isinstance(item, list): + collect += item + else: + collect.append(item) + + return collect + +def dict_to_paths_values(conf: dict) -> dict: + """ + Convert nested dictionary to simple dictionary, where key is a path is delimited by dot (.). + """ + list_of_paths = [] + dict_of_options ={} + for path in dict_to_key_paths(conf): + str_path = '.'.join(path) + list_of_paths.append(str_path) + + for path in list_of_paths: + dict_of_options[path] = dict_search(path,conf) + + return dict_of_options + +def dict_to_key_paths(d: dict) -> list: + """ Generator to return list of key paths from dict of list[str]|str + """ + def func(d, path): + if isinstance(d, dict): + if not d: + yield path + for k, v in d.items(): + for r in func(v, path + [k]): + yield r + elif isinstance(d, list): + yield path + elif isinstance(d, str): + yield path + else: + raise ValueError('object is not a dict of strings/list of strings') + for r in func(d, []): + yield r + +def dict_to_paths(d: dict) -> list: + """ Generator to return list of paths from dict of list[str]|str + """ + def func(d, path): + if isinstance(d, dict): + if not d: + yield path + for k, v in d.items(): + for r in func(v, path + [k]): + yield r + elif isinstance(d, list): + for i in d: + for r in func(i, path): + yield r + elif isinstance(d, str): + yield path + [d] + else: + raise ValueError('object is not a dict of strings/list of strings') + for r in func(d, []): + yield r + +def embed_dict(p: list[str], d: dict) -> dict: + path = p.copy() + ret = d + while path: + ret = {path.pop(): ret} + return ret + +def check_mutually_exclusive_options(d, keys, required=False): + """ Checks if a dict has at most one or only one of + mutually exclusive keys. + """ + present_keys = [] + + for k in d: + if k in keys: + present_keys.append(k) + + # Un-mangle the keys to make them match CLI option syntax + from re import sub + orig_keys = list(map(lambda s: sub(r'_', '-', s), keys)) + orig_present_keys = list(map(lambda s: sub(r'_', '-', s), present_keys)) + + if len(present_keys) > 1: + raise ValueError(f"Options {orig_keys} are mutually-exclusive but more than one of them is present: {orig_present_keys}") + + if required and (len(present_keys) < 1): + raise ValueError(f"At least one of the following options is required: {orig_keys}") + +class FixedDict(dict): + """ + FixedDict: A dictionnary not allowing new keys to be created after initialisation. + + >>> f = FixedDict(**{'count':1}) + >>> f['count'] = 2 + >>> f['king'] = 3 + File "...", line ..., in __setitem__ + raise ConfigError(f'Option "{k}" has no defined default') + """ + + from vyos import ConfigError + + def __init__(self, **options): + self._allowed = options.keys() + super().__init__(**options) + + def __setitem__(self, k, v): + """ + __setitem__ is a builtin which is called by python when setting dict values: + >>> d = dict() + >>> d['key'] = 'value' + >>> d + {'key': 'value'} + + is syntaxic sugar for + + >>> d = dict() + >>> d.__setitem__('key','value') + >>> d + {'key': 'value'} + """ + if k not in self._allowed: + raise ConfigError(f'Option "{k}" has no defined default') + super().__setitem__(k, v) + diff --git a/python/vyos/utils/disk.py b/python/vyos/utils/disk.py new file mode 100644 index 0000000..d4271eb --- /dev/null +++ b/python/vyos/utils/disk.py @@ -0,0 +1,72 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from pathlib import Path + +def device_from_id(id): + """ Return the device name from (partial) disk id """ + path = Path('/dev/disk/by-id') + for device in path.iterdir(): + if device.name.endswith(id): + return device.readlink().stem + +def get_storage_stats(directory, human_units=True): + """ Return basic storage stats for given directory """ + from re import sub as re_sub + from vyos.utils.process import cmd + from vyos.utils.convert import human_to_bytes + + # XXX: using `df -h` and converting human units to bytes + # may seem pointless, but there's a reason. + # df uses different header field names with `-h` and without it ("Size" vs "1K-blocks") + # and outputs values in 1K blocks without `-h`, + # so some amount of conversion is needed anyway. + # Using `df -h` by default seems simpler. + # + # This is what the output looks like, as of Debian Buster/Bullseye: + # $ df -h -t ext4 --output=source,size,used,avail,pcent + # Filesystem Size Used Avail Use% + # /dev/sda1 16G 7.6G 7.3G 51% + + out = cmd(f"df -h --output=source,size,used,avail,pcent {directory}") + lines = out.splitlines() + lists = [l.split() for l in lines] + res = {lists[0][i]: lists[1][i] for i in range(len(lists[0]))} + + convert = (lambda x: x) if human_units else human_to_bytes + + stats = {} + + stats["filesystem"] = res["Filesystem"] + stats["size"] = convert(res["Size"]) + stats["used"] = convert(res["Used"]) + stats["avail"] = convert(res["Avail"]) + stats["use_percentage"] = re_sub(r'%', '', res["Use%"]) + + return stats + +def get_persistent_storage_stats(human_units=True): + from os.path import exists as path_exists + + persistence_dir = "/usr/lib/live/mount/persistence" + if path_exists(persistence_dir): + stats = get_storage_stats(persistence_dir, human_units=human_units) + else: + # If the persistence path doesn't exist, + # the system is running from a live CD + # and the concept of persistence storage stats is not applicable + stats = None + + return stats diff --git a/python/vyos/utils/error.py b/python/vyos/utils/error.py new file mode 100644 index 0000000..8d4709b --- /dev/null +++ b/python/vyos/utils/error.py @@ -0,0 +1,24 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from enum import IntEnum + +class cli_shell_api_err(IntEnum): + """ vyatta-cfg/src/vyos-errors.h """ + VYOS_SUCCESS = 0 + VYOS_GENERAL_FAILURE = 1 + VYOS_INVALID_PATH = 2 + VYOS_EMPTY_CONFIG = 3 + VYOS_CONFIG_PARSE_ERROR = 4 diff --git a/python/vyos/utils/file.py b/python/vyos/utils/file.py new file mode 100644 index 0000000..eaebb57 --- /dev/null +++ b/python/vyos/utils/file.py @@ -0,0 +1,214 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +from vyos.utils.permission import chown + +def makedir(path, user=None, group=None): + if os.path.exists(path): + return + os.makedirs(path, mode=0o755) + chown(path, user, group) + +def file_is_persistent(path): + import re + location = r'^(/config|/opt/vyatta/etc/config)' + absolute = os.path.abspath(os.path.dirname(path)) + return re.match(location,absolute) + +def read_file(fname, defaultonfailure=None): + """ + read the content of a file, stripping any end characters (space, newlines) + should defaultonfailure be not None, it is returned on failure to read + """ + try: + """ Read a file to string """ + with open(fname, 'r') as f: + data = f.read().strip() + return data + except Exception as e: + if defaultonfailure is not None: + return defaultonfailure + raise e + +def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=None, append=False): + """ + Write content of data to given fname, should defaultonfailure be not None, + it is returned on failure to read. + + If directory of file is not present, it is auto-created. + """ + dirname = os.path.dirname(fname) + if dirname and not os.path.isdir(dirname): + os.makedirs(dirname, mode=0o755, exist_ok=False) + chown(dirname, user, group) + + try: + """ Write a file to string """ + bytes = 0 + with open(fname, 'w' if not append else 'a') as f: + bytes = f.write(data) + chown(fname, user, group) + chmod(fname, mode) + return bytes + except Exception as e: + if defaultonfailure is not None: + return defaultonfailure + raise e + +def read_json(fname, defaultonfailure=None): + """ + read and json decode the content of a file + should defaultonfailure be not None, it is returned on failure to read + """ + import json + try: + with open(fname, 'r') as f: + data = json.load(f) + return data + except Exception as e: + if defaultonfailure is not None: + return defaultonfailure + raise e + +def chown(path, user=None, group=None, recursive=False): + """ change file/directory owner """ + from pwd import getpwnam + from grp import getgrnam + + if user is None and group is None: + return False + + # path may also be an open file descriptor + if not isinstance(path, int) and not os.path.exists(path): + return False + + # keep current value if not specified otherwise + uid = -1 + gid = -1 + + if user: + uid = getpwnam(user).pw_uid + if group: + gid = getgrnam(group).gr_gid + + if recursive: + for dirpath, dirnames, filenames in os.walk(path): + os.chown(dirpath, uid, gid) + for filename in filenames: + os.chown(os.path.join(dirpath, filename), uid, gid) + else: + os.chown(path, uid, gid) + return True + + +def chmod(path, bitmask): + # path may also be an open file descriptor + if not isinstance(path, int) and not os.path.exists(path): + return + if bitmask is None: + return + os.chmod(path, bitmask) + + +def chmod_600(path): + """ Make file only read/writable by owner """ + from stat import S_IRUSR, S_IWUSR + + bitmask = S_IRUSR | S_IWUSR + chmod(path, bitmask) + + +def chmod_750(path): + """ Make file/directory only executable to user and group """ + from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP + + bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP + chmod(path, bitmask) + + +def chmod_755(path): + """ Make file executable by all """ + from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH + + bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | \ + S_IROTH | S_IXOTH + chmod(path, bitmask) + +def chmod_2775(path): + """ user/group permissions with set-group-id bit set """ + from stat import S_ISGID, S_IRWXU, S_IRWXG, S_IROTH, S_IXOTH + + bitmask = S_ISGID | S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH + chmod(path, bitmask) + +def chmod_775(path): + """ Make file executable by all """ + from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IXOTH + + bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | \ + S_IROTH | S_IXOTH + chmod(path, bitmask) + +def file_permissions(path): + """ Return file permissions in string format, e.g '0755' """ + return oct(os.stat(path).st_mode)[4:] + +def makedir(path, user=None, group=None): + if os.path.exists(path): + return + os.makedirs(path, mode=0o755) + chown(path, user, group) + +def wait_for_inotify(file_path, pre_hook=None, event_type=None, timeout=None, sleep_interval=0.1): + """ Waits for an inotify event to occur """ + if not os.path.dirname(file_path): + raise ValueError( + "File path {} does not have a directory part (required for inotify watching)".format(file_path)) + if not os.path.basename(file_path): + raise ValueError( + "File path {} does not have a file part, do not know what to watch for".format(file_path)) + + from inotify.adapters import Inotify + from time import time + from time import sleep + + time_start = time() + + i = Inotify() + i.add_watch(os.path.dirname(file_path)) + + if pre_hook: + pre_hook() + + for event in i.event_gen(yield_nones=True): + if (timeout is not None) and ((time() - time_start) > timeout): + # If the function didn't return until this point, + # the file failed to have been written to and closed within the timeout + raise OSError("Waiting for file {} to be written has failed".format(file_path)) + + # Most such events don't take much time, so it's better to check right away + # and sleep later. + if event is not None: + (_, type_names, path, filename) = event + if filename == os.path.basename(file_path): + if event_type in type_names: + return + sleep(sleep_interval) + +def wait_for_file_write_complete(file_path, pre_hook=None, timeout=None, sleep_interval=0.1): + """ Waits for a process to close a file after opening it in write mode. """ + wait_for_inotify(file_path, + event_type='IN_CLOSE_WRITE', pre_hook=pre_hook, timeout=timeout, sleep_interval=sleep_interval) diff --git a/python/vyos/utils/io.py b/python/vyos/utils/io.py new file mode 100644 index 0000000..205210b --- /dev/null +++ b/python/vyos/utils/io.py @@ -0,0 +1,113 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from typing import Callable, Optional + +def print_error(str='', end='\n'): + """ + Print `str` to stderr, terminated with `end`. + Used for warnings and out-of-band messages to avoid mangling precious + stdout output. + """ + import sys + sys.stderr.write(str) + sys.stderr.write(end) + sys.stderr.flush() + +def ask_input(question, default='', numeric_only=False, valid_responses=[], + no_echo=False, non_empty=False): + from getpass import getpass + question_out = question + if default: + question_out += f' (Default: {default})' + response = '' + while True: + if not no_echo: + response = input(question_out + ' ').strip() + else: + response = getpass(question_out + ' ').strip() + if not response and default: + return default + if numeric_only: + if not response.isnumeric(): + print("Invalid value, try again.") + continue + response = int(response) + if valid_responses and response not in valid_responses: + print("Invalid value, try again.") + continue + if non_empty and not response: + print("Non-empty value required; try again.") + continue + break + return response + +def ask_yes_no(question, default=False) -> bool: + """Ask a yes/no question via input() and return their answer.""" + from sys import stdout + default_msg = "[Y/n]" if default else "[y/N]" + while True: + try: + stdout.write("%s %s " % (question, default_msg)) + c = input().lower() + if c == '': + return default + elif c in ("y", "ye", "yes"): + return True + elif c in ("n", "no"): + return False + else: + stdout.write("Please respond with yes/y or no/n\n") + except EOFError: + stdout.write("\nPlease respond with yes/y or no/n\n") + except KeyboardInterrupt: + return False + +def is_interactive(): + """Try to determine if the routine was called from an interactive shell.""" + import os, sys + return os.getenv('TERM', default=False) and sys.stderr.isatty() and sys.stdout.isatty() + +def is_dumb_terminal(): + """Check if the current TTY is dumb, so that we can disable advanced terminal features.""" + import os + return os.getenv('TERM') in ['vt100', 'dumb'] + +def select_entry(l: list, list_msg: str = '', prompt_msg: str = '', + list_format: Optional[Callable] = None, + default_entry: Optional[int] = None) -> str: + """Select an entry from a list + + Args: + l (list): a list of entries + list_msg (str): a message to print before listing the entries + prompt_msg (str): a message to print as prompt for selection + + Returns: + str: a selected entry + """ + en = list(enumerate(l, 1)) + print(list_msg) + for i, e in en: + if list_format: + print(f'\t{i}: {list_format(e)}') + else: + print(f'\t{i}: {e}') + valid_entry = range(1, len(l)+1) + if default_entry and default_entry not in valid_entry: + default_entry = None + select = ask_input(prompt_msg, default=default_entry, numeric_only=True, + valid_responses=valid_entry) + return next(filter(lambda x: x[0] == select, en))[1] diff --git a/python/vyos/utils/kernel.py b/python/vyos/utils/kernel.py new file mode 100644 index 0000000..847f801 --- /dev/null +++ b/python/vyos/utils/kernel.py @@ -0,0 +1,113 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os + +def check_kmod(k_mod): + """ Common utility function to load required kernel modules on demand """ + from vyos import ConfigError + from vyos.utils.process import call + if isinstance(k_mod, str): + k_mod = k_mod.split() + for module in k_mod: + if not os.path.exists(f'/sys/module/{module}'): + if call(f'modprobe {module}') != 0: + raise ConfigError(f'Loading Kernel module {module} failed') + +def unload_kmod(k_mod): + """ Common utility function to unload required kernel modules on demand """ + from vyos import ConfigError + from vyos.utils.process import call + if isinstance(k_mod, str): + k_mod = k_mod.split() + for module in k_mod: + if os.path.exists(f'/sys/module/{module}'): + if call(f'rmmod {module}') != 0: + raise ConfigError(f'Unloading Kernel module {module} failed') + +def list_loaded_modules(): + """ Returns the list of currently loaded kernel modules """ + from os import listdir + return listdir('/sys/module/') + +def get_module_data(module: str): + """ Retrieves information about a module """ + from os import listdir + from os.path import isfile, dirname, basename, join + from vyos.utils.file import read_file + + def _get_file(path): + # Some files inside some modules are not readable at all, + # we just skip them. + try: + return read_file(path) + except PermissionError: + return None + + mod_path = join('/sys/module', module) + mod_data = {"name": module, "fields": {}, "parameters": {}} + + for f in listdir(mod_path): + if f in ["sections", "notes", "uevent"]: + # The uevent file is not readable + # and module build info and memory layout + # in notes and sections generally aren't useful + # for anything but kernel debugging. + pass + elif f == "drivers": + # Drivers are dir symlinks, + # we just list them + drivers = listdir(join(mod_path, f)) + if drivers: + mod_data["drivers"] = drivers + elif f == "holders": + # Holders (module that use this one) + # are always symlink to other modules. + # We only need the list. + holders = listdir(join(mod_path, f)) + if holders: + mod_data["holders"] = holders + elif f == "parameters": + # Many modules keep their configuration + # in the "parameters" subdir. + ppath = join(mod_path, "parameters") + ps = listdir(ppath) + for p in ps: + data = _get_file(join(ppath, p)) + if data: + mod_data["parameters"][p] = data + else: + # Everything else... + # There are standard fields like refcount and initstate, + # but many modules also keep custom information or settings + # in top-level fields. + # For now we don't separate well-known and custom fields. + if isfile(join(mod_path, f)): + data = _get_file(join(mod_path, f)) + if data: + mod_data["fields"][f] = data + else: + raise RuntimeError(f"Unexpected directory inside module {module}: {f}") + + return mod_data + +def lsmod(): + """ Returns information about all loaded modules. + Like lsmod(8), but more detailed. + """ + mods_data = [] + for m in list_loaded_modules(): + mods_data.append(get_module_data(m)) + return mods_data diff --git a/python/vyos/utils/list.py b/python/vyos/utils/list.py new file mode 100644 index 0000000..63ef720 --- /dev/null +++ b/python/vyos/utils/list.py @@ -0,0 +1,20 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +def is_list_equal(first: list, second: list) -> bool: + """ Check if 2 lists are equal and list not empty """ + if len(first) != len(second) or len(first) == 0: + return False + return sorted(first) == sorted(second) diff --git a/python/vyos/utils/locking.py b/python/vyos/utils/locking.py new file mode 100644 index 0000000..63cb1a8 --- /dev/null +++ b/python/vyos/utils/locking.py @@ -0,0 +1,115 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import fcntl +import re +import time +from pathlib import Path + + +class LockTimeoutError(Exception): + """Custom exception raised when lock acquisition times out.""" + + pass + + +class InvalidLockNameError(Exception): + """Custom exception raised when the lock name is invalid.""" + + pass + + +class Lock: + """Lock class to acquire and release a lock file""" + + def __init__(self, lock_name: str) -> None: + """Lock class constructor + + Args: + lock_name (str): Name of the lock file + + Raises: + InvalidLockNameError: If the lock name is invalid + """ + # Validate lock name + if not re.match(r'^[a-zA-Z0-9_\-]+$', lock_name): + raise InvalidLockNameError(f'Invalid lock name: {lock_name}') + + self.__lock_dir = Path('/run/vyos/lock') + self.__lock_dir.mkdir(parents=True, exist_ok=True) + + self.__lock_file_path: Path = self.__lock_dir / f'{lock_name}.lock' + self.__lock_file = None + + self._is_locked = False + + def __del__(self) -> None: + """Ensure the lock file is removed when the object is deleted""" + self.release() + + @property + def is_locked(self) -> bool: + """Check if the lock is acquired + + Returns: + bool: True if the lock is acquired, False otherwise + """ + return self._is_locked + + def __unlink_lockfile(self) -> None: + """Remove the lock file if it is not currently locked.""" + try: + with self.__lock_file_path.open('w') as f: + fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) + self.__lock_file_path.unlink(missing_ok=True) + except IOError: + # If we cannot acquire the lock, it means another process has it, so we do nothing. + pass + + def acquire(self, timeout: int = 0) -> None: + """Acquire a lock file + + Args: + timeout (int, optional): A time to wait for lock. Defaults to 0. + + Raises: + LockTimeoutError: If lock could not be acquired within timeout + """ + start_time: float = time.time() + while True: + try: + self.__lock_file = self.__lock_file_path.open('w') + fcntl.flock(self.__lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB) + self._is_locked = True + return + except IOError: + if timeout > 0 and (time.time() - start_time) >= timeout: + if self.__lock_file: + self.__lock_file.close() + raise LockTimeoutError( + f'Could not acquire lock within {timeout} seconds' + ) + time.sleep(0.1) + + def release(self) -> None: + """Release a lock file""" + if self.__lock_file and self._is_locked: + try: + fcntl.flock(self.__lock_file, fcntl.LOCK_UN) + self._is_locked = False + finally: + self.__lock_file.close() + self.__lock_file = None + self.__unlink_lockfile() diff --git a/python/vyos/utils/misc.py b/python/vyos/utils/misc.py new file mode 100644 index 0000000..d826559 --- /dev/null +++ b/python/vyos/utils/misc.py @@ -0,0 +1,66 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +def begin(*args): + """ + Evaluate arguments in order and return the result of the *last* argument. + For combining multiple expressions in one statement. Useful for lambdas. + """ + return args[-1] + +def begin0(*args): + """ + Evaluate arguments in order and return the result of the *first* argument. + For combining multiple expressions in one statement. Useful for lambdas. + """ + return args[0] + +def install_into_config(conf, config_paths, override_prompt=True): + # Allows op-mode scripts to install values if called from an active config session + # config_paths: dict of config paths + # override_prompt: if True, user will be prompted before existing nodes are overwritten + if not config_paths: + return None + + from vyos.config import Config + from vyos.utils.io import ask_yes_no + from vyos.utils.process import cmd + if not Config().in_session(): + print('You are not in configure mode, commands to install manually from configure mode:') + for path in config_paths: + print(f'set {path}') + return None + + count = 0 + failed = [] + + for path in config_paths: + if override_prompt and conf.exists(path) and not conf.is_multi(path): + if not ask_yes_no(f'Config node "{node}" already exists. Do you want to overwrite it?'): + continue + + try: + cmd(f'/opt/vyatta/sbin/my_set {path}') + count += 1 + except: + failed.append(path) + + if failed: + print(f'Failed to install {len(failed)} value(s). Commands to manually install:') + for path in failed: + print(f'set {path}') + + if count > 0: + print(f'{count} value(s) installed. Use "compare" to see the pending changes, and "commit" to apply.') diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py new file mode 100644 index 0000000..8fce08d --- /dev/null +++ b/python/vyos/utils/network.py @@ -0,0 +1,599 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +def _are_same_ip(one, two): + from socket import AF_INET + from socket import AF_INET6 + from socket import inet_pton + from vyos.template import is_ipv4 + # compare the binary representation of the IP + f_one = AF_INET if is_ipv4(one) else AF_INET6 + s_two = AF_INET if is_ipv4(two) else AF_INET6 + return inet_pton(f_one, one) == inet_pton(f_one, two) + +def get_protocol_by_name(protocol_name): + """Get protocol number by protocol name + + % get_protocol_by_name('tcp') + % 6 + """ + import socket + try: + protocol_number = socket.getprotobyname(protocol_name) + return protocol_number + except socket.error: + return protocol_name + +def interface_exists(interface) -> bool: + import os + return os.path.exists(f'/sys/class/net/{interface}') + +def is_netns_interface(interface, netns): + from vyos.utils.process import rc_cmd + rc, out = rc_cmd(f'sudo ip netns exec {netns} ip link show dev {interface}') + if rc == 0: + return True + return False + +def get_netns_all() -> list: + from json import loads + from vyos.utils.process import cmd + tmp = loads(cmd('ip --json netns ls')) + return [ netns['name'] for netns in tmp ] + +def get_vrf_members(vrf: str) -> list: + """ + Get list of interface VRF members + :param vrf: str + :return: list + """ + import json + from vyos.utils.process import cmd + interfaces = [] + try: + if not interface_exists(vrf): + raise ValueError(f'VRF "{vrf}" does not exist!') + output = cmd(f'ip --json --brief link show vrf {vrf}') + answer = json.loads(output) + for data in answer: + if 'ifname' in data: + interfaces.append(data.get('ifname')) + except: + pass + return interfaces + +def get_interface_vrf(interface): + """ Returns VRF of given interface """ + from vyos.utils.dict import dict_search + from vyos.utils.network import get_interface_config + tmp = get_interface_config(interface) + if dict_search('linkinfo.info_slave_kind', tmp) == 'vrf': + return tmp['master'] + return 'default' + +def get_vrf_tableid(interface: str): + """ Return VRF table ID for given interface name or None """ + from vyos.utils.dict import dict_search + table = None + tmp = get_interface_config(interface) + # Check if we are "the" VRF interface + if dict_search('linkinfo.info_kind', tmp) == 'vrf': + table = tmp['linkinfo']['info_data']['table'] + # or an interface bound to a VRF + elif dict_search('linkinfo.info_slave_kind', tmp) == 'vrf': + table = tmp['linkinfo']['info_slave_data']['table'] + return table + +def get_interface_config(interface): + """ Returns the used encapsulation protocol for given interface. + If interface does not exist, None is returned. + """ + if not interface_exists(interface): + return None + from json import loads + from vyos.utils.process import cmd + tmp = loads(cmd(f'ip --detail --json link show dev {interface}'))[0] + return tmp + +def get_interface_address(interface): + """ Returns the used encapsulation protocol for given interface. + If interface does not exist, None is returned. + """ + if not interface_exists(interface): + return None + from json import loads + from vyos.utils.process import cmd + tmp = loads(cmd(f'ip --detail --json addr show dev {interface}'))[0] + return tmp + +def get_interface_namespace(interface: str): + """ + Returns wich netns the interface belongs to + """ + from json import loads + from vyos.utils.process import cmd + + # Bail out early if netns does not exist + tmp = cmd(f'ip --json netns ls') + if not tmp: return None + + for ns in loads(tmp): + netns = f'{ns["name"]}' + # Search interface in each netns + data = loads(cmd(f'ip netns exec {netns} ip --json link show')) + for tmp in data: + if interface == tmp["ifname"]: + return netns + +def is_ipv6_tentative(iface: str, ipv6_address: str) -> bool: + """Check if IPv6 address is in tentative state. + + This function checks if an IPv6 address on a specific network interface is + in the tentative state. IPv6 tentative addresses are not fully configured + and are undergoing Duplicate Address Detection (DAD) to ensure they are + unique on the network. + + Args: + iface (str): The name of the network interface. + ipv6_address (str): The IPv6 address to check. + + Returns: + bool: True if the IPv6 address is tentative, False otherwise. + """ + import json + from vyos.utils.process import rc_cmd + + rc, out = rc_cmd(f'ip -6 --json address show dev {iface}') + if rc: + return False + + data = json.loads(out) + for addr_info in data[0]['addr_info']: + if ( + addr_info.get('local') == ipv6_address and + addr_info.get('tentative', False) + ): + return True + return False + +def is_wwan_connected(interface): + """ Determine if a given WWAN interface, e.g. wwan0 is connected to the + carrier network or not """ + import json + from vyos.utils.dict import dict_search + from vyos.utils.process import cmd + from vyos.utils.process import is_systemd_service_active + + if not interface.startswith('wwan'): + raise ValueError(f'Specified interface "{interface}" is not a WWAN interface') + + # ModemManager is required for connection(s) - if service is not running, + # there won't be any connection at all! + if not is_systemd_service_active('ModemManager.service'): + return False + + modem = interface.lstrip('wwan') + + tmp = cmd(f'mmcli --modem {modem} --output-json') + tmp = json.loads(tmp) + + # return True/False if interface is in connected state + return dict_search('modem.generic.state', tmp) == 'connected' + +def get_bridge_fdb(interface): + """ Returns the forwarding database entries for a given interface """ + if not interface_exists(interface): + return None + from json import loads + from vyos.utils.process import cmd + tmp = loads(cmd(f'bridge -j fdb show dev {interface}')) + return tmp + +def get_all_vrfs(): + """ Return a dictionary of all system wide known VRF instances """ + from json import loads + from vyos.utils.process import cmd + tmp = loads(cmd('ip --json vrf list')) + # Result is of type [{"name":"red","table":1000},{"name":"blue","table":2000}] + # so we will re-arrange it to a more nicer representation: + # {'red': {'table': 1000}, 'blue': {'table': 2000}} + data = {} + for entry in tmp: + name = entry.pop('name') + data[name] = entry + return data + +def interface_list() -> list: + from vyos.ifconfig import Section + """ + Get list of interfaces in system + :rtype: list + """ + return Section.interfaces() + + +def vrf_list() -> list: + """ + Get list of VRFs in system + :rtype: list + """ + return list(get_all_vrfs().keys()) + +def mac2eui64(mac, prefix=None): + """ + Convert a MAC address to a EUI64 address or, with prefix provided, a full + IPv6 address. + Thankfully copied from https://gist.github.com/wido/f5e32576bb57b5cc6f934e177a37a0d3 + """ + import re + from ipaddress import ip_network + # http://tools.ietf.org/html/rfc4291#section-2.5.1 + eui64 = re.sub(r'[.:-]', '', mac).lower() + eui64 = eui64[0:6] + 'fffe' + eui64[6:] + eui64 = hex(int(eui64[0:2], 16) ^ 2)[2:].zfill(2) + eui64[2:] + + if prefix is None: + return ':'.join(re.findall(r'.{4}', eui64)) + else: + try: + net = ip_network(prefix, strict=False) + euil = int('0x{0}'.format(eui64), 16) + return str(net[euil]) + except: # pylint: disable=bare-except + return + +def check_port_availability(ipaddress, port, protocol): + """ + Check if port is available and not used by any service + Return False if a port is busy or IP address does not exists + Should be used carefully for services that can start listening + dynamically, because IP address may be dynamic too + """ + from socketserver import TCPServer, UDPServer + from ipaddress import ip_address + + # verify arguments + try: + ipaddress = ip_address(ipaddress).compressed + except: + raise ValueError(f'The {ipaddress} is not a valid IPv4 or IPv6 address') + if port not in range(1, 65536): + raise ValueError(f'The port number {port} is not in the 1-65535 range') + if protocol not in ['tcp', 'udp']: + raise ValueError(f'The protocol {protocol} is not supported. Only tcp and udp are allowed') + + # check port availability + try: + if protocol == 'tcp': + server = TCPServer((ipaddress, port), None, bind_and_activate=True) + if protocol == 'udp': + server = UDPServer((ipaddress, port), None, bind_and_activate=True) + server.server_close() + except Exception as e: + # errno.h: + #define EADDRINUSE 98 /* Address already in use */ + if e.errno == 98: + return False + + return True + +def is_listen_port_bind_service(port: int, service: str) -> bool: + """Check if listen port bound to expected program name + :param port: Bind port + :param service: Program name + :return: bool + + Example: + % is_listen_port_bind_service(443, 'nginx') + True + % is_listen_port_bind_service(443, 'ocserv-main') + False + """ + from psutil import net_connections as connections + from psutil import Process as process + for connection in connections(): + addr = connection.laddr + pid = connection.pid + pid_name = process(pid).name() + pid_port = addr.port + if service == pid_name and port == pid_port: + return True + return False + +def is_ipv6_link_local(addr): + """ Check if addrsss is an IPv6 link-local address. Returns True/False """ + from ipaddress import ip_interface + from vyos.template import is_ipv6 + addr = addr.split('%')[0] + if is_ipv6(addr): + if ip_interface(addr).is_link_local: + return True + + return False + +def is_addr_assigned(ip_address, vrf=None, return_ifname=False, include_vrf=False) -> bool | str: + """ Verify if the given IPv4/IPv6 address is assigned to any interface """ + from netifaces import interfaces + from vyos.utils.network import get_interface_config + from vyos.utils.dict import dict_search + + for interface in interfaces(): + # Check if interface belongs to the requested VRF, if this is not the + # case there is no need to proceed with this data set - continue loop + # with next element + tmp = get_interface_config(interface) + if dict_search('master', tmp) != vrf and not include_vrf: + continue + + if is_intf_addr_assigned(interface, ip_address): + return interface if return_ifname else True + + return False + +def is_intf_addr_assigned(ifname: str, addr: str, netns: str=None) -> bool: + """ + Verify if the given IPv4/IPv6 address is assigned to specific interface. + It can check both a single IP address (e.g. 192.0.2.1 or a assigned CIDR + address 192.0.2.1/24. + """ + import json + import jmespath + + from vyos.utils.process import rc_cmd + from ipaddress import ip_interface + + netns_cmd = f'ip netns exec {netns}' if netns else '' + rc, out = rc_cmd(f'{netns_cmd} ip --json address show dev {ifname}') + if rc == 0: + json_out = json.loads(out) + addresses = jmespath.search("[].addr_info[].{family: family, address: local, prefixlen: prefixlen}", json_out) + for address_info in addresses: + family = address_info['family'] + address = address_info['address'] + prefixlen = address_info['prefixlen'] + # Remove the interface name if present in the given address + if '%' in addr: + addr = addr.split('%')[0] + interface = ip_interface(f"{address}/{prefixlen}") + if ip_interface(addr) == interface or address == addr: + return True + + return False + +def is_loopback_addr(addr): + """ Check if supplied IPv4/IPv6 address is a loopback address """ + from ipaddress import ip_address + return ip_address(addr).is_loopback + +def is_wireguard_key_pair(private_key: str, public_key:str) -> bool: + """ + Checks if public/private keys are keypair + :param private_key: Wireguard private key + :type private_key: str + :param public_key: Wireguard public key + :type public_key: str + :return: If public/private keys are keypair returns True else False + :rtype: bool + """ + from vyos.utils.process import cmd + gen_public_key = cmd('wg pubkey', input=private_key) + if gen_public_key == public_key: + return True + else: + return False + +def is_subnet_connected(subnet, primary=False): + """ + Verify is the given IPv4/IPv6 subnet is connected to any interface on this + system. + + primary check if the subnet is reachable via the primary IP address of this + interface, or in other words has a broadcast address configured. ISC DHCP + for instance will complain if it should listen on non broadcast interfaces. + + Return True/False + """ + from ipaddress import ip_address + from ipaddress import ip_network + + from netifaces import ifaddresses + from netifaces import interfaces + from netifaces import AF_INET + from netifaces import AF_INET6 + + from vyos.template import is_ipv6 + + # determine IP version (AF_INET or AF_INET6) depending on passed address + addr_type = AF_INET + if is_ipv6(subnet): + addr_type = AF_INET6 + + for interface in interfaces(): + # check if the requested address type is configured at all + if addr_type not in ifaddresses(interface).keys(): + continue + + # An interface can have multiple addresses, but some software components + # only support the primary address :( + if primary: + ip = ifaddresses(interface)[addr_type][0]['addr'] + if ip_address(ip) in ip_network(subnet): + return True + else: + # Check every assigned IP address if it is connected to the subnet + # in question + for ip in ifaddresses(interface)[addr_type]: + # remove interface extension (e.g. %eth0) that gets thrown on the end of _some_ addrs + addr = ip['addr'].split('%')[0] + if ip_address(addr) in ip_network(subnet): + return True + + return False + +def is_afi_configured(interface: str, afi): + """ Check if given address family is configured, or in other words - an IP + address is assigned to the interface. """ + from netifaces import ifaddresses + from netifaces import AF_INET + from netifaces import AF_INET6 + + if afi not in [AF_INET, AF_INET6]: + raise ValueError('Address family must be in [AF_INET, AF_INET6]') + + try: + addresses = ifaddresses(interface) + except ValueError as e: + print(e) + return False + + return afi in addresses + +def get_vxlan_vlan_tunnels(interface: str) -> list: + """ Return a list of strings with VLAN IDs configured in the Kernel """ + from json import loads + from vyos.utils.process import cmd + + if not interface.startswith('vxlan'): + raise ValueError('Only applicable for VXLAN interfaces!') + + # Determine current OS Kernel configured VLANs + # + # $ bridge -j -p vlan tunnelshow dev vxlan0 + # [ { + # "ifname": "vxlan0", + # "tunnels": [ { + # "vlan": 10, + # "vlanEnd": 11, + # "tunid": 10010, + # "tunidEnd": 10011 + # },{ + # "vlan": 20, + # "tunid": 10020 + # } ] + # } ] + # + os_configured_vlan_ids = [] + tmp = loads(cmd(f'bridge --json vlan tunnelshow dev {interface}')) + if tmp: + for tunnel in tmp[0].get('tunnels', {}): + vlanStart = tunnel['vlan'] + if 'vlanEnd' in tunnel: + vlanEnd = tunnel['vlanEnd'] + # Build a real list for user VLAN IDs + vlan_list = list(range(vlanStart, vlanEnd +1)) + # Convert list of integers to list or strings + os_configured_vlan_ids.extend(map(str, vlan_list)) + # Proceed with next tunnel - this one is complete + continue + + # Add single tunel id - not part of a range + os_configured_vlan_ids.append(str(vlanStart)) + + return os_configured_vlan_ids + +def get_vxlan_vni_filter(interface: str) -> list: + """ Return a list of strings with VNIs configured in the Kernel""" + from json import loads + from vyos.utils.process import cmd + + if not interface.startswith('vxlan'): + raise ValueError('Only applicable for VXLAN interfaces!') + + # Determine current OS Kernel configured VNI filters in VXLAN interface + # + # $ bridge -j vni show dev vxlan1 + # [{"ifname":"vxlan1","vnis":[{"vni":100},{"vni":200},{"vni":300,"vniEnd":399}]}] + # + # Example output: ['10010', '10020', '10021', '10022'] + os_configured_vnis = [] + tmp = loads(cmd(f'bridge --json vni show dev {interface}')) + if tmp: + for tunnel in tmp[0].get('vnis', {}): + vniStart = tunnel['vni'] + if 'vniEnd' in tunnel: + vniEnd = tunnel['vniEnd'] + # Build a real list for user VNIs + vni_list = list(range(vniStart, vniEnd +1)) + # Convert list of integers to list or strings + os_configured_vnis.extend(map(str, vni_list)) + # Proceed with next tunnel - this one is complete + continue + + # Add single tunel id - not part of a range + os_configured_vnis.append(str(vniStart)) + + return os_configured_vnis + +# Calculate prefix length of an IPv6 range, where possible +# Python-ified from source: https://gitlab.isc.org/isc-projects/dhcp/-/blob/master/keama/confparse.c#L4591 +def ipv6_prefix_length(low, high): + import socket + + bytemasks = [0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff] + + try: + lo = bytearray(socket.inet_pton(socket.AF_INET6, low)) + hi = bytearray(socket.inet_pton(socket.AF_INET6, high)) + except: + return None + + xor = bytearray(a ^ b for a, b in zip(lo, hi)) + + plen = 0 + while plen < 128 and xor[plen // 8] == 0: + plen += 8 + + if plen == 128: + return plen + + for i in range((plen // 8) + 1, 16): + if xor[i] != 0: + return None + + for i in range(8): + msk = ~xor[plen // 8] & 0xff + + if msk == bytemasks[i]: + return plen + i + 1 + + return None + +def get_nft_vrf_zone_mapping() -> dict: + """ + Retrieve current nftables conntrack mapping list from Kernel + + returns: [{'interface': 'red', 'vrf_tableid': 1000}, + {'interface': 'eth2', 'vrf_tableid': 1000}, + {'interface': 'blue', 'vrf_tableid': 2000}] + """ + from json import loads + from jmespath import search + from vyos.utils.process import cmd + output = [] + tmp = loads(cmd('sudo nft -j list table inet vrf_zones')) + # {'nftables': [{'metainfo': {'json_schema_version': 1, + # 'release_name': 'Old Doc Yak #3', + # 'version': '1.0.9'}}, + # {'table': {'family': 'inet', 'handle': 6, 'name': 'vrf_zones'}}, + # {'map': {'elem': [['eth0', 666], + # ['dum0', 666], + # ['wg500', 666], + # ['bond10.666', 666]], + vrf_list = search('nftables[].map.elem | [0]', tmp) + if not vrf_list: + return output + for (vrf_name, vrf_id) in vrf_list: + output.append({'interface' : vrf_name, 'vrf_tableid' : vrf_id}) + return output diff --git a/python/vyos/utils/permission.py b/python/vyos/utils/permission.py new file mode 100644 index 0000000..d938b49 --- /dev/null +++ b/python/vyos/utils/permission.py @@ -0,0 +1,78 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os + +def chown(path, user, group): + """ change file/directory owner """ + from pwd import getpwnam + from grp import getgrnam + + if user is None or group is None: + return False + + # path may also be an open file descriptor + if not isinstance(path, int) and not os.path.exists(path): + return False + + uid = getpwnam(user).pw_uid + gid = getgrnam(group).gr_gid + os.chown(path, uid, gid) + return True + +def chmod(path, bitmask): + # path may also be an open file descriptor + if not isinstance(path, int) and not os.path.exists(path): + return + if bitmask is None: + return + os.chmod(path, bitmask) + +def chmod_600(path): + """ make file only read/writable by owner """ + from stat import S_IRUSR, S_IWUSR + + bitmask = S_IRUSR | S_IWUSR + chmod(path, bitmask) + +def chmod_750(path): + """ make file/directory only executable to user and group """ + from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP + + bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP + chmod(path, bitmask) + +def chmod_755(path): + """ make file executable by all """ + from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH + + bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | \ + S_IROTH | S_IXOTH + chmod(path, bitmask) + +def is_admin() -> bool: + """Look if current user is in sudo group""" + from getpass import getuser + from grp import getgrnam + current_user = getuser() + (_, _, _, admin_group_members) = getgrnam('sudo') + return current_user in admin_group_members + +def get_cfg_group_id(): + from grp import getgrnam + from vyos.defaults import cfg_group + + group_data = getgrnam(cfg_group) + return group_data.gr_gid diff --git a/python/vyos/utils/process.py b/python/vyos/utils/process.py new file mode 100644 index 0000000..ce880f4 --- /dev/null +++ b/python/vyos/utils/process.py @@ -0,0 +1,262 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os + +from subprocess import Popen +from subprocess import PIPE +from subprocess import STDOUT +from subprocess import DEVNULL + +def popen(command, flag='', shell=None, input=None, timeout=None, env=None, + stdout=PIPE, stderr=PIPE, decode='utf-8'): + """ + popen is a wrapper helper aound subprocess.Popen + with it default setting it will return a tuple (out, err) + out: the output of the program run + err: the error code returned by the program + + it can be affected by the following flags: + shell: do not try to auto-detect if a shell is required + for example if a pipe (|) or redirection (>, >>) is used + input: data to sent to the child process via STDIN + the data should be bytes but string will be converted + timeout: time after which the command will be considered to have failed + env: mapping that defines the environment variables for the new process + stdout: define how the output of the program should be handled + - PIPE (default), sends stdout to the output + - DEVNULL, discard the output + stderr: define how the output of the program should be handled + - None (default), send/merge the data to/with stderr + - PIPE, popen will append it to output + - STDOUT, send the data to be merged with stdout + - DEVNULL, discard the output + decode: specify the expected text encoding (utf-8, ascii, ...) + the default is explicitely utf-8 which is python's own default + + usage: + get both stdout and stderr: popen('command', stdout=PIPE, stderr=STDOUT) + discard stdout and get stderr: popen('command', stdout=DEVNUL, stderr=PIPE) + """ + + # airbag must be left as an import in the function as otherwise we have a + # a circual import dependency + from vyos import debug + from vyos import airbag + + # log if the flag is set, otherwise log if command is set + if not debug.enabled(flag): + flag = 'command' + + cmd_msg = f"cmd '{command}'" + debug.message(cmd_msg, flag) + + use_shell = shell + stdin = None + if shell is None: + use_shell = False + if ' ' in command: + use_shell = True + if env: + use_shell = True + + if input: + stdin = PIPE + input = input.encode() if type(input) is str else input + + p = Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, + env=env, shell=use_shell) + + pipe = p.communicate(input, timeout) + + pipe_out = b'' + if stdout == PIPE: + pipe_out = pipe[0] + + pipe_err = b'' + if stderr == PIPE: + pipe_err = pipe[1] + + str_out = pipe_out.decode(decode).replace('\r\n', '\n').strip() + str_err = pipe_err.decode(decode).replace('\r\n', '\n').strip() + + out_msg = f"returned (out):\n{str_out}" + if str_out: + debug.message(out_msg, flag) + + if str_err: + from sys import stderr + err_msg = f"returned (err):\n{str_err}" + # this message will also be send to syslog via airbag + debug.message(err_msg, flag, destination=stderr) + + # should something go wrong, report this too via airbag + airbag.noteworthy(cmd_msg) + airbag.noteworthy(out_msg) + airbag.noteworthy(err_msg) + + return str_out, p.returncode + + +def run(command, flag='', shell=None, input=None, timeout=None, env=None, + stdout=DEVNULL, stderr=PIPE, decode='utf-8'): + """ + A wrapper around popen, which discard the stdout and + will return the error code of a command + """ + _, code = popen( + command, flag, + stdout=stdout, stderr=stderr, + input=input, timeout=timeout, + env=env, shell=shell, + decode=decode, + ) + return code + + +def cmd(command, flag='', shell=None, input=None, timeout=None, env=None, + stdout=PIPE, stderr=PIPE, decode='utf-8', raising=None, message='', + expect=[0]): + """ + A wrapper around popen, which returns the stdout and + will raise the error code of a command + + raising: specify which call should be used when raising + the class should only require a string as parameter + (default is OSError) with the error code + expect: a list of error codes to consider as normal + """ + decoded, code = popen( + command, flag, + stdout=stdout, stderr=stderr, + input=input, timeout=timeout, + env=env, shell=shell, + decode=decode, + ) + if code not in expect: + feedback = message + '\n' if message else '' + feedback += f'failed to run command: {command}\n' + feedback += f'returned: {decoded}\n' + feedback += f'exit code: {code}' + if raising is None: + # error code can be recovered with .errno + raise OSError(code, feedback) + else: + raise raising(feedback) + return decoded + + +def rc_cmd(command, flag='', shell=None, input=None, timeout=None, env=None, + stdout=PIPE, stderr=STDOUT, decode='utf-8'): + """ + A wrapper around popen, which returns the return code + of a command and stdout + + % rc_cmd('uname') + (0, 'Linux') + % rc_cmd('ip link show dev eth99') + (1, 'Device "eth99" does not exist.') + """ + out, code = popen( + command, flag, + stdout=stdout, stderr=stderr, + input=input, timeout=timeout, + env=env, shell=shell, + decode=decode, + ) + return code, out + +def call(command, flag='', shell=None, input=None, timeout=None, env=None, + stdout=None, stderr=None, decode='utf-8'): + """ + A wrapper around popen, which print the stdout and + will return the error code of a command + """ + out, code = popen( + command, flag, + stdout=stdout, stderr=stderr, + input=input, timeout=timeout, + env=env, shell=shell, + decode=decode, + ) + if out: + print(out) + return code + +def process_running(pid_file): + """ Checks if a process with PID in pid_file is running """ + from psutil import pid_exists + if not os.path.isfile(pid_file): + return False + with open(pid_file, 'r') as f: + pid = f.read().strip() + return pid_exists(int(pid)) + +def process_named_running(name: str, cmdline: str=None, timeout: int=0): + """ Checks if process with given name is running and returns its PID. + If Process is not running, return None + """ + from psutil import process_iter + def check_process(name, cmdline): + for p in process_iter(['name', 'pid', 'cmdline']): + if cmdline: + if name in p.info['name'] and cmdline in p.info['cmdline']: + return p.info['pid'] + elif name in p.info['name']: + return p.info['pid'] + return None + if timeout: + import time + time_expire = time.time() + timeout + while True: + tmp = check_process(name, cmdline) + if not tmp: + if time.time() > time_expire: + break + time.sleep(0.100) # wait 100ms + continue + return tmp + else: + return check_process(name, cmdline) + return None + +def is_systemd_service_active(service): + """ Test is a specified systemd service is activated. + Returns True if service is active, false otherwise. + Copied from: https://unix.stackexchange.com/a/435317 """ + tmp = cmd(f'systemctl show --value -p ActiveState {service}') + return bool((tmp == 'active')) + +def is_systemd_service_running(service): + """ Test is a specified systemd service is actually running. + Returns True if service is running, false otherwise. + Copied from: https://unix.stackexchange.com/a/435317 """ + tmp = cmd(f'systemctl show --value -p SubState {service}') + return bool((tmp == 'running')) + +def ip_cmd(args, json=True): + """ A helper for easily calling iproute2 commands """ + if json: + from json import loads + res = cmd(f"ip --json {args}").strip() + if res: + return loads(res) + else: + # Many mutation commands like "ip link set" + # return an empty string + return None + else: + res = cmd(f"ip {args}") + return res diff --git a/python/vyos/utils/serial.py b/python/vyos/utils/serial.py new file mode 100644 index 0000000..b646f88 --- /dev/null +++ b/python/vyos/utils/serial.py @@ -0,0 +1,118 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os, re, json +from typing import List + +from vyos.base import Warning +from vyos.utils.io import ask_yes_no +from vyos.utils.process import cmd + +GLOB_GETTY_UNITS = 'serial-getty@*.service' +RE_GETTY_DEVICES = re.compile(r'.+@(.+).service$') + +SD_UNIT_PATH = '/run/systemd/system' +UTMP_PATH = '/run/utmp' + +def get_serial_units(include_devices=[]): + # Since we cannot depend on the current config for decommissioned ports, + # we just grab everything that systemd knows about. + tmp = cmd(f'systemctl list-units {GLOB_GETTY_UNITS} --all --output json --no-pager') + getty_units = json.loads(tmp) + for sdunit in getty_units: + m = RE_GETTY_DEVICES.search(sdunit['unit']) + if m is None: + Warning(f'Serial console unit name "{sdunit["unit"]}" is malformed and cannot be checked for activity!') + continue + + getty_device = m.group(1) + if include_devices and getty_device not in include_devices: + continue + + sdunit['device'] = getty_device + + return getty_units + +def get_authenticated_ports(units): + connected = [] + ports = [ x['device'] for x in units if 'device' in x ] + # + # utmpdump just gives us an easily parseable dump of currently logged-in sessions, for eg: + # $ utmpdump /run/utmp + # Utmp dump of /run/utmp + # [2] [00000] [~~ ] [reboot ] [~ ] [6.6.31-amd64-vyos ] [0.0.0.0 ] [2024-06-18T13:56:53,958484+00:00] + # [1] [00051] [~~ ] [runlevel] [~ ] [6.6.31-amd64-vyos ] [0.0.0.0 ] [2024-06-18T13:57:01,790808+00:00] + # [6] [03178] [tty1] [LOGIN ] [tty1 ] [ ] [0.0.0.0 ] [2024-06-18T13:57:31,015392+00:00] + # [7] [37151] [ts/0] [vyos ] [pts/0 ] [10.9.8.7 ] [10.9.8.7 ] [2024-07-04T13:42:08,760892+00:00] + # [8] [24812] [ts/1] [ ] [pts/1 ] [10.9.8.7 ] [10.9.8.7 ] [2024-06-20T18:10:07,309365+00:00] + # + # We can safely skip blank or LOGIN sessions with valid device names. + # + for line in cmd(f'utmpdump {UTMP_PATH}').splitlines(): + row = line.split('] [') + user_name = row[3].strip() + user_term = row[4].strip() + if user_name and user_name != 'LOGIN' and user_term in ports: + connected.append(user_term) + + return connected + +def restart_login_consoles(prompt_user=False, quiet=True, devices: List[str]=[]): + # restart_login_consoles() is called from both conf- and op-mode scripts, including + # the warning messages and user prompts common to both. + # + # The default case, called with no arguments, is a simple serial-getty restart & + # cleanup wrapper with no output or prompts that can be used from anywhere. + # + # quiet and prompt_user args have been split from an original "no_prompt", in + # order to support the completely silent default use case. "no_prompt" would + # only suppress the user interactive prompt. + # + # quiet intentionally does not suppress a vyos.base.Warning() for malformed + # device names in _get_serial_units(). + # + cmd('systemctl daemon-reload') + + units = get_serial_units(devices) + connected = get_authenticated_ports(units) + + if connected: + if not quiet: + Warning('There are user sessions connected via serial console that '\ + 'will be terminated when serial console settings are changed!') + if not prompt_user: + # This flag is used by conf_mode/system_console.py to reset things, if there's + # a problem, the user should issue a manual restart for serial-getty. + Warning('Please ensure all settings are committed and saved before issuing a ' \ + '"restart serial console" command to apply new configuration!') + if not prompt_user: + return False + if not ask_yes_no('Any uncommitted changes from these sessions will be lost\n' \ + 'and in-progress actions may be left in an inconsistent state.\n'\ + '\nContinue?'): + return False + + for unit in units: + if 'device' not in unit: + continue # malformed or filtered. + unit_name = unit['unit'] + unit_device = unit['device'] + if os.path.exists(os.path.join(SD_UNIT_PATH, unit_name)): + cmd(f'systemctl restart {unit_name}') + else: + # Deleted stubs don't need to be restarted, just shut them down. + cmd(f'systemctl stop {unit_name}') + + return True diff --git a/python/vyos/utils/strip_config.py b/python/vyos/utils/strip_config.py new file mode 100644 index 0000000..7a9c78c --- /dev/null +++ b/python/vyos/utils/strip_config.py @@ -0,0 +1,210 @@ +#!/usr/bin/python3 +# +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +# XXX: these functions assume that the config is at the top level, +# and aren't capable of anonymizing config subtress. +# They shouldn't be used as a basis for a strip-private filter +# until we figure out if we can pass the config path information to the filter. + +import copy + +import vyos.configtree + + +def __anonymize_password(v): + return "<PASSWORD REDACTED>" + +def __anonymize_key(v): + return "<KEY DATA REDACTED>" + +def __anonymize_data(v): + return "<DATA REDACTED>" + +__secret_paths = [ + # System user password hashes + {"base_path": ['system', 'login', 'user'], "secret_path": ["authentication", "encrypted-password"], "func": __anonymize_password}, + + # PKI data + {"base_path": ["pki", "ca"], "secret_path": ["private", "key"], "func": __anonymize_key}, + {"base_path": ["pki", "ca"], "secret_path": ["certificate"], "func": __anonymize_key}, + {"base_path": ["pki", "ca"], "secret_path": ["crl"], "func": __anonymize_key}, + {"base_path": ["pki", "certificate"], "secret_path": ["private", "key"], "func": __anonymize_key}, + {"base_path": ["pki", "certificate"], "secret_path": ["certificate"], "func": __anonymize_key}, + {"base_path": ["pki", "certificate"], "secret_path": ["acme", "email"], "func": __anonymize_data}, + {"base_path": ["pki", "key-pair"], "secret_path": ["private", "key"], "func": __anonymize_key}, + {"base_path": ["pki", "key-pair"], "secret_path": ["public", "key"], "func": __anonymize_key}, + {"base_path": ["pki", "openssh"], "secret_path": ["private", "key"], "func": __anonymize_key}, + {"base_path": ["pki", "openssh"], "secret_path": ["public", "key"], "func": __anonymize_key}, + {"base_path": ["pki", "openvpn", "shared-secret"], "secret_path": ["key"], "func": __anonymize_key}, + {"base_path": ["pki", "dh"], "secret_path": ["parameters"], "func": __anonymize_key}, + + # IPsec pre-shared secrets + {"base_path": ['vpn', 'ipsec', 'authentication', 'psk'], "secret_path": ["secret"], "func": __anonymize_password}, + + # IPsec x509 passphrases + {"base_path": ['vpn', 'ipsec', 'site-to-site', 'peer'], "secret_path": ['authentication', 'x509'], "func": __anonymize_password}, + + # IPsec remote-access secrets and passwords + {"base_path": ["vpn", "ipsec", "remote-access", "connection"], "secret_path": ["authentication", "pre-shared-secret"], "func": __anonymize_password}, + # Passwords in remote-access IPsec local users have their own fixup + # due to deeper nesting. + + # PPTP passwords + {"base_path": ['vpn', 'pptp', 'remote-access', 'authentication', 'local-users', 'username'], "secret_path": ['password'], "func": __anonymize_password}, + + # L2TP passwords + {"base_path": ['vpn', 'l2tp', 'remote-access', 'authentication', 'local-users', 'username'], "secret_path": ['password'], "func": __anonymize_password}, + {"path": ['vpn', 'l2tp', 'remote-access', 'ipsec-settings', 'authentication', 'pre-shared-secret'], "func": __anonymize_password}, + + # SSTP passwords + {"base_path": ['vpn', 'sstp', 'remote-access', 'authentication', 'local-users', 'username'], "secret_path": ['password'], "func": __anonymize_password}, + + # OpenConnect passwords + {"base_path": ['vpn', 'openconnect', 'authentication', 'local-users', 'username'], "secret_path": ['password'], "func": __anonymize_password}, + + # PPPoE server passwords + {"base_path": ['service', 'pppoe-server', 'authentication', 'local-users', 'username'], "secret_path": ['password'], "func": __anonymize_password}, + + # RADIUS PSKs for VPN services + {"base_path": ["vpn", "sstp", "authentication", "radius", "server"], "secret_path": ["key"], "func": __anonymize_password}, + {"base_path": ["vpn", "l2tp", "authentication", "radius", "server"], "secret_path": ["key"], "func": __anonymize_password}, + {"base_path": ["vpn", "pptp", "authentication", "radius", "server"], "secret_path": ["key"], "func": __anonymize_password}, + {"base_path": ["vpn", "openconnect", "authentication", "radius", "server"], "secret_path": ["key"], "func": __anonymize_password}, + {"base_path": ["service", "ipoe-server", "authentication", "radius", "server"], "secret_path": ["key"], "func": __anonymize_password}, + {"base_path": ["service", "pppoe-server", "authentication", "radius", "server"], "secret_path": ["key"], "func": __anonymize_password}, + + # VRRP passwords + {"base_path": ['high-availability', 'vrrp', 'group'], "secret_path": ['authentication', 'password'], "func": __anonymize_password}, + + # BGP neighbor and peer group passwords + {"base_path": ['protocols', 'bgp', 'neighbor'], "secret_path": ["password"], "func": __anonymize_password}, + {"base_path": ['protocols', 'bgp', 'peer-group'], "secret_path": ["password"], "func": __anonymize_password}, + + # WireGuard private keys + {"base_path": ["interfaces", "wireguard"], "secret_path": ["private-key"], "func": __anonymize_password}, + + # NHRP passwords + {"base_path": ["protocols", "nhrp", "tunnel"], "secret_path": ["cisco-authentication"], "func": __anonymize_password}, + + # RIP passwords + {"base_path": ["protocols", "rip", "interface"], "secret_path": ["authentication", "plaintext-password"], "func": __anonymize_password}, + + # IS-IS passwords + {"path": ["protocols", "isis", "area-password", "plaintext-password"], "func": __anonymize_password}, + {"base_path": ["protocols", "isis", "interface"], "secret_path": ["password", "plaintext-password"], "func": __anonymize_password}, + + # HTTP API servers + {"base_path": ["service", "https", "api", "keys", "id"], "secret_path": ["key"], "func": __anonymize_password}, + + # Telegraf + {"path": ["service", "monitoring", "telegraf", "prometheus-client", "authentication", "password"], "func": __anonymize_password}, + {"path": ["service", "monitoring", "telegraf", "influxdb", "authentication", "token"], "func": __anonymize_password}, + {"path": ["service", "monitoring", "telegraf", "azure-data-explorer", "authentication", "client-secret"], "func": __anonymize_password}, + {"path": ["service", "monitoring", "telegraf", "splunk", "authentication", "token"], "func": __anonymize_password}, + + # SNMPv3 passwords + {"base_path": ["service", "snmp", "v3", "user"], "secret_path": ["privacy", "encrypted-password"], "func": __anonymize_password}, + {"base_path": ["service", "snmp", "v3", "user"], "secret_path": ["privacy", "plaintext-password"], "func": __anonymize_password}, + {"base_path": ["service", "snmp", "v3", "user"], "secret_path": ["auth", "encrypted-password"], "func": __anonymize_password}, + {"base_path": ["service", "snmp", "v3", "user"], "secret_path": ["auth", "encrypted-password"], "func": __anonymize_password}, +] + +def __prepare_secret_paths(config_tree, secret_paths): + """ Generate a list of secret paths for the current system, + adjusted for variable parts such as VRFs and remote access IPsec instances + """ + + # Fixup for remote-access IPsec local users that are nested under two tag nodes + # We generate the list of their paths dynamically + ipsec_ra_base = {"base_path": ["vpn", "ipsec", "remote-access", "connection"], "func": __anonymize_password} + if config_tree.exists(ipsec_ra_base["base_path"]): + for conn in config_tree.list_nodes(ipsec_ra_base["base_path"]): + if config_tree.exists(ipsec_ra_base["base_path"] + [conn] + ["authentication", "local-users", "username"]): + for u in config_tree.list_nodes(ipsec_ra_base["base_path"] + [conn] + ["authentication", "local-users", "username"]): + p = copy.copy(ipsec_ra_base) + p["base_path"] = p["base_path"] + [conn] + ["authentication", "local-users", "username"] + p["secret_path"] = ["password"] + secret_paths.append(p) + + # Fixup for VRFs that may contain routing protocols and other nodes nested under them + vrf_paths = [] + vrf_base_path = ["vrf", "name"] + if config_tree.exists(vrf_base_path): + for v in config_tree.list_nodes(vrf_base_path): + vrf_secret_paths = copy.deepcopy(secret_paths) + for sp in vrf_secret_paths: + if "base_path" in sp: + sp["base_path"] = vrf_base_path + [v] + sp["base_path"] + elif "path" in sp: + sp["path"] = vrf_base_path + [v] + sp["path"] + vrf_paths.append(sp) + + secret_paths = secret_paths + vrf_paths + + # Fixup for user SSH keys, that are nested under a tag node + #ssh_key_base_path = {"base_path": ['system', 'login', 'user'], "secret_path": ["authentication", "encrypted-password"], "func": __anonymize_password}, + user_base_path = ['system', 'login', 'user'] + ssh_key_paths = [] + if config_tree.exists(user_base_path): + for u in config_tree.list_nodes(user_base_path): + kp = {"base_path": user_base_path + [u, "authentication", "public-keys"], "secret_path": ["key"], "func": __anonymize_key} + ssh_key_paths.append(kp) + + secret_paths = secret_paths + ssh_key_paths + + # Fixup for OSPF passwords and keys that are nested under OSPF interfaces + ospf_base_path = ["protocols", "ospf", "interface"] + ospf_paths = [] + if config_tree.exists(ospf_base_path): + for i in config_tree.list_nodes(ospf_base_path): + # Plaintext password, there can be only one + opp = {"path": ospf_base_path + [i, "authentication", "plaintext-password"], "func": __anonymize_password} + md5kp = {"base_path": ospf_base_path + [i, "authentication", "md5", "key-id"], "secret_path": ["md5-key"], "func": __anonymize_password} + ospf_paths.append(opp) + ospf_paths.append(md5kp) + + secret_paths = secret_paths + ospf_paths + + return secret_paths + +def __strip_private(ct, secret_paths): + for sp in secret_paths: + if "base_path" in sp: + if ct.exists(sp["base_path"]): + for n in ct.list_nodes(sp["base_path"]): + if ct.exists(sp["base_path"] + [n] + sp["secret_path"]): + secret = ct.return_value(sp["base_path"] + [n] + sp["secret_path"]) + ct.set(sp["base_path"] + [n] + sp["secret_path"], value=sp["func"](secret)) + elif "path" in sp: + if ct.exists(sp["path"]): + secret = ct.return_value(sp["path"]) + ct.set(sp["path"], value=sp["func"](secret)) + else: + raise ValueError("Malformed secret path dict, has neither base_path nor path in it ") + + return ct.to_string() + +def strip_config_source(config_source): + config_tree = vyos.configtree.ConfigTree(config_source) + secret_paths = __prepare_secret_paths(config_tree, __secret_paths) + stripped_config = __strip_private(config_tree, secret_paths) + + return stripped_config + +def strip_config_tree(config_tree): + secret_paths = __prepare_secret_paths(config_tree, __secret_paths) + return __strip_private(config_tree, secret_paths) diff --git a/python/vyos/utils/system.py b/python/vyos/utils/system.py new file mode 100644 index 0000000..7b12efb --- /dev/null +++ b/python/vyos/utils/system.py @@ -0,0 +1,149 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +from subprocess import run + +def sysctl_read(name: str) -> str: + """Read and return current value of sysctl() option + + Args: + name (str): sysctl key name + + Returns: + str: sysctl key value + """ + tmp = run(['sysctl', '-nb', name], capture_output=True) + return tmp.stdout.decode() + +def sysctl_write(name: str, value: str | int) -> bool: + """Change value via sysctl() + + Args: + name (str): sysctl key name + value (str | int): sysctl key value + + Returns: + bool: True if changed, False otherwise + """ + # convert other types to string before comparison + if not isinstance(value, str): + value = str(value) + # do not change anything if a value is already configured + if sysctl_read(name) == value: + return True + # return False if sysctl call failed + if run(['sysctl', '-wq', f'{name}={value}']).returncode != 0: + return False + # compare old and new values + # sysctl may apply value, but its actual value will be + # different from requested + if sysctl_read(name) == value: + return True + # False in other cases + return False + +def sysctl_apply(sysctl_dict: dict[str, str], revert: bool = True) -> bool: + """Apply sysctl values. + + Args: + sysctl_dict (dict[str, str]): dictionary with sysctl keys with values + revert (bool, optional): Revert to original values if new were not + applied. Defaults to True. + + Returns: + bool: True if all params configured properly, False in other cases + """ + # get current values + sysctl_original: dict[str, str] = {} + for key_name in sysctl_dict.keys(): + sysctl_original[key_name] = sysctl_read(key_name) + # apply new values and revert in case one of them was not applied + for key_name, value in sysctl_dict.items(): + if not sysctl_write(key_name, value): + if revert: + sysctl_apply(sysctl_original, revert=False) + return False + # everything applied + return True + +def find_device_file(device): + """ Recurively search /dev for the given device file and return its full path. + If no device file was found 'None' is returned """ + from fnmatch import fnmatch + + for root, dirs, files in os.walk('/dev'): + for basename in files: + if fnmatch(basename, device): + return os.path.join(root, basename) + + return None + +def load_as_module(name: str, path: str): + import importlib.util + + spec = importlib.util.spec_from_file_location(name, path) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + +def load_as_module_source(name: str, path: str): + """ Necessary modification of load_as_module for files without *.py + extension """ + import importlib.util + from importlib.machinery import SourceFileLoader + + loader = SourceFileLoader(name, path) + spec = importlib.util.spec_from_loader(name, loader) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + +def get_uptime_seconds(): + """ Returns system uptime in seconds """ + from re import search + from vyos.utils.file import read_file + + data = read_file("/proc/uptime") + seconds = search(r"([0-9\.]+)\s", data).group(1) + res = int(float(seconds)) + + return res + +def get_load_averages(): + """ Returns load averages for 1, 5, and 15 minutes as a dict """ + from re import search + from vyos.utils.file import read_file + from vyos.utils.cpu import get_core_count + + data = read_file("/proc/loadavg") + matches = search(r"\s*(?P<one>[0-9\.]+)\s+(?P<five>[0-9\.]+)\s+(?P<fifteen>[0-9\.]+)\s*", data) + + core_count = get_core_count() + + res = {} + res[1] = float(matches["one"]) / core_count + res[5] = float(matches["five"]) / core_count + res[15] = float(matches["fifteen"]) / core_count + + return res + +def get_secure_boot_state() -> bool: + from vyos.utils.process import cmd + from vyos.utils.boot import is_uefi_system + if not is_uefi_system(): + return False + tmp = cmd('mokutil --sb-state') + return bool('enabled' in tmp) diff --git a/python/vyos/utils/vti_updown_db.py b/python/vyos/utils/vti_updown_db.py new file mode 100644 index 0000000..b491fc6 --- /dev/null +++ b/python/vyos/utils/vti_updown_db.py @@ -0,0 +1,194 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os + +from contextlib import contextmanager +from syslog import syslog + +VTI_WANT_UP_IFLIST = '/tmp/ipsec_vti_interfaces' + +def vti_updown_db_exists(): + """ Returns true if the database exists """ + return os.path.exists(VTI_WANT_UP_IFLIST) + +@contextmanager +def open_vti_updown_db_for_create_or_update(): + """ Opens the database for reading and writing, creating the database if it does not exist """ + if vti_updown_db_exists(): + f = open(VTI_WANT_UP_IFLIST, 'r+') + else: + f = open(VTI_WANT_UP_IFLIST, 'x+') + try: + db = VTIUpDownDB(f) + yield db + finally: + f.close() + +@contextmanager +def open_vti_updown_db_for_update(): + """ Opens the database for reading and writing, returning an error if it does not exist """ + f = open(VTI_WANT_UP_IFLIST, 'r+') + try: + db = VTIUpDownDB(f) + yield db + finally: + f.close() + +@contextmanager +def open_vti_updown_db_readonly(): + """ Opens the database for reading, returning an error if it does not exist """ + f = open(VTI_WANT_UP_IFLIST, 'r') + try: + db = VTIUpDownDB(f) + yield db + finally: + f.close() + +def remove_vti_updown_db(): + """ Brings down any interfaces referenced by the database and removes the database """ + # We need to process the DB first to bring down any interfaces still up + with open_vti_updown_db_for_update() as db: + db.removeAllOtherInterfaces([]) + # this usage of commit will only ever bring down interfaces, + # do not need to provide a functional interface dict supplier + db.commit(lambda _: None) + + os.unlink(VTI_WANT_UP_IFLIST) + +class VTIUpDownDB: + # The VTI Up-Down DB is a text-based database of space-separated "ifspecs". + # + # ifspecs can come in one of the two following formats: + # + # persistent format: <interface name> + # indicates the named interface should always be up. + # + # connection format: <interface name>:<connection name>:<protocol> + # indicates the named interface wants to be up due to an established + # connection <connection name> using the <protocol> protocol. + # + # The configuration tree and ipsec daemon connection up-down hook + # modify this file as needed and use it to determine when a + # particular event or configuration change should lead to changing + # the interface state. + + def __init__(self, f): + self._fileHandle = f + self._ifspecs = set([entry.strip() for entry in f.read().split(" ") if entry and not entry.isspace()]) + self._ifsUp = set() + self._ifsDown = set() + + def add(self, interface, connection = None, protocol = None): + """ + Adds a new entry to the DB. + + If an interface name, connection name, and protocol are supplied, + creates a connection entry. + + If only an interface name is specified, creates a persistent entry + for the given interface. + """ + ifspec = f"{interface}:{connection}:{protocol}" if (connection is not None and protocol is not None) else interface + if ifspec not in self._ifspecs: + self._ifspecs.add(ifspec) + self._ifsUp.add(interface) + self._ifsDown.discard(interface) + + def remove(self, interface, connection = None, protocol = None): + """ + Removes a matching entry from the DB. + + If no matching entry can be fonud, the operation returns successfully. + """ + ifspec = f"{interface}:{connection}:{protocol}" if (connection is not None and protocol is not None) else interface + if ifspec in self._ifspecs: + self._ifspecs.remove(ifspec) + interface_remains = False + for ifspec in self._ifspecs: + if ifspec.split(':')[0] == interface: + interface_remains = True + + if not interface_remains: + self._ifsDown.add(interface) + self._ifsUp.discard(interface) + + def wantsInterfaceUp(self, interface): + """ Returns whether the DB contains at least one entry referencing the given interface """ + for ifspec in self._ifspecs: + if ifspec.split(':')[0] == interface: + return True + + return False + + def removeAllOtherInterfaces(self, interface_list): + """ Removes all interfaces not included in the given list from the DB """ + updated_ifspecs = set([ifspec for ifspec in self._ifspecs if ifspec.split(':')[0] in interface_list]) + removed_ifspecs = self._ifspecs - updated_ifspecs + self._ifspecs = updated_ifspecs + interfaces_to_bring_down = [ifspec.split(':')[0] for ifspec in removed_ifspecs] + self._ifsDown.update(interfaces_to_bring_down) + self._ifsUp.difference_update(interfaces_to_bring_down) + + def setPersistentInterfaces(self, interface_list): + """ Updates the set of persistently up interfaces to match the given list """ + new_presistent_interfaces = set(interface_list) + current_presistent_interfaces = set([ifspec for ifspec in self._ifspecs if ':' not in ifspec]) + added_presistent_interfaces = new_presistent_interfaces - current_presistent_interfaces + removed_presistent_interfaces = current_presistent_interfaces - new_presistent_interfaces + + for interface in added_presistent_interfaces: + self.add(interface) + + for interface in removed_presistent_interfaces: + self.remove(interface) + + def commit(self, interface_dict_supplier): + """ + Writes the DB to disk and brings interfaces up and down as needed. + + Only interfaces referenced by entries modified in this DB session + are manipulated. If an interface is called to be brought up, the + provided interface_config_supplier function is invoked and expected + to return the config dictionary for the interface. + """ + from vyos.ifconfig import VTIIf + from vyos.utils.process import call + from vyos.utils.network import get_interface_config + + self._fileHandle.seek(0) + self._fileHandle.write(' '.join(self._ifspecs)) + self._fileHandle.truncate() + + for interface in self._ifsDown: + vti_link = get_interface_config(interface) + vti_link_up = (vti_link['operstate'] != 'DOWN' if 'operstate' in vti_link else False) + if vti_link_up: + call(f'sudo ip link set {interface} down') + syslog(f'Interface {interface} is admin down ...') + + self._ifsDown.clear() + + for interface in self._ifsUp: + vti_link = get_interface_config(interface) + vti_link_up = (vti_link['operstate'] != 'DOWN' if 'operstate' in vti_link else False) + if not vti_link_up: + vti = interface_dict_supplier(interface) + if 'disable' not in vti: + tmp = VTIIf(interface, bypass_vti_updown_db = True) + tmp.update(vti) + syslog(f'Interface {interface} is admin up ...') + + self._ifsUp.clear() diff --git a/python/vyos/version.py b/python/vyos/version.py new file mode 100644 index 0000000..86e96d0 --- /dev/null +++ b/python/vyos/version.py @@ -0,0 +1,142 @@ +# Copyright 2017-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +""" +VyOS version data access library. + +VyOS stores its version data, which include the version number and some +additional information in a JSON file. This module provides a convenient +interface to reading it. + +Example of the version data dict:: + { + 'built_by': 'autobuild@vyos.net', + 'build_id': '021ac2ee-cd07-448b-9991-9c68d878cddd', + 'version': '1.2.0-rolling+201806200337', + 'built_on': 'Wed 20 Jun 2018 03:37 UTC' + } +""" + +import os + +import requests +import vyos.defaults +from vyos.system.image import is_live_boot + +from vyos.utils.file import read_file +from vyos.utils.file import read_json +from vyos.utils.process import popen +from vyos.utils.process import DEVNULL + +version_file = os.path.join(vyos.defaults.directories['data'], 'version.json') + +def get_version_data(fname=version_file): + """ + Get complete version data + + Args: + file (str): path to the version file + + Returns: + dict: version data, if it can not be found and empty dict + + The optional ``file`` argument comes in handy in upgrade scripts + that need to retrieve information from images other than the running image. + It should not be used on a running system since the location of that file + is an implementation detail and may change in the future, while the interface + of this module will stay the same. + """ + return read_json(fname, {}) + + +def get_version(fname=version_file): + """ + Get the version number, or an empty string if it could not be determined + """ + return get_version_data(fname=fname).get('version', '') + + +def get_full_version_data(fname=version_file): + version_data = get_version_data(fname) + + # Get system architecture (well, kernel architecture rather) + version_data['system_arch'], _ = popen('uname -m', stderr=DEVNULL) + + hypervisor,code = popen('hvinfo', stderr=DEVNULL) + if code == 1: + # hvinfo returns 1 if it cannot detect any hypervisor + version_data['system_type'] = 'bare metal' + else: + version_data['system_type'] = f"{hypervisor} guest" + + # Get boot type, it can be livecd or installed image + # In installed images, the squashfs image file is named after its image version, + # while on livecd it's just "filesystem.squashfs", that's how we tell a livecd boot + # from an installed image + if is_live_boot(): + boot_via = "livecd" + else: + boot_via = "installed image" + version_data['boot_via'] = boot_via + + # Get hardware details from DMI + dmi = '/sys/class/dmi/id' + version_data['hardware_vendor'] = read_file(dmi + '/sys_vendor', 'Unknown') + version_data['hardware_model'] = read_file(dmi +'/product_name','Unknown') + + # These two assume script is run as root, normal users can't access those files + subsystem = '/sys/class/dmi/id/subsystem/id' + version_data['hardware_serial'] = read_file(subsystem + '/product_serial','Unknown') + version_data['hardware_uuid'] = read_file(subsystem + '/product_uuid', 'Unknown') + + return version_data + +def get_remote_version(url): + """ + Get remote available JSON file from remote URL + An example of the image-version.json + + [ + { + "arch":"amd64", + "flavors":[ + "generic" + ], + "image":"vyos-rolling-latest.iso", + "latest":true, + "lts":false, + "release_date":"2022-09-06", + "release_train":"sagitta", + "url":"http://xxx/rolling/current/vyos-rolling-latest.iso", + "version":"vyos-1.4-rolling-202209060217" + } + ] + """ + headers = {} + try: + remote_data = requests.get(url=url, headers=headers) + remote_data.raise_for_status() + if remote_data.status_code != 200: + return False + return remote_data.json() + except requests.exceptions.HTTPError as errh: + print ("HTTP Error:", errh) + except requests.exceptions.ConnectionError as errc: + print ("Connecting error:", errc) + except requests.exceptions.Timeout as errt: + print ("Timeout error:", errt) + except requests.exceptions.RequestException as err: + print ("Unable to get remote data", err) + return False diff --git a/python/vyos/xml_ref/__init__.py b/python/vyos/xml_ref/__init__.py new file mode 100644 index 0000000..99d8432 --- /dev/null +++ b/python/vyos/xml_ref/__init__.py @@ -0,0 +1,112 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from typing import Optional, Union, TYPE_CHECKING +from vyos.xml_ref import definition +from vyos.xml_ref import op_definition + +if TYPE_CHECKING: + from vyos.config import ConfigDict + +def load_reference(cache=[]): + if cache: + return cache[0] + + xml = definition.Xml() + + try: + from vyos.xml_ref.cache import reference + except Exception: + raise ImportError('no xml reference cache !!') + + if not reference: + raise ValueError('empty xml reference cache !!') + + xml.define(reference) + cache.append(xml) + + return xml + +def is_tag(path: list) -> bool: + return load_reference().is_tag(path) + +def is_tag_value(path: list) -> bool: + return load_reference().is_tag_value(path) + +def is_multi(path: list) -> bool: + return load_reference().is_multi(path) + +def is_valueless(path: list) -> bool: + return load_reference().is_valueless(path) + +def is_leaf(path: list) -> bool: + return load_reference().is_leaf(path) + +def owner(path: list, with_tag=False) -> str: + return load_reference().owner(path, with_tag=with_tag) + +def priority(path: list) -> str: + return load_reference().priority(path) + +def cli_defined(path: list, node: str, non_local=False) -> bool: + return load_reference().cli_defined(path, node, non_local=non_local) + +def component_version() -> dict: + return load_reference().component_version() + +def default_value(path: list) -> Optional[Union[str, list]]: + return load_reference().default_value(path) + +def multi_to_list(rpath: list, conf: dict) -> dict: + return load_reference().multi_to_list(rpath, conf) + +def get_defaults(path: list, get_first_key=False, recursive=False) -> dict: + return load_reference().get_defaults(path, get_first_key=get_first_key, + recursive=recursive) + +def relative_defaults(rpath: list, conf: dict, get_first_key=False, + recursive=False) -> dict: + + return load_reference().relative_defaults(rpath, conf, + get_first_key=get_first_key, + recursive=recursive) + +def from_source(d: dict, path: list) -> bool: + return definition.from_source(d, path) + +def ext_dict_merge(source: dict, destination: Union[dict, 'ConfigDict']): + return definition.ext_dict_merge(source, destination) + +def load_op_reference(op_cache=[]): + if op_cache: + return op_cache[0] + + op_xml = op_definition.OpXml() + + try: + from vyos.xml_ref.op_cache import op_reference + except Exception: + raise ImportError('no xml op reference cache !!') + + if not op_reference: + raise ValueError('empty xml op reference cache !!') + + op_xml.define(op_reference) + op_cache.append(op_xml) + + return op_xml + +def get_op_ref_path(path: list) -> list[op_definition.PathData]: + return load_op_reference()._get_op_ref_path(path) diff --git a/python/vyos/xml_ref/definition.py b/python/vyos/xml_ref/definition.py new file mode 100644 index 0000000..5ff28da --- /dev/null +++ b/python/vyos/xml_ref/definition.py @@ -0,0 +1,339 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from typing import Optional, Union, Any, TYPE_CHECKING + +# https://peps.python.org/pep-0484/#forward-references +# for type 'ConfigDict' +if TYPE_CHECKING: + from vyos.config import ConfigDict + +def set_source_recursive(o: Union[dict, str, list], b: bool): + d = {} + if not isinstance(o, dict): + d = {'_source': b} + else: + for k, v in o.items(): + d[k] = set_source_recursive(v, b) + d |= {'_source': b} + return d + +def source_dict_merge(src: dict, dest: dict): + from copy import deepcopy + dst = deepcopy(dest) + from_src = {} + + for key, value in src.items(): + if key not in dst: + dst[key] = value + from_src[key] = set_source_recursive(value, True) + elif isinstance(src[key], dict): + dst[key], f = source_dict_merge(src[key], dst[key]) + f |= {'_source': False} + from_src[key] = f + + return dst, from_src + +def ext_dict_merge(src: dict, dest: Union[dict, 'ConfigDict']): + d, f = source_dict_merge(src, dest) + if hasattr(d, '_from_defaults'): + setattr(d, '_from_defaults', f) + return d + +def from_source(d: dict, path: list) -> bool: + for key in path: + d = d[key] if key in d else {} + if not d or not isinstance(d, dict): + return False + return d.get('_source', False) + +class Xml: + def __init__(self): + self.ref = {} + + def define(self, ref: dict): + self.ref = ref + + def _get_ref_node_data(self, node: dict, data: str) -> Union[bool, str]: + res = node.get('node_data', {}) + if not res: + raise ValueError("non-existent node data") + if data not in res: + raise ValueError("non-existent data field") + + return res.get(data) + + def _get_ref_path(self, path: list) -> dict: + ref_path = path.copy() + d = self.ref + while ref_path and d: + d = d.get(ref_path[0], {}) + ref_path.pop(0) + if self._is_tag_node(d) and ref_path: + ref_path.pop(0) + + return d + + def _is_tag_node(self, node: dict) -> bool: + res = self._get_ref_node_data(node, 'node_type') + return res == 'tag' + + def is_tag(self, path: list) -> bool: + ref_path = path.copy() + d = self.ref + while ref_path and d: + d = d.get(ref_path[0], {}) + ref_path.pop(0) + if self._is_tag_node(d) and ref_path: + if len(ref_path) == 1: + return False + ref_path.pop(0) + + return self._is_tag_node(d) + + def is_tag_value(self, path: list) -> bool: + if len(path) < 2: + return False + + return self.is_tag(path[:-1]) + + def _is_multi_node(self, node: dict) -> bool: + b = self._get_ref_node_data(node, 'multi') + assert isinstance(b, bool) + return b + + def is_multi(self, path: list) -> bool: + d = self._get_ref_path(path) + return self._is_multi_node(d) + + def _is_valueless_node(self, node: dict) -> bool: + b = self._get_ref_node_data(node, 'valueless') + assert isinstance(b, bool) + return b + + def is_valueless(self, path: list) -> bool: + d = self._get_ref_path(path) + return self._is_valueless_node(d) + + def _is_leaf_node(self, node: dict) -> bool: + res = self._get_ref_node_data(node, 'node_type') + return res == 'leaf' + + def is_leaf(self, path: list) -> bool: + d = self._get_ref_path(path) + return self._is_leaf_node(d) + + def _least_upper_data(self, path: list, name: str) -> str: + ref_path = path.copy() + d = self.ref + data = '' + tag = '' + while ref_path and d: + tag_val = '' + d = d.get(ref_path[0], {}) + ref_path.pop(0) + if self._is_tag_node(d) and ref_path: + tag_val = ref_path[0] + ref_path.pop(0) + if self._is_leaf_node(d) and ref_path: + ref_path.pop(0) + res = self._get_ref_node_data(d, name) + if res is not None: + data = res + tag = tag_val + + return data, tag + + def owner(self, path: list, with_tag=False) -> str: + from pathlib import Path + data, tag = self._least_upper_data(path, 'owner') + tag_ext = f'_{tag}' if tag else '' + if data: + if with_tag: + data = Path(data.split()[0]).stem + data = f'{data}{tag_ext}' + else: + data = Path(data.split()[0]).name + return data + + def priority(self, path: list) -> str: + data, _ = self._least_upper_data(path, 'priority') + return data + + @staticmethod + def _dict_get(d: dict, path: list) -> dict: + for i in path: + d = d.get(i, {}) + if not isinstance(d, dict): + return {} + if not d: + break + return d + + def _dict_find(self, d: dict, key: str, non_local=False) -> bool: + for k in list(d): + if k in ('node_data', 'component_version'): + continue + if k == key: + return True + if non_local and isinstance(d[k], dict): + if self._dict_find(d[k], key): + return True + return False + + def cli_defined(self, path: list, node: str, non_local=False) -> bool: + d = self._dict_get(self.ref, path) + return self._dict_find(d, node, non_local=non_local) + + def component_version(self) -> dict: + d = {} + for k, v in self.ref['component_version'].items(): + d[k] = int(v) + return d + + def multi_to_list(self, rpath: list, conf: dict) -> dict: + res: Any = {} + + for k in list(conf): + d = self._get_ref_path(rpath + [k]) + if self._is_leaf_node(d): + if self._is_multi_node(d) and not isinstance(conf[k], list): + res[k] = [conf[k]] + else: + res[k] = conf[k] + else: + res[k] = self.multi_to_list(rpath + [k], conf[k]) + + return res + + def _get_default_value(self, node: dict) -> Optional[str]: + return self._get_ref_node_data(node, "default_value") + + def _get_default(self, node: dict) -> Optional[Union[str, list]]: + default = self._get_default_value(node) + if default is None: + return None + if self._is_multi_node(node): + return default.split() + return default + + def default_value(self, path: list) -> Optional[Union[str, list]]: + d = self._get_ref_path(path) + default = self._get_default_value(d) + if default is None: + return None + if self._is_multi_node(d) or self._is_tag_node(d): + return default.split() + return default + + def get_defaults(self, path: list, get_first_key=False, recursive=False) -> dict: + """Return dict containing default values below path + + Note that descent below path will not proceed beyond an encountered + tag node, as no tag node value is known. For a default dict relative + to an existing config dict containing tag node values, see function: + 'relative_defaults' + """ + res: dict = {} + if self.is_tag(path): + return res + + d = self._get_ref_path(path) + + if self._is_leaf_node(d): + default_value = self._get_default(d) + if default_value is not None: + return {path[-1]: default_value} if path else {} + + for k in list(d): + if k in ('node_data', 'component_version') : + continue + if self._is_leaf_node(d[k]): + default_value = self._get_default(d[k]) + if default_value is not None: + res |= {k: default_value} + elif self.is_tag(path + [k]): + # tag node defaults are used as suggestion, not default value; + # should this change, append to path and continue if recursive + pass + else: + if recursive: + pos = self.get_defaults(path + [k], recursive=True) + res |= pos + if res: + if get_first_key or not path: + return res + return {path[-1]: res} + + return {} + + def _well_defined(self, path: list, conf: dict) -> bool: + # test disjoint path + conf for sensible config paths + def step(c): + return [next(iter(c.keys()))] if c else [] + try: + tmp = step(conf) + if tmp and self.is_tag_value(path + tmp): + c = conf[tmp[0]] + if not isinstance(c, dict): + raise ValueError + tmp = tmp + step(c) + self._get_ref_path(path + tmp) + else: + self._get_ref_path(path + tmp) + except ValueError: + return False + return True + + def _relative_defaults(self, rpath: list, conf: dict, recursive=False) -> dict: + res: dict = {} + res = self.get_defaults(rpath, recursive=recursive, + get_first_key=True) + for k in list(conf): + if isinstance(conf[k], dict): + step = self._relative_defaults(rpath + [k], conf=conf[k], + recursive=recursive) + res |= step + + if res: + return {rpath[-1]: res} if rpath else res + + return {} + + def relative_defaults(self, path: list, conf: dict, get_first_key=False, + recursive=False) -> dict: + """Return dict containing defaults along paths of a config dict + """ + if not conf: + return self.get_defaults(path, get_first_key=get_first_key, + recursive=recursive) + if not self._well_defined(path, conf): + # adjust for possible overlap: + if path and path[-1] in list(conf): + conf = conf[path[-1]] + conf = {} if not isinstance(conf, dict) else conf + if not self._well_defined(path, conf): + print('path to config dict does not define full config paths') + return {} + + res = self._relative_defaults(path, conf, recursive=recursive) + + if get_first_key and path: + if res.values(): + res = next(iter(res.values())) + else: + res = {} + + return res diff --git a/python/vyos/xml_ref/generate_cache.py b/python/vyos/xml_ref/generate_cache.py new file mode 100644 index 0000000..5f3f84d --- /dev/null +++ b/python/vyos/xml_ref/generate_cache.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import json +from argparse import ArgumentParser +from argparse import ArgumentTypeError +from os.path import join +from os.path import abspath +from os.path import dirname +from xmltodict import parse + +_here = dirname(__file__) + +sys.path.append(join(_here, '..')) +from configtree import reference_tree_to_json, ConfigTreeError + +xml_cache_json = 'xml_cache.json' +xml_tmp = join('/tmp', xml_cache_json) +pkg_cache = abspath(join(_here, 'pkg_cache')) +ref_cache = abspath(join(_here, 'cache.py')) + +node_data_fields = ("node_type", "multi", "valueless", "default_value", + "owner", "priority") + +def trim_node_data(cache: dict): + for k in list(cache): + if k == "node_data": + for l in list(cache[k]): + if l not in node_data_fields: + del cache[k][l] + else: + if isinstance(cache[k], dict): + trim_node_data(cache[k]) + +def non_trivial(s): + if not s: + raise ArgumentTypeError("Argument must be non empty string") + return s + +def main(): + parser = ArgumentParser(description='generate and save dict from xml defintions') + parser.add_argument('--xml-dir', type=str, required=True, + help='transcluded xml interface-definition directory') + parser.add_argument('--package-name', type=non_trivial, default='vyos-1x', + help='name of current package') + parser.add_argument('--output-path', help='path to generated cache') + args = vars(parser.parse_args()) + + xml_dir = abspath(args['xml_dir']) + pkg_name = args['package_name'].replace('-','_') + cache_name = pkg_name + '_cache.py' + out_path = args['output_path'] + path = out_path if out_path is not None else pkg_cache + xml_cache = abspath(join(path, cache_name)) + + try: + reference_tree_to_json(xml_dir, xml_tmp) + except ConfigTreeError as e: + print(e) + sys.exit(1) + + with open(xml_tmp) as f: + d = json.loads(f.read()) + + trim_node_data(d) + + syntax_version = join(xml_dir, 'xml-component-version.xml') + try: + with open(syntax_version) as f: + component = f.read() + except FileNotFoundError: + if pkg_name != 'vyos_1x': + component = '' + else: + print("\nWARNING: missing xml-component-version.xml\n") + sys.exit(1) + + if component: + parsed = parse(component) + else: + parsed = None + version = {} + # addon package definitions may have empty (== 0) version info + if parsed is not None and parsed['interfaceDefinition'] is not None: + converted = parsed['interfaceDefinition']['syntaxVersion'] + if not isinstance(converted, list): + converted = [converted] + for i in converted: + tmp = {i['@component']: i['@version']} + version |= tmp + + version = {"component_version": version} + + d |= version + + with open(xml_cache, 'w') as f: + f.write(f'reference = {str(d)}') + + print(cache_name) + +if __name__ == '__main__': + main() diff --git a/python/vyos/xml_ref/generate_op_cache.py b/python/vyos/xml_ref/generate_op_cache.py new file mode 100644 index 0000000..cd2ac89 --- /dev/null +++ b/python/vyos/xml_ref/generate_op_cache.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import sys +import json +import glob + +from argparse import ArgumentParser +from os.path import join +from os.path import abspath +from os.path import dirname +from xml.etree import ElementTree as ET +from xml.etree.ElementTree import Element +from typing import TypeAlias +from typing import Optional + +_here = dirname(__file__) + +sys.path.append(join(_here, '..')) +from defaults import directories + +from op_definition import NodeData +from op_definition import PathData + +xml_op_cache_json = 'xml_op_cache.json' +xml_op_tmp = join('/tmp', xml_op_cache_json) +op_ref_cache = abspath(join(_here, 'op_cache.py')) + +OptElement: TypeAlias = Optional[Element] +DEBUG = False + + +def translate_exec(s: str) -> str: + s = s.replace('${vyos_op_scripts_dir}', directories['op_mode']) + s = s.replace('${vyos_libexec_dir}', directories['base']) + return s + + +def translate_position(s: str, pos: list[str]) -> str: + pos = pos.copy() + pat: re.Pattern = re.compile(r'(?:\")?\${?([0-9]+)}?(?:\")?') + t: str = pat.sub(r'_place_holder_\1_', s) + + # preferred to .format(*list) to avoid collisions with braces + for i, p in enumerate(pos): + t = t.replace(f'_place_holder_{i+1}_', p) + + return t + + +def translate_command(s: str, pos: list[str]) -> str: + s = translate_exec(s) + s = translate_position(s, pos) + return s + + +def translate_op_script(s: str) -> str: + s = s.replace('${vyos_completion_dir}', directories['completion_dir']) + s = s.replace('${vyos_op_scripts_dir}', directories['op_mode']) + return s + + +def insert_node(n: Element, l: list[PathData], path = None) -> None: + # pylint: disable=too-many-locals,too-many-branches + prop: OptElement = n.find('properties') + children: OptElement = n.find('children') + command: OptElement = n.find('command') + # name is not None as required by schema + name: str = n.get('name', 'schema_error') + node_type: str = n.tag + if path is None: + path = [] + + path.append(name) + if node_type == 'tagNode': + path.append(f'{name}-tag_value') + + help_prop: OptElement = None if prop is None else prop.find('help') + help_text = None if help_prop is None else help_prop.text + command_text = None if command is None else command.text + if command_text is not None: + command_text = translate_command(command_text, path) + + comp_help = None + if prop is not None: + che = prop.findall("completionHelp") + for c in che: + lists = c.findall("list") + paths = c.findall("path") + scripts = c.findall("script") + + comp_help = {} + list_l = [] + for i in lists: + list_l.append(i.text) + path_l = [] + for i in paths: + path_str = re.sub(r'\s+', '/', i.text) + path_l.append(path_str) + script_l = [] + for i in scripts: + script_str = translate_op_script(i.text) + script_l.append(script_str) + + comp_help['list'] = list_l + comp_help['fs_path'] = path_l + comp_help['script'] = script_l + + for d in l: + if name in list(d): + break + else: + d = {} + l.append(d) + + inner_l = d.setdefault(name, []) + + inner_d: PathData = {'node_data': NodeData(node_type=node_type, + help_text=help_text, + comp_help=comp_help, + command=command_text, + path=path)} + inner_l.append(inner_d) + + if children is not None: + inner_nodes = children.iterfind("*") + for inner_n in inner_nodes: + inner_path = path[:] + insert_node(inner_n, inner_l, inner_path) + + +def parse_file(file_path, l): + tree = ET.parse(file_path) + root = tree.getroot() + for n in root.iterfind("*"): + insert_node(n, l) + + +def main(): + parser = ArgumentParser(description='generate dict from xml defintions') + parser.add_argument('--xml-dir', type=str, required=True, + help='transcluded xml op-mode-definition file') + + args = vars(parser.parse_args()) + + xml_dir = abspath(args['xml_dir']) + + l = [] + + for fname in glob.glob(f'{xml_dir}/*.xml'): + parse_file(fname, l) + + with open(xml_op_tmp, 'w') as f: + json.dump(l, f, indent=2) + + with open(op_ref_cache, 'w') as f: + f.write(f'op_reference = {str(l)}') + +if __name__ == '__main__': + main() diff --git a/python/vyos/xml_ref/op_definition.py b/python/vyos/xml_ref/op_definition.py new file mode 100644 index 0000000..914f3a1 --- /dev/null +++ b/python/vyos/xml_ref/op_definition.py @@ -0,0 +1,49 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from typing import TypedDict +from typing import TypeAlias +from typing import Optional +from typing import Union + + +class NodeData(TypedDict): + node_type: Optional[str] + help_text: Optional[str] + comp_help: Optional[dict[str, list]] + command: Optional[str] + path: Optional[list[str]] + + +PathData: TypeAlias = dict[str, Union[NodeData|list['PathData']]] + + +class OpXml: + def __init__(self): + self.op_ref = {} + + def define(self, op_ref: list[PathData]) -> None: + self.op_ref = op_ref + + def _get_op_ref_path(self, path: list[str]) -> list[PathData]: + def _get_path_list(path: list[str], l: list[PathData]) -> list[PathData]: + if not path: + return l + for d in l: + if path[0] in list(d): + return _get_path_list(path[1:], d[path[0]]) + return [] + l = self.op_ref + return _get_path_list(path, l) diff --git a/python/vyos/xml_ref/pkg_cache/__init__.py b/python/vyos/xml_ref/pkg_cache/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/python/vyos/xml_ref/pkg_cache/__init__.py diff --git a/python/vyos/xml_ref/update_cache.py b/python/vyos/xml_ref/update_cache.py new file mode 100644 index 0000000..0842bcb --- /dev/null +++ b/python/vyos/xml_ref/update_cache.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# +import os +from copy import deepcopy +from generate_cache import pkg_cache +from generate_cache import ref_cache + +def dict_merge(source, destination): + dest = deepcopy(destination) + + for key, value in source.items(): + if key not in dest: + dest[key] = value + elif isinstance(source[key], dict): + dest[key] = dict_merge(source[key], dest[key]) + + return dest + +def main(): + res = {} + cache_dir = os.path.basename(pkg_cache) + for mod in os.listdir(pkg_cache): + mod = os.path.splitext(mod)[0] + if not mod.endswith('_cache'): + continue + d = getattr(__import__(f'{cache_dir}.{mod}', fromlist=[mod]), 'reference') + if mod == 'vyos_1x_cache': + res = dict_merge(res, d) + else: + res = dict_merge(d, res) + + with open(ref_cache, 'w') as f: + f.write(f'reference = {str(res)}') + +if __name__ == '__main__': + main() diff --git a/schema/interface_definition.rnc b/schema/interface_definition.rnc new file mode 100644 index 0000000..758d9ce --- /dev/null +++ b/schema/interface_definition.rnc @@ -0,0 +1,186 @@ +# interface_definition.rnc: VyConf reference tree XML grammar +# +# Copyright (C) 2014. 2017 VyOS maintainers and contributors <maintainers@vyos.net> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA + +# The language of this file is compact form RELAX-NG +# http://relaxng.org/compact-tutorial-20030326.htm +# (unless converted to XML, then just RELAX-NG :) + +# Interface definition starts with interfaceDefinition tag that may contain node tags +start = element interfaceDefinition +{ + syntaxVersion*, + node* +} + +# interfaceDefinition may contain syntax version attribute lists. +syntaxVersion = element syntaxVersion +{ + (componentAttr & versionAttr) +} + +# node tag may contain node, leafNode, or tagNode tags +# Those are intermediate configuration nodes that may only contain +# other nodes and must not have values +node = element node +{ + (ownerAttr? & nodeNameAttr), + (properties? & children? ) +} + +# Tag nodes are containers for nodes without predefined names, like network interfaces +# or user names (e.g. "interfaces ethernet eth0" or "user jrandomhacker") +# Tag nodes may contain node and leafNode elements, and also nameConstraint tags +# They must not contain other tag nodes +tagNode = element tagNode +{ + (ownerAttr? & nodeNameAttr), + (defaultValue? & properties? & children ) +} + +# Leaf nodes are terminal configuration nodes that can't have children, +# but can have values. +# Leaf node may contain one or more valueConstraint tags +# If multiple valueConstraint tags are used, they work a logical OR +# Leaf nodes can have "multi" attribute that indicated that it can have more than one value +# It can also have a default value +leafNode = element leafNode +{ + (ownerAttr? & nodeNameAttr), + (defaultValue? & properties) +} + +# Default value for leaf node, if applicable +# It is used to generate default node state representation +defaultValue = element defaultValue { text } + +# Normal and tag nodes may have children +children = element children +{ + (node | tagNode | leafNode)+ +} + +# Nodes may have properties +# For simplicity, any property is allowed in any node, +# but whether they are used or not is implementation-defined +# +# Leaf nodes may differ in number of values that can be +# associated with them. +# By default, a leaf node can have only one value. +# "multi" tag means a node can have one or more values, +# "valueless" means it can have no values at all. +# "hidden" means node visibility can be toggled, eg 'dangerous' commands, +# "secret" allows a node to hide its value from unprivileged users. +# +# "priority" is used to influence node processing order for nodes +# with exact same dependencies and in compatibility modes. +properties = element properties +{ + help? & + constraint? & + constraintGroup* & + valueHelp* & + (element constraintErrorMessage { text })? & + completionHelp* & + + # These are meaningful only for leaf nodes + (element valueless { empty })? & + (element multi { empty })? & + (element hidden { empty })? & + (element secret { empty })? & + (element priority { text })? & + + # These are meaningful only for tag nodes + (element keepChildOrder { empty })? +} + +componentAttr = attribute component +{ + text +} + +versionAttr = attribute version +{ + text +} + +# All nodes must have "name" attribute +nodeNameAttr = attribute name +{ + text +} + +# Ordinary nodes and tag nodes can have "owner" attribute. +# Owner is the component that is notified when node changes. +ownerAttr = attribute owner +{ + text +} + +# Tag and leaf nodes may have constraints on their names and values +# (respectively). +# When multiple constraints are listed, they work as logical OR +constraint = element constraint +{ + ( (element regex { text }) | + validator )+ +} + +# Tag and leaf nodes may have constraintGroups on their names and +# values (respectively). +# When multiple constraints are listed within a group, they work as +# logical AND +constraintGroup = element constraintGroup +{ + ( (element regex { text }) | + validator )+ +} + +# A constraint may also use an external validator rather than regex +validator = element validator +{ + ( (attribute name { text }) & + (attribute argument { text })? ), + empty +} + +# help tags contains brief description of the purpose of the node +help = element help +{ + text +} + +# valueHelp tags contain information about acceptable value format +valueHelp = element valueHelp +{ + element format { text } & + element description { text } +} + +# completionHelp tags contain information about allowed values of a node that is used for generating +# tab completion in the CLI frontend and drop-down lists in GUI frontends +# It is only meaninful for leaf nodes +# Allowed values can be given as a fixed list of values (e.g. <list>foo bar baz</list>), +# as a configuration path (e.g. <path>interfaces ethernet</path>), +# or as a path to a script file that generates the list (e.g. <script>/usr/lib/foo/list-things</script> +completionHelp = element completionHelp +{ + (element list { text })* & + (element path { text })* & + (element script { text })* +} diff --git a/schema/interface_definition.rng b/schema/interface_definition.rng new file mode 100644 index 0000000..94a828c --- /dev/null +++ b/schema/interface_definition.rng @@ -0,0 +1,331 @@ +<?xml version="1.0" encoding="UTF-8"?> +<grammar xmlns="http://relaxng.org/ns/structure/1.0"> + <!-- + interface_definition.rnc: VyConf reference tree XML grammar + + Copyright (C) 2014. 2017 VyOS maintainers and contributors <maintainers@vyos.net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + --> + <!-- + The language of this file is compact form RELAX-NG + http://relaxng.org/compact-tutorial-20030326.htm + (unless converted to XML, then just RELAX-NG :) + --> + <!-- Interface definition starts with interfaceDefinition tag that may contain node tags --> + <start> + <element name="interfaceDefinition"> + <zeroOrMore> + <ref name="syntaxVersion"/> + </zeroOrMore> + <zeroOrMore> + <ref name="node"/> + </zeroOrMore> + </element> + </start> + <!-- interfaceDefinition may contain syntax version attribute lists. --> + <define name="syntaxVersion"> + <element name="syntaxVersion"> + <interleave> + <ref name="componentAttr"/> + <ref name="versionAttr"/> + </interleave> + </element> + </define> + <!-- + node tag may contain node, leafNode, or tagNode tags + Those are intermediate configuration nodes that may only contain + other nodes and must not have values + --> + <define name="node"> + <element name="node"> + <interleave> + <optional> + <ref name="ownerAttr"/> + </optional> + <ref name="nodeNameAttr"/> + </interleave> + <interleave> + <optional> + <ref name="properties"/> + </optional> + <optional> + <ref name="children"/> + </optional> + </interleave> + </element> + </define> + <!-- + Tag nodes are containers for nodes without predefined names, like network interfaces + or user names (e.g. "interfaces ethernet eth0" or "user jrandomhacker") + Tag nodes may contain node and leafNode elements, and also nameConstraint tags + They must not contain other tag nodes + --> + <define name="tagNode"> + <element name="tagNode"> + <interleave> + <optional> + <ref name="ownerAttr"/> + </optional> + <ref name="nodeNameAttr"/> + </interleave> + <interleave> + <optional> + <ref name="defaultValue"/> + </optional> + <optional> + <ref name="properties"/> + </optional> + <ref name="children"/> + </interleave> + </element> + </define> + <!-- + Leaf nodes are terminal configuration nodes that can't have children, + but can have values. + Leaf node may contain one or more valueConstraint tags + If multiple valueConstraint tags are used, they work a logical OR + Leaf nodes can have "multi" attribute that indicated that it can have more than one value + It can also have a default value + --> + <define name="leafNode"> + <element name="leafNode"> + <interleave> + <optional> + <ref name="ownerAttr"/> + </optional> + <ref name="nodeNameAttr"/> + </interleave> + <interleave> + <optional> + <ref name="defaultValue"/> + </optional> + <ref name="properties"/> + </interleave> + </element> + </define> + <!-- + Default value for leaf node, if applicable + It is used to generate default node state representation + --> + <define name="defaultValue"> + <element name="defaultValue"> + <text/> + </element> + </define> + <!-- Normal and tag nodes may have children --> + <define name="children"> + <element name="children"> + <oneOrMore> + <choice> + <ref name="node"/> + <ref name="tagNode"/> + <ref name="leafNode"/> + </choice> + </oneOrMore> + </element> + </define> + <!-- + Nodes may have properties + For simplicity, any property is allowed in any node, + but whether they are used or not is implementation-defined + + Leaf nodes may differ in number of values that can be + associated with them. + By default, a leaf node can have only one value. + "multi" tag means a node can have one or more values, + "valueless" means it can have no values at all. + "hidden" means node visibility can be toggled, eg 'dangerous' commands, + "secret" allows a node to hide its value from unprivileged users. + + "priority" is used to influence node processing order for nodes + with exact same dependencies and in compatibility modes. + --> + <define name="properties"> + <element name="properties"> + <interleave> + <optional> + <ref name="help"/> + </optional> + <optional> + <ref name="constraint"/> + </optional> + <zeroOrMore> + <ref name="constraintGroup"/> + </zeroOrMore> + <zeroOrMore> + <ref name="valueHelp"/> + </zeroOrMore> + <optional> + <element name="constraintErrorMessage"> + <text/> + </element> + </optional> + <zeroOrMore> + <ref name="completionHelp"/> + </zeroOrMore> + <optional> + <!-- These are meaningful only for leaf nodes --> + <group> + <element name="valueless"> + <empty/> + </element> + </group> + </optional> + <optional> + <element name="multi"> + <empty/> + </element> + </optional> + <optional> + <element name="hidden"> + <empty/> + </element> + </optional> + <optional> + <element name="secret"> + <empty/> + </element> + </optional> + <optional> + <element name="priority"> + <text/> + </element> + </optional> + <optional> + <!-- These are meaningful only for tag nodes --> + <group> + <element name="keepChildOrder"> + <empty/> + </element> + </group> + </optional> + </interleave> + </element> + </define> + <define name="componentAttr"> + <attribute name="component"/> + </define> + <define name="versionAttr"> + <attribute name="version"/> + </define> + <!-- All nodes must have "name" attribute --> + <define name="nodeNameAttr"> + <attribute name="name"/> + </define> + <!-- + Ordinary nodes and tag nodes can have "owner" attribute. + Owner is the component that is notified when node changes. + --> + <define name="ownerAttr"> + <attribute name="owner"/> + </define> + <!-- + Tag and leaf nodes may have constraints on their names and values + (respectively). + When multiple constraints are listed, they work as logical OR + --> + <define name="constraint"> + <element name="constraint"> + <oneOrMore> + <choice> + <element name="regex"> + <text/> + </element> + <ref name="validator"/> + </choice> + </oneOrMore> + </element> + </define> + <!-- + Tag and leaf nodes may have constraintGroups on their names and + values (respectively). + When multiple constraints are listed within a group, they work as + logical AND + --> + <define name="constraintGroup"> + <element name="constraintGroup"> + <oneOrMore> + <choice> + <element name="regex"> + <text/> + </element> + <ref name="validator"/> + </choice> + </oneOrMore> + </element> + </define> + <!-- A constraint may also use an external validator rather than regex --> + <define name="validator"> + <element name="validator"> + <interleave> + <attribute name="name"/> + <optional> + <attribute name="argument"/> + </optional> + </interleave> + <empty/> + </element> + </define> + <!-- help tags contains brief description of the purpose of the node --> + <define name="help"> + <element name="help"> + <text/> + </element> + </define> + <!-- valueHelp tags contain information about acceptable value format --> + <define name="valueHelp"> + <element name="valueHelp"> + <interleave> + <element name="format"> + <text/> + </element> + <element name="description"> + <text/> + </element> + </interleave> + </element> + </define> + <!-- + completionHelp tags contain information about allowed values of a node that is used for generating + tab completion in the CLI frontend and drop-down lists in GUI frontends + It is only meaninful for leaf nodes + Allowed values can be given as a fixed list of values (e.g. <list>foo bar baz</list>), + as a configuration path (e.g. <path>interfaces ethernet</path>), + or as a path to a script file that generates the list (e.g. <script>/usr/lib/foo/list-things</script> + --> + <define name="completionHelp"> + <element name="completionHelp"> + <interleave> + <zeroOrMore> + <element name="list"> + <text/> + </element> + </zeroOrMore> + <zeroOrMore> + <element name="path"> + <text/> + </element> + </zeroOrMore> + <zeroOrMore> + <element name="script"> + <text/> + </element> + </zeroOrMore> + </interleave> + </element> + </define> +</grammar> diff --git a/schema/op-mode-definition.rnc b/schema/op-mode-definition.rnc new file mode 100644 index 0000000..ad41700 --- /dev/null +++ b/schema/op-mode-definition.rnc @@ -0,0 +1,109 @@ +# interface_definition.rnc: VyConf reference tree XML grammar +# +# Copyright (C) 2014. 2017 VyOS maintainers and contributors <maintainers@vyos.net> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA + +# The language of this file is compact form RELAX-NG +# http://relaxng.org/compact-tutorial-20030326.htm +# (unless converted to XML, then just RELAX-NG :) + +# Interface definition starts with interfaceDefinition tag that may contain node tags +start = element interfaceDefinition +{ + (node | tagNode)* +} + +# node tag may contain node, leafNode, or tagNode tags +# Those are intermediate configuration nodes that may only contain +# other nodes and must not have values +node = element node +{ + nodeNameAttr, + (properties? & children? & command?) +} + +# Tag nodes are containers for nodes without predefined names, like network interfaces +# or user names (e.g. "interfaces ethernet eth0" or "user jrandomhacker") +# Tag nodes may contain node and leafNode elements, and also nameConstraint tags +# They must not contain other tag nodes +tagNode = element tagNode +{ + nodeNameAttr, + (properties? & children? & command?) +} + +# Leaf nodes are terminal configuration nodes that can't have children, +# but can have values. + +leafNode = element leafNode +{ + nodeNameAttr, + (command & properties) +} + +# Normal and tag nodes may have children +children = element children +{ + (node | tagNode | leafNode)+ +} + +# Nodes may have properties +# For simplicity, any property is allowed in any node, +# but whether they are used or not is implementation-defined + + +properties = element properties +{ + help? & + completionHelp* +} + +# All nodes must have "name" attribute +nodeNameAttr = attribute name +{ + text +} + + + + + +# help tags contains brief description of the purpose of the node +help = element help +{ + text +} + +command = element command +{ + text +} + +# completionHelp tags contain information about allowed values of a node that is used for generating +# tab completion in the CLI frontend and drop-down lists in GUI frontends +# It is only meaningful for leaf nodes +# Allowed values can be given as a fixed list of values (e.g. <list>foo bar baz</list>), +# as a configuration path (e.g. <path>interfaces ethernet</path>), +# as a path to a script file that generates the list (e.g. <script>/usr/lib/foo/list-things</script>, +# or to enable built-in image path completion (<imagePath/>). +completionHelp = element completionHelp +{ + (element list { text })* & + (element path { text })* & + (element script { text })* & + (element imagePath { empty })? +} diff --git a/schema/op-mode-definition.rng b/schema/op-mode-definition.rng new file mode 100644 index 0000000..a255aeb --- /dev/null +++ b/schema/op-mode-definition.rng @@ -0,0 +1,173 @@ +<?xml version="1.0" encoding="UTF-8"?> +<grammar xmlns="http://relaxng.org/ns/structure/1.0"> + <!-- + interface_definition.rnc: VyConf reference tree XML grammar + + Copyright (C) 2014. 2017 VyOS maintainers and contributors <maintainers@vyos.net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + --> + <!-- + The language of this file is compact form RELAX-NG + http://relaxng.org/compact-tutorial-20030326.htm + (unless converted to XML, then just RELAX-NG :) + --> + <!-- Interface definition starts with interfaceDefinition tag that may contain node tags --> + <start> + <element name="interfaceDefinition"> + <zeroOrMore> + <choice> + <ref name="node"/> + <ref name="tagNode"/> + </choice> + </zeroOrMore> + </element> + </start> + <!-- + node tag may contain node, leafNode, or tagNode tags + Those are intermediate configuration nodes that may only contain + other nodes and must not have values + --> + <define name="node"> + <element name="node"> + <ref name="nodeNameAttr"/> + <interleave> + <optional> + <ref name="properties"/> + </optional> + <optional> + <ref name="children"/> + </optional> + <optional> + <ref name="command"/> + </optional> + </interleave> + </element> + </define> + <!-- + Tag nodes are containers for nodes without predefined names, like network interfaces + or user names (e.g. "interfaces ethernet eth0" or "user jrandomhacker") + Tag nodes may contain node and leafNode elements, and also nameConstraint tags + They must not contain other tag nodes + --> + <define name="tagNode"> + <element name="tagNode"> + <ref name="nodeNameAttr"/> + <interleave> + <optional> + <ref name="properties"/> + </optional> + <optional> + <ref name="children"/> + </optional> + <optional> + <ref name="command"/> + </optional> + </interleave> + </element> + </define> + <!-- + Leaf nodes are terminal configuration nodes that can't have children, + but can have values. + --> + <define name="leafNode"> + <element name="leafNode"> + <ref name="nodeNameAttr"/> + <interleave> + <ref name="command"/> + <ref name="properties"/> + </interleave> + </element> + </define> + <!-- Normal and tag nodes may have children --> + <define name="children"> + <element name="children"> + <oneOrMore> + <choice> + <ref name="node"/> + <ref name="tagNode"/> + <ref name="leafNode"/> + </choice> + </oneOrMore> + </element> + </define> + <!-- + Nodes may have properties + For simplicity, any property is allowed in any node, + but whether they are used or not is implementation-defined + --> + <define name="properties"> + <element name="properties"> + <interleave> + <optional> + <ref name="help"/> + </optional> + <zeroOrMore> + <ref name="completionHelp"/> + </zeroOrMore> + </interleave> + </element> + </define> + <!-- All nodes must have "name" attribute --> + <define name="nodeNameAttr"> + <attribute name="name"/> + </define> + <!-- help tags contains brief description of the purpose of the node --> + <define name="help"> + <element name="help"> + <text/> + </element> + </define> + <define name="command"> + <element name="command"> + <text/> + </element> + </define> + <!-- + completionHelp tags contain information about allowed values of a node that is used for generating + tab completion in the CLI frontend and drop-down lists in GUI frontends + It is only meaninful for leaf nodes + Allowed values can be given as a fixed list of values (e.g. <list>foo bar baz</list>), + as a configuration path (e.g. <path>interfaces ethernet</path>), + or as a path to a script file that generates the list (e.g. <script>/usr/lib/foo/list-things</script> + --> + <define name="completionHelp"> + <element name="completionHelp"> + <interleave> + <zeroOrMore> + <element name="list"> + <text/> + </element> + </zeroOrMore> + <zeroOrMore> + <element name="path"> + <text/> + </element> + </zeroOrMore> + <zeroOrMore> + <element name="script"> + <text/> + </element> + </zeroOrMore> + <optional> + <element name="imagePath"> + <empty/> + </element> + </optional> + </interleave> + </element> + </define> +</grammar> diff --git a/scripts/build-command-op-templates b/scripts/build-command-op-templates new file mode 100644 index 0000000..d203fdc --- /dev/null +++ b/scripts/build-command-op-templates @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 +# +# build-command-template: converts new style command definitions in XML +# to the old style (bunch of dirs and node.def's) command templates +# +# Copyright (C) 2017-2024 VyOS maintainers <maintainers@vyos.net> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA + +import re +import sys +import os +import argparse +import copy +import functools + +from lxml import etree as ET +from textwrap import fill + +# Defaults +validator_dir = "/opt/vyatta/libexec/validators" +default_constraint_err_msg = "Invalid value" + +## Get arguments +parser = argparse.ArgumentParser(description='Converts new-style XML interface definitions to old-style command templates') +parser.add_argument('--debug', help='Enable debug information output', action='store_true') +parser.add_argument('INPUT_FILE', type=str, help="XML interface definition file") +parser.add_argument('SCHEMA_FILE', type=str, help="RelaxNG schema file") +parser.add_argument('OUTPUT_DIR', type=str, help="Output directory") + +args = parser.parse_args() + +input_file = args.INPUT_FILE +schema_file = args.SCHEMA_FILE +output_dir = args.OUTPUT_DIR +debug = args.debug + +## Load and validate the inputs +try: + xml = ET.parse(input_file) +except Exception as e: + print(f"Failed to load interface definition file {input_file}") + print(e) + sys.exit(1) + +try: + relaxng_xml = ET.parse(schema_file) + validator = ET.RelaxNG(relaxng_xml) + + if not validator.validate(xml): + print(validator.error_log) + print(f"Interface definition file {input_file} does not match the schema!") + sys.exit(1) +except Exception as e: + print(f"Failed to load the XML schema {schema_file}") + print(e) + sys.exit(1) + +if not os.access(output_dir, os.W_OK): + print(f"The output directory {output_dir} is not writeable") + sys.exit(1) + +## If we got this far, everything must be ok and we can convert the file +def make_path(l): + path = functools.reduce(os.path.join, l) + if debug: + print(path) + return path + +def get_properties(p): + props = {} + + if p is None: + return props + + # Get the help string + try: + props["help"] = p.find("help").text + except: + props["help"] = "No help available" + + + # Get the completion help strings + try: + che = p.findall("completionHelp") + ch = "" + for c in che: + scripts = c.findall("script") + paths = c.findall("path") + lists = c.findall("list") + comptype = c.find("imagePath") + + # Current backend doesn't support multiple allowed: tags + # so we get to emulate it + comp_exprs = [] + for i in lists: + comp_exprs.append("echo \"{0}\"".format(i.text)) + for i in paths: + path = re.sub(r'\s+', '/', i.text) + comp_exprs.append("ls /opt/vyatta/config/active/{0} 2>/dev/null".format(path)) + for i in scripts: + comp_exprs.append("{0}".format(i.text)) + if comptype is not None: + props["comp_type"] = "imagefiles" + comp_exprs.append("echo -n \"<imagefiles>\"") + comp_help = " && ".join(comp_exprs) + props["comp_help"] = comp_help + + except: + props["comp_help"] = [] + + return props + + +def make_node_def(props, command): + # XXX: replace with a template processor if it grows + # out of control + node_def = "" + + if "help" in props: + help = props["help"] + help = fill(help, width=64, subsequent_indent='\t\t\t') + node_def += f'help: {help}\n' + if "comp_type" in props: + node_def += f'comptype: {props["comp_type"]}\n' + if "comp_help" in props: + node_def += f'allowed: {props["comp_help"]}\n' + if command is not None: + node_def += f'run: {command.text}\n' + if debug: + print('Contents of the node.def file:\n', node_def) + + return node_def + +def process_node(n, tmpl_dir): + # Avoid mangling the path from the outer call + my_tmpl_dir = copy.copy(tmpl_dir) + + props_elem = n.find("properties") + children = n.find("children") + command = n.find("command") + name = n.get("name") + + node_type = n.tag + + my_tmpl_dir.append(name) + + if debug: + print(f"Name of the node: {name};\n Created directory: ", end="") + os.makedirs(make_path(my_tmpl_dir), exist_ok=True) + + props = get_properties(props_elem) + + nodedef_path = os.path.join(make_path(my_tmpl_dir), "node.def") + if node_type == "node": + if debug: + print(f"Processing node {name}") + + # Only create the "node.def" file if it exists but is empty, or if it + # does not exist at all. + if not os.path.exists(nodedef_path) or os.path.getsize(nodedef_path) == 0: + with open(nodedef_path, "w") as f: + f.write(make_node_def(props, command)) + + if children is not None: + inner_nodes = children.iterfind("*") + for inner_n in inner_nodes: + process_node(inner_n, my_tmpl_dir) + elif node_type == "tagNode": + if debug: + print(f"Processing tagNode {name}") + + os.makedirs(make_path(my_tmpl_dir), exist_ok=True) + + # Only create the "node.def" file if it exists but is empty, or if it + # does not exist at all. + if not os.path.exists(nodedef_path) or os.path.getsize(nodedef_path) == 0: + with open(nodedef_path, "w") as f: + f.write('help: {0}\n'.format(props['help'])) + + # Create the inner node.tag part + my_tmpl_dir.append("node.tag") + os.makedirs(make_path(my_tmpl_dir), exist_ok=True) + if debug: + print("Created path for the tagNode: {}".format(make_path(my_tmpl_dir)), end="") + + # Not sure if we want partially defined tag nodes, write the file unconditionally + nodedef_path = os.path.join(make_path(my_tmpl_dir), "node.def") + # Only create the "node.def" file if it exists but is empty, or if it + # does not exist at all. + if not os.path.exists(nodedef_path) or os.path.getsize(nodedef_path) == 0: + with open(nodedef_path, "w") as f: + f.write(make_node_def(props, command)) + + if children is not None: + inner_nodes = children.iterfind("*") + for inner_n in inner_nodes: + process_node(inner_n, my_tmpl_dir) + elif node_type == "leafNode": + # This is a leaf node + if debug: + print(f"Processing leaf node {name}") + + if not os.path.exists(nodedef_path) or os.path.getsize(nodedef_path) == 0: + with open(nodedef_path, "w") as f: + f.write(make_node_def(props, command)) + else: + print(f"Unknown node_type: {node_type}") + + +def get_node_key(node, attr=None): + """ Return the sorting key of an xml node using tag and attributes """ + if attr is None: + return '%s' % node.tag + ':'.join([node.get(attr) + for attr in sorted(node.attrib)]) + if attr in node.attrib: + return '%s:%s' % (node.tag, node.get(attr)) + return '%s' % node.tag + + +def sort_children(node, attr=None): + """ Sort children along tag and given attribute. if attr is None, sort + along all attributes """ + if not isinstance(node.tag, str): # PYTHON 2: use basestring instead + # not a TAG, it is comment or DATA + # no need to sort + return + # sort child along attr + node[:] = sorted(node, key=lambda child: get_node_key(child, attr)) + # and recurse + for child in node: + sort_children(child, attr) + +root = xml.getroot() + +# process_node() processes the XML tree in a fixed order, "node" before "tagNode" +# before "leafNode". If the generator created a "node.def" file, it can no longer +# be overwritten - else we would have some stale "node.def" files with an empty +# help string (T2555). Without the fixed order this would resulted in a case +# where we get a node and a tagNode with the same name, e.g. "show interfaces +# ethernet" and "show interfaces ethernet eth0" that the node implementation +# was not callable from the CLI, rendering this command useless (T3807). +# +# This can be fixed by forcing the "node", "tagNode", "leafNode" order by sorting +# the input XML file automatically (sorting from https://stackoverflow.com/a/46128043) +# thus adding no additional overhead to the user. +sort_children(root, 'name') + +nodes = root.iterfind("*") +for n in nodes: + process_node(n, [output_dir]) diff --git a/scripts/build-command-templates b/scripts/build-command-templates new file mode 100644 index 0000000..36929ab --- /dev/null +++ b/scripts/build-command-templates @@ -0,0 +1,348 @@ +#!/usr/bin/env python3 +# +# build-command-template: converts new style command definitions in XML +# to the old style (bunch of dirs and node.def's) command templates +# +# Copyright (C) 2017 VyOS maintainers <maintainers@vyos.net> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA + +import sys +import os +import argparse +import copy +import functools + +from lxml import etree as ET +from textwrap import fill + +# Defaults + +#validator_dir = "/usr/libexec/vyos/validators" +validator_dir = "${vyos_validators_dir}" +default_constraint_err_msg = "Invalid value" + + +## Get arguments + +parser = argparse.ArgumentParser(description='Converts new-style XML interface definitions to old-style command templates') +parser.add_argument('--debug', help='Enable debug information output', action='store_true') +parser.add_argument('INPUT_FILE', type=str, help="XML interface definition file") +parser.add_argument('SCHEMA_FILE', type=str, help="RelaxNG schema file") +parser.add_argument('OUTPUT_DIR', type=str, help="Output directory") + +args = parser.parse_args() + +input_file = args.INPUT_FILE +schema_file = args.SCHEMA_FILE +output_dir = args.OUTPUT_DIR +debug = args.debug + +#debug = True + +## Load and validate the inputs + +try: + xml = ET.parse(input_file) +except Exception as e: + print("Failed to load interface definition file {0}".format(input_file)) + print(e) + sys.exit(1) + +try: + relaxng_xml = ET.parse(schema_file) + validator = ET.RelaxNG(relaxng_xml) + + if not validator.validate(xml): + print(validator.error_log) + print("Interface definition file {0} does not match the schema!".format(input_file)) + sys.exit(1) +except Exception as e: + print("Failed to load the XML schema {0}".format(schema_file)) + print(e) + sys.exit(1) + +if not os.access(output_dir, os.W_OK): + print("The output directory {0} is not writeable".format(output_dir)) + sys.exit(1) + +## If we got this far, everything must be ok and we can convert the file + +def make_path(l): + path = functools.reduce(os.path.join, l) + if debug: + print(path) + return path + +def collect_validators(ve): + regexes = [] + regex_elements = ve.findall("regex") + if regex_elements is not None: + regexes = list(map(lambda e: e.text.strip().replace('\\','\\\\'), regex_elements)) + if "" in regexes: + print("Warning: empty regex, node will be accepting any value") + + validator_elements = ve.findall("validator") + validators = [] + if validator_elements is not None: + for v in validator_elements: + v_name = os.path.join(validator_dir, v.get("name")) + + # XXX: lxml returns None for empty arguments + v_argument = None + try: + v_argument = v.get("argument") + except: + pass + if v_argument is None: + v_argument = "" + + validators.append("{0} {1}".format(v_name, v_argument)) + + + regex_args = " ".join(map(lambda s: "--regex \\\'{0}\\\'".format(s), regexes)) + validator_args = " ".join(map(lambda s: "--exec \\\"{0}\\\"".format(s), validators)) + + return regex_args + " " + validator_args + +def get_properties(p, default=None): + props = {} + + if p is None: + return props + + # Get the help string + try: + help = p.find("help").text + if default != None: + # DNS forwarding for instance has multiple defaults - specified as whitespace separated list + tmp = ', '.join(default.text.split()) + help += f' (default: {tmp})' + help = fill(help, width=64, subsequent_indent='\t\t\t') + props["help"] = help + except: + pass + + # Get value help strings + try: + vhe = p.findall("valueHelp") + vh = [] + for v in vhe: + format = v.find("format").text + description = v.find("description").text + if default != None and default.text == format: + description += f' (default)' + # Is no description was specified, keep it empty + if not description: description = '' + vh.append( (format, description) ) + props["val_help"] = vh + except: + props["val_help"] = [] + + # Get the constraint and constraintGroup statements + + error_msg = default_constraint_err_msg + # Get the error message if it's there + try: + error_msg = p.find("constraintErrorMessage").text + except: + pass + + vce = p.find("constraint") + + distinct_validator_string = "" + if vce is not None: + # The old backend doesn't support multiple validators in OR mode + # so we emulate it + + distinct_validator_string = collect_validators(vce) + + vcge = p.findall("constraintGroup") + + group_validator_string = "" + if len(vcge): + for vcg in vcge: + group_validator_string = group_validator_string + " --grp " + collect_validators(vcg) + + if vce is not None or len(vcge): + validator_script = '${vyos_libexec_dir}/validate-value' + validator_string = "exec \"{0} {1} {2} --value \\\'$VAR(@)\\\'\"; \"{3}\"".format(validator_script, distinct_validator_string, group_validator_string, error_msg) + + props["constraint"] = validator_string + + # Get the completion help strings + try: + che = p.findall("completionHelp") + ch = "" + for c in che: + scripts = c.findall("script") + paths = c.findall("path") + lists = c.findall("list") + + # Current backend doesn't support multiple allowed: tags + # so we get to emulate it + comp_exprs = [] + for i in lists: + comp_exprs.append(f'echo "{i.text}"') + for i in paths: + comp_exprs.append(f'/bin/cli-shell-api listNodes {i.text}') + for i in scripts: + comp_exprs.append(f'sh -c "{i.text}"') + comp_help = ' && echo " " && '.join(comp_exprs) + props["comp_help"] = comp_help + except: + props["comp_help"] = [] + + # Get priority + try: + props["priority"] = p.find("priority").text + except: + pass + + # Get "multi" + if p.find("multi") is not None: + props["multi"] = True + + # Get "valueless" + if p.find("valueless") is not None: + props["valueless"] = True + + return props + +def make_node_def(props): + # XXX: replace with a template processor if it grows + # out of control + + node_def = "" + + if "tag" in props: + node_def += "tag:\n" + + if "multi" in props: + node_def += "multi:\n" + + if "type" in props: + # Will always be txt in practice if it's set + node_def += "type: {0}\n".format(props["type"]) + + if "priority" in props: + node_def += "priority: {0}\n".format(props["priority"]) + + if "help" in props: + node_def += "help: {0}\n".format(props["help"]) + + if "val_help" in props: + for v in props["val_help"]: + node_def += "val_help: {0}; {1}\n".format(v[0], v[1]) + + if "comp_help" in props: + node_def += "allowed: {0}\n".format(props["comp_help"]) + + if "constraint" in props: + node_def += "syntax:expression: {0}\n".format(props["constraint"]) + + shim = '${vyshim}' + + if "owner" in props: + if "tag" in props: + node_def += "end: sudo sh -c \"{1} VYOS_TAGNODE_VALUE='$VAR(@)' {0}\"\n".format(props["owner"], shim) + else: + node_def += "end: sudo sh -c \"{1} {0}\"\n".format(props["owner"], shim) + + if debug: + print("The contents of the node.def file:\n", node_def) + + return node_def + +def process_node(n, tmpl_dir): + # Avoid mangling the path from the outer call + my_tmpl_dir = copy.copy(tmpl_dir) + + props_elem = n.find("properties") + children = n.find("children") + + name = n.get("name") + owner = n.get("owner") + node_type = n.tag + + my_tmpl_dir.append(name) + + if debug: + print("Name of the node: {0}. Created directory: {1}\n".format(name, "/".join(my_tmpl_dir)), end="") + os.makedirs(make_path(my_tmpl_dir), exist_ok=True) + + props = get_properties(props_elem, n.find("defaultValue")) + if owner: + props["owner"] = owner + # <priority> tag is mandatory if the parent node has an owner + if "priority" not in props: + raise ValueError( + f"<priority> tag should be set for the node <{name}> path '{' '.join(my_tmpl_dir[1:])}'" + ) + + # Type should not be set for non-tag, non-leaf nodes + # For non-valueless leaf nodes, set the type to txt: to make them have some type, + # actual value validation is handled by constraints translated to syntax:expression: + if node_type != "node": + if "valueless" not in props.keys(): + props["type"] = "txt" + if node_type == "tagNode": + props["tag"] = "True" + + if node_type != "leafNode": + if "multi" in props: + raise ValueError("<multi/> tag is only allowed in <leafNode>") + if "valueless" in props: + raise ValueError("<valueless/> is only allowed in <leafNode>") + + nodedef_path = os.path.join(make_path(my_tmpl_dir), "node.def") + + # Only create the "node.def" file if it exists but is empty, or if it does + # not exist at all. An empty node.def file could be generated by XML paths + # that derive from one another bot having a common base structure like + # "protocols static" + if not os.path.exists(nodedef_path) or os.path.getsize(nodedef_path) == 0: + with open(nodedef_path, "w") as f: + f.write(make_node_def(props)) + + if node_type == "node": + inner_nodes = children.iterfind("*") + for inner_n in inner_nodes: + process_node(inner_n, my_tmpl_dir) + if node_type == "tagNode": + my_tmpl_dir.append("node.tag") + if debug: + print("Created path for the tagNode:", end="") + os.makedirs(make_path(my_tmpl_dir), exist_ok=True) + inner_nodes = children.iterfind("*") + for inner_n in inner_nodes: + process_node(inner_n, my_tmpl_dir) + else: + # This is a leaf node + pass + + +root = xml.getroot() + +nodes = root.iterfind("*") +for n in nodes: + if n.tag == "syntaxVersion": + continue + try: + process_node(n, [output_dir]) + except ValueError as e: + print(e) + sys.exit(1) diff --git a/scripts/generate-configd-include-json.py b/scripts/generate-configd-include-json.py new file mode 100644 index 0000000..b4b627f --- /dev/null +++ b/scripts/generate-configd-include-json.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +from jinja2 import Template + +conf_scripts = 'src/conf_mode' +configd_include = 'data/configd-include.json' + +configd_template = Template("""[ +{% for file in files %} +"{{ file }}"{{ "," if not loop.last else "" }} +{% endfor %} +] +""", trim_blocks=True) + +files = [f for f in os.listdir(conf_scripts) if os.path.isfile(f'{conf_scripts}/{f}')] +files = sorted(files) + +tmp = {'files' : files} +with open(configd_include, 'w') as f: + f.write(configd_template.render(tmp)) diff --git a/scripts/override-default b/scripts/override-default new file mode 100644 index 0000000..5058e79 --- /dev/null +++ b/scripts/override-default @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +# +# override-default: preprocessor for XML interface definitions to interpret +# redundant entries (relative to path) with tag 'defaultValue' as an override +# directive. Must be called before build-command-templates, as the schema +# disallows redundancy. +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +# Use lxml xpath capability to find multiple elements with tag defaultValue +# relative to path; replace and remove to override the value. + +import sys +import glob +import logging +from copy import deepcopy +from lxml import etree + +debug = False + +logger = logging.getLogger(__name__) +logs_handler = logging.StreamHandler() +logger.addHandler(logs_handler) + +if debug: + logger.setLevel(logging.DEBUG) +else: + logger.setLevel(logging.INFO) + +def clear_empty_path(el): + # on the odd chance of interleaved comments + tmp = [l for l in el if isinstance(l.tag, str)] + if not tmp: + p = el.getparent() + p.remove(el) + clear_empty_path(p) + +def override_element(l: list): + """ + Allow multiple override elements; use the final one (in document order). + """ + if len(l) < 2: + logger.debug("passing list of single element to override_element") + return + + # assemble list of leafNodes of overriding defaultValues, for later removal + parents = [] + for el in l[1:]: + parents.append(el.getparent()) + + # replace element with final override + l[0].getparent().replace(l[0], l[-1]) + + # remove all but overridden element + for el in parents: + tmp = el.getparent() + tmp.remove(el) + clear_empty_path(tmp) + +def merge_remaining(l: list, elementtree): + """ + Merge (now) single leaf node containing 'defaultValue' with leaf nodes + of same path and no 'defaultValue'. + """ + for p in l: + p = p.split() + path_str = f'/interfaceDefinition/*' + path_list = [] + for i in range(len(p)): + path_list.append(f'[@name="{p[i]}"]') + path_str += '/children/*'.join(path_list) + rp = elementtree.xpath(path_str) + if len(rp) > 1: + for el in rp[1:]: + # in practice there will only be one child of the path, + # either defaultValue or Properties, since + # override_element() has already run + for child in el: + rp[0].append(deepcopy(child)) + tmp = el.getparent() + tmp.remove(el) + clear_empty_path(tmp) + +def collect_and_override(dir_name): + """ + Collect elements with defaultValue tag into dictionary indexed by name + attributes of ancestor path. + """ + for fname in glob.glob(f'{dir_name}/*.xml'): + tree = etree.parse(fname) + root = tree.getroot() + defv = {} + + xpath_str = '//defaultValue' + xp = tree.xpath(xpath_str) + + for element in xp: + ap = element.xpath('ancestor::*[@name]') + ap_name = [el.get("name") for el in ap] + ap_path_str = ' '.join(ap_name) + defv.setdefault(ap_path_str, []).append(element) + + for k, v in defv.items(): + if len(v) > 1: + logger.info(f"overriding default in path '{k}'") + override_element(v) + + to_merge = list(defv) + merge_remaining(to_merge, tree) + + revised_str = etree.tostring(root, encoding='unicode', pretty_print=True) + + with open(f'{fname}', 'w') as f: + f.write(revised_str) + +def main(): + if len(sys.argv) < 2: + logger.critical('Must specify XML directory!') + sys.exit(1) + + dir_name = sys.argv[1] + + collect_and_override(dir_name) + +if __name__ == '__main__': + main() diff --git a/scripts/transclude-template b/scripts/transclude-template new file mode 100644 index 0000000..5c6668a --- /dev/null +++ b/scripts/transclude-template @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# +# transclude-template: preprocessor for XML interface definitions to +# interpret #include statements to include nested XML fragments and +# snippets in documents. +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +import os +import re +import sys + +regexp = re.compile(r'^ *#include <(.+)>$') + +def parse_file(filename): + lines = "" + with open(filename, 'r') as f: + while True: + line = f.readline() + if line: + result = regexp.match(line) + if result: + lines += parse_file(os.path.join(directory, result.group(1))) + else: + lines += line + else: + return lines + +if __name__ == '__main__': + if len(sys.argv) < 2: + print('Must specify XML file!', file=sys.stderr) + sys.exit(1) + filename = sys.argv[1] + directory = os.path.dirname(os.path.abspath(filename)) + print(parse_file(filename)) + diff --git a/scripts/update-configd-include-file b/scripts/update-configd-include-file new file mode 100644 index 0000000..ca23408 --- /dev/null +++ b/scripts/update-configd-include-file @@ -0,0 +1,298 @@ +#!/usr/bin/env python3 +### +# A simple script for safely editing configd-include.json, the list of +# scripts which are off-loaded to be run by the daemon. +# Usage: +# update-configd-include-file --add script1.py script2.py ... +# --remove scriptA.py scriptB.py ... +# +# Additionally, it offers optional sanity checks by examining the signatures +# of functions and placement of Config instance for consistency with configd +# requirements. +# Usage: +# update-configd-include-file --check-current +# to check the current include list +# update-configd-include-file --check-file +# to check arbitrary conf_mode scripts +# +# Note that this feature is the basis for the configd smoketest, but it is of +# limited use in this script, as it requires an environment that has all script +# (python) dependencies installed (e.g. installed image) so that the script may +# be imported for introspection. Nonetheless, for testing and development, it has +# its uses. + +import os +import sys +import json +import argparse +import datetime +import importlib.util +from inspect import signature, getsource + +from vyos.defaults import directories +from vyos.version import get_version +from vyos.utils.process import cmd + +# Defaults + +installed_image = False + +include_file = 'configd-include.json' +build_relative_include_file = '../data/configd-include.json' +dirname = os.path.dirname(__file__) + +build_location_include_file = os.path.join(dirname, build_relative_include_file) +image_location_include_file = os.path.join(directories['data'], include_file) + +build_relative_conf_dir = '../src/conf_mode' + +build_location_conf_dir = os.path.join(dirname, build_relative_conf_dir) +image_location_conf_dir = directories['conf_mode'] + +# Get arguments + +parser = argparse.ArgumentParser(description='Add or remove scripts from the list of scripts to be run be daemon') +parser.add_argument('--add', nargs='*', default=[], + help='scripts to add to configd include list') +parser.add_argument('--remove', nargs='*', default=[], + help='scripts to remove from configd include list') +parser.add_argument('--show-diff', action='store_true', + help='show list of conf_mode scripts not in include list') +parser.add_argument('--check-file', nargs='*', default=[], + help='check files for suitability to run under daemon') +parser.add_argument('--check-current', action="store_true", + help='check current include list for suitability to run under daemon') + +args = vars(parser.parse_args()) + +# Check if we are running within installed image; since this script is not +# part of the distribution, there is no need to check if live cd +if get_version(): + installed_image = True + +if installed_image: + include_file = image_location_include_file + conf_dir = image_location_conf_dir +else: + include_file = build_location_include_file + conf_dir = build_location_conf_dir + +# Utilities for checking function signature and body +def import_script(s: str): + """ + A compact form of the import code in vyos-configd + """ + path = os.path.join(conf_dir, s) + if not os.path.exists(path): + print(f"script {s} is not in conf_mode directory") + return None + + name = os.path.splitext(s)[0].replace('-', '_') + + spec = importlib.util.spec_from_file_location(name, path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + return module + +funcs = { 'get_config': False, + 'verify': False, + 'generate': False, + 'apply': False + } + +def check_signatures(s: str) -> bool: + """ + Basic sanity check: script standard functions should all take one + argument, including get_config(config=None). + """ + funcd = dict(funcs) + for i in list(funcd): + m = import_script(s) + f = getattr(m, i, None) + if not f: + funcd[i] = True + continue + sig = signature(f) + params = sig.parameters + if len(params) != 1: + continue + if i == 'get_config': + for p in params.values(): + funcd[i] = True if (p.default is None) else False + else: + funcd[i] = True + + res = True + + for k, v in funcd.items(): + if v is False: + if k == 'get_config': + print(f"function '{k}' will need the standard modification") + else: + print(f"function '{k}' in script '{s}' has wrong signature") + res = False + + return res + +def check_instance_per_function(s: str) -> bool: + """ + The standard function 'get_config' should have one instantiation of Config; + all other standard functions, zero. + """ + funcd = dict(funcs) + for i in list(funcd): + m = import_script(s) + f = getattr(m, i, None) + if not f: + funcd[i] = True + continue + str_f = getsource(f) + n = str_f.count('Config()') + if n == 1 and i == 'get_config': + funcd[i] = True + if n == 0 and i != 'get_config': + funcd[i] = True + + res = True + + for k, v in funcd.items(): + if v is False: + fi = 'zero' if k == 'get_config' else 'non-zero' + print(f"function '{k}' in script '{s}' has {fi} instances of Config") + res = False + + return res + +def check_instance_total(s: str) -> bool: + """ + A script should have at most one instantiation of Config. + """ + m = import_script(s) + str_m = getsource(m) + n = str_m.count('Config()') + if n != 1: + print(f"instance of Config outside of 'get_config' in script '{s}'") + return False + + return True + +def check_config_modification(s: str) -> bool: + """ + Modification to the session config from within a script is necessary in + certain cases, but the script should then run as stand-alone. + """ + m = import_script(s) + str_m = getsource(m) + n = str_m.count('my_set') + if n != 0: + print(f"modification of config within script") + return False + + return True + +def check_viability(s: str) -> bool: + """ + Check existence, and if on installed image, signatures, instances of + Config, and modification of session config + """ + path = os.path.join(conf_dir, s) + if not os.path.exists(path): + print(f"script {s} is not in conf_mode directory") + return False + + if not installed_image: + if args['check_file'] or args['check_current']: + print(f"In order to check script viability for offload, run this script on installed image") + return True + + r1 = check_signatures(s) + r2 = check_instance_per_function(s) + r3 = check_instance_total(s) + r4 = check_config_modification(s) + + if not r1 or not r2 or not r3 or not r4: + return False + + return True + +def check_file(s: str) -> bool: + if not check_viability(s): + return False + return True + +def check_files(l: list) -> int: + check_list = l[:] + res = 0 + for s in check_list: + if not check_file(s): + res = 1 + return res + +# Status + +def show_diff(l: list): + print(conf_dir) + (_, _, filenames) = next(iter(os.walk(conf_dir))) + filenames.sort() + res = [i for i in filenames if i not in l] + print(res) + +# Read configd-include.json and add/remove/check/show scripts + +with open(include_file, 'r') as f: + try: + include_list = json.load(f) + except OSError as e: + print(f"configd include file error: {e}") + sys.exit(1) + except json.JSONDecodeError as e: + print(f"JSON load error: {e}") + sys.exit(1) + +if args['show_diff']: + show_diff(include_list) + sys.exit(0) + +if args['check_file']: + l = args['check_file'] + ret = check_files(l) + if not ret: + print('pass') + sys.exit(ret) + +if args['check_current']: + ret = check_files(include_list) + if not ret: + print('pass') + sys.exit(ret) + +add_list = args['add'] +# drop redundencies +add_list = [i for i in add_list if i not in include_list] +# prune entries that don't pass check +add_list = [i for i in add_list if check_file(i)] + +remove_list = args['remove'] + +if not add_list and not remove_list: + sys.exit(0) + +separator = '.' +backup_file_name = separator.join([include_file, + '{0:%Y-%m-%d-%H%M%S}'.format(datetime.datetime.now()), 'bak']) + +cmd(f'cp -p {include_file} {backup_file_name}') + +if add_list: + include_list.extend(add_list) + include_list.sort() +if remove_list: + include_list = [i for i in include_list if i not in remove_list] + +with open(include_file, 'w') as f: + try: + json.dump(include_list, f, indent=0) + except OSError as e: + print(f"error writing configd include file: {e}") + sys.exit(1) diff --git a/smoketest/bin/vyos-configtest b/smoketest/bin/vyos-configtest new file mode 100644 index 0000000..fbf4055 --- /dev/null +++ b/smoketest/bin/vyos-configtest @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys +import time +import logging +import unittest + +from vyos.configsession import ConfigSession, ConfigSessionError +from vyos import ConfigError + +config_dir = '/usr/libexec/vyos/tests/config' +config_test_dir = '/usr/libexec/vyos/tests/config-tests' +save_config = '/tmp/vyos-configtest-save' + +class DynamicClassBase(unittest.TestCase): + def setUp(self): + self._start_time = time.time() + self.session = ConfigSession(os.getpid()) + self.session.save_config(save_config) + + def tearDown(self): + self.session.migrate_and_load_config(save_config) + self.session.commit() + log.info(f" time: {time.time() - self._start_time:.3f}") + del self.session + try: + os.remove(save_config) + except OSError: + pass + +def make_test_function(filename, test_path=None): + def test_config_load(self): + config_path = os.path.join(config_dir, filename) + self.session.migrate_and_load_config(config_path) + try: + self.session.commit() + except (ConfigError, ConfigSessionError): + self.session.discard() + self.fail() + + if test_path: + config_commands = self.session.show(['configuration', 'commands']) + + with open(test_path, 'r') as f: + for line in f.readlines(): + if not line or line.startswith("#"): + continue + + self.assertIn(line, config_commands) + return test_config_load + +def class_name_from_func_name(s): + res = ''.join(str.capitalize(x) for x in s.split('_')) + return res + +if __name__ == '__main__': + logging.basicConfig(stream=sys.stdout, level=logging.DEBUG, + format='%(message)s') + log = logging.getLogger("TestConfigLog") + + start_time = time.time() + log.info("Generating tests") + + (_, _, config_list) = next(iter(os.walk(config_dir))) + config_list.sort() + + for config in config_list: + test_path = os.path.join(config_test_dir, config) + + if not os.path.exists(test_path): + log.error(f'Missing migration result test for config "{config}"') + sys.exit(1) + + log.info(f'Loaded migration result test for config "{config}"') + + test_func = make_test_function(config, test_path) + + func_name = config.replace('-', '_') + klassname = f'TestConfig{class_name_from_func_name(func_name)}' + + globals()[klassname] = type(klassname, + (DynamicClassBase,), + {f'test_{func_name}': test_func}) + + log.info(f"... completed: {time.time() - start_time:.6f}") + + unittest.main(verbosity=2) diff --git a/smoketest/bin/vyos-configtest-pki b/smoketest/bin/vyos-configtest-pki new file mode 100644 index 0000000..0f9ecdd --- /dev/null +++ b/smoketest/bin/vyos-configtest-pki @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024, VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from os import system +from vyos.pki import create_private_key +from vyos.pki import create_certificate_request +from vyos.pki import create_certificate +from vyos.pki import create_certificate_revocation_list +from vyos.pki import create_dh_parameters +from vyos.pki import encode_certificate +from vyos.pki import encode_dh_parameters +from vyos.pki import encode_private_key +from vyos.utils.file import write_file + +subject = {'country': 'DE', 'state': 'BY', 'locality': 'Cloud', 'organization': 'VyOS', 'common_name': 'VyOS'} +ca_subject = {'country': 'DE', 'state': 'BY', 'locality': 'Cloud', 'organization': 'VyOS', 'common_name': 'VyOS CA'} +subca_subject = {'country': 'DE', 'state': 'BY', 'locality': 'Cloud', 'organization': 'VyOS', 'common_name': 'VyOS SubCA'} + +ca_cert = '/config/auth/ovpn_test_ca.pem' +ca_key = '/config/auth/ovpn_test_ca.key' +ca_cert_chain = '/config/auth/ovpn_test_chain.pem' +ca_crl = '/config/auth/ovpn_test_ca.crl' +subca_cert = '/config/auth/ovpn_test_subca.pem' +subca_csr = '/tmp/subca.csr' +subca_key = '/config/auth/ovpn_test_subca.key' +ssl_cert = '/config/auth/ovpn_test_server.pem' +ssl_key = '/config/auth/ovpn_test_server.key' +dh_pem = '/config/auth/ovpn_test_dh.pem' +s2s_key = '/config/auth/ovpn_test_site2site.key' +auth_key = '/config/auth/ovpn_test_tls_auth.key' + +rpki_ssh_priv_key = """ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAweDyflDFR4qyEwETbJkZ2ZZc+sJNiDTvYpwGsWIkju49lJSxHe1x +Kf8FhwfyMu40Snt1yDlRmmmz4CsbLgbuZGMPvXG11e34+C0pSVUvpF6aqRTeLl1pDRK7Rn +jgm3su+I8SRLQR4qbLG6VXWOFuVpwiqbExLaU0hFYTPNP+dArNpsWEEKsohk6pTXdhg3Vz +Wp3vCMjl2JTshDa3lD7p2xISSAReEY0fnfEAmQzH4Z6DIwwGdFuMWoQIg+oFBM9ARrO2/F +IjRsz6AecR/WeU72JEw4aJic1/cAJQA6PiQBHwkuo3Wll1tbpxeRZoB2NQG22ETyJLvhfT +aooNLT9HpQAAA8joU5dM6FOXTAAAAAdzc2gtcnNhAAABAQDB4PJ+UMVHirITARNsmRnZll +z6wk2INO9inAaxYiSO7j2UlLEd7XEp/wWHB/Iy7jRKe3XIOVGaabPgKxsuBu5kYw+9cbXV +7fj4LSlJVS+kXpqpFN4uXWkNErtGeOCbey74jxJEtBHipssbpVdY4W5WnCKpsTEtpTSEVh +M80/50Cs2mxYQQqyiGTqlNd2GDdXNane8IyOXYlOyENreUPunbEhJIBF4RjR+d8QCZDMfh +noMjDAZ0W4xahAiD6gUEz0BGs7b8UiNGzPoB5xH9Z5TvYkTDhomJzX9wAlADo+JAEfCS6j +daWXW1unF5FmgHY1AbbYRPIku+F9Nqig0tP0elAAAAAwEAAQAAAQACkDlUjzfUhtJs6uY5 +WNrdJB5NmHUS+HQzzxFNlhkapK6+wKqI1UNaRUtq6iF7J+gcFf7MK2nXS098BsXguWm8fQ +zPuemoDvHsQhiaJhyvpSqRUrvPTB/f8t/0AhQiKiJIWgfpTaIw53inAGwjujNNxNm2eafH +TThhCYxOkRT7rsT6bnSio6yeqPy5QHg7IKFztp5FXDUyiOS3aX3SvzQcDUkMXALdvzX50t +1XIk+X48Rgkq72dL4VpV2oMNDu3hM6FqBUplf9Mv3s51FNSma/cibCQoVufrIfoqYjkNTj +IpYFUcq4zZ0/KvgXgzSsy9VN/4TtbalrOuu7X/SHJbvhAAAAgGPFsXgONYQvXxCnK1dIue +ozgaZg1I/n522E2ZCOXBW4dYJVyNpppwRreDzuFzTDEe061MpNHfScjVBJCCulivFYWscL +6oaGsryDbFxO3QmB4I98UBqrds2yan9/JGc6EYe299yvaHy7Y64+NC0+fN8H2RAZ61T4w1 +0JrCaJRyvzAAAAgQDvBfuV1U7o9k/fbU+U7W2UYnWblpOZAMfi1XQP6IJJeyWs90PdTdXh ++l0eIQrCawIiRJytNfxMmbD4huwTf77fWiyCcPznmALQ7ex/yJ+W5Z0V4dPGF3h7o1uiS2 +36JhQ7mfcliCkhp/1PIklBIMPcCp0zl+s9wMv2hX7w1Pah9QAAAIEAz6YgU9Xute+J+dBw +oWxEQ+igR6KE55Um7O9AvSrqnCm9r7lSFsXC2ErYOxoDSJ3yIBEV0b4XAGn6tbbVIs3jS8 +BnLHxclAHQecOx1PGn7PKbnPW0oJRq/X9QCIEelKYvlykpayn7uZooTXqcDaPZxfPpmPdy +e8chVJvdygi7kPEAAAAMY3BvQExSMS53dWUzAQIDBAUGBw== +-----END OPENSSH PRIVATE KEY----- +""" + +rpki_ssh_pub_key = """ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDB4PJ+UMVHirITARNsmRnZllz6wk2INO9inAaxYiSO7j2UlLEd7XEp/wWHB/Iy7jRKe3XIOVGaabPgKxsuBu5kYw+9cbXV7fj4LSlJVS+kXpqpFN4uXWkNErtGeOCbey74jxJEtBHipssbpVdY4W5WnCKpsTEtpTSEVhM80/50Cs2mxYQQqyiGTqlNd2GDdXNane8IyOXYlOyENreUPunbEhJIBF4RjR+d8QCZDMfhnoMjDAZ0W4xahAiD6gUEz0BGs7b8UiNGzPoB5xH9Z5TvYkTDhomJzX9wAlADo+JAEfCS6jdaWXW1unF5FmgHY1AbbYRPIku+F9Nqig0tP0el vyos@vyos +""" + +def create_cert(subject, cert_path, key_path, sign_by=None, sign_by_key=None, ca=False, sub_ca=False): + priv_key = create_private_key('rsa', 2048) + cert_req = create_certificate_request(subject, priv_key) + cert = create_certificate( + cert_req, + sign_by if sign_by else cert_req, + sign_by_key if sign_by_key else priv_key, + is_ca=ca, is_sub_ca=sub_ca) + + with open(cert_path, 'w') as f: + f.write(encode_certificate(cert)) + + with open(key_path, 'w') as f: + f.write(encode_private_key(priv_key)) + + return cert, priv_key + +def create_empty_crl(crl_path, sign_by, sign_by_key): + crl = create_certificate_revocation_list(sign_by, sign_by_key, [1]) + + with open(crl_path, 'w') as f: + f.write(encode_certificate(crl)) + + return crl + +if __name__ == '__main__': + # Create Root CA + ca_cert_obj, ca_key_obj = create_cert(ca_subject, ca_cert, ca_key, ca=True) + + # Create Empty CRL + create_empty_crl(ca_crl, ca_cert_obj, ca_key_obj) + + # Create Intermediate CA + subca_cert_obj, subca_key_obj = create_cert( + subca_subject, subca_cert, subca_key, + sign_by=ca_cert_obj, sign_by_key=ca_key_obj, + ca=True, sub_ca=True) + + # Create Chain + with open(ca_cert_chain, 'w') as f: + f.write(encode_certificate(subca_cert_obj) + "\n") + f.write(encode_certificate(ca_cert_obj) + "\n") + + # Create Server Cert + create_cert(subject, ssl_cert, ssl_key, sign_by=subca_cert_obj, sign_by_key=subca_key_obj) + + # Create DH params + dh_params = create_dh_parameters() + + with open(dh_pem, 'w') as f: + f.write(encode_dh_parameters(dh_params)) + + # OpenVPN S2S Key + system(f'openvpn --genkey secret {s2s_key}') + + # OpenVPN Auth Key + system(f'openvpn --genkey secret {auth_key}') + + write_file('/config/id_rsa', rpki_ssh_priv_key.strip()) + write_file('/config/id_rsa.pub', rpki_ssh_pub_key.strip()) + write_file('/config/known-hosts-file', '') diff --git a/smoketest/bin/vyos-smoketest b/smoketest/bin/vyos-smoketest new file mode 100644 index 0000000..135388a --- /dev/null +++ b/smoketest/bin/vyos-smoketest @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit +from stat import S_IXOTH +from subprocess import Popen, PIPE + +success = True +for root, dirs, files in os.walk('/usr/libexec/vyos/tests/smoke'): + for name in files: + test_file = os.path.join(root, name) + mode = os.stat(test_file).st_mode + + if name.startswith("test_") and mode & S_IXOTH: + print('Running Testcase: ' + test_file) + process = Popen([test_file], stdout=PIPE) + (output, err) = process.communicate() + exit_code = process.wait() + # We do not want an instant fail - other tests should be run, too + if exit_code != 0: + success = False + +if success: + exit(0) + +print("ERROR: One or more tests failed!") +exit(1) diff --git a/smoketest/config-tests/basic-api-service b/smoketest/config-tests/basic-api-service new file mode 100644 index 0000000..3f796f3 --- /dev/null +++ b/smoketest/config-tests/basic-api-service @@ -0,0 +1,28 @@ +set interfaces ethernet eth0 address '192.0.2.1/31' +set interfaces ethernet eth0 address '2001:db8::1234/64' +set interfaces ethernet eth0 offload gro +set interfaces loopback lo +set service https allow-client address '172.16.0.0/12' +set service https allow-client address '192.168.0.0/16' +set service https allow-client address '10.0.0.0/8' +set service https allow-client address '2001:db8::/32' +set service https api keys id 1 key 'S3cur3' +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server time1.vyos.net +set service ntp server time2.vyos.net +set service ntp server time3.vyos.net +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' diff --git a/smoketest/config-tests/basic-vyos b/smoketest/config-tests/basic-vyos new file mode 100644 index 0000000..6ff28ec --- /dev/null +++ b/smoketest/config-tests/basic-vyos @@ -0,0 +1,103 @@ +set interfaces ethernet eth0 address '192.168.0.1/24' +set interfaces ethernet eth0 address 'fe88::1/56' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 offload gro +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 offload gro +set interfaces ethernet eth1 speed 'auto' +set interfaces ethernet eth2 duplex 'auto' +set interfaces ethernet eth2 offload gro +set interfaces ethernet eth2 speed 'auto' +set interfaces ethernet eth2 vif 100 address '100.100.0.1/24' +set interfaces ethernet eth2 vif-s 200 address '100.64.200.254/24' +set interfaces ethernet eth2 vif-s 200 vif-c 201 address '100.64.201.254/24' +set interfaces ethernet eth2 vif-s 200 vif-c 201 address 'fe89::1/56' +set interfaces ethernet eth2 vif-s 200 vif-c 202 address '100.64.202.254/24' +set interfaces loopback lo +set protocols static arp interface eth0 address 192.168.0.20 mac '00:50:00:00:00:20' +set protocols static arp interface eth0 address 192.168.0.30 mac '00:50:00:00:00:30' +set protocols static arp interface eth0 address 192.168.0.40 mac '00:50:00:00:00:40' +set protocols static arp interface eth2.100 address 100.100.0.2 mac '00:50:00:00:02:02' +set protocols static arp interface eth2.100 address 100.100.0.3 mac '00:50:00:00:02:03' +set protocols static arp interface eth2.100 address 100.100.0.4 mac '00:50:00:00:02:04' +set protocols static arp interface eth2.200 address 100.64.200.1 mac '00:50:00:00:00:01' +set protocols static arp interface eth2.200 address 100.64.200.2 mac '00:50:00:00:00:02' +set protocols static arp interface eth2.200.201 address 100.64.201.10 mac '00:50:00:00:00:10' +set protocols static arp interface eth2.200.201 address 100.64.201.20 mac '00:50:00:00:00:20' +set protocols static arp interface eth2.200.202 address 100.64.202.30 mac '00:50:00:00:00:30' +set protocols static arp interface eth2.200.202 address 100.64.202.40 mac '00:50:00:00:00:40' +set protocols static route 0.0.0.0/0 next-hop 100.64.0.1 +set service dhcp-server shared-network-name LAN authoritative +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option default-router '192.168.0.1' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-name 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-search 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option name-server '192.168.0.1' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic start '192.168.0.30' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic stop '192.168.0.240' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST1-1 ip-address '192.168.0.11' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST1-1 mac '00:01:02:03:04:05' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST1-2 disable +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST1-2 ip-address '192.168.0.12' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST1-2 mac '00:01:02:03:04:05' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-1 ip-address '192.168.0.21' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-1 mac '00:01:02:03:04:21' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-2 disable +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-2 ip-address '192.168.0.21' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-2 mac '00:01:02:03:04:22' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 subnet-id '1' +set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 interface 'eth0' +set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 option domain-search 'vyos.net' +set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 option name-server 'fe88::1' +set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 range 1 prefix 'fe88::/60' +set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 range 2 start 'fe88:0000:0000:fe::' +set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 range 2 stop 'fe88:0000:0000:ff::' +set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 subnet-id '1' +set service dhcpv6-server shared-network-name LAN6 subnet fe89::/56 interface 'eth2.200.201' +set service dhcpv6-server shared-network-name LAN6 subnet fe89::/56 option domain-search 'vyos.net' +set service dhcpv6-server shared-network-name LAN6 subnet fe89::/56 option name-server 'fe89::1' +set service dhcpv6-server shared-network-name LAN6 subnet fe89::/56 range 1 prefix 'fe89::/60' +set service dhcpv6-server shared-network-name LAN6 subnet fe89::/56 range 2 start 'fe89:0000:0000:fe::' +set service dhcpv6-server shared-network-name LAN6 subnet fe89::/56 range 2 stop 'fe89:0000:0000:ff::' +set service dhcpv6-server shared-network-name LAN6 subnet fe89::/56 subnet-id '2' +set service dns forwarding allow-from '192.168.0.0/16' +set service dns forwarding cache-size '10000' +set service dns forwarding dnssec 'off' +set service dns forwarding listen-address '192.168.0.1' +set service ssh ciphers 'aes128-ctr' +set service ssh ciphers 'aes192-ctr' +set service ssh ciphers 'aes256-ctr' +set service ssh ciphers 'chacha20-poly1305@openssh.com' +set service ssh ciphers 'rijndael-cbc@lysator.liu.se' +set service ssh key-exchange 'curve25519-sha256@libssh.org' +set service ssh key-exchange 'diffie-hellman-group1-sha1' +set service ssh key-exchange 'diffie-hellman-group-exchange-sha1' +set service ssh key-exchange 'diffie-hellman-group-exchange-sha256' +set service ssh listen-address '192.168.0.1' +set service ssh port '22' +set system config-management commit-revisions '100' +set system conntrack ignore ipv4 rule 1 destination address '192.0.2.2' +set system conntrack ignore ipv4 rule 1 source address '192.0.2.1' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system name-server '192.168.0.1' +set system syslog console facility all level 'emerg' +set system syslog console facility mail level 'info' +set system syslog global facility all level 'info' +set system syslog global facility auth level 'info' +set system syslog global facility local7 level 'debug' +set system syslog global preserve-fqdn +set system syslog host syslog.vyos.net facility auth level 'warning' +set system syslog host syslog.vyos.net facility local7 level 'notice' +set system syslog host syslog.vyos.net format octet-counted +set system syslog host syslog.vyos.net port '8000' +set system time-zone 'Europe/Berlin' diff --git a/smoketest/config-tests/bgp-azure-ipsec-gateway b/smoketest/config-tests/bgp-azure-ipsec-gateway new file mode 100644 index 0000000..bbd7b96 --- /dev/null +++ b/smoketest/config-tests/bgp-azure-ipsec-gateway @@ -0,0 +1,231 @@ +set firewall global-options all-ping 'enable' +set firewall global-options broadcast-ping 'disable' +set firewall global-options ip-src-route 'disable' +set firewall global-options ipv6-receive-redirects 'disable' +set firewall global-options ipv6-src-route 'disable' +set firewall global-options log-martians 'disable' +set firewall global-options receive-redirects 'disable' +set firewall global-options send-redirects 'enable' +set firewall global-options source-validation 'disable' +set firewall global-options syn-cookies 'enable' +set firewall global-options twa-hazards-protection 'disable' +set high-availability vrrp group DMZ-VLAN-3962 address 192.168.34.36/27 +set high-availability vrrp group DMZ-VLAN-3962 interface 'eth1' +set high-availability vrrp group DMZ-VLAN-3962 preempt-delay '180' +set high-availability vrrp group DMZ-VLAN-3962 priority '200' +set high-availability vrrp group DMZ-VLAN-3962 vrid '62' +set interfaces ethernet eth0 address '192.0.2.189/27' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 offload gro +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth1 address '192.168.34.37/27' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 offload gro +set interfaces ethernet eth1 speed 'auto' +set interfaces loopback lo +set interfaces vti vti31 ip adjust-mss '1350' +set interfaces vti vti32 ip adjust-mss '1350' +set interfaces vti vti41 ip adjust-mss '1350' +set interfaces vti vti42 ip adjust-mss '1350' +set interfaces vti vti51 ip adjust-mss '1350' +set interfaces vti vti52 ip adjust-mss '1350' +set policy prefix-list AZURE-BGP-IPv4-in description 'Prefixes received from Azure' +set policy prefix-list AZURE-BGP-IPv4-in rule 100 action 'permit' +set policy prefix-list AZURE-BGP-IPv4-in rule 100 le '32' +set policy prefix-list AZURE-BGP-IPv4-in rule 100 prefix '100.64.0.0/10' +set policy prefix-list ONPREM-BGP-IPv4-out description 'Prefixes allowed to be announced into Azure' +set policy prefix-list ONPREM-BGP-IPv4-out rule 100 action 'permit' +set policy prefix-list ONPREM-BGP-IPv4-out rule 100 prefix '10.0.0.0/8' +set policy prefix-list ONPREM-BGP-IPv4-out rule 200 action 'permit' +set policy prefix-list ONPREM-BGP-IPv4-out rule 200 prefix '172.16.0.0/12' +set policy prefix-list ONPREM-BGP-IPv4-out rule 300 action 'permit' +set policy prefix-list ONPREM-BGP-IPv4-out rule 300 prefix '192.168.0.0/16' +set protocols bgp address-family ipv4-unicast network 10.0.0.0/8 +set protocols bgp address-family ipv4-unicast network 172.16.0.0/12 +set protocols bgp address-family ipv4-unicast network 192.168.0.0/16 +set protocols bgp neighbor 100.66.8.36 peer-group 'AZURE' +set protocols bgp neighbor 100.66.8.36 remote-as '64517' +set protocols bgp neighbor 100.66.8.37 peer-group 'AZURE' +set protocols bgp neighbor 100.66.8.37 remote-as '64517' +set protocols bgp neighbor 100.66.24.36 peer-group 'AZURE' +set protocols bgp neighbor 100.66.24.36 remote-as '64513' +set protocols bgp neighbor 100.66.24.37 peer-group 'AZURE' +set protocols bgp neighbor 100.66.24.37 remote-as '64513' +set protocols bgp neighbor 100.66.40.36 peer-group 'AZURE' +set protocols bgp neighbor 100.66.40.36 remote-as '64515' +set protocols bgp neighbor 100.66.40.37 peer-group 'AZURE' +set protocols bgp neighbor 100.66.40.37 remote-as '64515' +set protocols bgp neighbor 192.168.34.38 address-family ipv4-unicast nexthop-self +set protocols bgp neighbor 192.168.34.38 address-family ipv4-unicast soft-reconfiguration inbound +set protocols bgp neighbor 192.168.34.38 capability dynamic +set protocols bgp neighbor 192.168.34.38 password 'VyOSR0xx123' +set protocols bgp neighbor 192.168.34.38 remote-as '65522' +set protocols bgp neighbor 192.168.34.38 update-source 'eth1' +set protocols bgp peer-group AZURE address-family ipv4-unicast maximum-prefix '50' +set protocols bgp peer-group AZURE address-family ipv4-unicast prefix-list export 'ONPREM-BGP-IPv4-out' +set protocols bgp peer-group AZURE address-family ipv4-unicast prefix-list import 'AZURE-BGP-IPv4-in' +set protocols bgp peer-group AZURE ebgp-multihop '2' +set protocols bgp peer-group AZURE update-source 'eth1' +set protocols bgp system-as '65522' +set protocols bgp timers holdtime '30' +set protocols bgp timers keepalive '5' +set protocols static route 0.0.0.0/0 next-hop 192.168.34.33 +set protocols static route 51.105.0.0/16 next-hop 192.0.2.161 +set protocols static route 52.143.0.0/16 next-hop 192.0.2.161 +set protocols static route 100.66.8.36/32 interface vti31 +set protocols static route 100.66.8.36/32 interface vti32 +set protocols static route 100.66.8.37/32 interface vti31 +set protocols static route 100.66.8.37/32 interface vti32 +set protocols static route 100.66.24.36/32 interface vti41 +set protocols static route 100.66.24.36/32 interface vti42 +set protocols static route 100.66.24.37/32 interface vti41 +set protocols static route 100.66.24.37/32 interface vti42 +set protocols static route 100.66.40.36/32 interface vti51 +set protocols static route 100.66.40.36/32 interface vti52 +set protocols static route 100.66.40.37/32 interface vti51 +set protocols static route 100.66.40.37/32 interface vti52 +set protocols static route 195.137.175.0/24 next-hop 192.0.2.161 +set protocols static route 212.23.159.0/26 next-hop 192.0.2.161 +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server 192.0.2.254 +set service snmp v3 engineid 'ff42' +set service snmp v3 group default mode 'ro' +set service snmp v3 group default seclevel 'priv' +set service snmp v3 group default view 'default' +set service snmp v3 user VyOS auth encrypted-password '1ad73f4620b8c0dd2de066622f875b161a14adad' +set service snmp v3 user VyOS auth type 'sha' +set service snmp v3 user VyOS group 'default' +set service snmp v3 user VyOS privacy encrypted-password '1ad73f4620b8c0dd2de066622f875b16' +set service snmp v3 user VyOS privacy type 'aes' +set service snmp v3 view default oid 1 +set service ssh disable-host-validation +set service ssh port '22' +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system domain-name 'vyos.net' +set system flow-accounting interface 'eth1' +set system flow-accounting interface 'vti31' +set system flow-accounting interface 'vti32' +set system flow-accounting interface 'vti41' +set system flow-accounting interface 'vti42' +set system flow-accounting interface 'vti51' +set system flow-accounting interface 'vti52' +set system flow-accounting netflow server 10.0.1.1 port '2055' +set system flow-accounting netflow source-address '192.168.34.37' +set system flow-accounting netflow version '10' +set system flow-accounting syslog-facility 'daemon' +set system host-name 'azure-gw-01' +set system login radius server 192.0.2.253 key 'secret1234' +set system login radius server 192.0.2.253 port '1812' +set system login radius server 192.0.2.253 timeout '2' +set system login radius server 192.0.2.254 key 'secret1234' +set system login radius server 192.0.2.254 port '1812' +set system login radius server 192.0.2.254 timeout '2' +set system login radius source-address '192.168.34.37' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system logs logrotate messages max-size '20' +set system logs logrotate messages rotate '10' +set system name-server '192.0.2.254' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set system syslog host 10.0.9.188 facility all level 'info' +set system syslog host 10.0.9.188 protocol 'udp' +set system time-zone 'Europe/Berlin' +set vpn ipsec authentication psk peer_51-105-0-1 id '51.105.0.1' +set vpn ipsec authentication psk peer_51-105-0-1 id '192.0.2.189' +set vpn ipsec authentication psk peer_51-105-0-1 secret 'averysecretpsktowardsazure' +set vpn ipsec authentication psk peer_51-105-0-2 id '51.105.0.2' +set vpn ipsec authentication psk peer_51-105-0-2 id '192.0.2.189' +set vpn ipsec authentication psk peer_51-105-0-2 secret 'averysecretpsktowardsazure' +set vpn ipsec authentication psk peer_51-105-0-3 id '51.105.0.3' +set vpn ipsec authentication psk peer_51-105-0-3 id '192.0.2.189' +set vpn ipsec authentication psk peer_51-105-0-3 secret 'averysecretpsktowardsazure' +set vpn ipsec authentication psk peer_51-105-0-4 id '51.105.0.4' +set vpn ipsec authentication psk peer_51-105-0-4 id '192.0.2.189' +set vpn ipsec authentication psk peer_51-105-0-4 secret 'averysecretpsktowardsazure' +set vpn ipsec authentication psk peer_51-105-0-5 id '51.105.0.5' +set vpn ipsec authentication psk peer_51-105-0-5 id '192.0.2.189' +set vpn ipsec authentication psk peer_51-105-0-5 secret 'averysecretpsktowardsazure' +set vpn ipsec authentication psk peer_51-105-0-6 id '51.105.0.6' +set vpn ipsec authentication psk peer_51-105-0-6 id '192.0.2.189' +set vpn ipsec authentication psk peer_51-105-0-6 secret 'averysecretpsktowardsazure' +set vpn ipsec esp-group ESP-AZURE lifetime '27000' +set vpn ipsec esp-group ESP-AZURE mode 'tunnel' +set vpn ipsec esp-group ESP-AZURE pfs 'disable' +set vpn ipsec esp-group ESP-AZURE proposal 1 encryption 'aes256' +set vpn ipsec esp-group ESP-AZURE proposal 1 hash 'sha1' +set vpn ipsec ike-group IKE-AZURE close-action 'none' +set vpn ipsec ike-group IKE-AZURE dead-peer-detection action 'restart' +set vpn ipsec ike-group IKE-AZURE dead-peer-detection interval '2' +set vpn ipsec ike-group IKE-AZURE dead-peer-detection timeout '15' +set vpn ipsec ike-group IKE-AZURE key-exchange 'ikev2' +set vpn ipsec ike-group IKE-AZURE lifetime '27000' +set vpn ipsec ike-group IKE-AZURE proposal 1 dh-group '2' +set vpn ipsec ike-group IKE-AZURE proposal 1 encryption 'aes256' +set vpn ipsec ike-group IKE-AZURE proposal 1 hash 'sha1' +set vpn ipsec interface 'eth0' +set vpn ipsec log level '2' +set vpn ipsec log subsystem 'ike' +set vpn ipsec site-to-site peer peer_51-105-0-1 authentication mode 'pre-shared-secret' +set vpn ipsec site-to-site peer peer_51-105-0-1 authentication remote-id '51.105.0.1' +set vpn ipsec site-to-site peer peer_51-105-0-1 connection-type 'respond' +set vpn ipsec site-to-site peer peer_51-105-0-1 default-esp-group 'ESP-AZURE' +set vpn ipsec site-to-site peer peer_51-105-0-1 ike-group 'IKE-AZURE' +set vpn ipsec site-to-site peer peer_51-105-0-1 ikev2-reauth 'inherit' +set vpn ipsec site-to-site peer peer_51-105-0-1 local-address '192.0.2.189' +set vpn ipsec site-to-site peer peer_51-105-0-1 remote-address '51.105.0.1' +set vpn ipsec site-to-site peer peer_51-105-0-1 vti bind 'vti51' +set vpn ipsec site-to-site peer peer_51-105-0-2 authentication mode 'pre-shared-secret' +set vpn ipsec site-to-site peer peer_51-105-0-2 authentication remote-id '51.105.0.2' +set vpn ipsec site-to-site peer peer_51-105-0-2 connection-type 'respond' +set vpn ipsec site-to-site peer peer_51-105-0-2 default-esp-group 'ESP-AZURE' +set vpn ipsec site-to-site peer peer_51-105-0-2 ike-group 'IKE-AZURE' +set vpn ipsec site-to-site peer peer_51-105-0-2 ikev2-reauth 'inherit' +set vpn ipsec site-to-site peer peer_51-105-0-2 local-address '192.0.2.189' +set vpn ipsec site-to-site peer peer_51-105-0-2 remote-address '51.105.0.2' +set vpn ipsec site-to-site peer peer_51-105-0-2 vti bind 'vti52' +set vpn ipsec site-to-site peer peer_51-105-0-3 authentication mode 'pre-shared-secret' +set vpn ipsec site-to-site peer peer_51-105-0-3 authentication remote-id '51.105.0.3' +set vpn ipsec site-to-site peer peer_51-105-0-3 connection-type 'respond' +set vpn ipsec site-to-site peer peer_51-105-0-3 ike-group 'IKE-AZURE' +set vpn ipsec site-to-site peer peer_51-105-0-3 ikev2-reauth 'inherit' +set vpn ipsec site-to-site peer peer_51-105-0-3 local-address '192.0.2.189' +set vpn ipsec site-to-site peer peer_51-105-0-3 remote-address '51.105.0.3' +set vpn ipsec site-to-site peer peer_51-105-0-3 vti bind 'vti32' +set vpn ipsec site-to-site peer peer_51-105-0-3 vti esp-group 'ESP-AZURE' +set vpn ipsec site-to-site peer peer_51-105-0-4 authentication mode 'pre-shared-secret' +set vpn ipsec site-to-site peer peer_51-105-0-4 authentication remote-id '51.105.0.4' +set vpn ipsec site-to-site peer peer_51-105-0-4 connection-type 'respond' +set vpn ipsec site-to-site peer peer_51-105-0-4 ike-group 'IKE-AZURE' +set vpn ipsec site-to-site peer peer_51-105-0-4 ikev2-reauth 'inherit' +set vpn ipsec site-to-site peer peer_51-105-0-4 local-address '192.0.2.189' +set vpn ipsec site-to-site peer peer_51-105-0-4 remote-address '51.105.0.4' +set vpn ipsec site-to-site peer peer_51-105-0-4 vti bind 'vti31' +set vpn ipsec site-to-site peer peer_51-105-0-4 vti esp-group 'ESP-AZURE' +set vpn ipsec site-to-site peer peer_51-105-0-5 authentication mode 'pre-shared-secret' +set vpn ipsec site-to-site peer peer_51-105-0-5 authentication remote-id '51.105.0.5' +set vpn ipsec site-to-site peer peer_51-105-0-5 connection-type 'respond' +set vpn ipsec site-to-site peer peer_51-105-0-5 ike-group 'IKE-AZURE' +set vpn ipsec site-to-site peer peer_51-105-0-5 ikev2-reauth 'inherit' +set vpn ipsec site-to-site peer peer_51-105-0-5 local-address '192.0.2.189' +set vpn ipsec site-to-site peer peer_51-105-0-5 remote-address '51.105.0.5' +set vpn ipsec site-to-site peer peer_51-105-0-5 vti bind 'vti42' +set vpn ipsec site-to-site peer peer_51-105-0-5 vti esp-group 'ESP-AZURE' +set vpn ipsec site-to-site peer peer_51-105-0-6 authentication mode 'pre-shared-secret' +set vpn ipsec site-to-site peer peer_51-105-0-6 authentication remote-id '51.105.0.6' +set vpn ipsec site-to-site peer peer_51-105-0-6 connection-type 'respond' +set vpn ipsec site-to-site peer peer_51-105-0-6 ike-group 'IKE-AZURE' +set vpn ipsec site-to-site peer peer_51-105-0-6 ikev2-reauth 'inherit' +set vpn ipsec site-to-site peer peer_51-105-0-6 local-address '192.0.2.189' +set vpn ipsec site-to-site peer peer_51-105-0-6 remote-address '51.105.0.6' +set vpn ipsec site-to-site peer peer_51-105-0-6 vti bind 'vti41' +set vpn ipsec site-to-site peer peer_51-105-0-6 vti esp-group 'ESP-AZURE' diff --git a/smoketest/config-tests/bgp-bfd-communities b/smoketest/config-tests/bgp-bfd-communities new file mode 100644 index 0000000..6eee013 --- /dev/null +++ b/smoketest/config-tests/bgp-bfd-communities @@ -0,0 +1,201 @@ +set interfaces ethernet eth0 address '192.0.2.100/25' +set interfaces ethernet eth0 address '2001:db8::ffff/64' +set interfaces ethernet eth0 offload gro +set interfaces loopback lo +set policy large-community-list ANYCAST_ALL rule 10 action 'permit' +set policy large-community-list ANYCAST_ALL rule 10 description 'Allow all anycast from anywhere' +set policy large-community-list ANYCAST_ALL rule 10 regex '4242420696:100:.*' +set policy large-community-list ANYCAST_INT rule 10 action 'permit' +set policy large-community-list ANYCAST_INT rule 10 description 'Allow all anycast from int' +set policy large-community-list ANYCAST_INT rule 10 regex '4242420696:100:1' +set policy prefix-list BGP-BACKBONE-IN description 'Inbound backbone routes from other sites' +set policy prefix-list BGP-BACKBONE-IN rule 10 action 'deny' +set policy prefix-list BGP-BACKBONE-IN rule 10 description 'Block default route' +set policy prefix-list BGP-BACKBONE-IN rule 10 prefix '0.0.0.0/0' +set policy prefix-list BGP-BACKBONE-IN rule 20 action 'deny' +set policy prefix-list BGP-BACKBONE-IN rule 20 description 'Block int primary' +set policy prefix-list BGP-BACKBONE-IN rule 20 ge '21' +set policy prefix-list BGP-BACKBONE-IN rule 20 prefix '192.168.0.0/20' +set policy prefix-list BGP-BACKBONE-IN rule 30 action 'deny' +set policy prefix-list BGP-BACKBONE-IN rule 30 description 'Block loopbacks' +set policy prefix-list BGP-BACKBONE-IN rule 30 ge '25' +set policy prefix-list BGP-BACKBONE-IN rule 30 prefix '192.168.253.0/24' +set policy prefix-list BGP-BACKBONE-IN rule 40 action 'deny' +set policy prefix-list BGP-BACKBONE-IN rule 40 description 'Block backbone peering' +set policy prefix-list BGP-BACKBONE-IN rule 40 ge '25' +set policy prefix-list BGP-BACKBONE-IN rule 40 prefix '192.168.254.0/24' +set policy prefix-list BGP-BACKBONE-IN rule 999 action 'permit' +set policy prefix-list BGP-BACKBONE-IN rule 999 description 'Allow everything else' +set policy prefix-list BGP-BACKBONE-IN rule 999 ge '1' +set policy prefix-list BGP-BACKBONE-IN rule 999 prefix '0.0.0.0/0' +set policy prefix-list BGP-BACKBONE-OUT description 'Outbound backbone routes to other sites' +set policy prefix-list BGP-BACKBONE-OUT rule 10 action 'permit' +set policy prefix-list BGP-BACKBONE-OUT rule 10 description 'Int primary' +set policy prefix-list BGP-BACKBONE-OUT rule 10 ge '23' +set policy prefix-list BGP-BACKBONE-OUT rule 10 prefix '192.168.0.0/20' +set policy prefix-list GLOBAL description 'Globally redistributed routes' +set policy prefix-list GLOBAL rule 10 action 'permit' +set policy prefix-list GLOBAL rule 10 prefix '192.168.100.1/32' +set policy prefix-list GLOBAL rule 20 action 'permit' +set policy prefix-list GLOBAL rule 20 prefix '192.168.7.128/25' +set policy prefix-list6 BGP-BACKBONE-IN-V6 description 'Inbound backbone routes from other sites' +set policy prefix-list6 BGP-BACKBONE-IN-V6 rule 10 action 'deny' +set policy prefix-list6 BGP-BACKBONE-IN-V6 rule 10 description 'Block default route' +set policy prefix-list6 BGP-BACKBONE-IN-V6 rule 10 prefix '::/0' +set policy prefix-list6 BGP-BACKBONE-IN-V6 rule 20 action 'deny' +set policy prefix-list6 BGP-BACKBONE-IN-V6 rule 20 description 'Block int primary' +set policy prefix-list6 BGP-BACKBONE-IN-V6 rule 20 ge '53' +set policy prefix-list6 BGP-BACKBONE-IN-V6 rule 20 prefix 'fd52:d62e:8011::/52' +set policy prefix-list6 BGP-BACKBONE-IN-V6 rule 30 action 'deny' +set policy prefix-list6 BGP-BACKBONE-IN-V6 rule 30 description 'Block peering and stuff' +set policy prefix-list6 BGP-BACKBONE-IN-V6 rule 30 ge '53' +set policy prefix-list6 BGP-BACKBONE-IN-V6 rule 30 prefix 'fd52:d62e:8011:f000::/52' +set policy prefix-list6 BGP-BACKBONE-IN-V6 rule 999 action 'permit' +set policy prefix-list6 BGP-BACKBONE-IN-V6 rule 999 description 'Allow everything else' +set policy prefix-list6 BGP-BACKBONE-IN-V6 rule 999 ge '1' +set policy prefix-list6 BGP-BACKBONE-IN-V6 rule 999 prefix '::/0' +set policy prefix-list6 BGP-BACKBONE-OUT-V6 description 'Outbound backbone routes to other sites' +set policy prefix-list6 BGP-BACKBONE-OUT-V6 rule 10 action 'permit' +set policy prefix-list6 BGP-BACKBONE-OUT-V6 rule 10 ge '64' +set policy prefix-list6 BGP-BACKBONE-OUT-V6 rule 10 prefix 'fd52:d62e:8011::/52' +set policy prefix-list6 GLOBAL-V6 description 'Globally redistributed routes' +set policy prefix-list6 GLOBAL-V6 rule 10 action 'permit' +set policy prefix-list6 GLOBAL-V6 rule 10 ge '64' +set policy prefix-list6 GLOBAL-V6 rule 10 prefix 'fd52:d62e:8011:2::/63' +set policy route-map BGP-BACKBONE-IN rule 10 action 'permit' +set policy route-map BGP-BACKBONE-IN rule 10 match ip address prefix-list 'BGP-BACKBONE-IN' +set policy route-map BGP-BACKBONE-IN rule 20 action 'permit' +set policy route-map BGP-BACKBONE-IN rule 20 match ipv6 address prefix-list 'BGP-BACKBONE-IN-V6' +set policy route-map BGP-BACKBONE-IN rule 30 action 'permit' +set policy route-map BGP-BACKBONE-IN rule 30 match large-community large-community-list 'ANYCAST_ALL' +set policy route-map BGP-BACKBONE-OUT rule 10 action 'permit' +set policy route-map BGP-BACKBONE-OUT rule 10 match ip address prefix-list 'BGP-BACKBONE-OUT' +set policy route-map BGP-BACKBONE-OUT rule 20 action 'permit' +set policy route-map BGP-BACKBONE-OUT rule 20 match ipv6 address prefix-list 'BGP-BACKBONE-OUT-V6' +set policy route-map BGP-BACKBONE-OUT rule 30 action 'permit' +set policy route-map BGP-BACKBONE-OUT rule 30 match large-community large-community-list 'ANYCAST_INT' +set policy route-map BGP-BACKBONE-OUT rule 30 set as-path prepend '4242420666' +set policy route-map BGP-REDISTRIBUTE rule 10 action 'permit' +set policy route-map BGP-REDISTRIBUTE rule 10 description 'Prepend AS and allow VPN and modem' +set policy route-map BGP-REDISTRIBUTE rule 10 match ip address prefix-list 'GLOBAL' +set policy route-map BGP-REDISTRIBUTE rule 10 set as-path prepend '4242420666' +set policy route-map BGP-REDISTRIBUTE rule 20 action 'permit' +set policy route-map BGP-REDISTRIBUTE rule 20 description 'Allow VPN' +set policy route-map BGP-REDISTRIBUTE rule 20 match ipv6 address prefix-list 'GLOBAL-V6' +set protocols bfd peer 192.168.253.1 interval receive '50' +set protocols bfd peer 192.168.253.1 interval transmit '50' +set protocols bfd peer 192.168.253.1 multihop +set protocols bfd peer 192.168.253.1 source address '192.168.253.3' +set protocols bfd peer 192.168.253.2 interval receive '50' +set protocols bfd peer 192.168.253.2 interval transmit '50' +set protocols bfd peer 192.168.253.2 multihop +set protocols bfd peer 192.168.253.2 source address '192.168.253.3' +set protocols bfd peer 192.168.253.6 interval receive '50' +set protocols bfd peer 192.168.253.6 interval transmit '50' +set protocols bfd peer 192.168.253.6 multihop +set protocols bfd peer 192.168.253.6 source address '192.168.253.3' +set protocols bfd peer 192.168.253.7 interval receive '50' +set protocols bfd peer 192.168.253.7 interval transmit '50' +set protocols bfd peer 192.168.253.7 multihop +set protocols bfd peer 192.168.253.7 source address '192.168.253.3' +set protocols bfd peer 192.168.253.12 interval receive '100' +set protocols bfd peer 192.168.253.12 interval transmit '100' +set protocols bfd peer 192.168.253.12 multihop +set protocols bfd peer 192.168.253.12 source address '192.168.253.3' +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:1 interval receive '50' +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:1 interval transmit '50' +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:1 multihop +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:1 source address 'fd52:d62e:8011:fffe:192:168:253:3' +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:2 interval receive '50' +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:2 interval transmit '50' +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:2 multihop +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:2 source address 'fd52:d62e:8011:fffe:192:168:253:3' +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:6 interval receive '50' +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:6 interval transmit '50' +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:6 multihop +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:6 source address 'fd52:d62e:8011:fffe:192:168:253:3' +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:7 interval receive '50' +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:7 interval transmit '50' +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:7 multihop +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:7 source address 'fd52:d62e:8011:fffe:192:168:253:3' +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:12 interval receive '100' +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:12 interval transmit '100' +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:12 multihop +set protocols bfd peer fd52:d62e:8011:fffe:192:168:253:12 source address 'fd52:d62e:8011:fffe:192:168:253:3' +set protocols bgp address-family ipv4-unicast redistribute connected route-map 'BGP-REDISTRIBUTE' +set protocols bgp address-family ipv4-unicast redistribute static route-map 'BGP-REDISTRIBUTE' +set protocols bgp address-family ipv6-unicast redistribute connected route-map 'BGP-REDISTRIBUTE' +set protocols bgp neighbor 192.168.253.1 peer-group 'INT' +set protocols bgp neighbor 192.168.253.2 peer-group 'INT' +set protocols bgp neighbor 192.168.253.6 peer-group 'DAL13' +set protocols bgp neighbor 192.168.253.7 peer-group 'DAL13' +set protocols bgp neighbor 192.168.253.12 address-family ipv4-unicast route-map export 'BGP-BACKBONE-OUT' +set protocols bgp neighbor 192.168.253.12 address-family ipv4-unicast route-map import 'BGP-BACKBONE-IN' +set protocols bgp neighbor 192.168.253.12 address-family ipv4-unicast soft-reconfiguration inbound +set protocols bgp neighbor 192.168.253.12 bfd +set protocols bgp neighbor 192.168.253.12 ebgp-multihop '2' +set protocols bgp neighbor 192.168.253.12 remote-as '4242420669' +set protocols bgp neighbor 192.168.253.12 update-source 'dum0' +set protocols bgp neighbor fd52:d62e:8011:fffe:192:168:253:1 peer-group 'INTv6' +set protocols bgp neighbor fd52:d62e:8011:fffe:192:168:253:2 peer-group 'INTv6' +set protocols bgp neighbor fd52:d62e:8011:fffe:192:168:253:6 peer-group 'DAL13v6' +set protocols bgp neighbor fd52:d62e:8011:fffe:192:168:253:7 peer-group 'DAL13v6' +set protocols bgp neighbor fd52:d62e:8011:fffe:192:168:253:12 address-family ipv6-unicast route-map export 'BGP-BACKBONE-OUT' +set protocols bgp neighbor fd52:d62e:8011:fffe:192:168:253:12 address-family ipv6-unicast route-map import 'BGP-BACKBONE-IN' +set protocols bgp neighbor fd52:d62e:8011:fffe:192:168:253:12 address-family ipv6-unicast soft-reconfiguration inbound +set protocols bgp neighbor fd52:d62e:8011:fffe:192:168:253:12 bfd +set protocols bgp neighbor fd52:d62e:8011:fffe:192:168:253:12 ebgp-multihop '2' +set protocols bgp neighbor fd52:d62e:8011:fffe:192:168:253:12 remote-as '4242420669' +set protocols bgp neighbor fd52:d62e:8011:fffe:192:168:253:12 update-source 'dum0' +set protocols bgp parameters confederation identifier '4242420696' +set protocols bgp parameters confederation peers '4242420668' +set protocols bgp parameters confederation peers '4242420669' +set protocols bgp parameters distance global external '220' +set protocols bgp parameters distance global internal '220' +set protocols bgp parameters distance global local '220' +set protocols bgp parameters graceful-restart +set protocols bgp peer-group DAL13 address-family ipv4-unicast route-map export 'BGP-BACKBONE-OUT' +set protocols bgp peer-group DAL13 address-family ipv4-unicast route-map import 'BGP-BACKBONE-IN' +set protocols bgp peer-group DAL13 address-family ipv4-unicast soft-reconfiguration inbound +set protocols bgp peer-group DAL13 bfd +set protocols bgp peer-group DAL13 ebgp-multihop '2' +set protocols bgp peer-group DAL13 remote-as '4242420668' +set protocols bgp peer-group DAL13 update-source 'dum0' +set protocols bgp peer-group DAL13v6 address-family ipv6-unicast route-map export 'BGP-BACKBONE-OUT' +set protocols bgp peer-group DAL13v6 address-family ipv6-unicast route-map import 'BGP-BACKBONE-IN' +set protocols bgp peer-group DAL13v6 address-family ipv6-unicast soft-reconfiguration inbound +set protocols bgp peer-group DAL13v6 bfd +set protocols bgp peer-group DAL13v6 ebgp-multihop '2' +set protocols bgp peer-group DAL13v6 remote-as '4242420668' +set protocols bgp peer-group DAL13v6 update-source 'dum0' +set protocols bgp peer-group INT address-family ipv4-unicast default-originate +set protocols bgp peer-group INT address-family ipv4-unicast soft-reconfiguration inbound +set protocols bgp peer-group INT bfd +set protocols bgp peer-group INT remote-as '4242420666' +set protocols bgp peer-group INT update-source 'dum0' +set protocols bgp peer-group INTv6 address-family ipv6-unicast default-originate +set protocols bgp peer-group INTv6 address-family ipv6-unicast soft-reconfiguration inbound +set protocols bgp peer-group INTv6 bfd +set protocols bgp peer-group INTv6 remote-as '4242420666' +set protocols bgp peer-group INTv6 update-source 'dum0' +set protocols bgp system-as '4242420666' +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server 0.pool.ntp.org +set service ntp server 1.pool.ntp.org +set service ntp server 2.pool.ntp.org +set system config-management commit-revisions '200' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set system time-zone 'Europe/Berlin' diff --git a/smoketest/config-tests/bgp-big-as-cloud b/smoketest/config-tests/bgp-big-as-cloud new file mode 100644 index 0000000..8de0cdb --- /dev/null +++ b/smoketest/config-tests/bgp-big-as-cloud @@ -0,0 +1,850 @@ +set firewall global-options all-ping 'enable' +set firewall global-options broadcast-ping 'disable' +set firewall global-options ip-src-route 'disable' +set firewall global-options ipv6-receive-redirects 'disable' +set firewall global-options ipv6-src-route 'disable' +set firewall global-options log-martians 'enable' +set firewall global-options receive-redirects 'disable' +set firewall global-options send-redirects 'enable' +set firewall global-options source-validation 'disable' +set firewall global-options syn-cookies 'enable' +set firewall global-options twa-hazards-protection 'disable' +set firewall group address-group bgp-peers-4 address '192.0.68.3' +set firewall group address-group bgp-peers-4 address '192.0.68.2' +set firewall group address-group bgp-peers-4 address '192.0.176.193' +set firewall group address-group bgp-peers-4 address '192.0.52.0-192.0.52.255' +set firewall group address-group bgp-peers-4 address '192.0.53.0-192.0.53.255' +set firewall group address-group bgp-peers-4 address '192.0.16.209' +set firewall group address-group bgp-peers-4 address '192.0.192.0-192.0.192.255' +set firewall group address-group bgp-peers-4 address '192.0.193.0-192.0.193.255' +set firewall group address-group bgp-peers-4 address '192.0.194.0-192.0.194.255' +set firewall group address-group bgp-peers-4 address '192.0.195.0-192.0.195.255' +set firewall group address-group bgp-peers-4 address '192.0.196.0-192.0.196.255' +set firewall group address-group bgp-peers-4 address '192.0.197.0-192.0.197.255' +set firewall group address-group bgp-peers-4 address '192.0.198.0-192.0.198.255' +set firewall group address-group bgp-peers-4 address '192.0.199.0-192.0.199.255' +set firewall group address-group vrrp-peers-4 address '192.0.68.3' +set firewall group address-group vrrp-peers-4 address '192.0.160.3' +set firewall group address-group vrrp-peers-4 address '192.0.98.3' +set firewall group address-group vrrp-peers-4 address '192.0.71.131' +set firewall group address-group vrrp-peers-4 address '192.0.84.67' +set firewall group address-group vrrp-peers-4 address '192.0.71.195' +set firewall group address-group vrrp-peers-4 address '192.0.71.115' +set firewall group address-group vrrp-peers-4 address '192.0.70.195' +set firewall group address-group vrrp-peers-4 address '192.0.70.179' +set firewall group address-group vrrp-peers-4 address '192.0.70.163' +set firewall group address-group vrrp-peers-4 address '192.0.70.147' +set firewall group address-group vrrp-peers-4 address '192.0.70.131' +set firewall group address-group vrrp-peers-4 address '192.0.70.19' +set firewall group address-group vrrp-peers-4 address '192.0.70.3' +set firewall group address-group vrrp-peers-4 address '192.0.71.99' +set firewall group address-group vrrp-peers-4 address '192.0.68.67' +set firewall group address-group vrrp-peers-4 address '192.0.71.67' +set firewall group address-group vrrp-peers-4 address '192.0.71.3' +set firewall group address-group vrrp-peers-4 address '192.0.68.35' +set firewall group address-group vrrp-peers-4 address '192.0.68.131' +set firewall group address-group vrrp-peers-4 address '192.0.69.2' +set firewall group address-group vrrp-peers-4 address '192.0.70.35' +set firewall group address-group vrrp-peers-4 address '192.0.70.67' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:c::3' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:1000::2e9' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::fb' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::fc' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::fd' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::2e' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::3d' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::4a' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::5e' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::7' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::11' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::18' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::20' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::22' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::31' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::58' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::64' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::a5' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::aa' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::ab' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::b0' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::b3' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::bd' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::c' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::d2' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:24::d3' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8:838::1' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8::1a27:5051:c09d' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8::1a27:5051:c19d' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8::20ad:0:1' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8::2306:0:1' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8::2ca:0:1' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8::2ca:0:2' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8::2ca:0:3' +set firewall group ipv6-address-group bgp-peers-6 address '2001:db8::2ca:0:4' +set firewall group ipv6-address-group vrrp-peers-6 address 'fe80::fe89:15cf' +set firewall group ipv6-network-group AS64512-6 network '2001::/29' +set firewall group network-group AS64512-4 network '192.0.68.0/22' +set firewall group network-group AS64512-4 network '192.0.98.0/24' +set firewall group network-group AS64512-4 network '192.0.160.0/24' +set firewall group network-group AS64512-4 network '192.0.84.0/22' +set firewall ipv4 name management-to-local-4 default-action 'reject' +set firewall ipv4 name management-to-local-4 default-log +set firewall ipv4 name management-to-local-4 rule 500 action 'return' +set firewall ipv4 name management-to-local-4 rule 500 protocol 'icmp' +set firewall ipv4 name management-to-local-4 rule 501 action 'return' +set firewall ipv4 name management-to-local-4 rule 501 destination port '22' +set firewall ipv4 name management-to-local-4 rule 501 protocol 'tcp' +set firewall ipv4 name management-to-local-4 rule 502 action 'return' +set firewall ipv4 name management-to-local-4 rule 502 destination port 'snmp' +set firewall ipv4 name management-to-local-4 rule 502 protocol 'udp' +set firewall ipv4 name management-to-peers-4 default-action 'reject' +set firewall ipv4 name management-to-peers-4 default-log +set firewall ipv4 name management-to-servers-4 default-action 'reject' +set firewall ipv4 name management-to-servers-4 default-log +set firewall ipv4 name peers-to-local-4 default-action 'reject' +set firewall ipv4 name peers-to-local-4 default-log +set firewall ipv4 name peers-to-local-4 rule 500 action 'return' +set firewall ipv4 name peers-to-local-4 rule 500 protocol 'icmp' +set firewall ipv4 name peers-to-local-4 rule 501 action 'return' +set firewall ipv4 name peers-to-local-4 rule 501 protocol 'vrrp' +set firewall ipv4 name peers-to-local-4 rule 501 source group address-group 'vrrp-peers-4' +set firewall ipv4 name peers-to-local-4 rule 502 action 'return' +set firewall ipv4 name peers-to-local-4 rule 502 destination port 'bgp' +set firewall ipv4 name peers-to-local-4 rule 502 protocol 'tcp' +set firewall ipv4 name peers-to-local-4 rule 502 source group address-group 'bgp-peers-4' +set firewall ipv4 name peers-to-local-4 rule 503 action 'return' +set firewall ipv4 name peers-to-local-4 rule 503 protocol 'tcp' +set firewall ipv4 name peers-to-local-4 rule 503 source group address-group 'bgp-peers-4' +set firewall ipv4 name peers-to-local-4 rule 503 source port 'bgp' +set firewall ipv4 name peers-to-management-4 default-action 'reject' +set firewall ipv4 name peers-to-management-4 default-log +set firewall ipv4 name peers-to-servers-4 default-action 'reject' +set firewall ipv4 name peers-to-servers-4 default-log +set firewall ipv4 name peers-to-servers-4 rule 9990 action 'reject' +set firewall ipv4 name peers-to-servers-4 rule 9990 source group network-group 'AS64512-4' +set firewall ipv4 name peers-to-servers-4 rule 9999 action 'return' +set firewall ipv4 name peers-to-servers-4 rule 9999 destination group network-group 'AS64512-4' +set firewall ipv4 name servers-to-local-4 default-action 'reject' +set firewall ipv4 name servers-to-local-4 default-log +set firewall ipv4 name servers-to-local-4 rule 500 action 'return' +set firewall ipv4 name servers-to-local-4 rule 500 protocol 'icmp' +set firewall ipv4 name servers-to-local-4 rule 501 action 'return' +set firewall ipv4 name servers-to-local-4 rule 501 protocol 'vrrp' +set firewall ipv4 name servers-to-local-4 rule 501 source group address-group 'vrrp-peers-4' +set firewall ipv4 name servers-to-local-4 rule 511 action 'return' +set firewall ipv4 name servers-to-local-4 rule 511 protocol 'tcp_udp' +set firewall ipv4 name servers-to-local-4 rule 511 source port '53' +set firewall ipv4 name servers-to-management-4 default-action 'reject' +set firewall ipv4 name servers-to-management-4 default-log +set firewall ipv4 name servers-to-peers-4 default-action 'reject' +set firewall ipv4 name servers-to-peers-4 default-log +set firewall ipv4 name servers-to-peers-4 rule 51 action 'return' +set firewall ipv4 name servers-to-peers-4 rule 51 source group network-group 'AS64512-4' +set firewall ipv6 name management-to-local-6 default-action 'reject' +set firewall ipv6 name management-to-local-6 default-log +set firewall ipv6 name management-to-peers-6 default-action 'reject' +set firewall ipv6 name management-to-peers-6 default-log +set firewall ipv6 name management-to-servers-6 default-action 'reject' +set firewall ipv6 name management-to-servers-6 default-log +set firewall ipv6 name peers-to-local-6 default-action 'reject' +set firewall ipv6 name peers-to-local-6 default-log +set firewall ipv6 name peers-to-local-6 rule 500 action 'return' +set firewall ipv6 name peers-to-local-6 rule 500 protocol 'ipv6-icmp' +set firewall ipv6 name peers-to-local-6 rule 501 action 'return' +set firewall ipv6 name peers-to-local-6 rule 501 protocol 'vrrp' +set firewall ipv6 name peers-to-local-6 rule 501 source group address-group 'vrrp-peers-6' +set firewall ipv6 name peers-to-local-6 rule 502 action 'return' +set firewall ipv6 name peers-to-local-6 rule 502 destination port 'bgp' +set firewall ipv6 name peers-to-local-6 rule 502 protocol 'tcp' +set firewall ipv6 name peers-to-local-6 rule 502 source group address-group 'bgp-peers-6' +set firewall ipv6 name peers-to-local-6 rule 503 action 'return' +set firewall ipv6 name peers-to-local-6 rule 503 protocol 'tcp' +set firewall ipv6 name peers-to-local-6 rule 503 source group address-group 'bgp-peers-6' +set firewall ipv6 name peers-to-local-6 rule 503 source port 'bgp' +set firewall ipv6 name peers-to-management-6 default-action 'reject' +set firewall ipv6 name peers-to-management-6 default-log +set firewall ipv6 name peers-to-servers-6 default-action 'reject' +set firewall ipv6 name peers-to-servers-6 default-log +set firewall ipv6 name peers-to-servers-6 rule 9990 action 'reject' +set firewall ipv6 name peers-to-servers-6 rule 9990 source group network-group 'AS64512-6' +set firewall ipv6 name peers-to-servers-6 rule 9999 action 'return' +set firewall ipv6 name peers-to-servers-6 rule 9999 destination group network-group 'AS64512-6' +set firewall ipv6 name servers-to-local-6 default-action 'reject' +set firewall ipv6 name servers-to-local-6 default-log +set firewall ipv6 name servers-to-local-6 rule 500 action 'return' +set firewall ipv6 name servers-to-local-6 rule 500 protocol 'ipv6-icmp' +set firewall ipv6 name servers-to-local-6 rule 501 action 'return' +set firewall ipv6 name servers-to-local-6 rule 501 protocol 'vrrp' +set firewall ipv6 name servers-to-local-6 rule 501 source group address-group 'vrrp-peers-6' +set firewall ipv6 name servers-to-local-6 rule 511 action 'return' +set firewall ipv6 name servers-to-local-6 rule 511 protocol 'tcp_udp' +set firewall ipv6 name servers-to-local-6 rule 511 source port '53' +set firewall ipv6 name servers-to-management-6 default-action 'reject' +set firewall ipv6 name servers-to-management-6 default-log +set firewall ipv6 name servers-to-peers-6 default-action 'reject' +set firewall ipv6 name servers-to-peers-6 default-log +set firewall ipv6 name servers-to-peers-6 rule 51 action 'return' +set firewall ipv6 name servers-to-peers-6 rule 51 source group network-group 'AS64512-6' +set firewall zone local default-action 'drop' +set firewall zone local from management firewall ipv6-name 'management-to-local-6' +set firewall zone local from management firewall name 'management-to-local-4' +set firewall zone local from peers firewall ipv6-name 'peers-to-local-6' +set firewall zone local from peers firewall name 'peers-to-local-4' +set firewall zone local from servers firewall ipv6-name 'servers-to-local-6' +set firewall zone local from servers firewall name 'servers-to-local-4' +set firewall zone local local-zone +set firewall zone management default-action 'reject' +set firewall zone management from peers firewall ipv6-name 'peers-to-management-6' +set firewall zone management from peers firewall name 'peers-to-management-4' +set firewall zone management from servers firewall ipv6-name 'servers-to-management-6' +set firewall zone management from servers firewall name 'servers-to-management-4' +set firewall zone management interface 'eth0' +set firewall zone peers default-action 'reject' +set firewall zone peers from management firewall ipv6-name 'management-to-peers-6' +set firewall zone peers from management firewall name 'management-to-peers-4' +set firewall zone peers from servers firewall ipv6-name 'servers-to-peers-6' +set firewall zone peers from servers firewall name 'servers-to-peers-4' +set firewall zone peers interface 'eth0.4088' +set firewall zone peers interface 'eth0.4089' +set firewall zone peers interface 'eth0.11' +set firewall zone peers interface 'eth0.838' +set firewall zone peers interface 'eth0.886' +set firewall zone servers default-action 'reject' +set firewall zone servers from management firewall ipv6-name 'management-to-servers-6' +set firewall zone servers from management firewall name 'management-to-servers-4' +set firewall zone servers from peers firewall ipv6-name 'peers-to-servers-6' +set firewall zone servers from peers firewall name 'peers-to-servers-4' +set firewall zone servers interface 'eth0.1001' +set firewall zone servers interface 'eth0.105' +set firewall zone servers interface 'eth0.102' +set firewall zone servers interface 'eth0.1019' +set firewall zone servers interface 'eth0.1014' +set firewall zone servers interface 'eth0.1020' +set firewall zone servers interface 'eth0.1018' +set firewall zone servers interface 'eth0.1013' +set firewall zone servers interface 'eth0.1012' +set firewall zone servers interface 'eth0.1011' +set firewall zone servers interface 'eth0.1010' +set firewall zone servers interface 'eth0.1009' +set firewall zone servers interface 'eth0.1006' +set firewall zone servers interface 'eth0.1005' +set firewall zone servers interface 'eth0.1017' +set firewall zone servers interface 'eth0.1016' +set firewall zone servers interface 'eth0.1002' +set firewall zone servers interface 'eth0.1015' +set firewall zone servers interface 'eth0.1003' +set firewall zone servers interface 'eth0.1004' +set firewall zone servers interface 'eth0.1007' +set firewall zone servers interface 'eth0.1008' +set high-availability vrrp group 11-4 address 192.0.68.1/27 +set high-availability vrrp group 11-4 interface 'eth0.11' +set high-availability vrrp group 11-4 priority '200' +set high-availability vrrp group 11-4 vrid '4' +set high-availability vrrp group 11-6 address 2001:db8:c::1/64 +set high-availability vrrp group 11-6 interface 'eth0.11' +set high-availability vrrp group 11-6 priority '200' +set high-availability vrrp group 11-6 vrid '6' +set high-availability vrrp group 102-4 address 192.0.98.1/24 +set high-availability vrrp group 102-4 interface 'eth0.102' +set high-availability vrrp group 102-4 priority '200' +set high-availability vrrp group 102-4 vrid '4' +set high-availability vrrp group 102-6 address 2001:db8:0:102::1/64 +set high-availability vrrp group 102-6 interface 'eth0.102' +set high-availability vrrp group 102-6 priority '200' +set high-availability vrrp group 102-6 vrid '6' +set high-availability vrrp group 105-4 address 192.0.160.1/24 +set high-availability vrrp group 105-4 interface 'eth0.105' +set high-availability vrrp group 105-4 priority '200' +set high-availability vrrp group 105-4 vrid '4' +set high-availability vrrp group 105-6 address 2001:db8:0:105::1/64 +set high-availability vrrp group 105-6 interface 'eth0.105' +set high-availability vrrp group 105-6 priority '200' +set high-availability vrrp group 105-6 vrid '6' +set high-availability vrrp group 1001-4 address 192.0.68.33/27 +set high-availability vrrp group 1001-4 interface 'eth0.1001' +set high-availability vrrp group 1001-4 priority '200' +set high-availability vrrp group 1001-4 vrid '4' +set high-availability vrrp group 1001-6 address 2001:db8:0:1001::1/64 +set high-availability vrrp group 1001-6 interface 'eth0.1001' +set high-availability vrrp group 1001-6 priority '200' +set high-availability vrrp group 1001-6 vrid '6' +set high-availability vrrp group 1002-4 address 192.0.68.65/26 +set high-availability vrrp group 1002-4 interface 'eth0.1002' +set high-availability vrrp group 1002-4 priority '200' +set high-availability vrrp group 1002-4 vrid '4' +set high-availability vrrp group 1002-6 address 2001:db8:0:1002::1/64 +set high-availability vrrp group 1002-6 interface 'eth0.1002' +set high-availability vrrp group 1002-6 priority '200' +set high-availability vrrp group 1002-6 vrid '6' +set high-availability vrrp group 1003-4 address 192.0.68.129/25 +set high-availability vrrp group 1003-4 interface 'eth0.1003' +set high-availability vrrp group 1003-4 priority '200' +set high-availability vrrp group 1003-4 vrid '4' +set high-availability vrrp group 1003-6 address 2001:db8:0:1003::1/64 +set high-availability vrrp group 1003-6 interface 'eth0.1003' +set high-availability vrrp group 1003-6 priority '200' +set high-availability vrrp group 1003-6 vrid '6' +set high-availability vrrp group 1004-4 address 192.0.69.1/24 +set high-availability vrrp group 1004-4 interface 'eth0.1004' +set high-availability vrrp group 1004-4 priority '200' +set high-availability vrrp group 1004-4 vrid '4' +set high-availability vrrp group 1004-6 address 2001:db8:0:1004::1/64 +set high-availability vrrp group 1004-6 interface 'eth0.1004' +set high-availability vrrp group 1004-6 priority '200' +set high-availability vrrp group 1004-6 vrid '6' +set high-availability vrrp group 1005-4 address 192.0.70.1/28 +set high-availability vrrp group 1005-4 interface 'eth0.1005' +set high-availability vrrp group 1005-4 priority '200' +set high-availability vrrp group 1005-4 vrid '4' +set high-availability vrrp group 1005-6 address 2001:db8:0:1005::1/64 +set high-availability vrrp group 1005-6 interface 'eth0.1005' +set high-availability vrrp group 1005-6 priority '200' +set high-availability vrrp group 1005-6 vrid '6' +set high-availability vrrp group 1006-4 address 192.0.70.17/28 +set high-availability vrrp group 1006-4 interface 'eth0.1006' +set high-availability vrrp group 1006-4 priority '200' +set high-availability vrrp group 1006-4 vrid '4' +set high-availability vrrp group 1006-6 address 2001:db8:0:1006::1/64 +set high-availability vrrp group 1006-6 interface 'eth0.1006' +set high-availability vrrp group 1006-6 priority '200' +set high-availability vrrp group 1006-6 vrid '6' +set high-availability vrrp group 1007-4 address 192.0.70.33/27 +set high-availability vrrp group 1007-4 interface 'eth0.1007' +set high-availability vrrp group 1007-4 priority '200' +set high-availability vrrp group 1007-4 vrid '4' +set high-availability vrrp group 1007-6 address 2001:db8:0:1007::1/64 +set high-availability vrrp group 1007-6 interface 'eth0.1007' +set high-availability vrrp group 1007-6 priority '200' +set high-availability vrrp group 1007-6 vrid '6' +set high-availability vrrp group 1008-4 address 192.0.70.65/26 +set high-availability vrrp group 1008-4 interface 'eth0.1008' +set high-availability vrrp group 1008-4 priority '200' +set high-availability vrrp group 1008-4 vrid '4' +set high-availability vrrp group 1008-6 address 2001:db8:0:1008::1/64 +set high-availability vrrp group 1008-6 interface 'eth0.1008' +set high-availability vrrp group 1008-6 priority '200' +set high-availability vrrp group 1008-6 vrid '6' +set high-availability vrrp group 1009-4 address 192.0.70.129/28 +set high-availability vrrp group 1009-4 interface 'eth0.1009' +set high-availability vrrp group 1009-4 priority '200' +set high-availability vrrp group 1009-4 vrid '4' +set high-availability vrrp group 1009-6 address 2001:db8:0:1009::1/64 +set high-availability vrrp group 1009-6 interface 'eth0.1009' +set high-availability vrrp group 1009-6 priority '200' +set high-availability vrrp group 1009-6 vrid '6' +set high-availability vrrp group 1010-4 address 192.0.70.145/28 +set high-availability vrrp group 1010-4 interface 'eth0.1010' +set high-availability vrrp group 1010-4 priority '200' +set high-availability vrrp group 1010-4 vrid '4' +set high-availability vrrp group 1010-6 address 2001:db8:0:1010::1/64 +set high-availability vrrp group 1010-6 interface 'eth0.1010' +set high-availability vrrp group 1010-6 priority '200' +set high-availability vrrp group 1010-6 vrid '6' +set high-availability vrrp group 1011-4 address 192.0.70.161/28 +set high-availability vrrp group 1011-4 interface 'eth0.1011' +set high-availability vrrp group 1011-4 priority '200' +set high-availability vrrp group 1011-4 vrid '4' +set high-availability vrrp group 1011-6 address 2001:db8:0:1011::1/64 +set high-availability vrrp group 1011-6 interface 'eth0.1011' +set high-availability vrrp group 1011-6 priority '200' +set high-availability vrrp group 1011-6 vrid '6' +set high-availability vrrp group 1012-4 address 192.0.70.177/28 +set high-availability vrrp group 1012-4 interface 'eth0.1012' +set high-availability vrrp group 1012-4 priority '200' +set high-availability vrrp group 1012-4 vrid '4' +set high-availability vrrp group 1012-6 address 2001:db8:0:1012::1/64 +set high-availability vrrp group 1012-6 interface 'eth0.1012' +set high-availability vrrp group 1012-6 priority '200' +set high-availability vrrp group 1012-6 vrid '6' +set high-availability vrrp group 1013-4 address 192.0.70.193/27 +set high-availability vrrp group 1013-4 interface 'eth0.1013' +set high-availability vrrp group 1013-4 priority '200' +set high-availability vrrp group 1013-4 vrid '4' +set high-availability vrrp group 1013-6 address 2001:db8:0:1013::1/64 +set high-availability vrrp group 1013-6 interface 'eth0.1013' +set high-availability vrrp group 1013-6 priority '200' +set high-availability vrrp group 1013-6 vrid '6' +set high-availability vrrp group 1014-4 address 192.0.84.65/26 +set high-availability vrrp group 1014-4 interface 'eth0.1014' +set high-availability vrrp group 1014-4 priority '200' +set high-availability vrrp group 1014-4 vrid '4' +set high-availability vrrp group 1014-6 address 2001:db8:0:1014::1/64 +set high-availability vrrp group 1014-6 interface 'eth0.1014' +set high-availability vrrp group 1014-6 priority '200' +set high-availability vrrp group 1014-6 vrid '6' +set high-availability vrrp group 1015-4 address 192.0.71.1/26 +set high-availability vrrp group 1015-4 interface 'eth0.1015' +set high-availability vrrp group 1015-4 priority '200' +set high-availability vrrp group 1015-4 vrid '4' +set high-availability vrrp group 1015-6 address 2001:db8:0:1015::1/64 +set high-availability vrrp group 1015-6 interface 'eth0.1015' +set high-availability vrrp group 1015-6 priority '200' +set high-availability vrrp group 1015-6 vrid '6' +set high-availability vrrp group 1016-4 address 192.0.71.65/27 +set high-availability vrrp group 1016-4 interface 'eth0.1016' +set high-availability vrrp group 1016-4 priority '200' +set high-availability vrrp group 1016-4 vrid '4' +set high-availability vrrp group 1016-6 address 2001:db8:0:1016::1/64 +set high-availability vrrp group 1016-6 interface 'eth0.1016' +set high-availability vrrp group 1016-6 priority '200' +set high-availability vrrp group 1016-6 vrid '6' +set high-availability vrrp group 1017-4 address 192.0.71.97/28 +set high-availability vrrp group 1017-4 interface 'eth0.1017' +set high-availability vrrp group 1017-4 priority '200' +set high-availability vrrp group 1017-4 vrid '4' +set high-availability vrrp group 1017-6 address 2001:db8:0:1017::1/64 +set high-availability vrrp group 1017-6 interface 'eth0.1017' +set high-availability vrrp group 1017-6 priority '200' +set high-availability vrrp group 1017-6 vrid '6' +set high-availability vrrp group 1018-4 address 192.0.71.113/28 +set high-availability vrrp group 1018-4 interface 'eth0.1018' +set high-availability vrrp group 1018-4 priority '200' +set high-availability vrrp group 1018-4 vrid '4' +set high-availability vrrp group 1018-6 address 2001:db8:0:1018::1/64 +set high-availability vrrp group 1018-6 interface 'eth0.1018' +set high-availability vrrp group 1018-6 priority '200' +set high-availability vrrp group 1018-6 vrid '6' +set high-availability vrrp group 1019-4 address 192.0.71.129/26 +set high-availability vrrp group 1019-4 interface 'eth0.1019' +set high-availability vrrp group 1019-4 priority '200' +set high-availability vrrp group 1019-4 vrid '4' +set high-availability vrrp group 1019-6 address 2001:db8:0:1019::1/64 +set high-availability vrrp group 1019-6 interface 'eth0.1019' +set high-availability vrrp group 1019-6 priority '200' +set high-availability vrrp group 1019-6 vrid '6' +set high-availability vrrp group 1020-4 address 192.0.71.193/26 +set high-availability vrrp group 1020-4 interface 'eth0.1020' +set high-availability vrrp group 1020-4 priority '200' +set high-availability vrrp group 1020-4 vrid '4' +set high-availability vrrp group 1020-6 address 2001:db8:0:1020::1/64 +set high-availability vrrp group 1020-6 interface 'eth0.1020' +set high-availability vrrp group 1020-6 priority '200' +set high-availability vrrp group 1020-6 vrid '6' +set interfaces ethernet eth0 address '192.0.0.11/16' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 offload gro +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth0 vif 11 address '192.0.68.2/27' +set interfaces ethernet eth0 vif 11 address '2001:db8:c::2/64' +set interfaces ethernet eth0 vif 102 address '192.0.98.2/24' +set interfaces ethernet eth0 vif 102 address '2001:db8:0:102::2/64' +set interfaces ethernet eth0 vif 105 address '192.0.160.2/24' +set interfaces ethernet eth0 vif 105 address '2001:db8:0:105::2/64' +set interfaces ethernet eth0 vif 838 address '192.0.16.210/30' +set interfaces ethernet eth0 vif 838 address '2001:db8:838::2/64' +set interfaces ethernet eth0 vif 886 address '192.0.193.224/21' +set interfaces ethernet eth0 vif 886 address '2001:db8::3:669:0:1/64' +set interfaces ethernet eth0 vif 1001 address '192.0.68.34/27' +set interfaces ethernet eth0 vif 1001 address '2001:db8:0:1001::2/64' +set interfaces ethernet eth0 vif 1002 address '192.0.68.66/26' +set interfaces ethernet eth0 vif 1002 address '2001:db8:0:1002::2/64' +set interfaces ethernet eth0 vif 1003 address '192.0.68.130/25' +set interfaces ethernet eth0 vif 1003 address '2001:db8:0:1003::2/64' +set interfaces ethernet eth0 vif 1004 address '192.0.69.2/24' +set interfaces ethernet eth0 vif 1004 address '2001:db8:0:1004::2/64' +set interfaces ethernet eth0 vif 1005 address '192.0.70.2/28' +set interfaces ethernet eth0 vif 1005 address '2001:db8:0:1005::2/64' +set interfaces ethernet eth0 vif 1006 address '192.0.70.18/28' +set interfaces ethernet eth0 vif 1006 address '2001:db8:0:1006::2/64' +set interfaces ethernet eth0 vif 1007 address '192.0.70.34/27' +set interfaces ethernet eth0 vif 1007 address '2001:db8:0:1007::2/64' +set interfaces ethernet eth0 vif 1008 address '192.0.70.66/26' +set interfaces ethernet eth0 vif 1008 address '2001:db8:0:1008::2/64' +set interfaces ethernet eth0 vif 1009 address '192.0.70.130/28' +set interfaces ethernet eth0 vif 1009 address '2001:db8:0:1009::2/64' +set interfaces ethernet eth0 vif 1010 address '192.0.70.146/28' +set interfaces ethernet eth0 vif 1010 address '2001:db8:0:1010::2/64' +set interfaces ethernet eth0 vif 1011 address '192.0.70.162/28' +set interfaces ethernet eth0 vif 1011 address '2001:db8:0:1011::2/64' +set interfaces ethernet eth0 vif 1012 address '192.0.70.178/28' +set interfaces ethernet eth0 vif 1012 address '2001:db8:0:1012::2/64' +set interfaces ethernet eth0 vif 1013 address '192.0.70.194/27' +set interfaces ethernet eth0 vif 1013 address '2001:db8:0:1013::3/64' +set interfaces ethernet eth0 vif 1014 address '192.0.84.66/26' +set interfaces ethernet eth0 vif 1014 address '2001:db8:0:1014::2/64' +set interfaces ethernet eth0 vif 1015 address '192.0.71.2/26' +set interfaces ethernet eth0 vif 1015 address '2001:db8:0:1015::2/64' +set interfaces ethernet eth0 vif 1016 address '192.0.71.66/27' +set interfaces ethernet eth0 vif 1016 address '2001:db8:0:1016::2/64' +set interfaces ethernet eth0 vif 1017 address '192.0.71.98/28' +set interfaces ethernet eth0 vif 1017 address '2001:db8:0:1017::2/64' +set interfaces ethernet eth0 vif 1018 address '192.0.71.114/28' +set interfaces ethernet eth0 vif 1018 address '2001:db8:0:1018::2/64' +set interfaces ethernet eth0 vif 1019 address '192.0.71.130/26' +set interfaces ethernet eth0 vif 1019 address '2001:db8:0:1019::2/64' +set interfaces ethernet eth0 vif 1020 address '192.0.71.194/26' +set interfaces ethernet eth0 vif 1020 address '2001:db8:0:1020::2/64' +set interfaces ethernet eth0 vif 4088 address '2001:db8:24::c7/64' +set interfaces ethernet eth0 vif 4088 address '192.0.52.199/23' +set interfaces ethernet eth0 vif 4089 address '192.0.176.194/30' +set interfaces ethernet eth0 vif 4089 address '2001:db8:1000::2ea/126' +set interfaces loopback lo +set policy as-path-list AS64512 rule 10 action 'permit' +set policy as-path-list AS64512 rule 10 regex '^$' +set policy as-path-list AS64513-AS64514 rule 10 action 'permit' +set policy as-path-list AS64513-AS64514 rule 10 regex '^64513 64514$' +set policy prefix-list defaultV4 rule 10 action 'permit' +set policy prefix-list defaultV4 rule 10 prefix '0.0.0.0/0' +set policy prefix-list hostrouteV4 rule 10 action 'permit' +set policy prefix-list hostrouteV4 rule 10 ge '32' +set policy prefix-list hostrouteV4 rule 10 prefix '192.0.160.0/24' +set policy prefix-list hostrouteV4 rule 20 action 'permit' +set policy prefix-list hostrouteV4 rule 20 ge '32' +set policy prefix-list hostrouteV4 rule 20 prefix '192.0.98.0/24' +set policy prefix-list hostrouteV4 rule 30 action 'permit' +set policy prefix-list hostrouteV4 rule 30 ge '32' +set policy prefix-list hostrouteV4 rule 30 prefix '192.0.68.0/22' +set policy prefix-list hostrouteV4 rule 40 action 'permit' +set policy prefix-list hostrouteV4 rule 40 ge '32' +set policy prefix-list hostrouteV4 rule 40 prefix '192.0.84.0/22' +set policy prefix-list privateV4 rule 10 action 'permit' +set policy prefix-list privateV4 rule 10 le '32' +set policy prefix-list privateV4 rule 10 prefix '192.0.0.0/8' +set policy prefix-list privateV4 rule 20 action 'permit' +set policy prefix-list privateV4 rule 20 le '32' +set policy prefix-list privateV4 rule 20 prefix '192.0.0.0/12' +set policy prefix-list privateV4 rule 30 action 'permit' +set policy prefix-list privateV4 rule 30 le '32' +set policy prefix-list privateV4 rule 30 prefix '192.0.0.0/16' +set policy prefix-list vyosV4 rule 10 action 'permit' +set policy prefix-list vyosV4 rule 10 prefix '192.0.160.0/24' +set policy prefix-list vyosV4 rule 20 action 'permit' +set policy prefix-list vyosV4 rule 20 prefix '192.0.98.0/24' +set policy prefix-list vyosV4 rule 30 action 'permit' +set policy prefix-list vyosV4 rule 30 prefix '192.0.68.0/22' +set policy prefix-list vyosV4 rule 40 action 'permit' +set policy prefix-list vyosV4 rule 40 prefix '192.0.84.0/22' +set policy prefix-list6 all6 rule 10 action 'permit' +set policy prefix-list6 all6 rule 10 ge '4' +set policy prefix-list6 all6 rule 10 prefix '2000::/3' +set policy prefix-list6 hostrouteV6 rule 20 action 'permit' +set policy prefix-list6 hostrouteV6 rule 20 ge '128' +set policy prefix-list6 hostrouteV6 rule 20 prefix '2001:db8::/29' +set policy prefix-list6 privateV6 rule 10 action 'permit' +set policy prefix-list6 privateV6 rule 10 prefix 'fc00::/7' +set policy prefix-list6 vyosV6 rule 20 action 'permit' +set policy prefix-list6 vyosV6 rule 20 prefix '2001:db8::/29' +set policy route-map ExportRouteMap rule 5 action 'permit' +set policy route-map ExportRouteMap rule 5 match as-path 'AS64512' +set policy route-map ExportRouteMap rule 5 match ip address prefix-list 'hostrouteV4' +set policy route-map ExportRouteMap rule 5 set community replace '65000:666' +set policy route-map ExportRouteMap rule 10 action 'permit' +set policy route-map ExportRouteMap rule 10 match as-path 'AS64512' +set policy route-map ExportRouteMap rule 10 match ip address prefix-list 'vyosV4' +set policy route-map ExportRouteMap rule 15 action 'permit' +set policy route-map ExportRouteMap rule 15 match as-path 'AS64512' +set policy route-map ExportRouteMap rule 15 match ipv6 address prefix-list 'hostrouteV6' +set policy route-map ExportRouteMap rule 15 set community replace '65000:666' +set policy route-map ExportRouteMap rule 20 action 'permit' +set policy route-map ExportRouteMap rule 20 match as-path 'AS64512' +set policy route-map ExportRouteMap rule 20 match ipv6 address prefix-list 'vyosV6' +set policy route-map ExportRouteMap rule 100 action 'deny' +set policy route-map ExportRouteMapAS64513 rule 5 action 'permit' +set policy route-map ExportRouteMapAS64513 rule 5 match as-path 'AS64512' +set policy route-map ExportRouteMapAS64513 rule 5 match ip address prefix-list 'hostrouteV4' +set policy route-map ExportRouteMapAS64513 rule 5 set community replace '64513:666' +set policy route-map ExportRouteMapAS64513 rule 10 action 'permit' +set policy route-map ExportRouteMapAS64513 rule 10 match as-path 'AS64512' +set policy route-map ExportRouteMapAS64513 rule 10 match ip address prefix-list 'vyosV4' +set policy route-map ExportRouteMapAS64513 rule 15 action 'permit' +set policy route-map ExportRouteMapAS64513 rule 15 match as-path 'AS64512' +set policy route-map ExportRouteMapAS64513 rule 15 match ipv6 address prefix-list 'hostrouteV6' +set policy route-map ExportRouteMapAS64513 rule 15 set community replace '64513:666' +set policy route-map ExportRouteMapAS64513 rule 20 action 'permit' +set policy route-map ExportRouteMapAS64513 rule 20 match as-path 'AS64512' +set policy route-map ExportRouteMapAS64513 rule 20 match ipv6 address prefix-list 'vyosV6' +set policy route-map ExportRouteMapAS64513 rule 100 action 'deny' +set policy route-map ExportRouteMapAS64515 rule 10 action 'permit' +set policy route-map ExportRouteMapAS64515 rule 10 match ipv6 address prefix-list 'all6' +set policy route-map ExportRouteMapAS64515 rule 20 action 'deny' +set policy route-map ExportRouteMapAS64515 rule 20 match ip address prefix-list 'defaultV4' +set policy route-map ExportRouteMapAS64515 rule 100 action 'deny' +set policy route-map ExportRouteMapAS64516 rule 5 action 'permit' +set policy route-map ExportRouteMapAS64516 rule 5 match as-path 'AS64512' +set policy route-map ExportRouteMapAS64516 rule 5 match ip address prefix-list 'hostrouteV4' +set policy route-map ExportRouteMapAS64516 rule 5 set community replace '65000:666' +set policy route-map ExportRouteMapAS64516 rule 10 action 'permit' +set policy route-map ExportRouteMapAS64516 rule 10 match as-path 'AS64512' +set policy route-map ExportRouteMapAS64516 rule 10 match ip address prefix-list 'vyosV4' +set policy route-map ExportRouteMapAS64516 rule 15 action 'permit' +set policy route-map ExportRouteMapAS64516 rule 15 match as-path 'AS64512' +set policy route-map ExportRouteMapAS64516 rule 15 match ipv6 address prefix-list 'hostrouteV6' +set policy route-map ExportRouteMapAS64516 rule 15 set community replace '65000:666' +set policy route-map ExportRouteMapAS64516 rule 20 action 'permit' +set policy route-map ExportRouteMapAS64516 rule 20 match as-path 'AS64512' +set policy route-map ExportRouteMapAS64516 rule 20 match ipv6 address prefix-list 'vyosV6' +set policy route-map ExportRouteMapAS64516 rule 20 set as-path exclude '100 200 300' +set policy route-map ExportRouteMapAS64516 rule 20 set as-path prepend '64512 64512 64512' +set policy route-map ExportRouteMapAS64516 rule 100 action 'deny' +set policy route-map ExportRouteMapAS64517 rule 5 action 'permit' +set policy route-map ExportRouteMapAS64517 rule 5 match as-path 'AS64512' +set policy route-map ExportRouteMapAS64517 rule 5 match ip address prefix-list 'hostrouteV4' +set policy route-map ExportRouteMapAS64517 rule 5 set community replace '64517:666' +set policy route-map ExportRouteMapAS64517 rule 10 action 'permit' +set policy route-map ExportRouteMapAS64517 rule 10 match as-path 'AS64512' +set policy route-map ExportRouteMapAS64517 rule 10 match ip address prefix-list 'vyosV4' +set policy route-map ExportRouteMapAS64517 rule 15 action 'permit' +set policy route-map ExportRouteMapAS64517 rule 15 match as-path 'AS64512' +set policy route-map ExportRouteMapAS64517 rule 15 match ipv6 address prefix-list 'hostrouteV6' +set policy route-map ExportRouteMapAS64517 rule 15 set community replace '64517:666' +set policy route-map ExportRouteMapAS64517 rule 20 action 'permit' +set policy route-map ExportRouteMapAS64517 rule 20 match as-path 'AS64512' +set policy route-map ExportRouteMapAS64517 rule 20 match ipv6 address prefix-list 'vyosV6' +set policy route-map ExportRouteMapAS64517 rule 100 action 'deny' +set policy route-map ImportRouteMap rule 10 action 'deny' +set policy route-map ImportRouteMap rule 10 match ip address prefix-list 'privateV4' +set policy route-map ImportRouteMap rule 15 action 'deny' +set policy route-map ImportRouteMap rule 15 match ipv6 address prefix-list 'privateV6' +set policy route-map ImportRouteMap rule 20 action 'deny' +set policy route-map ImportRouteMap rule 20 match ip address prefix-list 'vyosV4' +set policy route-map ImportRouteMap rule 30 action 'deny' +set policy route-map ImportRouteMap rule 30 match ipv6 address prefix-list 'vyosV6' +set policy route-map ImportRouteMap rule 40 action 'deny' +set policy route-map ImportRouteMap rule 40 match as-path 'AS64512' +set policy route-map ImportRouteMap rule 50 action 'permit' +set policy route-map ImportRouteMap rule 50 match as-path 'AS64513-AS64514' +set policy route-map ImportRouteMap rule 50 set weight '10001' +set policy route-map ImportRouteMap rule 65535 action 'permit' +set protocols bgp address-family ipv4-unicast maximum-paths ebgp '8' +set protocols bgp address-family ipv4-unicast maximum-paths ibgp '16' +set protocols bgp address-family ipv4-unicast network 192.0.68.0/22 +set protocols bgp address-family ipv4-unicast network 192.0.84.0/22 +set protocols bgp address-family ipv4-unicast network 192.0.98.0/24 +set protocols bgp address-family ipv4-unicast network 192.0.160.0/24 +set protocols bgp address-family ipv4-unicast redistribute static route-map 'ExportRouteMap' +set protocols bgp address-family ipv6-unicast network 2001:db8::/29 +set protocols bgp address-family ipv6-unicast redistribute static route-map 'ExportRouteMap' +set protocols bgp neighbor 192.0.16.209 address-family ipv4-unicast route-map export 'ExportRouteMapAS64516' +set protocols bgp neighbor 192.0.16.209 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.16.209 remote-as '64501' +set protocols bgp neighbor 192.0.52.12 address-family ipv4-unicast maximum-prefix '300' +set protocols bgp neighbor 192.0.52.12 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.12 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.12 remote-as '64511' +set protocols bgp neighbor 192.0.52.17 address-family ipv4-unicast maximum-prefix '75' +set protocols bgp neighbor 192.0.52.17 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.17 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.17 password 'vyosvyos' +set protocols bgp neighbor 192.0.52.17 remote-as '64512' +set protocols bgp neighbor 192.0.52.24 address-family ipv4-unicast maximum-prefix '300' +set protocols bgp neighbor 192.0.52.24 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.24 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.24 remote-as '64513' +set protocols bgp neighbor 192.0.52.32 address-family ipv4-unicast maximum-prefix '50' +set protocols bgp neighbor 192.0.52.32 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.32 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.32 password 'vyosfoooo' +set protocols bgp neighbor 192.0.52.32 remote-as '64514' +set protocols bgp neighbor 192.0.52.34 address-family ipv4-unicast maximum-prefix '10' +set protocols bgp neighbor 192.0.52.34 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.34 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.34 remote-as '64515' +set protocols bgp neighbor 192.0.52.46 address-family ipv4-unicast maximum-prefix '10' +set protocols bgp neighbor 192.0.52.46 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.46 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.46 remote-as '64516' +set protocols bgp neighbor 192.0.52.49 address-family ipv4-unicast maximum-prefix '75' +set protocols bgp neighbor 192.0.52.49 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.49 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.49 password 'secret' +set protocols bgp neighbor 192.0.52.49 remote-as '64517' +set protocols bgp neighbor 192.0.52.74 address-family ipv4-unicast maximum-prefix '15000' +set protocols bgp neighbor 192.0.52.74 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.74 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.74 password 'secretvyos' +set protocols bgp neighbor 192.0.52.74 remote-as '64518' +set protocols bgp neighbor 192.0.52.94 address-family ipv4-unicast maximum-prefix '250' +set protocols bgp neighbor 192.0.52.94 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.94 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.94 remote-as '64519' +set protocols bgp neighbor 192.0.52.100 address-family ipv4-unicast maximum-prefix '50' +set protocols bgp neighbor 192.0.52.100 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.100 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.100 remote-as '64520' +set protocols bgp neighbor 192.0.52.119 address-family ipv4-unicast maximum-prefix '30' +set protocols bgp neighbor 192.0.52.119 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.119 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.119 remote-as '64521' +set protocols bgp neighbor 192.0.52.165 address-family ipv4-unicast maximum-prefix '50' +set protocols bgp neighbor 192.0.52.165 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.165 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.165 remote-as '64522' +set protocols bgp neighbor 192.0.52.170 address-family ipv4-unicast maximum-prefix '150000' +set protocols bgp neighbor 192.0.52.170 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.170 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.170 remote-as '64523' +set protocols bgp neighbor 192.0.52.171 address-family ipv4-unicast maximum-prefix '10000' +set protocols bgp neighbor 192.0.52.171 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.171 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.171 remote-as '64524' +set protocols bgp neighbor 192.0.52.179 address-family ipv4-unicast maximum-prefix '20' +set protocols bgp neighbor 192.0.52.179 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.179 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.179 remote-as '64525' +set protocols bgp neighbor 192.0.52.189 address-family ipv4-unicast maximum-prefix '1000' +set protocols bgp neighbor 192.0.52.189 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.189 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.189 remote-as '64526' +set protocols bgp neighbor 192.0.52.210 address-family ipv4-unicast maximum-prefix '15' +set protocols bgp neighbor 192.0.52.210 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.210 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.210 remote-as '64527' +set protocols bgp neighbor 192.0.52.211 address-family ipv4-unicast maximum-prefix '15' +set protocols bgp neighbor 192.0.52.211 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.211 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.211 remote-as '64528' +set protocols bgp neighbor 192.0.52.251 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.251 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.251 address-family ipv4-unicast weight '1010' +set protocols bgp neighbor 192.0.52.251 remote-as '64529' +set protocols bgp neighbor 192.0.52.252 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.52.252 address-family ipv4-unicast weight '1010' +set protocols bgp neighbor 192.0.52.252 remote-as '64530' +set protocols bgp neighbor 192.0.52.253 address-family ipv4-unicast route-map export 'ExportRouteMapAS64515' +set protocols bgp neighbor 192.0.52.253 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.52.253 passive +set protocols bgp neighbor 192.0.52.253 remote-as '64531' +set protocols bgp neighbor 192.0.68.3 address-family ipv4-unicast nexthop-self +set protocols bgp neighbor 192.0.68.3 address-family ipv4-unicast soft-reconfiguration inbound +set protocols bgp neighbor 192.0.68.3 remote-as '64532' +set protocols bgp neighbor 192.0.68.3 update-source '192.0.68.2' +set protocols bgp neighbor 192.0.176.193 address-family ipv4-unicast route-map export 'ExportRouteMapAS64516' +set protocols bgp neighbor 192.0.176.193 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.176.193 remote-as '64510' +set protocols bgp neighbor 192.0.192.6 address-family ipv4-unicast maximum-prefix '100' +set protocols bgp neighbor 192.0.192.6 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.192.6 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.192.6 remote-as '64502' +set protocols bgp neighbor 192.0.192.157 address-family ipv4-unicast maximum-prefix '350000' +set protocols bgp neighbor 192.0.192.157 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.192.157 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.192.157 remote-as '64503' +set protocols bgp neighbor 192.0.192.228 address-family ipv4-unicast maximum-prefix '10000' +set protocols bgp neighbor 192.0.192.228 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.192.228 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.192.228 remote-as '64504' +set protocols bgp neighbor 192.0.193.157 address-family ipv4-unicast maximum-prefix '350000' +set protocols bgp neighbor 192.0.193.157 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.193.157 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.193.157 remote-as '64505' +set protocols bgp neighbor 192.0.193.202 address-family ipv4-unicast maximum-prefix '10000' +set protocols bgp neighbor 192.0.193.202 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.193.202 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.193.202 remote-as '64506' +set protocols bgp neighbor 192.0.193.223 address-family ipv4-unicast maximum-prefix '10000' +set protocols bgp neighbor 192.0.193.223 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.193.223 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.193.223 remote-as '64507' +set protocols bgp neighbor 192.0.194.161 address-family ipv4-unicast maximum-prefix '10000' +set protocols bgp neighbor 192.0.194.161 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.194.161 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.194.161 remote-as '64508' +set protocols bgp neighbor 192.0.194.171 address-family ipv4-unicast maximum-prefix '10000' +set protocols bgp neighbor 192.0.194.171 address-family ipv4-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 192.0.194.171 address-family ipv4-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 192.0.194.171 remote-as '64509' +set protocols bgp neighbor 2001:db8:24::2e address-family ipv6-unicast maximum-prefix '5' +set protocols bgp neighbor 2001:db8:24::2e address-family ipv6-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 2001:db8:24::2e address-family ipv6-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 2001:db8:24::2e password 'vyossecret' +set protocols bgp neighbor 2001:db8:24::2e remote-as '64535' +set protocols bgp neighbor 2001:db8:24::4a address-family ipv6-unicast maximum-prefix '1000' +set protocols bgp neighbor 2001:db8:24::4a address-family ipv6-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 2001:db8:24::4a address-family ipv6-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 2001:db8:24::4a remote-as '64536' +set protocols bgp neighbor 2001:db8:24::5e address-family ipv6-unicast maximum-prefix '200' +set protocols bgp neighbor 2001:db8:24::5e address-family ipv6-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 2001:db8:24::5e address-family ipv6-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 2001:db8:24::5e remote-as '64537' +set protocols bgp neighbor 2001:db8:24::11 address-family ipv6-unicast maximum-prefix '20' +set protocols bgp neighbor 2001:db8:24::11 address-family ipv6-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 2001:db8:24::11 address-family ipv6-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 2001:db8:24::11 remote-as '64538' +set protocols bgp neighbor 2001:db8:24::18 address-family ipv6-unicast maximum-prefix '300' +set protocols bgp neighbor 2001:db8:24::18 address-family ipv6-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 2001:db8:24::18 address-family ipv6-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 2001:db8:24::18 remote-as '64539' +set protocols bgp neighbor 2001:db8:24::20 address-family ipv6-unicast maximum-prefix '10' +set protocols bgp neighbor 2001:db8:24::20 address-family ipv6-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 2001:db8:24::20 address-family ipv6-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 2001:db8:24::20 remote-as '64540' +set protocols bgp neighbor 2001:db8:24::22 address-family ipv6-unicast maximum-prefix '5' +set protocols bgp neighbor 2001:db8:24::22 address-family ipv6-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 2001:db8:24::22 address-family ipv6-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 2001:db8:24::22 remote-as '64541' +set protocols bgp neighbor 2001:db8:24::31 address-family ipv6-unicast maximum-prefix '20' +set protocols bgp neighbor 2001:db8:24::31 address-family ipv6-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 2001:db8:24::31 address-family ipv6-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 2001:db8:24::31 remote-as '64542' +set protocols bgp neighbor 2001:db8:24::58 address-family ipv6-unicast maximum-prefix '15' +set protocols bgp neighbor 2001:db8:24::58 address-family ipv6-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 2001:db8:24::58 address-family ipv6-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 2001:db8:24::58 remote-as '64543' +set protocols bgp neighbor 2001:db8:24::64 address-family ipv6-unicast maximum-prefix '10' +set protocols bgp neighbor 2001:db8:24::64 address-family ipv6-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 2001:db8:24::64 address-family ipv6-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 2001:db8:24::64 password 'geheim' +set protocols bgp neighbor 2001:db8:24::64 remote-as '64544' +set protocols bgp neighbor 2001:db8:24::a5 address-family ipv6-unicast maximum-prefix '10' +set protocols bgp neighbor 2001:db8:24::a5 address-family ipv6-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 2001:db8:24::a5 address-family ipv6-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 2001:db8:24::a5 remote-as '64545' +set protocols bgp neighbor 2001:db8:24::aa address-family ipv6-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 2001:db8:24::aa address-family ipv6-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 2001:db8:24::aa remote-as '64546' +set protocols bgp neighbor 2001:db8:24::ab address-family ipv6-unicast maximum-prefix '1800' +set protocols bgp neighbor 2001:db8:24::ab address-family ipv6-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 2001:db8:24::ab address-family ipv6-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 2001:db8:24::ab remote-as '64547' +set protocols bgp neighbor 2001:db8:24::b0 address-family ipv6-unicast maximum-prefix '5' +set protocols bgp neighbor 2001:db8:24::b0 address-family ipv6-unicast route-map export 'ExportRouteMap' +set protocols bgp neighbor 2001:db8:24::b0 address-family ipv6-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 2001:db8:24::b0 password 'secret123' +set protocols bgp neighbor 2001:db8:24::b0 remote-as '64548' +set protocols bgp neighbor 2001:db8:838::1 address-family ipv6-unicast route-map export 'ExportRouteMapAS64516' +set protocols bgp neighbor 2001:db8:838::1 address-family ipv6-unicast route-map import 'ImportRouteMap' +set protocols bgp neighbor 2001:db8:838::1 remote-as '64533' +set protocols bgp neighbor 2001:db8:c::3 address-family ipv6-unicast nexthop-self +set protocols bgp neighbor 2001:db8:c::3 address-family ipv6-unicast soft-reconfiguration inbound +set protocols bgp neighbor 2001:db8:c::3 remote-as '64534' +set protocols bgp neighbor 2001:db8:c::3 update-source '2001:db8:c::2' +set protocols bgp parameters log-neighbor-changes +set protocols bgp parameters router-id '192.0.68.2' +set protocols bgp system-as '64500' +set protocols static route 192.0.68.0/22 blackhole +set protocols static route 192.0.84.0/22 blackhole +set protocols static route 192.0.98.0/24 blackhole +set protocols static route 192.0.160.0/24 blackhole +set protocols static route6 2001:db8::/29 blackhole +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server 0.pool.ntp.org +set service ntp server 1.pool.ntp.org +set service ntp server 2.pool.ntp.org +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system flow-accounting disable-imt +set system flow-accounting interface 'eth0.4088' +set system flow-accounting interface 'eth0.4089' +set system flow-accounting netflow engine-id '1' +set system flow-accounting netflow server 192.0.2.55 port '2055' +set system flow-accounting netflow version '9' +set system flow-accounting sflow server 1.2.3.4 port '1234' +set system flow-accounting syslog-facility 'daemon' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/' +set system login user vyos authentication plaintext-password '' +set system name-server '2001:db8::1' +set system name-server '2001:db8::2' +set system name-server '192.0.2.1' +set system name-server '192.0.2.2' +set system syslog global facility all level 'all' +set system syslog global preserve-fqdn +set system time-zone 'Europe/Zurich' diff --git a/smoketest/config-tests/bgp-dmvpn-hub b/smoketest/config-tests/bgp-dmvpn-hub new file mode 100644 index 0000000..3052152 --- /dev/null +++ b/smoketest/config-tests/bgp-dmvpn-hub @@ -0,0 +1,69 @@ +set interfaces ethernet eth0 address '100.64.10.1/31' +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth1 speed 'auto' +set interfaces ethernet eth1 duplex 'auto' +set interfaces loopback lo +set interfaces tunnel tun0 address '192.168.254.62/26' +set interfaces tunnel tun0 enable-multicast +set interfaces tunnel tun0 encapsulation 'gre' +set interfaces tunnel tun0 parameters ip key '1' +set interfaces tunnel tun0 source-address '100.64.10.1' +set protocols bgp address-family ipv4-unicast network 172.20.0.0/16 +set protocols bgp neighbor 192.168.254.1 peer-group 'DMVPN' +set protocols bgp neighbor 192.168.254.1 remote-as '65001' +set protocols bgp neighbor 192.168.254.2 peer-group 'DMVPN' +set protocols bgp neighbor 192.168.254.2 remote-as '65002' +set protocols bgp neighbor 192.168.254.3 peer-group 'DMVPN' +set protocols bgp neighbor 192.168.254.3 remote-as '65003' +set protocols bgp parameters log-neighbor-changes +set protocols bgp peer-group DMVPN address-family ipv4-unicast +set protocols bgp system-as '65000' +set protocols bgp timers holdtime '30' +set protocols bgp timers keepalive '10' +set protocols nhrp tunnel tun0 cisco-authentication 'secret' +set protocols nhrp tunnel tun0 holding-time '300' +set protocols nhrp tunnel tun0 multicast 'dynamic' +set protocols nhrp tunnel tun0 redirect +set protocols nhrp tunnel tun0 shortcut +set protocols static route 0.0.0.0/0 next-hop 100.64.10.0 +set protocols static route 172.20.0.0/16 blackhole distance '200' +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server time1.vyos.net +set service ntp server time2.vyos.net +set service ntp server time3.vyos.net +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'cpe-4' +set system login user vyos authentication encrypted-password '$6$r/Yw/07NXNY$/ZB.Rjf9jxEV.BYoDyLdH.kH14rU52pOBtrX.4S34qlPt77chflCHvpTCq9a6huLzwaMR50rEICzA5GoIRZlM0' +set system login user vyos authentication plaintext-password '' +set system name-server '1.1.1.1' +set system name-server '8.8.8.8' +set system name-server '9.9.9.9' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set vpn ipsec esp-group ESP-DMVPN lifetime '1800' +set vpn ipsec esp-group ESP-DMVPN mode 'transport' +set vpn ipsec esp-group ESP-DMVPN pfs 'dh-group2' +set vpn ipsec esp-group ESP-DMVPN proposal 1 encryption 'aes256' +set vpn ipsec esp-group ESP-DMVPN proposal 1 hash 'sha1' +set vpn ipsec ike-group IKE-DMVPN close-action 'none' +set vpn ipsec ike-group IKE-DMVPN key-exchange 'ikev1' +set vpn ipsec ike-group IKE-DMVPN lifetime '3600' +set vpn ipsec ike-group IKE-DMVPN proposal 1 dh-group '2' +set vpn ipsec ike-group IKE-DMVPN proposal 1 encryption 'aes256' +set vpn ipsec ike-group IKE-DMVPN proposal 1 hash 'sha1' +set vpn ipsec interface 'eth0' +set vpn ipsec profile NHRPVPN authentication mode 'pre-shared-secret' +set vpn ipsec profile NHRPVPN authentication pre-shared-secret 'VyOS-topsecret' +set vpn ipsec profile NHRPVPN bind tunnel 'tun0' +set vpn ipsec profile NHRPVPN esp-group 'ESP-DMVPN' +set vpn ipsec profile NHRPVPN ike-group 'IKE-DMVPN' diff --git a/smoketest/config-tests/bgp-dmvpn-spoke b/smoketest/config-tests/bgp-dmvpn-spoke new file mode 100644 index 0000000..d1c7bc7 --- /dev/null +++ b/smoketest/config-tests/bgp-dmvpn-spoke @@ -0,0 +1,75 @@ +set interfaces ethernet eth0 vif 7 description 'PPPoE-UPLINK' +set interfaces ethernet eth1 address '172.17.1.1/24' +set interfaces loopback lo +set interfaces pppoe pppoe1 authentication password 'cpe-1' +set interfaces pppoe pppoe1 authentication username 'cpe-1' +set interfaces pppoe pppoe1 no-peer-dns +set interfaces pppoe pppoe1 source-interface 'eth0.7' +set interfaces tunnel tun0 address '192.168.254.1/26' +set interfaces tunnel tun0 enable-multicast +set interfaces tunnel tun0 encapsulation 'gre' +set interfaces tunnel tun0 parameters ip key '1' +set interfaces tunnel tun0 source-address '0.0.0.0' +set nat source rule 10 log +set nat source rule 10 outbound-interface name 'pppoe1' +set nat source rule 10 source address '172.17.0.0/16' +set nat source rule 10 translation address 'masquerade' +set protocols bgp address-family ipv4-unicast network 172.17.0.0/16 +set protocols bgp neighbor 192.168.254.62 address-family ipv4-unicast +set protocols bgp neighbor 192.168.254.62 remote-as '65000' +set protocols bgp parameters log-neighbor-changes +set protocols bgp system-as '65001' +set protocols bgp timers holdtime '30' +set protocols bgp timers keepalive '10' +set protocols nhrp tunnel tun0 cisco-authentication 'secret' +set protocols nhrp tunnel tun0 holding-time '300' +set protocols nhrp tunnel tun0 map 192.168.254.62/26 nbma-address '100.64.10.1' +set protocols nhrp tunnel tun0 map 192.168.254.62/26 register +set protocols nhrp tunnel tun0 multicast 'nhs' +set protocols nhrp tunnel tun0 redirect +set protocols nhrp tunnel tun0 shortcut +set protocols static route 172.17.0.0/16 blackhole distance '200' +set service dhcp-server shared-network-name LAN-3 subnet 172.17.1.0/24 option default-router '172.17.1.1' +set service dhcp-server shared-network-name LAN-3 subnet 172.17.1.0/24 option name-server '172.17.1.1' +set service dhcp-server shared-network-name LAN-3 subnet 172.17.1.0/24 range 0 start '172.17.1.100' +set service dhcp-server shared-network-name LAN-3 subnet 172.17.1.0/24 range 0 stop '172.17.1.200' +set service dhcp-server shared-network-name LAN-3 subnet 172.17.1.0/24 subnet-id '1' +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server time1.vyos.net +set service ntp server time2.vyos.net +set service ntp server time3.vyos.net +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'cpe-1' +set system login user vyos authentication encrypted-password '$6$r/Yw/07NXNY$/ZB.Rjf9jxEV.BYoDyLdH.kH14rU52pOBtrX.4S34qlPt77chflCHvpTCq9a6huLzwaMR50rEICzA5GoIRZlM0' +set system login user vyos authentication plaintext-password '' +set system name-server '1.1.1.1' +set system name-server '8.8.8.8' +set system name-server '9.9.9.9' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set vpn ipsec esp-group ESP-DMVPN lifetime '1800' +set vpn ipsec esp-group ESP-DMVPN mode 'transport' +set vpn ipsec esp-group ESP-DMVPN pfs 'dh-group2' +set vpn ipsec esp-group ESP-DMVPN proposal 1 encryption 'aes256' +set vpn ipsec esp-group ESP-DMVPN proposal 1 hash 'sha1' +set vpn ipsec ike-group IKE-DMVPN close-action 'none' +set vpn ipsec ike-group IKE-DMVPN key-exchange 'ikev1' +set vpn ipsec ike-group IKE-DMVPN lifetime '3600' +set vpn ipsec ike-group IKE-DMVPN proposal 1 dh-group '2' +set vpn ipsec ike-group IKE-DMVPN proposal 1 encryption 'aes256' +set vpn ipsec ike-group IKE-DMVPN proposal 1 hash 'sha1' +set vpn ipsec interface 'pppoe1' +set vpn ipsec profile NHRPVPN authentication mode 'pre-shared-secret' +set vpn ipsec profile NHRPVPN authentication pre-shared-secret 'VyOS-topsecret' +set vpn ipsec profile NHRPVPN bind tunnel 'tun0' +set vpn ipsec profile NHRPVPN esp-group 'ESP-DMVPN' +set vpn ipsec profile NHRPVPN ike-group 'IKE-DMVPN' diff --git a/smoketest/config-tests/bgp-evpn-l2vpn-leaf b/smoketest/config-tests/bgp-evpn-l2vpn-leaf new file mode 100644 index 0000000..315cb9e --- /dev/null +++ b/smoketest/config-tests/bgp-evpn-l2vpn-leaf @@ -0,0 +1,55 @@ +set interfaces bridge br100 member interface eth3 +set interfaces bridge br100 member interface vxlan100 +set interfaces dummy dum0 address '172.29.0.1/32' +set interfaces ethernet eth0 address '2001:db8::41/64' +set interfaces ethernet eth0 address '192.0.2.41/27' +set interfaces ethernet eth0 description 'Out-of-Band Managament Port' +set interfaces ethernet eth0 vrf 'MGMT' +set interfaces ethernet eth1 address '172.29.1.1/31' +set interfaces ethernet eth1 mtu '1600' +set interfaces ethernet eth2 address '172.29.2.1/31' +set interfaces ethernet eth2 mtu '1600' +set interfaces ethernet eth2 offload gro +set interfaces ethernet eth3 offload gro +set interfaces loopback lo +set interfaces vxlan vxlan100 mtu '1500' +set interfaces vxlan vxlan100 parameters nolearning +set interfaces vxlan vxlan100 port '8472' +set interfaces vxlan vxlan100 source-address '172.29.0.1' +set interfaces vxlan vxlan100 vni '100' +set protocols bgp address-family ipv4-unicast maximum-paths ibgp '4' +set protocols bgp address-family ipv4-unicast redistribute connected +set protocols bgp address-family l2vpn-evpn advertise-all-vni +set protocols bgp neighbor 172.29.1.0 peer-group 'evpn' +set protocols bgp neighbor 172.29.2.0 peer-group 'evpn' +set protocols bgp parameters log-neighbor-changes +set protocols bgp peer-group evpn address-family ipv4-unicast nexthop-self +set protocols bgp peer-group evpn address-family l2vpn-evpn nexthop-self +set protocols bgp peer-group evpn remote-as '65010' +set protocols bgp system-as '65010' +set service lldp interface all +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp listen-address '192.0.2.41' +set service ntp listen-address '2001:db8::41' +set service ntp server 0.de.pool.ntp.org prefer +set service ntp vrf 'MGMT' +set service ssh disable-host-validation +set service ssh vrf 'MGMT' +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set vrf name MGMT protocols static route 0.0.0.0/0 next-hop 192.0.2.62 +set vrf name MGMT protocols static route6 ::/0 next-hop 2001:db8::1 +set vrf name MGMT table '1000' diff --git a/smoketest/config-tests/bgp-evpn-l2vpn-spine b/smoketest/config-tests/bgp-evpn-l2vpn-spine new file mode 100644 index 0000000..dee29e0 --- /dev/null +++ b/smoketest/config-tests/bgp-evpn-l2vpn-spine @@ -0,0 +1,48 @@ +set interfaces ethernet eth0 address '192.0.2.51/27' +set interfaces ethernet eth0 address '2001:db8::51/64' +set interfaces ethernet eth0 description 'Out-of-Band Managament Port' +set interfaces ethernet eth0 vrf 'MGMT' +set interfaces ethernet eth1 address '172.29.1.0/31' +set interfaces ethernet eth1 mtu '1600' +set interfaces ethernet eth2 address '172.29.1.2/31' +set interfaces ethernet eth2 mtu '1600' +set interfaces ethernet eth2 offload gro +set interfaces ethernet eth3 address '172.29.1.4/31' +set interfaces ethernet eth3 mtu '1600' +set interfaces ethernet eth3 offload gro +set interfaces loopback lo +set protocols bgp address-family ipv4-unicast maximum-paths ibgp '4' +set protocols bgp address-family ipv4-unicast redistribute connected +set protocols bgp listen range 172.29.1.0/24 peer-group 'evpn' +set protocols bgp parameters log-neighbor-changes +set protocols bgp peer-group evpn address-family ipv4-unicast route-reflector-client +set protocols bgp peer-group evpn address-family l2vpn-evpn route-reflector-client +set protocols bgp peer-group evpn capability dynamic +set protocols bgp peer-group evpn remote-as '65010' +set protocols bgp system-as '65010' +set service lldp interface all +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp listen-address '192.0.2.51' +set service ntp listen-address '2001:db8::51' +set service ntp server 0.de.pool.ntp.org prefer +set service ntp vrf 'MGMT' +set service ssh disable-host-validation +set service ssh vrf 'MGMT' +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set vrf name MGMT protocols static route 0.0.0.0/0 next-hop 192.0.2.62 +set vrf name MGMT protocols static route6 ::/0 next-hop 2001:db8::1 +set vrf name MGMT table '1000' diff --git a/smoketest/config-tests/bgp-evpn-l3vpn-pe-router b/smoketest/config-tests/bgp-evpn-l3vpn-pe-router new file mode 100644 index 0000000..7a2ec9f --- /dev/null +++ b/smoketest/config-tests/bgp-evpn-l3vpn-pe-router @@ -0,0 +1,123 @@ +set interfaces bridge br2000 address '10.1.1.1/24' +set interfaces bridge br2000 description 'customer blue' +set interfaces bridge br2000 member interface eth4 +set interfaces bridge br2000 member interface vxlan2000 +set interfaces bridge br2000 vrf 'blue' +set interfaces bridge br3000 address '10.2.1.1/24' +set interfaces bridge br3000 description 'customer red' +set interfaces bridge br3000 member interface eth5 +set interfaces bridge br3000 member interface vxlan3000 +set interfaces bridge br3000 vrf 'red' +set interfaces bridge br4000 address '10.3.1.1/24' +set interfaces bridge br4000 description 'customer green' +set interfaces bridge br4000 member interface eth6 +set interfaces bridge br4000 member interface vxlan4000 +set interfaces bridge br4000 vrf 'green' +set interfaces dummy dum0 address '172.29.255.1/32' +set interfaces ethernet eth0 address '192.0.2.59/27' +set interfaces ethernet eth0 address '2001:db8:ffff::59/64' +set interfaces ethernet eth0 description 'Out-of-Band Managament Port' +set interfaces ethernet eth0 offload gro +set interfaces ethernet eth0 vrf 'mgmt' +set interfaces ethernet eth1 address '172.29.0.2/31' +set interfaces ethernet eth1 description 'link to pe2' +set interfaces ethernet eth1 mtu '1600' +set interfaces ethernet eth1 offload gro +set interfaces ethernet eth2 disable +set interfaces ethernet eth2 offload gro +set interfaces ethernet eth3 address '172.29.0.6/31' +set interfaces ethernet eth3 description 'link to pe3' +set interfaces ethernet eth3 mtu '1600' +set interfaces ethernet eth3 offload gro +set interfaces ethernet eth4 description 'customer blue' +set interfaces ethernet eth4 offload gro +set interfaces ethernet eth5 description 'customer red' +set interfaces ethernet eth5 offload gro +set interfaces ethernet eth6 description 'customer green' +set interfaces ethernet eth6 offload gro +set interfaces loopback lo +set interfaces vxlan vxlan2000 mtu '1500' +set interfaces vxlan vxlan2000 parameters nolearning +set interfaces vxlan vxlan2000 port '4789' +set interfaces vxlan vxlan2000 source-address '172.29.255.1' +set interfaces vxlan vxlan2000 vni '2000' +set interfaces vxlan vxlan3000 mtu '1500' +set interfaces vxlan vxlan3000 parameters nolearning +set interfaces vxlan vxlan3000 port '4789' +set interfaces vxlan vxlan3000 source-address '172.29.255.1' +set interfaces vxlan vxlan3000 vni '3000' +set interfaces vxlan vxlan4000 mtu '1500' +set interfaces vxlan vxlan4000 parameters nolearning +set interfaces vxlan vxlan4000 port '4789' +set interfaces vxlan vxlan4000 source-address '172.29.255.1' +set interfaces vxlan vxlan4000 vni '4000' +set protocols bgp address-family l2vpn-evpn advertise ipv4 unicast +set protocols bgp address-family l2vpn-evpn advertise-all-vni +set protocols bgp neighbor 172.29.255.2 peer-group 'ibgp' +set protocols bgp neighbor 172.29.255.3 peer-group 'ibgp' +set protocols bgp parameters log-neighbor-changes +set protocols bgp parameters router-id '172.29.255.1' +set protocols bgp peer-group ibgp address-family l2vpn-evpn +set protocols bgp peer-group ibgp remote-as '100' +set protocols bgp peer-group ibgp update-source 'dum0' +set protocols bgp system-as '100' +set protocols ospf area 0 network '172.29.0.2/31' +set protocols ospf area 0 network '172.29.0.6/31' +set protocols ospf interface eth1 network 'point-to-point' +set protocols ospf interface eth1 passive disable +set protocols ospf interface eth3 network 'point-to-point' +set protocols ospf interface eth3 passive disable +set protocols ospf log-adjacency-changes detail +set protocols ospf parameters abr-type 'cisco' +set protocols ospf parameters router-id '172.29.255.1' +set protocols ospf passive-interface 'default' +set protocols ospf redistribute connected +set service lldp interface all +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp listen-address '192.0.2.59' +set service ntp listen-address '2001:db8:ffff::59' +set service ntp server 192.0.2.251 +set service ntp server 192.0.2.252 +set service ntp server 2001:db8::251 +set service ntp server 2001:db8::252 +set service ntp vrf 'mgmt' +set service ssh disable-host-validation +set service ssh port '22' +set service ssh vrf 'mgmt' +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system domain-name 'vyos.net' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system name-server '192.0.2.251' +set system name-server '192.0.2.252' +set system name-server '2001:db8::1' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set vrf name blue protocols bgp address-family ipv4-unicast redistribute connected +set vrf name blue protocols bgp address-family l2vpn-evpn advertise ipv4 unicast +set vrf name blue protocols bgp system-as '100' +set vrf name blue table '2000' +set vrf name blue vni '2000' +set vrf name green protocols bgp address-family ipv4-unicast redistribute connected +set vrf name green protocols bgp address-family l2vpn-evpn advertise ipv4 unicast +set vrf name green protocols bgp system-as '100' +set vrf name green table '4000' +set vrf name green vni '4000' +set vrf name mgmt protocols static route 0.0.0.0/0 next-hop 192.0.2.62 +set vrf name mgmt protocols static route6 ::/0 next-hop 2001:db8:ffff::1 +set vrf name mgmt table '1000' +set vrf name red protocols bgp address-family ipv4-unicast redistribute connected +set vrf name red protocols bgp address-family l2vpn-evpn advertise ipv4 unicast +set vrf name red protocols bgp system-as '100' +set vrf name red table '3000' +set vrf name red vni '3000' diff --git a/smoketest/config-tests/bgp-medium-confederation b/smoketest/config-tests/bgp-medium-confederation new file mode 100644 index 0000000..582e280 --- /dev/null +++ b/smoketest/config-tests/bgp-medium-confederation @@ -0,0 +1,73 @@ +set interfaces dummy dum0 address '1.1.1.1/32' +set interfaces dummy dum0 address '2001:db8::1/128' +set interfaces ethernet eth0 address '192.168.253.1/24' +set interfaces ethernet eth0 address 'fd52:100:200:fffe::1/64' +set interfaces ethernet eth1 +set interfaces ethernet eth2 +set policy route-map BGP-IN rule 10 action 'permit' +set policy route-map BGP-OUT rule 10 action 'permit' +set policy route-map BGP-REDISTRIBUTE rule 10 action 'deny' +set policy route-map DEFAULT-ZEBRA-IN rule 10 action 'deny' +set protocols bgp address-family ipv4-unicast redistribute connected route-map 'BGP-REDISTRIBUTE' +set protocols bgp address-family ipv4-unicast redistribute static route-map 'BGP-REDISTRIBUTE' +set protocols bgp address-family ipv6-unicast redistribute connected route-map 'BGP-REDISTRIBUTE' +set protocols bgp neighbor 192.168.253.14 peer-group 'WDC07' +set protocols bgp neighbor 192.168.253.16 peer-group 'WDC07' +set protocols bgp neighbor 192.168.253.17 peer-group 'WDC07' +set protocols bgp neighbor 192.168.253.18 peer-group 'WDC07' +set protocols bgp neighbor 192.168.253.19 peer-group 'WDC07' +set protocols bgp neighbor eth1 interface v6only peer-group 'BACKBONE' +set protocols bgp neighbor eth1 interface v6only remote-as '666' +set protocols bgp neighbor eth2 interface v6only peer-group 'BACKBONE' +set protocols bgp neighbor eth2 interface v6only remote-as '666' +set protocols bgp neighbor fd52:100:200:fffe::14 address-family ipv6-unicast +set protocols bgp neighbor fd52:100:200:fffe::14 peer-group 'WDC07v6' +set protocols bgp neighbor fd52:100:200:fffe::16 address-family ipv6-unicast +set protocols bgp neighbor fd52:100:200:fffe::16 peer-group 'WDC07v6' +set protocols bgp neighbor fd52:100:200:fffe::17 address-family ipv6-unicast +set protocols bgp neighbor fd52:100:200:fffe::17 peer-group 'WDC07v6' +set protocols bgp neighbor fd52:100:200:fffe::18 address-family ipv6-unicast +set protocols bgp neighbor fd52:100:200:fffe::18 peer-group 'WDC07v6' +set protocols bgp neighbor fd52:100:200:fffe::19 address-family ipv6-unicast +set protocols bgp neighbor fd52:100:200:fffe::19 peer-group 'WDC07v6' +set protocols bgp parameters bestpath as-path confed +set protocols bgp parameters bestpath as-path multipath-relax +set protocols bgp parameters confederation identifier '696' +set protocols bgp parameters confederation peers '668' +set protocols bgp parameters confederation peers '669' +set protocols bgp parameters confederation peers '666' +set protocols bgp parameters graceful-restart +set protocols bgp parameters router-id '192.168.253.15' +set protocols bgp peer-group BACKBONE address-family ipv4-unicast nexthop-self +set protocols bgp peer-group BACKBONE address-family ipv4-unicast route-map export 'BGP-OUT' +set protocols bgp peer-group BACKBONE address-family ipv4-unicast route-map import 'BGP-IN' +set protocols bgp peer-group BACKBONE address-family ipv4-unicast soft-reconfiguration inbound +set protocols bgp peer-group BACKBONE address-family ipv6-unicast nexthop-self +set protocols bgp peer-group BACKBONE address-family ipv6-unicast route-map export 'BGP-OUT' +set protocols bgp peer-group BACKBONE address-family ipv6-unicast route-map import 'BGP-IN' +set protocols bgp peer-group BACKBONE address-family ipv6-unicast soft-reconfiguration inbound +set protocols bgp peer-group BACKBONE capability extended-nexthop +set protocols bgp peer-group WDC07 address-family ipv4-unicast default-originate +set protocols bgp peer-group WDC07 address-family ipv4-unicast nexthop-self +set protocols bgp peer-group WDC07 address-family ipv4-unicast route-map export 'BGP-OUT' +set protocols bgp peer-group WDC07 address-family ipv4-unicast route-map import 'BGP-IN' +set protocols bgp peer-group WDC07 address-family ipv4-unicast soft-reconfiguration inbound +set protocols bgp peer-group WDC07 remote-as '670' +set protocols bgp peer-group WDC07 update-source 'dum0' +set protocols bgp peer-group WDC07v6 address-family ipv6-unicast default-originate +set protocols bgp peer-group WDC07v6 address-family ipv6-unicast nexthop-self +set protocols bgp peer-group WDC07v6 address-family ipv6-unicast route-map export 'BGP-OUT' +set protocols bgp peer-group WDC07v6 address-family ipv6-unicast route-map import 'BGP-IN' +set protocols bgp peer-group WDC07v6 address-family ipv6-unicast soft-reconfiguration inbound +set protocols bgp peer-group WDC07v6 remote-as '670' +set protocols bgp peer-group WDC07v6 update-source 'dum0' +set protocols bgp system-as '670' +set system config-management commit-revisions '200' +set system console device ttyS0 speed '115200' +set system domain-name 'vyos.net' +set system host-name 'vyos' +set system ip protocol bgp route-map 'DEFAULT-ZEBRA-IN' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'notice' +set system syslog global facility local7 level 'debug' diff --git a/smoketest/config-tests/bgp-rpki b/smoketest/config-tests/bgp-rpki new file mode 100644 index 0000000..44e95ae --- /dev/null +++ b/smoketest/config-tests/bgp-rpki @@ -0,0 +1,43 @@ +set interfaces ethernet eth0 address '192.0.2.100/25' +set interfaces ethernet eth0 address '2001:db8::ffff/64' +set interfaces ethernet eth1 address '100.64.0.1/24' +set interfaces loopback lo +set policy route-map ebgp-transit-rpki rule 10 action 'deny' +set policy route-map ebgp-transit-rpki rule 10 match rpki 'invalid' +set policy route-map ebgp-transit-rpki rule 20 action 'permit' +set policy route-map ebgp-transit-rpki rule 20 match rpki 'notfound' +set policy route-map ebgp-transit-rpki rule 20 set local-preference '20' +set policy route-map ebgp-transit-rpki rule 30 action 'permit' +set policy route-map ebgp-transit-rpki rule 30 match rpki 'valid' +set policy route-map ebgp-transit-rpki rule 30 set local-preference '100' +set policy route-map ebgp-transit-rpki rule 40 action 'permit' +set policy route-map ebgp-transit-rpki rule 40 set extcommunity rt '192.0.2.100:100' +set policy route-map ebgp-transit-rpki rule 40 set extcommunity soo '64500:100' +set protocols bgp neighbor 1.2.3.4 address-family ipv4-unicast nexthop-self +set protocols bgp neighbor 1.2.3.4 address-family ipv4-unicast route-map import 'ebgp-transit-rpki' +set protocols bgp neighbor 1.2.3.4 remote-as '10' +set protocols bgp system-as '64500' +set protocols rpki cache 192.0.2.10 port '3323' +set protocols rpki cache 192.0.2.10 preference '1' +set protocols static route 0.0.0.0/0 next-hop 192.0.2.1 +set protocols static route6 ::/0 next-hop 2001:db8::1 +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server 0.pool.ntp.org +set service ntp server 1.pool.ntp.org +set service ntp server 2.pool.ntp.org +set service ssh +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' diff --git a/smoketest/config-tests/bgp-small-internet-exchange b/smoketest/config-tests/bgp-small-internet-exchange new file mode 100644 index 0000000..a9dce4d --- /dev/null +++ b/smoketest/config-tests/bgp-small-internet-exchange @@ -0,0 +1,209 @@ +set interfaces ethernet eth0 address '192.0.2.100/25' +set interfaces ethernet eth0 address '2001:db8:aaaa::ffff/64' +set interfaces ethernet eth1 address '192.0.2.200/25' +set interfaces ethernet eth1 address '2001:db8:bbbb::ffff/64' +set interfaces loopback lo +set policy as-path-list bogon-asns rule 10 action 'permit' +set policy as-path-list bogon-asns rule 10 description 'RFC 7607' +set policy as-path-list bogon-asns rule 10 regex '_0_' +set policy as-path-list bogon-asns rule 20 action 'permit' +set policy as-path-list bogon-asns rule 20 description 'RFC 4893' +set policy as-path-list bogon-asns rule 20 regex '_23456_' +set policy as-path-list bogon-asns rule 30 action 'permit' +set policy as-path-list bogon-asns rule 30 description 'RFC 5398/6996/7300' +set policy as-path-list bogon-asns rule 30 regex '_6449[6-9]_|_65[0-4][0-9][0-9]_|_655[0-4][0-9]_|_6555[0-1]_' +set policy as-path-list bogon-asns rule 40 action 'permit' +set policy as-path-list bogon-asns rule 40 description 'IANA reserved' +set policy as-path-list bogon-asns rule 40 regex '_6555[2-9]_|_655[6-9][0-9]_|_65[6-9][0-9][0-9]_|_6[6-9][0-9][0-9][0-]_|_[7-9][0-9][0-9][0-9][0-9]_|_1[0-2][0-9][0-9][0-9][0-9]_|_130[0-9][0-9][0-9]_|_1310[0-6][0-9]_|_13107[01]_' +set policy prefix-list IX-out-v4 rule 10 action 'permit' +set policy prefix-list IX-out-v4 rule 10 prefix '10.0.0.0/23' +set policy prefix-list IX-out-v4 rule 20 action 'permit' +set policy prefix-list IX-out-v4 rule 20 prefix '10.0.128.0/23' +set policy prefix-list bogon-v4 rule 10 action 'permit' +set policy prefix-list bogon-v4 rule 10 le '32' +set policy prefix-list bogon-v4 rule 10 prefix '0.0.0.0/8' +set policy prefix-list bogon-v4 rule 20 action 'permit' +set policy prefix-list bogon-v4 rule 20 le '32' +set policy prefix-list bogon-v4 rule 20 prefix '10.0.0.0/8' +set policy prefix-list bogon-v4 rule 30 action 'permit' +set policy prefix-list bogon-v4 rule 30 le '32' +set policy prefix-list bogon-v4 rule 30 prefix '100.64.0.0/10' +set policy prefix-list bogon-v4 rule 40 action 'permit' +set policy prefix-list bogon-v4 rule 40 le '32' +set policy prefix-list bogon-v4 rule 40 prefix '127.0.0.0/8' +set policy prefix-list bogon-v4 rule 50 action 'permit' +set policy prefix-list bogon-v4 rule 50 le '32' +set policy prefix-list bogon-v4 rule 50 prefix '169.254.0.0/16' +set policy prefix-list bogon-v4 rule 60 action 'permit' +set policy prefix-list bogon-v4 rule 60 le '32' +set policy prefix-list bogon-v4 rule 60 prefix '172.16.0.0/12' +set policy prefix-list bogon-v4 rule 70 action 'permit' +set policy prefix-list bogon-v4 rule 70 le '32' +set policy prefix-list bogon-v4 rule 70 prefix '192.0.2.0/24' +set policy prefix-list bogon-v4 rule 80 action 'permit' +set policy prefix-list bogon-v4 rule 80 le '32' +set policy prefix-list bogon-v4 rule 80 prefix '192.88.99.0/24' +set policy prefix-list bogon-v4 rule 90 action 'permit' +set policy prefix-list bogon-v4 rule 90 le '32' +set policy prefix-list bogon-v4 rule 90 prefix '192.168.0.0/16' +set policy prefix-list bogon-v4 rule 100 action 'permit' +set policy prefix-list bogon-v4 rule 100 le '32' +set policy prefix-list bogon-v4 rule 100 prefix '198.18.0.0/15' +set policy prefix-list bogon-v4 rule 110 action 'permit' +set policy prefix-list bogon-v4 rule 110 le '32' +set policy prefix-list bogon-v4 rule 110 prefix '198.51.100.0/24' +set policy prefix-list bogon-v4 rule 120 action 'permit' +set policy prefix-list bogon-v4 rule 120 le '32' +set policy prefix-list bogon-v4 rule 120 prefix '203.0.113.0/24' +set policy prefix-list bogon-v4 rule 130 action 'permit' +set policy prefix-list bogon-v4 rule 130 le '32' +set policy prefix-list bogon-v4 rule 130 prefix '224.0.0.0/4' +set policy prefix-list bogon-v4 rule 140 action 'permit' +set policy prefix-list bogon-v4 rule 140 le '32' +set policy prefix-list bogon-v4 rule 140 prefix '240.0.0.0/4' +set policy prefix-list prefix-filter-v4 rule 10 action 'permit' +set policy prefix-list prefix-filter-v4 rule 10 ge '25' +set policy prefix-list prefix-filter-v4 rule 10 prefix '0.0.0.0/0' +set policy prefix-list6 IX-out-v6 rule 10 action 'permit' +set policy prefix-list6 IX-out-v6 rule 10 prefix '2001:db8:100::/40' +set policy prefix-list6 IX-out-v6 rule 20 action 'permit' +set policy prefix-list6 IX-out-v6 rule 20 prefix '2001:db8:200::/40' +set policy prefix-list6 bogon-v6 rule 10 action 'permit' +set policy prefix-list6 bogon-v6 rule 10 description 'RFC 4291 IPv4-compatible, loopback, et al' +set policy prefix-list6 bogon-v6 rule 10 le '128' +set policy prefix-list6 bogon-v6 rule 10 prefix '::/8' +set policy prefix-list6 bogon-v6 rule 20 action 'permit' +set policy prefix-list6 bogon-v6 rule 20 description 'RFC 6666 Discard-Only' +set policy prefix-list6 bogon-v6 rule 20 le '128' +set policy prefix-list6 bogon-v6 rule 20 prefix '0100::/64' +set policy prefix-list6 bogon-v6 rule 30 action 'permit' +set policy prefix-list6 bogon-v6 rule 30 description 'RFC 5180 BMWG' +set policy prefix-list6 bogon-v6 rule 30 le '128' +set policy prefix-list6 bogon-v6 rule 30 prefix '2001:2::/48' +set policy prefix-list6 bogon-v6 rule 40 action 'permit' +set policy prefix-list6 bogon-v6 rule 40 description 'RFC 4843 ORCHID' +set policy prefix-list6 bogon-v6 rule 40 le '128' +set policy prefix-list6 bogon-v6 rule 40 prefix '2001:10::/28' +set policy prefix-list6 bogon-v6 rule 50 action 'permit' +set policy prefix-list6 bogon-v6 rule 50 description 'RFC 3849 documentation' +set policy prefix-list6 bogon-v6 rule 50 le '128' +set policy prefix-list6 bogon-v6 rule 50 prefix '2001:db8::/32' +set policy prefix-list6 bogon-v6 rule 60 action 'permit' +set policy prefix-list6 bogon-v6 rule 60 description 'RFC 7526 6to4 anycast relay' +set policy prefix-list6 bogon-v6 rule 60 le '128' +set policy prefix-list6 bogon-v6 rule 60 prefix '2002::/16' +set policy prefix-list6 bogon-v6 rule 70 action 'permit' +set policy prefix-list6 bogon-v6 rule 70 description 'RFC 3701 old 6bone' +set policy prefix-list6 bogon-v6 rule 70 le '128' +set policy prefix-list6 bogon-v6 rule 70 prefix '3ffe::/16' +set policy prefix-list6 bogon-v6 rule 80 action 'permit' +set policy prefix-list6 bogon-v6 rule 80 description 'RFC 4193 unique local unicast' +set policy prefix-list6 bogon-v6 rule 80 le '128' +set policy prefix-list6 bogon-v6 rule 80 prefix 'fc00::/7' +set policy prefix-list6 bogon-v6 rule 90 action 'permit' +set policy prefix-list6 bogon-v6 rule 90 description 'RFC 4291 link local unicast' +set policy prefix-list6 bogon-v6 rule 90 le '128' +set policy prefix-list6 bogon-v6 rule 90 prefix 'fe80::/10' +set policy prefix-list6 bogon-v6 rule 100 action 'permit' +set policy prefix-list6 bogon-v6 rule 100 description 'RFC 3879 old site local unicast' +set policy prefix-list6 bogon-v6 rule 100 le '128' +set policy prefix-list6 bogon-v6 rule 100 prefix 'fec0::/10' +set policy prefix-list6 bogon-v6 rule 110 action 'permit' +set policy prefix-list6 bogon-v6 rule 110 description 'RFC 4291 multicast' +set policy prefix-list6 bogon-v6 rule 110 le '128' +set policy prefix-list6 bogon-v6 rule 110 prefix 'ff00::/8' +set policy prefix-list6 prefix-filter-v6 rule 10 action 'permit' +set policy prefix-list6 prefix-filter-v6 rule 10 ge '49' +set policy prefix-list6 prefix-filter-v6 rule 10 prefix '::/0' +set policy route-map IX-in-v4 rule 5 action 'permit' +set policy route-map IX-in-v4 rule 5 call 'eBGP-IN-v4' +set policy route-map IX-in-v4 rule 5 on-match next +set policy route-map IX-in-v4 rule 10 action 'permit' +set policy route-map IX-in-v6 rule 5 action 'permit' +set policy route-map IX-in-v6 rule 5 call 'eBGP-IN-v6' +set policy route-map IX-in-v6 rule 5 on-match next +set policy route-map IX-in-v6 rule 10 action 'permit' +set policy route-map IX-out-v4 rule 10 action 'permit' +set policy route-map IX-out-v4 rule 10 match ip address prefix-list 'IX-out-v4' +set policy route-map IX-out-v6 rule 10 action 'permit' +set policy route-map IX-out-v6 rule 10 match ipv6 address prefix-list 'IX-out-v6' +set policy route-map eBGP-IN-v4 rule 10 action 'deny' +set policy route-map eBGP-IN-v4 rule 10 match as-path 'bogon-asns' +set policy route-map eBGP-IN-v4 rule 20 action 'deny' +set policy route-map eBGP-IN-v4 rule 20 match ip address prefix-list 'bogon-v4' +set policy route-map eBGP-IN-v4 rule 30 action 'deny' +set policy route-map eBGP-IN-v4 rule 30 match ip address prefix-list 'prefix-filter-v4' +set policy route-map eBGP-IN-v4 rule 40 action 'permit' +set policy route-map eBGP-IN-v4 rule 40 set local-preference '100' +set policy route-map eBGP-IN-v4 rule 40 set metric '0' +set policy route-map eBGP-IN-v6 rule 10 action 'deny' +set policy route-map eBGP-IN-v6 rule 10 match as-path 'bogon-asns' +set policy route-map eBGP-IN-v6 rule 20 action 'deny' +set policy route-map eBGP-IN-v6 rule 20 match ipv6 address prefix-list 'bogon-v6' +set policy route-map eBGP-IN-v6 rule 30 action 'deny' +set policy route-map eBGP-IN-v6 rule 30 match ipv6 address prefix-list 'prefix-filter-v6' +set policy route-map eBGP-IN-v6 rule 31 action 'deny' +set policy route-map eBGP-IN-v6 rule 31 match ipv6 nexthop address '2001:db8::1' +set policy route-map eBGP-IN-v6 rule 40 action 'permit' +set policy route-map eBGP-IN-v6 rule 40 set local-preference '100' +set policy route-map eBGP-IN-v6 rule 40 set metric '0' +set protocols bgp address-family ipv4-unicast network 10.0.0.0/23 +set protocols bgp address-family ipv4-unicast network 10.0.128.0/23 +set protocols bgp address-family ipv6-unicast network 2001:db8:100::/40 +set protocols bgp address-family ipv6-unicast network 2001:db8:200::/40 +set protocols bgp neighbor 192.0.2.1 description 'Peering: IX-1 (Route Server)' +set protocols bgp neighbor 192.0.2.1 peer-group 'IXPeeringIPv4' +set protocols bgp neighbor 192.0.2.1 remote-as '65020' +set protocols bgp neighbor 192.0.2.2 description 'Peering: IX-1 (Route Server)' +set protocols bgp neighbor 192.0.2.2 peer-group 'IXPeeringIPv4' +set protocols bgp neighbor 192.0.2.2 remote-as '65020' +set protocols bgp neighbor 192.0.2.3 description 'Peering: IX-1 (Route Server)' +set protocols bgp neighbor 192.0.2.3 peer-group 'IXPeeringIPv4' +set protocols bgp neighbor 192.0.2.3 remote-as '65020' +set protocols bgp neighbor 192.0.2.129 description 'Peering: IX-2 (Route Server)' +set protocols bgp neighbor 192.0.2.129 peer-group 'IXPeeringIPv4' +set protocols bgp neighbor 192.0.2.129 remote-as '65030' +set protocols bgp neighbor 192.0.2.130 description 'Peering: IX-2 (Route Server)' +set protocols bgp neighbor 192.0.2.130 peer-group 'IXPeeringIPv4' +set protocols bgp neighbor 192.0.2.130 remote-as '65030' +set protocols bgp neighbor 2001:db8:aaaa::1 description 'Peering: IX-1 (Route Server)' +set protocols bgp neighbor 2001:db8:aaaa::1 peer-group 'IXPeeringIPv6' +set protocols bgp neighbor 2001:db8:aaaa::1 remote-as '65020' +set protocols bgp neighbor 2001:db8:aaaa::2 description 'Peering: IX-1 (Route Server)' +set protocols bgp neighbor 2001:db8:aaaa::2 peer-group 'IXPeeringIPv6' +set protocols bgp neighbor 2001:db8:aaaa::2 remote-as '65020' +set protocols bgp neighbor 2001:db8:bbbb::1 description 'Peering: IX-2 (Route Server)' +set protocols bgp neighbor 2001:db8:bbbb::1 peer-group 'IXPeeringIPv6' +set protocols bgp neighbor 2001:db8:bbbb::1 remote-as '65030' +set protocols bgp neighbor 2001:db8:bbbb::2 description 'Peering: IX-2 (Route Server)' +set protocols bgp neighbor 2001:db8:bbbb::2 peer-group 'IXPeeringIPv6' +set protocols bgp neighbor 2001:db8:bbbb::2 remote-as '65030' +set protocols bgp peer-group IXPeeringIPv4 address-family ipv4-unicast route-map export 'IX-out-v4' +set protocols bgp peer-group IXPeeringIPv4 address-family ipv4-unicast soft-reconfiguration inbound +set protocols bgp peer-group IXPeeringIPv6 address-family ipv6-unicast route-map export 'IX-out-v6' +set protocols bgp peer-group IXPeeringIPv6 address-family ipv6-unicast soft-reconfiguration inbound +set protocols bgp system-as '65000' +set protocols static route 10.0.0.0/23 blackhole distance '250' +set protocols static route 10.0.128.0/23 blackhole distance '250' +set protocols static route6 2001:db8:100::/40 blackhole distance '250' +set protocols static route6 2001:db8:200::/40 blackhole distance '250' +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server 0.pool.ntp.org +set service ntp server 1.pool.ntp.org +set service ntp server 2.pool.ntp.org +set service ssh +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' diff --git a/smoketest/config-tests/bgp-small-ipv4-unicast b/smoketest/config-tests/bgp-small-ipv4-unicast new file mode 100644 index 0000000..b8c0e12 --- /dev/null +++ b/smoketest/config-tests/bgp-small-ipv4-unicast @@ -0,0 +1,32 @@ +set interfaces ethernet eth0 address '192.0.2.1/24' +set interfaces ethernet eth0 address '2001:db8::1/64' +set interfaces loopback lo +set protocols bgp address-family ipv4-unicast network 10.0.150.0/23 +set protocols bgp address-family ipv6-unicast network 2001:db8:200::/40 +set protocols bgp neighbor 192.0.2.10 address-family ipv4-unicast +set protocols bgp neighbor 192.0.2.10 remote-as '65010' +set protocols bgp neighbor 192.0.2.11 address-family ipv4-unicast +set protocols bgp neighbor 192.0.2.11 remote-as '65011' +set protocols bgp neighbor 2001:db8::10 address-family ipv4-unicast +set protocols bgp neighbor 2001:db8::10 remote-as '65010' +set protocols bgp neighbor 2001:db8::11 address-family ipv4-unicast +set protocols bgp neighbor 2001:db8::11 remote-as '65011' +set protocols bgp parameters log-neighbor-changes +set protocols bgp system-as '65001' +set service ssh disable-host-validation +set service ssh port '22' +set system config-management commit-revisions '200' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system domain-name 'vyos.net' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'notice' +set system syslog global facility local7 level 'debug' diff --git a/smoketest/config-tests/cluster-basic b/smoketest/config-tests/cluster-basic new file mode 100644 index 0000000..744c117 --- /dev/null +++ b/smoketest/config-tests/cluster-basic @@ -0,0 +1,21 @@ +set high-availability vrrp group VyOS address 192.0.2.10/24 +set high-availability vrrp group VyOS address 192.0.2.20/24 +set high-availability vrrp group VyOS advertise-interval '1' +set high-availability vrrp group VyOS authentication password 'qwerty' +set high-availability vrrp group VyOS authentication type 'plaintext-password' +set high-availability vrrp group VyOS interface 'eth1' +set high-availability vrrp group VyOS vrid '1' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth1 address '192.0.2.1/24' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 speed 'auto' +set interfaces loopback lo +set system config-management commit-revisions '100' +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set system time-zone 'Antarctica/South_Pole' diff --git a/smoketest/config-tests/container-simple b/smoketest/config-tests/container-simple new file mode 100644 index 0000000..fcc6651 --- /dev/null +++ b/smoketest/config-tests/container-simple @@ -0,0 +1,18 @@ +set container name c01 allow-host-networks +set container name c01 capability 'net-bind-service' +set container name c01 capability 'net-raw' +set container name c01 image 'busybox:stable' +set container name c02 allow-host-networks +set container name c02 allow-host-pid +set container name c02 capability 'sys-time' +set container name c02 image 'busybox:stable' +set container name c02 sysctl parameter kernel.msgmax value '8192' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 speed 'auto' +set system config-management commit-revisions '50' +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$r/Yw/07NXNY$/ZB.Rjf9jxEV.BYoDyLdH.kH14rU52pOBtrX.4S34qlPt77chflCHvpTCq9a6huLzwaMR50rEICzA5GoIRZlM0' +set system login user vyos authentication plaintext-password '' diff --git a/smoketest/config-tests/dialup-router-complex b/smoketest/config-tests/dialup-router-complex new file mode 100644 index 0000000..4416ef8 --- /dev/null +++ b/smoketest/config-tests/dialup-router-complex @@ -0,0 +1,740 @@ +set firewall global-options all-ping 'enable' +set firewall global-options broadcast-ping 'disable' +set firewall global-options ip-src-route 'disable' +set firewall global-options ipv6-receive-redirects 'disable' +set firewall global-options ipv6-src-route 'disable' +set firewall global-options log-martians 'enable' +set firewall global-options receive-redirects 'disable' +set firewall global-options send-redirects 'enable' +set firewall global-options source-validation 'disable' +set firewall global-options syn-cookies 'enable' +set firewall global-options timeout icmp '30' +set firewall global-options timeout other '600' +set firewall global-options timeout udp other '300' +set firewall global-options timeout udp stream '300' +set firewall global-options twa-hazards-protection 'disable' +set firewall group address-group AUDIO-STREAM address '172.16.35.20' +set firewall group address-group AUDIO-STREAM address '172.16.35.21' +set firewall group address-group AUDIO-STREAM address '172.16.35.22' +set firewall group address-group AUDIO-STREAM address '172.16.35.23' +set firewall group address-group DMZ-RDP-SERVER address '172.16.33.40' +set firewall group address-group DMZ-WEBSERVER address '172.16.36.10' +set firewall group address-group DMZ-WEBSERVER address '172.16.36.40' +set firewall group address-group DMZ-WEBSERVER address '172.16.36.20' +set firewall group address-group DOMAIN-CONTROLLER address '172.16.100.10' +set firewall group address-group DOMAIN-CONTROLLER address '172.16.100.20' +set firewall group address-group MEDIA-STREAMING-CLIENTS address '172.16.35.241' +set firewall group address-group MEDIA-STREAMING-CLIENTS address '172.16.35.242' +set firewall group address-group MEDIA-STREAMING-CLIENTS address '172.16.35.243' +set firewall group ipv6-network-group LOCAL-ADDRESSES network 'ff02::/64' +set firewall group ipv6-network-group LOCAL-ADDRESSES network 'fe80::/10' +set firewall group network-group SSH-IN-ALLOW network '192.0.2.0/24' +set firewall group network-group SSH-IN-ALLOW network '10.0.0.0/8' +set firewall group network-group SSH-IN-ALLOW network '172.16.0.0/12' +set firewall group network-group SSH-IN-ALLOW network '192.168.0.0/16' +set firewall group port-group SMART-TV-PORTS port '5005-5006' +set firewall group port-group SMART-TV-PORTS port '80' +set firewall group port-group SMART-TV-PORTS port '443' +set firewall group port-group SMART-TV-PORTS port '3722' +set firewall ipv4 name DMZ-GUEST default-action 'drop' +set firewall ipv4 name DMZ-GUEST default-log +set firewall ipv4 name DMZ-GUEST rule 1 action 'return' +set firewall ipv4 name DMZ-GUEST rule 1 state 'established' +set firewall ipv4 name DMZ-GUEST rule 1 state 'related' +set firewall ipv4 name DMZ-GUEST rule 2 action 'drop' +set firewall ipv4 name DMZ-GUEST rule 2 log +set firewall ipv4 name DMZ-GUEST rule 2 state 'invalid' +set firewall ipv4 name DMZ-LAN default-action 'drop' +set firewall ipv4 name DMZ-LAN default-log +set firewall ipv4 name DMZ-LAN rule 1 action 'return' +set firewall ipv4 name DMZ-LAN rule 1 state 'established' +set firewall ipv4 name DMZ-LAN rule 1 state 'related' +set firewall ipv4 name DMZ-LAN rule 2 action 'drop' +set firewall ipv4 name DMZ-LAN rule 2 log +set firewall ipv4 name DMZ-LAN rule 2 state 'invalid' +set firewall ipv4 name DMZ-LAN rule 100 action 'return' +set firewall ipv4 name DMZ-LAN rule 100 description 'NTP and LDAP to AD DC' +set firewall ipv4 name DMZ-LAN rule 100 destination group address-group 'DOMAIN-CONTROLLER' +set firewall ipv4 name DMZ-LAN rule 100 destination port '123,389,636' +set firewall ipv4 name DMZ-LAN rule 100 protocol 'tcp_udp' +set firewall ipv4 name DMZ-LAN rule 300 action 'return' +set firewall ipv4 name DMZ-LAN rule 300 destination group address-group 'DMZ-RDP-SERVER' +set firewall ipv4 name DMZ-LAN rule 300 destination port '3389' +set firewall ipv4 name DMZ-LAN rule 300 protocol 'tcp_udp' +set firewall ipv4 name DMZ-LAN rule 300 source address '172.16.36.20' +set firewall ipv4 name DMZ-LOCAL default-action 'drop' +set firewall ipv4 name DMZ-LOCAL default-log +set firewall ipv4 name DMZ-LOCAL rule 1 action 'return' +set firewall ipv4 name DMZ-LOCAL rule 1 state 'established' +set firewall ipv4 name DMZ-LOCAL rule 1 state 'related' +set firewall ipv4 name DMZ-LOCAL rule 2 action 'drop' +set firewall ipv4 name DMZ-LOCAL rule 2 log +set firewall ipv4 name DMZ-LOCAL rule 2 state 'invalid' +set firewall ipv4 name DMZ-LOCAL rule 50 action 'return' +set firewall ipv4 name DMZ-LOCAL rule 50 destination address '172.16.254.30' +set firewall ipv4 name DMZ-LOCAL rule 50 destination port '53' +set firewall ipv4 name DMZ-LOCAL rule 50 protocol 'tcp_udp' +set firewall ipv4 name DMZ-LOCAL rule 123 action 'return' +set firewall ipv4 name DMZ-LOCAL rule 123 destination port '123' +set firewall ipv4 name DMZ-LOCAL rule 123 protocol 'udp' +set firewall ipv4 name DMZ-LOCAL rule 800 action 'drop' +set firewall ipv4 name DMZ-LOCAL rule 800 description 'SSH anti brute force' +set firewall ipv4 name DMZ-LOCAL rule 800 destination port 'ssh' +set firewall ipv4 name DMZ-LOCAL rule 800 log +set firewall ipv4 name DMZ-LOCAL rule 800 protocol 'tcp' +set firewall ipv4 name DMZ-LOCAL rule 800 recent count '4' +set firewall ipv4 name DMZ-LOCAL rule 800 recent time 'minute' +set firewall ipv4 name DMZ-LOCAL rule 800 state 'new' +set firewall ipv4 name DMZ-WAN default-action 'return' +set firewall ipv4 name GUEST-DMZ default-action 'drop' +set firewall ipv4 name GUEST-DMZ default-log +set firewall ipv4 name GUEST-DMZ rule 1 action 'return' +set firewall ipv4 name GUEST-DMZ rule 1 state 'established' +set firewall ipv4 name GUEST-DMZ rule 1 state 'related' +set firewall ipv4 name GUEST-DMZ rule 2 action 'drop' +set firewall ipv4 name GUEST-DMZ rule 2 log +set firewall ipv4 name GUEST-DMZ rule 2 state 'invalid' +set firewall ipv4 name GUEST-DMZ rule 100 action 'return' +set firewall ipv4 name GUEST-DMZ rule 100 destination port '80,443' +set firewall ipv4 name GUEST-DMZ rule 100 protocol 'tcp' +set firewall ipv4 name GUEST-IOT default-action 'drop' +set firewall ipv4 name GUEST-IOT default-log +set firewall ipv4 name GUEST-IOT rule 1 action 'return' +set firewall ipv4 name GUEST-IOT rule 1 state 'established' +set firewall ipv4 name GUEST-IOT rule 1 state 'related' +set firewall ipv4 name GUEST-IOT rule 2 action 'drop' +set firewall ipv4 name GUEST-IOT rule 2 log +set firewall ipv4 name GUEST-IOT rule 2 state 'invalid' +set firewall ipv4 name GUEST-IOT rule 100 action 'return' +set firewall ipv4 name GUEST-IOT rule 100 description 'MEDIA-STREAMING-CLIENTS Devices to GUEST' +set firewall ipv4 name GUEST-IOT rule 100 destination group address-group 'MEDIA-STREAMING-CLIENTS' +set firewall ipv4 name GUEST-IOT rule 100 protocol 'tcp_udp' +set firewall ipv4 name GUEST-IOT rule 110 action 'return' +set firewall ipv4 name GUEST-IOT rule 110 description 'AUDIO-STREAM Devices to GUEST' +set firewall ipv4 name GUEST-IOT rule 110 destination group address-group 'AUDIO-STREAM' +set firewall ipv4 name GUEST-IOT rule 110 protocol 'tcp_udp' +set firewall ipv4 name GUEST-IOT rule 200 action 'return' +set firewall ipv4 name GUEST-IOT rule 200 description 'MCAST relay' +set firewall ipv4 name GUEST-IOT rule 200 destination address '224.0.0.251' +set firewall ipv4 name GUEST-IOT rule 200 destination port '5353' +set firewall ipv4 name GUEST-IOT rule 200 protocol 'udp' +set firewall ipv4 name GUEST-IOT rule 300 action 'return' +set firewall ipv4 name GUEST-IOT rule 300 description 'BCAST relay' +set firewall ipv4 name GUEST-IOT rule 300 destination port '1900' +set firewall ipv4 name GUEST-IOT rule 300 protocol 'udp' +set firewall ipv4 name GUEST-LAN default-action 'drop' +set firewall ipv4 name GUEST-LAN default-log +set firewall ipv4 name GUEST-LAN rule 1 action 'return' +set firewall ipv4 name GUEST-LAN rule 1 state 'established' +set firewall ipv4 name GUEST-LAN rule 1 state 'related' +set firewall ipv4 name GUEST-LAN rule 2 action 'drop' +set firewall ipv4 name GUEST-LAN rule 2 log +set firewall ipv4 name GUEST-LAN rule 2 state 'invalid' +set firewall ipv4 name GUEST-LOCAL default-action 'drop' +set firewall ipv4 name GUEST-LOCAL default-log +set firewall ipv4 name GUEST-LOCAL rule 1 action 'return' +set firewall ipv4 name GUEST-LOCAL rule 1 state 'established' +set firewall ipv4 name GUEST-LOCAL rule 1 state 'related' +set firewall ipv4 name GUEST-LOCAL rule 2 action 'drop' +set firewall ipv4 name GUEST-LOCAL rule 2 log +set firewall ipv4 name GUEST-LOCAL rule 2 state 'invalid' +set firewall ipv4 name GUEST-LOCAL rule 10 action 'return' +set firewall ipv4 name GUEST-LOCAL rule 10 description 'DNS' +set firewall ipv4 name GUEST-LOCAL rule 10 destination address '172.31.0.254' +set firewall ipv4 name GUEST-LOCAL rule 10 destination port '53' +set firewall ipv4 name GUEST-LOCAL rule 10 protocol 'tcp_udp' +set firewall ipv4 name GUEST-LOCAL rule 11 action 'return' +set firewall ipv4 name GUEST-LOCAL rule 11 description 'DHCP' +set firewall ipv4 name GUEST-LOCAL rule 11 destination port '67' +set firewall ipv4 name GUEST-LOCAL rule 11 protocol 'udp' +set firewall ipv4 name GUEST-LOCAL rule 15 action 'return' +set firewall ipv4 name GUEST-LOCAL rule 15 destination address '172.31.0.254' +set firewall ipv4 name GUEST-LOCAL rule 15 protocol 'icmp' +set firewall ipv4 name GUEST-LOCAL rule 200 action 'return' +set firewall ipv4 name GUEST-LOCAL rule 200 description 'MCAST relay' +set firewall ipv4 name GUEST-LOCAL rule 200 destination address '224.0.0.251' +set firewall ipv4 name GUEST-LOCAL rule 200 destination port '5353' +set firewall ipv4 name GUEST-LOCAL rule 200 protocol 'udp' +set firewall ipv4 name GUEST-LOCAL rule 210 action 'return' +set firewall ipv4 name GUEST-LOCAL rule 210 description 'AUDIO-STREAM Broadcast' +set firewall ipv4 name GUEST-LOCAL rule 210 destination port '1900' +set firewall ipv4 name GUEST-LOCAL rule 210 protocol 'udp' +set firewall ipv4 name GUEST-WAN default-action 'drop' +set firewall ipv4 name GUEST-WAN default-log +set firewall ipv4 name GUEST-WAN rule 1 action 'return' +set firewall ipv4 name GUEST-WAN rule 1 state 'established' +set firewall ipv4 name GUEST-WAN rule 1 state 'related' +set firewall ipv4 name GUEST-WAN rule 2 action 'drop' +set firewall ipv4 name GUEST-WAN rule 2 log +set firewall ipv4 name GUEST-WAN rule 2 state 'invalid' +set firewall ipv4 name GUEST-WAN rule 25 action 'return' +set firewall ipv4 name GUEST-WAN rule 25 description 'SMTP' +set firewall ipv4 name GUEST-WAN rule 25 destination port '25,587' +set firewall ipv4 name GUEST-WAN rule 25 protocol 'tcp' +set firewall ipv4 name GUEST-WAN rule 53 action 'return' +set firewall ipv4 name GUEST-WAN rule 53 destination port '53' +set firewall ipv4 name GUEST-WAN rule 53 protocol 'tcp_udp' +set firewall ipv4 name GUEST-WAN rule 60 action 'return' +set firewall ipv4 name GUEST-WAN rule 60 source address '172.31.0.200' +set firewall ipv4 name GUEST-WAN rule 80 action 'return' +set firewall ipv4 name GUEST-WAN rule 80 source address '172.31.0.200' +set firewall ipv4 name GUEST-WAN rule 100 action 'return' +set firewall ipv4 name GUEST-WAN rule 100 protocol 'icmp' +set firewall ipv4 name GUEST-WAN rule 110 action 'return' +set firewall ipv4 name GUEST-WAN rule 110 description 'POP3' +set firewall ipv4 name GUEST-WAN rule 110 destination port '110,995' +set firewall ipv4 name GUEST-WAN rule 110 limit rate '10/minute' +set firewall ipv4 name GUEST-WAN rule 110 protocol 'tcp' +set firewall ipv4 name GUEST-WAN rule 123 action 'return' +set firewall ipv4 name GUEST-WAN rule 123 description 'NTP Client' +set firewall ipv4 name GUEST-WAN rule 123 destination port '123' +set firewall ipv4 name GUEST-WAN rule 123 protocol 'udp' +set firewall ipv4 name GUEST-WAN rule 143 action 'return' +set firewall ipv4 name GUEST-WAN rule 143 description 'IMAP' +set firewall ipv4 name GUEST-WAN rule 143 destination port '143,993' +set firewall ipv4 name GUEST-WAN rule 143 protocol 'tcp' +set firewall ipv4 name GUEST-WAN rule 200 action 'return' +set firewall ipv4 name GUEST-WAN rule 200 destination port '80,443' +set firewall ipv4 name GUEST-WAN rule 200 protocol 'tcp' +set firewall ipv4 name GUEST-WAN rule 500 action 'return' +set firewall ipv4 name GUEST-WAN rule 500 description 'L2TP IPSec' +set firewall ipv4 name GUEST-WAN rule 500 destination port '500,4500' +set firewall ipv4 name GUEST-WAN rule 500 protocol 'udp' +set firewall ipv4 name GUEST-WAN rule 600 action 'return' +set firewall ipv4 name GUEST-WAN rule 600 destination port '5222-5224' +set firewall ipv4 name GUEST-WAN rule 600 protocol 'tcp' +set firewall ipv4 name GUEST-WAN rule 601 action 'return' +set firewall ipv4 name GUEST-WAN rule 601 destination port '3478-3497,4500,16384-16387,16393-16402' +set firewall ipv4 name GUEST-WAN rule 601 protocol 'udp' +set firewall ipv4 name GUEST-WAN rule 1000 action 'return' +set firewall ipv4 name GUEST-WAN rule 1000 source address '172.31.0.184' +set firewall ipv4 name IOT-GUEST default-action 'drop' +set firewall ipv4 name IOT-GUEST default-log +set firewall ipv4 name IOT-GUEST rule 1 action 'return' +set firewall ipv4 name IOT-GUEST rule 1 state 'established' +set firewall ipv4 name IOT-GUEST rule 1 state 'related' +set firewall ipv4 name IOT-GUEST rule 2 action 'drop' +set firewall ipv4 name IOT-GUEST rule 2 log +set firewall ipv4 name IOT-GUEST rule 2 state 'invalid' +set firewall ipv4 name IOT-GUEST rule 100 action 'return' +set firewall ipv4 name IOT-GUEST rule 100 description 'MEDIA-STREAMING-CLIENTS Devices to IOT' +set firewall ipv4 name IOT-GUEST rule 100 protocol 'tcp_udp' +set firewall ipv4 name IOT-GUEST rule 100 source group address-group 'MEDIA-STREAMING-CLIENTS' +set firewall ipv4 name IOT-GUEST rule 110 action 'return' +set firewall ipv4 name IOT-GUEST rule 110 description 'AUDIO-STREAM Devices to IOT' +set firewall ipv4 name IOT-GUEST rule 110 protocol 'tcp_udp' +set firewall ipv4 name IOT-GUEST rule 110 source group address-group 'AUDIO-STREAM' +set firewall ipv4 name IOT-GUEST rule 200 action 'return' +set firewall ipv4 name IOT-GUEST rule 200 description 'MCAST relay' +set firewall ipv4 name IOT-GUEST rule 200 destination address '224.0.0.251' +set firewall ipv4 name IOT-GUEST rule 200 destination port '5353' +set firewall ipv4 name IOT-GUEST rule 200 protocol 'udp' +set firewall ipv4 name IOT-GUEST rule 300 action 'return' +set firewall ipv4 name IOT-GUEST rule 300 description 'BCAST relay' +set firewall ipv4 name IOT-GUEST rule 300 destination port '1900' +set firewall ipv4 name IOT-GUEST rule 300 protocol 'udp' +set firewall ipv4 name IOT-LAN default-action 'drop' +set firewall ipv4 name IOT-LAN default-log +set firewall ipv4 name IOT-LAN rule 1 action 'return' +set firewall ipv4 name IOT-LAN rule 1 state 'established' +set firewall ipv4 name IOT-LAN rule 1 state 'related' +set firewall ipv4 name IOT-LAN rule 2 action 'drop' +set firewall ipv4 name IOT-LAN rule 2 log +set firewall ipv4 name IOT-LAN rule 2 state 'invalid' +set firewall ipv4 name IOT-LAN rule 100 action 'return' +set firewall ipv4 name IOT-LAN rule 100 description 'AppleTV to LAN' +set firewall ipv4 name IOT-LAN rule 100 destination group port-group 'SMART-TV-PORTS' +set firewall ipv4 name IOT-LAN rule 100 protocol 'tcp_udp' +set firewall ipv4 name IOT-LAN rule 100 source group address-group 'MEDIA-STREAMING-CLIENTS' +set firewall ipv4 name IOT-LAN rule 110 action 'return' +set firewall ipv4 name IOT-LAN rule 110 description 'AUDIO-STREAM Devices to LAN' +set firewall ipv4 name IOT-LAN rule 110 protocol 'tcp_udp' +set firewall ipv4 name IOT-LAN rule 110 source group address-group 'AUDIO-STREAM' +set firewall ipv4 name IOT-LOCAL default-action 'drop' +set firewall ipv4 name IOT-LOCAL default-log +set firewall ipv4 name IOT-LOCAL rule 1 action 'return' +set firewall ipv4 name IOT-LOCAL rule 1 state 'established' +set firewall ipv4 name IOT-LOCAL rule 1 state 'related' +set firewall ipv4 name IOT-LOCAL rule 2 action 'drop' +set firewall ipv4 name IOT-LOCAL rule 2 log +set firewall ipv4 name IOT-LOCAL rule 2 state 'invalid' +set firewall ipv4 name IOT-LOCAL rule 10 action 'return' +set firewall ipv4 name IOT-LOCAL rule 10 description 'DNS' +set firewall ipv4 name IOT-LOCAL rule 10 destination address '172.16.254.30' +set firewall ipv4 name IOT-LOCAL rule 10 destination port '53' +set firewall ipv4 name IOT-LOCAL rule 10 protocol 'tcp_udp' +set firewall ipv4 name IOT-LOCAL rule 11 action 'return' +set firewall ipv4 name IOT-LOCAL rule 11 description 'DHCP' +set firewall ipv4 name IOT-LOCAL rule 11 destination port '67' +set firewall ipv4 name IOT-LOCAL rule 11 protocol 'udp' +set firewall ipv4 name IOT-LOCAL rule 15 action 'return' +set firewall ipv4 name IOT-LOCAL rule 15 destination address '172.16.35.254' +set firewall ipv4 name IOT-LOCAL rule 15 protocol 'icmp' +set firewall ipv4 name IOT-LOCAL rule 200 action 'return' +set firewall ipv4 name IOT-LOCAL rule 200 description 'MCAST relay' +set firewall ipv4 name IOT-LOCAL rule 200 destination address '224.0.0.251' +set firewall ipv4 name IOT-LOCAL rule 200 destination port '5353' +set firewall ipv4 name IOT-LOCAL rule 200 protocol 'udp' +set firewall ipv4 name IOT-LOCAL rule 201 action 'return' +set firewall ipv4 name IOT-LOCAL rule 201 description 'MCAST relay' +set firewall ipv4 name IOT-LOCAL rule 201 destination address '172.16.35.254' +set firewall ipv4 name IOT-LOCAL rule 201 destination port '5353' +set firewall ipv4 name IOT-LOCAL rule 201 protocol 'udp' +set firewall ipv4 name IOT-LOCAL rule 210 action 'return' +set firewall ipv4 name IOT-LOCAL rule 210 description 'AUDIO-STREAM Broadcast' +set firewall ipv4 name IOT-LOCAL rule 210 destination port '1900,1902,6969' +set firewall ipv4 name IOT-LOCAL rule 210 protocol 'udp' +set firewall ipv4 name IOT-WAN default-action 'return' +set firewall ipv4 name LAN-DMZ default-action 'drop' +set firewall ipv4 name LAN-DMZ default-log +set firewall ipv4 name LAN-DMZ rule 1 action 'return' +set firewall ipv4 name LAN-DMZ rule 1 state 'established' +set firewall ipv4 name LAN-DMZ rule 1 state 'related' +set firewall ipv4 name LAN-DMZ rule 2 action 'drop' +set firewall ipv4 name LAN-DMZ rule 2 log +set firewall ipv4 name LAN-DMZ rule 2 state 'invalid' +set firewall ipv4 name LAN-DMZ rule 22 action 'return' +set firewall ipv4 name LAN-DMZ rule 22 description 'SSH into DMZ' +set firewall ipv4 name LAN-DMZ rule 22 destination port '22' +set firewall ipv4 name LAN-DMZ rule 22 protocol 'tcp' +set firewall ipv4 name LAN-DMZ rule 100 action 'return' +set firewall ipv4 name LAN-DMZ rule 100 destination group address-group 'DMZ-WEBSERVER' +set firewall ipv4 name LAN-DMZ rule 100 destination port '22,80,443' +set firewall ipv4 name LAN-DMZ rule 100 protocol 'tcp' +set firewall ipv4 name LAN-GUEST default-action 'drop' +set firewall ipv4 name LAN-GUEST default-log +set firewall ipv4 name LAN-GUEST rule 1 action 'return' +set firewall ipv4 name LAN-GUEST rule 1 state 'established' +set firewall ipv4 name LAN-GUEST rule 1 state 'related' +set firewall ipv4 name LAN-GUEST rule 2 action 'drop' +set firewall ipv4 name LAN-GUEST rule 2 log +set firewall ipv4 name LAN-GUEST rule 2 state 'invalid' +set firewall ipv4 name LAN-IOT default-action 'return' +set firewall ipv4 name LAN-LOCAL default-action 'return' +set firewall ipv4 name LAN-WAN default-action 'return' +set firewall ipv4 name LOCAL-DMZ default-action 'drop' +set firewall ipv4 name LOCAL-DMZ default-log +set firewall ipv4 name LOCAL-DMZ rule 1 action 'return' +set firewall ipv4 name LOCAL-DMZ rule 1 state 'established' +set firewall ipv4 name LOCAL-DMZ rule 1 state 'related' +set firewall ipv4 name LOCAL-DMZ rule 2 action 'drop' +set firewall ipv4 name LOCAL-DMZ rule 2 log +set firewall ipv4 name LOCAL-DMZ rule 2 state 'invalid' +set firewall ipv4 name LOCAL-GUEST default-action 'drop' +set firewall ipv4 name LOCAL-GUEST default-log +set firewall ipv4 name LOCAL-GUEST rule 1 action 'return' +set firewall ipv4 name LOCAL-GUEST rule 1 state 'established' +set firewall ipv4 name LOCAL-GUEST rule 1 state 'related' +set firewall ipv4 name LOCAL-GUEST rule 2 action 'drop' +set firewall ipv4 name LOCAL-GUEST rule 2 log +set firewall ipv4 name LOCAL-GUEST rule 2 state 'invalid' +set firewall ipv4 name LOCAL-GUEST rule 5 action 'return' +set firewall ipv4 name LOCAL-GUEST rule 5 protocol 'icmp' +set firewall ipv4 name LOCAL-GUEST rule 200 action 'return' +set firewall ipv4 name LOCAL-GUEST rule 200 description 'MCAST relay' +set firewall ipv4 name LOCAL-GUEST rule 200 destination address '224.0.0.251' +set firewall ipv4 name LOCAL-GUEST rule 200 destination port '5353' +set firewall ipv4 name LOCAL-GUEST rule 200 protocol 'udp' +set firewall ipv4 name LOCAL-GUEST rule 300 action 'return' +set firewall ipv4 name LOCAL-GUEST rule 300 description 'BCAST relay' +set firewall ipv4 name LOCAL-GUEST rule 300 destination port '1900' +set firewall ipv4 name LOCAL-GUEST rule 300 protocol 'udp' +set firewall ipv4 name LOCAL-IOT default-action 'drop' +set firewall ipv4 name LOCAL-IOT default-log +set firewall ipv4 name LOCAL-IOT rule 1 action 'return' +set firewall ipv4 name LOCAL-IOT rule 1 state 'established' +set firewall ipv4 name LOCAL-IOT rule 1 state 'related' +set firewall ipv4 name LOCAL-IOT rule 2 action 'drop' +set firewall ipv4 name LOCAL-IOT rule 2 log +set firewall ipv4 name LOCAL-IOT rule 2 state 'invalid' +set firewall ipv4 name LOCAL-IOT rule 5 action 'return' +set firewall ipv4 name LOCAL-IOT rule 5 protocol 'icmp' +set firewall ipv4 name LOCAL-IOT rule 200 action 'return' +set firewall ipv4 name LOCAL-IOT rule 200 description 'MCAST relay' +set firewall ipv4 name LOCAL-IOT rule 200 destination address '224.0.0.251' +set firewall ipv4 name LOCAL-IOT rule 200 destination port '5353' +set firewall ipv4 name LOCAL-IOT rule 200 protocol 'udp' +set firewall ipv4 name LOCAL-IOT rule 300 action 'return' +set firewall ipv4 name LOCAL-IOT rule 300 description 'BCAST relay' +set firewall ipv4 name LOCAL-IOT rule 300 destination port '1900,6969' +set firewall ipv4 name LOCAL-IOT rule 300 protocol 'udp' +set firewall ipv4 name LOCAL-LAN default-action 'return' +set firewall ipv4 name LOCAL-WAN default-action 'drop' +set firewall ipv4 name LOCAL-WAN default-log +set firewall ipv4 name LOCAL-WAN rule 1 action 'return' +set firewall ipv4 name LOCAL-WAN rule 1 state 'established' +set firewall ipv4 name LOCAL-WAN rule 1 state 'related' +set firewall ipv4 name LOCAL-WAN rule 2 action 'drop' +set firewall ipv4 name LOCAL-WAN rule 2 log +set firewall ipv4 name LOCAL-WAN rule 2 state 'invalid' +set firewall ipv4 name LOCAL-WAN rule 10 action 'return' +set firewall ipv4 name LOCAL-WAN rule 10 protocol 'icmp' +set firewall ipv4 name LOCAL-WAN rule 50 action 'return' +set firewall ipv4 name LOCAL-WAN rule 50 description 'DNS' +set firewall ipv4 name LOCAL-WAN rule 50 destination port '53' +set firewall ipv4 name LOCAL-WAN rule 50 protocol 'tcp_udp' +set firewall ipv4 name LOCAL-WAN rule 80 action 'return' +set firewall ipv4 name LOCAL-WAN rule 80 destination port '80,443' +set firewall ipv4 name LOCAL-WAN rule 80 protocol 'tcp' +set firewall ipv4 name LOCAL-WAN rule 123 action 'return' +set firewall ipv4 name LOCAL-WAN rule 123 description 'NTP' +set firewall ipv4 name LOCAL-WAN rule 123 destination port '123' +set firewall ipv4 name LOCAL-WAN rule 123 protocol 'udp' +set firewall ipv4 name WAN-DMZ default-action 'drop' +set firewall ipv4 name WAN-DMZ default-log +set firewall ipv4 name WAN-DMZ rule 1 action 'return' +set firewall ipv4 name WAN-DMZ rule 1 state 'established' +set firewall ipv4 name WAN-DMZ rule 1 state 'related' +set firewall ipv4 name WAN-DMZ rule 2 action 'drop' +set firewall ipv4 name WAN-DMZ rule 2 log +set firewall ipv4 name WAN-DMZ rule 2 state 'invalid' +set firewall ipv4 name WAN-DMZ rule 100 action 'return' +set firewall ipv4 name WAN-DMZ rule 100 destination address '172.16.36.10' +set firewall ipv4 name WAN-DMZ rule 100 destination port '80,443' +set firewall ipv4 name WAN-DMZ rule 100 protocol 'tcp' +set firewall ipv4 name WAN-GUEST default-action 'drop' +set firewall ipv4 name WAN-GUEST default-log +set firewall ipv4 name WAN-GUEST rule 1 action 'return' +set firewall ipv4 name WAN-GUEST rule 1 state 'established' +set firewall ipv4 name WAN-GUEST rule 1 state 'related' +set firewall ipv4 name WAN-GUEST rule 2 action 'drop' +set firewall ipv4 name WAN-GUEST rule 2 log +set firewall ipv4 name WAN-GUEST rule 2 state 'invalid' +set firewall ipv4 name WAN-GUEST rule 1000 action 'return' +set firewall ipv4 name WAN-GUEST rule 1000 destination address '172.31.0.184' +set firewall ipv4 name WAN-GUEST rule 8000 action 'return' +set firewall ipv4 name WAN-GUEST rule 8000 destination address '172.31.0.200' +set firewall ipv4 name WAN-GUEST rule 8000 destination port '10000' +set firewall ipv4 name WAN-GUEST rule 8000 protocol 'udp' +set firewall ipv4 name WAN-IOT default-action 'drop' +set firewall ipv4 name WAN-IOT default-log +set firewall ipv4 name WAN-IOT rule 1 action 'return' +set firewall ipv4 name WAN-IOT rule 1 state 'established' +set firewall ipv4 name WAN-IOT rule 1 state 'related' +set firewall ipv4 name WAN-IOT rule 2 action 'drop' +set firewall ipv4 name WAN-IOT rule 2 log +set firewall ipv4 name WAN-IOT rule 2 state 'invalid' +set firewall ipv4 name WAN-LAN default-action 'drop' +set firewall ipv4 name WAN-LAN default-log +set firewall ipv4 name WAN-LAN rule 1 action 'return' +set firewall ipv4 name WAN-LAN rule 1 state 'established' +set firewall ipv4 name WAN-LAN rule 1 state 'related' +set firewall ipv4 name WAN-LAN rule 2 action 'drop' +set firewall ipv4 name WAN-LAN rule 2 log +set firewall ipv4 name WAN-LAN rule 2 state 'invalid' +set firewall ipv4 name WAN-LAN rule 1000 action 'return' +set firewall ipv4 name WAN-LAN rule 1000 destination address '172.16.33.40' +set firewall ipv4 name WAN-LAN rule 1000 destination port '3389' +set firewall ipv4 name WAN-LAN rule 1000 protocol 'tcp' +set firewall ipv4 name WAN-LAN rule 1000 source group network-group 'SSH-IN-ALLOW' +set firewall ipv4 name WAN-LOCAL default-action 'drop' +set firewall ipv4 name WAN-LOCAL default-log +set firewall ipv4 name WAN-LOCAL rule 1 action 'return' +set firewall ipv4 name WAN-LOCAL rule 1 state 'established' +set firewall ipv4 name WAN-LOCAL rule 1 state 'related' +set firewall ipv4 name WAN-LOCAL rule 2 action 'drop' +set firewall ipv4 name WAN-LOCAL rule 2 log +set firewall ipv4 name WAN-LOCAL rule 2 state 'invalid' +set firewall ipv4 name WAN-LOCAL rule 22 action 'return' +set firewall ipv4 name WAN-LOCAL rule 22 destination port '22' +set firewall ipv4 name WAN-LOCAL rule 22 protocol 'tcp' +set firewall ipv4 name WAN-LOCAL rule 22 source group network-group 'SSH-IN-ALLOW' +set firewall ipv6 name ALLOW-ALL-6 default-action 'return' +set firewall ipv6 name ALLOW-BASIC-6 default-action 'drop' +set firewall ipv6 name ALLOW-BASIC-6 default-log +set firewall ipv6 name ALLOW-BASIC-6 rule 1 action 'return' +set firewall ipv6 name ALLOW-BASIC-6 rule 1 state 'established' +set firewall ipv6 name ALLOW-BASIC-6 rule 1 state 'related' +set firewall ipv6 name ALLOW-BASIC-6 rule 2 action 'drop' +set firewall ipv6 name ALLOW-BASIC-6 rule 2 state 'invalid' +set firewall ipv6 name ALLOW-BASIC-6 rule 10 action 'return' +set firewall ipv6 name ALLOW-BASIC-6 rule 10 protocol 'ipv6-icmp' +set firewall ipv6 name ALLOW-BASIC-6 rule 15 action 'return' +set firewall ipv6 name ALLOW-BASIC-6 rule 15 icmpv6 type '1' +set firewall ipv6 name ALLOW-BASIC-6 rule 15 protocol 'ipv6-icmp' +set firewall ipv6 name ALLOW-BASIC-6 rule 16 action 'return' +set firewall ipv6 name ALLOW-BASIC-6 rule 16 icmpv6 code '1' +set firewall ipv6 name ALLOW-BASIC-6 rule 16 icmpv6 type '1' +set firewall ipv6 name ALLOW-BASIC-6 rule 16 protocol 'ipv6-icmp' +set firewall ipv6 name ALLOW-BASIC-6 rule 17 action 'return' +set firewall ipv6 name ALLOW-BASIC-6 rule 17 icmpv6 type-name 'destination-unreachable' +set firewall ipv6 name ALLOW-BASIC-6 rule 17 protocol 'ipv6-icmp' +set firewall ipv6 name ALLOW-ESTABLISHED-6 default-action 'drop' +set firewall ipv6 name ALLOW-ESTABLISHED-6 default-log +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 1 action 'return' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 1 state 'established' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 1 state 'related' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 2 action 'drop' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 2 state 'invalid' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 10 action 'return' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 10 destination group network-group 'LOCAL-ADDRESSES' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 10 protocol 'ipv6-icmp' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 10 source address 'fe80::/10' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 20 action 'return' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 20 icmpv6 type-name 'echo-request' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 20 protocol 'ipv6-icmp' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 21 action 'return' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 21 icmpv6 type-name 'destination-unreachable' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 21 protocol 'ipv6-icmp' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 22 action 'return' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 22 icmpv6 type-name 'packet-too-big' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 22 protocol 'ipv6-icmp' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 23 action 'return' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 23 icmpv6 type-name 'time-exceeded' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 23 protocol 'ipv6-icmp' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 24 action 'return' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 24 icmpv6 type-name 'parameter-problem' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 24 protocol 'ipv6-icmp' +set firewall ipv6 name WAN-LOCAL-6 default-action 'drop' +set firewall ipv6 name WAN-LOCAL-6 default-log +set firewall ipv6 name WAN-LOCAL-6 rule 1 action 'return' +set firewall ipv6 name WAN-LOCAL-6 rule 1 state 'established' +set firewall ipv6 name WAN-LOCAL-6 rule 1 state 'related' +set firewall ipv6 name WAN-LOCAL-6 rule 2 action 'drop' +set firewall ipv6 name WAN-LOCAL-6 rule 2 state 'invalid' +set firewall ipv6 name WAN-LOCAL-6 rule 10 action 'return' +set firewall ipv6 name WAN-LOCAL-6 rule 10 destination address 'ff02::/64' +set firewall ipv6 name WAN-LOCAL-6 rule 10 protocol 'ipv6-icmp' +set firewall ipv6 name WAN-LOCAL-6 rule 10 source address 'fe80::/10' +set firewall ipv6 name WAN-LOCAL-6 rule 50 action 'return' +set firewall ipv6 name WAN-LOCAL-6 rule 50 description 'DHCPv6' +set firewall ipv6 name WAN-LOCAL-6 rule 50 destination address 'fe80::/10' +set firewall ipv6 name WAN-LOCAL-6 rule 50 destination port '546' +set firewall ipv6 name WAN-LOCAL-6 rule 50 protocol 'udp' +set firewall ipv6 name WAN-LOCAL-6 rule 50 source address 'fe80::/10' +set firewall ipv6 name WAN-LOCAL-6 rule 50 source port '547' +set firewall zone DMZ default-action 'drop' +set firewall zone DMZ from GUEST firewall name 'GUEST-DMZ' +set firewall zone DMZ from LAN firewall name 'LAN-DMZ' +set firewall zone DMZ from LOCAL firewall name 'LOCAL-DMZ' +set firewall zone DMZ from WAN firewall name 'WAN-DMZ' +set firewall zone DMZ interface 'eth0.50' +set firewall zone GUEST default-action 'drop' +set firewall zone GUEST from DMZ firewall name 'DMZ-GUEST' +set firewall zone GUEST from IOT firewall name 'IOT-GUEST' +set firewall zone GUEST from LAN firewall name 'LAN-GUEST' +set firewall zone GUEST from LOCAL firewall ipv6-name 'ALLOW-ALL-6' +set firewall zone GUEST from LOCAL firewall name 'LOCAL-GUEST' +set firewall zone GUEST from WAN firewall ipv6-name 'ALLOW-ESTABLISHED-6' +set firewall zone GUEST from WAN firewall name 'WAN-GUEST' +set firewall zone GUEST interface 'eth0.20' +set firewall zone IOT default-action 'drop' +set firewall zone IOT from GUEST firewall name 'GUEST-IOT' +set firewall zone IOT from LAN firewall name 'LAN-IOT' +set firewall zone IOT from LOCAL firewall name 'LOCAL-IOT' +set firewall zone IOT from WAN firewall name 'WAN-IOT' +set firewall zone IOT interface 'eth0.35' +set firewall zone LAN default-action 'drop' +set firewall zone LAN from DMZ firewall name 'DMZ-LAN' +set firewall zone LAN from GUEST firewall name 'GUEST-LAN' +set firewall zone LAN from IOT firewall name 'IOT-LAN' +set firewall zone LAN from LOCAL firewall ipv6-name 'ALLOW-ALL-6' +set firewall zone LAN from LOCAL firewall name 'LOCAL-LAN' +set firewall zone LAN from WAN firewall ipv6-name 'ALLOW-ESTABLISHED-6' +set firewall zone LAN from WAN firewall name 'WAN-LAN' +set firewall zone LAN interface 'eth0.5' +set firewall zone LAN interface 'eth0.10' +set firewall zone LAN interface 'eth0.100' +set firewall zone LAN interface 'eth0.201' +set firewall zone LAN interface 'eth0.202' +set firewall zone LAN interface 'eth0.203' +set firewall zone LAN interface 'eth0.204' +set firewall zone LOCAL default-action 'drop' +set firewall zone LOCAL from DMZ firewall name 'DMZ-LOCAL' +set firewall zone LOCAL from GUEST firewall ipv6-name 'ALLOW-ESTABLISHED-6' +set firewall zone LOCAL from GUEST firewall name 'GUEST-LOCAL' +set firewall zone LOCAL from IOT firewall name 'IOT-LOCAL' +set firewall zone LOCAL from LAN firewall ipv6-name 'ALLOW-ALL-6' +set firewall zone LOCAL from LAN firewall name 'LAN-LOCAL' +set firewall zone LOCAL from WAN firewall ipv6-name 'WAN-LOCAL-6' +set firewall zone LOCAL from WAN firewall name 'WAN-LOCAL' +set firewall zone LOCAL local-zone +set firewall zone WAN default-action 'drop' +set firewall zone WAN from DMZ firewall name 'DMZ-WAN' +set firewall zone WAN from GUEST firewall ipv6-name 'ALLOW-ALL-6' +set firewall zone WAN from GUEST firewall name 'GUEST-WAN' +set firewall zone WAN from IOT firewall name 'IOT-WAN' +set firewall zone WAN from LAN firewall ipv6-name 'ALLOW-ALL-6' +set firewall zone WAN from LAN firewall name 'LAN-WAN' +set firewall zone WAN from LOCAL firewall ipv6-name 'ALLOW-ALL-6' +set firewall zone WAN from LOCAL firewall name 'LOCAL-WAN' +set firewall zone WAN interface 'pppoe0' +set interfaces dummy dum0 address '172.16.254.30/32' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth0 vif 5 address '172.16.37.254/24' +set interfaces ethernet eth0 vif 10 address '172.16.33.254/24' +set interfaces ethernet eth0 vif 10 ip adjust-mss '1320' +set interfaces ethernet eth0 vif 10 ipv6 adjust-mss '1300' +set interfaces ethernet eth0 vif 20 address '172.31.0.254/24' +set interfaces ethernet eth0 vif 35 address '172.16.35.254/24' +set interfaces ethernet eth0 vif 50 address '172.16.36.254/24' +set interfaces ethernet eth0 vif 100 address '172.16.100.254/24' +set interfaces ethernet eth0 vif 201 address '172.18.201.254/24' +set interfaces ethernet eth0 vif 202 address '172.18.202.254/24' +set interfaces ethernet eth0 vif 203 address '172.18.203.254/24' +set interfaces ethernet eth0 vif 204 address '172.18.204.254/24' +set interfaces ethernet eth1 vif 7 description 'FTTH-PPPoE' +set interfaces loopback lo address '172.16.254.30/32' +set interfaces pppoe pppoe0 authentication password 'vyos' +set interfaces pppoe pppoe0 authentication username 'vyos' +set interfaces pppoe pppoe0 description 'FTTH 100/50MBit' +set interfaces pppoe pppoe0 dhcpv6-options pd 0 interface eth0.10 address '1' +set interfaces pppoe pppoe0 dhcpv6-options pd 0 interface eth0.10 sla-id '10' +set interfaces pppoe pppoe0 dhcpv6-options pd 0 interface eth0.20 address '1' +set interfaces pppoe pppoe0 dhcpv6-options pd 0 interface eth0.20 sla-id '20' +set interfaces pppoe pppoe0 dhcpv6-options pd 0 length '56' +set interfaces pppoe pppoe0 ip adjust-mss '1452' +set interfaces pppoe pppoe0 ipv6 address autoconf +set interfaces pppoe pppoe0 ipv6 adjust-mss '1432' +set interfaces pppoe pppoe0 mtu '1492' +set interfaces pppoe pppoe0 no-peer-dns +set interfaces pppoe pppoe0 source-interface 'eth1.7' +set nat destination rule 100 description 'HTTP(S)' +set nat destination rule 100 destination port '80,443' +set nat destination rule 100 inbound-interface name 'pppoe0' +set nat destination rule 100 log +set nat destination rule 100 protocol 'tcp' +set nat destination rule 100 translation address '172.16.36.10' +set nat destination rule 1000 destination port '3389' +set nat destination rule 1000 disable +set nat destination rule 1000 inbound-interface name 'pppoe0' +set nat destination rule 1000 protocol 'tcp' +set nat destination rule 1000 translation address '172.16.33.40' +set nat destination rule 8000 destination port '10000' +set nat destination rule 8000 inbound-interface name 'pppoe0' +set nat destination rule 8000 log +set nat destination rule 8000 protocol 'udp' +set nat destination rule 8000 translation address '172.31.0.200' +set nat source rule 100 log +set nat source rule 100 outbound-interface name 'pppoe0' +set nat source rule 100 source address '172.16.32.0/19' +set nat source rule 100 translation address 'masquerade' +set nat source rule 200 outbound-interface name 'pppoe0' +set nat source rule 200 source address '172.16.100.0/24' +set nat source rule 200 translation address 'masquerade' +set nat source rule 300 outbound-interface name 'pppoe0' +set nat source rule 300 source address '172.31.0.0/24' +set nat source rule 300 translation address 'masquerade' +set nat source rule 400 outbound-interface name 'pppoe0' +set nat source rule 400 source address '172.18.200.0/21' +set nat source rule 400 translation address 'masquerade' +set protocols static route 10.0.0.0/8 blackhole distance '254' +set protocols static route 169.254.0.0/16 blackhole distance '254' +set protocols static route 172.16.0.0/12 blackhole distance '254' +set protocols static route 192.168.0.0/16 blackhole distance '254' +set protocols static route6 2000::/3 interface pppoe0 +set qos policy shaper QoS bandwidth '50mbit' +set qos policy shaper QoS default bandwidth '100%' +set qos policy shaper QoS default burst '15k' +set qos policy shaper QoS default queue-limit '1000' +set qos policy shaper QoS default queue-type 'fq-codel' +set service dhcp-server shared-network-name BACKBONE authoritative +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 lease '86400' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 option default-router '172.16.37.254' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 option domain-name 'vyos.net' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 option domain-search 'vyos.net' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 option name-server '172.16.254.30' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 option ntp-server '172.16.254.30' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 range 0 start '172.16.37.120' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 range 0 stop '172.16.37.149' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 static-mapping AP1.wue3 ip-address '172.16.37.231' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 static-mapping AP1.wue3 mac '18:e8:29:6c:c3:a5' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 subnet-id '1' +set service dhcp-server shared-network-name GUEST authoritative +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 lease '86400' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 option default-router '172.31.0.254' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 option domain-name 'vyos.net' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 option domain-search 'vyos.net' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 option name-server '172.31.0.254' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 range 0 start '172.31.0.100' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 range 0 stop '172.31.0.199' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 static-mapping host01 ip-address '172.31.0.200' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 static-mapping host01 mac '00:50:00:00:00:01' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 static-mapping host02 ip-address '172.31.0.184' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 static-mapping host02 mac '00:50:00:00:00:02' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 subnet-id '2' +set service dhcp-server shared-network-name IOT authoritative +set service dhcp-server shared-network-name IOT subnet 172.16.35.0/24 lease '86400' +set service dhcp-server shared-network-name IOT subnet 172.16.35.0/24 option default-router '172.16.35.254' +set service dhcp-server shared-network-name IOT subnet 172.16.35.0/24 option domain-name 'vyos.net' +set service dhcp-server shared-network-name IOT subnet 172.16.35.0/24 option domain-search 'vyos.net' +set service dhcp-server shared-network-name IOT subnet 172.16.35.0/24 option name-server '172.16.254.30' +set service dhcp-server shared-network-name IOT subnet 172.16.35.0/24 option ntp-server '172.16.254.30' +set service dhcp-server shared-network-name IOT subnet 172.16.35.0/24 range 0 start '172.16.35.101' +set service dhcp-server shared-network-name IOT subnet 172.16.35.0/24 range 0 stop '172.16.35.149' +set service dhcp-server shared-network-name IOT subnet 172.16.35.0/24 subnet-id '3' +set service dhcp-server shared-network-name LAN authoritative +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 lease '86400' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 option default-router '172.16.33.254' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 option domain-name 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 option domain-search 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 option name-server '172.16.254.30' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 option ntp-server '172.16.254.30' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 range 0 start '172.16.33.100' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 range 0 stop '172.16.33.189' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 subnet-id '4' +set service dns forwarding allow-from '172.16.0.0/12' +set service dns forwarding cache-size '0' +set service dns forwarding domain 16.172.in-addr.arpa addnta +set service dns forwarding domain 16.172.in-addr.arpa name-server 172.16.100.10 +set service dns forwarding domain 16.172.in-addr.arpa name-server 172.16.100.20 +set service dns forwarding domain 16.172.in-addr.arpa name-server 172.16.110.30 +set service dns forwarding domain 16.172.in-addr.arpa recursion-desired +set service dns forwarding domain 18.172.in-addr.arpa addnta +set service dns forwarding domain 18.172.in-addr.arpa name-server 172.16.100.10 +set service dns forwarding domain 18.172.in-addr.arpa name-server 172.16.100.20 +set service dns forwarding domain 18.172.in-addr.arpa name-server 172.16.110.30 +set service dns forwarding domain 18.172.in-addr.arpa recursion-desired +set service dns forwarding domain vyos.net addnta +set service dns forwarding domain vyos.net name-server 172.16.100.10 +set service dns forwarding domain vyos.net name-server 172.16.100.20 +set service dns forwarding domain vyos.net name-server 172.16.110.30 +set service dns forwarding domain vyos.net recursion-desired +set service dns forwarding ignore-hosts-file +set service dns forwarding listen-address '172.16.254.30' +set service dns forwarding listen-address '172.31.0.254' +set service dns forwarding negative-ttl '60' +set service lldp legacy-protocols cdp +set service lldp snmp +set service mdns repeater interface 'eth0.35' +set service mdns repeater interface 'eth0.10' +set service ntp allow-client address '172.16.0.0/12' +set service ntp server 0.pool.ntp.org +set service ntp server 1.pool.ntp.org +set service ntp server 2.pool.ntp.org +set service router-advert interface eth0.10 prefix ::/64 preferred-lifetime '2700' +set service router-advert interface eth0.10 prefix ::/64 valid-lifetime '5400' +set service router-advert interface eth0.20 prefix ::/64 preferred-lifetime '2700' +set service router-advert interface eth0.20 prefix ::/64 valid-lifetime '5400' +set service snmp community fooBar authorization 'ro' +set service snmp community fooBar network '172.16.100.0/24' +set service snmp contact 'VyOS maintainers and contributors <maintainers@vyos.io>' +set service snmp listen-address 172.16.254.30 port '161' +set service snmp location 'The Internet' +set service ssh disable-host-validation +set service ssh port '22' +set system config-management commit-revisions '200' +set system conntrack expect-table-size '2048' +set system conntrack hash-size '32768' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sqlnet +set system conntrack modules tftp +set system conntrack table-size '262144' +set system conntrack timeout +set system console device ttyS0 speed '115200' +set system domain-name 'vyos.net' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/' +set system login user vyos authentication plaintext-password '' +set system name-server '172.16.254.30' +set system option ctrl-alt-delete 'ignore' +set system option reboot-on-panic +set system option startup-beep +set system syslog global facility all level 'debug' +set system syslog global facility local7 level 'debug' +set system syslog host 172.16.100.1 facility all level 'warning' +set system time-zone 'Europe/Berlin' diff --git a/smoketest/config-tests/dialup-router-medium-vpn b/smoketest/config-tests/dialup-router-medium-vpn new file mode 100644 index 0000000..d6b00c6 --- /dev/null +++ b/smoketest/config-tests/dialup-router-medium-vpn @@ -0,0 +1,322 @@ +set firewall global-options all-ping 'enable' +set firewall global-options broadcast-ping 'disable' +set firewall global-options ip-src-route 'disable' +set firewall global-options ipv6-receive-redirects 'disable' +set firewall global-options ipv6-src-route 'disable' +set firewall global-options log-martians 'enable' +set firewall global-options receive-redirects 'disable' +set firewall global-options send-redirects 'enable' +set firewall global-options source-validation 'disable' +set firewall global-options syn-cookies 'disable' +set firewall global-options twa-hazards-protection 'enable' +set firewall ipv4 name test_tcp_flags rule 1 action 'drop' +set firewall ipv4 name test_tcp_flags rule 1 protocol 'tcp' +set firewall ipv4 name test_tcp_flags rule 1 tcp flags ack +set firewall ipv4 name test_tcp_flags rule 1 tcp flags not fin +set firewall ipv4 name test_tcp_flags rule 1 tcp flags not rst +set firewall ipv4 name test_tcp_flags rule 1 tcp flags syn +set high-availability vrrp group LAN address 192.168.0.1/24 +set high-availability vrrp group LAN hello-source-address '192.168.0.250' +set high-availability vrrp group LAN interface 'eth1' +set high-availability vrrp group LAN peer-address '192.168.0.251' +set high-availability vrrp group LAN priority '200' +set high-availability vrrp group LAN vrid '1' +set high-availability vrrp sync-group failover-group member 'LAN' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 mtu '9000' +set interfaces ethernet eth0 offload gro +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth1 address '192.168.0.250/24' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 ip source-validation 'strict' +set interfaces ethernet eth1 mtu '9000' +set interfaces ethernet eth1 offload gro +set interfaces ethernet eth1 speed 'auto' +set interfaces loopback lo +set interfaces openvpn vtun0 encryption data-ciphers 'aes256' +set interfaces openvpn vtun0 hash 'sha512' +set interfaces openvpn vtun0 ip adjust-mss '1380' +set interfaces openvpn vtun0 ip source-validation 'strict' +set interfaces openvpn vtun0 keep-alive failure-count '3' +set interfaces openvpn vtun0 keep-alive interval '30' +set interfaces openvpn vtun0 mode 'client' +set interfaces openvpn vtun0 openvpn-option 'comp-lzo adaptive' +set interfaces openvpn vtun0 openvpn-option 'fast-io' +set interfaces openvpn vtun0 openvpn-option 'persist-key' +set interfaces openvpn vtun0 openvpn-option 'reneg-sec 86400' +set interfaces openvpn vtun0 persistent-tunnel +set interfaces openvpn vtun0 remote-host '192.0.2.10' +set interfaces openvpn vtun0 tls auth-key 'openvpn_vtun0_auth' +set interfaces openvpn vtun0 tls ca-certificate 'openvpn_vtun0_1' +set interfaces openvpn vtun0 tls ca-certificate 'openvpn_vtun0_2' +set interfaces openvpn vtun0 tls certificate 'openvpn_vtun0' +set interfaces openvpn vtun1 authentication password 'vyos1' +set interfaces openvpn vtun1 authentication username 'vyos1' +set interfaces openvpn vtun1 encryption data-ciphers 'aes256' +set interfaces openvpn vtun1 hash 'sha1' +set interfaces openvpn vtun1 ip adjust-mss '1380' +set interfaces openvpn vtun1 keep-alive failure-count '3' +set interfaces openvpn vtun1 keep-alive interval '30' +set interfaces openvpn vtun1 mode 'client' +set interfaces openvpn vtun1 openvpn-option 'comp-lzo adaptive' +set interfaces openvpn vtun1 openvpn-option 'tun-mtu 1500' +set interfaces openvpn vtun1 openvpn-option 'tun-mtu-extra 32' +set interfaces openvpn vtun1 openvpn-option 'mssfix 1300' +set interfaces openvpn vtun1 openvpn-option 'persist-key' +set interfaces openvpn vtun1 openvpn-option 'mute 10' +set interfaces openvpn vtun1 openvpn-option 'route-nopull' +set interfaces openvpn vtun1 openvpn-option 'fast-io' +set interfaces openvpn vtun1 openvpn-option 'reneg-sec 86400' +set interfaces openvpn vtun1 persistent-tunnel +set interfaces openvpn vtun1 protocol 'udp' +set interfaces openvpn vtun1 remote-host '01.foo.com' +set interfaces openvpn vtun1 remote-port '1194' +set interfaces openvpn vtun1 tls auth-key 'openvpn_vtun1_auth' +set interfaces openvpn vtun1 tls ca-certificate 'openvpn_vtun1_1' +set interfaces openvpn vtun1 tls ca-certificate 'openvpn_vtun1_2' +set interfaces openvpn vtun2 authentication password 'vyos2' +set interfaces openvpn vtun2 authentication username 'vyos2' +set interfaces openvpn vtun2 disable +set interfaces openvpn vtun2 encryption data-ciphers 'aes256' +set interfaces openvpn vtun2 hash 'sha512' +set interfaces openvpn vtun2 ip adjust-mss '1380' +set interfaces openvpn vtun2 keep-alive failure-count '3' +set interfaces openvpn vtun2 keep-alive interval '30' +set interfaces openvpn vtun2 mode 'client' +set interfaces openvpn vtun2 openvpn-option 'tun-mtu 1500' +set interfaces openvpn vtun2 openvpn-option 'tun-mtu-extra 32' +set interfaces openvpn vtun2 openvpn-option 'mssfix 1300' +set interfaces openvpn vtun2 openvpn-option 'persist-key' +set interfaces openvpn vtun2 openvpn-option 'mute 10' +set interfaces openvpn vtun2 openvpn-option 'route-nopull' +set interfaces openvpn vtun2 openvpn-option 'fast-io' +set interfaces openvpn vtun2 openvpn-option 'remote-random' +set interfaces openvpn vtun2 openvpn-option 'reneg-sec 86400' +set interfaces openvpn vtun2 persistent-tunnel +set interfaces openvpn vtun2 protocol 'udp' +set interfaces openvpn vtun2 remote-host '01.myvpn.com' +set interfaces openvpn vtun2 remote-host '02.myvpn.com' +set interfaces openvpn vtun2 remote-host '03.myvpn.com' +set interfaces openvpn vtun2 remote-port '1194' +set interfaces openvpn vtun2 tls auth-key 'openvpn_vtun2_auth' +set interfaces openvpn vtun2 tls ca-certificate 'openvpn_vtun2_1' +set interfaces pppoe pppoe0 authentication password 'password' +set interfaces pppoe pppoe0 authentication username 'vyos' +set interfaces pppoe pppoe0 mtu '1500' +set interfaces pppoe pppoe0 source-interface 'eth0' +set interfaces wireguard wg0 address '192.168.10.1/24' +set interfaces wireguard wg0 ip adjust-mss '1380' +set interfaces wireguard wg0 peer blue allowed-ips '192.168.10.3/32' +set interfaces wireguard wg0 peer blue persistent-keepalive '20' +set interfaces wireguard wg0 peer blue preshared-key 'ztFDOY9UyaDvn8N3X97SFMDwIfv7EEfuUIPP2yab6UI=' +set interfaces wireguard wg0 peer blue public-key 'G4pZishpMRrLmd96Kr6V7LIuNGdcUb81gWaYZ+FWkG0=' +set interfaces wireguard wg0 peer green allowed-ips '192.168.10.21/32' +set interfaces wireguard wg0 peer green persistent-keepalive '25' +set interfaces wireguard wg0 peer green preshared-key 'LQ9qmlTh9G4nZu4UgElxRUwg7JB/qoV799aADJOijnY=' +set interfaces wireguard wg0 peer green public-key '5iQUD3VoCDBTPXAPHOwUJ0p7xzKGHEY/wQmgvBVmaFI=' +set interfaces wireguard wg0 peer pink allowed-ips '192.168.10.14/32' +set interfaces wireguard wg0 peer pink allowed-ips '192.168.10.16/32' +set interfaces wireguard wg0 peer pink persistent-keepalive '25' +set interfaces wireguard wg0 peer pink preshared-key 'Qi9Odyx0/5itLPN5C5bEy3uMX+tmdl15QbakxpKlWqQ=' +set interfaces wireguard wg0 peer pink public-key 'i4qNPmxyy9EETL4tIoZOLKJF4p7IlVmpAE15gglnAk4=' +set interfaces wireguard wg0 peer red allowed-ips '192.168.10.4/32' +set interfaces wireguard wg0 peer red persistent-keepalive '20' +set interfaces wireguard wg0 peer red preshared-key 'CumyXX7osvUT9AwnS+m2TEfCaL0Ptc2LfuZ78Sujuk8=' +set interfaces wireguard wg0 peer red public-key 'ALGWvMJCKpHF2tVH3hEIHqUe9iFfAmZATUUok/WQzks=' +set interfaces wireguard wg0 port '7777' +set interfaces wireguard wg0 private-key 'aGx+fvW916Ej7QRnBbW3QMoldhNv1u95/WHz45zDmF0=' +set interfaces wireguard wg1 address '10.89.90.2/30' +set interfaces wireguard wg1 ip adjust-mss '1380' +set interfaces wireguard wg1 peer sam address '192.0.2.45' +set interfaces wireguard wg1 peer sam allowed-ips '10.1.1.0/24' +set interfaces wireguard wg1 peer sam allowed-ips '10.89.90.1/32' +set interfaces wireguard wg1 peer sam persistent-keepalive '20' +set interfaces wireguard wg1 peer sam port '1200' +set interfaces wireguard wg1 peer sam preshared-key 'XpFtzx2Z+nR8pBv9/sSf7I94OkZkVYTz0AeU5Q/QQUE=' +set interfaces wireguard wg1 peer sam public-key 'v5zfKGvH6W/lfDXJ0en96lvKo1gfFxMUWxe02+Fj5BU=' +set interfaces wireguard wg1 port '7778' +set interfaces wireguard wg1 private-key 'aGx+fvW916Ej7QRnBbW3QMoldhNv1u95/WHz45zDmF0=' +set nat destination rule 50 destination port '49371' +set nat destination rule 50 inbound-interface name 'pppoe0' +set nat destination rule 50 protocol 'tcp_udp' +set nat destination rule 50 translation address '192.168.0.5' +set nat destination rule 51 destination port '58050-58051' +set nat destination rule 51 inbound-interface name 'pppoe0' +set nat destination rule 51 protocol 'tcp' +set nat destination rule 51 translation address '192.168.0.5' +set nat destination rule 52 destination port '22067-22070' +set nat destination rule 52 inbound-interface name 'pppoe0' +set nat destination rule 52 protocol 'tcp' +set nat destination rule 52 translation address '192.168.0.5' +set nat destination rule 53 destination port '34342' +set nat destination rule 53 inbound-interface name 'pppoe0' +set nat destination rule 53 protocol 'tcp_udp' +set nat destination rule 53 translation address '192.168.0.121' +set nat destination rule 54 destination port '45459' +set nat destination rule 54 inbound-interface name 'pppoe0' +set nat destination rule 54 protocol 'tcp_udp' +set nat destination rule 54 translation address '192.168.0.120' +set nat destination rule 55 destination port '22' +set nat destination rule 55 inbound-interface name 'pppoe0' +set nat destination rule 55 protocol 'tcp' +set nat destination rule 55 translation address '192.168.0.5' +set nat destination rule 56 destination port '8920' +set nat destination rule 56 inbound-interface name 'pppoe0' +set nat destination rule 56 protocol 'tcp' +set nat destination rule 56 translation address '192.168.0.5' +set nat destination rule 60 destination port '80,443' +set nat destination rule 60 inbound-interface name 'pppoe0' +set nat destination rule 60 protocol 'tcp' +set nat destination rule 60 translation address '192.168.0.5' +set nat destination rule 70 destination port '5001' +set nat destination rule 70 inbound-interface name 'pppoe0' +set nat destination rule 70 protocol 'tcp' +set nat destination rule 70 translation address '192.168.0.5' +set nat destination rule 80 destination port '25' +set nat destination rule 80 inbound-interface name 'pppoe0' +set nat destination rule 80 protocol 'tcp' +set nat destination rule 80 translation address '192.168.0.5' +set nat destination rule 90 destination port '8123' +set nat destination rule 90 inbound-interface name 'pppoe0' +set nat destination rule 90 protocol 'tcp' +set nat destination rule 90 translation address '192.168.0.7' +set nat destination rule 91 destination port '1880' +set nat destination rule 91 inbound-interface name 'pppoe0' +set nat destination rule 91 protocol 'tcp' +set nat destination rule 91 translation address '192.168.0.7' +set nat destination rule 500 destination address '!192.168.0.0/24' +set nat destination rule 500 destination port '53' +set nat destination rule 500 inbound-interface name 'eth1' +set nat destination rule 500 protocol 'tcp_udp' +set nat destination rule 500 source address '!192.168.0.1-192.168.0.5' +set nat destination rule 500 translation address '192.168.0.1' +set nat source rule 1000 outbound-interface name 'pppoe0' +set nat source rule 1000 translation address 'masquerade' +set nat source rule 2000 outbound-interface name 'vtun0' +set nat source rule 2000 source address '192.168.0.0/16' +set nat source rule 2000 translation address 'masquerade' +set nat source rule 3000 outbound-interface name 'vtun1' +set nat source rule 3000 translation address 'masquerade' +set policy prefix-list user1-routes rule 1 action 'permit' +set policy prefix-list user1-routes rule 1 prefix '192.168.0.0/24' +set policy prefix-list user2-routes rule 1 action 'permit' +set policy prefix-list user2-routes rule 1 prefix '10.1.1.0/24' +set policy route LAN-POLICY-BASED-ROUTING interface 'eth1' +set policy route LAN-POLICY-BASED-ROUTING rule 10 destination +set policy route LAN-POLICY-BASED-ROUTING rule 10 disable +set policy route LAN-POLICY-BASED-ROUTING rule 10 set table '10' +set policy route LAN-POLICY-BASED-ROUTING rule 10 source address '192.168.0.119/32' +set policy route LAN-POLICY-BASED-ROUTING rule 20 destination +set policy route LAN-POLICY-BASED-ROUTING rule 20 set table '100' +set policy route LAN-POLICY-BASED-ROUTING rule 20 source address '192.168.0.240' +set policy route-map rm-static-to-bgp rule 10 action 'permit' +set policy route-map rm-static-to-bgp rule 10 match ip address prefix-list 'user1-routes' +set policy route-map rm-static-to-bgp rule 100 action 'deny' +set policy route6 LAN6-POLICY-BASED-ROUTING interface 'eth1' +set policy route6 LAN6-POLICY-BASED-ROUTING rule 10 destination +set policy route6 LAN6-POLICY-BASED-ROUTING rule 10 disable +set policy route6 LAN6-POLICY-BASED-ROUTING rule 10 set table '10' +set policy route6 LAN6-POLICY-BASED-ROUTING rule 10 source address '2002::1' +set policy route6 LAN6-POLICY-BASED-ROUTING rule 20 destination +set policy route6 LAN6-POLICY-BASED-ROUTING rule 20 set table '100' +set policy route6 LAN6-POLICY-BASED-ROUTING rule 20 source address '2008::f' +set protocols bgp address-family ipv4-unicast redistribute connected route-map 'rm-static-to-bgp' +set protocols bgp neighbor 10.89.90.1 address-family ipv4-unicast nexthop-self +set protocols bgp neighbor 10.89.90.1 address-family ipv4-unicast prefix-list export 'user1-routes' +set protocols bgp neighbor 10.89.90.1 address-family ipv4-unicast prefix-list import 'user2-routes' +set protocols bgp neighbor 10.89.90.1 address-family ipv4-unicast soft-reconfiguration inbound +set protocols bgp neighbor 10.89.90.1 password 'ericandre2020' +set protocols bgp neighbor 10.89.90.1 remote-as '64589' +set protocols bgp parameters log-neighbor-changes +set protocols bgp parameters router-id '10.89.90.2' +set protocols bgp system-as '64590' +set protocols static route 100.64.160.23/32 interface pppoe0 +set protocols static route 100.64.165.25/32 interface pppoe0 +set protocols static route 100.64.165.26/32 interface pppoe0 +set protocols static route 100.64.198.0/24 interface vtun0 +set protocols static table 10 route 0.0.0.0/0 interface vtun1 +set protocols static table 100 route 0.0.0.0/0 next-hop 192.168.10.5 +set service conntrack-sync accept-protocol 'tcp' +set service conntrack-sync accept-protocol 'udp' +set service conntrack-sync accept-protocol 'icmp' +set service conntrack-sync disable-external-cache +set service conntrack-sync event-listen-queue-size '8' +set service conntrack-sync expect-sync 'all' +set service conntrack-sync failover-mechanism vrrp sync-group 'failover-group' +set service conntrack-sync interface eth1 peer '192.168.0.251' +set service conntrack-sync sync-queue-size '8' +set service dhcp-server high-availability name 'DHCP02' +set service dhcp-server high-availability remote '192.168.0.251' +set service dhcp-server high-availability source-address '192.168.0.250' +set service dhcp-server high-availability status 'primary' +set service dhcp-server shared-network-name LAN authoritative +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 lease '86400' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option default-router '192.168.0.1' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-name 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-search 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option name-server '192.168.0.1' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic start '192.168.0.200' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic stop '192.168.0.240' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Audio ip-address '192.168.0.107' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Audio mac '00:50:01:dc:91:14' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping IPTV ip-address '192.168.0.104' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping IPTV mac '00:50:01:31:b5:f6' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus ip-address '192.168.0.60' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus mac '00:50:01:58:ac:95' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 ip-address '192.168.0.109' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 mac '00:50:01:bc:ac:51' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 ip-address '192.168.0.11' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 mac '00:50:01:70:b9:4d' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 ip-address '192.168.0.12' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 mac '00:50:01:70:b7:4f' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping pearTV ip-address '192.168.0.101' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping pearTV mac '00:50:01:ba:62:79' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping sand ip-address '192.168.0.110' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping sand mac '00:50:01:af:c5:d2' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 subnet-id '1' +set service dns forwarding allow-from '192.168.0.0/16' +set service dns forwarding cache-size '8192' +set service dns forwarding dnssec 'off' +set service dns forwarding listen-address '192.168.0.1' +set service dns forwarding name-server 100.64.0.1 +set service dns forwarding name-server 100.64.0.2 +set service ntp allow-client address '192.168.0.0/16' +set service ntp server nz.pool.ntp.org prefer +set service snmp community AwesomeCommunity authorization 'ro' +set service snmp community AwesomeCommunity client '127.0.0.1' +set service snmp community AwesomeCommunity network '192.168.0.0/24' +set service ssh access-control allow user 'vyos' +set service ssh client-keepalive-interval '60' +set service ssh listen-address '192.168.0.1' +set service ssh listen-address '192.168.10.1' +set service ssh listen-address '192.168.0.250' +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system ip arp table-size '1024' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system name-server '192.168.0.1' +set system name-server 'pppoe0' +set system option ctrl-alt-delete 'ignore' +set system option reboot-on-panic +set system option startup-beep +set system static-host-mapping host-name host60.vyos.net inet '192.168.0.60' +set system static-host-mapping host-name host104.vyos.net inet '192.168.0.104' +set system static-host-mapping host-name host107.vyos.net inet '192.168.0.107' +set system static-host-mapping host-name host109.vyos.net inet '192.168.0.109' +set system sysctl parameter net.core.default_qdisc value 'fq' +set system sysctl parameter net.ipv4.tcp_congestion_control value 'bbr' +set system syslog global facility all level 'info' +set system syslog host 192.168.0.252 facility all level 'debug' +set system syslog host 192.168.0.252 protocol 'udp' +set system task-scheduler task Update-Blacklists executable path '/config/scripts/vyos-foo-update.script' +set system task-scheduler task Update-Blacklists interval '3h' +set system time-zone 'Pacific/Auckland' diff --git a/smoketest/config-tests/dialup-router-wireguard-ipv6 b/smoketest/config-tests/dialup-router-wireguard-ipv6 new file mode 100644 index 0000000..ff4bf89 --- /dev/null +++ b/smoketest/config-tests/dialup-router-wireguard-ipv6 @@ -0,0 +1,697 @@ +set firewall global-options all-ping 'enable' +set firewall global-options broadcast-ping 'disable' +set firewall global-options ip-src-route 'disable' +set firewall global-options ipv6-receive-redirects 'disable' +set firewall global-options ipv6-src-route 'disable' +set firewall global-options log-martians 'enable' +set firewall global-options receive-redirects 'disable' +set firewall global-options send-redirects 'enable' +set firewall global-options source-validation 'disable' +set firewall global-options syn-cookies 'enable' +set firewall global-options timeout icmp '30' +set firewall global-options timeout other '600' +set firewall global-options timeout udp other '300' +set firewall global-options timeout udp stream '300' +set firewall global-options twa-hazards-protection 'disable' +set firewall group address-group DMZ-RDP-SERVER address '172.16.33.40' +set firewall group address-group DMZ-RDP-SERVER description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata' +set firewall group address-group DMZ-WEBSERVER address '172.16.36.10' +set firewall group address-group DMZ-WEBSERVER address '172.16.36.40' +set firewall group address-group DMZ-WEBSERVER address '172.16.36.20' +set firewall group address-group DMZ-WEBSERVER description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata' +set firewall group address-group DOMAIN-CONTROLLER address '172.16.100.10' +set firewall group address-group DOMAIN-CONTROLLER address '172.16.100.20' +set firewall group address-group DOMAIN-CONTROLLER address '172.16.110.30' +set firewall group address-group VIDEO address '172.16.33.211' +set firewall group address-group VIDEO address '172.16.33.212' +set firewall group address-group VIDEO address '172.16.33.213' +set firewall group address-group VIDEO address '172.16.33.214' +set firewall group ipv6-network-group LOCAL-ADDRESSES network 'ff02::/64' +set firewall group ipv6-network-group LOCAL-ADDRESSES network 'fe80::/10' +set firewall group network-group SSH-IN-ALLOW network '100.65.150.0/23' +set firewall group network-group SSH-IN-ALLOW network '100.64.69.205/32' +set firewall group network-group SSH-IN-ALLOW network '100.64.8.67/32' +set firewall group network-group SSH-IN-ALLOW network '100.64.55.1/32' +set firewall ipv4 name DMZ-GUEST default-action 'drop' +set firewall ipv4 name DMZ-GUEST default-log +set firewall ipv4 name DMZ-GUEST rule 1 action 'return' +set firewall ipv4 name DMZ-GUEST rule 1 state 'established' +set firewall ipv4 name DMZ-GUEST rule 1 state 'related' +set firewall ipv4 name DMZ-GUEST rule 2 action 'drop' +set firewall ipv4 name DMZ-GUEST rule 2 log +set firewall ipv4 name DMZ-GUEST rule 2 state 'invalid' +set firewall ipv4 name DMZ-LAN default-action 'drop' +set firewall ipv4 name DMZ-LAN default-log +set firewall ipv4 name DMZ-LAN description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata' +set firewall ipv4 name DMZ-LAN rule 1 action 'return' +set firewall ipv4 name DMZ-LAN rule 1 description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata' +set firewall ipv4 name DMZ-LAN rule 1 state 'established' +set firewall ipv4 name DMZ-LAN rule 1 state 'related' +set firewall ipv4 name DMZ-LAN rule 2 action 'drop' +set firewall ipv4 name DMZ-LAN rule 2 description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata' +set firewall ipv4 name DMZ-LAN rule 2 log +set firewall ipv4 name DMZ-LAN rule 2 state 'invalid' +set firewall ipv4 name DMZ-LAN rule 100 action 'return' +set firewall ipv4 name DMZ-LAN rule 100 description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata' +set firewall ipv4 name DMZ-LAN rule 100 destination group address-group 'DOMAIN-CONTROLLER' +set firewall ipv4 name DMZ-LAN rule 100 destination port '123,389,636' +set firewall ipv4 name DMZ-LAN rule 100 protocol 'tcp_udp' +set firewall ipv4 name DMZ-LAN rule 300 action 'return' +set firewall ipv4 name DMZ-LAN rule 300 description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata' +set firewall ipv4 name DMZ-LAN rule 300 destination group address-group 'DMZ-RDP-SERVER' +set firewall ipv4 name DMZ-LAN rule 300 destination port '3389' +set firewall ipv4 name DMZ-LAN rule 300 protocol 'tcp_udp' +set firewall ipv4 name DMZ-LAN rule 300 source address '172.16.36.20' +set firewall ipv4 name DMZ-LOCAL default-action 'drop' +set firewall ipv4 name DMZ-LOCAL default-log +set firewall ipv4 name DMZ-LOCAL description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata' +set firewall ipv4 name DMZ-LOCAL rule 1 action 'return' +set firewall ipv4 name DMZ-LOCAL rule 1 state 'established' +set firewall ipv4 name DMZ-LOCAL rule 1 state 'related' +set firewall ipv4 name DMZ-LOCAL rule 2 action 'drop' +set firewall ipv4 name DMZ-LOCAL rule 2 log +set firewall ipv4 name DMZ-LOCAL rule 2 state 'invalid' +set firewall ipv4 name DMZ-LOCAL rule 50 action 'return' +set firewall ipv4 name DMZ-LOCAL rule 50 description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata' +set firewall ipv4 name DMZ-LOCAL rule 50 destination address '172.16.254.30' +set firewall ipv4 name DMZ-LOCAL rule 50 destination port '53' +set firewall ipv4 name DMZ-LOCAL rule 50 protocol 'tcp_udp' +set firewall ipv4 name DMZ-LOCAL rule 123 action 'return' +set firewall ipv4 name DMZ-LOCAL rule 123 destination port '123' +set firewall ipv4 name DMZ-LOCAL rule 123 protocol 'udp' +set firewall ipv4 name DMZ-WAN default-action 'return' +set firewall ipv4 name DMZ-WAN description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata' +set firewall ipv4 name GUEST-DMZ default-action 'drop' +set firewall ipv4 name GUEST-DMZ default-log +set firewall ipv4 name GUEST-DMZ rule 1 action 'return' +set firewall ipv4 name GUEST-DMZ rule 1 state 'established' +set firewall ipv4 name GUEST-DMZ rule 1 state 'related' +set firewall ipv4 name GUEST-DMZ rule 2 action 'drop' +set firewall ipv4 name GUEST-DMZ rule 2 log +set firewall ipv4 name GUEST-DMZ rule 2 state 'invalid' +set firewall ipv4 name GUEST-LAN default-action 'drop' +set firewall ipv4 name GUEST-LAN default-log +set firewall ipv4 name GUEST-LAN rule 1 action 'return' +set firewall ipv4 name GUEST-LAN rule 1 state 'established' +set firewall ipv4 name GUEST-LAN rule 1 state 'related' +set firewall ipv4 name GUEST-LAN rule 2 action 'drop' +set firewall ipv4 name GUEST-LAN rule 2 log +set firewall ipv4 name GUEST-LAN rule 2 state 'invalid' +set firewall ipv4 name GUEST-LOCAL default-action 'drop' +set firewall ipv4 name GUEST-LOCAL default-log +set firewall ipv4 name GUEST-LOCAL rule 1 action 'return' +set firewall ipv4 name GUEST-LOCAL rule 1 state 'established' +set firewall ipv4 name GUEST-LOCAL rule 1 state 'related' +set firewall ipv4 name GUEST-LOCAL rule 2 action 'drop' +set firewall ipv4 name GUEST-LOCAL rule 2 log +set firewall ipv4 name GUEST-LOCAL rule 2 state 'invalid' +set firewall ipv4 name GUEST-LOCAL rule 10 action 'return' +set firewall ipv4 name GUEST-LOCAL rule 10 destination address '172.31.0.254' +set firewall ipv4 name GUEST-LOCAL rule 10 destination port '53' +set firewall ipv4 name GUEST-LOCAL rule 10 protocol 'tcp_udp' +set firewall ipv4 name GUEST-LOCAL rule 11 action 'return' +set firewall ipv4 name GUEST-LOCAL rule 11 destination port '67' +set firewall ipv4 name GUEST-LOCAL rule 11 protocol 'udp' +set firewall ipv4 name GUEST-LOCAL rule 15 action 'return' +set firewall ipv4 name GUEST-LOCAL rule 15 destination address '172.31.0.254' +set firewall ipv4 name GUEST-LOCAL rule 15 protocol 'icmp' +set firewall ipv4 name GUEST-LOCAL rule 100 action 'return' +set firewall ipv4 name GUEST-LOCAL rule 100 destination address '172.31.0.254' +set firewall ipv4 name GUEST-LOCAL rule 100 destination port '80,443' +set firewall ipv4 name GUEST-LOCAL rule 100 protocol 'tcp' +set firewall ipv4 name GUEST-WAN default-action 'drop' +set firewall ipv4 name GUEST-WAN default-log +set firewall ipv4 name GUEST-WAN rule 1 action 'return' +set firewall ipv4 name GUEST-WAN rule 1 state 'established' +set firewall ipv4 name GUEST-WAN rule 1 state 'related' +set firewall ipv4 name GUEST-WAN rule 2 action 'drop' +set firewall ipv4 name GUEST-WAN rule 2 log +set firewall ipv4 name GUEST-WAN rule 2 state 'invalid' +set firewall ipv4 name GUEST-WAN rule 25 action 'return' +set firewall ipv4 name GUEST-WAN rule 25 destination port '25,587' +set firewall ipv4 name GUEST-WAN rule 25 protocol 'tcp' +set firewall ipv4 name GUEST-WAN rule 53 action 'return' +set firewall ipv4 name GUEST-WAN rule 53 destination port '53' +set firewall ipv4 name GUEST-WAN rule 53 protocol 'tcp_udp' +set firewall ipv4 name GUEST-WAN rule 60 action 'return' +set firewall ipv4 name GUEST-WAN rule 60 source address '172.31.0.200' +set firewall ipv4 name GUEST-WAN rule 80 action 'return' +set firewall ipv4 name GUEST-WAN rule 80 source address '172.31.0.200' +set firewall ipv4 name GUEST-WAN rule 100 action 'return' +set firewall ipv4 name GUEST-WAN rule 100 protocol 'icmp' +set firewall ipv4 name GUEST-WAN rule 110 action 'return' +set firewall ipv4 name GUEST-WAN rule 110 destination port '110,995' +set firewall ipv4 name GUEST-WAN rule 110 protocol 'tcp' +set firewall ipv4 name GUEST-WAN rule 123 action 'return' +set firewall ipv4 name GUEST-WAN rule 123 destination port '123' +set firewall ipv4 name GUEST-WAN rule 123 protocol 'udp' +set firewall ipv4 name GUEST-WAN rule 143 action 'return' +set firewall ipv4 name GUEST-WAN rule 143 destination port '143,993' +set firewall ipv4 name GUEST-WAN rule 143 protocol 'tcp' +set firewall ipv4 name GUEST-WAN rule 200 action 'return' +set firewall ipv4 name GUEST-WAN rule 200 destination port '80,443' +set firewall ipv4 name GUEST-WAN rule 200 protocol 'tcp' +set firewall ipv4 name GUEST-WAN rule 500 action 'return' +set firewall ipv4 name GUEST-WAN rule 500 destination port '500,4500' +set firewall ipv4 name GUEST-WAN rule 500 protocol 'udp' +set firewall ipv4 name GUEST-WAN rule 600 action 'return' +set firewall ipv4 name GUEST-WAN rule 600 destination port '5222-5224' +set firewall ipv4 name GUEST-WAN rule 600 protocol 'tcp' +set firewall ipv4 name GUEST-WAN rule 601 action 'return' +set firewall ipv4 name GUEST-WAN rule 601 destination port '3478-3497,4500,16384-16387,16393-16402' +set firewall ipv4 name GUEST-WAN rule 601 protocol 'udp' +set firewall ipv4 name GUEST-WAN rule 1000 action 'return' +set firewall ipv4 name GUEST-WAN rule 1000 source address '172.31.0.184' +set firewall ipv4 name LAN-DMZ default-action 'drop' +set firewall ipv4 name LAN-DMZ default-log +set firewall ipv4 name LAN-DMZ rule 1 action 'return' +set firewall ipv4 name LAN-DMZ rule 1 state 'established' +set firewall ipv4 name LAN-DMZ rule 1 state 'related' +set firewall ipv4 name LAN-DMZ rule 2 action 'drop' +set firewall ipv4 name LAN-DMZ rule 2 log +set firewall ipv4 name LAN-DMZ rule 2 state 'invalid' +set firewall ipv4 name LAN-DMZ rule 22 action 'return' +set firewall ipv4 name LAN-DMZ rule 22 destination port '22' +set firewall ipv4 name LAN-DMZ rule 22 protocol 'tcp' +set firewall ipv4 name LAN-DMZ rule 100 action 'return' +set firewall ipv4 name LAN-DMZ rule 100 destination group address-group 'DMZ-WEBSERVER' +set firewall ipv4 name LAN-DMZ rule 100 destination port '22' +set firewall ipv4 name LAN-DMZ rule 100 protocol 'tcp' +set firewall ipv4 name LAN-GUEST default-action 'drop' +set firewall ipv4 name LAN-GUEST default-log +set firewall ipv4 name LAN-GUEST rule 1 action 'return' +set firewall ipv4 name LAN-GUEST rule 1 state 'established' +set firewall ipv4 name LAN-GUEST rule 1 state 'related' +set firewall ipv4 name LAN-GUEST rule 2 action 'drop' +set firewall ipv4 name LAN-GUEST rule 2 log +set firewall ipv4 name LAN-GUEST rule 2 state 'invalid' +set firewall ipv4 name LAN-LOCAL default-action 'return' +set firewall ipv4 name LAN-WAN default-action 'return' +set firewall ipv4 name LAN-WAN rule 90 action 'return' +set firewall ipv4 name LAN-WAN rule 90 destination address '100.65.150.0/23' +set firewall ipv4 name LAN-WAN rule 90 destination port '25' +set firewall ipv4 name LAN-WAN rule 90 protocol 'tcp_udp' +set firewall ipv4 name LAN-WAN rule 90 source group address-group 'VIDEO' +set firewall ipv4 name LAN-WAN rule 100 action 'drop' +set firewall ipv4 name LAN-WAN rule 100 source group address-group 'VIDEO' +set firewall ipv4 name LOCAL-DMZ default-action 'drop' +set firewall ipv4 name LOCAL-DMZ default-log +set firewall ipv4 name LOCAL-DMZ rule 1 action 'return' +set firewall ipv4 name LOCAL-DMZ rule 1 state 'established' +set firewall ipv4 name LOCAL-DMZ rule 1 state 'related' +set firewall ipv4 name LOCAL-DMZ rule 2 action 'drop' +set firewall ipv4 name LOCAL-DMZ rule 2 log +set firewall ipv4 name LOCAL-DMZ rule 2 state 'invalid' +set firewall ipv4 name LOCAL-DMZ rule 100 action 'return' +set firewall ipv4 name LOCAL-DMZ rule 100 destination address '172.16.36.40' +set firewall ipv4 name LOCAL-DMZ rule 100 destination port '80,443' +set firewall ipv4 name LOCAL-DMZ rule 100 protocol 'tcp' +set firewall ipv4 name LOCAL-GUEST default-action 'drop' +set firewall ipv4 name LOCAL-GUEST default-log +set firewall ipv4 name LOCAL-GUEST rule 1 action 'return' +set firewall ipv4 name LOCAL-GUEST rule 1 state 'established' +set firewall ipv4 name LOCAL-GUEST rule 1 state 'related' +set firewall ipv4 name LOCAL-GUEST rule 2 action 'drop' +set firewall ipv4 name LOCAL-GUEST rule 2 log +set firewall ipv4 name LOCAL-GUEST rule 2 state 'invalid' +set firewall ipv4 name LOCAL-GUEST rule 5 action 'return' +set firewall ipv4 name LOCAL-GUEST rule 5 protocol 'icmp' +set firewall ipv4 name LOCAL-GUEST rule 300 action 'return' +set firewall ipv4 name LOCAL-GUEST rule 300 destination port '1900' +set firewall ipv4 name LOCAL-GUEST rule 300 protocol 'udp' +set firewall ipv4 name LOCAL-LAN default-action 'return' +set firewall ipv4 name LOCAL-WAN default-action 'drop' +set firewall ipv4 name LOCAL-WAN default-log +set firewall ipv4 name LOCAL-WAN rule 1 action 'return' +set firewall ipv4 name LOCAL-WAN rule 1 state 'established' +set firewall ipv4 name LOCAL-WAN rule 1 state 'related' +set firewall ipv4 name LOCAL-WAN rule 2 action 'drop' +set firewall ipv4 name LOCAL-WAN rule 2 log +set firewall ipv4 name LOCAL-WAN rule 2 state 'invalid' +set firewall ipv4 name LOCAL-WAN rule 10 action 'return' +set firewall ipv4 name LOCAL-WAN rule 10 protocol 'icmp' +set firewall ipv4 name LOCAL-WAN rule 50 action 'return' +set firewall ipv4 name LOCAL-WAN rule 50 destination port '53' +set firewall ipv4 name LOCAL-WAN rule 50 protocol 'tcp_udp' +set firewall ipv4 name LOCAL-WAN rule 80 action 'return' +set firewall ipv4 name LOCAL-WAN rule 80 destination port '80,443' +set firewall ipv4 name LOCAL-WAN rule 80 protocol 'tcp' +set firewall ipv4 name LOCAL-WAN rule 123 action 'return' +set firewall ipv4 name LOCAL-WAN rule 123 destination port '123' +set firewall ipv4 name LOCAL-WAN rule 123 protocol 'udp' +set firewall ipv4 name LOCAL-WAN rule 800 action 'return' +set firewall ipv4 name LOCAL-WAN rule 800 destination address '100.65.151.213' +set firewall ipv4 name LOCAL-WAN rule 800 protocol 'udp' +set firewall ipv4 name LOCAL-WAN rule 805 action 'return' +set firewall ipv4 name LOCAL-WAN rule 805 destination address '100.65.151.2' +set firewall ipv4 name LOCAL-WAN rule 805 protocol 'all' +set firewall ipv4 name LOCAL-WAN rule 1010 action 'return' +set firewall ipv4 name LOCAL-WAN rule 1010 destination address '100.64.69.205' +set firewall ipv4 name LOCAL-WAN rule 1010 destination port '7705' +set firewall ipv4 name LOCAL-WAN rule 1010 protocol 'udp' +set firewall ipv4 name LOCAL-WAN rule 1010 source port '7705' +set firewall ipv4 name LOCAL-WAN rule 1990 action 'return' +set firewall ipv4 name LOCAL-WAN rule 1990 destination address '100.64.55.1' +set firewall ipv4 name LOCAL-WAN rule 1990 destination port '10666' +set firewall ipv4 name LOCAL-WAN rule 1990 protocol 'udp' +set firewall ipv4 name LOCAL-WAN rule 2000 action 'return' +set firewall ipv4 name LOCAL-WAN rule 2000 destination address '100.64.39.249' +set firewall ipv4 name LOCAL-WAN rule 10200 action 'return' +set firewall ipv4 name LOCAL-WAN rule 10200 destination address '100.64.89.98' +set firewall ipv4 name LOCAL-WAN rule 10200 destination port '10200' +set firewall ipv4 name LOCAL-WAN rule 10200 protocol 'udp' +set firewall ipv4 name LOCAL-WAN rule 10200 source port '10200' +set firewall ipv4 name WAN-DMZ default-action 'drop' +set firewall ipv4 name WAN-DMZ default-log +set firewall ipv4 name WAN-DMZ rule 1 action 'return' +set firewall ipv4 name WAN-DMZ rule 1 state 'established' +set firewall ipv4 name WAN-DMZ rule 1 state 'related' +set firewall ipv4 name WAN-DMZ rule 2 action 'drop' +set firewall ipv4 name WAN-DMZ rule 2 log +set firewall ipv4 name WAN-DMZ rule 2 state 'invalid' +set firewall ipv4 name WAN-DMZ rule 100 action 'return' +set firewall ipv4 name WAN-DMZ rule 100 destination address '172.16.36.10' +set firewall ipv4 name WAN-DMZ rule 100 destination port '80,443' +set firewall ipv4 name WAN-DMZ rule 100 protocol 'tcp' +set firewall ipv4 name WAN-GUEST default-action 'drop' +set firewall ipv4 name WAN-GUEST default-log +set firewall ipv4 name WAN-GUEST rule 1 action 'return' +set firewall ipv4 name WAN-GUEST rule 1 state 'established' +set firewall ipv4 name WAN-GUEST rule 1 state 'related' +set firewall ipv4 name WAN-GUEST rule 2 action 'drop' +set firewall ipv4 name WAN-GUEST rule 2 log +set firewall ipv4 name WAN-GUEST rule 2 state 'invalid' +set firewall ipv4 name WAN-GUEST rule 1000 action 'return' +set firewall ipv4 name WAN-GUEST rule 1000 destination address '172.31.0.184' +set firewall ipv4 name WAN-GUEST rule 8000 action 'return' +set firewall ipv4 name WAN-GUEST rule 8000 destination address '172.31.0.200' +set firewall ipv4 name WAN-GUEST rule 8000 destination port '10000' +set firewall ipv4 name WAN-GUEST rule 8000 protocol 'udp' +set firewall ipv4 name WAN-LAN default-action 'drop' +set firewall ipv4 name WAN-LAN default-log +set firewall ipv4 name WAN-LAN description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata' +set firewall ipv4 name WAN-LAN rule 1 action 'return' +set firewall ipv4 name WAN-LAN rule 1 description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata' +set firewall ipv4 name WAN-LAN rule 1 state 'established' +set firewall ipv4 name WAN-LAN rule 1 state 'related' +set firewall ipv4 name WAN-LAN rule 2 action 'drop' +set firewall ipv4 name WAN-LAN rule 2 log +set firewall ipv4 name WAN-LAN rule 2 state 'invalid' +set firewall ipv4 name WAN-LAN rule 1000 action 'return' +set firewall ipv4 name WAN-LAN rule 1000 destination address '172.16.33.40' +set firewall ipv4 name WAN-LAN rule 1000 destination port '3389' +set firewall ipv4 name WAN-LAN rule 1000 protocol 'tcp' +set firewall ipv4 name WAN-LAN rule 1000 source group network-group 'SSH-IN-ALLOW' +set firewall ipv4 name WAN-LOCAL default-action 'drop' +set firewall ipv4 name WAN-LOCAL rule 1 action 'return' +set firewall ipv4 name WAN-LOCAL rule 1 state 'established' +set firewall ipv4 name WAN-LOCAL rule 1 state 'related' +set firewall ipv4 name WAN-LOCAL rule 2 action 'drop' +set firewall ipv4 name WAN-LOCAL rule 2 log +set firewall ipv4 name WAN-LOCAL rule 2 state 'invalid' +set firewall ipv4 name WAN-LOCAL rule 22 action 'return' +set firewall ipv4 name WAN-LOCAL rule 22 destination port '22' +set firewall ipv4 name WAN-LOCAL rule 22 protocol 'tcp' +set firewall ipv4 name WAN-LOCAL rule 22 source group network-group 'SSH-IN-ALLOW' +set firewall ipv4 name WAN-LOCAL rule 1990 action 'return' +set firewall ipv4 name WAN-LOCAL rule 1990 destination port '10666' +set firewall ipv4 name WAN-LOCAL rule 1990 protocol 'udp' +set firewall ipv4 name WAN-LOCAL rule 1990 source address '100.64.55.1' +set firewall ipv4 name WAN-LOCAL rule 10000 action 'return' +set firewall ipv4 name WAN-LOCAL rule 10000 destination port '80,443' +set firewall ipv4 name WAN-LOCAL rule 10000 protocol 'tcp' +set firewall ipv4 name WAN-LOCAL rule 10100 action 'return' +set firewall ipv4 name WAN-LOCAL rule 10100 destination port '10100' +set firewall ipv4 name WAN-LOCAL rule 10100 protocol 'udp' +set firewall ipv4 name WAN-LOCAL rule 10100 source port '10100' +set firewall ipv4 name WAN-LOCAL rule 10200 action 'return' +set firewall ipv4 name WAN-LOCAL rule 10200 destination port '10200' +set firewall ipv4 name WAN-LOCAL rule 10200 protocol 'udp' +set firewall ipv4 name WAN-LOCAL rule 10200 source address '100.64.89.98' +set firewall ipv4 name WAN-LOCAL rule 10200 source port '10200' +set firewall ipv6 name ALLOW-ALL-6 default-action 'return' +set firewall ipv6 name ALLOW-ALL-6 description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata' +set firewall ipv6 name ALLOW-BASIC-6 default-action 'drop' +set firewall ipv6 name ALLOW-BASIC-6 default-log +set firewall ipv6 name ALLOW-BASIC-6 description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata' +set firewall ipv6 name ALLOW-BASIC-6 rule 1 action 'return' +set firewall ipv6 name ALLOW-BASIC-6 rule 1 description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata' +set firewall ipv6 name ALLOW-BASIC-6 rule 1 state 'established' +set firewall ipv6 name ALLOW-BASIC-6 rule 1 state 'related' +set firewall ipv6 name ALLOW-BASIC-6 rule 2 action 'drop' +set firewall ipv6 name ALLOW-BASIC-6 rule 2 description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata' +set firewall ipv6 name ALLOW-BASIC-6 rule 2 state 'invalid' +set firewall ipv6 name ALLOW-BASIC-6 rule 10 action 'return' +set firewall ipv6 name ALLOW-BASIC-6 rule 10 protocol 'ipv6-icmp' +set firewall ipv6 name ALLOW-ESTABLISHED-6 default-action 'drop' +set firewall ipv6 name ALLOW-ESTABLISHED-6 default-log +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 1 action 'return' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 1 state 'established' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 1 state 'related' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 2 action 'drop' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 2 state 'invalid' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 10 action 'return' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 10 destination group network-group 'LOCAL-ADDRESSES' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 10 protocol 'ipv6-icmp' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 10 source address 'fe80::/10' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 20 action 'return' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 20 icmpv6 type-name 'echo-request' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 20 protocol 'ipv6-icmp' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 21 action 'return' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 21 icmpv6 type-name 'destination-unreachable' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 21 protocol 'ipv6-icmp' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 22 action 'return' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 22 icmpv6 type-name 'packet-too-big' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 22 protocol 'ipv6-icmp' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 23 action 'return' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 23 icmpv6 type-name 'time-exceeded' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 23 protocol 'ipv6-icmp' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 24 action 'return' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 24 icmpv6 type-name 'parameter-problem' +set firewall ipv6 name ALLOW-ESTABLISHED-6 rule 24 protocol 'ipv6-icmp' +set firewall ipv6 name WAN-LOCAL-6 default-action 'drop' +set firewall ipv6 name WAN-LOCAL-6 default-log +set firewall ipv6 name WAN-LOCAL-6 rule 1 action 'return' +set firewall ipv6 name WAN-LOCAL-6 rule 1 state 'established' +set firewall ipv6 name WAN-LOCAL-6 rule 1 state 'related' +set firewall ipv6 name WAN-LOCAL-6 rule 2 action 'drop' +set firewall ipv6 name WAN-LOCAL-6 rule 2 state 'invalid' +set firewall ipv6 name WAN-LOCAL-6 rule 10 action 'return' +set firewall ipv6 name WAN-LOCAL-6 rule 10 destination address 'ff02::/64' +set firewall ipv6 name WAN-LOCAL-6 rule 10 protocol 'ipv6-icmp' +set firewall ipv6 name WAN-LOCAL-6 rule 10 source address 'fe80::/10' +set firewall ipv6 name WAN-LOCAL-6 rule 50 action 'return' +set firewall ipv6 name WAN-LOCAL-6 rule 50 destination address 'fe80::/10' +set firewall ipv6 name WAN-LOCAL-6 rule 50 destination port '546' +set firewall ipv6 name WAN-LOCAL-6 rule 50 protocol 'udp' +set firewall ipv6 name WAN-LOCAL-6 rule 50 source address 'fe80::/10' +set firewall ipv6 name WAN-LOCAL-6 rule 50 source port '547' +set firewall zone DMZ default-action 'drop' +set firewall zone DMZ from GUEST firewall name 'GUEST-DMZ' +set firewall zone DMZ from LAN firewall name 'LAN-DMZ' +set firewall zone DMZ from LOCAL firewall name 'LOCAL-DMZ' +set firewall zone DMZ from WAN firewall name 'WAN-DMZ' +set firewall zone DMZ interface 'eth0.50' +set firewall zone GUEST default-action 'drop' +set firewall zone GUEST from DMZ firewall name 'DMZ-GUEST' +set firewall zone GUEST from LAN firewall name 'LAN-GUEST' +set firewall zone GUEST from LOCAL firewall ipv6-name 'ALLOW-ALL-6' +set firewall zone GUEST from LOCAL firewall name 'LOCAL-GUEST' +set firewall zone GUEST from WAN firewall ipv6-name 'ALLOW-ESTABLISHED-6' +set firewall zone GUEST from WAN firewall name 'WAN-GUEST' +set firewall zone GUEST interface 'eth1.20' +set firewall zone LAN default-action 'drop' +set firewall zone LAN from DMZ firewall name 'DMZ-LAN' +set firewall zone LAN from GUEST firewall name 'GUEST-LAN' +set firewall zone LAN from LOCAL firewall ipv6-name 'ALLOW-ALL-6' +set firewall zone LAN from LOCAL firewall name 'LOCAL-LAN' +set firewall zone LAN from WAN firewall ipv6-name 'ALLOW-ESTABLISHED-6' +set firewall zone LAN from WAN firewall name 'WAN-LAN' +set firewall zone LAN interface 'eth0.5' +set firewall zone LAN interface 'eth0.10' +set firewall zone LAN interface 'wg100' +set firewall zone LAN interface 'wg200' +set firewall zone LOCAL default-action 'drop' +set firewall zone LOCAL from DMZ firewall name 'DMZ-LOCAL' +set firewall zone LOCAL from GUEST firewall ipv6-name 'ALLOW-ESTABLISHED-6' +set firewall zone LOCAL from GUEST firewall name 'GUEST-LOCAL' +set firewall zone LOCAL from LAN firewall ipv6-name 'ALLOW-ALL-6' +set firewall zone LOCAL from LAN firewall name 'LAN-LOCAL' +set firewall zone LOCAL from WAN firewall ipv6-name 'WAN-LOCAL-6' +set firewall zone LOCAL from WAN firewall name 'WAN-LOCAL' +set firewall zone LOCAL local-zone +set firewall zone WAN default-action 'drop' +set firewall zone WAN from DMZ firewall name 'DMZ-WAN' +set firewall zone WAN from GUEST firewall ipv6-name 'ALLOW-ALL-6' +set firewall zone WAN from GUEST firewall name 'GUEST-WAN' +set firewall zone WAN from LAN firewall ipv6-name 'ALLOW-ALL-6' +set firewall zone WAN from LAN firewall name 'LAN-WAN' +set firewall zone WAN from LOCAL firewall ipv6-name 'ALLOW-ALL-6' +set firewall zone WAN from LOCAL firewall name 'LOCAL-WAN' +set firewall zone WAN interface 'pppoe0' +set firewall zone WAN interface 'wg666' +set interfaces dummy dum0 address '172.16.254.30/32' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 offload gro +set interfaces ethernet eth0 ring-buffer rx '256' +set interfaces ethernet eth0 ring-buffer tx '256' +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth0 vif 5 address '172.16.37.254/24' +set interfaces ethernet eth0 vif 10 address '172.16.33.254/24' +set interfaces ethernet eth0 vif 10 address '172.16.40.254/24' +set interfaces ethernet eth0 vif 50 address '172.16.36.254/24' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 offload gro +set interfaces ethernet eth1 speed 'auto' +set interfaces ethernet eth1 vif 20 address '172.31.0.254/24' +set interfaces ethernet eth2 disable +set interfaces ethernet eth2 duplex 'auto' +set interfaces ethernet eth2 offload gro +set interfaces ethernet eth2 speed 'auto' +set interfaces ethernet eth3 duplex 'auto' +set interfaces ethernet eth3 offload gro +set interfaces ethernet eth3 ring-buffer rx '256' +set interfaces ethernet eth3 ring-buffer tx '256' +set interfaces ethernet eth3 speed 'auto' +set interfaces ethernet eth3 vif 7 +set interfaces loopback lo address '172.16.254.30/32' +set interfaces pppoe pppoe0 authentication password 'vyos' +set interfaces pppoe pppoe0 authentication username 'vyos' +set interfaces pppoe pppoe0 dhcpv6-options pd 0 interface eth0.10 address '1' +set interfaces pppoe pppoe0 dhcpv6-options pd 0 interface eth0.10 sla-id '10' +set interfaces pppoe pppoe0 dhcpv6-options pd 0 interface eth1.20 address '1' +set interfaces pppoe pppoe0 dhcpv6-options pd 0 interface eth1.20 sla-id '20' +set interfaces pppoe pppoe0 dhcpv6-options pd 0 length '56' +set interfaces pppoe pppoe0 ip adjust-mss '1452' +set interfaces pppoe pppoe0 ipv6 address autoconf +set interfaces pppoe pppoe0 ipv6 adjust-mss '1432' +set interfaces pppoe pppoe0 no-peer-dns +set interfaces pppoe pppoe0 source-interface 'eth3.7' +set interfaces wireguard wg100 address '172.16.252.128/31' +set interfaces wireguard wg100 mtu '1500' +set interfaces wireguard wg100 peer HR6 address '100.65.151.213' +set interfaces wireguard wg100 peer HR6 allowed-ips '0.0.0.0/0' +set interfaces wireguard wg100 peer HR6 port '10100' +set interfaces wireguard wg100 peer HR6 public-key 'yLpi+UZuI019bmWH2h5fX3gStbpPPPLgEoYMyrdkOnQ=' +set interfaces wireguard wg100 port '10100' +set interfaces wireguard wg100 private-key 'aGx+fvW916Ej7QRnBbW3QMoldhNv1u95/WHz45zDmF0=' +set interfaces wireguard wg200 address '172.16.252.130/31' +set interfaces wireguard wg200 mtu '1500' +set interfaces wireguard wg200 peer WH56 address '80.151.69.205' +set interfaces wireguard wg200 peer WH56 allowed-ips '0.0.0.0/0' +set interfaces wireguard wg200 peer WH56 port '10200' +set interfaces wireguard wg200 peer WH56 public-key 'XQbkj6vnKKBJfJQyThXysU0iGxCvEOEb31kpaZgkrD8=' +set interfaces wireguard wg200 port '10200' +set interfaces wireguard wg200 private-key 'aGx+fvW916Ej7QRnBbW3QMoldhNv1u95/WHz45zDmF0=' +set interfaces wireguard wg666 address '172.29.0.1/31' +set interfaces wireguard wg666 mtu '1500' +set interfaces wireguard wg666 peer WH34 address '100.65.55.1' +set interfaces wireguard wg666 peer WH34 allowed-ips '0.0.0.0/0' +set interfaces wireguard wg666 peer WH34 port '10666' +set interfaces wireguard wg666 peer WH34 public-key 'yaTN4+xAafKM04D+Baeg5GWfbdaw35TE9HQivwRgAk0=' +set interfaces wireguard wg666 port '10666' +set interfaces wireguard wg666 private-key 'aGx+fvW916Ej7QRnBbW3QMoldhNv1u95/WHz45zDmF0=' +set nat destination rule 8000 destination port '10000' +set nat destination rule 8000 inbound-interface name 'pppoe0' +set nat destination rule 8000 protocol 'udp' +set nat destination rule 8000 translation address '172.31.0.200' +set nat source rule 50 outbound-interface name 'pppoe0' +set nat source rule 50 source address '100.64.0.0/24' +set nat source rule 50 translation address 'masquerade' +set nat source rule 100 outbound-interface name 'pppoe0' +set nat source rule 100 source address '172.16.32.0/21' +set nat source rule 100 translation address 'masquerade' +set nat source rule 200 outbound-interface name 'pppoe0' +set nat source rule 200 source address '172.16.100.0/24' +set nat source rule 200 translation address 'masquerade' +set nat source rule 300 outbound-interface name 'pppoe0' +set nat source rule 300 source address '172.31.0.0/24' +set nat source rule 300 translation address 'masquerade' +set nat source rule 400 outbound-interface name 'pppoe0' +set nat source rule 400 source address '172.18.200.0/21' +set nat source rule 400 translation address 'masquerade' +set nat source rule 1000 destination address '192.168.189.0/24' +set nat source rule 1000 outbound-interface name 'wg666' +set nat source rule 1000 source address '172.16.32.0/21' +set nat source rule 1000 translation address '172.29.0.1' +set nat source rule 1001 destination address '192.168.189.0/24' +set nat source rule 1001 outbound-interface name 'wg666' +set nat source rule 1001 source address '172.16.100.0/24' +set nat source rule 1001 translation address '172.29.0.1' +set policy route-map MAP-OSPF-CONNECTED rule 1 action 'deny' +set policy route-map MAP-OSPF-CONNECTED rule 1 match interface 'eth1.20' +set policy route-map MAP-OSPF-CONNECTED rule 20 action 'permit' +set policy route-map MAP-OSPF-CONNECTED rule 20 match interface 'eth0.10' +set policy route-map MAP-OSPF-CONNECTED rule 40 action 'permit' +set policy route-map MAP-OSPF-CONNECTED rule 40 match interface 'eth0.50' +set protocols bfd peer 172.16.252.129 +set protocols bfd peer 172.16.252.131 +set protocols bfd peer 172.18.254.201 +set protocols bgp address-family ipv4-unicast network 172.16.32.0/21 +set protocols bgp address-family ipv4-unicast network 172.16.100.0/24 +set protocols bgp address-family ipv4-unicast network 172.16.252.128/31 +set protocols bgp address-family ipv4-unicast network 172.16.252.130/31 +set protocols bgp address-family ipv4-unicast network 172.16.254.30/32 +set protocols bgp address-family ipv4-unicast network 172.18.0.0/16 +set protocols bgp neighbor 172.16.252.129 peer-group 'WIREGUARD' +set protocols bgp neighbor 172.16.252.131 peer-group 'WIREGUARD' +set protocols bgp neighbor 172.18.254.201 address-family ipv4-unicast nexthop-self +set protocols bgp neighbor 172.18.254.201 bfd +set protocols bgp neighbor 172.18.254.201 remote-as '64503' +set protocols bgp neighbor 172.18.254.201 update-source 'dum0' +set protocols bgp parameters log-neighbor-changes +set protocols bgp peer-group WIREGUARD address-family ipv4-unicast soft-reconfiguration inbound +set protocols bgp peer-group WIREGUARD bfd +set protocols bgp peer-group WIREGUARD remote-as 'external' +set protocols bgp system-as '64503' +set protocols bgp timers holdtime '30' +set protocols bgp timers keepalive '10' +set protocols ospf area 0 network '172.16.254.30/32' +set protocols ospf area 0 network '172.16.37.0/24' +set protocols ospf area 0 network '172.18.201.0/24' +set protocols ospf area 0 network '172.18.202.0/24' +set protocols ospf area 0 network '172.18.203.0/24' +set protocols ospf area 0 network '172.18.204.0/24' +set protocols ospf default-information originate always +set protocols ospf default-information originate metric-type '2' +set protocols ospf interface eth0.5 authentication md5 key-id 10 md5-key 'ospf' +set protocols ospf interface eth0.5 dead-interval '40' +set protocols ospf interface eth0.5 hello-interval '10' +set protocols ospf interface eth0.5 passive disable +set protocols ospf interface eth0.5 priority '1' +set protocols ospf interface eth0.5 retransmit-interval '5' +set protocols ospf interface eth0.5 transmit-delay '1' +set protocols ospf log-adjacency-changes detail +set protocols ospf parameters abr-type 'cisco' +set protocols ospf parameters router-id '172.16.254.30' +set protocols ospf passive-interface 'default' +set protocols ospf redistribute connected metric-type '2' +set protocols ospf redistribute connected route-map 'MAP-OSPF-CONNECTED' +set protocols static route 10.0.0.0/8 blackhole distance '254' +set protocols static route 169.254.0.0/16 blackhole distance '254' +set protocols static route 172.16.0.0/12 blackhole distance '254' +set protocols static route 172.16.32.0/21 blackhole +set protocols static route 172.18.0.0/16 blackhole +set protocols static route 172.29.0.2/31 next-hop 172.29.0.0 +set protocols static route 192.168.0.0/16 blackhole distance '254' +set protocols static route 192.168.189.0/24 next-hop 172.29.0.0 +set protocols static route6 2000::/3 interface pppoe0 +set qos policy shaper QoS bandwidth '50mbit' +set qos policy shaper QoS default bandwidth '100%' +set qos policy shaper QoS default burst '15k' +set qos policy shaper QoS default queue-limit '1000' +set qos policy shaper QoS default queue-type 'fq-codel' +set service dhcp-server shared-network-name BACKBONE authoritative +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 lease '86400' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 option default-router '172.16.37.254' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 option domain-name 'vyos.net' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 option domain-search 'vyos.net' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 option name-server '172.16.254.30' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 option ntp-server '172.16.254.30' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 range 0 start '172.16.37.120' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 range 0 stop '172.16.37.149' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 static-mapping AP1 ip-address '172.16.37.231' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 static-mapping AP1 mac '02:00:00:00:ee:18' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 static-mapping AP2 ip-address '172.16.37.232' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 static-mapping AP2 mac '02:00:00:00:52:84' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 static-mapping AP3 ip-address '172.16.37.233' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 static-mapping AP3 mac '02:00:00:00:51:c0' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 static-mapping AP4 ip-address '172.16.37.234' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 static-mapping AP4 mac '02:00:00:00:e6:fc' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 static-mapping AP5 ip-address '172.16.37.235' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 static-mapping AP5 mac '02:00:00:00:c3:50' +set service dhcp-server shared-network-name BACKBONE subnet 172.16.37.0/24 subnet-id '1' +set service dhcp-server shared-network-name GUEST authoritative +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 lease '86400' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 option default-router '172.31.0.254' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 option domain-name 'vyos.net' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 option domain-search 'vyos.net' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 option name-server '172.31.0.254' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 range 0 start '172.31.0.101' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 range 0 stop '172.31.0.199' +set service dhcp-server shared-network-name GUEST subnet 172.31.0.0/24 subnet-id '2' +set service dhcp-server shared-network-name LAN authoritative +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 lease '86400' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 option default-router '172.16.33.254' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 option domain-name 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 option domain-search 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 option name-server '172.16.254.30' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 option ntp-server '172.16.254.30' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 range 0 start '172.16.33.100' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 range 0 stop '172.16.33.189' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 static-mapping four ip-address '172.16.33.214' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 static-mapping four mac '02:00:00:00:c4:33' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 static-mapping one ip-address '172.16.33.221' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 static-mapping one mac '02:00:00:00:eb:a6' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 static-mapping three ip-address '172.16.33.212' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 static-mapping three mac '02:00:00:00:12:c7' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 static-mapping two ip-address '172.16.33.211' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 static-mapping two mac '02:00:00:00:58:90' +set service dhcp-server shared-network-name LAN subnet 172.16.33.0/24 subnet-id '3' +set service dns dynamic name service-vyos-pppoe0 address interface 'pppoe0' +set service dns dynamic name service-vyos-pppoe0 host-name 'r1.vyos.net' +set service dns dynamic name service-vyos-pppoe0 password 'vyos' +set service dns dynamic name service-vyos-pppoe0 protocol 'dyndns2' +set service dns dynamic name service-vyos-pppoe0 server 'dyndns.vyos.io' +set service dns dynamic name service-vyos-pppoe0 username 'vyos-vyos' +set service dns forwarding allow-from '172.16.0.0/12' +set service dns forwarding domain 16.172.in-addr.arpa addnta +set service dns forwarding domain 16.172.in-addr.arpa name-server 172.16.100.10 +set service dns forwarding domain 16.172.in-addr.arpa name-server 172.16.100.20 +set service dns forwarding domain 16.172.in-addr.arpa recursion-desired +set service dns forwarding domain 18.172.in-addr.arpa addnta +set service dns forwarding domain 18.172.in-addr.arpa name-server 172.16.100.10 +set service dns forwarding domain 18.172.in-addr.arpa name-server 172.16.100.20 +set service dns forwarding domain 18.172.in-addr.arpa recursion-desired +set service dns forwarding domain vyos.net addnta +set service dns forwarding domain vyos.net name-server 172.16.100.10 +set service dns forwarding domain vyos.net name-server 172.16.100.20 +set service dns forwarding domain vyos.net recursion-desired +set service dns forwarding ignore-hosts-file +set service dns forwarding listen-address '172.16.254.30' +set service dns forwarding listen-address '172.31.0.254' +set service dns forwarding negative-ttl '60' +set service lldp legacy-protocols cdp +set service lldp legacy-protocols edp +set service lldp legacy-protocols fdp +set service lldp legacy-protocols sonmp +set service lldp snmp +set service ntp allow-client address '172.16.0.0/12' +set service ntp server time1.vyos.net +set service ntp server time2.vyos.net +set service router-advert interface eth0.10 prefix ::/64 preferred-lifetime '2700' +set service router-advert interface eth0.10 prefix ::/64 valid-lifetime '5400' +set service router-advert interface eth1.20 prefix ::/64 preferred-lifetime '2700' +set service router-advert interface eth1.20 prefix ::/64 valid-lifetime '5400' +set service snmp community ro-community authorization 'ro' +set service snmp community ro-community network '172.16.100.0/24' +set service snmp contact 'VyOS' +set service snmp listen-address 172.16.254.30 port '161' +set service snmp location 'CLOUD' +set service ssh disable-host-validation +set service ssh port '22' +set system config-management commit-revisions '200' +set system conntrack expect-table-size '2048' +set system conntrack hash-size '32768' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sqlnet +set system conntrack modules tftp +set system conntrack table-size '262144' +set system conntrack timeout +set system console device ttyS0 speed '115200' +set system domain-name 'vyos.net' +set system host-name 'r1' +set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/' +set system login user vyos authentication plaintext-password '' +set system name-server '172.16.254.30' +set system option ctrl-alt-delete 'ignore' +set system option performance 'latency' +set system option reboot-on-panic +set system option startup-beep +set system syslog global facility all level 'debug' +set system syslog global facility local7 level 'debug' +set system syslog host 172.16.100.1 facility all level 'warning' +set system time-zone 'Europe/Berlin' diff --git a/smoketest/config-tests/egp-igp-route-maps b/smoketest/config-tests/egp-igp-route-maps new file mode 100644 index 0000000..fc46d25 --- /dev/null +++ b/smoketest/config-tests/egp-igp-route-maps @@ -0,0 +1,46 @@ +set interfaces ethernet eth0 address '192.0.2.1/25' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth1 address '192.0.2.129/25' +set interfaces ethernet eth1 address '2001:db8::1234/64' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 speed 'auto' +set interfaces loopback lo +set policy route-map zebra-bgp rule 10 action 'permit' +set policy route-map zebra-isis rule 10 action 'permit' +set policy route-map zebra-ospf rule 10 action 'permit' +set policy route-map zebra-ospfv3 rule 10 action 'permit' +set policy route-map zebra-ripng rule 10 action 'permit' +set policy route-map zebra-static rule 10 action 'permit' +set protocols bgp system-as '100' +set protocols isis interface eth0 +set protocols isis net '49.0001.1921.6800.1002.00' +set protocols ospf area 0 network '192.0.2.0/25' +set protocols ospf area 0 network '192.0.2.128/25' +set protocols ospf interface eth0 passive disable +set protocols ospf interface eth1 passive disable +set protocols ospf log-adjacency-changes +set protocols ospf parameters abr-type 'cisco' +set protocols ospf parameters router-id '1.1.1.1' +set protocols ospf passive-interface 'default' +set protocols ospfv3 area 0 +set protocols ospfv3 interface eth1 area '0' +set protocols ospfv3 parameters router-id '1.1.1.1' +set protocols ripng interface eth1 +set protocols static +set system config-management commit-revisions '100' +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system ip protocol bgp route-map 'zebra-bgp' +set system ip protocol isis route-map 'zebra-isis' +set system ip protocol ospf route-map 'zebra-ospf' +set system ip protocol static route-map 'zebra-static' +set system ipv6 protocol ospfv3 route-map 'zebra-ospfv3' +set system ipv6 protocol ripng route-map 'zebra-ripng' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system logs logrotate messages max-size '1' +set system logs logrotate messages rotate '5' +set system name-server '192.168.0.1' +set system syslog global facility all level 'info' +set system time-zone 'Europe/Berlin' diff --git a/smoketest/config-tests/igmp-pim-small b/smoketest/config-tests/igmp-pim-small new file mode 100644 index 0000000..909c3d6 --- /dev/null +++ b/smoketest/config-tests/igmp-pim-small @@ -0,0 +1,37 @@ +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth1 address '100.64.0.1/24' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 speed 'auto' +set interfaces ethernet eth2 address '172.16.0.2/24' +set interfaces ethernet eth2 duplex 'auto' +set interfaces ethernet eth2 offload gro +set interfaces ethernet eth2 speed 'auto' +set protocols pim interface eth1 igmp join 224.1.0.0 source-address '1.1.1.1' +set protocols pim interface eth1 igmp join 224.1.0.0 source-address '1.1.1.2' +set protocols pim interface eth1 igmp query-interval '1000' +set protocols pim interface eth1 igmp query-max-response-time '30' +set protocols pim interface eth1 igmp version '2' +set protocols pim interface eth2 +set protocols pim rp address 172.16.255.1 group '224.0.0.0/4' +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server 0.pool.ntp.org +set service ntp server 1.pool.ntp.org +set service ntp server 2.pool.ntp.org +set system config-management commit-revisions '200' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system domain-name 'vyos.io' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set system time-zone 'Europe/Berlin' diff --git a/smoketest/config-tests/ipoe-server b/smoketest/config-tests/ipoe-server new file mode 100644 index 0000000..f4a12f5 --- /dev/null +++ b/smoketest/config-tests/ipoe-server @@ -0,0 +1,48 @@ +set interfaces ethernet eth0 address 'dhcp' +set interfaces ethernet eth1 address '192.168.0.1/24' +set interfaces ethernet eth2 offload gro +set interfaces loopback lo +set nat source rule 100 outbound-interface name 'eth0' +set nat source rule 100 source address '192.168.0.0/24' +set nat source rule 100 translation address 'masquerade' +set service ipoe-server authentication interface eth1 mac 08:00:27:2f:d8:06 rate-limit download '1000' +set service ipoe-server authentication interface eth1 mac 08:00:27:2f:d8:06 rate-limit upload '500' +set service ipoe-server authentication interface eth1 mac 08:00:27:2f:d8:06 vlan '100' +set service ipoe-server authentication interface eth2 mac 08:00:27:2f:d8:06 +set service ipoe-server authentication mode 'local' +set service ipoe-server client-ip-pool POOL1 range '192.0.2.0/24' +set service ipoe-server client-ipv6-pool ipv6-pool delegate 2001:db8:1::/48 delegation-prefix '56' +set service ipoe-server client-ipv6-pool ipv6-pool prefix 2001:db8::/48 mask '64' +set service ipoe-server default-ipv6-pool 'ipv6-pool' +set service ipoe-server default-pool 'POOL1' +set service ipoe-server gateway-address '192.0.2.1/24' +set service ipoe-server interface eth1 mode 'l3' +set service ipoe-server interface eth1 network 'vlan' +set service ipoe-server interface eth1 vlan '100' +set service ipoe-server interface eth1 vlan '200' +set service ipoe-server interface eth1 vlan '1000-2000' +set service ipoe-server interface eth1 vlan '2500-2700' +set service ipoe-server name-server '10.10.1.1' +set service ipoe-server name-server '10.10.1.2' +set service ipoe-server name-server '2001:db8:aaa::' +set service ipoe-server name-server '2001:db8:bbb::' +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server time1.vyos.net +set service ntp server time2.vyos.net +set service ntp server time3.vyos.net +set service ssh +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' diff --git a/smoketest/config-tests/ipv6-disable b/smoketest/config-tests/ipv6-disable new file mode 100644 index 0000000..40e34fa --- /dev/null +++ b/smoketest/config-tests/ipv6-disable @@ -0,0 +1,31 @@ +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth0 vif 201 address '172.18.201.10/24' +set interfaces ethernet eth0 vif 202 address '172.18.202.10/24' +set interfaces ethernet eth0 vif 203 address '172.18.203.10/24' +set interfaces ethernet eth0 vif 204 address '172.18.204.10/24' +set protocols static route 0.0.0.0/0 next-hop 172.18.201.254 distance '10' +set protocols static route 0.0.0.0/0 next-hop 172.18.202.254 distance '20' +set protocols static route 0.0.0.0/0 next-hop 172.18.203.254 distance '30' +set protocols static route 0.0.0.0/0 next-hop 172.18.204.254 distance '40' +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server 172.16.254.20 +set service ntp server 172.16.254.30 +set system config-management commit-revisions '200' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system domain-name 'vyos.net' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/' +set system login user vyos authentication plaintext-password '' +set system name-server '172.16.254.20' +set system name-server '172.16.254.30' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' diff --git a/smoketest/config-tests/isis-small b/smoketest/config-tests/isis-small new file mode 100644 index 0000000..b322f4e --- /dev/null +++ b/smoketest/config-tests/isis-small @@ -0,0 +1,44 @@ +set interfaces dummy dum0 address '203.0.113.1/24' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 offload sg +set interfaces ethernet eth0 offload tso +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth1 address '192.0.2.1/24' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 offload sg +set interfaces ethernet eth1 offload tso +set interfaces ethernet eth1 speed 'auto' +set interfaces ethernet eth2 duplex 'auto' +set interfaces ethernet eth2 offload sg +set interfaces ethernet eth2 offload tso +set interfaces ethernet eth2 speed 'auto' +set interfaces ethernet eth3 duplex 'auto' +set interfaces ethernet eth3 offload sg +set interfaces ethernet eth3 offload tso +set interfaces ethernet eth3 speed 'auto' +set policy prefix-list EXPORT-ISIS rule 10 action 'permit' +set policy prefix-list EXPORT-ISIS rule 10 prefix '203.0.113.0/24' +set policy route-map EXPORT-ISIS rule 10 action 'permit' +set policy route-map EXPORT-ISIS rule 10 match ip address prefix-list 'EXPORT-ISIS' +set protocols isis interface eth1 bfd +set protocols isis net '49.0001.1921.6800.1002.00' +set protocols isis redistribute ipv4 connected level-2 route-map 'EXPORT-ISIS' +set system config-management commit-revisions '200' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system domain-name 'vyos.io' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/' +set system login user vyos authentication plaintext-password '' +set service ntp server time1.vyos.net +set service ntp server time2.vyos.net +set service ntp server time3.vyos.net +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set system time-zone 'Europe/Berlin' diff --git a/smoketest/config-tests/nat-basic b/smoketest/config-tests/nat-basic new file mode 100644 index 0000000..471add3 --- /dev/null +++ b/smoketest/config-tests/nat-basic @@ -0,0 +1,88 @@ +set interfaces bonding bond10 hash-policy 'layer3+4' +set interfaces bonding bond10 member interface 'eth2' +set interfaces bonding bond10 member interface 'eth3' +set interfaces bonding bond10 mode '802.3ad' +set interfaces bonding bond10 vif 50 address '192.168.189.1/24' +set interfaces ethernet eth0 disable +set interfaces ethernet eth0 offload gro +set interfaces ethernet eth0 offload rps +set interfaces ethernet eth1 offload gro +set interfaces ethernet eth1 offload rps +set interfaces ethernet eth2 offload gro +set interfaces ethernet eth2 offload rps +set interfaces ethernet eth3 offload gro +set interfaces ethernet eth3 offload rps +set interfaces loopback lo +set interfaces pppoe pppoe7 authentication password 'vyos' +set interfaces pppoe pppoe7 authentication username 'vyos' +set interfaces pppoe pppoe7 dhcpv6-options pd 0 interface bond10.50 address '1' +set interfaces pppoe pppoe7 dhcpv6-options pd 0 length '56' +set interfaces pppoe pppoe7 ip adjust-mss '1452' +set interfaces pppoe pppoe7 ipv6 address autoconf +set interfaces pppoe pppoe7 ipv6 adjust-mss '1432' +set interfaces pppoe pppoe7 mtu '1492' +set interfaces pppoe pppoe7 no-peer-dns +set interfaces pppoe pppoe7 source-interface 'eth1' +set nat destination rule 1000 destination port '3389' +set nat destination rule 1000 inbound-interface name 'pppoe7' +set nat destination rule 1000 protocol 'tcp' +set nat destination rule 1000 translation address '192.168.189.5' +set nat destination rule 1000 translation port '3389' +set nat destination rule 10022 destination port '10022' +set nat destination rule 10022 inbound-interface name 'pppoe7' +set nat destination rule 10022 protocol 'tcp' +set nat destination rule 10022 translation address '192.168.189.2' +set nat destination rule 10022 translation port '22' +set nat destination rule 10300 destination port '10300' +set nat destination rule 10300 inbound-interface name 'pppoe7' +set nat destination rule 10300 protocol 'udp' +set nat destination rule 10300 translation address '192.168.189.2' +set nat destination rule 10300 translation port '10300' +set nat source rule 10 outbound-interface name 'eth1' +set nat source rule 10 source address '192.168.189.0/24' +set nat source rule 10 translation address 'masquerade' +set nat source rule 10 translation options port-mapping 'random' +set nat source rule 50 outbound-interface name 'pppoe7' +set nat source rule 50 protocol 'udp' +set nat source rule 50 source address '192.168.189.2' +set nat source rule 50 source port '10300' +set nat source rule 50 translation address 'masquerade' +set nat source rule 50 translation port '10300' +set nat source rule 100 outbound-interface name 'pppoe7' +set nat source rule 100 source address '192.168.189.0/24' +set nat source rule 100 translation address 'masquerade' +set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 lease '604800' +set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 option default-router '192.168.189.1' +set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 option domain-name 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 option name-server '1.1.1.1' +set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 option name-server '9.9.9.9' +set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 range 0 start '192.168.189.20' +set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 range 0 stop '192.168.189.254' +set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 subnet-id '1' +set service lldp interface all +set service lldp interface eth1 disable +set service ntp allow-client address '192.168.189.0/24' +set service ntp listen-address '192.168.189.1' +set service ntp server time1.vyos.net +set service ntp server time2.vyos.net +set service router-advert interface bond10.50 prefix ::/64 preferred-lifetime '2700' +set service router-advert interface bond10.50 prefix ::/64 valid-lifetime '5400' +set service ssh disable-host-validation +set service ssh dynamic-protection +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system domain-name 'vyos.net' +set system host-name 'R1' +set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/' +set system login user vyos authentication plaintext-password '' +set system name-server '1.1.1.1' +set system name-server '9.9.9.9' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' diff --git a/smoketest/config-tests/ospf-simple b/smoketest/config-tests/ospf-simple new file mode 100644 index 0000000..3557094 --- /dev/null +++ b/smoketest/config-tests/ospf-simple @@ -0,0 +1,24 @@ +set interfaces ethernet eth0 vif 20 address '193.201.42.173/28' +set interfaces ethernet eth0 vif 666 address '10.66.66.1/24' +set interfaces ethernet eth1 +set interfaces ethernet eth2 +set interfaces loopback lo +set protocols ospf area 0 area-type normal +set protocols ospf area 0 network '193.201.42.160/28' +set protocols ospf area 0 network '10.66.66.0/24' +set protocols ospf interface eth0.20 cost '999' +set protocols ospf interface eth0.20 dead-interval '4' +set protocols ospf interface eth0.20 hello-interval '1' +set protocols ospf interface eth0.20 priority '255' +set protocols ospf interface eth0.20 retransmit-interval '5' +set protocols ospf interface eth0.20 transmit-delay '1' +set protocols ospf interface eth0.666 passive +set protocols ospf log-adjacency-changes detail +set protocols static route 0.0.0.0/0 next-hop 193.201.42.170 distance '130' +set system config-management commit-revisions '100' +set system console device ttyS0 speed '115200' +set system host-name 'lab-vyos-r1' +set system login user vyos authentication encrypted-password '$6$R.OnGzfXSfl6J$Iba/hl9bmjBs0VPtZ2zdW.Snh/nHuvxUwi0R6ruypgW63iKEbicJH.uUst8xZCyByURblxRtjAC1lAnYfIt.b0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' diff --git a/smoketest/config-tests/ospf-small b/smoketest/config-tests/ospf-small new file mode 100644 index 0000000..a7f8b68 --- /dev/null +++ b/smoketest/config-tests/ospf-small @@ -0,0 +1,82 @@ +set interfaces dummy dum0 address '172.18.254.200/32' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth0 vif 201 address '172.18.201.9/24' +set interfaces ethernet eth0 vif 202 address '172.18.202.9/24' +set interfaces ethernet eth0 vif 203 address '172.18.203.9/24' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 speed 'auto' +set protocols ospf area 0 network '172.18.201.0/24' +set protocols ospf area 0 network '172.18.202.0/24' +set protocols ospf area 0 network '172.18.203.0/24' +set protocols ospf area 0 network '172.18.254.200/32' +set protocols ospf interface eth0.201 authentication md5 key-id 10 md5-key 'OSPFVyOSNET' +set protocols ospf interface eth0.201 dead-interval '40' +set protocols ospf interface eth0.201 hello-interval '10' +set protocols ospf interface eth0.201 passive disable +set protocols ospf interface eth0.201 priority '1' +set protocols ospf interface eth0.201 retransmit-interval '5' +set protocols ospf interface eth0.201 transmit-delay '1' +set protocols ospf interface eth0.202 authentication md5 key-id 10 md5-key 'OSPFVyOSNET' +set protocols ospf interface eth0.202 dead-interval '40' +set protocols ospf interface eth0.202 hello-interval '10' +set protocols ospf interface eth0.202 passive disable +set protocols ospf interface eth0.202 priority '1' +set protocols ospf interface eth0.202 retransmit-interval '5' +set protocols ospf interface eth0.202 transmit-delay '1' +set protocols ospf interface eth0.203 authentication md5 key-id 10 md5-key 'OSPFVyOSNET' +set protocols ospf interface eth0.203 dead-interval '40' +set protocols ospf interface eth0.203 hello-interval '10' +set protocols ospf interface eth0.203 passive disable +set protocols ospf interface eth0.203 priority '1' +set protocols ospf interface eth0.203 retransmit-interval '5' +set protocols ospf interface eth0.203 transmit-delay '1' +set protocols ospf log-adjacency-changes +set protocols ospf parameters abr-type 'cisco' +set protocols ospf parameters router-id '172.18.254.200' +set protocols ospf passive-interface 'default' +set protocols ospfv3 area 0.0.0.0 +set protocols ospfv3 interface eth0.201 area '0.0.0.0' +set protocols ospfv3 interface eth0.201 bfd +set protocols ospfv3 interface eth0.201 cost '40' +set protocols ospfv3 interface eth0.202 area '0.0.0.0' +set protocols ospfv3 interface eth0.202 bfd +set protocols ospfv3 interface eth0.202 cost '40' +set protocols ospfv3 interface eth0.203 area '0.0.0.0' +set protocols ospfv3 interface eth0.203 bfd +set protocols ospfv3 interface eth0.203 cost '40' +set protocols ospfv3 interface eth1 area '0.0.0.0' +set protocols ospfv3 interface eth1 bfd +set protocols ospfv3 interface eth1 cost '60' +set protocols ospfv3 interface eth1 mtu-ignore +set protocols ospfv3 interface eth1 network 'broadcast' +set protocols ospfv3 interface eth1 priority '20' +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server time1.vyos.net +set service ntp server time2.vyos.net +set service ssh disable-host-validation +set service ssh port '22' +set system config-management commit-revisions '200' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system domain-name 'vyos.net' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/' +set system login user vyos authentication plaintext-password '' +set system name-server '172.16.254.30' +set system sysctl parameter net.ipv4.conf.eth0.tag value '1' +set system sysctl parameter net.ipv4.conf.eth1.tag value '1' +set system sysctl parameter net.ipv4.igmp_max_memberships value '5' +set system sysctl parameter net.ipv4.ipfrag_time value '4' +set system sysctl parameter net.mpls.default_ttl value '10' +set system sysctl parameter net.mpls.ip_ttl_propagate value '0' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set system time-zone 'Europe/Berlin' diff --git a/smoketest/config-tests/pppoe-server b/smoketest/config-tests/pppoe-server new file mode 100644 index 0000000..34fbea2 --- /dev/null +++ b/smoketest/config-tests/pppoe-server @@ -0,0 +1,47 @@ +set interfaces ethernet eth0 address 'dhcp' +set interfaces ethernet eth1 address '192.168.0.1/24' +set interfaces ethernet eth1 speed 'auto' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth2 speed 'auto' +set interfaces ethernet eth2 duplex 'auto' +set interfaces loopback lo +set nat source rule 100 outbound-interface name 'eth0' +set nat source rule 100 source address '192.168.0.0/24' +set nat source rule 100 translation address 'masquerade' +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server 0.pool.ntp.org +set service ntp server 1.pool.ntp.org +set service ntp server 2.pool.ntp.org +set service pppoe-server access-concentrator 'ACN' +set service pppoe-server authentication local-users username foo password 'bar' +set service pppoe-server authentication local-users username foo rate-limit download '20480' +set service pppoe-server authentication local-users username foo rate-limit upload '10240' +set service pppoe-server authentication mode 'local' +set service pppoe-server client-ip-pool default-range-pool range '10.0.0.0/24' +set service pppoe-server client-ip-pool default-range-pool range '10.0.1.0/24' +set service pppoe-server client-ip-pool default-range-pool range '10.0.2.0/24' +set service pppoe-server default-pool 'default-range-pool' +set service pppoe-server gateway-address '192.168.0.2' +set service pppoe-server interface eth1 +set service pppoe-server interface eth2 vlan '10' +set service pppoe-server interface eth2 vlan '20' +set service pppoe-server interface eth2 vlan '30-40' +set service pppoe-server interface eth2 vlan '50-60' +set service pppoe-server name-server '192.168.0.1' +set service pppoe-server ppp-options disable-ccp +set service ssh +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' diff --git a/smoketest/config-tests/qos-basic b/smoketest/config-tests/qos-basic new file mode 100644 index 0000000..0e198b8 --- /dev/null +++ b/smoketest/config-tests/qos-basic @@ -0,0 +1,75 @@ +set interfaces ethernet eth0 address '10.1.1.100/24' +set interfaces ethernet eth1 address '10.2.1.1/24' +set interfaces ethernet eth2 address '10.9.9.1/24' +set interfaces ethernet eth2 vif 200 +set interfaces loopback lo +set qos interface eth0 egress 'FS' +set qos interface eth1 egress 'ISPC' +set qos interface eth2 egress 'MY-HTB' +set qos interface eth2.200 egress 'foo-emulate' +set qos policy network-emulator foo-emulate bandwidth '300mbit' +set qos policy shaper FS bandwidth 'auto' +set qos policy shaper FS class 10 bandwidth '100%' +set qos policy shaper FS class 10 burst '15k' +set qos policy shaper FS class 10 match ADDRESS10 ip source address '172.17.1.2/32' +set qos policy shaper FS class 10 queue-type 'fair-queue' +set qos policy shaper FS class 20 bandwidth '100%' +set qos policy shaper FS class 20 burst '15k' +set qos policy shaper FS class 20 match ADDRESS20 ip source address '172.17.1.3/32' +set qos policy shaper FS class 20 queue-type 'fair-queue' +set qos policy shaper FS class 30 bandwidth '100%' +set qos policy shaper FS class 30 burst '15k' +set qos policy shaper FS class 30 match ADDRESS30 ip source address '172.17.1.4/32' +set qos policy shaper FS class 30 queue-type 'fair-queue' +set qos policy shaper FS default bandwidth '10%' +set qos policy shaper FS default burst '15k' +set qos policy shaper FS default ceiling '100%' +set qos policy shaper FS default priority '7' +set qos policy shaper FS default queue-type 'fair-queue' +set qos policy shaper ISPC bandwidth '600mbit' +set qos policy shaper ISPC default bandwidth '50%' +set qos policy shaper ISPC default burst '768k' +set qos policy shaper ISPC default ceiling '100%' +set qos policy shaper ISPC default queue-type 'fq-codel' +set qos policy shaper ISPC description 'Outbound Traffic Shaper - ISPC' +set qos policy shaper MY-HTB bandwidth '10mbit' +set qos policy shaper MY-HTB class 30 bandwidth '10%' +set qos policy shaper MY-HTB class 30 burst '15k' +set qos policy shaper MY-HTB class 30 ceiling '50%' +set qos policy shaper MY-HTB class 30 match ADDRESS30 ip source address '10.1.1.0/24' +set qos policy shaper MY-HTB class 30 priority '5' +set qos policy shaper MY-HTB class 30 queue-type 'fair-queue' +set qos policy shaper MY-HTB class 40 bandwidth '90%' +set qos policy shaper MY-HTB class 40 burst '15k' +set qos policy shaper MY-HTB class 40 ceiling '100%' +set qos policy shaper MY-HTB class 40 match ADDRESS40 ip source address '10.2.1.0/24' +set qos policy shaper MY-HTB class 40 priority '5' +set qos policy shaper MY-HTB class 40 queue-type 'fair-queue' +set qos policy shaper MY-HTB class 50 bandwidth '100%' +set qos policy shaper MY-HTB class 50 burst '15k' +set qos policy shaper MY-HTB class 50 match ADDRESS50 ipv6 source address '2001:db8::1/64' +set qos policy shaper MY-HTB class 50 queue-type 'fair-queue' +set qos policy shaper MY-HTB default bandwidth '10%' +set qos policy shaper MY-HTB default burst '15k' +set qos policy shaper MY-HTB default ceiling '100%' +set qos policy shaper MY-HTB default priority '7' +set qos policy shaper MY-HTB default queue-type 'fair-queue' +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server time1.vyos.net +set service ntp server time2.vyos.net +set service ntp server time3.vyos.net +set system config-management commit-revisions '10' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$r/Yw/07NXNY$/ZB.Rjf9jxEV.BYoDyLdH.kH14rU52pOBtrX.4S34qlPt77chflCHvpTCq9a6huLzwaMR50rEICzA5GoIRZlM0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' diff --git a/smoketest/config-tests/rip-router b/smoketest/config-tests/rip-router new file mode 100644 index 0000000..829aafb --- /dev/null +++ b/smoketest/config-tests/rip-router @@ -0,0 +1,83 @@ +set interfaces dummy dum0 address '192.0.2.0/32' +set interfaces ethernet eth0 address '172.18.202.10/24' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 speed 'auto' +set interfaces ethernet eth1 vif 20 +set interfaces ethernet eth1 vif-s 200 vif-c 2000 +set interfaces ethernet eth1 vif-s 200 vif-c 3000 +set policy access-list6 198 rule 10 action 'permit' +set policy access-list6 198 rule 10 source any +set policy access-list6 199 rule 20 action 'deny' +set policy access-list6 199 rule 20 source any +set policy prefix-list6 bar-prefix rule 200 action 'deny' +set policy prefix-list6 bar-prefix rule 200 prefix '2001:db8::/32' +set policy prefix-list6 foo-prefix rule 100 action 'permit' +set policy prefix-list6 foo-prefix rule 100 prefix '2001:db8::/32' +set policy route-map FooBar123 rule 10 action 'permit' +set protocols rip default-distance '20' +set protocols rip default-information originate +set protocols rip interface eth0 authentication md5 1 password 'VyOSsecure' +set protocols rip interface eth0 split-horizon poison-reverse +set protocols rip interface eth1.20 authentication plaintext-password 'VyOSsecure' +set protocols rip interface eth1.20 split-horizon poison-reverse +set protocols rip interface eth1.200 authentication md5 1 password 'VyOSsecure' +set protocols rip interface eth1.200 split-horizon disable +set protocols rip interface eth1.200.2000 authentication md5 1 password 'VyOSsecure' +set protocols rip interface eth1.200.3000 split-horizon disable +set protocols rip network '192.168.0.0/24' +set protocols rip redistribute connected +set protocols ripng aggregate-address '2001:db8:1000::/48' +set protocols ripng default-information originate +set protocols ripng default-metric '8' +set protocols ripng distribute-list access-list in '198' +set protocols ripng distribute-list access-list out '199' +set protocols ripng distribute-list interface eth0 access-list in '198' +set protocols ripng distribute-list interface eth0 access-list out '199' +set protocols ripng distribute-list interface eth0 prefix-list in 'foo-prefix' +set protocols ripng distribute-list interface eth0 prefix-list out 'bar-prefix' +set protocols ripng distribute-list interface eth1 access-list in '198' +set protocols ripng distribute-list interface eth1 access-list out '199' +set protocols ripng distribute-list interface eth1 prefix-list in 'foo-prefix' +set protocols ripng distribute-list interface eth1 prefix-list out 'bar-prefix' +set protocols ripng distribute-list interface eth2 access-list in '198' +set protocols ripng distribute-list interface eth2 access-list out '199' +set protocols ripng distribute-list interface eth2 prefix-list in 'foo-prefix' +set protocols ripng distribute-list interface eth2 prefix-list out 'bar-prefix' +set protocols ripng distribute-list prefix-list in 'foo-prefix' +set protocols ripng distribute-list prefix-list out 'bar-prefix' +set protocols ripng interface eth0 split-horizon poison-reverse +set protocols ripng interface eth1.20 split-horizon disable +set protocols ripng interface eth1.200 split-horizon poison-reverse +set protocols ripng interface eth1.200.3000 split-horizon poison-reverse +set protocols ripng network '2001:db8:1000::/64' +set protocols ripng network '2001:db8:1001::/64' +set protocols ripng network '2001:db8:2000::/64' +set protocols ripng network '2001:db8:2001::/64' +set protocols ripng passive-interface 'default' +set protocols ripng redistribute connected metric '8' +set protocols ripng redistribute connected route-map 'FooBar123' +set protocols ripng redistribute static metric '8' +set protocols ripng redistribute static route-map 'FooBar123' +set protocols ripng route '2001:db8:1000::/64' +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server 0.pool.ntp.org +set service ntp server 1.pool.ntp.org +set service ntp server 2.pool.ntp.org +set service ssh port '22' +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' diff --git a/smoketest/config-tests/rpki-only b/smoketest/config-tests/rpki-only new file mode 100644 index 0000000..dcbc767 --- /dev/null +++ b/smoketest/config-tests/rpki-only @@ -0,0 +1,42 @@ +set interfaces ethernet eth0 address '192.0.2.1/24' +set interfaces ethernet eth0 address '2001:db8::1/64' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 speed 'auto' +set interfaces loopback lo +set pki openssh rpki-5.6.7.8 private key 'b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAweDyflDFR4qyEwETbJkZ2ZZc+sJNiDTvYpwGsWIkju49lJSxHe1xKf8FhwfyMu40Snt1yDlRmmmz4CsbLgbuZGMPvXG11e34+C0pSVUvpF6aqRTeLl1pDRK7Rnjgm3su+I8SRLQR4qbLG6VXWOFuVpwiqbExLaU0hFYTPNP+dArNpsWEEKsohk6pTXdhg3VzWp3vCMjl2JTshDa3lD7p2xISSAReEY0fnfEAmQzH4Z6DIwwGdFuMWoQIg+oFBM9ARrO2/FIjRsz6AecR/WeU72JEw4aJic1/cAJQA6PiQBHwkuo3Wll1tbpxeRZoB2NQG22ETyJLvhfTaooNLT9HpQAAA8joU5dM6FOXTAAAAAdzc2gtcnNhAAABAQDB4PJ+UMVHirITARNsmRnZllz6wk2INO9inAaxYiSO7j2UlLEd7XEp/wWHB/Iy7jRKe3XIOVGaabPgKxsuBu5kYw+9cbXV7fj4LSlJVS+kXpqpFN4uXWkNErtGeOCbey74jxJEtBHipssbpVdY4W5WnCKpsTEtpTSEVhM80/50Cs2mxYQQqyiGTqlNd2GDdXNane8IyOXYlOyENreUPunbEhJIBF4RjR+d8QCZDMfhnoMjDAZ0W4xahAiD6gUEz0BGs7b8UiNGzPoB5xH9Z5TvYkTDhomJzX9wAlADo+JAEfCS6jdaWXW1unF5FmgHY1AbbYRPIku+F9Nqig0tP0elAAAAAwEAAQAAAQACkDlUjzfUhtJs6uY5WNrdJB5NmHUS+HQzzxFNlhkapK6+wKqI1UNaRUtq6iF7J+gcFf7MK2nXS098BsXguWm8fQzPuemoDvHsQhiaJhyvpSqRUrvPTB/f8t/0AhQiKiJIWgfpTaIw53inAGwjujNNxNm2eafHTThhCYxOkRT7rsT6bnSio6yeqPy5QHg7IKFztp5FXDUyiOS3aX3SvzQcDUkMXALdvzX50t1XIk+X48Rgkq72dL4VpV2oMNDu3hM6FqBUplf9Mv3s51FNSma/cibCQoVufrIfoqYjkNTjIpYFUcq4zZ0/KvgXgzSsy9VN/4TtbalrOuu7X/SHJbvhAAAAgGPFsXgONYQvXxCnK1dIueozgaZg1I/n522E2ZCOXBW4dYJVyNpppwRreDzuFzTDEe061MpNHfScjVBJCCulivFYWscL6oaGsryDbFxO3QmB4I98UBqrds2yan9/JGc6EYe299yvaHy7Y64+NC0+fN8H2RAZ61T4w10JrCaJRyvzAAAAgQDvBfuV1U7o9k/fbU+U7W2UYnWblpOZAMfi1XQP6IJJeyWs90PdTdXh+l0eIQrCawIiRJytNfxMmbD4huwTf77fWiyCcPznmALQ7ex/yJ+W5Z0V4dPGF3h7o1uiS236JhQ7mfcliCkhp/1PIklBIMPcCp0zl+s9wMv2hX7w1Pah9QAAAIEAz6YgU9Xute+J+dBwoWxEQ+igR6KE55Um7O9AvSrqnCm9r7lSFsXC2ErYOxoDSJ3yIBEV0b4XAGn6tbbVIs3jS8BnLHxclAHQecOx1PGn7PKbnPW0oJRq/X9QCIEelKYvlykpayn7uZooTXqcDaPZxfPpmPdye8chVJvdygi7kPEAAAAMY3BvQExSMS53dWUzAQIDBAUGBw==' +set pki openssh rpki-5.6.7.8 public key 'AAAAB3NzaC1yc2EAAAADAQABAAABAQDB4PJ+UMVHirITARNsmRnZllz6wk2INO9inAaxYiSO7j2UlLEd7XEp/wWHB/Iy7jRKe3XIOVGaabPgKxsuBu5kYw+9cbXV7fj4LSlJVS+kXpqpFN4uXWkNErtGeOCbey74jxJEtBHipssbpVdY4W5WnCKpsTEtpTSEVhM80/50Cs2mxYQQqyiGTqlNd2GDdXNane8IyOXYlOyENreUPunbEhJIBF4RjR+d8QCZDMfhnoMjDAZ0W4xahAiD6gUEz0BGs7b8UiNGzPoB5xH9Z5TvYkTDhomJzX9wAlADo+JAEfCS6jdaWXW1unF5FmgHY1AbbYRPIku+F9Nqig0tP0el' +set pki openssh rpki-5.6.7.8 public type 'ssh-rsa' +set policy route-map ROUTES-IN rule 10 action 'permit' +set policy route-map ROUTES-IN rule 10 match rpki 'valid' +set policy route-map ROUTES-IN rule 10 set local-preference '300' +set policy route-map ROUTES-IN rule 20 action 'permit' +set policy route-map ROUTES-IN rule 20 match rpki 'notfound' +set policy route-map ROUTES-IN rule 20 set local-preference '125' +set policy route-map ROUTES-IN rule 30 action 'deny' +set policy route-map ROUTES-IN rule 30 match rpki 'invalid' +set protocols bgp neighbor 192.0.2.200 address-family ipv4-unicast route-map import 'ROUTES-IN' +set protocols bgp neighbor 192.0.2.200 remote-as '200' +set protocols bgp neighbor 2001:db8::200 address-family ipv4-unicast +set protocols bgp neighbor 2001:db8::200 address-family ipv6-unicast route-map import 'ROUTES-IN' +set protocols bgp neighbor 2001:db8::200 remote-as '200' +set protocols bgp system-as '100' +set protocols rpki cache 1.2.3.4 port '3323' +set protocols rpki cache 1.2.3.4 preference '10' +set protocols rpki cache 5.6.7.8 port '2222' +set protocols rpki cache 5.6.7.8 preference '20' +set protocols rpki cache 5.6.7.8 ssh key 'rpki-5.6.7.8' +set protocols rpki cache 5.6.7.8 ssh username 'vyos' +set system config-management commit-revisions '200' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$r/Yw/07NXNY$/ZB.Rjf9jxEV.BYoDyLdH.kH14rU52pOBtrX.4S34qlPt77chflCHvpTCq9a6huLzwaMR50rEICzA5GoIRZlM0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'debug' +set system syslog global facility local7 level 'debug' diff --git a/smoketest/config-tests/tunnel-broker b/smoketest/config-tests/tunnel-broker new file mode 100644 index 0000000..ee6301c --- /dev/null +++ b/smoketest/config-tests/tunnel-broker @@ -0,0 +1,75 @@ +set interfaces dummy dum0 address '192.0.2.0/32' +set interfaces dummy dum1 address '192.0.2.1/32' +set interfaces dummy dum2 address '192.0.2.2/32' +set interfaces dummy dum3 address '192.0.2.3/32' +set interfaces dummy dum4 address '192.0.2.4/32' +set interfaces ethernet eth0 address '172.18.202.10/24' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 speed 'auto' +set interfaces l2tpv3 l2tpeth10 destination-port '5010' +set interfaces l2tpv3 l2tpeth10 encapsulation 'ip' +set interfaces l2tpv3 l2tpeth10 peer-session-id '110' +set interfaces l2tpv3 l2tpeth10 peer-tunnel-id '10' +set interfaces l2tpv3 l2tpeth10 remote '172.18.202.110' +set interfaces l2tpv3 l2tpeth10 session-id '110' +set interfaces l2tpv3 l2tpeth10 source-address '172.18.202.10' +set interfaces l2tpv3 l2tpeth10 source-port '5010' +set interfaces l2tpv3 l2tpeth10 tunnel-id '10' +set interfaces l2tpv3 l2tpeth20 destination-port '5020' +set interfaces l2tpv3 l2tpeth20 encapsulation 'ip' +set interfaces l2tpv3 l2tpeth20 peer-session-id '120' +set interfaces l2tpv3 l2tpeth20 peer-tunnel-id '20' +set interfaces l2tpv3 l2tpeth20 remote '172.18.202.120' +set interfaces l2tpv3 l2tpeth20 session-id '120' +set interfaces l2tpv3 l2tpeth20 source-address '172.18.202.10' +set interfaces l2tpv3 l2tpeth20 source-port '5020' +set interfaces l2tpv3 l2tpeth20 tunnel-id '20' +set interfaces l2tpv3 l2tpeth30 destination-port '5030' +set interfaces l2tpv3 l2tpeth30 encapsulation 'ip' +set interfaces l2tpv3 l2tpeth30 peer-session-id '130' +set interfaces l2tpv3 l2tpeth30 peer-tunnel-id '30' +set interfaces l2tpv3 l2tpeth30 remote '172.18.202.130' +set interfaces l2tpv3 l2tpeth30 session-id '130' +set interfaces l2tpv3 l2tpeth30 source-address '172.18.202.10' +set interfaces l2tpv3 l2tpeth30 source-port '5030' +set interfaces l2tpv3 l2tpeth30 tunnel-id '30' +set interfaces tunnel tun100 address '172.16.0.1/30' +set interfaces tunnel tun100 encapsulation 'gretap' +set interfaces tunnel tun100 remote '192.0.2.100' +set interfaces tunnel tun100 source-address '192.0.2.1' +set interfaces tunnel tun200 address '172.16.0.5/30' +set interfaces tunnel tun200 encapsulation 'gre' +set interfaces tunnel tun200 remote '192.0.2.101' +set interfaces tunnel tun200 source-interface 'eth0' +set interfaces tunnel tun300 address '172.16.0.9/30' +set interfaces tunnel tun300 encapsulation 'ipip' +set interfaces tunnel tun300 remote '192.0.2.102' +set interfaces tunnel tun300 source-address '192.0.2.2' +set interfaces tunnel tun400 address '172.16.0.13/30' +set interfaces tunnel tun400 encapsulation 'gretap' +set interfaces tunnel tun400 remote '192.0.2.103' +set interfaces tunnel tun400 source-address '192.0.2.3' +set interfaces tunnel tun500 address '172.16.0.17/30' +set interfaces tunnel tun500 encapsulation 'gre' +set interfaces tunnel tun500 remote '192.0.2.104' +set interfaces tunnel tun500 source-address '192.0.2.4' +set protocols static route 0.0.0.0/0 next-hop 172.18.202.254 +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server 0.pool.ntp.org +set service ntp server 1.pool.ntp.org +set service ntp server 2.pool.ntp.org +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' diff --git a/smoketest/config-tests/vpn-openconnect-sstp b/smoketest/config-tests/vpn-openconnect-sstp new file mode 100644 index 0000000..28d7d5d --- /dev/null +++ b/smoketest/config-tests/vpn-openconnect-sstp @@ -0,0 +1,35 @@ +set interfaces ethernet eth0 address '192.168.150.1/24' +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server time1.vyos.net +set service ntp server time2.vyos.net +set service ntp server time3.vyos.net +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set vpn openconnect authentication local-users username test password 'test' +set vpn openconnect authentication mode local 'password' +set vpn openconnect network-settings client-ip-settings subnet '192.168.160.0/24' +set vpn openconnect ssl ca-certificate 'openconnect' +set vpn openconnect ssl certificate 'openconnect' +set vpn openconnect tls-version-min '1.0' +set vpn sstp authentication local-users username test password 'test' +set vpn sstp authentication mode 'local' +set vpn sstp authentication protocols 'mschap-v2' +set vpn sstp client-ip-pool default-range-pool range '192.168.170.0/24' +set vpn sstp default-pool 'default-range-pool' +set vpn sstp gateway-address '192.168.150.1' +set vpn sstp port '8443' +set vpn sstp ssl ca-certificate 'sstp' +set vpn sstp ssl certificate 'sstp' diff --git a/smoketest/config-tests/vrf-basic b/smoketest/config-tests/vrf-basic new file mode 100644 index 0000000..1d2874a --- /dev/null +++ b/smoketest/config-tests/vrf-basic @@ -0,0 +1,65 @@ +set interfaces ethernet eth0 address '192.0.2.1/24' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 speed 'auto' +set interfaces ethernet eth1 vrf 'green' +set interfaces ethernet eth2 vrf 'red' +set protocols static route 0.0.0.0/0 next-hop 192.0.2.254 distance '10' +set protocols static table 10 route 1.0.0.0/8 interface eth0 distance '20' +set protocols static table 10 route 2.0.0.0/8 interface eth0 distance '20' +set protocols static table 10 route 3.0.0.0/8 interface eth0 distance '20' +set protocols static table 20 route 4.0.0.0/8 interface eth0 distance '20' +set protocols static table 20 route 5.0.0.0/8 interface eth0 distance '50' +set protocols static table 20 route 6.0.0.0/8 interface eth0 distance '60' +set protocols static table 20 route 11.0.0.0/8 next-hop 1.1.1.1 interface 'eth0' +set protocols static table 20 route 12.0.0.0/8 next-hop 1.1.1.1 interface 'eth0' +set protocols static table 20 route 13.0.0.0/8 next-hop 1.1.1.1 interface 'eth0' +set protocols static table 20 route6 2001:db8:100::/40 interface eth1 distance '20' +set protocols static table 20 route6 2001:db8::/40 interface eth1 distance '10' +set protocols static table 30 route 14.0.0.0/8 next-hop 2.2.1.1 interface 'eth1' +set protocols static table 30 route 15.0.0.0/8 next-hop 2.2.1.1 interface 'eth1' +set protocols static table 30 route6 2001:db8:200::/40 interface eth1 distance '20' +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server 0.pool.ntp.org +set service ntp server 1.pool.ntp.org +set service ntp server 2.pool.ntp.org +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set system time-zone 'Europe/Berlin' +set vrf name green protocols static route 20.0.0.0/8 next-hop 1.1.1.1 interface 'eth1' +set vrf name green protocols static route 20.0.0.0/8 next-hop 1.1.1.1 vrf 'default' +set vrf name green protocols static route 21.0.0.0/8 next-hop 2.2.1.1 interface 'eth1' +set vrf name green protocols static route 21.0.0.0/8 next-hop 2.2.1.1 vrf 'default' +set vrf name green protocols static route 100.0.0.0/8 interface eth0 distance '200' +set vrf name green protocols static route 100.0.0.0/8 interface eth0 vrf 'default' +set vrf name green protocols static route 101.0.0.0/8 interface eth0 vrf 'default' +set vrf name green protocols static route 101.0.0.0/8 interface eth1 +set vrf name green protocols static route6 2001:db8:100::/40 next-hop fe80::1 interface 'eth0' +set vrf name green protocols static route6 2001:db8:100::/40 next-hop fe80::1 vrf 'default' +set vrf name green protocols static route6 2001:db8:300::/40 interface eth1 distance '20' +set vrf name green protocols static route6 2001:db8:300::/40 interface eth1 vrf 'default' +set vrf name green table '1000' +set vrf name red protocols static route 30.0.0.0/8 next-hop 1.1.1.1 interface 'eth1' +set vrf name red protocols static route 40.0.0.0/8 next-hop 2.2.1.1 interface 'eth1' +set vrf name red protocols static route 40.0.0.0/8 next-hop 2.2.1.1 vrf 'default' +set vrf name red protocols static route 103.0.0.0/8 interface eth0 distance '201' +set vrf name red protocols static route 103.0.0.0/8 interface eth0 vrf 'default' +set vrf name red protocols static route 104.0.0.0/8 interface eth0 vrf 'default' +set vrf name red protocols static route 104.0.0.0/8 interface eth1 vrf 'default' +set vrf name red protocols static route6 2001:db8:100::/40 next-hop fe80::1 interface 'eth0' +set vrf name red protocols static route6 2001:db8:100::/40 next-hop fe80::1 vrf 'default' +set vrf name red protocols static route6 2001:db8:400::/40 interface eth1 distance '24' +set vrf name red protocols static route6 2001:db8:400::/40 interface eth1 vrf 'default' +set vrf name red table '2000' diff --git a/smoketest/config-tests/vrf-bgp-pppoe-underlay b/smoketest/config-tests/vrf-bgp-pppoe-underlay new file mode 100644 index 0000000..bd64c91 --- /dev/null +++ b/smoketest/config-tests/vrf-bgp-pppoe-underlay @@ -0,0 +1,186 @@ +set interfaces bridge br50 address '192.168.0.1/24' +set interfaces bridge br50 member interface eth0.50 +set interfaces bridge br50 member interface eth2 +set interfaces bridge br50 member interface eth3 +set interfaces dummy dum0 address '100.64.51.252/32' +set interfaces dummy dum0 address '2001:db8:200:ffff::1/128' +set interfaces dummy dum0 vrf 'vyos-test-01' +set interfaces ethernet eth0 offload gro +set interfaces ethernet eth0 offload rps +set interfaces ethernet eth0 ring-buffer rx '256' +set interfaces ethernet eth0 ring-buffer tx '256' +set interfaces ethernet eth0 vif 5 address '2001:db8:200:f0::114/64' +set interfaces ethernet eth0 vif 5 address '100.64.50.121/28' +set interfaces ethernet eth0 vif 5 vrf 'vyos-test-01' +set interfaces ethernet eth0 vif 10 address '2001:db8:200:10::ffff/64' +set interfaces ethernet eth0 vif 10 address '2001:db8:200::ffff/64' +set interfaces ethernet eth0 vif 10 address '100.64.50.62/26' +set interfaces ethernet eth0 vif 10 vrf 'vyos-test-01' +set interfaces ethernet eth0 vif 15 address '100.64.50.78/28' +set interfaces ethernet eth0 vif 15 address '2001:db8:200:15::ffff/64' +set interfaces ethernet eth0 vif 15 vrf 'vyos-test-01' +set interfaces ethernet eth0 vif 50 description 'Member of bridge br50' +set interfaces ethernet eth0 vif 110 address '100.64.51.190/27' +set interfaces ethernet eth0 vif 110 address '100.64.51.158/28' +set interfaces ethernet eth0 vif 110 address '2001:db8:200:101::ffff/64' +set interfaces ethernet eth0 vif 110 vrf 'vyos-test-01' +set interfaces ethernet eth0 vif 410 address '100.64.51.206/28' +set interfaces ethernet eth0 vif 410 address '2001:db8:200:104::ffff/64' +set interfaces ethernet eth0 vif 410 vrf 'vyos-test-01' +set interfaces ethernet eth0 vif 500 address '100.64.51.238/28' +set interfaces ethernet eth0 vif 500 address '2001:db8:200:50::ffff/64' +set interfaces ethernet eth0 vif 500 vrf 'vyos-test-01' +set interfaces ethernet eth0 vif 520 address '100.64.50.190/28' +set interfaces ethernet eth0 vif 520 address '2001:db8:200:520::ffff/64' +set interfaces ethernet eth0 vif 520 vrf 'vyos-test-01' +set interfaces ethernet eth0 vif 666 address '2001:db8:200:ff::101:1/112' +set interfaces ethernet eth0 vif 666 address '100.64.51.223/31' +set interfaces ethernet eth0 vif 666 vrf 'vyos-test-01' +set interfaces ethernet eth0 vif 800 address '2001:db8:200:ff::104:1/112' +set interfaces ethernet eth0 vif 800 address '100.64.51.212/31' +set interfaces ethernet eth0 vif 800 vrf 'vyos-test-01' +set interfaces ethernet eth0 vif 810 address '100.64.51.30/27' +set interfaces ethernet eth0 vif 810 address '2001:db8:200:102::ffff/64' +set interfaces ethernet eth0 vif 810 vrf 'vyos-test-01' +set interfaces ethernet eth1 offload gro +set interfaces ethernet eth1 offload rps +set interfaces ethernet eth1 ring-buffer rx '256' +set interfaces ethernet eth1 ring-buffer tx '256' +set interfaces ethernet eth2 offload gro +set interfaces ethernet eth3 offload gro +set interfaces loopback lo +set interfaces pppoe pppoe7 authentication password 'vyos' +set interfaces pppoe pppoe7 authentication username 'vyos' +set interfaces pppoe pppoe7 dhcpv6-options pd 0 interface br50 address '1' +set interfaces pppoe pppoe7 dhcpv6-options pd 0 length '56' +set interfaces pppoe pppoe7 ip adjust-mss '1452' +set interfaces pppoe pppoe7 ipv6 address autoconf +set interfaces pppoe pppoe7 ipv6 adjust-mss '1432' +set interfaces pppoe pppoe7 mtu '1492' +set interfaces pppoe pppoe7 no-peer-dns +set interfaces pppoe pppoe7 source-interface 'eth1' +set interfaces virtual-ethernet veth0 address '100.64.51.220/31' +set interfaces virtual-ethernet veth0 address '2001:db8:200:ff::105:1/112' +set interfaces virtual-ethernet veth0 description 'Core: connect vyos-test-01 and default VRF' +set interfaces virtual-ethernet veth0 peer-name 'veth1' +set interfaces virtual-ethernet veth1 address '100.64.51.221/31' +set interfaces virtual-ethernet veth1 address '2001:db8:200:ff::105:2/112' +set interfaces virtual-ethernet veth1 description 'Core: connect vyos-test-01 and default VRF' +set interfaces virtual-ethernet veth1 peer-name 'veth0' +set interfaces virtual-ethernet veth1 vrf 'vyos-test-01' +set interfaces wireguard wg500 address '100.64.51.209/31' +set interfaces wireguard wg500 mtu '1500' +set interfaces wireguard wg500 peer A address '192.0.2.1' +set interfaces wireguard wg500 peer A allowed-ips '0.0.0.0/0' +set interfaces wireguard wg500 peer A port '5500' +set interfaces wireguard wg500 peer A public-key 'KGSXF4QckzGe7f7CT+r6VZ5brOD/pVYk8yvrxOQ+X0Y=' +set interfaces wireguard wg500 port '5500' +set interfaces wireguard wg500 private-key 'iLJh6Me6AdPJtNv3dgGhUbtyFxExxmNU4v0Fs6YE2Xc=' +set interfaces wireguard wg500 vrf 'vyos-test-01' +set interfaces wireguard wg501 address '2001:db8:200:ff::102:2/112' +set interfaces wireguard wg501 mtu '1500' +set interfaces wireguard wg501 peer A address '2001:db8:300::1' +set interfaces wireguard wg501 peer A allowed-ips '::/0' +set interfaces wireguard wg501 peer A port '5501' +set interfaces wireguard wg501 peer A public-key 'OF+1OJ+VfQ0Yw1mgVtQ2ion4CnAdy8Bvx7yEiO4+Pn8=' +set interfaces wireguard wg501 port '5501' +set interfaces wireguard wg501 private-key '0MP5X0PW58O4q2LDpuIXgZ0ySyAoWH8/kdpvQccCbUU=' +set interfaces wireguard wg501 vrf 'vyos-test-01' +set interfaces wireguard wg666 address '172.29.0.0/31' +set interfaces wireguard wg666 mtu '1500' +set interfaces wireguard wg666 peer B allowed-ips '0.0.0.0/0' +set interfaces wireguard wg666 peer B public-key '2HT+RfwcqJMYNYzdmtmpem8Ht0dL37o31APHVwmh024=' +set interfaces wireguard wg666 port '50666' +set interfaces wireguard wg666 private-key 'zvPnp2MLAoX7SotuHLFLDyy4sdlD7ttbD1xNEqA3mkU=' +set nat source rule 100 outbound-interface name 'pppoe7' +set nat source rule 100 source address '192.168.0.0/24' +set nat source rule 100 translation address 'masquerade' +set policy prefix-list AS100-origin-v4 rule 10 action 'permit' +set policy prefix-list AS100-origin-v4 rule 10 prefix '100.64.0.0/12' +set policy prefix-list AS100-origin-v4 rule 100 action 'permit' +set policy prefix-list AS100-origin-v4 rule 100 prefix '0.0.0.0/0' +set policy prefix-list AS200-origin-v4 rule 10 action 'permit' +set policy prefix-list AS200-origin-v4 rule 10 prefix '10.0.0.0/8' +set policy prefix-list AS200-origin-v4 rule 20 action 'permit' +set policy prefix-list AS200-origin-v4 rule 20 prefix '172.16.0.0/12' +set policy prefix-list6 AS100-origin-v6 rule 10 action 'permit' +set policy prefix-list6 AS100-origin-v6 rule 10 prefix '2001:db8:200::/40' +set policy prefix-list6 AS200-origin-v6 rule 10 action 'permit' +set policy prefix-list6 AS200-origin-v6 rule 10 prefix '2001:db8:100::/40' +set protocols static route 100.64.50.0/23 next-hop 100.64.51.221 +set protocols static route 192.0.2.255/32 interface pppoe7 +set protocols static route6 2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128 interface pppoe7 +set qos interface pppoe7 egress 'isp-out' +set qos policy shaper isp-out bandwidth '38mbit' +set qos policy shaper isp-out default bandwidth '100%' +set qos policy shaper isp-out default burst '15k' +set qos policy shaper isp-out default queue-limit '1000' +set qos policy shaper isp-out default queue-type 'fq-codel' +set service router-advert interface br50 prefix ::/64 preferred-lifetime '2700' +set service router-advert interface br50 prefix ::/64 valid-lifetime '5400' +set service router-advert interface eth0.500 default-preference 'high' +set service router-advert interface eth0.500 name-server '2001:db8:200::1' +set service router-advert interface eth0.500 name-server '2001:db8:200::2' +set service router-advert interface eth0.500 prefix 2001:db8:200:50::/64 valid-lifetime 'infinity' +set service router-advert interface eth0.520 default-preference 'high' +set service router-advert interface eth0.520 name-server '2001:db8:200::1' +set service router-advert interface eth0.520 name-server '2001:db8:200::2' +set service router-advert interface eth0.520 prefix 2001:db8:200:520::/64 valid-lifetime 'infinity' +set service ssh disable-host-validation +set service ssh dynamic-protection allow-from '100.64.0.0/10' +set service ssh dynamic-protection allow-from '2001:db8:200::/40' +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system domain-name 'vyos.net' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system name-server '192.168.0.1' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set system time-zone 'Europe/Berlin' +set vrf bind-to-all +set vrf name vyos-test-01 protocols bgp address-family ipv4-unicast network 100.64.50.0/23 +set vrf name vyos-test-01 protocols bgp address-family ipv6-unicast network 2001:db8:200:ffff::1/128 +set vrf name vyos-test-01 protocols bgp neighbor 100.64.51.208 peer-group 'AS100v4' +set vrf name vyos-test-01 protocols bgp neighbor 100.64.51.222 address-family ipv4-unicast default-originate +set vrf name vyos-test-01 protocols bgp neighbor 100.64.51.222 address-family ipv4-unicast maximum-prefix '10' +set vrf name vyos-test-01 protocols bgp neighbor 100.64.51.222 address-family ipv4-unicast prefix-list export 'AS100-origin-v4' +set vrf name vyos-test-01 protocols bgp neighbor 100.64.51.222 address-family ipv4-unicast prefix-list import 'AS200-origin-v4' +set vrf name vyos-test-01 protocols bgp neighbor 100.64.51.222 address-family ipv4-unicast soft-reconfiguration inbound +set vrf name vyos-test-01 protocols bgp neighbor 100.64.51.222 capability dynamic +set vrf name vyos-test-01 protocols bgp neighbor 100.64.51.222 remote-as '200' +set vrf name vyos-test-01 protocols bgp neighbor 100.64.51.251 peer-group 'AS100v4' +set vrf name vyos-test-01 protocols bgp neighbor 100.64.51.251 shutdown +set vrf name vyos-test-01 protocols bgp neighbor 100.64.51.254 peer-group 'AS100v4' +set vrf name vyos-test-01 protocols bgp neighbor 100.64.51.254 shutdown +set vrf name vyos-test-01 protocols bgp neighbor 2001:db8:200:ff::101:2 address-family ipv6-unicast maximum-prefix '10' +set vrf name vyos-test-01 protocols bgp neighbor 2001:db8:200:ff::101:2 address-family ipv6-unicast prefix-list export 'AS100-origin-v6' +set vrf name vyos-test-01 protocols bgp neighbor 2001:db8:200:ff::101:2 address-family ipv6-unicast prefix-list import 'AS200-origin-v6' +set vrf name vyos-test-01 protocols bgp neighbor 2001:db8:200:ff::101:2 address-family ipv6-unicast soft-reconfiguration inbound +set vrf name vyos-test-01 protocols bgp neighbor 2001:db8:200:ff::101:2 capability dynamic +set vrf name vyos-test-01 protocols bgp neighbor 2001:db8:200:ff::101:2 remote-as '200' +set vrf name vyos-test-01 protocols bgp neighbor 2001:db8:200:ffff::2 peer-group 'AS100v6' +set vrf name vyos-test-01 protocols bgp neighbor 2001:db8:200:ffff::2 shutdown +set vrf name vyos-test-01 protocols bgp neighbor 2001:db8:200:ffff::a peer-group 'AS100v6' +set vrf name vyos-test-01 protocols bgp peer-group AS100v4 address-family ipv4-unicast nexthop-self +set vrf name vyos-test-01 protocols bgp peer-group AS100v4 capability dynamic +set vrf name vyos-test-01 protocols bgp peer-group AS100v4 remote-as 'internal' +set vrf name vyos-test-01 protocols bgp peer-group AS100v4 update-source 'dum0' +set vrf name vyos-test-01 protocols bgp peer-group AS100v6 address-family ipv6-unicast nexthop-self +set vrf name vyos-test-01 protocols bgp peer-group AS100v6 capability dynamic +set vrf name vyos-test-01 protocols bgp peer-group AS100v6 remote-as 'internal' +set vrf name vyos-test-01 protocols bgp peer-group AS100v6 update-source 'dum0' +set vrf name vyos-test-01 protocols bgp system-as '100' +set vrf name vyos-test-01 protocols static route 100.64.50.0/23 blackhole +set vrf name vyos-test-01 protocols static route 100.64.51.32/27 next-hop 100.64.51.5 +set vrf name vyos-test-01 protocols static route 192.168.0.0/24 next-hop 100.64.51.220 +set vrf name vyos-test-01 protocols static route6 2001:db8:2fe:ffff::/64 next-hop 2001:db8:200:102::5 +set vrf name vyos-test-01 table '1000' diff --git a/smoketest/config-tests/vrf-ospf b/smoketest/config-tests/vrf-ospf new file mode 100644 index 0000000..fd14615 --- /dev/null +++ b/smoketest/config-tests/vrf-ospf @@ -0,0 +1,59 @@ +set interfaces ethernet eth0 address '192.0.2.1/24' +set interfaces ethernet eth0 offload gro +set interfaces ethernet eth1 offload gro +set interfaces ethernet eth1 vrf 'red' +set interfaces ethernet eth2 offload gro +set interfaces ethernet eth2 vrf 'blue' +set protocols ospf area 0 network '192.0.2.0/24' +set protocols ospf interface eth0 authentication md5 key-id 10 md5-key 'ospfkey' +set protocols ospf interface eth0 passive disable +set protocols ospf log-adjacency-changes +set protocols ospf parameters abr-type 'cisco' +set protocols ospf parameters router-id '1.2.3.4' +set protocols ospf passive-interface 'default' +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server 0.pool.ntp.org +set service ntp server 1.pool.ntp.org +set service ntp server 2.pool.ntp.org +set system config-management commit-revisions '100' +set system conntrack modules ftp +set system conntrack modules h323 +set system conntrack modules nfs +set system conntrack modules pptp +set system conntrack modules sip +set system conntrack modules sqlnet +set system conntrack modules tftp +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set system time-zone 'Europe/Berlin' +set vrf name blue protocols ospf area 0 network '172.18.201.0/24' +set vrf name blue protocols ospf interface eth2 authentication md5 key-id 30 md5-key 'vyoskey456' +set vrf name blue protocols ospf interface eth2 dead-interval '40' +set vrf name blue protocols ospf interface eth2 hello-interval '10' +set vrf name blue protocols ospf interface eth2 passive disable +set vrf name blue protocols ospf interface eth2 priority '1' +set vrf name blue protocols ospf interface eth2 retransmit-interval '5' +set vrf name blue protocols ospf interface eth2 transmit-delay '1' +set vrf name blue protocols ospf log-adjacency-changes +set vrf name blue protocols ospf parameters abr-type 'cisco' +set vrf name blue protocols ospf parameters router-id '5.6.7.8' +set vrf name blue protocols ospf passive-interface 'default' +set vrf name blue table '2000' +set vrf name red protocols ospf area 0 network '172.18.202.0/24' +set vrf name red protocols ospf interface eth1 authentication md5 key-id 20 md5-key 'vyoskey123' +set vrf name red protocols ospf interface eth1 dead-interval '40' +set vrf name red protocols ospf interface eth1 hello-interval '10' +set vrf name red protocols ospf interface eth1 passive disable +set vrf name red protocols ospf interface eth1 priority '1' +set vrf name red protocols ospf interface eth1 retransmit-interval '5' +set vrf name red protocols ospf interface eth1 transmit-delay '1' +set vrf name red protocols ospf log-adjacency-changes +set vrf name red protocols ospf parameters abr-type 'cisco' +set vrf name red protocols ospf parameters router-id '9.10.11.12' +set vrf name red protocols ospf passive-interface 'default' +set vrf name red table '1000' diff --git a/smoketest/config-tests/wireless-basic b/smoketest/config-tests/wireless-basic new file mode 100644 index 0000000..d9e6c8f --- /dev/null +++ b/smoketest/config-tests/wireless-basic @@ -0,0 +1,25 @@ +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 speed 'auto' +set interfaces wireless wlan0 address '192.168.0.1/24' +set interfaces wireless wlan0 channel '1' +set interfaces wireless wlan0 mode 'n' +set interfaces wireless wlan0 security wpa cipher 'CCMP' +set interfaces wireless wlan0 security wpa mode 'wpa2' +set interfaces wireless wlan0 security wpa passphrase '12345678' +set interfaces wireless wlan0 ssid 'VyOS' +set interfaces wireless wlan0 type 'access-point' +set interfaces wireless wlan1 address '192.168.1.1/24' +set interfaces wireless wlan1 channel '2' +set interfaces wireless wlan1 mode 'n' +set interfaces wireless wlan1 ssid 'VyOS-PUBLIC' +set interfaces wireless wlan1 type 'access-point' +set system config-management commit-revisions '200' +set system console device ttyS0 speed '115200' +set system domain-name 'dev.vyos.net' +set system host-name 'WR1' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set system wireless country-code 'es' diff --git a/smoketest/configs.no-load/bgp-small-as b/smoketest/configs.no-load/bgp-small-as new file mode 100644 index 0000000..6b953a3 --- /dev/null +++ b/smoketest/configs.no-load/bgp-small-as @@ -0,0 +1,687 @@ +firewall { + all-ping enable + broadcast-ping disable + config-trap disable + group { + address-group NET-VYOS-HTTPS-4 { + address 10.0.150.73 + } + ipv6-network-group NET-VYOS-6 { + network 2001:db8:200::/40 + } + network-group NET-VYOS-4 { + network 10.0.150.0/23 + network 192.168.189.0/24 + } + port-group MY-NAS-PORTS { + port 80 + port 5000 + port 5001 + port 6022 + port 9443 + } + } + ipv6-name WAN-TO-VLAN15-6 { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + source { + group { + network-group NET-VYOS-6 + } + } + } + rule 1010 { + action accept + destination { + address 2001:db8:200:15::a + group { + port-group MY-NAS-PORTS + } + } + protocol tcp + } + } + ipv6-receive-redirects disable + ipv6-src-route disable + ip-src-route disable + log-martians enable + name WAN-TO-VLAN15-4 { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + source { + group { + network-group NET-VYOS-4 + } + } + } + rule 1000 { + action accept + destination { + group { + address-group NET-VYOS-HTTPS-4 + } + port 80,443 + } + protocol tcp + } + rule 1010 { + action accept + destination { + address 10.0.150.74 + group { + port-group MY-NAS-PORTS + } + } + protocol tcp + } + } + receive-redirects disable + send-redirects enable + source-validation disable + syn-cookies enable + twa-hazards-protection disable +} +high-availability { + vrrp { + group VLAN5-IPv4 { + interface eth0.5 + preempt-delay 180 + priority 250 + virtual-address 10.0.150.120/28 + vrid 5 + } + group VLAN5-IPv6 { + interface eth0.5 + preempt-delay 180 + priority 250 + virtual-address 2001:db8:200:f0::ffff/64 + vrid 6 + } + group VLAN10-IPv4 { + interface eth0.10 + preempt-delay 180 + priority 250 + virtual-address 10.0.150.62/26 + vrid 10 + } + group VLAN10-IPv6 { + interface eth0.10 + preempt-delay 180 + priority 250 + virtual-address 2001:db8:200:10::ffff/64 + virtual-address 2001:db8:200::ffff/64 + vrid 11 + } + group VLAN15-IPv4 { + interface eth0.15 + preempt-delay 180 + priority 250 + virtual-address 10.0.150.78/28 + vrid 15 + } + group VLAN15-IPv6 { + interface eth0.15 + preempt-delay 180 + priority 250 + virtual-address 2001:db8:200:15::ffff/64 + vrid 16 + } + group VLAN500-IPv4 { + interface eth0.500 + preempt-delay 180 + priority 250 + virtual-address 10.0.151.238/28 + vrid 238 + } + group VLAN500-IPv6 { + interface eth0.500 + preempt-delay 180 + priority 250 + virtual-address 2001:db8:200:50::ffff/64 + vrid 239 + } + group VLAN520-IPv4 { + interface eth0.520 + preempt-delay 180 + priority 250 + virtual-address 10.0.150.190/28 + vrid 52 + } + group VLAN520-IPv6 { + interface eth0.520 + preempt-delay 180 + priority 250 + virtual-address 2001:db8:200:520::ffff/64 + vrid 53 + } + group VLAN810-IPv4 { + interface eth0.810 + preempt-delay 180 + priority 250 + virtual-address 10.0.151.30/27 + vrid 80 + } + group VLAN810-IPv6 { + interface eth0.810 + preempt-delay 180 + priority 250 + virtual-address 2001:db8:200:102::ffff/64 + vrid 81 + } + sync-group VYOS { + member VLAN5-IPv4 + member VLAN5-IPv6 + member VLAN10-IPv4 + member VLAN10-IPv6 + member VLAN500-IPv4 + member VLAN500-IPv6 + member VLAN15-IPv4 + member VLAN15-IPv6 + member VLAN810-IPv6 + member VLAN810-IPv4 + member VLAN520-IPv4 + member VLAN520-IPv6 + } + } +} +interfaces { + dummy dum0 { + address 2001:db8:200:ffff::2/128 + address 10.0.151.251/32 + } + ethernet eth0 { + vif 5 { + address 10.0.150.121/28 + address 2001:db8:200:f0::4/64 + ip { + ospf { + authentication { + md5 { + key-id 10 { + md5-key vyosospfkey + } + } + } + cost 10 + dead-interval 40 + hello-interval 10 + network broadcast + priority 200 + retransmit-interval 5 + transmit-delay 5 + } + } + } + vif 10 { + address 2001:db8:200:10::1:ffff/64 + address 2001:db8:200::1:ffff/64 + address 10.0.150.60/26 + } + vif 15 { + address 10.0.150.76/28 + address 2001:db8:200:15::1:ffff/64 + firewall { + out { + ipv6-name WAN-TO-VLAN15-6 + name WAN-TO-VLAN15-4 + } + } + } + vif 50 { + address 192.168.189.2/24 + } + vif 110 { + address 2001:db8:200:101::ffff/64 + address 10.0.151.190/27 + address 10.0.151.158/28 + } + vif 410 { + address 10.0.151.206/28 + address 2001:db8:200:104::ffff/64 + } + vif 450 { + address 2001:db8:200:103::ffff/64 + address 10.0.151.142/29 + disable + } + vif 500 { + address 10.0.151.236/28 + address 2001:db8:200:50::1:ffff/64 + } + vif 520 { + address 10.0.150.188/26 + address 2001:db8:200:520::1:ffff/64 + } + vif 800 { + address 2001:db8:200:ff::104:1/112 + address 10.0.151.212/31 + } + vif 810 { + address 10.0.151.28/27 + address 2001:db8:200:102::1:ffff/64 + } + } + ethernet eth1 { + } + loopback lo { + } +} +policy { + prefix-list as65000-origin-v4 { + rule 10 { + action permit + prefix 10.0.150.0/23 + } + rule 100 { + action permit + prefix 0.0.0.0/0 + } + } + prefix-list6 as65000-origin-v6 { + rule 10 { + action permit + prefix 2001:db8:200::/40 + } + } + route-map as65010-in { + rule 10 { + action permit + set { + local-preference 30 + } + } + } + route-map as65010-out { + rule 10 { + action permit + set { + as-path-prepend "65000 65000" + } + } + } +} +protocols { + bgp 65000 { + address-family { + ipv4-unicast { + network 10.0.150.0/23 { + } + } + ipv6-unicast { + network 2001:db8:200::/40 { + } + } + } + neighbor 10.0.151.222 { + disable-send-community { + extended + standard + } + address-family { + ipv4-unicast { + default-originate { + } + prefix-list { + export as65000-origin-v4 + } + route-map { + export as65010-out + import as65010-in + } + soft-reconfiguration { + inbound + } + } + } + capability { + dynamic + } + remote-as 65010 + } + neighbor 10.0.151.252 { + peer-group VYOSv4 + } + neighbor 10.0.151.254 { + peer-group VYOSv4 + } + neighbor 2001:db8:200:ffff::3 { + peer-group VYOSv6 + } + neighbor 2001:db8:200:ffff::a { + peer-group VYOSv6 + } + neighbor 2001:db8:200:ff::101:2 { + address-family { + ipv6-unicast { + capability { + dynamic + } + prefix-list { + export as65000-origin-v6 + } + route-map { + import as65010-in + } + soft-reconfiguration { + inbound + } + } + } + remote-as 65010 + } + parameters { + default { + no-ipv4-unicast + } + log-neighbor-changes + router-id 10.0.151.251 + } + peer-group VYOSv4 { + address-family { + ipv4-unicast { + nexthop-self { + } + } + } + capability { + dynamic + } + remote-as 65000 + update-source dum0 + } + peer-group VYOSv6 { + address-family { + ipv6-unicast { + nexthop-self { + } + } + } + capability { + dynamic + } + remote-as 65000 + update-source dum0 + } + timers { + holdtime 30 + keepalive 10 + } + } + ospf { + area 0 { + area-type { + normal + } + authentication md5 + network 10.0.151.251/32 + network 10.0.151.208/31 + network 10.0.150.112/28 + } + parameters { + abr-type cisco + router-id 10.0.151.251 + } + passive-interface default + passive-interface-exclude dum0 + passive-interface-exclude eth0.5 + redistribute { + connected { + metric-type 2 + } + static { + metric-type 2 + } + } + } + ospfv3 { + area 0.0.0.0 { + interface dum0 + interface eth0.5 + } + parameters { + router-id 10.0.151.251 + } + redistribute { + connected { + } + static { + } + } + } + static { + route 10.0.0.0/8 { + MY-NAS { + distance 254 + } + } + route 172.16.0.0/12 { + MY-NAS { + distance 254 + } + } + route 192.168.0.0/16 { + MY-NAS { + distance 254 + } + } + route 193.148.249.144/32 { + next-hop 192.168.189.1 { + } + } + route 10.0.150.0/23 { + MY-NAS { + distance 254 + } + } + route 10.0.151.32/27 { + next-hop 10.0.151.5 { + } + } + route6 2001:db8:2fe:ffff::/64 { + next-hop 2001:db8:200:102::4 { + } + } + route6 2001:db8:2ff::/48 { + next-hop 2001:db8:200:101::1 { + } + } + route6 2001:db8:200::/40 { + MY-NAS { + distance 254 + } + } + } +} +service { + dhcp-server { + shared-network-name NET-VYOS-DHCP-1 { + subnet 10.0.151.224/28 { + default-router 10.0.151.238 + dns-server 10.0.150.2 + dns-server 10.0.150.1 + domain-name vyos.net + failover { + local-address 10.0.151.236 + name NET-VYOS-DHCP-1 + peer-address 10.0.151.237 + status primary + } + lease 1800 + range 0 { + start 10.0.151.225 + stop 10.0.151.237 + } + } + } + shared-network-name NET-VYOS-HOSTING-1 { + subnet 10.0.150.128/26 { + default-router 10.0.150.190 + dns-server 10.0.150.2 + dns-server 10.0.150.1 + domain-name vyos.net + failover { + local-address 10.0.150.188 + name NET-VYOS-HOSTING-1 + peer-address 10.0.150.189 + status primary + } + lease 604800 + range 0 { + start 10.0.150.129 + stop 10.0.150.187 + } + } + } + } + lldp { + interface all { + } + management-address 10.0.151.251 + snmp { + enable + } + } + router-advert { + interface eth4.500 { + default-preference high + name-server 2001:db8:200::1 + name-server 2001:db8:200::2 + prefix 2001:db8:200:50::/64 { + valid-lifetime infinity + } + } + interface eth4.520 { + default-preference high + name-server 2001:db8:200::1 + name-server 2001:db8:200::2 + prefix 2001:db8:200:520::/64 { + valid-lifetime infinity + } + } + } + snmp { + community public { + network 10.0.150.0/26 + network 2001:db8:200:10::/64 + } + contact noc@vyos.net + listen-address 10.0.151.251 { + } + listen-address 2001:db8:200:ffff::2 { + } + location "Jenkins" + } + ssh { + disable-host-validation + listen-address 10.0.151.251 + listen-address 2001:db8:200:ffff::2 + listen-address 192.168.189.2 + loglevel fatal + port 22 + } +} +system { + config-management { + commit-revisions 200 + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.net + host-name vyos + login { + banner { + pre-login "VyOS - Network\n" + } + radius { + server 192.0.2.1 { + key SuperS3cretRADIUSkey + timeout 1 + } + server 192.0.2.2 { + key SuperS3cretRADIUSkey + timeout 1 + } + source-address 192.0.2.254 + } + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + name-server 192.0.2.1 + name-server 192.0.2.2 + name-server 2001:db8:200::1 + name-server 2001:db8:200::2 + ntp { + allow-clients { + address 10.0.150.0/23 + address 2001:db8:200::/40 + } + listen-address 10.0.151.251 + listen-address 2001:db8:200:ffff::2 + server 0.de.pool.ntp.org { + } + server 1.de.pool.ntp.org { + } + server 2.de.pool.ntp.org { + } + } + syslog { + global { + facility all { + level notice + } + facility protocols { + level debug + } + } + host 10.0.150.26 { + facility all { + level all + } + } + } + time-zone Europe/Berlin +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@18:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@6:salt@1:snmp@2:ssh@2:sstp@3:system@20:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3-beta-202101151942 diff --git a/smoketest/configs.no-load/firewall-big b/smoketest/configs.no-load/firewall-big new file mode 100644 index 0000000..94b0c6d --- /dev/null +++ b/smoketest/configs.no-load/firewall-big @@ -0,0 +1,43440 @@ +firewall { + all-ping enable + broadcast-ping disable + config-trap disable + group { + address-group CENTREON_SERVERS { + address 109.228.63.82 + } + address-group CLUSTER_ADDRESSES { + address 10.255.255.4 + address 10.255.255.5 + address 77.68.76.16 + address 77.68.77.16 + address 172.16.255.254 + address 77.68.76.14 + address 77.68.77.14 + address 77.68.76.13 + address 77.68.77.13 + address 77.68.76.12 + address 77.68.77.12 + address 77.68.77.67 + address 77.68.77.103 + address 77.68.77.130 + address 77.68.76.245 + address 77.68.77.85 + address 77.68.76.45 + address 77.68.77.144 + address 77.68.77.105 + address 77.68.76.122 + address 77.68.76.104 + address 77.68.77.115 + address 77.68.77.178 + address 77.68.76.239 + address 77.68.76.30 + address 77.68.77.249 + address 77.68.76.59 + address 77.68.77.44 + address 77.68.77.200 + address 77.68.77.228 + address 77.68.76.191 + address 77.68.76.102 + address 77.68.77.26 + address 77.68.76.152 + address 77.68.77.212 + address 77.68.76.142 + address 77.68.76.60 + address 77.68.77.253 + address 77.68.76.54 + address 77.68.76.33 + address 77.68.77.114 + address 77.68.77.176 + address 77.68.77.219 + address 77.68.77.19 + address 77.68.77.22 + address 77.68.77.248 + address 77.68.76.161 + address 77.68.77.56 + address 77.68.77.129 + address 77.68.77.140 + address 77.68.76.177 + address 77.68.77.117 + address 77.68.77.108 + address 77.68.76.50 + address 77.68.76.217 + address 77.68.77.160 + address 77.68.77.30 + address 77.68.77.21 + address 77.68.76.29 + address 77.68.76.158 + address 77.68.76.203 + address 77.68.77.243 + address 77.68.77.54 + address 77.68.76.22 + address 77.68.76.25 + address 77.68.76.21 + address 77.68.77.221 + address 77.68.77.76 + address 77.68.76.127 + address 77.68.77.139 + address 77.68.77.240 + address 77.68.76.39 + address 77.68.76.149 + address 77.68.77.57 + address 77.68.77.185 + address 77.68.76.116 + address 77.68.76.160 + address 77.68.77.70 + address 77.68.77.149 + address 77.68.76.57 + address 77.68.76.115 + address 77.68.76.200 + address 77.68.76.23 + address 77.68.77.46 + address 77.68.76.198 + address 77.68.77.141 + address 77.68.77.50 + address 77.68.77.128 + address 77.68.77.88 + address 77.68.76.80 + address 77.68.76.35 + address 77.68.77.204 + address 77.68.77.201 + address 77.68.77.97 + address 77.68.76.195 + address 77.68.76.202 + address 77.68.76.157 + address 77.68.77.159 + address 77.68.76.118 + address 77.68.76.38 + address 77.68.77.203 + address 77.68.77.233 + address 77.68.77.163 + address 77.68.77.49 + address 77.68.76.58 + address 77.68.77.171 + address 77.68.77.150 + address 77.68.77.199 + address 77.68.76.220 + address 77.68.77.156 + address 77.68.76.248 + address 77.68.76.171 + address 77.68.76.212 + address 77.68.77.132 + address 77.68.77.81 + address 77.68.76.37 + address 77.68.76.197 + address 77.68.76.20 + address 77.68.76.99 + address 77.68.77.211 + address 77.68.77.236 + address 77.68.76.252 + address 77.68.77.32 + address 77.68.77.247 + address 77.68.76.209 + address 77.68.77.202 + address 77.68.76.247 + address 77.68.77.99 + address 77.68.76.169 + address 77.68.76.95 + address 77.68.76.187 + address 77.68.77.222 + address 77.68.77.53 + address 77.68.77.124 + address 77.68.76.61 + address 77.68.77.43 + address 77.68.76.94 + address 77.68.77.165 + address 77.68.77.152 + address 77.68.76.44 + address 77.68.76.47 + address 77.68.76.74 + address 77.68.76.55 + address 77.68.77.75 + address 77.68.77.239 + address 77.68.76.75 + address 77.68.77.71 + address 77.68.76.145 + address 77.68.77.145 + address 77.68.77.68 + address 77.68.76.126 + address 77.68.76.88 + address 77.68.77.181 + address 77.68.76.112 + address 77.68.77.33 + address 77.68.77.137 + address 77.68.77.92 + address 77.68.76.111 + address 77.68.76.185 + address 77.68.76.208 + address 77.68.76.150 + address 77.68.77.208 + address 77.68.76.42 + address 77.68.76.164 + address 77.68.77.207 + address 77.68.76.49 + address 77.68.77.227 + address 77.68.76.136 + address 77.68.76.77 + address 77.68.76.123 + address 77.68.76.31 + address 77.68.76.148 + address 77.68.77.120 + address 77.68.76.183 + address 77.68.77.107 + address 77.68.76.141 + address 77.68.76.105 + address 77.68.76.251 + address 77.68.76.249 + address 77.68.77.59 + address 77.68.77.37 + address 77.68.77.65 + address 77.68.76.231 + address 77.68.77.24 + address 77.68.77.63 + address 77.68.76.234 + address 77.68.76.93 + address 77.68.77.77 + address 77.68.77.151 + address 77.68.76.235 + address 77.68.77.95 + address 77.68.77.190 + address 77.68.76.91 + address 77.68.77.79 + address 77.68.77.100 + address 77.68.76.241 + address 77.68.77.209 + address 77.68.76.110 + address 77.68.76.40 + address 77.68.76.76 + address 77.68.76.124 + address 77.68.77.234 + address 77.68.76.219 + address 77.68.77.90 + address 77.68.76.107 + address 77.68.76.26 + address 77.68.76.211 + address 77.68.76.19 + address 77.68.77.231 + address 77.68.76.254 + address 77.68.77.251 + address 77.68.77.74 + address 77.68.77.192 + address 77.68.76.253 + address 77.68.77.214 + address 77.68.76.92 + address 77.68.76.250 + address 77.68.77.215 + address 77.68.76.165 + address 77.68.77.254 + address 77.68.76.120 + address 77.68.76.228 + address 77.68.77.157 + address 77.68.77.205 + address 77.68.76.138 + address 77.68.77.102 + address 77.68.76.181 + address 77.68.76.139 + address 77.68.76.243 + address 77.68.76.244 + address 77.68.76.114 + address 77.68.77.72 + address 77.68.77.161 + address 77.68.77.38 + address 77.68.77.62 + address 77.68.92.186 + address 77.68.91.195 + address 77.68.23.35 + address 77.68.84.155 + address 77.68.17.26 + address 77.68.76.96 + address 77.68.28.145 + address 77.68.76.48 + address 109.228.56.185 + address 77.68.84.147 + address 77.68.23.64 + address 77.68.26.166 + address 77.68.29.178 + address 77.68.12.195 + address 77.68.21.78 + address 77.68.5.166 + address 77.68.5.187 + address 77.68.4.111 + address 77.68.4.22 + address 77.68.7.227 + address 77.68.4.24 + address 77.68.4.74 + address 77.68.6.202 + address 77.68.5.241 + address 77.68.7.222 + address 77.68.4.39 + address 77.68.4.25 + address 77.68.7.160 + address 77.68.27.211 + address 77.68.89.183 + address 77.68.24.59 + address 77.68.7.114 + address 77.68.75.113 + address 77.68.81.44 + address 77.68.90.106 + address 77.68.94.181 + address 77.68.30.164 + address 77.68.30.133 + address 77.68.7.67 + address 77.68.77.174 + address 77.68.27.54 + address 77.68.4.136 + address 77.68.72.202 + address 77.68.112.83 + address 77.68.85.172 + address 77.68.23.158 + address 77.68.112.75 + address 77.68.24.112 + address 77.68.112.213 + address 77.68.72.254 + address 77.68.20.161 + address 77.68.26.216 + address 77.68.112.184 + address 77.68.79.82 + address 77.68.27.57 + address 77.68.20.231 + address 77.68.118.17 + address 77.68.118.120 + address 77.68.117.51 + address 77.68.118.102 + address 77.68.116.119 + address 77.68.117.45 + address 77.68.116.220 + address 77.68.116.232 + address 77.68.117.222 + address 77.68.118.15 + address 77.68.116.221 + address 77.68.116.183 + address 77.68.119.14 + address 77.68.112.91 + address 77.68.117.202 + address 77.68.118.104 + address 77.68.7.172 + address 77.68.83.41 + address 77.68.15.95 + address 77.68.4.57 + address 77.68.85.27 + address 77.68.86.40 + address 77.68.88.164 + address 109.228.56.26 + address 77.68.7.123 + address 77.68.112.248 + address 109.228.60.215 + address 109.228.55.82 + address 77.68.7.186 + address 77.68.6.210 + address 77.68.77.238 + address 77.68.10.142 + address 77.68.31.144 + address 77.68.93.246 + address 77.68.121.127 + address 77.68.121.94 + address 77.68.120.241 + address 77.68.121.106 + address 77.68.122.195 + address 77.68.122.89 + address 77.68.120.146 + address 77.68.120.249 + address 77.68.122.241 + address 77.68.119.92 + address 77.68.120.26 + address 77.68.81.141 + address 77.68.79.206 + address 77.68.116.52 + address 77.68.88.100 + address 77.68.6.105 + address 77.68.78.229 + address 77.68.6.32 + address 77.68.10.170 + address 77.68.76.229 + address 77.68.95.42 + address 77.68.28.207 + address 77.68.17.186 + address 77.68.4.252 + address 77.68.24.220 + address 77.68.2.215 + address 77.68.91.128 + address 77.68.22.146 + address 77.68.23.112 + address 77.68.75.245 + address 77.68.125.218 + address 77.68.125.32 + address 77.68.12.250 + address 109.228.37.174 + address 77.68.127.151 + address 109.228.37.114 + address 109.228.36.229 + address 109.228.37.240 + address 109.228.61.31 + address 109.228.35.110 + address 109.228.39.157 + address 109.228.39.249 + address 109.228.38.171 + address 109.228.40.226 + address 109.228.40.207 + address 109.228.40.247 + address 77.68.126.51 + address 77.68.117.214 + address 77.68.113.117 + address 77.68.117.142 + address 77.68.17.200 + address 77.68.4.242 + address 77.68.86.148 + address 109.228.39.151 + address 109.228.40.194 + address 77.68.114.183 + address 77.68.90.132 + address 77.68.16.247 + address 77.68.6.110 + address 109.228.36.37 + address 77.68.127.172 + address 77.68.14.88 + address 77.68.120.229 + address 213.171.212.203 + address 213.171.213.41 + address 213.171.213.175 + address 213.171.213.97 + address 213.171.212.171 + address 213.171.212.89 + address 213.171.214.96 + address 213.171.212.172 + address 213.171.215.252 + address 213.171.213.242 + address 213.171.213.31 + address 213.171.212.71 + address 213.171.208.58 + address 77.68.25.130 + address 213.171.215.184 + address 77.68.13.76 + address 109.228.56.242 + address 77.68.25.146 + address 109.228.46.81 + address 77.68.77.69 + address 213.171.210.19 + address 77.68.120.45 + address 77.68.116.36 + address 213.171.211.128 + address 77.68.25.124 + address 109.228.48.249 + address 213.171.210.59 + address 213.171.215.43 + address 109.228.40.195 + address 109.228.52.186 + address 77.68.113.164 + address 77.68.114.93 + address 77.68.75.253 + address 109.228.53.243 + address 109.228.36.194 + address 77.68.28.147 + address 77.68.123.250 + address 185.132.36.24 + address 185.132.39.129 + address 185.132.36.142 + address 185.132.39.68 + address 185.132.36.17 + address 185.132.36.148 + address 185.132.37.101 + address 185.132.39.44 + address 185.132.39.37 + address 185.132.37.102 + address 185.132.38.142 + address 185.132.38.114 + address 185.132.38.95 + address 185.132.37.83 + address 185.132.36.7 + address 109.228.40.222 + address 77.68.119.188 + address 77.68.74.85 + address 77.68.91.22 + address 213.171.212.136 + address 185.132.38.216 + address 77.68.120.31 + address 77.68.95.212 + address 109.228.42.232 + address 77.68.13.137 + address 77.68.85.73 + address 77.68.85.115 + address 109.228.36.174 + address 77.68.9.186 + address 77.68.27.18 + address 77.68.27.27 + address 77.68.27.28 + address 77.68.3.80 + address 77.68.3.121 + address 77.68.3.144 + address 77.68.3.161 + address 77.68.3.194 + address 77.68.3.247 + address 77.68.28.139 + address 77.68.81.218 + address 77.68.93.125 + address 77.68.74.39 + address 77.68.78.73 + address 77.68.5.95 + address 77.68.74.152 + address 77.68.87.212 + address 77.68.3.52 + address 77.68.114.136 + address 77.68.125.60 + address 213.171.214.167 + address 77.68.114.234 + address 213.171.213.42 + address 109.228.59.247 + address 185.132.39.99 + address 185.132.39.145 + address 109.228.35.84 + address 185.132.36.60 + address 185.132.40.11 + address 185.132.39.219 + address 77.68.26.221 + address 185.132.40.56 + address 77.68.117.29 + address 185.132.40.90 + address 109.228.38.201 + address 185.132.40.244 + address 77.68.11.140 + address 213.171.210.155 + address 185.132.37.23 + address 213.171.214.234 + address 77.68.77.29 + address 77.68.20.217 + address 185.132.40.152 + address 77.68.9.75 + address 213.171.210.177 + address 185.132.41.72 + address 185.132.41.73 + address 77.68.5.155 + address 185.132.43.6 + address 77.68.75.45 + address 109.228.46.196 + address 185.132.43.28 + address 77.68.89.72 + address 185.132.43.98 + address 77.68.76.176 + address 185.132.43.164 + address 185.132.43.157 + address 77.68.6.119 + address 77.68.92.92 + address 77.68.10.152 + address 77.68.73.73 + address 77.68.32.43 + address 185.132.38.248 + address 77.68.120.218 + address 77.68.32.31 + address 77.68.32.254 + address 77.68.32.118 + address 77.68.82.157 + address 77.68.121.119 + address 77.68.74.209 + address 77.68.33.68 + address 77.68.24.172 + address 77.68.33.197 + address 77.68.33.48 + address 77.68.34.26 + address 77.68.34.28 + address 77.68.79.89 + address 77.68.76.137 + address 77.68.33.216 + address 77.68.32.83 + address 77.68.32.86 + address 77.68.32.89 + address 77.68.34.138 + address 77.68.34.139 + address 77.68.123.177 + address 77.68.35.116 + address 77.68.33.171 + address 213.171.208.40 + address 77.68.118.86 + address 77.68.48.81 + address 77.68.48.89 + address 77.68.48.105 + address 77.68.85.18 + address 77.68.26.228 + address 77.68.49.4 + address 77.68.80.26 + address 77.68.80.97 + address 77.68.126.101 + address 77.68.126.14 + address 77.68.49.12 + address 77.68.117.173 + address 77.68.8.144 + address 77.68.82.147 + address 77.68.24.134 + address 77.68.112.167 + address 77.68.49.126 + address 77.68.49.178 + address 77.68.50.91 + address 77.68.50.90 + address 77.68.24.63 + address 109.228.37.187 + address 77.68.50.193 + address 77.68.50.198 + address 77.68.50.142 + address 77.68.114.237 + address 77.68.115.17 + address 77.68.49.159 + address 77.68.49.160 + address 213.171.208.176 + address 77.68.116.84 + address 77.68.126.160 + address 185.132.36.56 + address 77.68.49.161 + address 77.68.34.50 + address 185.132.41.240 + address 77.68.51.214 + address 77.68.51.202 + address 185.132.37.133 + address 77.68.77.42 + address 77.68.100.132 + address 77.68.100.134 + address 77.68.100.150 + address 185.132.41.148 + address 77.68.101.64 + address 213.171.210.25 + address 77.68.101.124 + address 77.68.101.125 + address 77.68.89.247 + address 185.132.39.109 + address 77.68.100.167 + address 77.68.5.125 + address 77.68.4.80 + address 77.68.49.152 + address 77.68.12.45 + address 77.68.4.180 + address 213.171.214.102 + address 77.68.126.22 + address 77.68.114.205 + address 109.228.36.119 + address 213.171.212.90 + address 77.68.33.37 + address 185.132.43.71 + address 185.132.43.113 + address 77.68.48.202 + address 185.132.40.166 + address 77.68.112.90 + address 77.68.112.175 + address 77.68.103.19 + address 77.68.103.120 + address 77.68.33.24 + address 77.68.103.147 + address 109.228.47.223 + address 109.228.58.134 + address 109.228.56.97 + address 77.68.31.96 + address 77.68.103.227 + address 88.208.196.91 + address 88.208.196.92 + address 88.208.196.154 + address 88.208.197.10 + address 77.68.87.164 + address 77.68.93.164 + address 185.132.37.47 + address 77.68.75.64 + address 88.208.197.118 + address 88.208.197.135 + address 88.208.197.150 + address 88.208.197.155 + address 88.208.197.160 + address 88.208.197.60 + address 109.228.37.10 + address 88.208.215.61 + address 77.68.102.129 + address 88.208.196.123 + address 109.228.36.79 + address 185.132.38.182 + address 88.208.215.62 + address 88.208.215.157 + address 88.208.198.251 + address 88.208.215.19 + address 88.208.198.39 + address 109.228.38.117 + address 77.68.29.65 + address 88.208.215.121 + address 77.68.115.142 + address 77.68.76.108 + address 88.208.198.64 + address 88.208.198.66 + address 77.68.3.61 + address 88.208.198.92 + address 77.68.74.232 + address 77.68.118.88 + address 77.68.100.77 + address 77.68.48.14 + address 88.208.198.69 + address 88.208.197.23 + address 88.208.199.249 + address 213.171.212.114 + address 109.228.39.41 + address 88.208.199.141 + address 77.68.21.171 + address 88.208.199.233 + address 88.208.212.31 + address 77.68.102.5 + address 88.208.212.94 + address 109.228.61.37 + address 88.208.199.46 + address 77.68.78.113 + address 88.208.212.182 + address 88.208.212.188 + address 185.132.40.124 + address 213.171.209.217 + address 77.68.103.56 + address 88.208.197.208 + address 88.208.197.129 + } + address-group CMK_SATELLITES { + address 82.223.144.252 + address 109.228.63.67 + address 109.228.63.66 + address 82.223.200.61 + address 195.20.253.14 + address 217.72.206.27 + } + address-group DHCP_SERVERS { + address 10.255.241.13 + address 10.255.241.14 + address 10.255.242.13 + address 10.255.242.14 + address 10.255.243.13 + address 10.255.243.14 + address 10.255.244.13 + address 10.255.244.14 + address 10.255.245.13 + address 10.255.245.14 + address 10.255.246.13 + address 10.255.246.14 + address 10.255.247.13 + address 10.255.247.14 + address 10.255.248.13 + address 10.255.248.14 + address 10.255.249.13 + address 10.255.249.14 + address 77.68.76.14 + address 77.68.77.14 + address 77.68.76.13 + address 77.68.77.13 + } + address-group DNSCACHE_SERVERS { + address 10.255.255.4 + address 10.255.255.5 + address 77.68.76.12 + address 77.68.77.12 + } + address-group DT_BLOCKED { + address 172.16.255.254 + } + address-group DT_FW0A5C4_1 { + address 185.132.40.56 + } + address-group DT_FW0B352_1 { + address 77.68.77.238 + } + address-group DT_FW0BB22_1 { + address 77.68.16.247 + } + address-group DT_FW0BD92_3 { + address 109.228.36.79 + } + address-group DT_FW0C2E6_4 { + address 77.68.76.110 + } + address-group DT_FW0C8E1_1 { + address 77.68.77.103 + } + address-group DT_FW0C25B_1 { + address 77.68.86.148 + } + address-group DT_FW00D98_1 { + address 77.68.76.88 + } + address-group DT_FW0E2EE_1 { + address 213.171.211.128 + } + address-group DT_FW0E383_9 { + address 77.68.77.114 + } + address-group DT_FW0EA3F_1 { + address 77.68.49.159 + } + address-group DT_FW1ACD9_2 { + address 77.68.76.108 + } + address-group DT_FW1C8F2_1 { + address 185.132.37.83 + } + address-group DT_FW1CB16_1 { + address 77.68.29.178 + } + address-group DT_FW1CC15_2 { + address 77.68.77.248 + } + address-group DT_FW1D511_2 { + address 213.171.213.175 + } + address-group DT_FW1F3D0_6 { + address 77.68.76.250 + } + address-group DT_FW1F126_1 { + address 77.68.76.137 + } + address-group DT_FW1FA8E_1 { + address 185.132.37.101 + } + address-group DT_FW1FA9E_1 { + address 77.68.118.104 + } + address-group DT_FW2ACFF_1 { + address 77.68.24.220 + } + address-group DT_FW2B4BA_1 { + address 77.68.33.68 + } + address-group DT_FW2B279_4 { + address 77.68.77.204 + } + address-group DT_FW2BB8D_1 { + address 77.68.77.181 + } + address-group DT_FW2BF20_3 { + address 77.68.76.187 + } + address-group DT_FW2C5AE_1 { + address 77.68.76.228 + } + address-group DT_FW2E8D4_1 { + address 77.68.77.249 + } + address-group DT_FW2E060_1 { + address 77.68.77.215 + } + address-group DT_FW2ED4D_2 { + address 109.228.39.151 + } + address-group DT_FW2EF2C_1 { + address 77.68.11.140 + } + address-group DT_FW2F868_6 { + address 77.68.76.254 + } + address-group DT_FW2FB61_1 { + address 109.228.38.117 + } + address-group DT_FW3A12F_1 { + address 77.68.5.95 + } + address-group DT_FW3AD6F_1 { + address 77.68.120.241 + } + address-group DT_FW03B35_1 { + address 77.68.125.60 + } + address-group DT_FW3B068_2 { + address 77.68.77.63 + } + address-group DT_FW3CAAB_1 { + address 77.68.76.234 + } + address-group DT_FW3DBF8_9 { + address 77.68.76.198 + } + address-group DT_FW3EBC8_1 { + address 77.68.13.76 + } + address-group DT_FW03F2E_1 { + address 77.68.102.5 + } + address-group DT_FW3F465_1 { + address 109.228.36.119 + } + address-group DT_FW4AE7D_1 { + address 77.68.76.60 + } + address-group DT_FW4C136_1 { + address 77.68.76.50 + } + address-group DT_FW4D3E6_1 { + address 77.68.100.77 + } + address-group DT_FW4DB0A_1 { + address 77.68.49.161 + } + address-group DT_FW4E314_1 { + address 109.228.40.222 + } + address-group DT_FW4E399_1 { + address 213.171.214.96 + } + address-group DT_FW4F5EE_10 { + address 77.68.116.220 + } + address-group DT_FW4F81F_4 { + address 77.68.77.43 + } + address-group DT_FW5A5D7_3 { + address 77.68.77.205 + } + address-group DT_FW5A77C_16 { + address 77.68.76.202 + } + address-group DT_FW5A521_3 { + address 77.68.79.89 + } + address-group DT_FW05AD0_2 { + address 77.68.77.72 + } + address-group DT_FW5AE10_1 { + address 109.228.37.114 + } + address-group DT_FW5CBB2_1 { + address 77.68.77.150 + } + address-group DT_FW5D0FA_1 { + address 185.132.43.157 + } + address-group DT_FW6A684_1 { + address 77.68.116.119 + } + address-group DT_FW6B9B9_1 { + address 185.132.41.72 + } + address-group DT_FW6B39D_1 { + address 77.68.4.111 + address 77.68.77.174 + } + address-group DT_FW6C992_1 { + address 77.68.85.27 + } + address-group DT_FW6CD7E_2 { + address 77.68.76.148 + } + address-group DT_FW6D0CD_1 { + address 77.68.76.241 + } + address-group DT_FW6ECA4_1 { + address 77.68.117.51 + } + address-group DT_FW6EFD7_1 { + address 77.68.84.147 + } + address-group DT_FW6F539_1 { + address 77.68.76.217 + } + address-group DT_FW7A9B0_9 { + address 77.68.76.47 + } + address-group DT_FW7C4D9_14 { + address 109.228.36.37 + } + address-group DT_FW7DAE2_3 { + address 185.132.38.216 + } + address-group DT_FW7F28A_1 { + address 77.68.76.31 + } + address-group DT_FW8A3FC_3 { + address 77.68.77.132 + address 77.68.76.185 + address 77.68.77.90 + } + address-group DT_FW8A49A_1 { + address 77.68.77.85 + } + address-group DT_FW8A57A_1 { + address 77.68.77.222 + address 77.68.112.83 + } + address-group DT_FW8AFF1_7 { + address 77.68.76.118 + } + address-group DT_FW8B21D_1 { + address 77.68.23.64 + } + address-group DT_FW8C72E_1 { + address 77.68.27.54 + } + address-group DT_FW8C927_1 { + address 77.68.7.160 + } + address-group DT_FW8EA04_1 { + address 77.68.20.161 + } + address-group DT_FW8ECF4_1 { + address 77.68.2.215 + } + address-group DT_FW9B6FB_1 { + address 77.68.4.242 + } + address-group DT_FW9C682_3 { + address 213.171.212.203 + } + address-group DT_FW9D5C7_1 { + address 77.68.115.17 + } + address-group DT_FW9E550_1 { + address 213.171.212.71 + } + address-group DT_FW9EEDD_1 { + address 77.68.4.80 + address 77.68.49.152 + } + address-group DT_FW10C3D_19 { + address 77.68.25.124 + } + address-group DT_FW10FEE_1 { + address 77.68.122.89 + } + address-group DT_FW12C32_1 { + address 77.68.4.25 + address 77.68.7.114 + } + address-group DT_FW013EF_2 { + address 77.68.77.26 + } + address-group DT_FW15C99_6 { + address 77.68.114.237 + } + address-group DT_FW18E6E_3 { + address 77.68.76.112 + } + address-group DT_FW21A75_2 { + address 88.208.198.66 + } + address-group DT_FW24AB7_1 { + address 213.171.213.242 + } + address-group DT_FW26F0A_1 { + address 77.68.78.73 + } + address-group DT_FW27A8F_1 { + address 77.68.76.219 + } + address-group DT_FW028C0_2 { + address 77.68.26.221 + } + address-group DT_FW28EC8_1 { + address 77.68.76.93 + } + address-group DT_FW30D21_1 { + address 77.68.95.42 + } + address-group DT_FW32EFF_16 { + address 77.68.118.120 + } + address-group DT_FW32EFF_25 { + address 77.68.27.211 + } + address-group DT_FW32EFF_49 { + address 109.228.37.187 + } + address-group DT_FW34C91_3 { + address 77.68.76.142 + } + address-group DT_FW35F7B_1 { + address 77.68.30.164 + } + address-group DT_FW37E59_5 { + address 77.68.76.37 + } + address-group DT_FW40AE4_1 { + address 77.68.79.206 + } + address-group DT_FW42BC7_1 { + address 77.68.76.95 + } + address-group DT_FW44BF9_1 { + address 77.68.77.200 + } + address-group DT_FW45BEB_1 { + address 77.68.75.245 + } + address-group DT_FW45F3D_1 { + address 109.228.40.247 + } + address-group DT_FW45F87_1 { + address 77.68.77.207 + } + address-group DT_FW46F4A_1 { + address 88.208.197.135 + } + address-group DT_FW48A55_2 { + address 109.228.39.157 + } + address-group DT_FW49C3D_4 { + address 77.68.76.149 + } + address-group DT_FW49C3D_6 { + address 77.68.76.160 + } + address-group DT_FW050AC_1 { + address 77.68.77.214 + } + address-group DT_FW52F6F_1 { + address 77.68.82.147 + } + address-group DT_FW53C72_1 { + address 88.208.197.118 + } + address-group DT_FW58C69_4 { + address 77.68.76.141 + } + address-group DT_FW59F39_1 { + address 77.68.87.212 + } + address-group DT_FW60FD6_5 { + address 77.68.92.92 + } + address-group DT_FW69D6D_2 { + address 77.68.77.221 + } + address-group DT_FW72F37_1 { + address 77.68.77.100 + } + address-group DT_FW73A64_1 { + address 77.68.118.15 + } + address-group DT_FW75CA4_6 { + address 77.68.4.22 + } + address-group DT_FW85A7C_1 { + address 77.68.6.210 + } + address-group DT_FW85E02_11 { + address 77.68.77.233 + } + address-group DT_FW90AE3_1 { + address 77.68.88.100 + } + address-group DT_FW91B7A_1 { + address 77.68.76.40 + } + address-group DT_FW138F8_1 { + address 77.68.50.193 + } + address-group DT_FW0192C_1 { + address 185.132.39.68 + } + address-group DT_FW197DB_1 { + address 77.68.77.240 + } + address-group DT_FW210E2_8 { + address 77.68.94.181 + } + address-group DT_FW274FD_1 { + address 185.132.36.24 + } + address-group DT_FW310C6_3 { + address 88.208.198.39 + } + address-group DT_FW364CF_1 { + address 77.68.76.203 + address 77.68.77.97 + } + address-group DT_FW406AB_1 { + address 109.228.47.223 + } + address-group DT_FW444AF_1 { + address 185.132.37.102 + } + address-group DT_FW481D7_1 { + address 77.68.76.243 + } + address-group DT_FW539FB_1 { + address 77.68.21.171 + } + address-group DT_FW578BE_1 { + address 109.228.56.185 + } + address-group DT_FW597A6_1 { + address 77.68.5.125 + address 88.208.196.123 + address 88.208.212.31 + } + address-group DT_FW608FA_1 { + address 77.68.74.232 + } + address-group DT_FW633DD_1 { + address 77.68.121.119 + } + address-group DT_FW672AB_1 { + address 213.171.213.41 + } + address-group DT_FW0745F_5 { + address 77.68.117.222 + } + address-group DT_FW748B7_1 { + address 77.68.120.249 + } + address-group DT_FW825C8_19 { + address 77.68.76.111 + address 77.68.76.42 + } + address-group DT_FW825C8_24 { + address 77.68.77.120 + address 77.68.76.183 + } + address-group DT_FW826BA_3 { + address 77.68.77.152 + } + address-group DT_FW856FA_1 { + address 77.68.77.151 + } + address-group DT_FW883EB_1 { + address 77.68.76.152 + } + address-group DT_FW930F3_1 { + address 77.68.85.73 + } + address-group DT_FW930F3_3 { + address 77.68.114.234 + } + address-group DT_FW934AE_1 { + address 77.68.5.166 + } + address-group DT_FW0937A_1 { + address 77.68.6.119 + } + address-group DT_FW0952B_1 { + address 77.68.93.125 + } + address-group DT_FW996B4_2 { + address 77.68.76.157 + } + address-group DT_FW1208C_1 { + address 77.68.77.33 + } + address-group DT_FW1226C_3 { + address 77.68.117.45 + } + address-group DT_FW1271A_2 { + address 77.68.76.102 + } + address-group DT_FW2379F_14 { + address 213.171.212.89 + address 77.68.76.44 + address 77.68.77.239 + address 213.171.212.114 + address 77.68.103.56 + } + address-group DT_FW4293B_1 { + address 77.68.76.57 + } + address-group DT_FW4513E_1 { + address 77.68.77.75 + } + address-group DT_FW4735F_1 { + address 77.68.77.74 + } + address-group DT_FW05064_1 { + address 213.171.210.19 + } + address-group DT_FW05339_1 { + address 185.132.40.152 + } + address-group DT_FW5658C_1 { + address 77.68.77.185 + } + address-group DT_FW5858F_1 { + address 77.68.121.127 + } + address-group DT_FW06176_1 { + address 77.68.77.38 + } + address-group DT_FW6187E_1 { + address 77.68.103.147 + } + address-group DT_FW6863A_4 { + address 77.68.7.222 + } + address-group DT_FW6906B_1 { + address 185.132.43.28 + } + address-group DT_FW06940_3 { + address 77.68.33.216 + address 77.68.33.37 + address 77.68.50.90 + } + address-group DT_FW7648D_1 { + address 77.68.76.77 + } + address-group DT_FW08061_1 { + address 77.68.76.45 + } + address-group DT_FW8428B_1 { + address 77.68.33.24 + } + address-group DT_FW8871B_1 { + address 77.68.78.113 + } + address-group DT_FW11082_1 { + address 77.68.113.117 + } + address-group DT_FW16375_5 { + address 77.68.77.171 + } + address-group DT_FW19987_4 { + address 77.68.77.54 + } + address-group DT_FW20449_2 { + address 77.68.126.101 + } + address-group DT_FW25843_1 { + address 77.68.24.59 + } + address-group DT_FW26846_1 { + address 88.208.197.10 + } + address-group DT_FW27947_1 { + address 77.68.77.102 + } + address-group DT_FW27949_2 { + address 77.68.117.214 + } + address-group DT_FW28892_1 { + address 77.68.77.144 + } + address-group DT_FW31525_6 { + address 77.68.77.46 + } + address-group DT_FW36425_1 { + address 77.68.119.14 + } + address-group DT_FW40416_1 { + address 77.68.121.94 + } + address-group DT_FW42661_3 { + address 77.68.77.202 + } + address-group DT_FW44217_2 { + address 77.68.89.247 + } + address-group DT_FW45000_1 { + address 77.68.24.172 + } + address-group DT_FW48814_3 { + address 77.68.77.219 + } + address-group DT_FW49897_1 { + address 185.132.36.7 + } + address-group DT_FW56335_2 { + address 88.208.198.92 + } + address-group DT_FW56496_1 { + address 77.68.51.202 + address 77.68.101.64 + } + address-group DT_FW62858_12 { + address 77.68.77.145 + } + address-group DT_FW63230_1 { + address 77.68.76.220 + } + address-group DT_FW66347_1 { + address 77.68.92.186 + } + address-group DT_FW73215_1 { + address 213.171.209.217 + } + address-group DT_FW73573_1 { + address 77.68.76.249 + } + address-group DT_FW73573_2 { + address 77.68.77.62 + } + address-group DT_FW78137_1 { + address 77.68.34.50 + } + address-group DT_FW81138_1 { + address 77.68.77.59 + } + address-group DT_FW81286_1 { + address 77.68.77.243 + } + address-group DT_FW85040_1 { + address 77.68.5.187 + } + address-group DT_FW85619_1 { + address 77.68.127.172 + } + address-group DT_FW89619_1 { + address 77.68.76.253 + } + address-group DT_FW98818_1 { + address 88.208.197.129 + } + address-group DT_FWA0AA0_1 { + address 77.68.113.164 + } + address-group DT_FWA0B7F_1 { + address 185.132.39.44 + } + address-group DT_FWA2FF8_4 { + address 77.68.76.231 + } + address-group DT_FWA3EA3_1 { + address 77.68.77.42 + } + address-group DT_FWA4BC8_1 { + address 77.68.112.75 + } + address-group DT_FWA5D67_1 { + address 185.132.37.133 + } + address-group DT_FWA7A50_1 { + address 77.68.27.57 + address 77.68.118.102 + } + address-group DT_FWA69A0_1 { + address 213.171.212.90 + } + address-group DT_FWA076E_1 { + address 77.68.76.19 + } + address-group DT_FWA83DF_1 { + address 77.68.7.123 + } + address-group DT_FWA86A4_1 { + address 109.228.56.97 + } + address-group DT_FWA86ED_101 { + address 77.68.85.172 + address 109.228.38.171 + address 88.208.199.233 + } + address-group DT_FWA373F_1 { + address 77.68.76.171 + } + address-group DT_FWA0531_1 { + address 213.171.215.252 + } + address-group DT_FWA884B_5 { + address 88.208.199.249 + } + address-group DT_FWA7625_1 { + address 213.171.215.43 + } + address-group DT_FWAA38E_1 { + address 77.68.93.164 + } + address-group DT_FWAB44B_1 { + address 185.132.37.47 + } + address-group DT_FWAE88B_1 { + address 77.68.125.218 + } + address-group DT_FWAF6E8_1 { + address 77.68.76.115 + } + address-group DT_FWAFF0A_1 { + address 77.68.91.195 + } + address-group DT_FWB2CD2_1 { + address 77.68.72.254 + } + address-group DT_FWB28B6_5 { + address 77.68.77.209 + } + address-group DT_FWB36A0_1 { + address 77.68.77.108 + } + address-group DT_FWB118A_1 { + address 77.68.48.14 + } + address-group DT_FWB4438_2 { + address 88.208.215.61 + } + address-group DT_FWB6101_1 { + address 88.208.215.62 + } + address-group DT_FWB9699_7 { + address 77.68.76.123 + } + address-group DT_FWB9699_11 { + address 77.68.77.165 + } + address-group DT_FWBB718_1 { + address 77.68.77.71 + } + address-group DT_FWBC8A6_1 { + address 77.68.112.175 + } + address-group DT_FWBC280_1 { + address 77.68.100.167 + } + address-group DT_FWBD9D0_1 { + address 77.68.120.31 + } + address-group DT_FWBE878_1 { + address 213.171.212.172 + } + address-group DT_FWBED52_1 { + address 77.68.112.213 + } + address-group DT_FWBF494_1 { + address 77.68.76.209 + } + address-group DT_FWBFC02_1 { + address 77.68.112.90 + } + address-group DT_FWBFDED_1 { + address 77.68.76.30 + } + address-group DT_FWC0CE0_1 { + address 77.68.112.184 + } + address-group DT_FWC1ACD_1 { + address 77.68.85.18 + } + address-group DT_FWC2D30_1 { + address 77.68.76.48 + } + address-group DT_FWC2EF2_1 { + address 77.68.17.200 + } + address-group DT_FWC2EF2_2 { + address 77.68.17.200 + } + address-group DT_FWC7D36_1 { + address 77.68.76.126 + } + address-group DT_FWC8E8E_1 { + address 77.68.28.207 + } + address-group DT_FWC32BE_1 { + address 77.68.117.173 + } + address-group DT_FWC37B9_1 { + address 77.68.28.139 + } + address-group DT_FWC055A_1 { + address 77.68.77.30 + } + address-group DT_FWC72E5_1 { + address 77.68.103.227 + } + address-group DT_FWC96A1_1 { + address 77.68.75.253 + } + address-group DT_FWC1315_1 { + address 77.68.4.57 + } + address-group DT_FWC3921_1 { + address 77.68.76.164 + } + address-group DT_FWC6301_1 { + address 77.68.34.26 + } + address-group DT_FWCA628_1 { + address 185.132.39.99 + } + address-group DT_FWCB0CF_7 { + address 77.68.77.163 + } + address-group DT_FWCB29D_1 { + address 88.208.197.23 + } + address-group DT_FWCC18F_2 { + address 77.68.76.59 + } + address-group DT_FWCD7CE_1 { + address 77.68.77.56 + } + address-group DT_FWCDBC7_1 { + address 77.68.77.141 + } + address-group DT_FWCDD8B_1 { + address 185.132.37.23 + } + address-group DT_FWCE020_1 { + address 77.68.48.202 + } + address-group DT_FWD0E22_4 { + address 77.68.77.99 + } + address-group DT_FWD4A27_1 { + address 77.68.76.244 + } + address-group DT_FWD7EAB_1 { + address 77.68.7.67 + } + address-group DT_FWD8DD1_2 { + address 213.171.210.155 + } + address-group DT_FWD42CF_1 { + address 185.132.38.114 + } + address-group DT_FWD56A2_1 { + address 213.171.213.31 + } + address-group DT_FWD61BF_1 { + address 88.208.199.46 + } + address-group DT_FWD338A_1 { + address 77.68.77.69 + } + address-group DT_FWD498E_1 { + address 109.228.39.41 + } + address-group DT_FWD2082_1 { + address 77.68.76.94 + } + address-group DT_FWD2440_1 { + address 77.68.114.136 + } + address-group DT_FWD3431_2 { + address 77.68.77.105 + } + address-group DT_FWD7382_1 { + address 185.132.40.11 + } + address-group DT_FWDA443_6 { + address 77.68.34.28 + } + address-group DT_FWDAA4F_1 { + address 77.68.76.124 + } + address-group DT_FWDAF47_1 { + address 77.68.23.35 + } + address-group DT_FWDCA36_3 { + address 77.68.77.81 + } + address-group DT_FWDD089_5 { + address 77.68.77.21 + } + address-group DT_FWDEDB9_1 { + address 77.68.22.146 + } + address-group DT_FWE2AB5_8 { + address 77.68.26.166 + } + address-group DT_FWE3E77_1 { + address 77.68.76.49 + } + address-group DT_FWE6AB2_1 { + address 185.132.40.166 + } + address-group DT_FWE9F7D_1 { + address 77.68.32.118 + } + address-group DT_FWE012D_1 { + address 77.68.77.190 + } + address-group DT_FWE30A1_4 { + address 77.68.33.48 + } + address-group DT_FWE32F2_8 { + address 77.68.82.157 + } + address-group DT_FWE47DA_1 { + address 77.68.91.128 + } + address-group DT_FWE57AD_1 { + address 109.228.56.26 + } + address-group DT_FWE928F_1 { + address 77.68.77.129 + } + address-group DT_FWE7180_1 { + address 77.68.123.177 + } + address-group DT_FWEAE53_1 { + address 77.68.26.216 + } + address-group DT_FWEB321_1 { + address 77.68.4.74 + } + address-group DT_FWECBFB_14 { + address 77.68.77.44 + } + address-group DT_FWEE03C_1 { + address 77.68.116.232 + } + address-group DT_FWEEC75_1 { + address 77.68.76.29 + } + address-group DT_FWEF92E_5 { + address 77.68.77.57 + } + address-group DT_FWEF92E_6 { + address 77.68.77.70 + } + address-group DT_FWEF92E_7 { + address 77.68.77.149 + } + address-group DT_FWF3A1B_1 { + address 109.228.52.186 + } + address-group DT_FWF7B68_1 { + address 77.68.77.231 + } + address-group DT_FWF7BFA_1 { + address 77.68.120.45 + } + address-group DT_FWF8E67_1 { + address 77.68.85.115 + } + address-group DT_FWF8F85_1 { + address 109.228.36.229 + } + address-group DT_FWF9C28_2 { + address 77.68.84.155 + } + address-group DT_FWF9C28_4 { + address 77.68.28.145 + } + address-group DT_FWF19FB_2 { + address 77.68.76.212 + } + address-group DT_FWF30BD_1 { + address 77.68.14.88 + } + address-group DT_FWF48EB_1 { + address 77.68.76.21 + } + address-group DT_FWF0221_1 { + address 185.132.36.60 + address 185.132.40.244 + } + address-group DT_FWF323F_1 { + address 185.132.39.109 + } + address-group DT_FWF699D_4 { + address 185.132.40.90 + } + address-group DT_FWF791C_1 { + address 77.68.90.132 + } + address-group DT_FWF879C_1 { + address 77.68.76.169 + } + address-group DT_FWF3574_1 { + address 77.68.76.191 + } + address-group DT_FWF4063_1 { + address 77.68.32.254 + } + address-group DT_FWFD9AF_9 { + address 77.68.77.24 + } + address-group DT_FWFDCC7_1 { + address 109.228.59.247 + } + address-group DT_FWFDD94_15 { + address 77.68.76.161 + } + address-group DT_FWFDE34_1 { + address 185.132.38.182 + } + address-group DT_FWFEF05_1 { + address 88.208.197.150 + } + address-group DT_H71F96 { + address 77.68.23.112 + } + address-group DT_SMTP_BLOCKED { + address 172.16.255.254 + address 77.68.77.209 + address 77.68.76.148 + address 77.68.77.211 + address 77.68.21.78 + address 77.68.77.247 + address 77.68.77.203 + address 77.68.77.68 + address 77.68.77.43 + address 77.68.77.165 + address 77.68.76.145 + address 77.68.76.239 + address 77.68.77.67 + address 77.68.76.177 + address 77.68.77.117 + address 77.68.76.50 + address 77.68.76.158 + address 77.68.76.22 + address 77.68.76.123 + address 77.68.76.251 + address 77.68.77.63 + address 77.68.7.186 + address 77.68.93.246 + address 77.68.4.252 + address 77.68.76.30 + address 77.68.76.77 + address 77.68.76.31 + address 77.68.77.248 + address 77.68.3.52 + address 77.68.76.88 + address 213.171.214.234 + address 185.132.39.219 + address 77.68.5.155 + address 77.68.80.97 + address 77.68.101.124 + address 77.68.76.111 + address 77.68.76.42 + address 77.68.77.120 + address 77.68.76.183 + address 88.208.197.160 + address 88.208.197.10 + address 77.68.76.250 + address 77.68.77.219 + address 77.68.77.152 + address 77.68.76.60 + } + address-group DT_VPN-2661 { + address 185.132.40.90 + } + address-group DT_VPN-3575 { + address 77.68.77.202 + } + address-group DT_VPN-6103 { + address 77.68.77.21 + } + address-group DT_VPN-7030 { + address 77.68.77.44 + } + address-group DT_VPN-7902 { + address 77.68.77.43 + } + address-group DT_VPN-8159 { + address 77.68.77.163 + } + address-group DT_VPN-8203 { + address 77.68.77.202 + } + address-group DT_VPN-8625 { + address 77.68.94.181 + } + address-group DT_VPN-9415 { + address 77.68.76.114 + } + address-group DT_VPN-9484 { + address 77.68.77.76 + address 77.68.76.120 + } + address-group DT_VPN-9727 { + address 185.132.40.90 + } + address-group DT_VPN-9749 { + address 213.171.212.89 + address 77.68.76.44 + address 77.68.77.239 + address 213.171.212.114 + address 77.68.103.56 + } + address-group DT_VPN-9765 { + address 77.68.76.50 + } + address-group DT_VPN-10131 { + address 77.68.76.110 + } + address-group DT_VPN-11083 { + address 213.171.212.89 + address 77.68.76.44 + address 77.68.77.239 + address 213.171.212.114 + address 77.68.103.56 + } + address-group DT_VPN-11913 { + address 77.68.76.60 + } + address-group DT_VPN-12870 { + address 77.68.77.163 + } + address-group DT_VPN-12899 { + address 77.68.77.95 + } + address-group DT_VPN-13261 { + address 77.68.77.76 + address 77.68.76.120 + } + address-group DT_VPN-13983 { + address 77.68.3.52 + } + address-group DT_VPN-14649 { + address 77.68.76.161 + } + address-group DT_VPN-14657 { + address 77.68.76.161 + } + address-group DT_VPN-14658 { + address 77.68.76.161 + } + address-group DT_VPN-14673 { + address 77.68.76.161 + } + address-group DT_VPN-15625 { + address 77.68.77.44 + } + address-group DT_VPN-15950 { + address 77.68.101.124 + } + address-group DT_VPN-15951 { + address 77.68.118.120 + address 77.68.27.211 + address 109.228.37.187 + } + address-group DT_VPN-15960 { + address 77.68.101.124 + } + address-group DT_VPN-16402 { + address 109.228.39.151 + } + address-group DT_VPN-16450 { + address 77.68.77.163 + } + address-group DT_VPN-17207 { + address 77.68.77.163 + } + address-group DT_VPN-17558 { + address 77.68.77.163 + } + address-group DT_VPN-18646 { + address 77.68.77.163 + } + address-group DT_VPN-18647 { + address 77.68.77.163 + } + address-group DT_VPN-18830 { + address 77.68.118.120 + address 77.68.27.211 + address 109.228.37.187 + } + address-group DT_VPN-19135 { + address 109.228.39.151 + } + address-group DT_VPN-19474 { + address 77.68.118.120 + address 77.68.27.211 + address 109.228.37.187 + } + address-group DT_VPN-19807 { + address 77.68.76.198 + } + address-group DT_VPN-19992 { + address 77.68.25.124 + } + address-group DT_VPN-20306 { + address 77.68.77.248 + } + address-group DT_VPN-21673 { + address 77.68.15.95 + address 77.68.75.64 + } + address-group DT_VPN-21821 { + address 77.68.15.95 + address 77.68.75.64 + } + address-group DT_VPN-21822 { + address 77.68.15.95 + address 77.68.75.64 + } + address-group DT_VPN-21876 { + address 77.68.77.163 + } + address-group DT_VPN-21982 { + address 77.68.15.95 + address 77.68.75.64 + } + address-group DT_VPN-23209 { + address 77.68.77.24 + } + address-group DT_VPN-23729 { + address 77.68.118.120 + address 77.68.27.211 + address 109.228.37.187 + } + address-group DT_VPN-23733 { + address 77.68.118.120 + address 77.68.27.211 + address 109.228.37.187 + } + address-group DT_VPN-23734 { + address 77.68.118.120 + address 77.68.27.211 + address 109.228.37.187 + } + address-group DT_VPN-23738 { + address 77.68.118.120 + address 77.68.27.211 + address 109.228.37.187 + } + address-group DT_VPN-23946 { + address 77.68.77.44 + } + address-group DT_VPN-24398 { + address 77.68.76.118 + } + address-group DT_VPN-24589 { + address 77.68.76.118 + } + address-group DT_VPN-24591 { + address 77.68.76.118 + } + address-group DT_VPN-24592 { + address 77.68.76.118 + } + address-group DT_VPN-24593 { + address 77.68.76.118 + } + address-group DT_VPN-24594 { + address 77.68.76.118 + } + address-group DT_VPN-24595 { + address 77.68.76.118 + } + address-group DT_VPN-25822 { + address 77.68.15.95 + address 77.68.75.64 + } + address-group DT_VPN-26124 { + address 77.68.77.163 + } + address-group DT_VPN-26157 { + address 77.68.77.205 + } + address-group DT_VPN-26772 { + address 185.132.40.90 + } + address-group DT_VPN-28031 { + address 77.68.77.44 + } + address-group DT_VPN-28484 { + address 77.68.118.120 + address 77.68.27.211 + address 109.228.37.187 + } + address-group DT_VPN-28515 { + address 77.68.82.157 + } + address-group DT_VPN-29631 { + address 77.68.77.44 + } + address-group DT_VPN-30261 { + address 77.68.77.163 + } + address-group DT_VPN-30262 { + address 77.68.77.163 + } + address-group DT_VPN-30679 { + address 77.68.77.163 + } + address-group DT_VPN-30791 { + address 77.68.118.120 + address 77.68.27.211 + address 109.228.37.187 + } + address-group DT_VPN-31002 { + address 109.228.36.119 + } + address-group DT_VPN-31301 { + address 88.208.197.10 + } + address-group DT_VPN-32528 { + address 77.68.76.118 + } + address-group DT_VPN-33204 { + address 77.68.77.163 + } + address-group DT_VPN-34006 { + address 77.68.33.216 + address 77.68.33.37 + address 77.68.50.90 + } + address-group DT_VPN-34122 { + address 77.68.114.237 + } + address-group DT_VPN-34309 { + address 77.68.77.44 + } + address-group DT_VPN-34501 { + address 77.68.50.142 + } + address-group DT_VPN-34583 { + address 77.68.77.145 + } + address-group G-ALL_OPEN { + address 172.16.255.254 + address 77.68.76.208 + address 77.68.77.251 + address 109.228.36.174 + address 77.68.89.72 + address 77.68.77.29 + address 185.132.43.6 + address 109.228.46.196 + address 185.132.43.98 + address 185.132.41.148 + address 77.68.49.126 + address 77.68.49.178 + address 77.68.116.84 + address 185.132.36.56 + address 77.68.126.160 + address 213.171.208.176 + address 88.208.197.155 + address 88.208.198.69 + address 77.68.29.65 + } + address-group G-ICMP { + address 172.16.255.254 + address 77.68.76.141 + address 77.68.76.16 + address 77.68.76.22 + address 77.68.76.241 + address 77.68.77.128 + address 77.68.77.130 + address 77.68.77.16 + address 77.68.77.201 + address 77.68.77.22 + address 77.68.77.71 + address 77.68.76.254 + address 77.68.5.187 + address 77.68.94.181 + address 77.68.76.243 + address 77.68.92.186 + address 77.68.76.23 + address 77.68.26.216 + address 77.68.76.157 + address 77.68.76.102 + address 77.68.76.169 + address 77.68.76.30 + address 109.228.39.157 + address 77.68.76.77 + address 77.68.7.67 + address 109.228.55.82 + address 77.68.95.212 + address 77.68.85.73 + address 77.68.117.222 + address 77.68.125.60 + address 185.132.43.157 + address 77.68.114.136 + address 77.68.77.105 + address 77.68.33.197 + address 77.68.23.64 + address 77.68.112.184 + address 77.68.49.161 + address 77.68.76.191 + address 109.228.56.97 + address 185.132.37.101 + address 77.68.76.112 + address 77.68.117.173 + address 77.68.33.216 + address 77.68.33.37 + address 77.68.50.90 + address 77.68.16.247 + address 77.68.76.212 + address 77.68.77.185 + address 77.68.77.238 + } + address-group G-20-TCP { + address 172.16.255.254 + address 77.68.76.80 + address 77.68.77.253 + address 77.68.86.148 + address 77.68.77.248 + address 77.68.79.206 + address 109.228.40.222 + address 77.68.24.172 + address 77.68.77.144 + address 77.68.76.112 + } + address-group G-21-TCP { + address 172.16.255.254 + address 77.68.76.104 + address 77.68.76.127 + address 77.68.76.136 + address 77.68.76.141 + address 77.68.76.187 + address 77.68.76.195 + address 77.68.76.203 + address 77.68.76.209 + address 77.68.76.217 + address 77.68.76.22 + address 77.68.76.220 + address 77.68.76.235 + address 77.68.76.245 + address 77.68.76.38 + address 77.68.76.54 + address 77.68.76.75 + address 77.68.76.80 + address 77.68.76.91 + address 77.68.76.94 + address 77.68.77.107 + address 77.68.77.128 + address 77.68.77.137 + address 77.68.77.150 + address 77.68.77.151 + address 77.68.77.171 + address 77.68.77.200 + address 77.68.77.201 + address 77.68.77.207 + address 77.68.77.22 + address 77.68.77.236 + address 77.68.77.240 + address 77.68.77.253 + address 77.68.77.32 + address 77.68.77.49 + address 77.68.77.50 + address 77.68.77.56 + address 77.68.77.63 + address 77.68.77.71 + address 77.68.77.81 + address 77.68.77.85 + address 77.68.77.92 + address 77.68.77.97 + address 77.68.77.99 + address 77.68.77.190 + address 77.68.77.103 + address 77.68.76.26 + address 77.68.76.107 + address 77.68.76.148 + address 77.68.76.19 + address 77.68.77.192 + address 77.68.77.157 + address 77.68.91.195 + address 77.68.77.211 + address 109.228.56.185 + address 77.68.84.147 + address 77.68.77.74 + address 77.68.4.74 + address 77.68.30.133 + address 77.68.28.145 + address 77.68.26.216 + address 77.68.77.130 + address 77.68.116.119 + address 77.68.116.220 + address 109.228.56.26 + address 77.68.7.123 + address 77.68.84.155 + address 77.68.86.40 + address 77.68.120.241 + address 77.68.122.89 + address 77.68.10.142 + address 77.68.122.241 + address 77.68.6.105 + address 77.68.17.186 + address 77.68.95.42 + address 77.68.22.146 + address 77.68.4.252 + address 109.228.36.229 + address 109.228.40.207 + address 77.68.31.144 + address 109.228.37.174 + address 109.228.37.114 + address 77.68.112.75 + address 77.68.77.160 + address 77.68.76.152 + address 77.68.7.67 + address 77.68.113.117 + address 77.68.86.148 + address 77.68.23.35 + address 109.228.40.194 + address 77.68.90.132 + address 77.68.77.26 + address 77.68.76.95 + address 77.68.120.26 + address 109.228.61.31 + address 77.68.120.249 + address 77.68.6.210 + address 213.171.213.41 + address 77.68.77.248 + address 213.171.215.184 + address 77.68.25.146 + address 213.171.210.19 + address 213.171.213.242 + address 109.228.48.249 + address 109.228.40.195 + address 77.68.127.172 + address 77.68.79.206 + address 77.68.28.147 + address 185.132.36.148 + address 185.132.37.83 + address 77.68.117.51 + address 77.68.25.124 + address 77.68.13.137 + address 109.228.52.186 + address 185.132.36.24 + address 77.68.77.69 + address 109.228.40.222 + address 77.68.87.212 + address 185.132.39.99 + address 109.228.38.201 + address 185.132.39.219 + address 77.68.28.139 + address 77.68.81.218 + address 77.68.4.111 + address 77.68.77.174 + address 77.68.117.222 + address 185.132.41.73 + address 77.68.76.45 + address 77.68.77.215 + address 77.68.77.214 + address 77.68.79.89 + address 77.68.76.21 + address 77.68.33.68 + address 77.68.80.97 + address 77.68.77.65 + address 185.132.41.148 + address 77.68.24.172 + address 77.68.5.95 + address 77.68.5.125 + address 213.171.208.40 + address 77.68.76.40 + address 77.68.113.164 + address 77.68.114.93 + address 185.132.36.60 + address 185.132.40.244 + address 213.171.214.102 + address 88.208.197.160 + address 88.208.196.123 + address 77.68.77.144 + address 77.68.126.14 + address 77.68.76.171 + address 88.208.198.69 + address 77.68.34.139 + address 88.208.212.31 + address 77.68.76.112 + address 77.68.76.228 + address 77.68.77.75 + address 88.208.198.66 + address 77.68.77.219 + address 77.68.77.204 + address 77.68.4.25 + address 77.68.7.114 + address 77.68.123.177 + address 77.68.114.237 + address 77.68.77.222 + address 77.68.112.83 + address 185.132.37.47 + address 77.68.77.238 + } + address-group G-22-TCP { + address 172.16.255.254 + address 77.68.76.104 + address 77.68.76.105 + address 77.68.76.115 + address 77.68.76.122 + address 77.68.76.126 + address 77.68.76.127 + address 77.68.76.136 + address 77.68.76.141 + address 77.68.76.145 + address 77.68.76.148 + address 77.68.76.158 + address 77.68.76.164 + address 77.68.76.177 + address 77.68.76.187 + address 77.68.76.195 + address 77.68.76.197 + address 77.68.76.20 + address 77.68.76.200 + address 77.68.76.209 + address 77.68.76.217 + address 77.68.76.22 + address 77.68.76.235 + address 77.68.76.239 + address 77.68.76.245 + address 77.68.76.247 + address 77.68.76.25 + address 77.68.76.251 + address 77.68.76.252 + address 77.68.76.33 + address 77.68.76.37 + address 77.68.76.38 + address 77.68.76.49 + address 77.68.76.54 + address 77.68.76.55 + address 77.68.76.57 + address 77.68.76.61 + address 77.68.76.74 + address 77.68.76.80 + address 77.68.76.99 + address 77.68.77.100 + address 77.68.77.103 + address 77.68.77.107 + address 77.68.77.108 + address 77.68.77.117 + address 77.68.77.124 + address 77.68.77.128 + address 77.68.77.129 + address 77.68.77.130 + address 77.68.77.137 + address 77.68.77.139 + address 77.68.77.140 + address 77.68.77.141 + address 77.68.77.150 + address 77.68.77.151 + address 77.68.77.159 + address 77.68.77.171 + address 77.68.77.176 + address 77.68.77.19 + address 77.68.77.190 + address 77.68.77.200 + address 77.68.77.201 + address 77.68.77.203 + address 77.68.77.207 + address 77.68.77.211 + address 77.68.77.212 + address 77.68.77.22 + address 77.68.77.221 + address 77.68.77.227 + address 77.68.77.240 + address 77.68.77.243 + address 77.68.77.247 + address 77.68.77.253 + address 77.68.77.32 + address 77.68.77.33 + address 77.68.77.37 + address 77.68.77.43 + address 77.68.77.49 + address 77.68.77.50 + address 77.68.77.53 + address 77.68.77.56 + address 77.68.77.67 + address 77.68.77.68 + address 77.68.77.77 + address 77.68.77.79 + address 77.68.77.81 + address 77.68.77.85 + address 77.68.77.88 + address 77.68.77.92 + address 77.68.77.99 + address 77.68.76.110 + address 77.68.76.76 + address 77.68.76.211 + address 77.68.76.19 + address 77.68.77.74 + address 77.68.76.165 + address 77.68.77.254 + address 77.68.77.157 + address 77.68.76.138 + address 77.68.76.139 + address 77.68.76.124 + address 77.68.76.243 + address 77.68.76.114 + address 77.68.76.244 + address 77.68.77.192 + address 77.68.77.161 + address 77.68.91.195 + address 77.68.17.26 + address 77.68.28.145 + address 77.68.84.147 + address 109.228.56.185 + address 77.68.26.166 + address 77.68.12.195 + address 77.68.29.178 + address 77.68.5.187 + address 77.68.7.227 + address 77.68.4.24 + address 77.68.4.74 + address 77.68.6.202 + address 77.68.5.241 + address 77.68.4.39 + address 77.68.81.44 + address 77.68.90.106 + address 77.68.27.54 + address 77.68.30.133 + address 77.68.4.136 + address 77.68.24.112 + address 77.68.92.186 + address 77.68.20.161 + address 77.68.26.216 + address 77.68.20.231 + address 77.68.118.17 + address 77.68.116.119 + address 77.68.116.232 + address 77.68.7.172 + address 77.68.116.221 + address 77.68.89.183 + address 77.68.83.41 + address 77.68.86.40 + address 77.68.88.164 + address 109.228.56.26 + address 77.68.7.123 + address 77.68.112.248 + address 109.228.60.215 + address 77.68.7.186 + address 77.68.93.246 + address 77.68.120.241 + address 77.68.121.106 + address 77.68.122.195 + address 77.68.122.89 + address 77.68.122.241 + address 77.68.81.141 + address 77.68.116.52 + address 77.68.6.32 + address 77.68.76.229 + address 77.68.28.207 + address 77.68.4.252 + address 77.68.17.186 + address 77.68.24.220 + address 77.68.22.146 + address 77.68.23.112 + address 77.68.125.32 + address 77.68.72.202 + address 109.228.36.229 + address 77.68.31.144 + address 77.68.2.215 + address 77.68.117.142 + address 77.68.5.166 + address 77.68.76.102 + address 109.228.37.174 + address 109.228.37.114 + address 77.68.76.169 + address 109.228.37.240 + address 77.68.112.75 + address 77.68.77.160 + address 109.228.39.249 + address 77.68.76.77 + address 109.228.40.226 + address 77.68.7.67 + address 77.68.126.51 + address 77.68.75.113 + address 77.68.86.148 + address 77.68.23.35 + address 77.68.114.183 + address 109.228.40.194 + address 77.68.76.31 + address 77.68.90.132 + address 77.68.77.26 + address 77.68.76.96 + address 77.68.77.30 + address 77.68.76.95 + address 77.68.10.170 + address 77.68.120.26 + address 109.228.61.31 + address 77.68.76.59 + address 213.171.213.41 + address 77.68.77.248 + address 213.171.212.171 + address 77.68.4.22 + address 77.68.119.14 + address 213.171.215.184 + address 77.68.77.202 + address 77.68.25.146 + address 213.171.213.31 + address 77.68.78.229 + address 77.68.77.102 + address 213.171.210.19 + address 77.68.24.59 + address 213.171.213.97 + address 213.171.213.242 + address 109.228.48.249 + address 109.228.40.195 + address 77.68.120.229 + address 77.68.79.206 + address 77.68.123.250 + address 77.68.28.147 + address 185.132.36.142 + address 213.171.212.172 + address 185.132.36.148 + address 213.171.208.58 + address 77.68.25.130 + address 185.132.38.142 + address 109.228.56.242 + address 109.228.46.81 + address 185.132.38.95 + address 185.132.37.83 + address 77.68.117.51 + address 77.68.116.36 + address 77.68.120.45 + address 213.171.210.59 + address 213.171.215.43 + address 185.132.37.102 + address 109.228.42.232 + address 109.228.52.186 + address 77.68.9.186 + address 77.68.13.76 + address 109.228.36.194 + address 185.132.36.24 + address 77.68.77.69 + address 185.132.39.129 + address 185.132.36.17 + address 109.228.40.222 + address 77.68.74.39 + address 77.68.118.104 + address 213.171.212.136 + address 77.68.120.31 + address 77.68.74.152 + address 185.132.39.37 + address 77.68.87.212 + address 77.68.119.188 + address 77.68.74.85 + address 77.68.91.22 + address 77.68.76.88 + address 77.68.4.242 + address 77.68.76.181 + address 77.68.76.161 + address 109.228.35.84 + address 185.132.39.99 + address 77.68.95.212 + address 77.68.85.73 + address 77.68.76.219 + address 77.68.27.27 + address 77.68.3.194 + address 77.68.3.144 + address 77.68.3.80 + address 77.68.27.28 + address 77.68.3.247 + address 77.68.3.161 + address 77.68.27.18 + address 77.68.3.121 + address 213.171.214.234 + address 185.132.39.219 + address 77.68.28.139 + address 77.68.81.218 + address 77.68.4.111 + address 77.68.77.174 + address 77.68.117.222 + address 213.171.211.128 + address 77.68.5.155 + address 185.132.41.73 + address 213.171.214.167 + address 185.132.43.28 + address 213.171.213.42 + address 77.68.76.45 + address 185.132.41.72 + address 185.132.43.157 + address 185.132.40.56 + address 185.132.37.23 + address 77.68.117.29 + address 77.68.75.253 + address 77.68.11.140 + address 77.68.77.215 + address 77.68.20.217 + address 77.68.76.198 + address 77.68.77.214 + address 213.171.210.177 + address 185.132.38.114 + address 77.68.33.48 + address 77.68.32.89 + address 77.68.32.86 + address 77.68.34.138 + address 77.68.32.83 + address 77.68.75.45 + address 77.68.76.176 + address 185.132.43.164 + address 77.68.76.137 + address 185.132.40.152 + address 77.68.33.68 + address 77.68.93.125 + address 77.68.24.134 + address 185.132.38.248 + address 77.68.32.43 + address 77.68.120.218 + address 77.68.112.167 + address 77.68.32.31 + address 77.68.32.254 + address 77.68.80.26 + address 77.68.80.97 + address 77.68.121.119 + address 77.68.74.209 + address 77.68.77.65 + address 185.132.43.6 + address 109.228.46.196 + address 185.132.43.98 + address 185.132.41.148 + address 77.68.24.172 + address 77.68.33.197 + address 213.171.210.25 + address 77.68.5.95 + address 77.68.23.64 + address 77.68.101.125 + address 77.68.5.125 + address 77.68.100.167 + address 109.228.59.247 + address 77.68.35.116 + address 77.68.33.171 + address 77.68.48.105 + address 77.68.48.81 + address 77.68.49.4 + address 109.228.36.119 + address 77.68.121.127 + address 77.68.82.147 + address 77.68.49.12 + address 77.68.8.144 + address 77.68.116.183 + address 77.68.103.19 + address 77.68.50.91 + address 77.68.24.63 + address 77.68.118.15 + address 77.68.50.198 + address 77.68.49.160 + address 77.68.49.161 + address 77.68.76.191 + address 77.68.76.40 + address 77.68.113.164 + address 77.68.77.42 + address 77.68.100.134 + address 77.68.100.132 + address 77.68.114.93 + address 185.132.36.60 + address 185.132.40.244 + address 77.68.85.18 + address 77.68.50.193 + address 77.68.89.247 + address 88.208.197.10 + address 77.68.102.129 + address 109.228.36.79 + address 185.132.38.182 + address 185.132.41.240 + address 77.68.51.214 + address 88.208.196.123 + address 77.68.126.22 + address 213.171.212.90 + address 77.68.114.205 + address 77.68.48.202 + address 77.68.112.175 + address 77.68.112.90 + address 185.132.40.166 + address 77.68.103.120 + address 77.68.103.147 + address 77.68.33.24 + address 109.228.58.134 + address 109.228.47.223 + address 109.228.56.97 + address 77.68.103.227 + address 88.208.196.92 + address 88.208.196.154 + address 185.132.39.44 + address 77.68.76.248 + address 88.208.198.92 + address 77.68.77.144 + address 77.68.126.14 + address 88.208.196.91 + address 77.68.100.77 + address 185.132.37.101 + address 77.68.87.164 + address 77.68.76.120 + address 77.68.93.164 + address 77.68.76.171 + address 88.208.197.135 + address 88.208.197.118 + address 88.208.197.150 + address 77.68.34.139 + address 213.171.213.175 + address 77.68.21.171 + address 88.208.197.60 + address 109.228.37.10 + address 88.208.215.61 + address 88.208.212.31 + address 109.228.53.243 + address 77.68.48.89 + address 88.208.212.188 + address 88.208.198.251 + address 88.208.215.19 + address 77.68.76.228 + address 109.228.39.41 + address 77.68.115.142 + address 77.68.78.73 + address 213.171.214.96 + address 88.208.198.66 + address 77.68.3.61 + address 77.68.77.219 + address 77.68.26.228 + address 77.68.4.25 + address 77.68.7.114 + address 77.68.123.177 + address 77.68.77.222 + address 77.68.112.83 + address 77.68.117.214 + address 88.208.199.141 + address 185.132.39.109 + address 185.132.37.47 + address 77.68.102.5 + address 77.68.16.247 + address 88.208.212.94 + address 77.68.72.254 + address 109.228.61.37 + address 77.68.50.142 + address 77.68.78.113 + address 88.208.212.182 + address 185.132.40.124 + address 88.208.197.208 + address 88.208.197.129 + address 77.68.77.238 + address 77.68.79.82 + address 185.132.38.216 + } + address-group G-25-TCP { + address 172.16.255.254 + address 77.68.76.115 + address 77.68.76.141 + address 77.68.76.187 + address 77.68.76.195 + address 77.68.76.197 + address 77.68.76.203 + address 77.68.76.209 + address 77.68.76.55 + address 77.68.76.57 + address 77.68.76.75 + address 77.68.76.91 + address 77.68.76.99 + address 77.68.77.107 + address 77.68.77.129 + address 77.68.77.130 + address 77.68.77.141 + address 77.68.77.150 + address 77.68.77.159 + address 77.68.77.171 + address 77.68.77.176 + address 77.68.77.207 + address 77.68.77.22 + address 77.68.77.236 + address 77.68.77.240 + address 77.68.77.243 + address 77.68.77.32 + address 77.68.77.33 + address 77.68.77.49 + address 77.68.77.50 + address 77.68.77.56 + address 77.68.77.63 + address 77.68.77.81 + address 77.68.77.85 + address 77.68.77.92 + address 77.68.77.97 + address 77.68.77.99 + address 77.68.77.77 + address 77.68.76.19 + address 77.68.77.192 + address 77.68.77.254 + address 77.68.76.139 + address 77.68.84.147 + address 77.68.4.74 + address 77.68.6.202 + address 77.68.81.44 + address 77.68.30.133 + address 77.68.77.74 + address 77.68.77.100 + address 77.68.92.186 + address 77.68.76.114 + address 77.68.116.119 + address 77.68.116.221 + address 77.68.116.220 + address 109.228.56.26 + address 77.68.7.123 + address 77.68.120.241 + address 109.228.60.215 + address 77.68.7.172 + address 77.68.116.52 + address 77.68.91.128 + address 77.68.24.112 + address 77.68.76.94 + address 109.228.37.114 + address 77.68.112.75 + address 77.68.77.160 + address 77.68.7.67 + address 77.68.113.117 + address 77.68.126.51 + address 77.68.86.148 + address 77.68.23.35 + address 77.68.77.30 + address 77.68.76.95 + address 77.68.10.170 + address 213.171.213.41 + address 213.171.215.184 + address 77.68.25.146 + address 213.171.213.31 + address 77.68.78.229 + address 213.171.210.19 + address 77.68.79.206 + address 213.171.215.252 + address 109.228.52.186 + address 77.68.77.69 + address 109.228.40.222 + address 77.68.87.212 + address 185.132.39.99 + address 77.68.85.73 + address 77.68.28.139 + address 77.68.4.111 + address 77.68.77.174 + address 77.68.117.222 + address 185.132.43.28 + address 185.132.37.23 + address 77.68.77.215 + address 77.68.77.214 + address 185.132.38.114 + address 77.68.33.48 + address 77.68.79.89 + address 77.68.76.21 + address 77.68.76.137 + address 77.68.80.26 + address 77.68.5.95 + address 77.68.100.167 + address 77.68.4.80 + address 77.68.49.152 + address 213.171.208.40 + address 77.68.112.184 + address 77.68.115.17 + address 77.68.82.147 + address 77.68.118.15 + address 77.68.76.191 + address 77.68.50.193 + address 77.68.102.129 + address 77.68.76.118 + address 88.208.198.69 + address 77.68.34.139 + address 88.208.197.60 + address 88.208.212.188 + address 77.68.76.112 + address 77.68.77.75 + address 213.171.214.96 + address 88.208.198.66 + address 77.68.77.219 + address 77.68.77.204 + address 77.68.76.202 + address 77.68.123.177 + address 77.68.77.222 + address 77.68.112.83 + address 185.132.37.47 + address 77.68.77.152 + address 77.68.77.181 + address 77.68.77.185 + address 77.68.77.238 + address 77.68.79.82 + } + address-group G-53-TCP { + address 172.16.255.254 + address 77.68.94.181 + address 77.68.28.145 + address 77.68.84.155 + address 77.68.78.229 + address 185.132.39.99 + address 185.132.43.28 + address 77.68.77.215 + address 185.132.40.152 + address 77.68.49.161 + address 77.68.76.118 + } + address-group G-53-UDP { + address 172.16.255.254 + address 77.68.76.235 + address 77.68.76.93 + address 77.68.77.107 + address 77.68.77.151 + address 77.68.77.37 + address 77.68.76.139 + address 77.68.81.44 + address 77.68.94.181 + address 77.68.28.145 + address 77.68.81.141 + address 77.68.4.252 + address 77.68.125.32 + address 77.68.86.148 + address 77.68.78.229 + address 185.132.43.28 + address 77.68.75.45 + address 185.132.40.152 + address 77.68.4.80 + address 77.68.49.152 + address 77.68.49.161 + address 77.68.34.50 + } + address-group G-80-TCP { + address 172.16.255.254 + address 77.68.76.104 + address 77.68.76.105 + address 77.68.76.115 + address 77.68.76.116 + address 77.68.76.122 + address 77.68.76.126 + address 77.68.76.127 + address 77.68.76.136 + address 77.68.76.141 + address 77.68.76.145 + address 77.68.76.148 + address 77.68.76.150 + address 77.68.76.158 + address 77.68.76.164 + address 77.68.76.177 + address 77.68.76.187 + address 77.68.76.195 + address 77.68.76.197 + address 77.68.76.20 + address 77.68.76.200 + address 77.68.76.203 + address 77.68.76.209 + address 77.68.76.217 + address 77.68.76.22 + address 77.68.76.220 + address 77.68.76.23 + address 77.68.76.231 + address 77.68.76.235 + address 77.68.76.239 + address 77.68.76.241 + address 77.68.76.245 + address 77.68.76.247 + address 77.68.76.25 + address 77.68.76.251 + address 77.68.76.252 + address 77.68.76.33 + address 77.68.76.35 + address 77.68.76.37 + address 77.68.76.38 + address 77.68.76.39 + address 77.68.76.49 + address 77.68.76.50 + address 77.68.76.54 + address 77.68.76.55 + address 77.68.76.57 + address 77.68.76.58 + address 77.68.76.61 + address 77.68.76.74 + address 77.68.76.75 + address 77.68.76.80 + address 77.68.76.91 + address 77.68.76.93 + address 77.68.76.94 + address 77.68.76.99 + address 77.68.77.100 + address 77.68.77.103 + address 77.68.77.107 + address 77.68.77.108 + address 77.68.77.115 + address 77.68.77.117 + address 77.68.77.124 + address 77.68.77.128 + address 77.68.77.129 + address 77.68.77.130 + address 77.68.77.137 + address 77.68.77.139 + address 77.68.77.140 + address 77.68.77.141 + address 77.68.77.150 + address 77.68.77.151 + address 77.68.77.156 + address 77.68.77.159 + address 77.68.77.171 + address 77.68.77.176 + address 77.68.77.178 + address 77.68.77.19 + address 77.68.77.190 + address 77.68.77.199 + address 77.68.77.200 + address 77.68.77.201 + address 77.68.77.203 + address 77.68.77.207 + address 77.68.77.211 + address 77.68.77.212 + address 77.68.77.22 + address 77.68.77.227 + address 77.68.77.228 + address 77.68.77.236 + address 77.68.77.240 + address 77.68.77.243 + address 77.68.77.247 + address 77.68.77.253 + address 77.68.77.32 + address 77.68.77.33 + address 77.68.77.37 + address 77.68.77.49 + address 77.68.77.50 + address 77.68.77.53 + address 77.68.77.56 + address 77.68.77.63 + address 77.68.77.67 + address 77.68.77.68 + address 77.68.77.71 + address 77.68.77.77 + address 77.68.77.79 + address 77.68.77.81 + address 77.68.77.85 + address 77.68.77.88 + address 77.68.77.92 + address 77.68.77.97 + address 77.68.77.99 + address 77.68.76.76 + address 77.68.76.124 + address 77.68.76.211 + address 77.68.76.19 + address 77.68.77.74 + address 77.68.77.192 + address 77.68.76.92 + address 77.68.76.165 + address 77.68.77.254 + address 77.68.77.157 + address 77.68.76.138 + address 77.68.76.139 + address 77.68.76.114 + address 77.68.76.244 + address 77.68.77.161 + address 77.68.77.62 + address 77.68.77.38 + address 77.68.91.195 + address 77.68.17.26 + address 77.68.28.145 + address 109.228.56.185 + address 77.68.84.147 + address 77.68.12.195 + address 77.68.21.78 + address 77.68.5.187 + address 77.68.7.227 + address 77.68.4.24 + address 77.68.4.74 + address 77.68.6.202 + address 77.68.5.241 + address 77.68.4.39 + address 77.68.81.44 + address 77.68.90.106 + address 77.68.94.181 + address 77.68.30.164 + address 77.68.30.133 + address 77.68.4.136 + address 77.68.23.158 + address 77.68.92.186 + address 77.68.24.112 + address 77.68.112.213 + address 77.68.20.161 + address 77.68.26.216 + address 77.68.20.231 + address 77.68.118.17 + address 77.68.116.119 + address 77.68.116.220 + address 77.68.116.232 + address 77.68.76.142 + address 77.68.117.202 + address 77.68.7.172 + address 77.68.116.221 + address 77.68.89.183 + address 77.68.83.41 + address 77.68.86.40 + address 77.68.88.164 + address 109.228.56.26 + address 77.68.7.123 + address 77.68.112.248 + address 109.228.60.215 + address 77.68.7.186 + address 77.68.93.246 + address 77.68.84.155 + address 77.68.120.241 + address 77.68.121.106 + address 77.68.122.195 + address 77.68.122.89 + address 77.68.120.146 + address 77.68.122.241 + address 77.68.119.92 + address 77.68.81.141 + address 77.68.10.142 + address 77.68.116.52 + address 77.68.6.105 + address 77.68.76.229 + address 77.68.95.42 + address 77.68.28.207 + address 77.68.4.252 + address 77.68.17.186 + address 77.68.91.128 + address 77.68.22.146 + address 77.68.23.112 + address 77.68.24.220 + address 77.68.125.32 + address 77.68.76.243 + address 77.68.12.250 + address 77.68.72.202 + address 109.228.36.229 + address 109.228.40.207 + address 77.68.31.144 + address 77.68.2.215 + address 77.68.117.142 + address 77.68.5.166 + address 109.228.37.174 + address 109.228.37.114 + address 77.68.76.169 + address 109.228.37.240 + address 77.68.112.75 + address 77.68.76.30 + address 109.228.35.110 + address 77.68.77.160 + address 77.68.77.208 + address 77.68.76.152 + address 109.228.39.249 + address 77.68.76.77 + address 109.228.40.226 + address 77.68.7.67 + address 77.68.113.117 + address 77.68.126.51 + address 77.68.75.113 + address 77.68.86.148 + address 77.68.23.35 + address 77.68.114.183 + address 109.228.40.194 + address 77.68.76.31 + address 77.68.77.72 + address 77.68.90.132 + address 77.68.6.110 + address 77.68.76.96 + address 77.68.77.30 + address 77.68.76.95 + address 77.68.10.170 + address 77.68.120.26 + address 109.228.61.31 + address 77.68.76.59 + address 77.68.120.249 + address 77.68.6.210 + address 213.171.213.41 + address 77.68.77.248 + address 213.171.212.171 + address 77.68.4.22 + address 77.68.119.14 + address 213.171.215.184 + address 77.68.77.202 + address 77.68.25.146 + address 213.171.213.31 + address 77.68.78.229 + address 77.68.77.102 + address 213.171.210.19 + address 77.68.24.59 + address 213.171.213.97 + address 213.171.213.242 + address 77.68.77.205 + address 109.228.48.249 + address 109.228.40.195 + address 77.68.120.229 + address 77.68.127.172 + address 77.68.79.206 + address 77.68.123.250 + address 77.68.28.147 + address 213.171.212.172 + address 185.132.36.148 + address 213.171.208.58 + address 77.68.25.130 + address 109.228.56.242 + address 109.228.46.81 + address 185.132.38.95 + address 185.132.37.83 + address 77.68.117.51 + address 77.68.116.36 + address 77.68.120.45 + address 77.68.25.124 + address 213.171.210.59 + address 213.171.215.43 + address 213.171.215.252 + address 185.132.37.102 + address 109.228.42.232 + address 109.228.52.186 + address 77.68.9.186 + address 77.68.13.76 + address 109.228.36.194 + address 185.132.36.7 + address 185.132.36.24 + address 77.68.77.69 + address 185.132.39.129 + address 185.132.36.17 + address 109.228.40.222 + address 77.68.118.104 + address 77.68.120.31 + address 77.68.74.152 + address 185.132.39.37 + address 77.68.3.52 + address 77.68.87.212 + address 77.68.76.29 + address 77.68.119.188 + address 77.68.74.85 + address 77.68.91.22 + address 77.68.76.88 + address 77.68.4.242 + address 77.68.76.181 + address 77.68.76.161 + address 185.132.39.99 + address 77.68.95.212 + address 77.68.85.73 + address 77.68.76.219 + address 77.68.27.27 + address 77.68.3.194 + address 77.68.3.144 + address 77.68.3.80 + address 77.68.27.28 + address 77.68.3.247 + address 77.68.3.161 + address 77.68.27.18 + address 77.68.3.121 + address 213.171.214.234 + address 109.228.38.201 + address 185.132.39.219 + address 77.68.28.139 + address 77.68.81.218 + address 77.68.4.111 + address 77.68.77.174 + address 77.68.117.222 + address 213.171.211.128 + address 77.68.5.155 + address 185.132.41.73 + address 77.68.77.231 + address 213.171.214.167 + address 185.132.43.28 + address 213.171.213.42 + address 77.68.76.45 + address 185.132.41.72 + address 77.68.92.92 + address 185.132.40.56 + address 185.132.37.23 + address 77.68.117.29 + address 77.68.75.253 + address 77.68.11.140 + address 77.68.77.215 + address 77.68.20.217 + address 77.68.10.152 + address 77.68.73.73 + address 77.68.76.198 + address 77.68.77.214 + address 77.68.9.75 + address 213.171.210.177 + address 77.68.76.160 + address 185.132.38.114 + address 77.68.33.48 + address 185.132.40.90 + address 77.68.79.89 + address 77.68.34.28 + address 77.68.76.21 + address 77.68.75.45 + address 77.68.76.176 + address 77.68.77.95 + address 185.132.39.68 + address 185.132.43.164 + address 77.68.76.137 + address 185.132.40.152 + address 77.68.77.249 + address 77.68.33.68 + address 77.68.24.134 + address 185.132.38.248 + address 77.68.32.43 + address 77.68.120.218 + address 77.68.112.167 + address 77.68.32.31 + address 77.68.32.118 + address 77.68.32.254 + address 77.68.80.26 + address 77.68.17.200 + address 77.68.80.97 + address 77.68.121.119 + address 77.68.74.209 + address 77.68.77.65 + address 185.132.43.6 + address 109.228.46.196 + address 185.132.43.98 + address 77.68.100.150 + address 185.132.41.148 + address 77.68.24.172 + address 77.68.33.197 + address 77.68.5.95 + address 77.68.23.64 + address 77.68.101.124 + address 77.68.5.125 + address 77.68.100.167 + address 77.68.4.80 + address 77.68.49.152 + address 109.228.59.247 + address 213.171.208.40 + address 77.68.112.184 + address 77.68.35.116 + address 77.68.33.171 + address 77.68.76.111 + address 77.68.76.42 + address 77.68.77.120 + address 77.68.76.183 + address 77.68.118.86 + address 77.68.48.105 + address 77.68.48.81 + address 77.68.49.4 + address 109.228.36.119 + address 77.68.34.26 + address 77.68.115.17 + address 77.68.121.127 + address 77.68.82.147 + address 77.68.49.12 + address 77.68.8.144 + address 77.68.116.183 + address 213.171.212.89 + address 77.68.76.44 + address 77.68.77.239 + address 77.68.51.202 + address 77.68.101.64 + address 77.68.103.19 + address 77.68.50.91 + address 77.68.24.63 + address 77.68.118.15 + address 77.68.50.198 + address 77.68.77.59 + address 77.68.49.160 + address 77.68.76.191 + address 77.68.126.101 + address 77.68.113.164 + address 77.68.77.42 + address 77.68.100.134 + address 77.68.100.132 + address 77.68.114.93 + address 185.132.36.60 + address 185.132.40.244 + address 77.68.85.18 + address 213.171.214.102 + address 77.68.50.193 + address 88.208.197.160 + address 88.208.197.10 + address 77.68.102.129 + address 109.228.36.79 + address 185.132.38.182 + address 185.132.41.240 + address 77.68.51.214 + address 88.208.196.123 + address 88.208.215.157 + address 77.68.126.22 + address 77.68.4.180 + address 213.171.212.90 + address 77.68.114.205 + address 185.132.43.71 + address 77.68.77.114 + address 77.68.48.202 + address 77.68.112.175 + address 77.68.112.90 + address 185.132.40.166 + address 77.68.76.118 + address 77.68.103.120 + address 77.68.33.24 + address 109.228.58.134 + address 109.228.47.223 + address 77.68.31.96 + address 77.68.103.227 + address 77.68.76.250 + address 213.171.212.203 + address 88.208.196.92 + address 88.208.196.154 + address 185.132.39.44 + address 77.68.76.248 + address 88.208.198.92 + address 109.228.36.37 + address 77.68.77.144 + address 77.68.126.14 + address 88.208.196.91 + address 77.68.100.77 + address 185.132.37.101 + address 77.68.87.164 + address 77.68.77.76 + address 77.68.76.120 + address 77.68.82.157 + address 77.68.93.164 + address 77.68.76.171 + address 88.208.197.135 + address 88.208.197.118 + address 88.208.197.150 + address 213.171.212.114 + address 88.208.198.69 + address 77.68.34.139 + address 77.68.21.171 + address 88.208.197.60 + address 77.68.85.27 + address 109.228.37.10 + address 88.208.215.61 + address 88.208.199.249 + address 88.208.212.31 + address 109.228.53.243 + address 77.68.48.89 + address 88.208.212.188 + address 88.208.198.251 + address 77.68.76.112 + address 77.68.48.14 + address 88.208.215.19 + address 77.68.103.56 + address 77.68.76.228 + address 77.68.77.75 + address 77.68.117.173 + address 88.208.215.121 + address 109.228.39.41 + address 77.68.88.100 + address 77.68.76.108 + address 77.68.115.142 + address 213.171.214.96 + address 88.208.198.66 + address 88.208.198.64 + address 77.68.3.61 + address 77.68.77.219 + address 77.68.77.204 + address 77.68.26.228 + address 77.68.74.232 + address 77.68.118.88 + address 77.68.76.48 + address 77.68.76.202 + address 77.68.4.25 + address 77.68.7.114 + address 77.68.123.177 + address 88.208.197.23 + address 77.68.114.237 + address 77.68.77.222 + address 77.68.112.83 + address 88.208.199.141 + address 77.68.77.163 + address 185.132.39.109 + address 77.68.77.44 + address 185.132.37.47 + address 77.68.102.5 + address 77.68.16.247 + address 88.208.212.94 + address 77.68.72.254 + address 77.68.77.152 + address 77.68.50.142 + address 88.208.199.46 + address 77.68.78.113 + address 88.208.212.182 + address 77.68.77.181 + address 77.68.15.95 + address 77.68.75.64 + address 213.171.212.71 + address 185.132.40.124 + address 88.208.197.208 + address 88.208.197.129 + address 77.68.76.60 + address 77.68.6.119 + address 77.68.77.185 + address 77.68.77.238 + address 77.68.79.82 + address 109.228.39.151 + } + address-group G-110-TCP { + address 172.16.255.254 + address 77.68.76.187 + address 77.68.77.107 + address 77.68.77.128 + address 77.68.77.129 + address 77.68.77.171 + address 77.68.77.176 + address 77.68.77.190 + address 77.68.77.207 + address 77.68.77.22 + address 77.68.77.33 + address 77.68.77.49 + address 77.68.77.92 + address 77.68.77.77 + address 77.68.76.19 + address 77.68.77.192 + address 77.68.84.147 + address 77.68.4.74 + address 77.68.6.202 + address 77.68.116.119 + address 77.68.116.221 + address 77.68.120.241 + address 109.228.60.215 + address 77.68.116.52 + address 77.68.126.51 + address 77.68.23.35 + address 77.68.76.95 + address 213.171.215.184 + address 77.68.25.146 + address 77.68.79.206 + address 213.171.215.252 + address 109.228.52.186 + address 109.228.40.222 + address 185.132.39.99 + address 77.68.77.214 + address 185.132.38.114 + address 77.68.79.89 + address 77.68.5.95 + address 77.68.100.167 + address 77.68.4.80 + address 77.68.49.152 + address 213.171.208.40 + address 77.68.50.193 + address 77.68.102.129 + address 88.208.198.69 + address 88.208.212.188 + address 88.208.198.66 + address 77.68.4.25 + address 77.68.7.114 + address 77.68.123.177 + address 77.68.77.185 + address 77.68.77.238 + } + address-group G-143-TCP { + address 172.16.255.254 + address 77.68.76.115 + address 77.68.76.123 + address 77.68.76.187 + address 77.68.77.129 + address 77.68.77.130 + address 77.68.77.141 + address 77.68.77.171 + address 77.68.77.176 + address 77.68.77.207 + address 77.68.77.22 + address 77.68.77.33 + address 77.68.77.49 + address 77.68.77.50 + address 77.68.77.92 + address 77.68.77.77 + address 77.68.77.192 + address 77.68.84.147 + address 77.68.4.74 + address 77.68.6.202 + address 77.68.81.44 + address 77.68.92.186 + address 77.68.116.119 + address 77.68.116.221 + address 109.228.60.215 + address 77.68.7.172 + address 77.68.116.52 + address 77.68.24.112 + address 77.68.77.107 + address 77.68.112.75 + address 77.68.7.67 + address 77.68.126.51 + address 77.68.23.35 + address 77.68.76.95 + address 213.171.215.184 + address 77.68.25.146 + address 213.171.213.31 + address 213.171.210.19 + address 77.68.79.206 + address 77.68.77.69 + address 109.228.40.222 + address 185.132.39.99 + address 77.68.117.222 + address 77.68.33.48 + address 77.68.79.89 + address 77.68.5.95 + address 77.68.100.167 + address 77.68.4.80 + address 77.68.49.152 + address 213.171.208.40 + address 77.68.115.17 + address 77.68.102.129 + address 88.208.198.69 + address 77.68.34.139 + address 88.208.212.188 + address 88.208.198.66 + address 77.68.77.204 + address 77.68.4.25 + address 77.68.7.114 + address 77.68.123.177 + address 77.68.77.222 + address 77.68.112.83 + } + address-group G-443-TCP { + address 172.16.255.254 + address 77.68.76.104 + address 77.68.76.105 + address 77.68.76.115 + address 77.68.76.116 + address 77.68.76.122 + address 77.68.76.126 + address 77.68.76.127 + address 77.68.76.136 + address 77.68.76.141 + address 77.68.76.145 + address 77.68.76.148 + address 77.68.76.150 + address 77.68.76.158 + address 77.68.76.164 + address 77.68.76.177 + address 77.68.76.187 + address 77.68.76.195 + address 77.68.76.197 + address 77.68.76.20 + address 77.68.76.200 + address 77.68.76.203 + address 77.68.76.209 + address 77.68.76.217 + address 77.68.76.22 + address 77.68.76.220 + address 77.68.76.23 + address 77.68.76.231 + address 77.68.76.235 + address 77.68.76.239 + address 77.68.76.241 + address 77.68.76.245 + address 77.68.76.25 + address 77.68.76.252 + address 77.68.76.33 + address 77.68.76.35 + address 77.68.76.37 + address 77.68.76.38 + address 77.68.76.39 + address 77.68.76.49 + address 77.68.76.50 + address 77.68.76.54 + address 77.68.76.55 + address 77.68.76.57 + address 77.68.76.58 + address 77.68.76.61 + address 77.68.76.74 + address 77.68.76.75 + address 77.68.76.80 + address 77.68.76.91 + address 77.68.76.93 + address 77.68.76.94 + address 77.68.76.99 + address 77.68.77.100 + address 77.68.77.103 + address 77.68.77.107 + address 77.68.77.108 + address 77.68.77.117 + address 77.68.77.124 + address 77.68.77.128 + address 77.68.77.129 + address 77.68.77.130 + address 77.68.77.137 + address 77.68.77.139 + address 77.68.77.140 + address 77.68.77.141 + address 77.68.77.150 + address 77.68.77.151 + address 77.68.77.156 + address 77.68.77.159 + address 77.68.77.171 + address 77.68.77.176 + address 77.68.77.178 + address 77.68.77.19 + address 77.68.77.190 + address 77.68.77.199 + address 77.68.77.200 + address 77.68.77.201 + address 77.68.77.203 + address 77.68.77.207 + address 77.68.77.211 + address 77.68.77.212 + address 77.68.77.22 + address 77.68.77.221 + address 77.68.77.227 + address 77.68.77.228 + address 77.68.77.236 + address 77.68.77.240 + address 77.68.77.243 + address 77.68.77.247 + address 77.68.77.253 + address 77.68.77.32 + address 77.68.77.33 + address 77.68.77.37 + address 77.68.77.49 + address 77.68.77.50 + address 77.68.77.53 + address 77.68.77.56 + address 77.68.77.63 + address 77.68.77.67 + address 77.68.77.68 + address 77.68.77.71 + address 77.68.77.77 + address 77.68.77.79 + address 77.68.77.81 + address 77.68.77.85 + address 77.68.77.88 + address 77.68.77.92 + address 77.68.77.97 + address 77.68.77.99 + address 77.68.76.76 + address 77.68.76.124 + address 77.68.76.211 + address 77.68.76.19 + address 77.68.76.110 + address 77.68.77.74 + address 77.68.77.192 + address 77.68.76.92 + address 77.68.76.165 + address 77.68.77.254 + address 77.68.77.157 + address 77.68.76.138 + address 77.68.76.139 + address 77.68.76.114 + address 77.68.76.244 + address 77.68.77.161 + address 77.68.77.38 + address 77.68.91.195 + address 77.68.17.26 + address 77.68.28.145 + address 109.228.56.185 + address 77.68.84.147 + address 77.68.12.195 + address 77.68.21.78 + address 77.68.5.187 + address 77.68.7.227 + address 77.68.4.24 + address 77.68.4.74 + address 77.68.6.202 + address 77.68.5.241 + address 77.68.4.39 + address 77.68.81.44 + address 77.68.90.106 + address 77.68.94.181 + address 77.68.30.164 + address 77.68.30.133 + address 77.68.4.136 + address 77.68.23.158 + address 77.68.24.112 + address 77.68.92.186 + address 77.68.20.161 + address 77.68.112.213 + address 77.68.26.216 + address 77.68.20.231 + address 77.68.118.17 + address 77.68.116.119 + address 77.68.116.220 + address 77.68.116.232 + address 77.68.76.142 + address 77.68.117.202 + address 77.68.7.172 + address 77.68.116.221 + address 77.68.89.183 + address 77.68.83.41 + address 77.68.86.40 + address 77.68.88.164 + address 109.228.56.26 + address 77.68.7.123 + address 77.68.112.248 + address 109.228.60.215 + address 77.68.7.186 + address 77.68.93.246 + address 77.68.84.155 + address 77.68.120.241 + address 77.68.121.106 + address 77.68.122.195 + address 77.68.122.89 + address 77.68.120.146 + address 77.68.122.241 + address 77.68.81.141 + address 77.68.116.52 + address 77.68.6.105 + address 77.68.76.229 + address 77.68.95.42 + address 77.68.28.207 + address 77.68.4.252 + address 77.68.17.186 + address 77.68.91.128 + address 77.68.22.146 + address 77.68.23.112 + address 77.68.24.220 + address 77.68.125.32 + address 77.68.12.250 + address 77.68.76.243 + address 77.68.72.202 + address 109.228.36.229 + address 109.228.40.207 + address 77.68.31.144 + address 77.68.2.215 + address 77.68.117.142 + address 77.68.5.166 + address 77.68.76.102 + address 109.228.37.174 + address 109.228.37.114 + address 109.228.37.240 + address 77.68.112.75 + address 77.68.76.30 + address 109.228.35.110 + address 77.68.77.160 + address 77.68.77.208 + address 77.68.76.152 + address 109.228.39.249 + address 77.68.76.77 + address 77.68.7.160 + address 109.228.40.226 + address 77.68.7.67 + address 77.68.113.117 + address 77.68.126.51 + address 77.68.75.113 + address 77.68.86.148 + address 77.68.114.183 + address 109.228.40.194 + address 77.68.76.31 + address 77.68.77.72 + address 77.68.90.132 + address 77.68.6.110 + address 77.68.77.26 + address 77.68.76.96 + address 77.68.77.30 + address 77.68.76.95 + address 77.68.10.170 + address 77.68.76.234 + address 77.68.120.26 + address 109.228.61.31 + address 77.68.76.59 + address 77.68.120.249 + address 77.68.6.210 + address 213.171.213.41 + address 77.68.77.248 + address 213.171.212.171 + address 77.68.4.22 + address 77.68.119.14 + address 213.171.215.184 + address 77.68.77.202 + address 77.68.25.146 + address 213.171.213.31 + address 77.68.78.229 + address 77.68.77.102 + address 213.171.210.19 + address 77.68.24.59 + address 213.171.213.97 + address 213.171.213.242 + address 77.68.77.205 + address 109.228.48.249 + address 109.228.40.195 + address 77.68.120.229 + address 77.68.127.172 + address 77.68.79.206 + address 77.68.123.250 + address 77.68.28.147 + address 213.171.212.172 + address 185.132.36.148 + address 213.171.208.58 + address 77.68.25.130 + address 109.228.56.242 + address 109.228.46.81 + address 185.132.38.95 + address 185.132.37.83 + address 77.68.117.51 + address 77.68.116.36 + address 77.68.120.45 + address 77.68.25.124 + address 213.171.210.59 + address 213.171.215.43 + address 213.171.215.252 + address 185.132.37.102 + address 109.228.42.232 + address 109.228.52.186 + address 77.68.9.186 + address 77.68.13.76 + address 109.228.36.194 + address 185.132.36.7 + address 185.132.36.24 + address 77.68.77.69 + address 185.132.39.129 + address 185.132.36.17 + address 109.228.40.222 + address 77.68.118.104 + address 77.68.120.31 + address 77.68.74.152 + address 185.132.39.37 + address 77.68.3.52 + address 77.68.87.212 + address 77.68.76.29 + address 77.68.119.188 + address 77.68.74.85 + address 77.68.91.22 + address 77.68.76.88 + address 77.68.4.242 + address 77.68.76.181 + address 77.68.76.161 + address 185.132.39.99 + address 77.68.95.212 + address 77.68.76.219 + address 77.68.27.27 + address 77.68.3.194 + address 77.68.3.144 + address 77.68.3.80 + address 77.68.27.28 + address 77.68.3.247 + address 77.68.3.161 + address 77.68.27.18 + address 77.68.3.121 + address 213.171.214.234 + address 109.228.38.201 + address 185.132.39.219 + address 77.68.28.139 + address 77.68.81.218 + address 77.68.4.111 + address 77.68.77.174 + address 77.68.117.222 + address 213.171.211.128 + address 77.68.5.155 + address 77.68.77.231 + address 213.171.214.167 + address 185.132.43.28 + address 213.171.213.42 + address 77.68.76.45 + address 77.68.92.92 + address 77.68.77.233 + address 185.132.40.56 + address 185.132.37.23 + address 77.68.117.29 + address 77.68.75.253 + address 77.68.11.140 + address 77.68.77.215 + address 77.68.20.217 + address 77.68.10.152 + address 77.68.73.73 + address 77.68.76.198 + address 77.68.77.214 + address 77.68.9.75 + address 213.171.210.177 + address 77.68.77.70 + address 77.68.77.149 + address 77.68.76.160 + address 185.132.38.114 + address 77.68.33.48 + address 185.132.40.90 + address 77.68.79.89 + address 77.68.34.28 + address 77.68.76.21 + address 77.68.75.45 + address 77.68.76.176 + address 77.68.77.95 + address 185.132.39.68 + address 185.132.43.164 + address 77.68.76.137 + address 185.132.40.152 + address 77.68.77.249 + address 77.68.24.134 + address 185.132.38.248 + address 77.68.32.43 + address 77.68.120.218 + address 77.68.112.167 + address 77.68.32.31 + address 77.68.32.118 + address 77.68.32.254 + address 77.68.80.26 + address 77.68.17.200 + address 77.68.80.97 + address 77.68.121.119 + address 77.68.74.209 + address 77.68.77.65 + address 185.132.43.6 + address 109.228.46.196 + address 185.132.43.98 + address 77.68.100.150 + address 185.132.41.148 + address 77.68.24.172 + address 77.68.33.197 + address 77.68.5.95 + address 77.68.23.64 + address 77.68.101.124 + address 77.68.5.125 + address 77.68.100.167 + address 77.68.4.80 + address 77.68.49.152 + address 109.228.59.247 + address 213.171.208.40 + address 77.68.112.184 + address 77.68.35.116 + address 185.132.40.11 + address 77.68.33.171 + address 77.68.76.111 + address 77.68.76.42 + address 77.68.77.120 + address 77.68.76.183 + address 77.68.118.86 + address 77.68.48.105 + address 77.68.48.81 + address 77.68.49.4 + address 109.228.36.119 + address 77.68.34.26 + address 77.68.115.17 + address 77.68.82.147 + address 77.68.49.12 + address 77.68.8.144 + address 77.68.51.202 + address 77.68.101.64 + address 77.68.103.19 + address 77.68.50.91 + address 77.68.24.63 + address 77.68.118.15 + address 77.68.50.198 + address 77.68.77.59 + address 77.68.49.160 + address 77.68.76.191 + address 77.68.126.101 + address 77.68.76.40 + address 77.68.77.42 + address 77.68.100.134 + address 77.68.100.132 + address 77.68.114.93 + address 185.132.36.60 + address 185.132.40.244 + address 77.68.85.18 + address 213.171.214.102 + address 77.68.50.193 + address 88.208.197.160 + address 88.208.197.10 + address 77.68.102.129 + address 109.228.36.79 + address 185.132.38.182 + address 185.132.41.240 + address 77.68.51.214 + address 88.208.196.123 + address 88.208.215.157 + address 77.68.126.22 + address 77.68.4.180 + address 213.171.212.90 + address 77.68.114.205 + address 185.132.43.71 + address 88.208.215.62 + address 77.68.77.114 + address 77.68.48.202 + address 77.68.112.175 + address 77.68.112.90 + address 185.132.40.166 + address 77.68.76.118 + address 77.68.103.120 + address 77.68.33.24 + address 109.228.58.134 + address 109.228.47.223 + address 77.68.31.96 + address 77.68.103.227 + address 213.171.212.203 + address 88.208.196.92 + address 88.208.196.154 + address 185.132.39.44 + address 77.68.76.248 + address 88.208.198.92 + address 109.228.36.37 + address 77.68.77.144 + address 77.68.126.14 + address 88.208.196.91 + address 77.68.100.77 + address 185.132.37.101 + address 77.68.87.164 + address 77.68.77.76 + address 77.68.76.120 + address 77.68.82.157 + address 77.68.93.164 + address 77.68.76.171 + address 88.208.197.135 + address 88.208.197.118 + address 88.208.197.150 + address 88.208.198.69 + address 77.68.34.139 + address 77.68.21.171 + address 88.208.197.60 + address 77.68.85.27 + address 109.228.37.10 + address 88.208.215.61 + address 88.208.199.249 + address 88.208.212.31 + address 109.228.53.243 + address 77.68.48.89 + address 88.208.212.188 + address 88.208.198.251 + address 77.68.76.112 + address 77.68.48.14 + address 88.208.215.19 + address 77.68.77.75 + address 77.68.117.173 + address 88.208.215.121 + address 109.228.39.41 + address 77.68.88.100 + address 77.68.76.108 + address 77.68.115.142 + address 77.68.33.216 + address 77.68.33.37 + address 77.68.50.90 + address 213.171.214.96 + address 88.208.198.66 + address 88.208.198.64 + address 77.68.3.61 + address 77.68.77.219 + address 77.68.77.204 + address 77.68.26.228 + address 77.68.74.232 + address 77.68.118.88 + address 77.68.77.46 + address 77.68.76.48 + address 77.68.76.202 + address 77.68.4.25 + address 77.68.7.114 + address 88.208.197.23 + address 77.68.114.237 + address 77.68.77.222 + address 77.68.112.83 + address 77.68.117.214 + address 88.208.199.141 + address 77.68.77.163 + address 185.132.39.109 + address 77.68.77.44 + address 185.132.37.47 + address 77.68.102.5 + address 77.68.16.247 + address 88.208.212.94 + address 77.68.72.254 + address 77.68.76.212 + address 77.68.77.152 + address 77.68.50.142 + address 88.208.199.46 + address 77.68.78.113 + address 88.208.212.182 + address 77.68.77.181 + address 77.68.15.95 + address 77.68.75.64 + address 213.171.212.71 + address 185.132.40.124 + address 88.208.197.208 + address 88.208.197.129 + address 77.68.76.60 + address 77.68.6.119 + address 77.68.77.185 + address 77.68.77.238 + address 77.68.27.57 + address 77.68.118.102 + address 77.68.79.82 + address 109.228.39.151 + } + address-group G-465-TCP { + address 172.16.255.254 + address 77.68.76.115 + address 77.68.76.141 + address 77.68.76.187 + address 77.68.76.197 + address 77.68.76.209 + address 77.68.76.99 + address 77.68.77.107 + address 77.68.77.129 + address 77.68.77.130 + address 77.68.77.141 + address 77.68.77.150 + address 77.68.77.171 + address 77.68.77.176 + address 77.68.77.190 + address 77.68.77.207 + address 77.68.77.22 + address 77.68.77.32 + address 77.68.77.33 + address 77.68.77.63 + address 77.68.77.92 + address 77.68.77.99 + address 77.68.77.77 + address 77.68.77.192 + address 77.68.84.147 + address 77.68.4.74 + address 77.68.6.202 + address 77.68.77.74 + address 77.68.77.100 + address 77.68.116.221 + address 109.228.60.215 + address 77.68.116.52 + address 77.68.7.172 + address 77.68.95.42 + address 77.68.91.128 + address 77.68.24.112 + address 109.228.37.114 + address 77.68.112.75 + address 77.68.7.67 + address 77.68.113.117 + address 77.68.126.51 + address 77.68.23.35 + address 77.68.10.170 + address 77.68.76.234 + address 213.171.213.31 + address 77.68.78.229 + address 213.171.210.19 + address 109.228.52.186 + address 77.68.77.69 + address 109.228.40.222 + address 77.68.87.212 + address 77.68.28.139 + address 77.68.4.111 + address 77.68.77.174 + address 77.68.117.222 + address 185.132.43.28 + address 77.68.77.214 + address 185.132.38.114 + address 77.68.33.48 + address 77.68.79.89 + address 77.68.76.21 + address 77.68.80.26 + address 77.68.5.95 + address 77.68.100.167 + address 77.68.4.80 + address 77.68.49.152 + address 77.68.112.184 + address 77.68.115.17 + address 77.68.82.147 + address 77.68.50.193 + address 88.208.215.61 + address 213.171.214.96 + address 88.208.198.66 + address 77.68.77.204 + address 77.68.123.177 + address 77.68.77.222 + address 77.68.112.83 + address 77.68.77.185 + address 77.68.79.82 + } + address-group G-587-TCP { + address 172.16.255.254 + address 77.68.76.141 + address 77.68.76.187 + address 77.68.76.197 + address 77.68.76.209 + address 77.68.77.128 + address 77.68.77.129 + address 77.68.77.141 + address 77.68.77.171 + address 77.68.77.190 + address 77.68.77.207 + address 77.68.77.32 + address 77.68.77.33 + address 77.68.77.63 + address 77.68.77.85 + address 77.68.77.92 + address 77.68.77.99 + address 77.68.77.77 + address 77.68.4.74 + address 77.68.6.202 + address 77.68.81.44 + address 77.68.77.100 + address 77.68.92.186 + address 77.68.116.119 + address 77.68.116.221 + address 77.68.120.241 + address 109.228.60.215 + address 77.68.122.241 + address 77.68.116.52 + address 77.68.91.128 + address 77.68.24.112 + address 77.68.77.107 + address 109.228.37.114 + address 77.68.112.75 + address 77.68.77.160 + address 77.68.113.117 + address 77.68.126.51 + address 77.68.23.35 + address 77.68.76.95 + address 77.68.10.170 + address 77.68.76.234 + address 213.171.213.41 + address 213.171.213.31 + address 77.68.78.229 + address 213.171.210.19 + address 109.228.52.186 + address 109.228.40.222 + address 77.68.87.212 + address 185.132.39.219 + address 77.68.28.139 + address 77.68.4.111 + address 77.68.77.174 + address 77.68.117.222 + address 185.132.43.28 + address 77.68.77.215 + address 77.68.77.214 + address 185.132.38.114 + address 77.68.33.48 + address 77.68.76.21 + address 77.68.100.167 + address 77.68.4.80 + address 77.68.49.152 + address 77.68.112.184 + address 77.68.115.17 + address 77.68.82.147 + address 77.68.76.191 + address 77.68.50.193 + address 77.68.77.114 + address 88.208.215.61 + address 77.68.76.112 + address 77.68.33.216 + address 77.68.33.37 + address 77.68.50.90 + address 88.208.198.66 + address 77.68.77.219 + address 77.68.123.177 + address 77.68.77.222 + address 77.68.112.83 + address 77.68.77.152 + address 77.68.79.82 + } + address-group G-993-TCP { + address 172.16.255.254 + address 77.68.76.115 + address 77.68.77.129 + address 77.68.77.130 + address 77.68.77.141 + address 77.68.77.150 + address 77.68.77.171 + address 77.68.77.176 + address 77.68.77.190 + address 77.68.77.207 + address 77.68.77.22 + address 77.68.77.33 + address 77.68.77.49 + address 77.68.77.56 + address 77.68.77.77 + address 77.68.77.192 + address 77.68.84.147 + address 77.68.4.74 + address 77.68.6.202 + address 77.68.81.44 + address 77.68.77.74 + address 77.68.77.100 + address 77.68.92.186 + address 77.68.116.119 + address 77.68.116.221 + address 77.68.120.241 + address 77.68.7.172 + address 77.68.91.128 + address 77.68.23.112 + address 77.68.24.112 + address 77.68.77.107 + address 109.228.37.114 + address 77.68.112.75 + address 77.68.7.67 + address 77.68.113.117 + address 77.68.126.51 + address 77.68.86.148 + address 77.68.23.35 + address 77.68.76.95 + address 213.171.215.184 + address 77.68.25.146 + address 213.171.213.31 + address 213.171.210.19 + address 77.68.79.206 + address 77.68.123.250 + address 77.68.77.69 + address 109.228.40.222 + address 77.68.87.212 + address 77.68.91.22 + address 185.132.39.99 + address 77.68.28.139 + address 77.68.4.111 + address 77.68.77.174 + address 77.68.117.222 + address 77.68.5.155 + address 185.132.43.28 + address 77.68.77.215 + address 77.68.10.152 + address 77.68.73.73 + address 77.68.77.214 + address 185.132.38.114 + address 77.68.33.48 + address 77.68.79.89 + address 77.68.5.95 + address 77.68.4.80 + address 77.68.49.152 + address 213.171.208.40 + address 77.68.115.17 + address 77.68.103.19 + address 185.132.36.60 + address 185.132.40.244 + address 88.208.197.10 + address 77.68.102.129 + address 88.208.215.157 + address 88.208.198.69 + address 88.208.212.188 + address 213.171.214.96 + address 88.208.198.66 + address 77.68.77.204 + address 77.68.74.232 + address 77.68.4.25 + address 77.68.7.114 + address 77.68.123.177 + address 77.68.77.222 + address 77.68.112.83 + address 77.68.79.82 + } + address-group G-995-TCP { + address 172.16.255.254 + address 77.68.76.115 + address 77.68.77.129 + address 77.68.77.171 + address 77.68.77.176 + address 77.68.77.190 + address 77.68.77.22 + address 77.68.77.33 + address 77.68.77.92 + address 77.68.77.77 + address 77.68.84.147 + address 77.68.4.74 + address 77.68.6.202 + address 77.68.77.74 + address 77.68.77.100 + address 77.68.116.221 + address 77.68.120.241 + address 77.68.7.172 + address 77.68.95.42 + address 77.68.91.128 + address 77.68.23.112 + address 77.68.24.112 + address 77.68.77.107 + address 109.228.37.114 + address 77.68.7.67 + address 77.68.126.51 + address 77.68.79.206 + address 77.68.123.250 + address 109.228.52.186 + address 109.228.40.222 + address 77.68.91.22 + address 77.68.4.111 + address 77.68.77.174 + address 77.68.5.155 + address 185.132.43.28 + address 77.68.77.214 + address 185.132.38.114 + address 77.68.79.89 + address 77.68.80.26 + address 77.68.4.80 + address 77.68.49.152 + address 77.68.103.19 + address 77.68.50.193 + address 88.208.197.10 + address 213.171.214.96 + address 88.208.198.66 + address 77.68.74.232 + address 77.68.4.25 + address 77.68.7.114 + address 77.68.77.185 + } + address-group G-1433-TCP { + address 172.16.255.254 + address 77.68.76.94 + address 77.68.30.164 + address 77.68.10.142 + address 77.68.77.95 + address 77.68.126.101 + address 77.68.76.118 + address 77.68.77.75 + } + address-group G-3306-TCP { + address 172.16.255.254 + address 77.68.76.127 + address 77.68.76.187 + address 77.68.76.252 + address 77.68.76.55 + address 77.68.76.80 + address 77.68.77.21 + address 77.68.77.63 + address 77.68.77.81 + address 77.68.77.85 + address 77.68.77.92 + address 77.68.76.241 + address 109.228.56.185 + address 77.68.28.145 + address 77.68.76.114 + address 77.68.17.26 + address 77.68.120.241 + address 77.68.6.32 + address 77.68.91.128 + address 109.228.37.114 + address 77.68.76.169 + address 77.68.76.77 + address 77.68.113.117 + address 77.68.86.148 + address 77.68.76.234 + address 77.68.76.59 + address 77.68.77.202 + address 77.68.28.147 + address 109.228.52.186 + address 77.68.117.222 + address 213.171.213.42 + address 77.68.75.253 + address 77.68.77.215 + address 77.68.79.89 + address 77.68.118.15 + address 109.228.36.79 + address 77.68.33.216 + address 77.68.33.37 + address 77.68.50.90 + address 77.68.76.48 + address 77.68.77.222 + address 77.68.112.83 + address 77.68.77.44 + address 88.208.212.94 + } + address-group G-3389-TCP { + address 172.16.255.254 + address 77.68.76.116 + address 77.68.76.150 + address 77.68.76.203 + address 77.68.76.220 + address 77.68.76.23 + address 77.68.76.241 + address 77.68.76.35 + address 77.68.76.39 + address 77.68.76.47 + address 77.68.76.49 + address 77.68.76.50 + address 77.68.76.58 + address 77.68.76.75 + address 77.68.76.91 + address 77.68.76.93 + address 77.68.76.94 + address 77.68.76.99 + address 77.68.77.115 + address 77.68.77.156 + address 77.68.77.178 + address 77.68.77.199 + address 77.68.77.236 + address 77.68.77.63 + address 77.68.77.71 + address 77.68.77.97 + address 77.68.77.99 + address 77.68.76.107 + address 77.68.76.26 + address 77.68.76.92 + address 77.68.77.38 + address 77.68.21.78 + address 77.68.94.181 + address 77.68.30.164 + address 77.68.23.158 + address 77.68.27.54 + address 77.68.76.142 + address 77.68.117.202 + address 77.68.116.220 + address 77.68.84.155 + address 77.68.120.146 + address 77.68.119.92 + address 77.68.10.142 + address 77.68.6.105 + address 77.68.4.252 + address 77.68.127.151 + address 77.68.77.228 + address 109.228.40.207 + address 77.68.77.24 + address 109.228.35.110 + address 77.68.76.152 + address 77.68.76.77 + address 77.68.113.117 + address 77.68.6.110 + address 77.68.76.96 + address 77.68.127.172 + address 185.132.37.83 + address 77.68.25.124 + address 77.68.3.52 + address 77.68.114.234 + address 77.68.85.73 + address 109.228.38.201 + address 77.68.26.221 + address 77.68.10.152 + address 77.68.73.73 + address 77.68.76.198 + address 77.68.9.75 + address 77.68.79.89 + address 77.68.77.95 + address 77.68.77.65 + address 77.68.100.150 + address 77.68.101.125 + address 77.68.101.124 + address 213.171.208.40 + address 77.68.12.45 + address 77.68.118.86 + address 77.68.77.59 + address 77.68.126.101 + address 213.171.214.102 + address 88.208.197.160 + address 88.208.215.157 + address 77.68.4.180 + address 185.132.43.71 + address 77.68.31.96 + address 109.228.36.37 + address 77.68.77.76 + address 77.68.82.157 + address 109.228.37.10 + address 77.68.77.75 + address 77.68.117.173 + address 88.208.215.121 + address 77.68.115.142 + address 77.68.33.216 + address 77.68.33.37 + address 77.68.50.90 + address 88.208.198.64 + address 77.68.118.88 + address 77.68.114.237 + address 77.68.50.142 + address 77.68.15.95 + address 77.68.75.64 + address 77.68.77.238 + } + address-group G-8080-TCP { + address 172.16.255.254 + address 77.68.76.57 + address 77.68.76.243 + address 77.68.28.145 + address 77.68.76.114 + address 77.68.76.157 + address 77.68.77.248 + address 77.68.77.202 + address 77.68.24.59 + address 77.68.81.218 + address 77.68.77.105 + address 185.132.40.152 + address 109.228.36.119 + address 77.68.121.127 + address 77.68.116.183 + address 77.68.34.139 + address 77.68.88.100 + address 77.68.77.222 + address 77.68.112.83 + address 77.68.77.163 + address 88.208.212.94 + address 77.68.78.113 + address 77.68.15.95 + address 213.171.212.71 + } + address-group G-8443-TCP { + address 172.16.255.254 + address 77.68.76.104 + address 77.68.76.105 + address 77.68.76.127 + address 77.68.76.136 + address 77.68.76.141 + address 77.68.76.148 + address 77.68.76.150 + address 77.68.76.158 + address 77.68.76.187 + address 77.68.76.195 + address 77.68.76.197 + address 77.68.76.20 + address 77.68.76.200 + address 77.68.76.209 + address 77.68.76.217 + address 77.68.76.22 + address 77.68.76.231 + address 77.68.76.235 + address 77.68.76.239 + address 77.68.76.245 + address 77.68.76.247 + address 77.68.76.249 + address 77.68.76.25 + address 77.68.76.251 + address 77.68.76.252 + address 77.68.76.33 + address 77.68.76.37 + address 77.68.76.57 + address 77.68.76.61 + address 77.68.76.74 + address 77.68.76.80 + address 77.68.76.93 + address 77.68.77.100 + address 77.68.77.103 + address 77.68.77.107 + address 77.68.77.108 + address 77.68.77.115 + address 77.68.77.117 + address 77.68.77.128 + address 77.68.77.130 + address 77.68.77.137 + address 77.68.77.139 + address 77.68.77.140 + address 77.68.77.141 + address 77.68.77.151 + address 77.68.77.159 + address 77.68.77.176 + address 77.68.77.190 + address 77.68.77.200 + address 77.68.77.201 + address 77.68.77.207 + address 77.68.77.211 + address 77.68.77.22 + address 77.68.77.227 + address 77.68.77.240 + address 77.68.77.247 + address 77.68.77.253 + address 77.68.77.32 + address 77.68.77.37 + address 77.68.77.49 + address 77.68.77.50 + address 77.68.77.56 + address 77.68.77.68 + address 77.68.77.81 + address 77.68.77.85 + address 77.68.77.88 + address 77.68.77.92 + address 77.68.77.99 + address 77.68.76.211 + address 77.68.76.19 + address 77.68.77.192 + address 77.68.77.254 + address 77.68.77.157 + address 77.68.76.138 + address 77.68.76.139 + address 77.68.76.243 + address 77.68.77.38 + address 77.68.77.62 + address 77.68.91.195 + address 77.68.17.26 + address 77.68.84.147 + address 109.228.56.185 + address 77.68.5.187 + address 77.68.4.24 + address 77.68.4.74 + address 77.68.6.202 + address 77.68.5.241 + address 77.68.77.74 + address 77.68.76.115 + address 77.68.81.44 + address 77.68.90.106 + address 77.68.94.181 + address 77.68.30.133 + address 77.68.4.136 + address 77.68.28.145 + address 77.68.24.112 + address 77.68.92.186 + address 77.68.26.216 + address 77.68.20.231 + address 77.68.118.17 + address 77.68.116.119 + address 77.68.76.142 + address 77.68.7.172 + address 77.68.116.221 + address 77.68.89.183 + address 77.68.83.41 + address 77.68.86.40 + address 77.68.88.164 + address 109.228.56.26 + address 77.68.7.123 + address 77.68.116.220 + address 109.228.60.215 + address 77.68.7.186 + address 77.68.93.246 + address 77.68.120.241 + address 77.68.122.195 + address 77.68.122.89 + address 77.68.81.141 + address 77.68.116.52 + address 77.68.6.105 + address 77.68.76.229 + address 77.68.4.252 + address 77.68.17.186 + address 77.68.91.128 + address 77.68.22.146 + address 77.68.125.32 + address 109.228.36.229 + address 77.68.31.144 + address 77.68.117.142 + address 109.228.37.174 + address 109.228.37.114 + address 77.68.76.169 + address 77.68.112.75 + address 77.68.77.160 + address 109.228.39.249 + address 77.68.7.67 + address 77.68.113.117 + address 77.68.126.51 + address 77.68.86.148 + address 77.68.114.183 + address 109.228.40.194 + address 77.68.90.132 + address 77.68.77.26 + address 77.68.76.96 + address 77.68.77.30 + address 77.68.76.95 + address 77.68.10.170 + address 77.68.120.26 + address 109.228.61.31 + address 77.68.76.59 + address 77.68.120.249 + address 213.171.213.41 + address 77.68.119.14 + address 213.171.215.184 + address 77.68.77.202 + address 77.68.25.146 + address 213.171.213.31 + address 77.68.77.102 + address 213.171.210.19 + address 213.171.213.97 + address 109.228.48.249 + address 109.228.40.195 + address 77.68.127.172 + address 77.68.79.206 + address 109.228.56.242 + address 109.228.46.81 + address 185.132.38.95 + address 77.68.116.36 + address 77.68.120.45 + address 185.132.37.102 + address 77.68.13.137 + address 109.228.36.194 + address 185.132.36.7 + address 185.132.36.24 + address 77.68.77.69 + address 185.132.39.129 + address 77.68.87.212 + address 77.68.76.29 + address 77.68.76.88 + address 77.68.76.181 + address 77.68.76.161 + address 77.68.85.73 + address 77.68.76.219 + address 109.228.38.201 + address 185.132.39.219 + address 77.68.28.139 + address 77.68.81.218 + address 77.68.4.111 + address 77.68.77.174 + address 77.68.117.222 + address 77.68.76.45 + address 185.132.40.56 + address 77.68.75.253 + address 77.68.10.152 + address 77.68.73.73 + address 77.68.77.214 + address 185.132.38.114 + address 185.132.40.90 + address 77.68.79.89 + address 77.68.76.21 + address 77.68.75.45 + address 77.68.24.134 + address 77.68.32.43 + address 77.68.80.26 + address 77.68.17.200 + address 77.68.80.97 + address 77.68.74.209 + address 77.68.77.65 + address 77.68.33.197 + address 77.68.5.95 + address 77.68.23.64 + address 77.68.5.125 + address 77.68.100.167 + address 77.68.4.80 + address 77.68.49.152 + address 77.68.48.105 + address 77.68.48.81 + address 77.68.49.12 + address 213.171.212.89 + address 77.68.76.44 + address 77.68.77.239 + address 77.68.77.59 + address 77.68.126.101 + address 77.68.76.40 + address 77.68.114.93 + address 77.68.50.193 + address 88.208.197.160 + address 109.228.36.79 + address 185.132.38.182 + address 88.208.196.123 + address 88.208.215.157 + address 77.68.76.118 + address 77.68.103.227 + address 88.208.196.92 + address 185.132.39.44 + address 88.208.198.92 + address 77.68.126.14 + address 88.208.196.91 + address 77.68.100.77 + address 185.132.37.101 + address 77.68.76.120 + address 213.171.212.114 + address 77.68.34.139 + address 88.208.215.61 + address 88.208.212.31 + address 109.228.53.243 + address 77.68.103.56 + address 213.171.214.96 + address 88.208.198.66 + address 77.68.77.219 + address 77.68.4.25 + address 77.68.7.114 + address 77.68.77.222 + address 77.68.112.83 + address 77.68.77.44 + address 77.68.72.254 + address 77.68.78.113 + address 213.171.212.71 + address 185.132.40.124 + address 88.208.197.208 + address 77.68.77.238 + address 77.68.79.82 + } + address-group G-8447-TCP { + address 172.16.255.254 + address 77.68.76.104 + address 77.68.76.105 + address 77.68.76.127 + address 77.68.76.136 + address 77.68.76.141 + address 77.68.76.148 + address 77.68.76.150 + address 77.68.76.158 + address 77.68.76.187 + address 77.68.76.195 + address 77.68.76.197 + address 77.68.76.20 + address 77.68.76.209 + address 77.68.76.22 + address 77.68.76.231 + address 77.68.76.235 + address 77.68.76.239 + address 77.68.76.245 + address 77.68.76.25 + address 77.68.76.252 + address 77.68.76.33 + address 77.68.76.37 + address 77.68.76.57 + address 77.68.76.61 + address 77.68.76.74 + address 77.68.76.93 + address 77.68.77.100 + address 77.68.77.103 + address 77.68.77.107 + address 77.68.77.108 + address 77.68.77.117 + address 77.68.77.128 + address 77.68.77.130 + address 77.68.77.137 + address 77.68.77.139 + address 77.68.77.141 + address 77.68.77.151 + address 77.68.77.159 + address 77.68.77.176 + address 77.68.77.190 + address 77.68.77.200 + address 77.68.77.201 + address 77.68.77.207 + address 77.68.77.211 + address 77.68.77.22 + address 77.68.77.227 + address 77.68.77.240 + address 77.68.77.247 + address 77.68.77.253 + address 77.68.77.32 + address 77.68.77.37 + address 77.68.77.49 + address 77.68.77.50 + address 77.68.77.56 + address 77.68.77.68 + address 77.68.77.81 + address 77.68.77.85 + address 77.68.77.88 + address 77.68.77.92 + address 77.68.77.99 + address 77.68.76.211 + address 77.68.76.19 + address 77.68.77.192 + address 77.68.77.254 + address 77.68.77.157 + address 77.68.76.138 + address 77.68.76.139 + address 77.68.91.195 + address 77.68.17.26 + address 109.228.56.185 + address 77.68.84.147 + address 77.68.5.187 + address 77.68.4.24 + address 77.68.4.74 + address 77.68.6.202 + address 77.68.5.241 + address 77.68.77.74 + address 77.68.81.44 + address 77.68.90.106 + address 77.68.94.181 + address 77.68.4.136 + address 77.68.28.145 + address 77.68.24.112 + address 77.68.92.186 + address 77.68.26.216 + address 77.68.20.231 + address 77.68.118.17 + address 77.68.116.119 + address 77.68.76.142 + address 77.68.7.172 + address 77.68.83.41 + address 77.68.116.221 + address 77.68.86.40 + address 77.68.88.164 + address 109.228.56.26 + address 77.68.7.123 + address 77.68.116.220 + address 109.228.60.215 + address 77.68.7.186 + address 77.68.93.246 + address 77.68.120.241 + address 77.68.122.195 + address 77.68.122.89 + address 77.68.81.141 + address 77.68.116.52 + address 77.68.6.105 + address 77.68.76.229 + address 77.68.4.252 + address 77.68.17.186 + address 77.68.91.128 + address 77.68.22.146 + address 77.68.125.32 + address 109.228.36.229 + address 77.68.31.144 + address 77.68.117.142 + address 109.228.37.174 + address 109.228.37.114 + address 77.68.112.75 + address 77.68.77.160 + address 109.228.39.249 + address 77.68.7.67 + address 77.68.113.117 + address 77.68.126.51 + address 77.68.86.148 + address 77.68.114.183 + address 109.228.40.194 + address 77.68.90.132 + address 77.68.76.96 + address 77.68.77.30 + address 77.68.76.95 + address 77.68.10.170 + address 109.228.61.31 + address 77.68.76.59 + address 77.68.120.249 + address 213.171.213.41 + address 213.171.215.184 + address 77.68.25.146 + address 213.171.213.31 + address 77.68.77.102 + address 213.171.210.19 + address 213.171.213.97 + address 109.228.48.249 + address 77.68.127.172 + address 77.68.79.206 + address 109.228.56.242 + address 109.228.46.81 + address 185.132.38.95 + address 77.68.116.36 + address 109.228.36.194 + address 185.132.36.7 + address 185.132.36.24 + address 77.68.77.69 + address 185.132.39.129 + address 77.68.87.212 + address 77.68.76.88 + address 77.68.76.181 + address 77.68.76.219 + address 185.132.39.219 + address 77.68.28.139 + address 77.68.4.111 + address 77.68.77.174 + address 77.68.117.222 + address 77.68.77.231 + address 77.68.76.45 + address 185.132.40.56 + address 77.68.10.152 + address 77.68.73.73 + address 77.68.77.214 + address 185.132.38.114 + address 185.132.40.90 + address 77.68.79.89 + address 77.68.76.21 + address 77.68.75.45 + address 77.68.24.134 + address 77.68.32.43 + address 77.68.80.26 + address 77.68.17.200 + address 77.68.80.97 + address 77.68.74.209 + address 77.68.33.197 + address 77.68.5.95 + address 77.68.5.125 + address 77.68.100.167 + address 77.68.4.80 + address 77.68.49.152 + address 77.68.48.105 + address 77.68.48.81 + address 77.68.49.12 + address 213.171.212.89 + address 77.68.76.44 + address 77.68.77.239 + address 77.68.77.59 + address 77.68.126.101 + address 77.68.114.93 + address 77.68.50.193 + address 88.208.197.160 + address 109.228.36.79 + address 185.132.38.182 + address 88.208.196.123 + address 88.208.215.157 + address 77.68.76.118 + address 77.68.103.227 + address 88.208.196.92 + address 185.132.39.44 + address 88.208.198.92 + address 77.68.126.14 + address 88.208.196.91 + address 77.68.100.77 + address 185.132.37.101 + address 77.68.76.120 + address 213.171.212.114 + address 77.68.34.139 + address 88.208.215.61 + address 88.208.212.31 + address 109.228.53.243 + address 77.68.103.56 + address 213.171.214.96 + address 88.208.198.66 + address 77.68.77.219 + address 77.68.77.204 + address 77.68.76.48 + address 77.68.4.25 + address 77.68.7.114 + address 77.68.77.222 + address 77.68.112.83 + address 77.68.72.254 + address 77.68.78.113 + address 213.171.212.71 + address 185.132.40.124 + address 88.208.197.208 + address 77.68.79.82 + } + address-group G-10000-TCP { + address 172.16.255.254 + address 77.68.76.177 + address 77.68.76.54 + address 77.68.30.133 + address 77.68.76.114 + address 77.68.11.140 + address 77.68.76.112 + address 77.68.78.113 + } + address-group LAN_ADDRESSES { + address 10.255.255.2 + address 10.255.255.3 + } + address-group MANAGEMENT_ADDRESSES { + address 82.223.200.175 + address 82.223.200.177 + } + address-group NAGIOS_PROBES { + address 77.68.76.16 + address 77.68.77.16 + } + address-group NAS_ARRAYS { + address 10.7.197.251 + address 10.7.197.252 + address 10.7.197.253 + address 10.7.197.254 + } + address-group NAS_DOMAIN_CONTROLLERS { + address 10.7.197.16 + address 10.7.197.17 + } + address-group NLB_ADDRESSES { + address 109.228.63.15 + address 109.228.63.16 + address 109.228.63.132 + address 109.228.63.133 + } + network-group NAS_NETWORKS { + network 10.7.197.0/24 + } + network-group RFC1918 { + network 10.0.0.0/8 + network 172.16.0.0/12 + network 192.168.0.0/16 + } + network-group TRANSFER_NETS { + network 109.228.63.128/25 + } + } + ipv6-receive-redirects disable + ipv6-src-route disable + ip-src-route disable + log-martians enable + name LAN-INBOUND { + default-action drop + rule 10 { + action drop + description "Anti-spoofing non-cluster addresses" + source { + group { + address-group !CLUSTER_ADDRESSES + } + } + } + rule 20 { + action drop + description "Drop traffic to datacenter transfer net" + destination { + group { + network-group TRANSFER_NETS + } + } + source { + group { + address-group CLUSTER_ADDRESSES + } + } + } + rule 400 { + action drop + description Anti-spoofing_10.255.255.2 + source { + address 10.255.255.2 + mac-address !00:50:56:af:61:20 + } + } + rule 401 { + action drop + description Anti-spoofing_77.68.126.51 + source { + address 77.68.126.51 + mac-address !00:50:56:03:df:06 + } + } + rule 402 { + action drop + description Anti-spoofing_109.228.36.37 + source { + address 109.228.36.37 + mac-address !00:50:56:38:c4:2c + } + } + rule 403 { + action drop + description Anti-spoofing_77.68.117.214 + source { + address 77.68.117.214 + mac-address !00:50:56:00:28:c3 + } + } + rule 404 { + action drop + description Anti-spoofing_77.68.127.172 + source { + address 77.68.127.172 + mac-address !00:50:56:08:ce:ec + } + } + rule 405 { + action drop + description Anti-spoofing_77.68.117.142 + source { + address 77.68.117.142 + mac-address !00:50:56:1a:02:40 + } + } + rule 406 { + action drop + description Anti-spoofing_77.68.14.88 + source { + address 77.68.14.88 + mac-address !00:50:56:3c:79:85 + } + } + rule 407 { + action drop + description Anti-spoofing_77.68.17.200 + source { + address 77.68.17.200 + mac-address !00:50:56:0c:1b:57 + } + } + rule 408 { + action drop + description Anti-spoofing_77.68.120.229 + source { + address 77.68.120.229 + mac-address !00:50:56:18:af:65 + } + } + rule 410 { + action drop + description Anti-spoofing_10.255.255.3 + source { + address 10.255.255.3 + mac-address !00:50:56:af:cd:42 + } + } + rule 411 { + action drop + description Anti-spoofing_77.68.4.242 + source { + address 77.68.4.242 + mac-address !00:50:56:25:d9:34 + } + } + rule 412 { + action drop + description Anti-spoofing_77.68.113.117 + source { + address 77.68.113.117 + mac-address !00:50:56:36:ea:1d + } + } + rule 413 { + action drop + description Anti-spoofing_213.171.213.242 + source { + address 213.171.213.242 + mac-address !00:50:56:29:dd:5c + } + } + rule 414 { + action drop + description Anti-spoofing_77.68.86.148 + source { + address 77.68.86.148 + mac-address !00:50:56:01:91:19 + } + } + rule 418 { + action drop + description Anti-spoofing_213.171.212.203 + source { + address 213.171.212.203 + mac-address !00:50:56:01:c3:39 + } + } + rule 419 { + action drop + description Anti-spoofing_77.68.114.234 + source { + address 77.68.114.234 + mac-address !00:50:56:1b:72:cd + } + } + rule 420 { + action drop + description Anti-spoofing_10.255.255.4 + source { + address 10.255.255.4 + mac-address !00:50:56:af:09:7d + } + } + rule 421 { + action drop + description Anti-spoofing_213.171.212.171 + source { + address 213.171.212.171 + mac-address !00:50:56:12:54:58 + } + } + rule 422 { + action drop + description Anti-spoofing_77.68.114.183 + source { + address 77.68.114.183 + mac-address !00:50:56:3d:9b:eb + } + } + rule 423 { + action drop + description Anti-spoofing_213.171.213.41 + source { + address 213.171.213.41 + mac-address !00:50:56:2a:ef:a2 + } + } + rule 424 { + action drop + description Anti-spoofing_77.68.90.132 + source { + address 77.68.90.132 + mac-address !00:50:56:28:04:1e + } + } + rule 425 { + action drop + description Anti-spoofing_10.255.255.5 + source { + address 10.255.255.5 + mac-address !00:50:56:af:3b:bb + } + } + rule 426 { + action drop + description Anti-spoofing_213.171.213.175 + source { + address 213.171.213.175 + mac-address !00:50:56:0d:d4:b1 + } + } + rule 427 { + action drop + description Anti-spoofing_109.228.39.151 + source { + address 109.228.39.151 + mac-address !00:50:56:39:67:8d + } + } + rule 428 { + action drop + description Anti-spoofing_77.68.112.167 + source { + address 77.68.112.167 + mac-address !00:50:56:32:24:c9 + } + } + rule 429 { + action drop + description Anti-spoofing_109.228.40.194 + source { + address 109.228.40.194 + mac-address !00:50:56:19:49:71 + } + } + rule 430 { + action drop + description Anti-spoofing_77.68.76.12 + source { + address 77.68.76.12 + mac-address !00:50:56:af:09:7d + } + } + rule 431 { + action drop + description Anti-spoofing_213.171.213.97 + source { + address 213.171.213.97 + mac-address !00:50:56:15:d9:89 + } + } + rule 432 { + action drop + description Anti-spoofing_77.68.16.247 + source { + address 77.68.16.247 + mac-address !00:50:56:01:49:07 + } + } + rule 433 { + action drop + description Anti-spoofing_77.68.33.48 + source { + address 77.68.33.48 + mac-address !00:50:56:11:0e:07 + } + } + rule 434 { + action drop + description Anti-spoofing_77.68.6.110 + source { + address 77.68.6.110 + mac-address !00:50:56:31:76:8a + } + } + rule 435 { + action drop + description Anti-spoofing_77.68.77.12 + source { + address 77.68.77.12 + mac-address !00:50:56:af:3b:bb + } + } + rule 436 { + action drop + description Anti-spoofing_213.171.215.252 + source { + address 213.171.215.252 + mac-address !00:50:56:11:88:0a + } + } + rule 437 { + action drop + description Anti-spoofing_88.208.197.208 + source { + address 88.208.197.208 + mac-address !00:50:56:1d:97:93 + } + } + rule 438 { + action drop + description Anti-spoofing_213.171.212.89 + source { + address 213.171.212.89 + mac-address !00:50:56:36:8d:bf + } + } + rule 439 { + action drop + description Anti-spoofing_77.68.93.125 + source { + address 77.68.93.125 + mac-address !00:50:56:19:f1:6f + } + } + rule 440 { + action drop + description Anti-spoofing_probe_77.68.76.16 + source { + address 77.68.76.16 + mac-address !00:50:56:aa:48:d4 + } + } + rule 441 { + action drop + description Anti-spoofing_213.171.214.96 + source { + address 213.171.214.96 + mac-address !00:50:56:0c:45:b5 + } + } + rule 442 { + action drop + description Anti-spoofing_77.68.76.176 + source { + address 77.68.76.176 + mac-address !00:50:56:2b:e6:f7 + } + } + rule 444 { + action drop + description Anti-spoofing_213.171.212.172 + source { + address 213.171.212.172 + mac-address !00:50:56:35:ab:43 + } + } + rule 446 { + action drop + description Anti-spoofing_185.132.38.95 + source { + address 185.132.38.95 + mac-address !00:50:56:07:a6:f7 + } + } + rule 447 { + action drop + description Anti-spoofing_185.132.38.248 + source { + address 185.132.38.248 + mac-address !00:50:56:19:e5:16 + } + } + rule 448 { + action drop + description Anti-spoofing_109.228.52.186 + source { + address 109.228.52.186 + mac-address !00:50:56:20:80:4f + } + } + rule 449 { + action drop + description Anti-spoofing_213.171.213.31 + source { + address 213.171.213.31 + mac-address !00:50:56:34:e3:61 + } + } + rule 450 { + action drop + description Anti-spoofing_probe_77.68.77.16 + source { + address 77.68.77.16 + mac-address !00:50:56:aa:4a:32 + } + } + rule 451 { + action drop + description Anti-spoofing_213.171.210.59 + source { + address 213.171.210.59 + mac-address !00:50:56:10:74:b6 + } + } + rule 452 { + action drop + description Anti-spoofing_185.132.36.7 + source { + address 185.132.36.7 + mac-address !00:50:56:17:24:16 + } + } + rule 453 { + action drop + description Anti-spoofing_213.171.212.71 + source { + address 213.171.212.71 + mac-address !00:50:56:1d:50:e0 + } + } + rule 454 { + action drop + description Anti-spoofing_213.171.208.58 + source { + address 213.171.208.58 + mac-address !00:50:56:05:1c:70 + } + } + rule 455 { + action drop + description Anti-spoofing_77.68.77.69 + source { + address 77.68.77.69 + mac-address !00:50:56:17:f9:d1 + } + } + rule 456 { + action drop + description Anti-spoofing_77.68.25.130 + source { + address 77.68.25.130 + mac-address !00:50:56:3c:92:ff + } + } + rule 457 { + action drop + description Anti-spoofing_213.171.215.184 + source { + address 213.171.215.184 + mac-address !00:50:56:18:84:ff + } + } + rule 458 { + action drop + description Anti-spoofing_77.68.74.39 + source { + address 77.68.74.39 + mac-address !00:50:56:0a:41:ee + } + } + rule 459 { + action drop + description Anti-spoofing_109.228.56.242 + source { + address 109.228.56.242 + mac-address !00:50:56:28:8c:ff + } + } + rule 460 { + action drop + description Anti-spoofing_77.68.76.13 + source { + address 77.68.76.13 + mac-address !00:50:56:8f:62:1e + } + } + rule 461 { + action drop + description Anti-spoofing_77.68.13.76 + source { + address 77.68.13.76 + mac-address !00:50:56:2c:c7:38 + } + } + rule 462 { + action drop + description Anti-spoofing_77.68.119.188 + source { + address 77.68.119.188 + mac-address !00:50:56:02:1c:16 + } + } + rule 463 { + action drop + description Anti-spoofing_109.228.46.81 + source { + address 109.228.46.81 + mac-address !00:50:56:31:1f:8a + } + } + rule 464 { + action drop + description Anti-spoofing_77.68.25.146 + source { + address 77.68.25.146 + mac-address !00:50:56:07:cc:76 + } + } + rule 465 { + action drop + description Anti-spoofing_77.68.76.14 + source { + address 77.68.76.14 + mac-address !00:50:56:8f:6a:24 + } + } + rule 466 { + action drop + description Anti-spoofing_77.68.116.36 + source { + address 77.68.116.36 + mac-address !00:50:56:1c:c9:83 + } + } + rule 467 { + action drop + description Anti-spoofing_185.132.43.113 + source { + address 185.132.43.113 + mac-address !00:50:56:22:79:ac + } + } + rule 468 { + action drop + description Anti-spoofing_213.171.210.19 + source { + address 213.171.210.19 + mac-address !00:50:56:32:6c:19 + } + } + rule 469 { + action drop + description Anti-spoofing_77.68.113.164 + source { + address 77.68.113.164 + mac-address !00:50:56:07:28:41 + } + } + rule 470 { + action drop + description Anti-spoofing_77.68.77.13 + source { + address 77.68.77.13 + mac-address !00:50:56:8f:62:1e + } + } + rule 471 { + action drop + description Anti-spoofing_213.171.211.128 + source { + address 213.171.211.128 + mac-address !00:50:56:37:b2:85 + } + } + rule 472 { + action drop + description Anti-spoofing_77.68.120.45 + source { + address 77.68.120.45 + mac-address !00:50:56:13:5e:ca + } + } + rule 473 { + action drop + description Anti-spoofing_77.68.25.124 + source { + address 77.68.25.124 + mac-address !00:50:56:2f:27:08 + } + } + rule 474 { + action drop + description Anti-spoofing_77.68.33.68 + source { + address 77.68.33.68 + mac-address !00:50:56:1c:96:48 + } + } + rule 475 { + action drop + description Anti-spoofing_77.68.77.14 + source { + address 77.68.77.14 + mac-address !00:50:56:8f:6a:24 + } + } + rule 476 { + action drop + description Anti-spoofing_109.228.48.249 + source { + address 109.228.48.249 + mac-address !00:50:56:06:32:ac + } + } + rule 477 { + action drop + description Anti-spoofing_109.228.40.195 + source { + address 109.228.40.195 + mac-address !00:50:56:21:46:3e + } + } + rule 478 { + action drop + description Anti-spoofing_213.171.215.43 + source { + address 213.171.215.43 + mac-address !00:50:56:24:c0:53 + } + } + rule 479 { + action drop + description Anti-spoofing_185.132.37.101 + source { + address 185.132.37.101 + mac-address !00:50:56:2c:08:73 + } + } + rule 480 { + action drop + description Anti-spoofing_109.228.53.243 + source { + address 109.228.53.243 + mac-address !00:50:56:31:d1:1a + } + } + rule 481 { + action drop + description Anti-spoofing_77.68.81.218 + source { + address 77.68.81.218 + mac-address !00:50:56:03:e1:62 + } + } + rule 482 { + action drop + description Anti-spoofing_77.68.102.5 + source { + address 77.68.102.5 + mac-address !00:50:56:12:a3:05 + } + } + rule 483 { + action drop + description Anti-spoofing_77.68.114.93 + source { + address 77.68.114.93 + mac-address !00:50:56:3c:d8:18 + } + } + rule 485 { + action drop + description Anti-spoofing_77.68.76.137 + source { + address 77.68.76.137 + mac-address !00:50:56:25:38:78 + } + } + rule 486 { + action drop + description Anti-spoofing_77.68.75.253 + source { + address 77.68.75.253 + mac-address !00:50:56:32:f9:d7 + } + } + rule 487 { + action drop + description Anti-spoofing_77.68.6.119 + source { + address 77.68.6.119 + mac-address !00:50:56:2a:06:e0 + } + } + rule 488 { + action drop + description Anti-spoofing_185.132.39.68 + source { + address 185.132.39.68 + mac-address !00:50:56:22:2e:b5 + } + } + rule 489 { + action drop + description Anti-spoofing_77.68.5.95 + source { + address 77.68.5.95 + mac-address !00:50:56:34:d6:94 + } + } + rule 490 { + action drop + description Anti-spoofing_109.228.36.194 + source { + address 109.228.36.194 + mac-address !00:50:56:02:d4:bb + } + } + rule 491 { + action drop + description Anti-spoofing_77.68.34.50 + source { + address 77.68.34.50 + mac-address !00:50:56:07:df:24 + } + } + rule 492 { + action drop + description Anti-spoofing_77.68.27.18 + source { + address 77.68.27.18 + mac-address !00:50:56:1c:9d:9e + } + } + rule 493 { + action drop + description Anti-spoofing_77.68.28.147 + source { + address 77.68.28.147 + mac-address !00:50:56:29:e0:70 + } + } + rule 494 { + action drop + description Anti-spoofing_77.68.123.250 + source { + address 77.68.123.250 + mac-address !00:50:56:0d:49:c0 + } + } + rule 495 { + action drop + description Anti-spoofing_185.132.39.129 + source { + address 185.132.39.129 + mac-address !00:50:56:29:5a:4c + } + } + rule 496 { + action drop + description Anti-spoofing_185.132.36.24 + source { + address 185.132.36.24 + mac-address !00:50:56:12:df:2d + } + } + rule 497 { + action drop + description Anti-spoofing_185.132.38.114 + source { + address 185.132.38.114 + mac-address !00:50:56:1d:ce:df + } + } + rule 498 { + action drop + description Anti-spoofing_185.132.36.148 + source { + address 185.132.36.148 + mac-address !00:50:56:04:d1:7e + } + } + rule 499 { + action drop + description Anti-spoofing_185.132.36.142 + source { + address 185.132.36.142 + mac-address !00:50:56:13:22:d1 + } + } + rule 500 { + action drop + description Anti-spoofing_77.68.77.67 + source { + address 77.68.77.67 + mac-address !00:50:56:26:3e:0a + } + } + rule 501 { + action drop + description Anti-spoofing_185.132.39.44 + source { + address 185.132.39.44 + mac-address !00:50:56:32:a0:22 + } + } + rule 502 { + action drop + description Anti-spoofing_77.68.76.114 + source { + address 77.68.76.114 + mac-address !00:50:56:32:42:42 + } + } + rule 503 { + action drop + description Anti-spoofing_77.68.77.103 + source { + address 77.68.77.103 + mac-address !00:50:56:1e:6d:9b + } + } + rule 504 { + action drop + description Anti-spoofing_77.68.77.130 + source { + address 77.68.77.130 + mac-address !00:50:56:24:79:76 + } + } + rule 505 { + action drop + description Anti-spoofing_77.68.76.245 + source { + address 77.68.76.245 + mac-address !00:50:56:1d:0f:83 + } + } + rule 506 { + action drop + description Anti-spoofing_77.68.118.17 + source { + address 77.68.118.17 + mac-address !00:50:56:18:d3:d1 + } + } + rule 507 { + action drop + description Anti-spoofing_77.68.79.82 + source { + address 77.68.79.82 + mac-address !00:50:56:22:e9:9e + } + } + rule 509 { + action drop + description Anti-spoofing_77.68.77.85 + source { + address 77.68.77.85 + mac-address !00:50:56:1d:40:33 + } + } + rule 510 { + action drop + description Anti-spoofing_77.68.76.45 + source { + address 77.68.76.45 + mac-address !00:50:56:18:dc:fe + } + } + rule 511 { + action drop + description Anti-spoofing_77.68.77.144 + source { + address 77.68.77.144 + mac-address !00:50:56:3c:9a:1a + } + } + rule 512 { + action drop + description Anti-spoofing_77.68.77.105 + source { + address 77.68.77.105 + mac-address !00:50:56:1f:f9:c9 + } + } + rule 513 { + action drop + description Anti-spoofing_77.68.12.250 + source { + address 77.68.12.250 + mac-address !00:50:56:3e:06:ca + } + } + rule 514 { + action drop + description Anti-spoofing_77.68.76.76 + source { + address 77.68.76.76 + mac-address !00:50:56:03:1f:db + } + } + rule 515 { + action drop + description Anti-spoofing_185.132.36.17 + source { + address 185.132.36.17 + mac-address !00:50:56:36:7a:94 + } + } + rule 516 { + action drop + description Anti-spoofing_77.68.76.122 + source { + address 77.68.76.122 + mac-address !00:50:56:20:3d:43 + } + } + rule 517 { + action drop + description Anti-spoofing_77.68.76.104 + source { + address 77.68.76.104 + mac-address !00:50:56:3c:80:ff + } + } + rule 518 { + action drop + description Anti-spoofing_77.68.114.136 + source { + address 77.68.114.136 + mac-address !00:50:56:38:34:6e + } + } + rule 519 { + action drop + description Anti-spoofing_77.68.77.115 + source { + address 77.68.77.115 + mac-address !00:50:56:2c:ad:ee + } + } + rule 520 { + action drop + description Anti-spoofing_77.68.77.178 + source { + address 77.68.77.178 + mac-address !00:50:56:14:c1:42 + } + } + rule 521 { + action drop + description Anti-spoofing_77.68.76.239 + source { + address 77.68.76.239 + mac-address !00:50:56:0d:5a:47 + } + } + rule 522 { + action drop + description Anti-spoofing_77.68.87.164 + source { + address 77.68.87.164 + mac-address !00:50:56:11:19:46 + } + } + rule 523 { + action drop + description Anti-spoofing_77.68.15.95 + source { + address 77.68.15.95 + mac-address !00:50:56:16:04:4e + } + } + rule 524 { + action drop + description Anti-spoofing_77.68.4.39 + source { + address 77.68.4.39 + mac-address !00:50:56:06:57:b6 + } + } + rule 525 { + action drop + description Anti-spoofing_77.68.76.30 + source { + address 77.68.76.30 + mac-address !00:50:56:25:b8:e3 + } + } + rule 526 { + action drop + description Anti-spoofing_77.68.77.249 + source { + address 77.68.77.249 + mac-address !00:50:56:36:5f:b3 + } + } + rule 527 { + action drop + description Anti-spoofing_77.68.76.59 + source { + address 77.68.76.59 + mac-address !00:50:56:06:e8:bb + } + } + rule 528 { + action drop + description Anti-spoofing_77.68.8.144 + source { + address 77.68.8.144 + mac-address !00:50:56:28:58:e5 + } + } + rule 529 { + action drop + description Anti-spoofing_77.68.77.44 + source { + address 77.68.77.44 + mac-address !00:50:56:31:c0:9d + } + } + rule 530 { + action drop + description Anti-spoofing_77.68.77.200 + source { + address 77.68.77.200 + mac-address !00:50:56:15:2e:a4 + } + } + rule 531 { + action drop + description Anti-spoofing_77.68.77.228 + source { + address 77.68.77.228 + mac-address !00:50:56:23:e4:44 + } + } + rule 532 { + action drop + description Anti-spoofing_77.68.4.25 + source { + address 77.68.4.25 + mac-address !00:50:56:33:0d:5e + } + } + rule 534 { + action drop + description Anti-spoofing_77.68.76.191 + source { + address 77.68.76.191 + mac-address !00:50:56:10:72:7c + } + } + rule 535 { + action drop + description Anti-spoofing_77.68.117.29 + source { + address 77.68.117.29 + mac-address !00:50:56:0c:e4:e3 + } + } + rule 536 { + action drop + description Anti-spoofing_213.171.212.90 + source { + address 213.171.212.90 + mac-address !00:50:56:35:fc:da + } + } + rule 537 { + action drop + description Anti-spoofing_77.68.76.102 + source { + address 77.68.76.102 + mac-address !00:50:56:35:87:43 + } + } + rule 538 { + action drop + description Anti-spoofing_185.132.39.37 + source { + address 185.132.39.37 + mac-address !00:50:56:21:72:64 + } + } + rule 539 { + action drop + description Anti-spoofing_185.132.38.142 + source { + address 185.132.38.142 + mac-address !00:50:56:09:e8:30 + } + } + rule 540 { + action drop + description Anti-spoofing_77.68.77.26 + source { + address 77.68.77.26 + mac-address !00:50:56:10:ec:c2 + } + } + rule 541 { + action drop + description Anti-spoofing_77.68.76.152 + source { + address 77.68.76.152 + mac-address !00:50:56:2b:79:48 + } + } + rule 542 { + action drop + description Anti-spoofing_185.132.37.83 + source { + address 185.132.37.83 + mac-address !00:50:56:09:b3:41 + } + } + rule 543 { + action drop + description Anti-spoofing_77.68.77.212 + source { + address 77.68.77.212 + mac-address !00:50:56:07:ab:f2 + } + } + rule 544 { + action drop + description Anti-spoofing_77.68.75.64 + source { + address 77.68.75.64 + mac-address !00:50:56:07:e2:85 + } + } + rule 546 { + action drop + description Anti-spoofing_77.68.85.73 + source { + address 77.68.85.73 + mac-address !00:50:56:14:68:9c + } + } + rule 547 { + action drop + description Anti-spoofing_77.68.116.119 + source { + address 77.68.116.119 + mac-address !00:50:56:0f:68:91 + } + } + rule 548 { + action drop + description Anti-spoofing_77.68.76.142 + source { + address 77.68.76.142 + mac-address !50:9a:4c:74:07:ea + } + } + rule 549 { + action drop + description Anti-spoofing_77.68.76.211 + source { + address 77.68.76.211 + mac-address !00:50:56:18:9d:15 + } + } + rule 550 { + action drop + description Anti-spoofing_77.68.76.60 + source { + address 77.68.76.60 + mac-address !00:50:56:2b:07:02 + } + } + rule 551 { + action drop + description Anti-spoofing_77.68.77.253 + source { + address 77.68.77.253 + mac-address !00:50:56:30:a5:77 + } + } + rule 552 { + action drop + description Anti-spoofing_77.68.75.245 + source { + address 77.68.75.245 + mac-address !00:50:56:12:00:e9 + } + } + rule 553 { + action drop + description Anti-spoofing_185.132.37.102 + source { + address 185.132.37.102 + mac-address !00:50:56:3d:ae:26 + } + } + rule 554 { + action drop + description Anti-spoofing_77.68.120.31 + source { + address 77.68.120.31 + mac-address !00:50:56:1f:29:84 + } + } + rule 555 { + action drop + description Anti-spoofing_77.68.76.54 + source { + address 77.68.76.54 + mac-address !00:50:56:30:b4:74 + } + } + rule 556 { + action drop + description Anti-spoofing_88.208.196.154 + source { + address 88.208.196.154 + mac-address !00:50:56:14:6f:a8 + } + } + rule 557 { + action drop + description Anti-spoofing_185.132.40.152 + source { + address 185.132.40.152 + mac-address !00:50:56:24:25:3c + } + } + rule 558 { + action drop + description Anti-spoofing_77.68.76.33 + source { + address 77.68.76.33 + mac-address !00:50:56:3c:9b:bc + } + } + rule 559 { + action drop + description Anti-spoofing_77.68.12.195 + source { + address 77.68.12.195 + mac-address !00:50:56:3d:52:1a + } + } + rule 560 { + action drop + description Anti-spoofing_77.68.77.114 + source { + address 77.68.77.114 + mac-address !00:50:56:06:80:89 + } + } + rule 561 { + action drop + description Anti-spoofing_77.68.77.176 + source { + address 77.68.77.176 + mac-address !00:50:56:3e:2b:da + } + } + rule 562 { + action drop + description Anti-spoofing_109.228.40.222 + source { + address 109.228.40.222 + mac-address !00:50:56:0a:dc:63 + } + } + rule 563 { + action drop + description Anti-spoofing_77.68.77.219 + source { + address 77.68.77.219 + mac-address !00:50:56:13:82:67 + } + } + rule 564 { + action drop + description Anti-spoofing_77.68.77.19 + source { + address 77.68.77.19 + mac-address !00:50:56:36:e3:b1 + } + } + rule 565 { + action drop + description Anti-spoofing_77.68.74.85 + source { + address 77.68.74.85 + mac-address !00:50:56:13:b7:2d + } + } + rule 566 { + action drop + description Anti-spoofing_77.68.116.221 + source { + address 77.68.116.221 + mac-address !00:50:56:24:67:bd + } + } + rule 567 { + action drop + description Anti-spoofing_77.68.77.22 + source { + address 77.68.77.22 + mac-address !00:50:56:07:09:ae + } + } + rule 568 { + action drop + description Anti-spoofing_77.68.112.184 + source { + address 77.68.112.184 + mac-address !00:50:56:2a:db:d3 + } + } + rule 569 { + action drop + description Anti-spoofing_77.68.77.248 + source { + address 77.68.77.248 + mac-address !00:50:56:18:03:92 + } + } + rule 570 { + action drop + description Anti-spoofing_77.68.76.161 + source { + address 77.68.76.161 + mac-address !00:50:56:34:57:75 + } + } + rule 571 { + action drop + description Anti-spoofing_77.68.77.56 + source { + address 77.68.77.56 + mac-address !00:50:56:38:22:ae + } + } + rule 572 { + action drop + description Anti-spoofing_77.68.77.129 + source { + address 77.68.77.129 + mac-address !00:50:56:08:d9:20 + } + } + rule 573 { + action drop + description Anti-spoofing_77.68.77.205 + source { + address 77.68.77.205 + mac-address !00:50:56:35:f1:c3 + } + } + rule 574 { + action drop + description Anti-spoofing_77.68.77.140 + source { + address 77.68.77.140 + mac-address !00:50:56:1b:2d:c7 + } + } + rule 575 { + action drop + description Anti-spoofing_77.68.120.146 + source { + address 77.68.120.146 + mac-address !00:50:56:0d:fb:7b + } + } + rule 576 { + action drop + description Anti-spoofing_77.68.78.73 + source { + address 77.68.78.73 + mac-address !00:50:56:14:4b:f4 + } + } + rule 577 { + action drop + description Anti-spoofing_77.68.76.177 + source { + address 77.68.76.177 + mac-address !00:50:56:26:ac:11 + } + } + rule 578 { + action drop + description Anti-spoofing_77.68.77.117 + source { + address 77.68.77.117 + mac-address !00:50:56:09:4d:ce + } + } + rule 579 { + action drop + description Anti-spoofing_77.68.77.108 + source { + address 77.68.77.108 + mac-address !00:50:56:3a:b7:59 + } + } + rule 580 { + action drop + description Anti-spoofing_77.68.7.222 + source { + address 77.68.7.222 + mac-address !00:50:56:36:cc:37 + } + } + rule 581 { + action drop + description Anti-spoofing_77.68.76.50 + source { + address 77.68.76.50 + mac-address !00:50:56:34:78:88 + } + } + rule 582 { + action drop + description Anti-spoofing_77.68.77.192 + source { + address 77.68.77.192 + mac-address !00:50:56:0f:eb:a4 + } + } + rule 583 { + action drop + description Anti-spoofing_77.68.76.217 + source { + address 77.68.76.217 + mac-address !00:50:56:29:6d:a9 + } + } + rule 584 { + action drop + description Anti-spoofing_77.68.92.186 + source { + address 77.68.92.186 + mac-address !00:50:56:08:8b:d0 + } + } + rule 585 { + action drop + description Anti-spoofing_77.68.76.165 + source { + address 77.68.76.165 + mac-address !00:50:56:19:74:17 + } + } + rule 586 { + action drop + description Anti-spoofing_77.68.91.22 + source { + address 77.68.91.22 + mac-address !00:50:56:2e:2c:cb + } + } + rule 587 { + action drop + description Anti-spoofing_77.68.77.160 + source { + address 77.68.77.160 + mac-address !00:50:56:27:75:65 + } + } + rule 588 { + action drop + description Anti-spoofing_77.68.77.30 + source { + address 77.68.77.30 + mac-address !00:50:56:3b:95:8f + } + } + rule 589 { + action drop + description Anti-spoofing_77.68.77.21 + source { + address 77.68.77.21 + mac-address !00:50:56:34:cd:82 + } + } + rule 590 { + action drop + description Anti-spoofing_77.68.76.29 + source { + address 77.68.76.29 + mac-address !00:50:56:2f:a3:ef + } + } + rule 591 { + action drop + description Anti-spoofing_213.171.212.136 + source { + address 213.171.212.136 + mac-address !00:50:56:19:fb:be + } + } + rule 592 { + action drop + description Anti-spoofing_77.68.76.158 + source { + address 77.68.76.158 + mac-address !00:50:56:36:97:69 + } + } + rule 593 { + action drop + description Anti-spoofing_77.68.76.203 + source { + address 77.68.76.203 + mac-address !00:50:56:2f:48:47 + } + } + rule 594 { + action drop + description Anti-spoofing_77.68.77.243 + source { + address 77.68.77.243 + mac-address !00:50:56:20:1f:c4 + } + } + rule 595 { + action drop + description Anti-spoofing_77.68.77.54 + source { + address 77.68.77.54 + mac-address !00:50:56:0e:da:e1 + } + } + rule 596 { + action drop + description Anti-spoofing_77.68.76.22 + source { + address 77.68.76.22 + mac-address !00:50:56:1b:a3:e6 + } + } + rule 597 { + action drop + description Anti-spoofing_77.68.103.120 + source { + address 77.68.103.120 + mac-address !00:50:56:1f:cb:8e + } + } + rule 598 { + action drop + description Anti-spoofing_109.228.37.174 + source { + address 109.228.37.174 + mac-address !00:50:56:1d:0f:a0 + } + } + rule 599 { + action drop + description Anti-spoofing_77.68.17.26 + source { + address 77.68.17.26 + mac-address !00:50:56:13:4a:e1 + } + } + rule 600 { + action drop + description Anti-spoofing_77.68.76.25 + source { + address 77.68.76.25 + mac-address !00:50:56:1f:54:d9 + } + } + rule 601 { + action drop + description Anti-spoofing_77.68.76.21 + source { + address 77.68.76.21 + mac-address !00:50:56:15:a8:33 + } + } + rule 602 { + action drop + description Anti-spoofing_77.68.77.221 + source { + address 77.68.77.221 + mac-address !00:50:56:06:2a:ae + } + } + rule 603 { + action drop + description Anti-spoofing_77.68.77.76 + source { + address 77.68.77.76 + mac-address !00:50:56:18:01:78 + } + } + rule 604 { + action drop + description Anti-spoofing_77.68.76.127 + source { + address 77.68.76.127 + mac-address !00:50:56:24:a4:85 + } + } + rule 605 { + action drop + description Anti-spoofing_77.68.77.139 + source { + address 77.68.77.139 + mac-address !00:50:56:3b:1e:be + } + } + rule 606 { + action drop + description Anti-spoofing_77.68.77.240 + source { + address 77.68.77.240 + mac-address !00:50:56:2b:d5:dd + } + } + rule 607 { + action drop + description Anti-spoofing_185.132.38.216 + source { + address 185.132.38.216 + mac-address !00:50:56:26:a7:47 + } + } + rule 608 { + action drop + description Anti-spoofing_77.68.76.39 + source { + address 77.68.76.39 + mac-address !00:50:56:1e:0d:c1 + } + } + rule 609 { + action drop + description Anti-spoofing_77.68.76.149 + source { + address 77.68.76.149 + mac-address !00:50:56:32:30:e7 + } + } + rule 610 { + action drop + description Anti-spoofing_77.68.77.57 + source { + address 77.68.77.57 + mac-address !00:50:56:26:33:75 + } + } + rule 611 { + action drop + description Anti-spoofing_77.68.77.185 + source { + address 77.68.77.185 + mac-address !00:50:56:22:72:c9 + } + } + rule 612 { + action drop + description Anti-spoofing_77.68.76.116 + source { + address 77.68.76.116 + mac-address !00:50:56:09:f2:df + } + } + rule 613 { + action drop + description Anti-spoofing_77.68.95.212 + source { + address 77.68.95.212 + mac-address !00:50:56:21:4b:e6 + } + } + rule 614 { + action drop + description Anti-spoofing_77.68.76.160 + source { + address 77.68.76.160 + mac-address !00:50:56:3a:fa:b3 + } + } + rule 615 { + action drop + description Anti-spoofing_77.68.77.70 + source { + address 77.68.77.70 + mac-address !00:50:56:37:9d:47 + } + } + rule 616 { + action drop + description Anti-spoofing_77.68.77.149 + source { + address 77.68.77.149 + mac-address !00:50:56:2c:f8:51 + } + } + rule 617 { + action drop + description Anti-spoofing_77.68.76.57 + source { + address 77.68.76.57 + mac-address !00:50:56:32:d9:0f + } + } + rule 618 { + action drop + description Anti-spoofing_77.68.76.115 + source { + address 77.68.76.115 + mac-address !00:50:56:09:67:90 + } + } + rule 619 { + action drop + description Anti-spoofing_185.132.41.72 + source { + address 185.132.41.72 + mac-address !00:50:56:2b:aa:79 + } + } + rule 620 { + action drop + description Anti-spoofing_77.68.84.155 + source { + address 77.68.84.155 + mac-address !00:50:56:05:52:76 + } + } + rule 621 { + action drop + description Anti-spoofing_77.68.76.200 + source { + address 77.68.76.200 + mac-address !00:50:56:00:5f:48 + } + } + rule 622 { + action drop + description Anti-spoofing_77.68.76.23 + source { + address 77.68.76.23 + mac-address !00:50:56:27:eb:9b + } + } + rule 623 { + action drop + description Anti-spoofing_77.68.77.46 + source { + address 77.68.77.46 + mac-address !00:50:56:22:73:37 + } + } + rule 624 { + action drop + description Anti-spoofing_77.68.91.195 + source { + address 77.68.91.195 + mac-address !00:50:56:09:f1:74 + } + } + rule 625 { + action drop + description Anti-spoofing_77.68.76.198 + source { + address 77.68.76.198 + mac-address !00:50:56:05:4b:16 + } + } + rule 626 { + action drop + description Anti-spoofing_77.68.77.141 + source { + address 77.68.77.141 + mac-address !00:50:56:0c:04:05 + } + } + rule 627 { + action drop + description Anti-spoofing_77.68.77.50 + source { + address 77.68.77.50 + mac-address !00:50:56:2d:5b:c6 + } + } + rule 628 { + action drop + description Anti-spoofing_77.68.77.128 + source { + address 77.68.77.128 + mac-address !00:50:56:27:0f:74 + } + } + rule 629 { + action drop + description Anti-spoofing_77.68.115.142 + source { + address 77.68.115.142 + mac-address !00:50:56:1b:e1:25 + } + } + rule 630 { + action drop + description Anti-spoofing_77.68.77.88 + source { + address 77.68.77.88 + mac-address !00:50:56:2b:db:7e + } + } + rule 631 { + action drop + description Anti-spoofing_77.68.4.74 + source { + address 77.68.4.74 + mac-address !00:50:56:0f:22:a5 + } + } + rule 632 { + action drop + description Anti-spoofing_77.68.76.80 + source { + address 77.68.76.80 + mac-address !00:50:56:1f:17:01 + } + } + rule 633 { + action drop + description Anti-spoofing_77.68.76.35 + source { + address 77.68.76.35 + mac-address !00:50:56:30:e3:a1 + } + } + rule 634 { + action drop + description Anti-spoofing_77.68.77.204 + source { + address 77.68.77.204 + mac-address !00:50:56:23:70:3a + } + } + rule 635 { + action drop + description Anti-spoofing_77.68.77.201 + source { + address 77.68.77.201 + mac-address !50:9a:4c:74:06:06 + } + } + rule 636 { + action drop + description Anti-spoofing_77.68.77.97 + source { + address 77.68.77.97 + mac-address !00:50:56:2f:48:47 + } + } + rule 637 { + action drop + description Anti-spoofing_77.68.76.195 + source { + address 77.68.76.195 + mac-address !00:50:56:14:c5:49 + } + } + rule 638 { + action drop + description Anti-spoofing_77.68.76.202 + source { + address 77.68.76.202 + mac-address !00:50:56:07:3c:3c + } + } + rule 640 { + action drop + description Anti-spoofing_77.68.76.157 + source { + address 77.68.76.157 + mac-address !00:50:56:35:c8:20 + } + } + rule 641 { + action drop + description Anti-spoofing_213.171.212.114 + source { + address 213.171.212.114 + mac-address !00:50:56:11:7f:32 + } + } + rule 642 { + action drop + description Anti-spoofing_77.68.77.159 + source { + address 77.68.77.159 + mac-address !00:50:56:14:d8:f0 + } + } + rule 643 { + action drop + description Anti-spoofing_213.171.214.234 + source { + address 213.171.214.234 + mac-address !00:50:56:29:94:38 + } + } + rule 644 { + action drop + description Anti-spoofing_77.68.76.48 + source { + address 77.68.76.48 + mac-address !00:50:56:33:38:d6 + } + } + rule 645 { + action drop + description Anti-spoofing_77.68.76.118 + source { + address 77.68.76.118 + mac-address !00:50:56:1c:cd:d3 + } + } + rule 646 { + action drop + description Anti-spoofing_77.68.76.38 + source { + address 77.68.76.38 + mac-address !00:50:56:01:59:2a + } + } + rule 647 { + action drop + description Anti-spoofing_77.68.31.144 + source { + address 77.68.31.144 + mac-address !00:50:56:01:89:fb + } + } + rule 648 { + action drop + description Anti-spoofing_77.68.23.35 + source { + address 77.68.23.35 + mac-address !00:50:56:3b:1f:ee + } + } + rule 649 { + action drop + description Anti-spoofing_77.68.4.80 + source { + address 77.68.4.80 + mac-address !00:50:56:1a:06:95 + } + } + rule 650 { + action drop + description Anti-spoofing_77.68.127.151 + source { + address 77.68.127.151 + mac-address !00:50:56:32:48:a6 + } + } + rule 651 { + action drop + description Anti-spoofing_77.68.77.203 + source { + address 77.68.77.203 + mac-address !00:50:56:11:05:40 + } + } + rule 652 { + action drop + description Anti-spoofing_77.68.77.233 + source { + address 77.68.77.233 + mac-address !00:50:56:37:0e:b3 + } + } + rule 653 { + action drop + description Anti-spoofing_77.68.77.163 + source { + address 77.68.77.163 + mac-address !00:50:56:08:a3:b4 + } + } + rule 654 { + action drop + description Anti-spoofing_77.68.77.49 + source { + address 77.68.77.49 + mac-address !00:50:56:03:ba:26 + } + } + rule 655 { + action drop + description Anti-spoofing_77.68.76.58 + source { + address 77.68.76.58 + mac-address !00:50:56:03:bd:d2 + } + } + rule 656 { + action drop + description Anti-spoofing_77.68.77.171 + source { + address 77.68.77.171 + mac-address !00:50:56:22:3d:21 + } + } + rule 657 { + action drop + description Anti-spoofing_77.68.116.220 + source { + address 77.68.116.220 + mac-address !00:50:56:2e:06:02 + } + } + rule 658 { + action drop + description Anti-spoofing_77.68.77.150 + source { + address 77.68.77.150 + mac-address !00:50:56:23:ac:01 + } + } + rule 659 { + action drop + description Anti-spoofing_77.68.121.106 + source { + address 77.68.121.106 + mac-address !00:50:56:38:2f:3f + } + } + rule 660 { + action drop + description Anti-spoofing_77.68.77.199 + source { + address 77.68.77.199 + mac-address !00:50:56:37:e8:23 + } + } + rule 661 { + action drop + description Anti-spoofing_77.68.76.220 + source { + address 77.68.76.220 + mac-address !00:50:56:26:27:93 + } + } + rule 662 { + action drop + description Anti-spoofing_77.68.85.172 + source { + address 77.68.85.172 + mac-address !00:50:56:24:a5:72 + } + } + rule 663 { + action drop + description Anti-spoofing_109.228.42.232 + source { + address 109.228.42.232 + mac-address !00:50:56:2c:34:e5 + } + } + rule 664 { + action drop + description Anti-spoofing_77.68.33.216 + source { + address 77.68.33.216 + mac-address !00:50:56:08:a3:d8 + } + } + rule 665 { + action drop + description Anti-spoofing_109.228.35.110 + source { + address 109.228.35.110 + mac-address !00:50:56:20:bc:f6 + } + } + rule 666 { + action drop + description Anti-spoofing_77.68.87.212 + source { + address 77.68.87.212 + mac-address !00:50:56:20:7a:5b + } + } + rule 667 { + action drop + description Anti-spoofing_109.228.36.174 + source { + address 109.228.36.174 + mac-address !00:50:56:05:73:0a + } + } + rule 668 { + action drop + description Anti-spoofing_77.68.122.241 + source { + address 77.68.122.241 + mac-address !00:50:56:3d:34:86 + } + } + rule 669 { + action drop + description Anti-spoofing_77.68.10.170 + source { + address 77.68.10.170 + mac-address !00:50:56:2e:a7:d6 + } + } + rule 670 { + action drop + description Anti-spoofing_109.228.59.247 + source { + address 109.228.59.247 + mac-address !00:50:56:11:77:61 + } + } + rule 671 { + action drop + description Anti-spoofing_77.68.77.156 + source { + address 77.68.77.156 + mac-address !00:50:56:37:e8:23 + } + } + rule 672 { + action drop + description Anti-spoofing_77.68.76.248 + source { + address 77.68.76.248 + mac-address !00:50:56:22:40:ae + } + } + rule 673 { + action drop + description Anti-spoofing_77.68.76.19 + source { + address 77.68.76.19 + mac-address !00:50:56:26:ce:06 + } + } + rule 674 { + action drop + description Anti-spoofing_77.68.77.29 + source { + address 77.68.77.29 + mac-address !00:50:56:11:83:b8 + } + } + rule 675 { + action drop + description Anti-spoofing_77.68.76.250 + source { + address 77.68.76.250 + mac-address !00:50:56:2d:ca:5b + } + } + rule 676 { + action drop + description Anti-spoofing_77.68.76.110 + source { + address 77.68.76.110 + mac-address !00:50:56:1e:db:08 + } + } + rule 677 { + action drop + description Anti-spoofing_77.68.76.171 + source { + address 77.68.76.171 + mac-address !00:50:56:01:8b:92 + } + } + rule 678 { + action drop + description Anti-spoofing_77.68.76.212 + source { + address 77.68.76.212 + mac-address !00:50:56:2b:28:99 + } + } + rule 679 { + action drop + description Anti-spoofing_77.68.112.248 + source { + address 77.68.112.248 + mac-address !00:50:56:35:e3:48 + } + } + rule 680 { + action drop + description Anti-spoofing_77.68.77.132 + source { + address 77.68.77.132 + mac-address !00:50:56:21:ab:ff + } + } + rule 681 { + action drop + description Anti-spoofing_77.68.120.218 + source { + address 77.68.120.218 + mac-address !00:50:56:10:a8:be + } + } + rule 682 { + action drop + description Anti-spoofing_77.68.120.249 + source { + address 77.68.120.249 + mac-address !00:50:56:2f:70:ed + } + } + rule 683 { + action drop + description Anti-spoofing_77.68.77.81 + source { + address 77.68.77.81 + mac-address !00:50:56:1e:9f:f8 + } + } + rule 684 { + action drop + description Anti-spoofing_77.68.76.37 + source { + address 77.68.76.37 + mac-address !00:50:56:07:f8:48 + } + } + rule 685 { + action drop + description Anti-spoofing_77.68.76.197 + source { + address 77.68.76.197 + mac-address !00:50:56:31:a0:ee + } + } + rule 686 { + action drop + description Anti-spoofing_77.68.76.20 + source { + address 77.68.76.20 + mac-address !00:50:56:18:a2:03 + } + } + rule 687 { + action drop + description Anti-spoofing_77.68.76.108 + source { + address 77.68.76.108 + mac-address !00:50:56:0d:4d:25 + } + } + rule 688 { + action drop + description Anti-spoofing_77.68.76.139 + source { + address 77.68.76.139 + mac-address !00:50:56:1c:52:a8 + } + } + rule 689 { + action drop + description Anti-spoofing_77.68.76.99 + source { + address 77.68.76.99 + mac-address !00:50:56:2e:8d:48 + } + } + rule 690 { + action drop + description Anti-spoofing_77.68.77.211 + source { + address 77.68.77.211 + mac-address !00:50:56:30:37:77 + } + } + rule 691 { + action drop + description Anti-spoofing_77.68.77.236 + source { + address 77.68.77.236 + mac-address !00:50:56:18:13:8b + } + } + rule 692 { + action drop + description Anti-spoofing_77.68.76.252 + source { + address 77.68.76.252 + mac-address !00:50:56:16:03:6e + } + } + rule 693 { + action drop + description Anti-spoofing_77.68.122.89 + source { + address 77.68.122.89 + mac-address !00:50:56:25:66:5d + } + } + rule 694 { + action drop + description Anti-spoofing_77.68.76.120 + source { + address 77.68.76.120 + mac-address !00:50:56:39:de:31 + } + } + rule 695 { + action drop + description Anti-spoofing_77.68.77.234 + source { + address 77.68.77.234 + mac-address !00:50:56:26:a1:9a + } + } + rule 696 { + action drop + description Anti-spoofing_77.68.77.32 + source { + address 77.68.77.32 + mac-address !00:50:56:38:e8:59 + } + } + rule 697 { + action drop + description Anti-spoofing_77.68.77.247 + source { + address 77.68.77.247 + mac-address !00:50:56:27:8a:8b + } + } + rule 698 { + action drop + description Anti-spoofing_77.68.76.229 + source { + address 77.68.76.229 + mac-address !00:50:56:16:56:30 + } + } + rule 699 { + action drop + description Anti-spoofing_77.68.76.209 + source { + address 77.68.76.209 + mac-address !00:50:56:19:24:73 + } + } + rule 700 { + action drop + description Anti-spoofing_77.68.125.32 + source { + address 77.68.125.32 + mac-address !00:50:56:00:07:47 + } + } + rule 701 { + action drop + description Anti-spoofing_77.68.76.219 + source { + address 77.68.76.219 + mac-address !00:50:56:2d:04:90 + } + } + rule 702 { + action drop + description Anti-spoofing_77.68.76.253 + source { + address 77.68.76.253 + mac-address !00:50:56:12:7b:d8 + } + } + rule 703 { + action drop + description Anti-spoofing_77.68.13.137 + source { + address 77.68.13.137 + mac-address !00:50:56:16:c6:86 + } + } + rule 704 { + action drop + description Anti-spoofing_77.68.85.115 + source { + address 77.68.85.115 + mac-address !00:50:56:3c:51:df + } + } + rule 705 { + action drop + description Anti-spoofing_77.68.77.202 + source { + address 77.68.77.202 + mac-address !00:50:56:0c:94:82 + } + } + rule 706 { + action drop + description Anti-spoofing_77.68.76.247 + source { + address 77.68.76.247 + mac-address !00:50:56:1b:f1:83 + } + } + rule 707 { + action drop + description Anti-spoofing_77.68.9.75 + source { + address 77.68.9.75 + mac-address !00:50:56:21:9b:fe + } + } + rule 708 { + action drop + description Anti-spoofing_109.228.39.157 + source { + address 109.228.39.157 + mac-address !00:50:56:2b:55:32 + } + } + rule 709 { + action drop + description Anti-spoofing_77.68.77.99 + source { + address 77.68.77.99 + mac-address !00:50:56:09:d5:e8 + } + } + rule 710 { + action drop + description Anti-spoofing_77.68.23.158 + source { + address 77.68.23.158 + mac-address !00:50:56:15:8f:75 + } + } + rule 711 { + action drop + description Anti-spoofing_77.68.76.169 + source { + address 77.68.76.169 + mac-address !00:50:56:0b:6d:e4 + } + } + rule 712 { + action drop + description Anti-spoofing_77.68.76.95 + source { + address 77.68.76.95 + mac-address !00:50:56:17:08:c9 + } + } + rule 713 { + action drop + description Anti-spoofing_77.68.76.187 + source { + address 77.68.76.187 + mac-address !00:50:56:14:79:08 + } + } + rule 714 { + action drop + description Anti-spoofing_109.228.37.114 + source { + address 109.228.37.114 + mac-address !00:50:56:15:3d:4b + } + } + rule 715 { + action drop + description Anti-spoofing_77.68.5.187 + source { + address 77.68.5.187 + mac-address !00:50:56:07:60:de + } + } + rule 716 { + action drop + description Anti-spoofing_77.68.77.222 + source { + address 77.68.77.222 + mac-address !00:50:56:38:03:ce + } + } + rule 717 { + action drop + description Anti-spoofing_77.68.77.53 + source { + address 77.68.77.53 + mac-address !00:50:56:18:cc:5a + } + } + rule 718 { + action drop + description Anti-spoofing_77.68.77.124 + source { + address 77.68.77.124 + mac-address !00:50:56:21:67:74 + } + } + rule 719 { + action drop + description Anti-spoofing_77.68.76.61 + source { + address 77.68.76.61 + mac-address !00:50:56:10:fa:46 + } + } + rule 720 { + action drop + description Anti-spoofing_109.228.37.240 + source { + address 109.228.37.240 + mac-address !00:50:56:0a:d3:2d + } + } + rule 721 { + action drop + description Anti-spoofing_77.68.27.27 + source { + address 77.68.27.27 + mac-address !00:50:56:14:b0:2a + } + } + rule 722 { + action drop + description Anti-spoofing_77.68.77.43 + source { + address 77.68.77.43 + mac-address !00:50:56:30:92:94 + } + } + rule 723 { + action drop + description Anti-spoofing_77.68.76.94 + source { + address 77.68.76.94 + mac-address !00:50:56:00:10:ce + } + } + rule 724 { + action drop + description Anti-spoofing_77.68.77.165 + source { + address 77.68.77.165 + mac-address !00:50:56:26:5f:42 + } + } + rule 725 { + action drop + description Anti-spoofing_77.68.77.251 + source { + address 77.68.77.251 + mac-address !00:50:56:39:db:9e + } + } + rule 726 { + action drop + description Anti-spoofing_77.68.77.152 + source { + address 77.68.77.152 + mac-address !00:50:56:12:68:ca + } + } + rule 727 { + action drop + description Anti-spoofing_185.132.43.164 + source { + address 185.132.43.164 + mac-address !00:50:56:2f:98:9b + } + } + rule 728 { + action drop + description Anti-spoofing_77.68.9.186 + source { + address 77.68.9.186 + mac-address !00:50:56:06:07:22 + } + } + rule 729 { + action drop + description Anti-spoofing_77.68.27.28 + source { + address 77.68.27.28 + mac-address !00:50:56:27:c6:2d + } + } + rule 730 { + action drop + description Anti-spoofing_77.68.84.147 + source { + address 77.68.84.147 + mac-address !00:50:56:28:d5:4d + } + } + rule 731 { + action drop + description Anti-spoofing_77.68.3.80 + source { + address 77.68.3.80 + mac-address !00:50:56:35:66:85 + } + } + rule 732 { + action drop + description Anti-spoofing_77.68.76.44 + source { + address 77.68.76.44 + mac-address !00:50:56:2b:8f:62 + } + } + rule 733 { + action drop + description Anti-spoofing_77.68.76.47 + source { + address 77.68.76.47 + mac-address !50:9a:4c:74:52:56 + } + } + rule 734 { + action drop + description Anti-spoofing_77.68.76.74 + source { + address 77.68.76.74 + mac-address !00:50:56:30:a0:57 + } + } + rule 735 { + action drop + description Anti-spoofing_77.68.5.166 + source { + address 77.68.5.166 + mac-address !00:50:56:17:e2:18 + } + } + rule 736 { + action drop + description Anti-spoofing_77.68.76.55 + source { + address 77.68.76.55 + mac-address !00:50:56:0f:46:86 + } + } + rule 737 { + action drop + description Anti-spoofing_77.68.10.142 + source { + address 77.68.10.142 + mac-address !00:50:56:19:04:d3 + } + } + rule 738 { + action drop + description Anti-spoofing_77.68.77.75 + source { + address 77.68.77.75 + mac-address !00:50:56:0e:a6:a8 + } + } + rule 739 { + action drop + description Anti-spoofing_77.68.77.239 + source { + address 77.68.77.239 + mac-address !00:50:56:26:f4:c8 + } + } + rule 740 { + action drop + description Anti-spoofing_213.171.208.176 + source { + address 213.171.208.176 + mac-address !00:50:56:34:50:f7 + } + } + rule 741 { + action drop + description Anti-spoofing_77.68.4.111 + source { + address 77.68.4.111 + mac-address !00:50:56:2a:61:0b + } + } + rule 742 { + action drop + description Anti-spoofing_77.68.118.120 + source { + address 77.68.118.120 + mac-address !00:50:56:3c:35:39 + } + } + rule 743 { + action drop + description Anti-spoofing_77.68.76.75 + source { + address 77.68.76.75 + mac-address !00:50:56:2a:42:ca + } + } + rule 744 { + action drop + description Anti-spoofing_77.68.77.71 + source { + address 77.68.77.71 + mac-address !00:50:56:38:ae:bf + } + } + rule 745 { + action drop + description Anti-spoofing_77.68.76.138 + source { + address 77.68.76.138 + mac-address !00:50:56:14:c0:d8 + } + } + rule 746 { + action drop + description Anti-spoofing_77.68.76.145 + source { + address 77.68.76.145 + mac-address !00:50:56:3b:e8:48 + } + } + rule 747 { + action drop + description Anti-spoofing_77.68.77.145 + source { + address 77.68.77.145 + mac-address !00:50:56:12:b0:43 + } + } + rule 748 { + action drop + description Anti-spoofing_77.68.3.121 + source { + address 77.68.3.121 + mac-address !00:50:56:03:7b:9d + } + } + rule 749 { + action drop + description Anti-spoofing_77.68.3.144 + source { + address 77.68.3.144 + mac-address !00:50:56:18:a0:ed + } + } + rule 750 { + action drop + description Anti-spoofing_77.68.77.68 + source { + address 77.68.77.68 + mac-address !00:50:56:3c:dc:4f + } + } + rule 751 { + action drop + description Anti-spoofing_77.68.76.126 + source { + address 77.68.76.126 + mac-address !00:50:56:0f:d0:ae + } + } + rule 752 { + action drop + description Anti-spoofing_77.68.76.88 + source { + address 77.68.76.88 + mac-address !00:50:56:15:d6:12 + } + } + rule 753 { + action drop + description Anti-spoofing_77.68.77.254 + source { + address 77.68.77.254 + mac-address !00:50:56:0e:5e:74 + } + } + rule 754 { + action drop + description Anti-spoofing_185.132.40.124 + source { + address 185.132.40.124 + mac-address !00:50:56:08:f8:6a + } + } + rule 755 { + action drop + description Anti-spoofing_77.68.20.231 + source { + address 77.68.20.231 + mac-address !00:50:56:05:35:ce + } + } + rule 756 { + action drop + description Anti-spoofing_77.68.77.181 + source { + address 77.68.77.181 + mac-address !00:50:56:20:03:6f + } + } + rule 757 { + action drop + description Anti-spoofing_77.68.22.146 + source { + address 77.68.22.146 + mac-address !00:50:56:0e:85:95 + } + } + rule 758 { + action drop + description Anti-spoofing_77.68.112.75 + source { + address 77.68.112.75 + mac-address !00:50:56:09:33:e6 + } + } + rule 759 { + action drop + description Anti-spoofing_77.68.4.22 + source { + address 77.68.4.22 + mac-address !00:50:56:14:be:3f + } + } + rule 760 { + action drop + description Anti-spoofing_77.68.76.96 + source { + address 77.68.76.96 + mac-address !00:50:56:32:91:fb + } + } + rule 761 { + action drop + description Anti-spoofing_77.68.3.161 + source { + address 77.68.3.161 + mac-address !00:50:56:12:82:40 + } + } + rule 762 { + action drop + description Anti-spoofing_109.228.37.10 + source { + address 109.228.37.10 + mac-address !00:50:56:0a:ef:ab + } + } + rule 763 { + action drop + description Anti-spoofing_77.68.76.228 + source { + address 77.68.76.228 + mac-address !00:50:56:2b:39:b1 + } + } + rule 764 { + action drop + description Anti-spoofing_77.68.121.94 + source { + address 77.68.121.94 + mac-address !00:50:56:0a:d7:68 + } + } + rule 765 { + action drop + description Anti-spoofing_77.68.3.194 + source { + address 77.68.3.194 + mac-address !00:50:56:10:90:6a + } + } + rule 766 { + action drop + description Anti-spoofing_77.68.76.112 + source { + address 77.68.76.112 + mac-address !00:50:56:24:e2:52 + } + } + rule 767 { + action drop + description Anti-spoofing_77.68.100.77 + source { + address 77.68.100.77 + mac-address !00:50:56:0e:f3:7a + } + } + rule 768 { + action drop + description Anti-spoofing_77.68.3.247 + source { + address 77.68.3.247 + mac-address !00:50:56:29:30:8a + } + } + rule 769 { + action drop + description Anti-spoofing_77.68.77.157 + source { + address 77.68.77.157 + mac-address !00:50:56:36:39:a5 + } + } + rule 770 { + action drop + description Anti-spoofing_77.68.29.65 + source { + address 77.68.29.65 + mac-address !00:50:56:2e:1b:f9 + } + } + rule 771 { + action drop + description Anti-spoofing_77.68.74.152 + source { + address 77.68.74.152 + mac-address !00:50:56:16:1d:31 + } + } + rule 772 { + action drop + description Anti-spoofing_185.132.39.145 + source { + address 185.132.39.145 + mac-address !00:50:56:03:77:75 + } + } + rule 773 { + action drop + description Anti-spoofing_77.68.28.139 + source { + address 77.68.28.139 + mac-address !00:50:56:25:a9:de + } + } + rule 774 { + action drop + description Anti-spoofing_77.68.77.33 + source { + address 77.68.77.33 + mac-address !00:50:56:09:16:76 + } + } + rule 775 { + action drop + description Anti-spoofing_77.68.77.137 + source { + address 77.68.77.137 + mac-address !00:50:56:15:b6:84 + } + } + rule 776 { + action drop + description Anti-spoofing_77.68.76.244 + source { + address 77.68.76.244 + mac-address !00:50:56:21:11:27 + } + } + rule 777 { + action drop + description Anti-spoofing_77.68.77.92 + source { + address 77.68.77.92 + mac-address !00:50:56:11:58:f5 + } + } + rule 778 { + action drop + description Anti-spoofing_77.68.7.227 + source { + address 77.68.7.227 + mac-address !00:50:56:34:a8:22 + } + } + rule 779 { + action drop + description Anti-spoofing_77.68.76.111 + source { + address 77.68.76.111 + mac-address !00:50:56:3e:44:ea + } + } + rule 780 { + action drop + description Anti-spoofing_77.68.76.185 + source { + address 77.68.76.185 + mac-address !00:50:56:1b:75:e8 + } + } + rule 781 { + action drop + description Anti-spoofing_77.68.76.208 + source { + address 77.68.76.208 + mac-address !50:9a:4c:98:c2:68 + } + } + rule 782 { + action drop + description Anti-spoofing_77.68.76.150 + source { + address 77.68.76.150 + mac-address !50:9a:4c:98:5c:c0 + } + } + rule 783 { + action drop + description Anti-spoofing_77.68.77.208 + source { + address 77.68.77.208 + mac-address !50:9a:4c:98:5c:c0 + } + } + rule 784 { + action drop + description Anti-spoofing_77.68.103.56 + source { + address 77.68.103.56 + mac-address !00:50:56:05:2f:9e + } + } + rule 785 { + action drop + description Anti-spoofing_77.68.125.60 + source { + address 77.68.125.60 + mac-address !00:50:56:2a:4a:20 + } + } + rule 786 { + action drop + description Anti-spoofing_77.68.76.42 + source { + address 77.68.76.42 + mac-address !00:50:56:3e:44:ea + } + } + rule 787 { + action drop + description Anti-spoofing_77.68.26.216 + source { + address 77.68.26.216 + mac-address !00:50:56:07:56:c4 + } + } + rule 788 { + action drop + description Anti-spoofing_77.68.76.164 + source { + address 77.68.76.164 + mac-address !00:50:56:1c:df:57 + } + } + rule 789 { + action drop + description Anti-spoofing_77.68.89.72 + source { + address 77.68.89.72 + mac-address !00:50:56:1b:84:5c + } + } + rule 790 { + action drop + description Anti-spoofing_77.68.76.181 + source { + address 77.68.76.181 + mac-address !00:50:56:36:5d:1e + } + } + rule 791 { + action drop + description Anti-spoofing_77.68.3.52 + source { + address 77.68.3.52 + mac-address !00:50:56:12:e2:00 + } + } + rule 792 { + action drop + description Anti-spoofing_77.68.77.207 + source { + address 77.68.77.207 + mac-address !00:50:56:16:24:34 + } + } + rule 793 { + action drop + description Anti-spoofing_77.68.81.44 + source { + address 77.68.81.44 + mac-address !00:50:56:1a:2f:81 + } + } + rule 794 { + action drop + description Anti-spoofing_77.68.28.145 + source { + address 77.68.28.145 + mac-address !00:50:56:39:78:a6 + } + } + rule 795 { + action drop + description Anti-spoofing_77.68.76.49 + source { + address 77.68.76.49 + mac-address !00:50:56:08:ae:5e + } + } + rule 796 { + action drop + description Anti-spoofing_77.68.77.227 + source { + address 77.68.77.227 + mac-address !ac:1f:6b:93:59:d4 + } + } + rule 797 { + action drop + description Anti-spoofing_77.68.76.136 + source { + address 77.68.76.136 + mac-address !00:50:56:0b:b2:b0 + } + } + rule 798 { + action drop + description Anti-spoofing_77.68.77.102 + source { + address 77.68.77.102 + mac-address !00:50:56:3d:91:75 + } + } + rule 799 { + action drop + description Anti-spoofing_77.68.5.155 + source { + address 77.68.5.155 + mac-address !00:50:56:13:33:02 + } + } + rule 801 { + action drop + description Anti-spoofing_77.68.88.100 + source { + address 77.68.88.100 + mac-address !00:50:56:08:dc:d0 + } + } + rule 802 { + action drop + description Anti-spoofing_77.68.72.254 + source { + address 77.68.72.254 + mac-address !00:50:56:0c:c2:8d + } + } + rule 803 { + action drop + description Anti-spoofing_77.68.77.74 + source { + address 77.68.77.74 + mac-address !00:50:56:18:d8:12 + } + } + rule 804 { + action drop + description Anti-spoofing_77.68.76.77 + source { + address 77.68.76.77 + mac-address !ac:1f:6b:4d:bd:60 + } + } + rule 805 { + action drop + description Anti-spoofing_77.68.76.123 + source { + address 77.68.76.123 + mac-address !00:50:56:38:5b:9d + } + } + rule 806 { + action drop + description Anti-spoofing_77.68.4.24 + source { + address 77.68.4.24 + mac-address !00:50:56:16:54:a8 + } + } + rule 807 { + action drop + description Anti-spoofing_213.171.214.167 + source { + address 213.171.214.167 + mac-address !00:50:56:13:7d:80 + } + } + rule 808 { + action drop + description Anti-spoofing_77.68.112.213 + source { + address 77.68.112.213 + mac-address !00:50:56:0b:ec:f2 + } + } + rule 809 { + action drop + description Anti-spoofing_185.132.40.166 + source { + address 185.132.40.166 + mac-address !00:50:56:22:c7:e0 + } + } + rule 810 { + action drop + description Anti-spoofing_77.68.76.31 + source { + address 77.68.76.31 + mac-address !00:50:56:38:22:33 + } + } + rule 811 { + action drop + description Anti-spoofing_77.68.76.148 + source { + address 77.68.76.148 + mac-address !00:50:56:16:6c:9c + } + } + rule 812 { + action drop + description Anti-spoofing_77.68.93.246 + source { + address 77.68.93.246 + mac-address !00:50:56:29:2c:65 + } + } + rule 813 { + action drop + description Anti-spoofing_77.68.77.120 + source { + address 77.68.77.120 + mac-address !00:50:56:39:92:1c + } + } + rule 814 { + action drop + description Anti-spoofing_77.68.7.123 + source { + address 77.68.7.123 + mac-address !00:50:56:33:46:a6 + } + } + rule 815 { + action drop + description Anti-spoofing_77.68.76.183 + source { + address 77.68.76.183 + mac-address !00:50:56:39:92:1c + } + } + rule 816 { + action drop + description Anti-spoofing_77.68.112.90 + source { + address 77.68.112.90 + mac-address !00:50:56:29:f8:91 + } + } + rule 817 { + action drop + description Anti-spoofing_77.68.50.90 + source { + address 77.68.50.90 + mac-address !00:50:56:11:d5:cb + } + } + rule 818 { + action drop + description Anti-spoofing_77.68.3.61 + source { + address 77.68.3.61 + mac-address !00:50:56:03:0b:87 + } + } + rule 819 { + action drop + description Anti-spoofing_213.171.213.42 + source { + address 213.171.213.42 + mac-address !00:50:56:37:90:bd + } + } + rule 820 { + action drop + description Anti-spoofing_77.68.77.107 + source { + address 77.68.77.107 + mac-address !00:50:56:1e:74:40 + } + } + rule 821 { + action drop + description Anti-spoofing_77.68.89.183 + source { + address 77.68.89.183 + mac-address !00:50:56:04:b9:ce + } + } + rule 822 { + action drop + description Anti-spoofing_77.68.112.83 + source { + address 77.68.112.83 + mac-address !00:50:56:38:03:ce + } + } + rule 823 { + action drop + description Anti-spoofing_77.68.76.141 + source { + address 77.68.76.141 + mac-address !00:50:56:12:2e:7c + } + } + rule 825 { + action drop + description Anti-spoofing_77.68.76.105 + source { + address 77.68.76.105 + mac-address !00:50:56:00:0b:f6 + } + } + rule 826 { + action drop + description Anti-spoofing_77.68.76.251 + source { + address 77.68.76.251 + mac-address !00:50:56:34:1e:f4 + } + } + rule 827 { + action drop + description Anti-spoofing_77.68.6.202 + source { + address 77.68.6.202 + mac-address !00:50:56:17:65:5f + } + } + rule 828 { + action drop + description Anti-spoofing_88.208.198.92 + source { + address 88.208.198.92 + mac-address !00:50:56:0c:5d:98 + } + } + rule 829 { + action drop + description Anti-spoofing_77.68.76.249 + source { + address 77.68.76.249 + mac-address !00:50:56:01:18:09 + } + } + rule 830 { + action drop + description Anti-spoofing_77.68.30.164 + source { + address 77.68.30.164 + mac-address !00:50:56:3c:2a:3a + } + } + rule 831 { + action drop + description Anti-spoofing_77.68.77.59 + source { + address 77.68.77.59 + mac-address !00:50:56:18:09:81 + } + } + rule 832 { + action drop + description Anti-spoofing_77.68.76.40 + source { + address 77.68.76.40 + mac-address !00:50:56:13:e6:96 + } + } + rule 833 { + action drop + description Anti-spoofing_77.68.88.164 + source { + address 77.68.88.164 + mac-address !00:50:56:07:f9:c8 + } + } + rule 834 { + action drop + description Anti-spoofing_77.68.77.37 + source { + address 77.68.77.37 + mac-address !00:50:56:2f:1e:7b + } + } + rule 835 { + action drop + description Anti-spoofing_185.132.39.99 + source { + address 185.132.39.99 + mac-address !00:50:56:1d:4e:dd + } + } + rule 836 { + action drop + description Anti-spoofing_77.68.121.127 + source { + address 77.68.121.127 + mac-address !00:50:56:29:fd:29 + } + } + rule 837 { + action drop + description Anti-spoofing_77.68.77.65 + source { + address 77.68.77.65 + mac-address !00:50:56:30:1f:8b + } + } + rule 838 { + action drop + description Anti-spoofing_77.68.27.211 + source { + address 77.68.27.211 + mac-address !00:50:56:25:b4:d1 + } + } + rule 839 { + action drop + description Anti-spoofing_77.68.24.112 + source { + address 77.68.24.112 + mac-address !00:50:56:06:50:e8 + } + } + rule 840 { + action drop + description Anti-spoofing_109.228.38.201 + source { + address 109.228.38.201 + mac-address !00:50:56:36:33:0c + } + } + rule 841 { + action drop + description Anti-spoofing_77.68.115.17 + source { + address 77.68.115.17 + mac-address !00:50:56:16:da:60 + } + } + rule 842 { + action drop + description Anti-spoofing_185.132.36.60 + source { + address 185.132.36.60 + mac-address !00:50:56:14:a7:b2 + } + } + rule 843 { + action drop + description Anti-spoofing_77.68.76.231 + source { + address 77.68.76.231 + mac-address !00:50:56:03:c5:bc + } + } + rule 844 { + action drop + description Anti-spoofing_185.132.37.23 + source { + address 185.132.37.23 + mac-address !00:50:56:27:46:b8 + } + } + rule 845 { + action drop + description Anti-spoofing_109.228.35.84 + source { + address 109.228.35.84 + mac-address !00:50:56:17:74:b7 + } + } + rule 846 { + action drop + description Anti-spoofing_77.68.11.140 + source { + address 77.68.11.140 + mac-address !00:50:56:08:ce:61 + } + } + rule 848 { + action drop + description Anti-spoofing_77.68.77.24 + source { + address 77.68.77.24 + mac-address !00:50:56:28:65:cb + } + } + rule 849 { + action drop + description Anti-spoofing_77.68.78.113 + source { + address 77.68.78.113 + mac-address !00:50:56:2c:5a:e3 + } + } + rule 850 { + action drop + description Anti-spoofing_185.132.39.219 + source { + address 185.132.39.219 + mac-address !00:50:56:11:0d:fd + } + } + rule 851 { + action drop + description Anti-spoofing_185.132.40.11 + source { + address 185.132.40.11 + mac-address !00:50:56:27:50:a3 + } + } + rule 852 { + action drop + description Anti-spoofing_77.68.23.64 + source { + address 77.68.23.64 + mac-address !00:50:56:0a:b2:3c + } + } + rule 853 { + action drop + description Anti-spoofing_185.132.37.133 + source { + address 185.132.37.133 + mac-address !00:50:56:0b:0a:21 + } + } + rule 854 { + action drop + description Anti-spoofing_77.68.85.27 + source { + address 77.68.85.27 + mac-address !00:50:56:34:82:24 + } + } + rule 855 { + action drop + description Anti-spoofing_77.68.26.221 + source { + address 77.68.26.221 + mac-address !00:50:56:30:56:a2 + } + } + rule 856 { + action drop + description Anti-spoofing_77.68.76.243 + source { + address 77.68.76.243 + mac-address !00:50:56:1c:a0:2d + } + } + rule 857 { + action drop + description Anti-spoofing_77.68.116.52 + source { + address 77.68.116.52 + mac-address !00:50:56:2b:59:35 + } + } + rule 858 { + action drop + description Anti-spoofing_77.68.120.26 + source { + address 77.68.120.26 + mac-address !00:50:56:07:3b:2b + } + } + rule 859 { + action drop + description Anti-spoofing_185.132.40.56 + source { + address 185.132.40.56 + mac-address !00:50:56:21:cb:e3 + } + } + rule 860 { + action drop + description Anti-spoofing_213.171.210.155 + source { + address 213.171.210.155 + mac-address !00:50:56:2a:53:9f + } + } + rule 861 { + action drop + description Anti-spoofing_185.132.43.157 + source { + address 185.132.43.157 + mac-address !00:50:56:27:e6:d5 + } + } + rule 862 { + action drop + description Anti-spoofing_77.68.4.252 + source { + address 77.68.4.252 + mac-address !00:50:56:08:ff:66 + } + } + rule 863 { + action drop + description Anti-spoofing_77.68.77.63 + source { + address 77.68.77.63 + mac-address !00:50:56:10:9c:ca + } + } + rule 864 { + action drop + description Anti-spoofing_77.68.20.161 + source { + address 77.68.20.161 + mac-address !00:50:56:0d:06:6f + } + } + rule 865 { + action drop + description Anti-spoofing_77.68.117.45 + source { + address 77.68.117.45 + mac-address !00:50:56:05:e0:11 + } + } + rule 866 { + action drop + description Anti-spoofing_77.68.76.234 + source { + address 77.68.76.234 + mac-address !00:50:56:3a:d3:9e + } + } + rule 867 { + action drop + description Anti-spoofing_185.132.40.90 + source { + address 185.132.40.90 + mac-address !00:50:56:2c:90:4f + } + } + rule 868 { + action drop + description Anti-spoofing_77.68.77.90 + source { + address 77.68.77.90 + mac-address !00:50:56:1d:ec:a2 + } + } + rule 869 { + action drop + description Anti-spoofing_77.68.76.93 + source { + address 77.68.76.93 + mac-address !00:50:56:19:cb:e8 + } + } + rule 870 { + action drop + description Anti-spoofing_77.68.26.166 + source { + address 77.68.26.166 + mac-address !00:50:56:1e:34:14 + } + } + rule 871 { + action drop + description Anti-spoofing_185.132.40.244 + source { + address 185.132.40.244 + mac-address !00:50:56:14:a7:b2 + } + } + rule 872 { + action drop + description Anti-spoofing_77.68.77.77 + source { + address 77.68.77.77 + mac-address !00:50:56:0c:9b:e1 + } + } + rule 873 { + action drop + description Anti-spoofing_77.68.27.57 + source { + address 77.68.27.57 + mac-address !00:50:56:3e:06:ca + } + } + rule 874 { + action drop + description Anti-spoofing_77.68.7.114 + source { + address 77.68.7.114 + mac-address !00:50:56:33:0d:5e + } + } + rule 875 { + action drop + description Anti-spoofing_109.228.36.229 + source { + address 109.228.36.229 + mac-address !00:50:56:32:a6:83 + } + } + rule 876 { + action drop + description Anti-spoofing_77.68.77.151 + source { + address 77.68.77.151 + mac-address !00:50:56:0a:e4:20 + } + } + rule 877 { + action drop + description Anti-spoofing_77.68.76.92 + source { + address 77.68.76.92 + mac-address !00:50:56:2b:a5:38 + } + } + rule 878 { + action drop + description Anti-spoofing_77.68.49.159 + source { + address 77.68.49.159 + mac-address !00:50:56:16:4f:24 + } + } + rule 879 { + action drop + description Anti-spoofing_77.68.77.38 + source { + address 77.68.77.38 + mac-address !00:50:56:2c:fe:a1 + } + } + rule 880 { + action drop + description Anti-spoofing_77.68.20.217 + source { + address 77.68.20.217 + mac-address !00:50:56:3a:61:47 + } + } + rule 881 { + action drop + description Anti-spoofing_77.68.92.92 + source { + address 77.68.92.92 + mac-address !00:50:56:1b:64:85 + } + } + rule 882 { + action drop + description Anti-spoofing_77.68.76.124 + source { + address 77.68.76.124 + mac-address !00:50:56:0e:c1:e4 + } + } + rule 884 { + action drop + description Anti-spoofing_77.68.126.101 + source { + address 77.68.126.101 + mac-address !00:50:56:31:d1:a3 + } + } + rule 885 { + action drop + description Anti-spoofing_77.68.76.235 + source { + address 77.68.76.235 + mac-address !00:50:56:15:d1:66 + } + } + rule 886 { + action drop + description Anti-spoofing_77.68.77.95 + source { + address 77.68.77.95 + mac-address !00:50:56:39:c6:52 + } + } + rule 887 { + action drop + description Anti-spoofing_77.68.26.228 + source { + address 77.68.26.228 + mac-address !00:50:56:03:ab:9e + } + } + rule 888 { + action drop + description Anti-spoofing_77.68.32.118 + source { + address 77.68.32.118 + mac-address !00:50:56:0e:db:9d + } + } + rule 889 { + action drop + description Anti-spoofing_77.68.24.172 + source { + address 77.68.24.172 + mac-address !00:50:56:0e:2a:9c + } + } + rule 891 { + action drop + description Anti-spoofing_77.68.77.190 + source { + address 77.68.77.190 + mac-address !00:50:56:31:e8:fb + } + } + rule 892 { + action drop + description Anti-spoofing_77.68.33.197 + source { + address 77.68.33.197 + mac-address !00:50:56:2b:27:c4 + } + } + rule 893 { + action drop + description Anti-spoofing_213.171.210.177 + source { + address 213.171.210.177 + mac-address !00:50:56:04:96:31 + } + } + rule 894 { + action drop + description Anti-spoofing_185.132.41.73 + source { + address 185.132.41.73 + mac-address !00:50:56:35:b4:a5 + } + } + rule 895 { + action drop + description Anti-spoofing_77.68.21.78 + source { + address 77.68.21.78 + mac-address !00:50:56:23:87:f2 + } + } + rule 896 { + action drop + description Anti-spoofing_77.68.77.209 + source { + address 77.68.77.209 + mac-address !00:50:56:3b:95:06 + } + } + rule 897 { + action drop + description Anti-spoofing_88.208.215.19 + source { + address 88.208.215.19 + mac-address !00:50:56:1f:e1:4b + } + } + rule 898 { + action drop + description Anti-spoofing_77.68.77.214 + source { + address 77.68.77.214 + mac-address !00:50:56:2b:03:2b + } + } + rule 899 { + action drop + description Anti-spoofing_77.68.76.91 + source { + address 77.68.76.91 + mac-address !00:50:56:3b:3c:fb + } + } + rule 900 { + action drop + description Anti-spoofing_77.68.119.92 + source { + address 77.68.119.92 + mac-address !00:50:56:25:ba:8c + } + } + rule 901 { + action drop + description Anti-spoofing_77.68.77.79 + source { + address 77.68.77.79 + mac-address !00:50:56:28:f5:72 + } + } + rule 902 { + action drop + description Anti-spoofing_77.68.75.45 + source { + address 77.68.75.45 + mac-address !00:50:56:04:51:74 + } + } + rule 903 { + action drop + description Anti-spoofing_109.228.56.185 + source { + address 109.228.56.185 + mac-address !00:50:56:13:e5:07 + } + } + rule 904 { + action drop + description Anti-spoofing_185.132.43.6 + source { + address 185.132.43.6 + mac-address !00:50:56:38:d1:d5 + } + } + rule 905 { + action drop + description Anti-spoofing_77.68.117.202 + source { + address 77.68.117.202 + mac-address !00:50:56:01:b2:9f + } + } + rule 906 { + action drop + description Anti-spoofing_77.68.86.40 + source { + address 77.68.86.40 + mac-address !00:50:56:03:e2:49 + } + } + rule 907 { + action drop + description Anti-spoofing_77.68.49.126 + source { + address 77.68.49.126 + mac-address !00:50:56:3b:47:f3 + } + } + rule 909 { + action drop + description Anti-spoofing_77.68.77.100 + source { + address 77.68.77.100 + mac-address !00:50:56:34:d7:5b + } + } + rule 910 { + action drop + description Anti-spoofing_109.228.46.196 + source { + address 109.228.46.196 + mac-address !00:50:56:1a:a0:0e + } + } + rule 911 { + action drop + description Anti-spoofing_77.68.77.72 + source { + address 77.68.77.72 + mac-address !00:50:56:1e:67:f7 + } + } + rule 912 { + action drop + description Anti-spoofing_185.132.43.28 + source { + address 185.132.43.28 + mac-address !00:50:56:35:a5:36 + } + } + rule 913 { + action drop + description Anti-spoofing_77.68.103.19 + source { + address 77.68.103.19 + mac-address !00:50:56:27:34:a3 + } + } + rule 914 { + action drop + description Anti-spoofing_77.68.118.104 + source { + address 77.68.118.104 + mac-address !00:50:56:2d:f8:d7 + } + } + rule 915 { + action drop + description Anti-spoofing_77.68.116.183 + source { + address 77.68.116.183 + mac-address !00:50:56:17:23:d4 + } + } + rule 916 { + action drop + description Anti-spoofing_77.68.76.107 + source { + address 77.68.76.107 + mac-address !00:50:56:36:c0:da + } + } + rule 917 { + action drop + description Anti-spoofing_77.68.93.164 + source { + address 77.68.93.164 + mac-address !00:50:56:36:cd:1a + } + } + rule 918 { + action drop + description Anti-spoofing_77.68.5.241 + source { + address 77.68.5.241 + mac-address !00:50:56:11:2d:22 + } + } + rule 919 { + action drop + description Anti-spoofing_185.132.43.98 + source { + address 185.132.43.98 + mac-address !00:50:56:20:7b:87 + } + } + rule 920 { + action drop + description Anti-spoofing_77.68.76.241 + source { + address 77.68.76.241 + mac-address !00:50:56:00:50:f6 + } + } + rule 921 { + action drop + description Anti-spoofing_77.68.74.232 + source { + address 77.68.74.232 + mac-address !00:50:56:19:df:41 + } + } + rule 922 { + action drop + description Anti-spoofing_77.68.76.26 + source { + address 77.68.76.26 + mac-address !00:50:56:36:c0:da + } + } + rule 923 { + action drop + description Anti-spoofing_77.68.28.207 + source { + address 77.68.28.207 + mac-address !00:50:56:36:41:da + } + } + rule 924 { + action drop + description Anti-spoofing_77.68.29.178 + source { + address 77.68.29.178 + mac-address !00:50:56:21:81:be + } + } + rule 925 { + action drop + description Anti-spoofing_77.68.121.119 + source { + address 77.68.121.119 + mac-address !00:50:56:0b:d8:e1 + } + } + rule 926 { + action drop + description Anti-spoofing_77.68.126.22 + source { + address 77.68.126.22 + mac-address !00:50:56:32:62:56 + } + } + rule 927 { + action drop + description Anti-spoofing_109.228.61.31 + source { + address 109.228.61.31 + mac-address !00:50:56:21:a0:04 + } + } + rule 928 { + action drop + description Anti-spoofing_77.68.114.205 + source { + address 77.68.114.205 + mac-address !00:50:56:2a:f1:3f + } + } + rule 929 { + action drop + description Anti-spoofing_77.68.75.113 + source { + address 77.68.75.113 + mac-address !00:50:56:33:6c:b9 + } + } + rule 930 { + action drop + description Anti-spoofing_77.68.79.206 + source { + address 77.68.79.206 + mac-address !00:50:56:36:86:66 + } + } + rule 931 { + action drop + description Anti-spoofing_88.208.198.64 + source { + address 88.208.198.64 + mac-address !00:50:56:39:2c:fe + } + } + rule 932 { + action drop + description Anti-spoofing_77.68.77.161 + source { + address 77.68.77.161 + mac-address !00:50:56:0a:7e:6c + } + } + rule 933 { + action drop + description Anti-spoofing_77.68.114.237 + source { + address 77.68.114.237 + mac-address !00:50:56:16:f4:39 + } + } + rule 934 { + action drop + description Anti-spoofing_109.228.36.119 + source { + address 109.228.36.119 + mac-address !00:50:56:28:63:37 + } + } + rule 935 { + action drop + description Anti-spoofing_77.68.76.254 + source { + address 77.68.76.254 + mac-address !00:50:56:3b:49:08 + } + } + rule 936 { + action drop + description Anti-spoofing_77.68.77.231 + source { + address 77.68.77.231 + mac-address !00:50:56:36:78:72 + } + } + rule 937 { + action drop + description Anti-spoofing_77.68.7.172 + source { + address 77.68.7.172 + mac-address !00:50:56:19:39:45 + } + } + rule 938 { + action drop + description Anti-spoofing_77.68.77.62 + source { + address 77.68.77.62 + mac-address !00:50:56:04:8c:b4 + } + } + rule 939 { + action drop + description Anti-spoofing_77.68.77.215 + source { + address 77.68.77.215 + mac-address !00:50:56:35:f3:5a + } + } + rule 940 { + action drop + description Anti-spoofing_77.68.6.105 + source { + address 77.68.6.105 + mac-address !00:50:56:03:0e:07 + } + } + rule 941 { + action drop + description Anti-spoofing_77.68.33.37 + source { + address 77.68.33.37 + mac-address !00:50:56:00:6b:a3 + } + } + rule 942 { + action drop + description Anti-spoofing_77.68.4.180 + source { + address 77.68.4.180 + mac-address !00:50:56:11:6c:dc + } + } + rule 943 { + action drop + description Anti-spoofing_77.68.78.229 + source { + address 77.68.78.229 + mac-address !00:50:56:1e:58:2f + } + } + rule 944 { + action drop + description Anti-spoofing_77.68.73.73 + source { + address 77.68.73.73 + mac-address !00:50:56:38:d7:1a + } + } + rule 945 { + action drop + description Anti-spoofing_77.68.2.215 + source { + address 77.68.2.215 + mac-address !00:50:56:31:3c:87 + } + } + rule 946 { + action drop + description Anti-spoofing_77.68.48.81 + source { + address 77.68.48.81 + mac-address !00:50:56:3a:13:df + } + } + rule 947 { + action drop + description Anti-spoofing_213.171.214.102 + source { + address 213.171.214.102 + mac-address !00:50:56:00:60:5a + } + } + rule 948 { + action drop + description Anti-spoofing_77.68.123.177 + source { + address 77.68.123.177 + mac-address !00:50:56:3c:07:ef + } + } + rule 949 { + action drop + description Anti-spoofing_77.68.7.160 + source { + address 77.68.7.160 + mac-address !00:50:56:09:6e:79 + } + } + rule 950 { + action drop + description Anti-spoofing_77.68.24.59 + source { + address 77.68.24.59 + mac-address !00:50:56:3c:b7:c1 + } + } + rule 951 { + action drop + description Anti-spoofing_77.68.80.97 + source { + address 77.68.80.97 + mac-address !00:50:56:15:cc:c6 + } + } + rule 952 { + action drop + description Anti-spoofing_77.68.7.67 + source { + address 77.68.7.67 + mac-address !00:50:56:13:92:b7 + } + } + rule 953 { + action drop + description Anti-spoofing_109.228.36.79 + source { + address 109.228.36.79 + mac-address !00:50:56:17:c9:65 + } + } + rule 954 { + action drop + description Anti-spoofing_77.68.32.43 + source { + address 77.68.32.43 + mac-address !00:50:56:13:6d:02 + } + } + rule 955 { + action drop + description Anti-spoofing_77.68.90.106 + source { + address 77.68.90.106 + mac-address !00:50:56:1b:6d:fb + } + } + rule 956 { + action drop + description Anti-spoofing_77.68.77.174 + source { + address 77.68.77.174 + mac-address !00:50:56:2a:61:0b + } + } + rule 957 { + action drop + description Anti-spoofing_77.68.94.181 + source { + address 77.68.94.181 + mac-address !00:50:56:0b:7c:cc + } + } + rule 958 { + action drop + description Anti-spoofing_77.68.4.136 + source { + address 77.68.4.136 + mac-address !00:50:56:10:4d:5c + } + } + rule 959 { + action drop + description Anti-spoofing_77.68.32.31 + source { + address 77.68.32.31 + mac-address !00:50:56:0a:f5:03 + } + } + rule 960 { + action drop + description Anti-spoofing_77.68.30.133 + source { + address 77.68.30.133 + mac-address !00:50:56:3a:96:4e + } + } + rule 961 { + action drop + description Anti-spoofing_77.68.72.202 + source { + address 77.68.72.202 + mac-address !00:50:56:2e:ca:a2 + } + } + rule 962 { + action drop + description Anti-spoofing_77.68.81.141 + source { + address 77.68.81.141 + mac-address !00:50:56:00:07:47 + } + } + rule 963 { + action drop + description Anti-spoofing_77.68.27.54 + source { + address 77.68.27.54 + mac-address !00:50:56:37:ad:51 + } + } + rule 964 { + action drop + description Anti-spoofing_77.68.32.254 + source { + address 77.68.32.254 + mac-address !00:50:56:2d:d0:36 + } + } + rule 965 { + action drop + description Anti-spoofing_77.68.10.152 + source { + address 77.68.10.152 + mac-address !00:50:56:38:d7:1a + } + } + rule 967 { + action drop + description Anti-spoofing_109.228.47.223 + source { + address 109.228.47.223 + mac-address !00:50:56:02:f7:24 + } + } + rule 968 { + action drop + description Anti-spoofing_77.68.5.125 + source { + address 77.68.5.125 + mac-address !00:50:56:16:21:98 + } + } + rule 969 { + action drop + description Anti-spoofing_77.68.119.14 + source { + address 77.68.119.14 + mac-address !00:50:56:2e:87:33 + } + } + rule 970 { + action drop + description Anti-spoofing_77.68.117.51 + source { + address 77.68.117.51 + mac-address !00:50:56:17:c0:6c + } + } + rule 971 { + action drop + description Anti-spoofing_77.68.118.102 + source { + address 77.68.118.102 + mac-address !00:50:56:3e:06:ca + } + } + rule 972 { + action drop + description Anti-spoofing_185.132.43.71 + source { + address 185.132.43.71 + mac-address !00:50:56:2d:6a:8d + } + } + rule 973 { + action drop + description Anti-spoofing_77.68.112.91 + source { + address 77.68.112.91 + mac-address !00:50:56:2b:c3:9f + } + } + rule 974 { + action drop + description Anti-spoofing_77.68.116.232 + source { + address 77.68.116.232 + mac-address !00:50:56:2a:f9:fd + } + } + rule 976 { + action drop + description Anti-spoofing_77.68.82.157 + source { + address 77.68.82.157 + mac-address !00:50:56:3d:81:41 + } + } + rule 977 { + action drop + description Anti-spoofing_77.68.117.222 + source { + address 77.68.117.222 + mac-address !00:50:56:16:92:58 + } + } + rule 978 { + action drop + description Anti-spoofing_77.68.118.15 + source { + address 77.68.118.15 + mac-address !00:50:56:28:28:de + } + } + rule 979 { + action drop + description Anti-spoofing_77.68.117.173 + source { + address 77.68.117.173 + mac-address !00:50:56:12:7a:57 + } + } + rule 980 { + action drop + description Anti-spoofing_77.68.83.41 + source { + address 77.68.83.41 + mac-address !00:50:56:13:ef:0e + } + } + rule 981 { + action drop + description Anti-spoofing_77.68.4.57 + source { + address 77.68.4.57 + mac-address !00:50:56:23:f0:c3 + } + } + rule 983 { + action drop + description Anti-spoofing_77.68.118.86 + source { + address 77.68.118.86 + mac-address !00:50:56:03:73:3d + } + } + rule 984 { + action drop + description Anti-spoofing_109.228.56.26 + source { + address 109.228.56.26 + mac-address !00:50:56:36:47:8c + } + } + rule 985 { + action drop + description Anti-spoofing_109.228.38.171 + source { + address 109.228.38.171 + mac-address !00:50:56:18:da:1c + } + } + rule 986 { + action drop + description Anti-spoofing_77.68.91.128 + source { + address 77.68.91.128 + mac-address !00:50:56:34:d0:41 + } + } + rule 987 { + action drop + description Anti-spoofing_77.68.79.89 + source { + address 77.68.79.89 + mac-address !00:50:56:14:67:52 + } + } + rule 988 { + action drop + description Anti-spoofing_88.208.198.66 + source { + address 88.208.198.66 + mac-address !00:50:56:3c:e0:8d + } + } + rule 989 { + action drop + description Anti-spoofing_77.68.118.88 + source { + address 77.68.118.88 + mac-address !00:50:56:2f:ac:5f + } + } + rule 990 { + action drop + description Anti-spoofing_109.228.60.215 + source { + address 109.228.60.215 + mac-address !00:50:56:2b:59:35 + } + } + rule 991 { + action drop + description Anti-spoofing_109.228.55.82 + source { + address 109.228.55.82 + mac-address !00:50:56:32:15:bc + } + } + rule 992 { + action drop + description Anti-spoofing_77.68.48.14 + source { + address 77.68.48.14 + mac-address !00:50:56:2e:2e:5a + } + } + rule 993 { + action drop + description Anti-spoofing_77.68.7.186 + source { + address 77.68.7.186 + mac-address !00:50:56:06:63:ae + } + } + rule 994 { + action drop + description Anti-spoofing_77.68.74.209 + source { + address 77.68.74.209 + mac-address !00:50:56:01:c5:88 + } + } + rule 995 { + action drop + description Anti-spoofing_77.68.6.32 + source { + address 77.68.6.32 + mac-address !00:50:56:19:b2:9e + } + } + rule 996 { + action drop + description Anti-spoofing_77.68.6.210 + source { + address 77.68.6.210 + mac-address !00:50:56:03:16:58 + } + } + rule 997 { + action drop + description Anti-spoofing_77.68.34.26 + source { + address 77.68.34.26 + mac-address !00:50:56:16:f0:f3 + } + } + rule 998 { + action drop + description Anti-spoofing_77.68.77.238 + source { + address 77.68.77.238 + mac-address !00:50:56:25:b8:e7 + } + } + rule 999 { + action drop + description Anti-spoofing_77.68.35.116 + source { + address 77.68.35.116 + mac-address !00:50:56:22:c6:b9 + } + } + rule 1000 { + action drop + description Anti-spoofing_77.68.23.112 + source { + address 77.68.23.112 + mac-address !00:50:56:1f:06:9f + } + } + rule 1001 { + action drop + description Anti-spoofing_77.68.120.241 + source { + address 77.68.120.241 + mac-address !00:50:56:18:1e:aa + } + } + rule 1002 { + action drop + description Anti-spoofing_77.68.34.28 + source { + address 77.68.34.28 + mac-address !00:50:56:24:5e:9a + } + } + rule 1003 { + action drop + description Anti-spoofing_77.68.122.195 + source { + address 77.68.122.195 + mac-address !00:50:56:0d:fd:66 + } + } + rule 1004 { + action drop + description Anti-spoofing_77.68.126.14 + source { + address 77.68.126.14 + mac-address !00:50:56:02:46:82 + } + } + rule 1005 { + action drop + description Anti-spoofing_109.228.38.117 + source { + address 109.228.38.117 + mac-address !00:50:56:05:55:f0 + } + } + rule 1006 { + action drop + description Anti-spoofing_77.68.33.171 + source { + address 77.68.33.171 + mac-address !00:50:56:07:69:46 + } + } + rule 1007 { + action drop + description Anti-spoofing_77.68.24.220 + source { + address 77.68.24.220 + mac-address !00:50:56:1f:53:df + } + } + rule 1008 { + action drop + description Anti-spoofing_88.208.197.23 + source { + address 88.208.197.23 + mac-address !00:50:56:23:fa:2f + } + } + rule 1009 { + action drop + description Anti-spoofing_77.68.80.26 + source { + address 77.68.80.26 + mac-address !00:50:56:21:23:8e + } + } + rule 1010 { + action drop + description Anti-spoofing_77.68.32.83 + source { + address 77.68.32.83 + mac-address !00:50:56:26:5d:1a + } + } + rule 1011 { + action drop + description Anti-spoofing_77.68.95.42 + source { + address 77.68.95.42 + mac-address !00:50:56:00:77:9a + } + } + rule 1012 { + action drop + description Anti-spoofing_213.171.209.217 + source { + address 213.171.209.217 + mac-address !00:50:56:18:7b:c2 + } + } + rule 1014 { + action drop + description Anti-spoofing_109.228.39.249 + source { + address 109.228.39.249 + mac-address !00:50:56:0e:4b:f9 + } + } + rule 1015 { + action drop + description Anti-spoofing_77.68.32.86 + source { + address 77.68.32.86 + mac-address !00:50:56:29:ff:6f + } + } + rule 1016 { + action drop + description Anti-spoofing_77.68.125.218 + source { + address 77.68.125.218 + mac-address !00:50:56:2f:4d:38 + } + } + rule 1017 { + action drop + description Anti-spoofing_77.68.17.186 + source { + address 77.68.17.186 + mac-address !00:50:56:2e:6b:f3 + } + } + rule 1018 { + action drop + description Anti-spoofing_77.68.12.45 + source { + address 77.68.12.45 + mac-address !00:50:56:15:e4:38 + } + } + rule 1019 { + action drop + description Anti-spoofing_109.228.40.247 + source { + address 109.228.40.247 + mac-address !00:50:56:20:62:b7 + } + } + rule 1020 { + action drop + description Anti-spoofing_77.68.32.89 + source { + address 77.68.32.89 + mac-address !00:50:56:2e:21:46 + } + } + rule 1022 { + action drop + description Anti-spoofing_77.68.34.138 + source { + address 77.68.34.138 + mac-address !00:50:56:10:0a:08 + } + } + rule 1023 { + action drop + description Anti-spoofing_77.68.34.139 + source { + address 77.68.34.139 + mac-address !00:50:56:0d:24:2f + } + } + rule 1024 { + action drop + description Anti-spoofing_213.171.208.40 + source { + address 213.171.208.40 + mac-address !00:50:56:07:df:6e + } + } + rule 1026 { + action drop + description Anti-spoofing_109.228.40.226 + source { + address 109.228.40.226 + mac-address !00:50:56:2d:c8:2a + } + } + rule 1028 { + action drop + description Anti-spoofing_185.132.39.109 + source { + address 185.132.39.109 + mac-address !00:50:56:2c:3e:98 + } + } + rule 1029 { + action drop + description Anti-spoofing_109.228.40.207 + source { + address 109.228.40.207 + mac-address !00:50:56:04:ba:9c + } + } + rule 1030 { + action drop + description Anti-spoofing_77.68.48.89 + source { + address 77.68.48.89 + mac-address !00:50:56:33:b3:05 + } + } + rule 1031 { + action drop + description Anti-spoofing_77.68.48.105 + source { + address 77.68.48.105 + mac-address !00:50:56:13:8d:55 + } + } + rule 1032 { + action drop + description Anti-spoofing_77.68.50.142 + source { + address 77.68.50.142 + mac-address !00:50:56:2e:58:85 + } + } + rule 1033 { + action drop + description Anti-spoofing_77.68.49.12 + source { + address 77.68.49.12 + mac-address !00:50:56:0f:ed:da + } + } + rule 1034 { + action drop + description Anti-spoofing_77.68.85.18 + source { + address 77.68.85.18 + mac-address !00:50:56:3b:0a:8b + } + } + rule 1035 { + action drop + description Anti-spoofing_77.68.49.4 + source { + address 77.68.49.4 + mac-address !00:50:56:05:e5:05 + } + } + rule 1036 { + action drop + description Anti-spoofing_109.228.37.187 + source { + address 109.228.37.187 + mac-address !00:50:56:37:21:f0 + } + } + rule 1037 { + action drop + description Anti-spoofing_77.68.49.178 + source { + address 77.68.49.178 + mac-address !00:50:56:26:00:f7 + } + } + rule 1038 { + action drop + description Anti-spoofing_77.68.82.147 + source { + address 77.68.82.147 + mac-address !00:50:56:13:75:25 + } + } + rule 1040 { + action drop + description Anti-spoofing_77.68.24.134 + source { + address 77.68.24.134 + mac-address !00:50:56:29:0b:02 + } + } + rule 1041 { + action drop + description Anti-spoofing_77.68.24.63 + source { + address 77.68.24.63 + mac-address !00:50:56:08:7e:4a + } + } + rule 1042 { + action drop + description Anti-spoofing_77.68.50.91 + source { + address 77.68.50.91 + mac-address !00:50:56:35:b6:4f + } + } + rule 1043 { + action drop + description Anti-spoofing_77.68.49.160 + source { + address 77.68.49.160 + mac-address !00:50:56:0e:29:ce + } + } + rule 1044 { + action drop + description Anti-spoofing_77.68.116.84 + source { + address 77.68.116.84 + mac-address !00:50:56:2d:e7:75 + } + } + rule 1045 { + action drop + description Anti-spoofing_77.68.126.160 + source { + address 77.68.126.160 + mac-address !00:50:56:19:a1:cf + } + } + rule 1046 { + action drop + description Anti-spoofing_185.132.41.240 + source { + address 185.132.41.240 + mac-address !00:50:56:08:f6:7c + } + } + rule 1047 { + action drop + description Anti-spoofing_77.68.50.193 + source { + address 77.68.50.193 + mac-address !00:50:56:0f:44:05 + } + } + rule 1048 { + action drop + description Anti-spoofing_77.68.49.161 + source { + address 77.68.49.161 + mac-address !00:50:56:09:4a:87 + } + } + rule 1049 { + action drop + description Anti-spoofing_109.228.58.134 + source { + address 109.228.58.134 + mac-address !00:50:56:06:82:eb + } + } + rule 1050 { + action drop + description Anti-spoofing_185.132.36.56 + source { + address 185.132.36.56 + mac-address !00:50:56:11:89:a1 + } + } + rule 1051 { + action drop + description Anti-spoofing_77.68.50.198 + source { + address 77.68.50.198 + mac-address !00:50:56:21:8f:66 + } + } + rule 1052 { + action drop + description Anti-spoofing_77.68.100.150 + source { + address 77.68.100.150 + mac-address !00:50:56:3a:15:0a + } + } + rule 1053 { + action drop + description Anti-spoofing_88.208.196.91 + source { + address 88.208.196.91 + mac-address !00:50:56:0a:06:31 + } + } + rule 1054 { + action drop + description Anti-spoofing_185.132.41.148 + source { + address 185.132.41.148 + mac-address !00:50:56:3b:d9:ec + } + } + rule 1055 { + action drop + description Anti-spoofing_213.171.210.25 + source { + address 213.171.210.25 + mac-address !00:50:56:0a:b8:6c + } + } + rule 1056 { + action drop + description Anti-spoofing_77.68.51.214 + source { + address 77.68.51.214 + mac-address !00:50:56:16:29:41 + } + } + rule 1057 { + action drop + description Anti-spoofing_77.68.51.202 + source { + address 77.68.51.202 + mac-address !00:50:56:24:5a:0f + } + } + rule 1058 { + action drop + description Anti-spoofing_77.68.100.132 + source { + address 77.68.100.132 + mac-address !00:50:56:27:18:b7 + } + } + rule 1059 { + action drop + description Anti-spoofing_77.68.77.42 + source { + address 77.68.77.42 + mac-address !00:50:56:34:d1:d5 + } + } + rule 1060 { + action drop + description Anti-spoofing_109.228.39.41 + source { + address 109.228.39.41 + mac-address !00:50:56:2e:6a:41 + } + } + rule 1061 { + action drop + description Anti-spoofing_77.68.100.134 + source { + address 77.68.100.134 + mac-address !00:50:56:19:a0:13 + } + } + rule 1062 { + action drop + description Anti-spoofing_77.68.89.247 + source { + address 77.68.89.247 + mac-address !00:50:56:2b:ed:68 + } + } + rule 1063 { + action drop + description Anti-spoofing_77.68.101.64 + source { + address 77.68.101.64 + mac-address !00:50:56:24:5a:0f + } + } + rule 1064 { + action drop + description Anti-spoofing_88.208.199.249 + source { + address 88.208.199.249 + mac-address !00:50:56:16:3e:ed + } + } + rule 1065 { + action drop + description Anti-spoofing_77.68.101.124 + source { + address 77.68.101.124 + mac-address !00:50:56:15:0e:e0 + } + } + rule 1066 { + action drop + description Anti-spoofing_77.68.101.125 + source { + address 77.68.101.125 + mac-address !00:50:56:33:ce:ff + } + } + rule 1068 { + action drop + description Anti-spoofing_77.68.100.167 + source { + address 77.68.100.167 + mac-address !00:50:56:34:b3:5d + } + } + rule 1069 { + action drop + description Anti-spoofing_77.68.49.152 + source { + address 77.68.49.152 + mac-address !00:50:56:1a:06:95 + } + } + rule 1070 { + action drop + description Anti-spoofing_77.68.103.147 + source { + address 77.68.103.147 + mac-address !00:50:56:2e:52:7f + } + } + rule 1071 { + action drop + description Anti-spoofing_77.68.48.202 + source { + address 77.68.48.202 + mac-address !00:50:56:0b:da:01 + } + } + rule 1072 { + action drop + description Anti-spoofing_77.68.112.175 + source { + address 77.68.112.175 + mac-address !00:50:56:05:9e:e5 + } + } + rule 1073 { + action drop + description Anti-spoofing_109.228.56.97 + source { + address 109.228.56.97 + mac-address !00:50:56:36:cd:04 + } + } + rule 1074 { + action drop + description Anti-spoofing_185.132.37.47 + source { + address 185.132.37.47 + mac-address !00:50:56:3a:de:38 + } + } + rule 1075 { + action drop + description Anti-spoofing_77.68.31.96 + source { + address 77.68.31.96 + mac-address !00:50:56:07:d0:cf + } + } + rule 1076 { + action drop + description Anti-spoofing_109.228.61.37 + source { + address 109.228.61.37 + mac-address !00:50:56:1a:93:80 + } + } + rule 1077 { + action drop + description Anti-spoofing_77.68.33.24 + source { + address 77.68.33.24 + mac-address !00:50:56:0d:ae:e8 + } + } + rule 1078 { + action drop + description Anti-spoofing_88.208.197.135 + source { + address 88.208.197.135 + mac-address !00:50:56:3b:39:6b + } + } + rule 1079 { + action drop + description Anti-spoofing_77.68.103.227 + source { + address 77.68.103.227 + mac-address !00:50:56:28:cd:95 + } + } + rule 1080 { + action drop + description Anti-spoofing_185.132.38.182 + source { + address 185.132.38.182 + mac-address !00:50:56:39:4b:e3 + } + } + rule 1081 { + action drop + description Anti-spoofing_88.208.197.118 + source { + address 88.208.197.118 + mac-address !00:50:56:2c:cd:e3 + } + } + rule 1082 { + action drop + description Anti-spoofing_88.208.196.92 + source { + address 88.208.196.92 + mac-address !00:50:56:05:77:19 + } + } + rule 1083 { + action drop + description Anti-spoofing_88.208.197.150 + source { + address 88.208.197.150 + mac-address !00:50:56:0c:ae:6c + } + } + rule 1084 { + action drop + description Anti-spoofing_88.208.215.121 + source { + address 88.208.215.121 + mac-address !00:50:56:16:0b:60 + } + } + rule 1085 { + action drop + description Anti-spoofing_88.208.197.10 + source { + address 88.208.197.10 + mac-address !00:50:56:1c:8b:fb + } + } + rule 1086 { + action drop + description Anti-spoofing_88.208.198.69 + source { + address 88.208.198.69 + mac-address !00:50:56:06:e7:eb + } + } + rule 1087 { + action drop + description Anti-spoofing_88.208.197.155 + source { + address 88.208.197.155 + mac-address !00:50:56:39:39:8e + } + } + rule 1088 { + action drop + description Anti-spoofing_88.208.198.39 + source { + address 88.208.198.39 + mac-address !00:50:56:22:2d:07 + } + } + rule 1089 { + action drop + description Anti-spoofing_88.208.197.160 + source { + address 88.208.197.160 + mac-address !00:50:56:2e:03:9a + } + } + rule 1090 { + action drop + description Anti-spoofing_88.208.197.60 + source { + address 88.208.197.60 + mac-address !00:50:56:3e:59:7c + } + } + rule 1091 { + action drop + description Anti-spoofing_77.68.102.129 + source { + address 77.68.102.129 + mac-address !00:50:56:2c:9d:a5 + } + } + rule 1092 { + action drop + description Anti-spoofing_88.208.196.123 + source { + address 88.208.196.123 + mac-address !00:50:56:21:ac:31 + } + } + rule 1093 { + action drop + description Anti-spoofing_88.208.215.61 + source { + address 88.208.215.61 + mac-address !00:50:56:05:91:dd + } + } + rule 1094 { + action drop + description Anti-spoofing_88.208.215.62 + source { + address 88.208.215.62 + mac-address !00:50:56:2d:ff:f4 + } + } + rule 1095 { + action drop + description Anti-spoofing_88.208.199.141 + source { + address 88.208.199.141 + mac-address !00:50:56:10:8f:10 + } + } + rule 1096 { + action drop + description Anti-spoofing_88.208.215.157 + source { + address 88.208.215.157 + mac-address !00:50:56:38:d7:1a + } + } + rule 1097 { + action drop + description Anti-spoofing_77.68.21.171 + source { + address 77.68.21.171 + mac-address !00:50:56:29:e0:5f + } + } + rule 1098 { + action drop + description Anti-spoofing_88.208.198.251 + source { + address 88.208.198.251 + mac-address !00:50:56:2b:2a:6a + } + } + rule 1099 { + action drop + description Anti-spoofing_88.208.199.233 + source { + address 88.208.199.233 + mac-address !00:50:56:1e:bf:95 + } + } + rule 1100 { + action drop + description Anti-spoofing_88.208.212.31 + source { + address 88.208.212.31 + mac-address !00:50:56:28:f4:aa + } + } + rule 1101 { + action drop + description Anti-spoofing_88.208.197.129 + source { + address 88.208.197.129 + mac-address !00:50:56:1f:71:bf + } + } + rule 1102 { + action drop + description Anti-spoofing_88.208.199.46 + source { + address 88.208.199.46 + mac-address !00:50:56:34:dc:e5 + } + } + rule 1103 { + action drop + description Anti-spoofing_88.208.212.94 + source { + address 88.208.212.94 + mac-address !00:50:56:3d:f5:16 + } + } + rule 1105 { + action drop + description Anti-spoofing_88.208.212.182 + source { + address 88.208.212.182 + mac-address !00:50:56:12:e4:1b + } + } + rule 1108 { + action drop + description Anti-spoofing_88.208.212.188 + source { + address 88.208.212.188 + mac-address !00:50:56:36:a8:9e + } + } + rule 1500 { + action drop + description "Block port 11211-udp" + protocol udp + source { + group { + address-group CLUSTER_ADDRESSES + } + port 11211 + } + } + rule 1510 { + action drop + description "Test Drive - Outgoing traffic blocked" + destination { + group { + network-group !NAS_NETWORKS + } + } + source { + group { + address-group DT_BLOCKED + } + } + } + rule 1520 { + action drop + description "Deny outgoing SMTP to new contracts" + destination { + port smtp + } + protocol tcp + source { + group { + address-group DT_SMTP_BLOCKED + } + } + } + rule 1600 { + action accept + description "Allow unicast requests to DHCP servers" + destination { + group { + address-group DHCP_SERVERS + } + port bootps + } + protocol tcp_udp + source { + group { + address-group CLUSTER_ADDRESSES + } + } + } + rule 1610 { + action accept + description "Allow DNS queries to dnscache servers" + destination { + group { + address-group DNSCACHE_SERVERS + } + port 53 + } + protocol tcp_udp + source { + group { + address-group CLUSTER_ADDRESSES + } + } + } + rule 1620 { + action accept + destination { + group { + address-group NAS_ARRAYS + } + } + source { + group { + address-group CLUSTER_ADDRESSES + } + } + } + rule 1630 { + action accept + description "Kerberos authentication to Domain Controllers" + destination { + group { + address-group NAS_DOMAIN_CONTROLLERS + } + port 88 + } + protocol tcp_udp + source { + group { + address-group CLUSTER_ADDRESSES + } + } + } + rule 1640 { + action drop + description "Deny rest of the traffic to NAS" + destination { + group { + network-group NAS_NETWORKS + } + } + } + rule 2000 { + action accept + description "TOP port - SSH" + destination { + group { + address-group G-22-TCP + } + port ssh + } + protocol tcp + } + rule 2001 { + action accept + description "TOP port - RDESKTOP" + destination { + group { + address-group G-3389-TCP + } + port 3389 + } + protocol tcp + } + rule 2002 { + action accept + description "TOP port - HTTP" + destination { + group { + address-group G-80-TCP + } + port http + } + protocol tcp + } + rule 2003 { + action accept + description "TOP port - HTTPS" + destination { + group { + address-group G-443-TCP + } + port https + } + protocol tcp + } + rule 2004 { + action accept + description "TOP port - DOMAIN TCP" + destination { + group { + address-group G-53-TCP + } + port domain + } + protocol tcp + } + rule 2005 { + action accept + description "TOP port - DOMAIN UDP" + destination { + group { + address-group G-53-UDP + } + port domain + } + protocol udp + } + rule 2006 { + action accept + description "TOP port - SMTP" + destination { + group { + address-group G-25-TCP + } + port smtp + } + protocol tcp + } + rule 2007 { + action accept + description "TOP port - IMAP" + destination { + group { + address-group G-143-TCP + } + port imap2 + } + protocol tcp + } + rule 2008 { + action accept + description "TOP port - POP3" + destination { + group { + address-group G-110-TCP + } + port pop3 + } + protocol tcp + } + rule 2009 { + action accept + description "TOP port - MSSQL TCP" + destination { + group { + address-group G-1433-TCP + } + port ms-sql-s + } + protocol tcp + } + rule 2010 { + action accept + description "TOP port - MYSQL TCP" + destination { + group { + address-group G-3306-TCP + } + port mysql + } + protocol tcp + } + rule 2011 { + action accept + description "TOP port - FTPDATA" + destination { + group { + address-group G-20-TCP + } + port ftp-data + } + protocol tcp + } + rule 2012 { + action accept + description "TOP port - FTP" + destination { + group { + address-group G-21-TCP + } + port ftp + } + protocol tcp + } + rule 2013 { + action accept + description "TOP port - SSMTP" + destination { + group { + address-group G-465-TCP + } + port ssmtp + } + protocol tcp + } + rule 2014 { + action accept + description "TOP port - SMTPS" + destination { + group { + address-group G-587-TCP + } + port 587 + } + protocol tcp + } + rule 2015 { + action accept + description "TOP port - IMAPS" + destination { + group { + address-group G-993-TCP + } + port imaps + } + protocol tcp + } + rule 2016 { + action accept + description "TOP port - POP3S" + destination { + group { + address-group G-995-TCP + } + port pop3s + } + protocol tcp + } + rule 2017 { + action accept + description "TOP port - TOMCAT" + destination { + group { + address-group G-8080-TCP + } + port 8080 + } + protocol tcp + } + rule 2018 { + action accept + description "TOP port - Alternative HTTPS" + destination { + group { + address-group G-8443-TCP + } + port 8443 + } + protocol tcp + } + rule 2019 { + action accept + description "TOP port - 10000/TCP" + destination { + group { + address-group G-10000-TCP + } + port 10000 + } + protocol tcp + } + rule 2020 { + action accept + description "TOP port - 8447/TCP" + destination { + group { + address-group G-8447-TCP + } + port 8447 + } + protocol tcp + } + rule 2040 { + action accept + description "TOP port - All ports open" + destination { + group { + address-group G-ALL_OPEN + } + } + } + rule 2050 { + action accept + description "ICMP group" + destination { + group { + address-group G-ICMP + } + } + protocol icmp + } + rule 2100 { + action accept + description FW2BB8D_1-TCP-ALLOW-104.192.143.2 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 7999,22 + } + protocol tcp + source { + address 104.192.143.2 + } + } + rule 2101 { + action accept + description FW19987_4-TCP-ALLOW-77.68.74.54 + destination { + group { + address-group DT_FW19987_4 + } + port 443 + } + protocol tcp + source { + address 77.68.74.54 + } + } + rule 2102 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-109.72.210.46 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 109.72.210.46 + } + } + rule 2103 { + action accept + description FW5A77C_16-TCP-ALLOW-213.171.217.107 + destination { + group { + address-group DT_FW5A77C_16 + } + port 22 + } + protocol tcp + source { + address 213.171.217.107 + } + } + rule 2104 { + action accept + description FW826BA_3-TCP-ALLOW-164.177.156.192 + destination { + group { + address-group DT_FW826BA_3 + } + port 3389,1433,21 + } + protocol tcp + source { + address 164.177.156.192 + } + } + rule 2105 { + action accept + description FWDAA4F_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWDAA4F_1 + } + port 22335 + } + protocol tcp + } + rule 2106 { + action accept + description FW6D0CD_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW6D0CD_1 + } + port 6900,7000 + } + protocol tcp + } + rule 2107 { + action accept + description FW6D0CD_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW6D0CD_1 + } + port 9001 + } + protocol tcp_udp + } + rule 2108 { + action accept + description FW06176_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW06176_1 + } + port 5900 + } + protocol tcp + } + rule 2109 { + action accept + description FW19987_4-TCP-ALLOW-77.68.77.70 + destination { + group { + address-group DT_FW19987_4 + } + port 443 + } + protocol tcp + source { + address 77.68.77.70 + } + } + rule 2110 { + action accept + description FWF7B68_1-TCP-ALLOW-54.221.251.224 + destination { + group { + address-group DT_FWF7B68_1 + } + port 8443,3306,22,21,20 + } + protocol tcp + source { + address 54.221.251.224 + } + } + rule 2111 { + action accept + description FW05AD0_2-TCP-ALLOW-178.251.181.41 + destination { + group { + address-group DT_FW05AD0_2 + } + port 3389,1433,21 + } + protocol tcp + source { + address 178.251.181.41 + } + } + rule 2112 { + action accept + description FW05AD0_2-TCP-ALLOW-178.251.181.6 + destination { + group { + address-group DT_FW05AD0_2 + } + port 3389,1433,21 + } + protocol tcp + source { + address 178.251.181.6 + } + } + rule 2113 { + action accept + description VPN-7030-ANY-ALLOW-10.4.58.119 + destination { + group { + address-group DT_VPN-7030 + } + } + source { + address 10.4.58.119 + } + } + rule 2114 { + action accept + description FW58C69_4-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW58C69_4 + } + port 5666 + } + protocol tcp + } + rule 2115 { + action accept + description FW2BB8D_1-TCP-ALLOW-185.201.180.35 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 27017,5000,22 + } + protocol tcp + source { + address 185.201.180.35 + } + } + rule 2116 { + action accept + description FW19987_4-TCP-ALLOW-77.68.8.74 + destination { + group { + address-group DT_FW19987_4 + } + port 3389,445,443 + } + protocol tcp + source { + address 77.68.8.74 + } + } + rule 2117 { + action accept + description FW19987_4-TCP-ALLOW-87.224.33.215 + destination { + group { + address-group DT_FW19987_4 + } + port 3389,445,443 + } + protocol tcp + source { + address 87.224.33.215 + } + } + rule 2118 { + action accept + description FW5658C_1-TCP-ALLOW-212.159.160.65 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443,3389,3306,22,21 + } + protocol tcp + source { + address 212.159.160.65 + } + } + rule 2119 { + action accept + description FW5658C_1-TCP-ALLOW-79.78.20.149 + destination { + group { + address-group DT_FW5658C_1 + } + port 8447,8443,3389,3306,993,143,22,21 + } + protocol tcp + source { + address 79.78.20.149 + } + } + rule 2120 { + action accept + description FW5658C_1-TCP-ALLOW-77.68.77.185 + destination { + group { + address-group DT_FW5658C_1 + } + port 3306 + } + protocol tcp + source { + address 77.68.77.185 + } + } + rule 2121 { + action accept + description FW5658C_1-TCP-ALLOW-82.165.232.19 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443,3389 + } + protocol tcp + source { + address 82.165.232.19 + } + } + rule 2122 { + action accept + description FW2C5AE_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW2C5AE_1 + } + port 30303,5717 + } + protocol tcp_udp + } + rule 2123 { + action accept + description VPN-12899-ANY-ALLOW-10.4.58.207 + destination { + group { + address-group DT_VPN-12899 + } + } + source { + address 10.4.58.207 + } + } + rule 2124 { + action accept + description FW7648D_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW7648D_1 + } + port 8501,8050,7801,4444,1443 + } + protocol tcp + } + rule 2125 { + action accept + description FW0C2E6_4-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW0C2E6_4 + } + port 1194 + } + protocol udp + } + rule 2126 { + action accept + description FW5658C_1-TCP-ALLOW-39.37.175.132 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443 + } + protocol tcp + source { + address 39.37.175.132 + } + } + rule 2127 { + action accept + description FW826BA_3-TCP-ALLOW-165.255.242.223 + destination { + group { + address-group DT_FW826BA_3 + } + port 3389,1433,21 + } + protocol tcp + source { + address 165.255.242.223 + } + } + rule 2128 { + action accept + description VPN-10131-ANY-ALLOW-10.4.56.51 + destination { + group { + address-group DT_VPN-10131 + } + } + source { + address 10.4.56.51 + } + } + rule 2129 { + action accept + description FW2BB8D_1-TCP-ALLOW-212.227.84.142 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 22 + } + protocol tcp + source { + address 212.227.84.142 + } + } + rule 2130 { + action accept + description FW2BB8D_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW2BB8D_1 + } + port 53 + } + protocol tcp_udp + } + rule 2131 { + action accept + description FWFDD94_15-TCP-ALLOW-90.29.180.234 + destination { + group { + address-group DT_FWFDD94_15 + } + port 5683,1883 + } + protocol tcp + source { + address 90.29.180.234 + } + } + rule 2132 { + action accept + description VPN-10131-ANY-ALLOW-10.4.57.51 + destination { + group { + address-group DT_VPN-10131 + } + } + source { + address 10.4.57.51 + } + } + rule 2133 { + action accept + description FW2BB8D_1-TCP-ALLOW-109.228.49.193 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 5000 + } + protocol tcp + source { + address 109.228.49.193 + } + } + rule 2134 { + action accept + description FW81138_1-ICMP-ALLOW-3.10.221.168 + destination { + group { + address-group DT_FW81138_1 + } + } + protocol icmp + source { + address 3.10.221.168 + } + } + rule 2135 { + action accept + description FWB28B6_5-AH-ALLOW-77.68.36.46 + destination { + group { + address-group DT_FWB28B6_5 + } + } + protocol ah + source { + address 77.68.36.46 + } + } + rule 2136 { + action accept + description FWB28B6_5-ESP-ALLOW-77.68.36.46 + destination { + group { + address-group DT_FWB28B6_5 + } + } + protocol esp + source { + address 77.68.36.46 + } + } + rule 2137 { + action accept + description FW825C8_24-TCP-ALLOW-77.68.87.201 + destination { + group { + address-group DT_FW825C8_24 + } + port 1433 + } + protocol tcp + source { + address 77.68.87.201 + } + } + rule 2138 { + action accept + description FWB28B6_5-AH-ALLOW-213.171.196.146 + destination { + group { + address-group DT_FWB28B6_5 + } + } + protocol ah + source { + address 213.171.196.146 + } + } + rule 2139 { + action accept + description FWB28B6_5-ESP-ALLOW-213.171.196.146 + destination { + group { + address-group DT_FWB28B6_5 + } + } + protocol esp + source { + address 213.171.196.146 + } + } + rule 2140 { + action accept + description FWB28B6_5-UDP-ALLOW-213.171.196.146 + destination { + group { + address-group DT_FWB28B6_5 + } + port 500,4500 + } + protocol udp + source { + address 213.171.196.146 + } + } + rule 2141 { + action accept + description FWB28B6_5-TCP_UDP-ALLOW-213.171.196.146 + destination { + group { + address-group DT_FWB28B6_5 + } + port 1701 + } + protocol tcp_udp + source { + address 213.171.196.146 + } + } + rule 2142 { + action accept + description FWB28B6_5-TCP_UDP-ALLOW-77.68.36.46 + destination { + group { + address-group DT_FWB28B6_5 + } + port 1701 + } + protocol tcp_udp + source { + address 77.68.36.46 + } + } + rule 2143 { + action accept + description FWB28B6_5-UDP-ALLOW-77.68.36.46 + destination { + group { + address-group DT_FWB28B6_5 + } + port 500,4500 + } + protocol udp + source { + address 77.68.36.46 + } + } + rule 2144 { + action accept + description VPN-12899-ANY-ALLOW-10.4.59.207 + destination { + group { + address-group DT_VPN-12899 + } + } + source { + address 10.4.59.207 + } + } + rule 2145 { + action accept + description FWB28B6_5-TCP-ALLOW-81.130.141.175 + destination { + group { + address-group DT_FWB28B6_5 + } + port 3389 + } + protocol tcp + source { + address 81.130.141.175 + } + } + rule 2146 { + action accept + description FWB28B6_5-UDP-ALLOW-77.68.38.195 + destination { + group { + address-group DT_FWB28B6_5 + } + port 4500,500 + } + protocol udp + source { + address 77.68.38.195 + } + } + rule 2147 { + action accept + description FWB28B6_5-AH-ALLOW-77.68.38.195 + destination { + group { + address-group DT_FWB28B6_5 + } + } + protocol ah + source { + address 77.68.38.195 + } + } + rule 2148 { + action accept + description FWB28B6_5-ESP-ALLOW-77.68.38.195 + destination { + group { + address-group DT_FWB28B6_5 + } + } + protocol esp + source { + address 77.68.38.195 + } + } + rule 2149 { + action accept + description FWB28B6_5-TCP_UDP-ALLOW-77.68.38.195 + destination { + group { + address-group DT_FWB28B6_5 + } + port 1701 + } + protocol tcp_udp + source { + address 77.68.38.195 + } + } + rule 2150 { + action accept + description FW5658C_1-TCP-ALLOW-39.37.178.77 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443 + } + protocol tcp + source { + address 39.37.178.77 + } + } + rule 2151 { + action accept + description FW5A77C_16-TCP-ALLOW-51.241.139.56 + destination { + group { + address-group DT_FW5A77C_16 + } + port 22 + } + protocol tcp + source { + address 51.241.139.56 + } + } + rule 2152 { + action accept + description FWA86ED_101-TCP-ALLOW-150.143.57.138 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389 + } + protocol tcp + source { + address 150.143.57.138 + } + } + rule 2153 { + action accept + description FW6ECA4_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW6ECA4_1 + } + port 3939,3335,3334,3333,3000,999,444 + } + protocol tcp_udp + } + rule 2154 { + action accept + description FW5658C_1-TCP-ALLOW-39.45.13.20 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443 + } + protocol tcp + source { + address 39.45.13.20 + } + } + rule 2155 { + action accept + description FW481D7_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW481D7_1 + } + port 3478 + } + protocol tcp_udp + } + rule 2156 { + action accept + description FW5A5D7_3-GRE-ALLOW-51.219.222.28 + destination { + group { + address-group DT_FW5A5D7_3 + } + } + protocol gre + source { + address 51.219.222.28 + } + } + rule 2157 { + action accept + description FWA86ED_101-TCP-ALLOW-94.195.127.217 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 94.195.127.217 + } + } + rule 2158 { + action accept + description FW2E060_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2E060_1 + } + port 49152-65535,8443-8447 + } + protocol tcp + } + rule 2159 { + action accept + description FWFDD94_15-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWFDD94_15 + } + port 9090,5080,1935 + } + protocol tcp + } + rule 2160 { + action accept + description FW5658C_1-TCP-ALLOW-39.45.190.224 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443 + } + protocol tcp + source { + address 39.45.190.224 + } + } + rule 2161 { + action accept + description FW9E550_1-TCP-ALLOW-109.249.187.56 + destination { + group { + address-group DT_FW9E550_1 + } + port 3389 + } + protocol tcp + source { + address 109.249.187.56 + } + } + rule 2162 { + action accept + description FW89619_1-TCP-ALLOW-81.133.80.114 + destination { + group { + address-group DT_FW89619_1 + } + port 22 + } + protocol tcp + source { + address 81.133.80.114 + } + } + rule 2163 { + action accept + description FW8A3FC_3-TCP-ALLOW-212.227.72.218 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 212.227.72.218 + } + } + rule 2164 { + action accept + description FW0E383_9-TCP-ALLOW-151.229.59.51 + destination { + group { + address-group DT_FW0E383_9 + } + port 1433 + } + protocol tcp + source { + address 151.229.59.51 + } + } + rule 2165 { + action accept + description FW8AFF1_7-TCP-ALLOW-178.251.181.41 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433,21 + } + protocol tcp + source { + address 178.251.181.41 + } + } + rule 2166 { + action accept + description FW3CAAB_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW3CAAB_1 + } + port 49152-65535,30000-30400,8443-8447,5432,80-110,21-25 + } + protocol tcp + } + rule 2167 { + action accept + description FW91B7A_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW91B7A_1 + } + port 3389,80 + } + protocol tcp_udp + } + rule 2168 { + action accept + description FW40416_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW40416_1 + } + port 1-65535 + } + protocol tcp + } + rule 2169 { + action accept + description FW5A77C_16-TCP-ALLOW-81.151.24.216 + destination { + group { + address-group DT_FW5A77C_16 + } + port 10000,22 + } + protocol tcp + source { + address 81.151.24.216 + } + } + rule 2170 { + action accept + description VPN-7030-ANY-ALLOW-10.4.59.119 + destination { + group { + address-group DT_VPN-7030 + } + } + source { + address 10.4.59.119 + } + } + rule 2171 { + action accept + description FW0E383_9-TCP-ALLOW-62.252.94.138 + destination { + group { + address-group DT_FW0E383_9 + } + port 3389,1433 + } + protocol tcp + source { + address 62.252.94.138 + } + } + rule 2172 { + action accept + description FW89619_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW89619_1 + } + port 5015,5001,5000 + } + protocol tcp + } + rule 2173 { + action accept + description FW89619_1-TCP_UDP-ALLOW-167.98.162.142 + destination { + group { + address-group DT_FW89619_1 + } + port 5060 + } + protocol tcp_udp + source { + address 167.98.162.142 + } + } + rule 2174 { + action accept + description FW013EF_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW013EF_2 + } + port 44445,7770-7800,5090,5060-5070,5015,5001,2000-2500 + } + protocol tcp + } + rule 2175 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.12 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.214.12 + } + } + rule 2176 { + action accept + description VPN-15625-ANY-ALLOW-10.4.88.79 + destination { + group { + address-group DT_VPN-15625 + } + } + source { + address 10.4.88.79 + } + } + rule 2177 { + action accept + description FW1F3D0_6-TCP-ALLOW-109.228.53.128 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306,22 + } + protocol tcp + source { + address 109.228.53.128 + } + } + rule 2178 { + action accept + description FW8AFF1_7-TCP-ALLOW-178.251.181.6 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 3389,1433,21 + } + protocol tcp + source { + address 178.251.181.6 + } + } + rule 2179 { + action accept + description FW578BE_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW578BE_1 + } + port 23,1521,1522 + } + protocol tcp + } + rule 2180 { + action accept + description FWE012D_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE012D_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2181 { + action accept + description FW8AFF1_7-TCP-ALLOW-213.171.209.161 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 3389,1433,21 + } + protocol tcp + source { + address 213.171.209.161 + } + } + rule 2182 { + action accept + description VPN-8203-ANY-ALLOW-10.4.58.109 + destination { + group { + address-group DT_VPN-8203 + } + } + source { + address 10.4.58.109 + } + } + rule 2183 { + action accept + description VPN-9415-ANY-ALLOW-10.4.58.168 + destination { + group { + address-group DT_VPN-9415 + } + } + source { + address 10.4.58.168 + } + } + rule 2184 { + action accept + description VPN-9415-ANY-ALLOW-10.4.59.168 + destination { + group { + address-group DT_VPN-9415 + } + } + source { + address 10.4.59.168 + } + } + rule 2185 { + action accept + description FW27A8F_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW27A8F_1 + } + port 9990,8458,8090,6543,5432 + } + protocol tcp + } + rule 2186 { + action accept + description FW2BB8D_1-TCP-ALLOW-77.68.11.224 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 5000 + } + protocol tcp + source { + address 77.68.11.224 + } + } + rule 2187 { + action accept + description VPN-15625-ANY-ALLOW-10.4.89.79 + destination { + group { + address-group DT_VPN-15625 + } + } + source { + address 10.4.89.79 + } + } + rule 2188 { + action accept + description VPN-14649-ANY-ALLOW-10.4.86.35 + destination { + group { + address-group DT_VPN-14649 + } + } + source { + address 10.4.86.35 + } + } + rule 2189 { + action accept + description VPN-14649-ANY-ALLOW-10.4.87.35 + destination { + group { + address-group DT_VPN-14649 + } + } + source { + address 10.4.87.35 + } + } + rule 2190 { + action accept + description VPN-14657-ANY-ALLOW-10.4.86.38 + destination { + group { + address-group DT_VPN-14657 + } + } + source { + address 10.4.86.38 + } + } + rule 2191 { + action accept + description VPN-14657-ANY-ALLOW-10.4.87.38 + destination { + group { + address-group DT_VPN-14657 + } + } + source { + address 10.4.87.38 + } + } + rule 2192 { + action accept + description VPN-14658-ANY-ALLOW-10.4.88.38 + destination { + group { + address-group DT_VPN-14658 + } + } + source { + address 10.4.88.38 + } + } + rule 2193 { + action accept + description VPN-14658-ANY-ALLOW-10.4.89.38 + destination { + group { + address-group DT_VPN-14658 + } + } + source { + address 10.4.89.38 + } + } + rule 2194 { + action accept + description FW0BB22_1-GRE-ALLOW-ANY + destination { + group { + address-group DT_FW0BB22_1 + } + } + protocol gre + } + rule 2195 { + action accept + description FW0BB22_1-ESP-ALLOW-ANY + destination { + group { + address-group DT_FW0BB22_1 + } + } + protocol esp + } + rule 2196 { + action accept + description FW1CC15_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW1CC15_2 + } + port 8089,8085,990,81 + } + protocol tcp + } + rule 2197 { + action accept + description FW8AFF1_7-TCP-ALLOW-109.228.0.124 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433 + } + protocol tcp + source { + address 109.228.0.124 + } + } + rule 2198 { + action accept + description FW5A5D7_3-TCP-ALLOW-51.219.222.28 + destination { + group { + address-group DT_FW5A5D7_3 + } + port 8172,3389,1723,1701,47 + } + protocol tcp + source { + address 51.219.222.28 + } + } + rule 2199 { + action accept + description FW1CB16_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW1CB16_1 + } + port 3306,27017,53 + } + protocol tcp_udp + } + rule 2200 { + action accept + description FWE47DA_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE47DA_1 + } + port 7770-7800,44445 + } + protocol tcp + } + rule 2201 { + action accept + description FW37E59_5-TCP-ALLOW-77.68.20.244 + destination { + group { + address-group DT_FW37E59_5 + } + port 30303 + } + protocol tcp + source { + address 77.68.20.244 + } + } + rule 2202 { + action accept + description FW274FD_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW274FD_1 + } + port 49152-65534 + } + protocol tcp + } + rule 2203 { + action accept + description FW6CD7E_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW6CD7E_2 + } + port 49152-65535 + } + protocol tcp + } + rule 2204 { + action accept + description FW826BA_3-TCP-ALLOW-178.17.252.59 + destination { + group { + address-group DT_FW826BA_3 + } + port 21 + } + protocol tcp + source { + address 178.17.252.59 + } + } + rule 2205 { + action accept + description FW89619_1-TCP_UDP-ALLOW-185.83.64.108 + destination { + group { + address-group DT_FW89619_1 + } + port 5060 + } + protocol tcp_udp + source { + address 185.83.64.108 + } + } + rule 2206 { + action accept + description FW0937A_1-TCP-ALLOW-83.135.134.13 + destination { + group { + address-group DT_FW0937A_1 + } + port 22 + } + protocol tcp + source { + address 83.135.134.13 + } + } + rule 2207 { + action accept + description FW2BB8D_1-TCP-ALLOW-77.68.112.64 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 27017,5000 + } + protocol tcp + source { + address 77.68.112.64 + } + } + rule 2208 { + action accept + description FW6CD7E_2-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW6CD7E_2 + } + port 53 + } + protocol tcp_udp + } + rule 2209 { + action accept + description FW1F3D0_6-TCP-ALLOW-194.73.17.47 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306,22 + } + protocol tcp + source { + address 194.73.17.47 + } + } + rule 2210 { + action accept + description FW0E383_9-TCP-ALLOW-77.68.115.33 + destination { + group { + address-group DT_FW0E383_9 + } + port 1433 + } + protocol tcp + source { + address 77.68.115.33 + } + } + rule 2211 { + action accept + description FWA3EA3_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA3EA3_1 + } + port 943 + } + protocol tcp + } + rule 2212 { + action accept + description FW6863A_4-TCP-ALLOW-82.165.100.25 + destination { + group { + address-group DT_FW6863A_4 + } + port 21-10000 + } + protocol tcp + source { + address 82.165.100.25 + } + } + rule 2213 { + action accept + description FWECBFB_14-TCP-ALLOW-109.228.59.50 + destination { + group { + address-group DT_FWECBFB_14 + } + port 22 + } + protocol tcp + source { + address 109.228.59.50 + } + } + rule 2214 { + action accept + description FW2F868_6-TCP-ALLOW-213.171.217.100 + destination { + group { + address-group DT_FW2F868_6 + } + port 22 + } + protocol tcp + source { + address 213.171.217.100 + } + } + rule 2215 { + action accept + description FWD7EAB_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD7EAB_1 + } + port 60000-60100 + } + protocol tcp + } + rule 2216 { + action accept + description FWEB321_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWEB321_1 + } + port 113,4190 + } + protocol tcp + } + rule 2217 { + action accept + description FW9C682_3-TCP-ALLOW-195.206.180.132 + destination { + group { + address-group DT_FW9C682_3 + } + port 8443,22 + } + protocol tcp + source { + address 195.206.180.132 + } + } + rule 2218 { + action accept + description VPN-8159-ANY-ALLOW-10.4.58.91 + destination { + group { + address-group DT_VPN-8159 + } + } + source { + address 10.4.58.91 + } + } + rule 2219 { + action accept + description VPN-21673-ANY-ALLOW-10.4.88.187 + destination { + group { + address-group DT_VPN-21673 + } + } + source { + address 10.4.88.187 + } + } + rule 2220 { + action accept + description VPN-21673-ANY-ALLOW-10.4.89.187 + destination { + group { + address-group DT_VPN-21673 + } + } + source { + address 10.4.89.187 + } + } + rule 2221 { + action accept + description VPN-21821-ANY-ALLOW-10.4.88.49 + destination { + group { + address-group DT_VPN-21821 + } + } + source { + address 10.4.88.49 + } + } + rule 2222 { + action accept + description VPN-21821-ANY-ALLOW-10.4.89.49 + destination { + group { + address-group DT_VPN-21821 + } + } + source { + address 10.4.89.49 + } + } + rule 2223 { + action accept + description FWECBFB_14-TCP-ALLOW-81.133.80.58 + destination { + group { + address-group DT_FWECBFB_14 + } + port 22 + } + protocol tcp + source { + address 81.133.80.58 + } + } + rule 2224 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.238 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.238 + } + } + rule 2225 { + action accept + description FW826BA_3-TCP-ALLOW-185.212.168.51 + destination { + group { + address-group DT_FW826BA_3 + } + port 3389,1433,21 + } + protocol tcp + source { + address 185.212.168.51 + } + } + rule 2226 { + action accept + description FW8B21D_1-ANY-ALLOW-212.187.250.2 + destination { + group { + address-group DT_FW8B21D_1 + } + } + source { + address 212.187.250.2 + } + } + rule 2227 { + action accept + description FW35F7B_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW35F7B_1 + } + port 1434 + } + protocol tcp_udp + } + rule 2228 { + action accept + description FWD338A_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD338A_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2229 { + action accept + description FW35F7B_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW35F7B_1 + } + port 56791 + } + protocol tcp + } + rule 2230 { + action accept + description FW0E383_9-TCP-ALLOW-77.68.77.114 + destination { + group { + address-group DT_FW0E383_9 + } + port 1433 + } + protocol tcp + source { + address 77.68.77.114 + } + } + rule 2231 { + action accept + description FW90AE3_1-TCP-ALLOW-194.74.137.17 + destination { + group { + address-group DT_FW90AE3_1 + } + port 22 + } + protocol tcp + source { + address 194.74.137.17 + } + } + rule 2232 { + action accept + description FW52F6F_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW52F6F_1 + } + port 53 + } + protocol tcp_udp + } + rule 2233 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.23.109 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306,22 + } + protocol tcp + source { + address 77.68.23.109 + } + } + rule 2234 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.247 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.210.247 + } + } + rule 2235 { + action accept + description FW4E314_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW4E314_1 + } + port 53 + } + protocol tcp_udp + } + rule 2236 { + action accept + description FW73573_2-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW73573_2 + } + port 25 + } + protocol tcp_udp + } + rule 2237 { + action accept + description FW0E383_9-TCP-ALLOW-77.68.93.89 + destination { + group { + address-group DT_FW0E383_9 + } + port 1433 + } + protocol tcp + source { + address 77.68.93.89 + } + } + rule 2238 { + action accept + description FW856FA_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW856FA_1 + } + port 6003 + } + protocol tcp + } + rule 2239 { + action accept + description FWECBFB_14-TCP-ALLOW-81.19.214.155 + destination { + group { + address-group DT_FWECBFB_14 + } + port 22 + } + protocol tcp + source { + address 81.19.214.155 + } + } + rule 2240 { + action accept + description FW826BA_3-TCP-ALLOW-51.219.168.170 + destination { + group { + address-group DT_FW826BA_3 + } + port 3389,1433,21 + } + protocol tcp + source { + address 51.219.168.170 + } + } + rule 2241 { + action accept + description FW30D21_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW30D21_1 + } + port 2083-2087,53,2812,2096,25,993,587 + } + protocol tcp_udp + } + rule 2242 { + action accept + description FWA076E_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA076E_1 + } + port 2199,2197 + } + protocol tcp + } + rule 2243 { + action accept + description FWA076E_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA076E_1 + } + port 8000-8010 + } + protocol tcp_udp + } + rule 2244 { + action accept + description FW8A3FC_3-TCP-ALLOW-82.165.166.41 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 8447,8443,443,80,22 + } + protocol tcp + source { + address 82.165.166.41 + } + } + rule 2245 { + action accept + description FW2F868_6-TCP-ALLOW-213.171.217.180 + destination { + group { + address-group DT_FW2F868_6 + } + port 22,80 + } + protocol tcp + source { + address 213.171.217.180 + } + } + rule 2246 { + action accept + description FW2F868_6-TCP-ALLOW-213.171.217.184 + destination { + group { + address-group DT_FW2F868_6 + } + port 22 + } + protocol tcp + source { + address 213.171.217.184 + } + } + rule 2247 { + action accept + description FW2F868_6-TCP-ALLOW-213.171.217.185 + destination { + group { + address-group DT_FW2F868_6 + } + port 22 + } + protocol tcp + source { + address 213.171.217.185 + } + } + rule 2248 { + action accept + description FW2F868_6-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW2F868_6 + } + port 161 + } + protocol udp + } + rule 2249 { + action accept + description FW2F868_6-TCP-ALLOW-213.171.217.102 + destination { + group { + address-group DT_FW2F868_6 + } + port 22,24 + } + protocol tcp + source { + address 213.171.217.102 + } + } + rule 2250 { + action accept + description FW9C682_3-TCP-ALLOW-80.194.78.162 + destination { + group { + address-group DT_FW9C682_3 + } + port 8443,22 + } + protocol tcp + source { + address 80.194.78.162 + } + } + rule 2251 { + action accept + description VPN-21822-ANY-ALLOW-10.4.54.47 + destination { + group { + address-group DT_VPN-21822 + } + } + source { + address 10.4.54.47 + } + } + rule 2252 { + action accept + description FW825C8_19-TCP-ALLOW-77.68.75.244 + destination { + group { + address-group DT_FW825C8_19 + } + port 1433 + } + protocol tcp + source { + address 77.68.75.244 + } + } + rule 2253 { + action accept + description FW2B279_4-TCP-ALLOW-195.147.173.92 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443,22 + } + protocol tcp + source { + address 195.147.173.92 + } + } + rule 2254 { + action accept + description FW1D511_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW1D511_2 + } + port 8090 + } + protocol tcp + } + rule 2255 { + action accept + description FW8A3FC_3-TCP-ALLOW-85.17.25.47 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 85.17.25.47 + } + } + rule 2256 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.89.209 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306,22 + } + protocol tcp + source { + address 77.68.89.209 + } + } + rule 2257 { + action accept + description FWE2AB5_8-TCP-ALLOW-213.171.217.184 + destination { + group { + address-group DT_FWE2AB5_8 + } + port 7000 + } + protocol tcp + source { + address 213.171.217.184 + } + } + rule 2258 { + action accept + description FW0E383_9-TCP-ALLOW-77.68.94.177 + destination { + group { + address-group DT_FW0E383_9 + } + port 1433 + } + protocol tcp + source { + address 77.68.94.177 + } + } + rule 2259 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.95.129 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306,22 + } + protocol tcp + source { + address 77.68.95.129 + } + } + rule 2260 { + action accept + description FW1F3D0_6-TCP-ALLOW-109.104.118.136 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 109.104.118.136 + } + } + rule 2261 { + action accept + description FW1FA9E_1-TCP-ALLOW-78.88.254.99 + destination { + group { + address-group DT_FW1FA9E_1 + } + port 9000,8200,5601,4444 + } + protocol tcp + source { + address 78.88.254.99 + } + } + rule 2262 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.46.27 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 175.157.46.27 + } + } + rule 2263 { + action accept + description FWA7A50_1-TCP-ALLOW-81.110.192.198 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp + source { + address 81.110.192.198 + } + } + rule 2264 { + action accept + description VPN-21822-ANY-ALLOW-10.4.55.47 + destination { + group { + address-group DT_VPN-21822 + } + } + source { + address 10.4.55.47 + } + } + rule 2265 { + action accept + description FW2BB8D_1-TCP-ALLOW-77.68.31.195 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 27017,5000 + } + protocol tcp + source { + address 77.68.31.195 + } + } + rule 2266 { + action accept + description FW45BEB_1-TCP-ALLOW-62.3.71.238 + destination { + group { + address-group DT_FW45BEB_1 + } + port 3389 + } + protocol tcp + source { + address 62.3.71.238 + } + } + rule 2267 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.113 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.113 + } + } + rule 2268 { + action accept + description VPN-23946-ANY-ALLOW-10.4.58.13 + destination { + group { + address-group DT_VPN-23946 + } + } + source { + address 10.4.58.13 + } + } + rule 2269 { + action accept + description FW98818_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW98818_1 + } + port 27015 + } + protocol tcp + } + rule 2270 { + action accept + description VPN-23946-ANY-ALLOW-10.4.59.13 + destination { + group { + address-group DT_VPN-23946 + } + } + source { + address 10.4.59.13 + } + } + rule 2271 { + action accept + description VPN-28031-ANY-ALLOW-10.4.88.197 + destination { + group { + address-group DT_VPN-28031 + } + } + source { + address 10.4.88.197 + } + } + rule 2272 { + action accept + description FW1F3D0_6-TCP-ALLOW-109.104.118.231 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 109.104.118.231 + } + } + rule 2273 { + action accept + description FW5A5D7_3-TCP_UDP-ALLOW-51.219.222.28 + destination { + group { + address-group DT_FW5A5D7_3 + } + port 500 + } + protocol tcp_udp + source { + address 51.219.222.28 + } + } + rule 2274 { + action accept + description FW32EFF_25-TCP-ALLOW-185.106.220.231 + destination { + group { + address-group DT_FW32EFF_25 + } + port 443 + } + protocol tcp + source { + address 185.106.220.231 + } + } + rule 2275 { + action accept + description FW1F3D0_6-TCP-ALLOW-109.104.118.66 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 109.104.118.66 + } + } + rule 2276 { + action accept + description FW934AE_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW934AE_1 + } + port 1194 + } + protocol udp + } + rule 2277 { + action accept + description VPN-28031-ANY-ALLOW-10.4.89.197 + destination { + group { + address-group DT_VPN-28031 + } + } + source { + address 10.4.89.197 + } + } + rule 2278 { + action accept + description FW6863A_4-TCP_UDP-ALLOW-82.165.166.41 + destination { + group { + address-group DT_FW6863A_4 + } + port 21-10000 + } + protocol tcp_udp + source { + address 82.165.166.41 + } + } + rule 2279 { + action accept + description FW1F3D0_6-TCP-ALLOW-109.104.119.162 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 109.104.119.162 + } + } + rule 2280 { + action accept + description FW1F3D0_6-TCP-ALLOW-109.74.199.143 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 109.74.199.143 + } + } + rule 2281 { + action accept + description FW1F3D0_6-TCP-ALLOW-185.92.25.48 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 185.92.25.48 + } + } + rule 2282 { + action accept + description FW1F3D0_6-TCP-ALLOW-207.148.2.40 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 207.148.2.40 + } + } + rule 2283 { + action accept + description FW1F3D0_6-TCP-ALLOW-45.76.235.62 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 45.76.235.62 + } + } + rule 2284 { + action accept + description FW1F3D0_6-TCP-ALLOW-45.76.236.93 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 45.76.236.93 + } + } + rule 2285 { + action accept + description FW1F3D0_6-TCP-ALLOW-45.76.59.5 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 45.76.59.5 + } + } + rule 2286 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.15.134 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4444,3306 + } + protocol tcp + source { + address 77.68.15.134 + } + } + rule 2287 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.22.208 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4444,3306 + } + protocol tcp + source { + address 77.68.22.208 + } + } + rule 2288 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.23.108 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 77.68.23.108 + } + } + rule 2289 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.23.54 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 77.68.23.54 + } + } + rule 2290 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.30.45 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 77.68.30.45 + } + } + rule 2291 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.7.198 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 77.68.7.198 + } + } + rule 2292 { + action accept + description VPN-29631-ANY-ALLOW-10.4.54.76 + destination { + group { + address-group DT_VPN-29631 + } + } + source { + address 10.4.54.76 + } + } + rule 2293 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.89.200 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4444,3306 + } + protocol tcp + source { + address 77.68.89.200 + } + } + rule 2294 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.91.50 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 77.68.91.50 + } + } + rule 2295 { + action accept + description FW1F3D0_6-TCP-ALLOW-82.165.206.230 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 82.165.206.230 + } + } + rule 2296 { + action accept + description FW1F3D0_6-TCP-ALLOW-82.165.207.109 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4444,3306 + } + protocol tcp + source { + address 82.165.207.109 + } + } + rule 2297 { + action accept + description FW1F3D0_6-TCP-ALLOW-94.196.156.5 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 94.196.156.5 + } + } + rule 2298 { + action accept + description FW1F3D0_6-TCP_UDP-ALLOW-77.68.15.134 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4567-4568 + } + protocol tcp_udp + source { + address 77.68.15.134 + } + } + rule 2299 { + action accept + description FW1F3D0_6-TCP_UDP-ALLOW-77.68.22.208 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4567-4568 + } + protocol tcp_udp + source { + address 77.68.22.208 + } + } + rule 2300 { + action accept + description FW1F3D0_6-TCP_UDP-ALLOW-77.68.23.109 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4567-4568 + } + protocol tcp_udp + source { + address 77.68.23.109 + } + } + rule 2301 { + action accept + description FW1F3D0_6-TCP_UDP-ALLOW-77.68.89.200 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4567-4568 + } + protocol tcp_udp + source { + address 77.68.89.200 + } + } + rule 2302 { + action accept + description FW05339_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW05339_1 + } + port 8085,5055,5013,5005,444 + } + protocol tcp + } + rule 2303 { + action accept + description FW32EFF_25-TCP-ALLOW-217.169.61.164 + destination { + group { + address-group DT_FW32EFF_25 + } + port 443 + } + protocol tcp + source { + address 217.169.61.164 + } + } + rule 2304 { + action accept + description FW89619_1-TCP_UDP-ALLOW-185.83.65.45 + destination { + group { + address-group DT_FW89619_1 + } + port 5060 + } + protocol tcp_udp + source { + address 185.83.65.45 + } + } + rule 2305 { + action accept + description VPN-13983-ANY-ALLOW-10.4.58.176 + destination { + group { + address-group DT_VPN-13983 + } + } + source { + address 10.4.58.176 + } + } + rule 2306 { + action accept + description FWDAF47_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWDAF47_1 + } + port 8090,7080,443,53 + } + protocol tcp_udp + } + rule 2307 { + action accept + description VPN-29631-ANY-ALLOW-10.4.55.77 + destination { + group { + address-group DT_VPN-29631 + } + } + source { + address 10.4.55.77 + } + } + rule 2308 { + action accept + description VPN-34309-ANY-ALLOW-10.4.58.142 + destination { + group { + address-group DT_VPN-34309 + } + } + source { + address 10.4.58.142 + } + } + rule 2309 { + action accept + description FW27949_2-TCP-ALLOW-138.124.142.180 + destination { + group { + address-group DT_FW27949_2 + } + port 443,80 + } + protocol tcp + source { + address 138.124.142.180 + } + } + rule 2310 { + action accept + description FWF8F85_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWF8F85_1 + } + port 3306 + } + protocol tcp_udp + } + rule 2311 { + action accept + description FWDAF47_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWDAF47_1 + } + port 40110-40210 + } + protocol tcp + } + rule 2312 { + action accept + description VPN-34309-ANY-ALLOW-10.4.59.142 + destination { + group { + address-group DT_VPN-34309 + } + } + source { + address 10.4.59.142 + } + } + rule 2313 { + action accept + description FWA0531_1-TCP-ALLOW-87.224.39.220 + destination { + group { + address-group DT_FWA0531_1 + } + port 22 + } + protocol tcp + source { + address 87.224.39.220 + } + } + rule 2314 { + action accept + description FW5A5D7_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW5A5D7_3 + } + port 1334 + } + protocol tcp + } + rule 2315 { + action accept + description FW8C927_1-TCP_UDP-ALLOW-84.92.125.78 + destination { + group { + address-group DT_FW8C927_1 + } + port 3306,22 + } + protocol tcp_udp + source { + address 84.92.125.78 + } + } + rule 2316 { + action accept + description FW8C927_1-TCP_UDP-ALLOW-88.208.238.152 + destination { + group { + address-group DT_FW8C927_1 + } + port 3306,22 + } + protocol tcp_udp + source { + address 88.208.238.152 + } + } + rule 2317 { + action accept + description FW81138_1-ICMP-ALLOW-82.165.232.19 + destination { + group { + address-group DT_FW81138_1 + } + } + protocol icmp + source { + address 82.165.232.19 + } + } + rule 2318 { + action accept + description FW28892_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW28892_1 + } + port 7000 + } + protocol tcp + } + rule 2319 { + action accept + description FWC96A1_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWC96A1_1 + } + port 222 + } + protocol tcp + } + rule 2320 { + action accept + description VPN-13983-ANY-ALLOW-10.4.59.176 + destination { + group { + address-group DT_VPN-13983 + } + } + source { + address 10.4.59.176 + } + } + rule 2321 { + action accept + description FW2FB61_1-TCP-ALLOW-5.183.104.15 + destination { + group { + address-group DT_FW2FB61_1 + } + port 22 + } + protocol tcp + source { + address 5.183.104.15 + } + } + rule 2322 { + action accept + description FW81138_1-ICMP-ALLOW-82.20.69.137 + destination { + group { + address-group DT_FW81138_1 + } + } + protocol icmp + source { + address 82.20.69.137 + } + } + rule 2323 { + action accept + description FW72F37_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW72F37_1 + } + port 7770-7800,44445 + } + protocol tcp + } + rule 2324 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-81.111.155.34 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 81.111.155.34 + } + } + rule 2325 { + action accept + description VPN-20306-ANY-ALLOW-10.4.88.173 + destination { + group { + address-group DT_VPN-20306 + } + } + source { + address 10.4.88.173 + } + } + rule 2326 { + action accept + description FW6C992_1-TCP-ALLOW-89.33.185.0_24 + destination { + group { + address-group DT_FW6C992_1 + } + port 8447,8443,22 + } + protocol tcp + source { + address 89.33.185.0/24 + } + } + rule 2327 { + action accept + description FW2FB61_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2FB61_1 + } + port 45000 + } + protocol tcp + } + rule 2328 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.46.202 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 175.157.46.202 + } + } + rule 2329 { + action accept + description FWF9C28_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF9C28_2 + } + port 7770-7800,44445 + } + protocol tcp + } + rule 2330 { + action accept + description FW3DBF8_9-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW3DBF8_9 + } + port 8088,8080,5090,5060,3478,1935 + } + protocol tcp_udp + } + rule 2331 { + action accept + description FW3DBF8_9-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW3DBF8_9 + } + port 5062,5061,5015,5001 + } + protocol tcp + } + rule 2332 { + action accept + description VPN-16402-ANY-ALLOW-10.4.88.60 + destination { + group { + address-group DT_VPN-16402 + } + } + source { + address 10.4.88.60 + } + } + rule 2333 { + action accept + description FWC1315_1-TCP-ALLOW-62.3.71.238 + destination { + group { + address-group DT_FWC1315_1 + } + port 3389 + } + protocol tcp + source { + address 62.3.71.238 + } + } + rule 2334 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA7A50_1 + } + port 8001,80 + } + protocol tcp_udp + } + rule 2335 { + action accept + description FWAFF0A_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWAFF0A_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2336 { + action accept + description FW2B279_4-TCP-ALLOW-195.20.253.19 + destination { + group { + address-group DT_FW2B279_4 + } + port 22 + } + protocol tcp + source { + address 195.20.253.19 + } + } + rule 2337 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.73 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.215.73 + } + } + rule 2338 { + action accept + description VPN-16402-ANY-ALLOW-10.4.89.60 + destination { + group { + address-group DT_VPN-16402 + } + } + source { + address 10.4.89.60 + } + } + rule 2339 { + action accept + description VPN-15951-ANY-ALLOW-10.4.86.90 + destination { + group { + address-group DT_VPN-15951 + } + } + source { + address 10.4.86.90 + } + } + rule 2340 { + action accept + description FW2BB8D_1-TCP-ALLOW-77.68.77.181 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 27017,5000 + } + protocol tcp + source { + address 77.68.77.181 + } + } + rule 2341 { + action accept + description FWE9F7D_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE9F7D_1 + } + port 4035 + } + protocol tcp + } + rule 2342 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.208.131 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.208.131 + } + } + rule 2343 { + action accept + description VPN-15951-ANY-ALLOW-10.4.87.90 + destination { + group { + address-group DT_VPN-15951 + } + } + source { + address 10.4.87.90 + } + } + rule 2344 { + action accept + description FW2BB8D_1-TCP-ALLOW-77.68.93.190 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 27017,5000 + } + protocol tcp + source { + address 77.68.93.190 + } + } + rule 2345 { + action accept + description VPN-8159-ANY-ALLOW-10.4.59.91 + destination { + group { + address-group DT_VPN-8159 + } + } + source { + address 10.4.59.91 + } + } + rule 2346 { + action accept + description VPN-12870-ANY-ALLOW-10.4.54.67 + destination { + group { + address-group DT_VPN-12870 + } + } + source { + address 10.4.54.67 + } + } + rule 2347 { + action accept + description FW930F3_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW930F3_1 + } + port 53 + } + protocol tcp_udp + } + rule 2348 { + action accept + description FW12C32_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW12C32_1 + } + port 465,53,25 + } + protocol tcp_udp + } + rule 2349 { + action accept + description FW28EC8_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW28EC8_1 + } + port 20443 + } + protocol tcp + } + rule 2350 { + action accept + description VPN-12870-ANY-ALLOW-10.4.55.68 + destination { + group { + address-group DT_VPN-12870 + } + } + source { + address 10.4.55.68 + } + } + rule 2351 { + action accept + description FW934AE_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW934AE_1 + } + port 32401,32400,8081 + } + protocol tcp_udp + } + rule 2352 { + action accept + description FW6863A_4-TCP-ALLOW-185.173.161.154 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 185.173.161.154 + } + } + rule 2353 { + action accept + description FW013EF_2-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW013EF_2 + } + port 10600-10998,9000-9398,5090,5060-5070 + } + protocol udp + } + rule 2354 { + action accept + description FW85040_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW85040_1 + } + port 3210 + } + protocol tcp_udp + } + rule 2355 { + action accept + description FW8B21D_1-TCP_UDP-ALLOW-131.153.100.98 + destination { + group { + address-group DT_FW8B21D_1 + } + port 22 + } + protocol tcp_udp + source { + address 131.153.100.98 + } + } + rule 2356 { + action accept + description FW8B21D_1-TCP_UDP-ALLOW-213.133.99.176 + destination { + group { + address-group DT_FW8B21D_1 + } + port 22 + } + protocol tcp_udp + source { + address 213.133.99.176 + } + } + rule 2357 { + action accept + description FW6EFD7_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW6EFD7_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2358 { + action accept + description FW8B21D_1-TCP_UDP-ALLOW-62.253.153.163 + destination { + group { + address-group DT_FW8B21D_1 + } + port 8443,22 + } + protocol tcp_udp + source { + address 62.253.153.163 + } + } + rule 2359 { + action accept + description FWCB0CF_7-TCP-ALLOW-212.159.153.201 + destination { + group { + address-group DT_FWCB0CF_7 + } + port 6443,5432-5434,5000-5100,3306-3308,990,989,22,21 + } + protocol tcp + source { + address 212.159.153.201 + } + } + rule 2360 { + action accept + description FW75CA4_6-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW75CA4_6 + } + port 51472,3747,3420 + } + protocol tcp + } + rule 2361 { + action accept + description FWF9C28_4-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF9C28_4 + } + port 23,7770-7800,44445,6109 + } + protocol tcp + } + rule 2362 { + action accept + description FW6B39D_1-TCP-ALLOW-120.72.95.88_29 + destination { + group { + address-group DT_FW6B39D_1 + } + port 3306 + } + protocol tcp + source { + address 120.72.95.88/29 + } + } + rule 2363 { + action accept + description FW934AE_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW934AE_1 + } + port 20000 + } + protocol tcp + } + rule 2364 { + action accept + description FW12C32_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW12C32_1 + } + port 2323,953 + } + protocol tcp + } + rule 2365 { + action accept + description FW49897_1-TCP-ALLOW-2.121.90.207 + destination { + group { + address-group DT_FW49897_1 + } + port 22 + } + protocol tcp + source { + address 2.121.90.207 + } + } + rule 2366 { + action accept + description FW6B39D_1-TCP-ALLOW-120.72.91.104_29 + destination { + group { + address-group DT_FW6B39D_1 + } + port 3306 + } + protocol tcp + source { + address 120.72.91.104/29 + } + } + rule 2367 { + action accept + description FW4F5EE_10-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW4F5EE_10 + } + port 83,86,82 + } + protocol tcp + } + rule 2368 { + action accept + description FWF791C_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF791C_1 + } + port 6001 + } + protocol tcp + } + rule 2369 { + action accept + description FWEF92E_5-ESP-ALLOW-109.228.37.19 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol esp + source { + address 109.228.37.19 + } + } + rule 2370 { + action accept + description FWE57AD_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE57AD_1 + } + port 57000-58000 + } + protocol tcp + } + rule 2371 { + action accept + description FWC0CE0_1-TCP-ALLOW-62.232.209.221 + destination { + group { + address-group DT_FWC0CE0_1 + } + port 49152-65535,8447,8443,22,21 + } + protocol tcp + source { + address 62.232.209.221 + } + } + rule 2372 { + action accept + description FW0192C_1-TCP-ALLOW-41.140.242.86 + destination { + group { + address-group DT_FW0192C_1 + } + port 3306,22 + } + protocol tcp + source { + address 41.140.242.86 + } + } + rule 2373 { + action accept + description FWEEC75_1-TCP-ALLOW-54.171.71.110 + destination { + group { + address-group DT_FWEEC75_1 + } + port 21 + } + protocol tcp + source { + address 54.171.71.110 + } + } + rule 2374 { + action accept + description FW8B21D_1-TCP_UDP-ALLOW-95.149.182.69 + destination { + group { + address-group DT_FW8B21D_1 + } + port 22 + } + protocol tcp_udp + source { + address 95.149.182.69 + } + } + rule 2375 { + action accept + description FW8B21D_1-TCP-ALLOW-185.201.16.0_22 + destination { + group { + address-group DT_FW8B21D_1 + } + port 25 + } + protocol tcp + source { + address 185.201.16.0/22 + } + } + rule 2376 { + action accept + description FW8B21D_1-TCP-ALLOW-213.133.99.176 + destination { + group { + address-group DT_FW8B21D_1 + } + port 25 + } + protocol tcp + source { + address 213.133.99.176 + } + } + rule 2377 { + action accept + description FW8B21D_1-TCP-ALLOW-95.211.160.147 + destination { + group { + address-group DT_FW8B21D_1 + } + port 25 + } + protocol tcp + source { + address 95.211.160.147 + } + } + rule 2378 { + action accept + description FW6863A_4-TCP-ALLOW-212.227.9.72 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 212.227.9.72 + } + } + rule 2379 { + action accept + description FW8B21D_1-ESP-ALLOW-ANY + destination { + group { + address-group DT_FW8B21D_1 + } + } + protocol esp + } + rule 2380 { + action accept + description FW8B21D_1-AH-ALLOW-ANY + destination { + group { + address-group DT_FW8B21D_1 + } + } + protocol ah + } + rule 2381 { + action accept + description FW8B21D_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW8B21D_1 + } + port 8181,4500,1194,993,941,500,53 + } + protocol tcp_udp + } + rule 2382 { + action accept + description FW6863A_4-TCP-ALLOW-85.17.25.47 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 85.17.25.47 + } + } + rule 2383 { + action accept + description FW6863A_4-TCP-ALLOW-91.232.105.39 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 91.232.105.39 + } + } + rule 2384 { + action accept + description FW6863A_4-TCP-ALLOW-93.190.142.120 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 93.190.142.120 + } + } + rule 2385 { + action accept + description FW6863A_4-TCP-ALLOW-95.168.171.130 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 95.168.171.130 + } + } + rule 2386 { + action accept + description FW6863A_4-TCP-ALLOW-95.168.171.157 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 95.168.171.157 + } + } + rule 2387 { + action accept + description FWD4A27_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD4A27_1 + } + port 32400 + } + protocol tcp + } + rule 2388 { + action accept + description FW2ACFF_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW2ACFF_1 + } + port 10299,60050-60055 + } + protocol tcp_udp + } + rule 2389 { + action accept + description FWCB0CF_7-TCP-ALLOW-193.248.62.45 + destination { + group { + address-group DT_FWCB0CF_7 + } + port 22 + } + protocol tcp + source { + address 193.248.62.45 + } + } + rule 2390 { + action accept + description FWCB0CF_7-TCP-ALLOW-78.249.208.17 + destination { + group { + address-group DT_FWCB0CF_7 + } + port 22 + } + protocol tcp + source { + address 78.249.208.17 + } + } + rule 2391 { + action accept + description FWC8E8E_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWC8E8E_1 + } + port 6000 + } + protocol tcp_udp + } + rule 2392 { + action accept + description FW30D21_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW30D21_1 + } + port 2476 + } + protocol tcp + } + rule 2393 { + action accept + description FW0192C_1-TCP-ALLOW-41.140.242.94 + destination { + group { + address-group DT_FW0192C_1 + } + port 3306,22 + } + protocol tcp + source { + address 41.140.242.94 + } + } + rule 2394 { + action accept + description FW59F39_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW59F39_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2395 { + action accept + description FWEF92E_7-ESP-ALLOW-77.68.77.57 + destination { + group { + address-group DT_FWEF92E_7 + } + } + protocol esp + source { + address 77.68.77.57 + } + } + rule 2396 { + action accept + description FW826BA_3-TCP-ALLOW-51.219.47.177 + destination { + group { + address-group DT_FW826BA_3 + } + port 3389,21 + } + protocol tcp + source { + address 51.219.47.177 + } + } + rule 2397 { + action accept + description FW826BA_3-TCP-ALLOW-86.172.128.50 + destination { + group { + address-group DT_FW826BA_3 + } + port 1433,21 + } + protocol tcp + source { + address 86.172.128.50 + } + } + rule 2398 { + action accept + description FW826BA_3-TCP-ALLOW-88.105.1.20 + destination { + group { + address-group DT_FW826BA_3 + } + port 21 + } + protocol tcp + source { + address 88.105.1.20 + } + } + rule 2399 { + action accept + description FW6863A_4-TCP-ALLOW-95.211.243.198 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 95.211.243.198 + } + } + rule 2400 { + action accept + description FW25843_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW25843_1 + } + port 9001,7070,5500,5488,5000,4500,4000,3500,3000,1883,1880 + } + protocol tcp + } + rule 2401 { + action accept + description FW89619_1-TCP_UDP-ALLOW-185.83.65.46 + destination { + group { + address-group DT_FW89619_1 + } + port 5060 + } + protocol tcp_udp + source { + address 185.83.65.46 + } + } + rule 2402 { + action accept + description FW5858F_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW5858F_1 + } + port 1883 + } + protocol tcp + } + rule 2403 { + action accept + description FW826BA_3-TCP-ALLOW-95.147.108.173 + destination { + group { + address-group DT_FW826BA_3 + } + port 21 + } + protocol tcp + source { + address 95.147.108.173 + } + } + rule 2404 { + action accept + description FW9C682_3-TCP-ALLOW-52.56.193.88 + destination { + group { + address-group DT_FW9C682_3 + } + port 3306 + } + protocol tcp + source { + address 52.56.193.88 + } + } + rule 2405 { + action accept + description FW0745F_5-TCP-ALLOW-109.228.63.82 + destination { + group { + address-group DT_FW0745F_5 + } + port 5666 + } + protocol tcp + source { + address 109.228.63.82 + } + } + rule 2406 { + action accept + description FWC0CE0_1-TCP-ALLOW-90.255.228.213 + destination { + group { + address-group DT_FWC0CE0_1 + } + port 49152-65535,8443,21 + } + protocol tcp + source { + address 90.255.228.213 + } + } + rule 2407 { + action accept + description FW210E2_8-AH-ALLOW-ANY + destination { + group { + address-group DT_FW210E2_8 + } + } + protocol ah + } + rule 2408 { + action accept + description FW210E2_8-ESP-ALLOW-ANY + destination { + group { + address-group DT_FW210E2_8 + } + } + protocol esp + } + rule 2409 { + action accept + description FW210E2_8-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW210E2_8 + } + port 41,62000,23,4500,50,9876,3391,88,135 + } + protocol tcp + } + rule 2410 { + action accept + description FW210E2_8-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW210E2_8 + } + port 500 + } + protocol udp + } + rule 2411 { + action accept + description VPN-8625-ANY-ALLOW-10.4.54.103 + destination { + group { + address-group DT_VPN-8625 + } + } + source { + address 10.4.54.103 + } + } + rule 2412 { + action accept + description VPN-8625-ANY-ALLOW-10.4.55.104 + destination { + group { + address-group DT_VPN-8625 + } + } + source { + address 10.4.55.104 + } + } + rule 2413 { + action accept + description FW73A64_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW73A64_1 + } + port 61616,8181,8161,8082,4244,4243,4242,4241 + } + protocol tcp + } + rule 2414 { + action accept + description VPN-19135-ANY-ALLOW-10.4.86.165 + destination { + group { + address-group DT_VPN-19135 + } + } + source { + address 10.4.86.165 + } + } + rule 2415 { + action accept + description FWCB0CF_7-TCP-ALLOW-82.65.107.3 + destination { + group { + address-group DT_FWCB0CF_7 + } + port 22 + } + protocol tcp + source { + address 82.65.107.3 + } + } + rule 2416 { + action accept + description FWCB0CF_7-TCP-ALLOW-195.2.139.221 + destination { + group { + address-group DT_FWCB0CF_7 + } + port 5432-5434,3306-3308 + } + protocol tcp + source { + address 195.2.139.221 + } + } + rule 2417 { + action accept + description VPN-19135-ANY-ALLOW-10.4.87.165 + destination { + group { + address-group DT_VPN-19135 + } + } + source { + address 10.4.87.165 + } + } + rule 2418 { + action accept + description FW2BB8D_1-TCP-ALLOW-87.75.109.83 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 27017,5000 + } + protocol tcp + source { + address 87.75.109.83 + } + } + rule 2419 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.83 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.83 + } + } + rule 2420 { + action accept + description FW2ED4D_2-TCP-ALLOW-84.92.65.192 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 22 + } + protocol tcp + source { + address 84.92.65.192 + } + } + rule 2421 { + action accept + description FW73A64_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW73A64_1 + } + port 9200,5601,4247,4246,4245 + } + protocol tcp_udp + } + rule 2422 { + action accept + description FW4735F_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW4735F_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2423 { + action accept + description FW2ED4D_2-TCP-ALLOW-109.176.154.238 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 7990,3389 + } + protocol tcp + source { + address 109.176.154.238 + } + } + rule 2424 { + action accept + description FW6863A_4-TCP-ALLOW-95.211.243.206 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 95.211.243.206 + } + } + rule 2425 { + action accept + description FW89619_1-TCP_UDP-ALLOW-81.133.80.114 + destination { + group { + address-group DT_FW89619_1 + } + port 5060 + } + protocol tcp_udp + source { + address 81.133.80.114 + } + } + rule 2426 { + action accept + description FW89619_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW89619_1 + } + port 5090 + } + protocol tcp_udp + } + rule 2427 { + action accept + description FW8A57A_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW8A57A_1 + } + port 49155,49154,7700,53,43 + } + protocol tcp_udp + } + rule 2428 { + action accept + description FW8C72E_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW8C72E_1 + } + port 500,4500 + } + protocol udp + } + rule 2429 { + action accept + description FW2ED4D_2-TCP-ALLOW-18.135.66.162 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 18.135.66.162 + } + } + rule 2430 { + action accept + description FW2C5AE_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2C5AE_1 + } + port 58080,58008,8545,7175 + } + protocol tcp + } + rule 2431 { + action accept + description FW2ED4D_2-TCP-ALLOW-80.209.144.52 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 80.209.144.52 + } + } + rule 2432 { + action accept + description FW2ED4D_2-TCP-ALLOW-82.153.21.103 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 7990,3389 + } + protocol tcp + source { + address 82.153.21.103 + } + } + rule 2433 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.41 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.215.41 + } + } + rule 2434 { + action accept + description FW0745F_5-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0745F_5 + } + port 32770,8001,7801 + } + protocol tcp + } + rule 2435 { + action accept + description FW85E02_11-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW85E02_11 + } + port 5090,5060 + } + protocol tcp_udp + } + rule 2436 { + action accept + description VPN-21982-ANY-ALLOW-10.4.58.43 + destination { + group { + address-group DT_VPN-21982 + } + } + source { + address 10.4.58.43 + } + } + rule 2437 { + action accept + description FW2ED4D_2-TCP-ALLOW-82.17.52.191 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 82.17.52.191 + } + } + rule 2438 { + action accept + description FW66347_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW66347_1 + } + port 53 + } + protocol tcp_udp + } + rule 2439 { + action accept + description FW11082_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW11082_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2440 { + action accept + description VPN-21982-ANY-ALLOW-10.4.59.43 + destination { + group { + address-group DT_VPN-21982 + } + } + source { + address 10.4.59.43 + } + } + rule 2441 { + action accept + description FW2BB8D_1-TCP-ALLOW-92.207.193.203 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 5000 + } + protocol tcp + source { + address 92.207.193.203 + } + } + rule 2442 { + action accept + description FWC2D30_1-TCP-ALLOW-77.99.253.161 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443,22,21 + } + protocol tcp + source { + address 77.99.253.161 + } + } + rule 2443 { + action accept + description FW0E383_9-TCP-ALLOW-77.99.245.103 + destination { + group { + address-group DT_FW0E383_9 + } + port 3389 + } + protocol tcp + source { + address 77.99.245.103 + } + } + rule 2444 { + action accept + description FW2ED4D_2-TCP-ALLOW-82.19.19.52 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 7990,3389 + } + protocol tcp + source { + address 82.19.19.52 + } + } + rule 2445 { + action accept + description FWEF92E_7-AH-ALLOW-77.68.77.57 + destination { + group { + address-group DT_FWEF92E_7 + } + } + protocol ah + source { + address 77.68.77.57 + } + } + rule 2446 { + action accept + description VPN-16450-ANY-ALLOW-10.4.88.99 + destination { + group { + address-group DT_VPN-16450 + } + } + source { + address 10.4.88.99 + } + } + rule 2447 { + action accept + description FW2ED4D_2-TCP-ALLOW-82.2.186.129 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 82.2.186.129 + } + } + rule 2448 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.157 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.215.157 + } + } + rule 2449 { + action accept + description FW8EA04_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW8EA04_1 + } + port 1194 + } + protocol udp + } + rule 2450 { + action accept + description FW2ED4D_2-TCP-ALLOW-82.21.59.207 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 82.21.59.207 + } + } + rule 2451 { + action accept + description FWC2D30_1-TCP-ALLOW-82.9.22.158 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443,21 + } + protocol tcp + source { + address 82.9.22.158 + } + } + rule 2452 { + action accept + description FWF3A1B_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWF3A1B_1 + } + port 1981,53 + } + protocol tcp_udp + } + rule 2453 { + action accept + description FWEF92E_5-ESP-ALLOW-77.68.11.54 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol esp + source { + address 77.68.11.54 + } + } + rule 2454 { + action accept + description FW2ED4D_2-TCP-ALLOW-82.40.177.186 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 82.40.177.186 + } + } + rule 2455 { + action accept + description FW0C25B_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0C25B_1 + } + port 49152-65535,5224 + } + protocol tcp + } + rule 2456 { + action accept + description FW85A7C_1-TCP-ALLOW-82.24.242.137 + destination { + group { + address-group DT_FW85A7C_1 + } + port 22 + } + protocol tcp + source { + address 82.24.242.137 + } + } + rule 2457 { + action accept + description FW2ED4D_2-TCP-ALLOW-82.68.25.66 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 82.68.25.66 + } + } + rule 2458 { + action accept + description FW826BA_3-TCP-ALLOW-51.89.148.173 + destination { + group { + address-group DT_FW826BA_3 + } + port 1433 + } + protocol tcp + source { + address 51.89.148.173 + } + } + rule 2459 { + action accept + description FWA69A0_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA69A0_1 + } + port 48402 + } + protocol udp + } + rule 2460 { + action accept + description FW2ED4D_2-TCP-ALLOW-82.69.79.85 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 82.69.79.85 + } + } + rule 2461 { + action accept + description FWEF92E_5-ESP-ALLOW-77.68.77.149 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol esp + source { + address 77.68.77.149 + } + } + rule 2462 { + action accept + description FWEF92E_6-ESP-ALLOW-77.68.77.57 + destination { + group { + address-group DT_FWEF92E_6 + } + } + protocol esp + source { + address 77.68.77.57 + } + } + rule 2463 { + action accept + description FWEF92E_7-TCP-ALLOW-77.68.8.74 + destination { + group { + address-group DT_FWEF92E_7 + } + port 3389,445 + } + protocol tcp + source { + address 77.68.8.74 + } + } + rule 2464 { + action accept + description FW49C3D_4-TCP-ALLOW-77.68.8.74 + destination { + group { + address-group DT_FW49C3D_4 + } + port 3389,445,443,80 + } + protocol tcp + source { + address 77.68.8.74 + } + } + rule 2465 { + action accept + description FW49C3D_6-TCP-ALLOW-77.68.8.74 + destination { + group { + address-group DT_FW49C3D_6 + } + port 3389,445 + } + protocol tcp + source { + address 77.68.8.74 + } + } + rule 2466 { + action accept + description FW34C91_3-TCP-ALLOW-77.68.121.4 + destination { + group { + address-group DT_FW34C91_3 + } + port 1433 + } + protocol tcp + source { + address 77.68.121.4 + } + } + rule 2467 { + action accept + description VPN-16450-ANY-ALLOW-10.4.89.99 + destination { + group { + address-group DT_VPN-16450 + } + } + source { + address 10.4.89.99 + } + } + rule 2468 { + action accept + description FW0BB22_1-AH-ALLOW-ANY + destination { + group { + address-group DT_FW0BB22_1 + } + } + protocol ah + } + rule 2469 { + action accept + description FW2ED4D_2-TCP-ALLOW-86.139.57.116 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 86.139.57.116 + } + } + rule 2470 { + action accept + description FW9E550_1-TCP-ALLOW-86.142.67.13 + destination { + group { + address-group DT_FW9E550_1 + } + port 3389 + } + protocol tcp + source { + address 86.142.67.13 + } + } + rule 2471 { + action accept + description FW8B21D_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW8B21D_1 + } + port 2096,2095,2087,2086,2083,2082 + } + protocol tcp + } + rule 2472 { + action accept + description FW050AC_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW050AC_1 + } + port 2087 + } + protocol tcp + } + rule 2473 { + action accept + description FW1FA9E_1-TCP-ALLOW-109.228.50.206 + destination { + group { + address-group DT_FW1FA9E_1 + } + port 5432 + } + protocol tcp + source { + address 109.228.50.206 + } + } + rule 2474 { + action accept + description FW8A3FC_3-TCP-ALLOW-217.23.11.155 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 217.23.11.155 + } + } + rule 2475 { + action accept + description FW2ED4D_2-TCP-ALLOW-88.96.110.198 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 88.96.110.198 + } + } + rule 2476 { + action accept + description FWEAE53_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWEAE53_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2477 { + action accept + description VPN-19474-ANY-ALLOW-10.4.88.161 + destination { + group { + address-group DT_VPN-19474 + } + } + source { + address 10.4.88.161 + } + } + rule 2478 { + action accept + description VPN-19474-ANY-ALLOW-10.4.89.161 + destination { + group { + address-group DT_VPN-19474 + } + } + source { + address 10.4.89.161 + } + } + rule 2479 { + action accept + description FW90AE3_1-TCP-ALLOW-68.33.220.233 + destination { + group { + address-group DT_FW90AE3_1 + } + port 22 + } + protocol tcp + source { + address 68.33.220.233 + } + } + rule 2480 { + action accept + description FWC2D30_1-TCP-ALLOW-86.10.163.127 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443,21 + } + protocol tcp + source { + address 86.10.163.127 + } + } + rule 2481 { + action accept + description FW2FB61_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW2FB61_1 + } + port 60182 + } + protocol udp + } + rule 2482 { + action accept + description FW85A7C_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW85A7C_1 + } + port 2457,2456 + } + protocol tcp_udp + } + rule 2483 { + action accept + description FWBED52_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWBED52_1 + } + port 1221,9000 + } + protocol tcp + } + rule 2484 { + action accept + description FWA86ED_101-TCP-ALLOW-90.250.2.109 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 90.250.2.109 + } + } + rule 2485 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.49 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.213.49 + } + } + rule 2486 { + action accept + description FWEF92E_5-ESP-ALLOW-77.68.77.70 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol esp + source { + address 77.68.77.70 + } + } + rule 2487 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.250 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.250 + } + } + rule 2488 { + action accept + description FW8A3FC_3-TCP-ALLOW-95.168.171.131 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 95.168.171.131 + } + } + rule 2489 { + action accept + description FW2379F_14-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2379F_14 + } + port 48030,10997,10993,10992,10991,10902,1723,1701 + } + protocol tcp + } + rule 2490 { + action accept + description FW8C927_1-TCP-ALLOW-84.92.125.78 + destination { + group { + address-group DT_FW8C927_1 + } + port 80 + } + protocol tcp + source { + address 84.92.125.78 + } + } + rule 2491 { + action accept + description FWC2D30_1-TCP-ALLOW-86.146.220.229 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443,21 + } + protocol tcp + source { + address 86.146.220.229 + } + } + rule 2492 { + action accept + description FW2B279_4-TCP-ALLOW-2.218.5.59 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443,22 + } + protocol tcp + source { + address 2.218.5.59 + } + } + rule 2493 { + action accept + description VPN-18830-ANY-ALLOW-10.4.86.156 + destination { + group { + address-group DT_VPN-18830 + } + } + source { + address 10.4.86.156 + } + } + rule 2494 { + action accept + description VPN-18830-ANY-ALLOW-10.4.87.156 + destination { + group { + address-group DT_VPN-18830 + } + } + source { + address 10.4.87.156 + } + } + rule 2495 { + action accept + description FWEF92E_5-ESP-ALLOW-77.68.92.33 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol esp + source { + address 77.68.92.33 + } + } + rule 2496 { + action accept + description FWA86ED_101-TCP-ALLOW-146.198.100.105 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 146.198.100.105 + } + } + rule 2497 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.55 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.211.55 + } + } + rule 2498 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.84.113 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 123.231.84.113 + } + } + rule 2499 { + action accept + description FW8C72E_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW8C72E_1 + } + port 60134,60135 + } + protocol tcp + } + rule 2500 { + action accept + description FWAB44B_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWAB44B_1 + } + port 3306 + } + protocol tcp_udp + } + rule 2501 { + action accept + description FW2379F_14-TCP-ALLOW-51.148.87.29 + destination { + group { + address-group DT_FW2379F_14 + } + port 3389,21 + } + protocol tcp + source { + address 51.148.87.29 + } + } + rule 2502 { + action accept + description VPN-23738-ANY-ALLOW-10.4.56.13 + destination { + group { + address-group DT_VPN-23738 + } + } + source { + address 10.4.56.13 + } + } + rule 2503 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.100 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.210.100 + } + } + rule 2504 { + action accept + description FW996B4_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW996B4_2 + } + port 43595,30160 + } + protocol tcp + } + rule 2505 { + action accept + description FW8871B_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW8871B_1 + } + port 15672,8083,8082,8081,5672 + } + protocol tcp + } + rule 2506 { + action accept + description FWAB44B_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWAB44B_1 + } + port 9090,8069,5432 + } + protocol tcp + } + rule 2507 { + action accept + description FW6187E_1-ICMP-ALLOW-85.214.201.250 + destination { + group { + address-group DT_FW6187E_1 + } + } + protocol icmp + source { + address 85.214.201.250 + } + } + rule 2508 { + action accept + description FW8A3FC_3-TCP-ALLOW-217.23.11.126 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 217.23.11.126 + } + } + rule 2509 { + action accept + description FW78137_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW78137_1 + } + port 1-65535 + } + protocol tcp + } + rule 2510 { + action accept + description FW32EFF_25-TCP-ALLOW-46.252.65.10 + destination { + group { + address-group DT_FW32EFF_25 + } + port 443 + } + protocol tcp + source { + address 46.252.65.10 + } + } + rule 2511 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.50 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.214.50 + } + } + rule 2512 { + action accept + description FW6A684_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW6A684_1 + } + port 53 + } + protocol tcp_udp + } + rule 2513 { + action accept + description FWF48EB_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF48EB_1 + } + port 9204,9202,3395 + } + protocol tcp + } + rule 2514 { + action accept + description FW44217_2-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW44217_2 + } + port 443,80 + } + protocol tcp_udp + } + rule 2515 { + action accept + description FW6187E_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW6187E_1 + } + port 2282 + } + protocol tcp + } + rule 2516 { + action accept + description FW8AFF1_7-TCP-ALLOW-109.228.0.58 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433 + } + protocol tcp + source { + address 109.228.0.58 + } + } + rule 2517 { + action accept + description VPN-34501-ANY-ALLOW-10.4.86.235 + destination { + group { + address-group DT_VPN-34501 + } + } + source { + address 10.4.86.235 + } + } + rule 2518 { + action accept + description FW1271A_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW1271A_2 + } + port 5090,5061,5060,5015,5001 + } + protocol tcp + } + rule 2519 { + action accept + description FW1271A_2-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW1271A_2 + } + port 9000-10999,5090,5060 + } + protocol udp + } + rule 2520 { + action accept + description FW1226C_3-TCP-ALLOW-216.113.160.71 + destination { + group { + address-group DT_FW1226C_3 + } + port 80,22 + } + protocol tcp + source { + address 216.113.160.71 + } + } + rule 2521 { + action accept + description FW32EFF_16-TCP-ALLOW-84.19.45.82 + destination { + group { + address-group DT_FW32EFF_16 + } + port 33888 + } + protocol tcp + source { + address 84.19.45.82 + } + } + rule 2522 { + action accept + description FW03F2E_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW03F2E_1 + } + port 1194 + } + protocol udp + } + rule 2523 { + action accept + description FW03F2E_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW03F2E_1 + } + port 4432,4431,4430 + } + protocol tcp + } + rule 2524 { + action accept + description FW1226C_3-TCP-ALLOW-216.113.162.65 + destination { + group { + address-group DT_FW1226C_3 + } + port 80,22 + } + protocol tcp + source { + address 216.113.162.65 + } + } + rule 2525 { + action accept + description VPN-20306-ANY-ALLOW-10.4.89.173 + destination { + group { + address-group DT_VPN-20306 + } + } + source { + address 10.4.89.173 + } + } + rule 2526 { + action accept + description FW8A49A_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW8A49A_1 + } + port 2525,8448-65535 + } + protocol tcp + } + rule 2527 { + action accept + description FWD3431_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD3431_2 + } + port 43595,30377,30289 + } + protocol tcp + } + rule 2528 { + action accept + description FW1226C_3-TCP-ALLOW-66.135.200.200 + destination { + group { + address-group DT_FW1226C_3 + } + port 80,22 + } + protocol tcp + source { + address 66.135.200.200 + } + } + rule 2529 { + action accept + description FW1226C_3-TCP-ALLOW-193.28.178.38 + destination { + group { + address-group DT_FW1226C_3 + } + port 80 + } + protocol tcp + source { + address 193.28.178.38 + } + } + rule 2530 { + action accept + description FWAE88B_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWAE88B_1 + } + port 65432,8080,7300,1195,1194,993,587,465,443,442,143,110,80,53,22 + } + protocol tcp_udp + } + rule 2531 { + action accept + description FW1226C_3-TCP-ALLOW-195.234.136.80 + destination { + group { + address-group DT_FW1226C_3 + } + port 80 + } + protocol tcp + source { + address 195.234.136.80 + } + } + rule 2532 { + action accept + description FW1226C_3-TCP-ALLOW-93.94.41.83 + destination { + group { + address-group DT_FW1226C_3 + } + port 80 + } + protocol tcp + source { + address 93.94.41.83 + } + } + rule 2533 { + action accept + description VPN-6103-ANY-ALLOW-10.4.56.102 + destination { + group { + address-group DT_VPN-6103 + } + } + source { + address 10.4.56.102 + } + } + rule 2534 { + action accept + description VPN-6103-ANY-ALLOW-10.4.57.102 + destination { + group { + address-group DT_VPN-6103 + } + } + source { + address 10.4.57.102 + } + } + rule 2535 { + action accept + description FW9E550_1-TCP-ALLOW-86.198.190.104 + destination { + group { + address-group DT_FW9E550_1 + } + port 3389 + } + protocol tcp + source { + address 86.198.190.104 + } + } + rule 2536 { + action accept + description FW34C91_3-TCP-ALLOW-81.149.71.244 + destination { + group { + address-group DT_FW34C91_3 + } + port 1433 + } + protocol tcp + source { + address 81.149.71.244 + } + } + rule 2537 { + action accept + description FW0BB22_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW0BB22_1 + } + port 27917,27017,9592,9092,1080,587 + } + protocol tcp_udp + } + rule 2538 { + action accept + description FWC2D30_1-TCP-ALLOW-89.213.26.156 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443,21 + } + protocol tcp + source { + address 89.213.26.156 + } + } + rule 2539 { + action accept + description FW34C91_3-UDP-ALLOW-81.149.71.244 + destination { + group { + address-group DT_FW34C91_3 + } + port 1434 + } + protocol udp + source { + address 81.149.71.244 + } + } + rule 2540 { + action accept + description VPN-17207-ANY-ALLOW-10.4.86.121 + destination { + group { + address-group DT_VPN-17207 + } + } + source { + address 10.4.86.121 + } + } + rule 2541 { + action accept + description FW0B352_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW0B352_1 + } + port 4500,500 + } + protocol udp + } + rule 2542 { + action accept + description FW85E02_11-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW85E02_11 + } + port 5854,5853,5061 + } + protocol tcp + } + rule 2543 { + action accept + description FW0BB22_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0BB22_1 + } + port 9200,8082 + } + protocol tcp + } + rule 2544 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.140 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.213.140 + } + } + rule 2545 { + action accept + description FWC2D30_1-TCP-ALLOW-91.125.244.28 + destination { + group { + address-group DT_FWC2D30_1 + } + port 21 + } + protocol tcp + source { + address 91.125.244.28 + } + } + rule 2546 { + action accept + description FWA86ED_101-TCP-ALLOW-86.172.252.221 + destination { + group { + address-group DT_FWA86ED_101 + } + port 80-3389 + } + protocol tcp + source { + address 86.172.252.221 + } + } + rule 2547 { + action accept + description FWC2D30_1-TCP-ALLOW-92.207.184.106 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443,21 + } + protocol tcp + source { + address 92.207.184.106 + } + } + rule 2548 { + action accept + description FW45F3D_1-ANY-ALLOW-146.255.0.198 + destination { + group { + address-group DT_FW45F3D_1 + } + } + source { + address 146.255.0.198 + } + } + rule 2549 { + action accept + description FWBFDED_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWBFDED_1 + } + port 1723,445 + } + protocol tcp + } + rule 2550 { + action accept + description FW8A3FC_3-TCP-ALLOW-212.227.9.72 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 212.227.9.72 + } + } + rule 2551 { + action accept + description FWE928F_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE928F_1 + } + port 2082,2083,2086,2087,2096 + } + protocol tcp + } + rule 2552 { + action accept + description FW5CBB2_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW5CBB2_1 + } + port 2082,2083,2086,2087 + } + protocol tcp + } + rule 2553 { + action accept + description FW63230_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW63230_1 + } + port 445,139 + } + protocol tcp_udp + } + rule 2554 { + action accept + description FW90AE3_1-TCP-ALLOW-71.244.176.5 + destination { + group { + address-group DT_FW90AE3_1 + } + port 22 + } + protocol tcp + source { + address 71.244.176.5 + } + } + rule 2555 { + action accept + description FWA4BC8_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA4BC8_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2556 { + action accept + description VPN-17207-ANY-ALLOW-10.4.87.121 + destination { + group { + address-group DT_VPN-17207 + } + } + source { + address 10.4.87.121 + } + } + rule 2557 { + action accept + description VPN-17558-ANY-ALLOW-10.4.86.143 + destination { + group { + address-group DT_VPN-17558 + } + } + source { + address 10.4.86.143 + } + } + rule 2558 { + action accept + description FWB2CD2_1-TCP-ALLOW-86.167.68.241 + destination { + group { + address-group DT_FWB2CD2_1 + } + port 21 + } + protocol tcp + source { + address 86.167.68.241 + } + } + rule 2559 { + action accept + description FW32EFF_25-TCP-ALLOW-84.19.45.82 + destination { + group { + address-group DT_FW32EFF_25 + } + port 33888,443 + } + protocol tcp + source { + address 84.19.45.82 + } + } + rule 2560 { + action accept + description FW44217_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW44217_2 + } + port 9001,7946,2376 + } + protocol tcp + } + rule 2561 { + action accept + description FW7DAE2_3-TCP-ALLOW-212.227.253.11 + destination { + group { + address-group DT_FW7DAE2_3 + } + port 25,22 + } + protocol tcp + source { + address 212.227.253.11 + } + } + rule 2562 { + action accept + description FW7DAE2_3-TCP-ALLOW-217.160.126.118 + destination { + group { + address-group DT_FW7DAE2_3 + } + port 25,22 + } + protocol tcp + source { + address 217.160.126.118 + } + } + rule 2563 { + action accept + description FWAF6E8_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWAF6E8_1 + } + port 2082,2083,2086,2087,2096 + } + protocol tcp + } + rule 2564 { + action accept + description FWCD7CE_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWCD7CE_1 + } + port 49152-65534 + } + protocol tcp + } + rule 2565 { + action accept + description FW32EFF_16-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW32EFF_16 + } + port 47779,47778,47777,47776 + } + protocol tcp + } + rule 2566 { + action accept + description FW0745F_5-TCP-ALLOW-77.68.117.222 + destination { + group { + address-group DT_FW0745F_5 + } + port 49170 + } + protocol tcp + source { + address 77.68.117.222 + } + } + rule 2567 { + action accept + description FWC2D30_1-TCP-ALLOW-92.207.199.107 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443,22,21 + } + protocol tcp + source { + address 92.207.199.107 + } + } + rule 2568 { + action accept + description FW8AFF1_7-TCP-ALLOW-109.228.0.89 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433 + } + protocol tcp + source { + address 109.228.0.89 + } + } + rule 2569 { + action accept + description FW8A3FC_3-TCP-ALLOW-190.2.130.41 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 190.2.130.41 + } + } + rule 2570 { + action accept + description FWFDCC7_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWFDCC7_1 + } + port 10000 + } + protocol tcp_udp + } + rule 2571 { + action accept + description FWF19FB_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF19FB_2 + } + port 43595,40001,30616-30631,30531,30204-30435 + } + protocol tcp + } + rule 2572 { + action accept + description FW2B279_4-TCP-ALLOW-213.171.217.107 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443,22 + } + protocol tcp + source { + address 213.171.217.107 + } + } + rule 2573 { + action accept + description FW4E314_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW4E314_1 + } + port 21543,888 + } + protocol tcp + } + rule 2574 { + action accept + description FW73215_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW73215_1 + } + port 4380 + } + protocol udp + } + rule 2575 { + action accept + description VPN-31301-ANY-ALLOW-10.4.86.223 + destination { + group { + address-group DT_VPN-31301 + } + } + source { + address 10.4.86.223 + } + } + rule 2576 { + action accept + description FW8428B_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW8428B_1 + } + port 48402 + } + protocol udp + } + rule 2577 { + action accept + description FWF3A1B_1-TCP_UDP-ALLOW-185.195.124.169 + destination { + group { + address-group DT_FWF3A1B_1 + } + port 2222 + } + protocol tcp_udp + source { + address 185.195.124.169 + } + } + rule 2578 { + action accept + description FW34C91_3-UDP-ALLOW-77.68.121.4 + destination { + group { + address-group DT_FW34C91_3 + } + port 1434 + } + protocol udp + source { + address 77.68.121.4 + } + } + rule 2579 { + action accept + description FW73215_1-TCP-ALLOW-82.38.58.135 + destination { + group { + address-group DT_FW73215_1 + } + port 10685 + } + protocol tcp + source { + address 82.38.58.135 + } + } + rule 2580 { + action accept + description FW52F6F_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW52F6F_1 + } + port 8888 + } + protocol tcp + } + rule 2581 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.86 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.213.86 + } + } + rule 2582 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.125.13 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 123.231.125.13 + } + } + rule 2583 { + action accept + description FWEE03C_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWEE03C_1 + } + port 2087,2083 + } + protocol tcp + } + rule 2584 { + action accept + description FW748B7_1-TCP-ALLOW-157.231.123.154 + destination { + group { + address-group DT_FW748B7_1 + } + port 22 + } + protocol tcp + source { + address 157.231.123.154 + } + } + rule 2585 { + action accept + description VPN-34501-ANY-ALLOW-10.4.87.235 + destination { + group { + address-group DT_VPN-34501 + } + } + source { + address 10.4.87.235 + } + } + rule 2586 { + action accept + description FWE47DA_1-TCP-ALLOW-81.134.85.245 + destination { + group { + address-group DT_FWE47DA_1 + } + port 22 + } + protocol tcp + source { + address 81.134.85.245 + } + } + rule 2587 { + action accept + description FWD61BF_1-ANY-ALLOW-193.237.81.213_32 + destination { + group { + address-group DT_FWD61BF_1 + } + } + source { + address 193.237.81.213/32 + } + } + rule 2588 { + action accept + description FW2B279_4-TCP-ALLOW-23.106.238.241 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443,3306,22 + } + protocol tcp + source { + address 23.106.238.241 + } + } + rule 2589 { + action accept + description FW2B279_4-TCP-ALLOW-35.204.202.196 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443,3306,22 + } + protocol tcp + source { + address 35.204.202.196 + } + } + rule 2590 { + action accept + description FW2B279_4-TCP-ALLOW-35.242.141.128 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443,3306,22 + } + protocol tcp + source { + address 35.242.141.128 + } + } + rule 2591 { + action accept + description FWC2EF2_2-TCP-ALLOW-90.251.221.19 + destination { + group { + address-group DT_FWC2EF2_2 + } + port 995,993,587,465,143,110,25,22 + } + protocol tcp + source { + address 90.251.221.19 + } + } + rule 2592 { + action accept + description VPN-14673-ANY-ALLOW-10.4.88.44 + destination { + group { + address-group DT_VPN-14673 + } + } + source { + address 10.4.88.44 + } + } + rule 2593 { + action accept + description FWA83DF_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA83DF_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2594 { + action accept + description FW31525_6-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW31525_6 + } + port 35467 + } + protocol tcp + } + rule 2595 { + action accept + description FW4293B_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW4293B_1 + } + port 9080,8888,8881,7815,8419 + } + protocol tcp + } + rule 2596 { + action accept + description FW4AE7D_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW4AE7D_1 + } + port 8083,81 + } + protocol tcp + } + rule 2597 { + action accept + description FWC2D30_1-TCP-ALLOW-143.52.53.22 + destination { + group { + address-group DT_FWC2D30_1 + } + port 22 + } + protocol tcp + source { + address 143.52.53.22 + } + } + rule 2598 { + action accept + description FW44217_2-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW44217_2 + } + port 7946,4789 + } + protocol udp + } + rule 2599 { + action accept + description FW2B279_4-TCP-ALLOW-46.249.82.162 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443,22 + } + protocol tcp + source { + address 46.249.82.162 + } + } + rule 2600 { + action accept + description FW27949_2-TCP-ALLOW-80.95.202.106 + destination { + group { + address-group DT_FW27949_2 + } + port 443,80 + } + protocol tcp + source { + address 80.95.202.106 + } + } + rule 2601 { + action accept + description FWEF92E_5-ESP-ALLOW-77.68.93.82 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol esp + source { + address 77.68.93.82 + } + } + rule 2602 { + action accept + description FW2ACFF_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2ACFF_1 + } + port 8082,5093 + } + protocol tcp + } + rule 2603 { + action accept + description FWC2EF2_2-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWC2EF2_2 + } + port 10000,953,53 + } + protocol tcp_udp + } + rule 2604 { + action accept + description FW0C8E1_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0C8E1_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2605 { + action accept + description FWA86ED_101-TCP_UDP-ALLOW-82.5.189.5 + destination { + group { + address-group DT_FWA86ED_101 + } + port 1-65535 + } + protocol tcp_udp + source { + address 82.5.189.5 + } + } + rule 2606 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.208.179 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.208.179 + } + } + rule 2607 { + action accept + description FWEF92E_5-ESP-ALLOW-88.208.198.93 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol esp + source { + address 88.208.198.93 + } + } + rule 2608 { + action accept + description FW5658C_1-TCP-ALLOW-39.45.43.109 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443 + } + protocol tcp + source { + address 39.45.43.109 + } + } + rule 2609 { + action accept + description FW5658C_1-TCP-ALLOW-5.67.3.195 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443 + } + protocol tcp + source { + address 5.67.3.195 + } + } + rule 2610 { + action accept + description FWDCA36_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWDCA36_3 + } + port 49152-65534,5901 + } + protocol tcp + } + rule 2611 { + action accept + description FWE928F_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWE928F_1 + } + port 53 + } + protocol tcp_udp + } + rule 2612 { + action accept + description FW69D6D_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW69D6D_2 + } + port 5001,5090,5060,5015 + } + protocol tcp + } + rule 2613 { + action accept + description FW69D6D_2-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW69D6D_2 + } + port 5090,5060,9000-9500 + } + protocol udp + } + rule 2614 { + action accept + description VPN-9765-ANY-ALLOW-10.4.56.45 + destination { + group { + address-group DT_VPN-9765 + } + } + source { + address 10.4.56.45 + } + } + rule 2615 { + action accept + description VPN-9765-ANY-ALLOW-10.4.57.45 + destination { + group { + address-group DT_VPN-9765 + } + } + source { + address 10.4.57.45 + } + } + rule 2616 { + action accept + description FW4C136_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW4C136_1 + } + port 1194 + } + protocol tcp_udp + } + rule 2617 { + action accept + description FW6F539_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW6F539_1 + } + port 49152-65534 + } + protocol tcp + } + rule 2618 { + action accept + description FWDD089_5-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWDD089_5 + } + port 5666-5667,12489 + } + protocol tcp_udp + } + rule 2619 { + action accept + description FWDD089_5-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWDD089_5 + } + port 161-162 + } + protocol tcp + } + rule 2620 { + action accept + description FWEF92E_5-AH-ALLOW-109.228.37.19 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol ah + source { + address 109.228.37.19 + } + } + rule 2621 { + action accept + description FW0A5C4_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0A5C4_1 + } + port 9000,6697,6667,5000 + } + protocol tcp + } + rule 2622 { + action accept + description FWEF92E_5-AH-ALLOW-77.68.11.54 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol ah + source { + address 77.68.11.54 + } + } + rule 2623 { + action accept + description FW2BB8D_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2BB8D_1 + } + port 7990 + } + protocol tcp + } + rule 2624 { + action accept + description FWAF6E8_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWAF6E8_1 + } + port 7770-7800,44445,53 + } + protocol tcp_udp + } + rule 2625 { + action accept + description FW81286_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW81286_1 + } + port 2082,2083,2086,2087,2096 + } + protocol tcp + } + rule 2626 { + action accept + description FW05064_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW05064_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2627 { + action accept + description FWD7382_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWD7382_1 + } + port 4500,1701,500 + } + protocol udp + } + rule 2628 { + action accept + description FWD7382_1-TCP-ALLOW-174.91.7.198 + destination { + group { + address-group DT_FWD7382_1 + } + port 3389 + } + protocol tcp + source { + address 174.91.7.198 + } + } + rule 2629 { + action accept + description VPN-9484-ANY-ALLOW-10.4.56.164 + destination { + group { + address-group DT_VPN-9484 + } + } + source { + address 10.4.56.164 + } + } + rule 2630 { + action accept + description VPN-9484-ANY-ALLOW-10.4.57.164 + destination { + group { + address-group DT_VPN-9484 + } + } + source { + address 10.4.57.164 + } + } + rule 2631 { + action accept + description VPN-9749-ANY-ALLOW-10.4.58.144 + destination { + group { + address-group DT_VPN-9749 + } + } + source { + address 10.4.58.144 + } + } + rule 2632 { + action accept + description FWEF92E_5-AH-ALLOW-77.68.77.149 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol ah + source { + address 77.68.77.149 + } + } + rule 2633 { + action accept + description FW10FEE_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW10FEE_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2634 { + action accept + description FW5658C_1-TCP-ALLOW-5.71.30.141 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443 + } + protocol tcp + source { + address 5.71.30.141 + } + } + rule 2635 { + action accept + description VPN-9749-ANY-ALLOW-10.4.59.144 + destination { + group { + address-group DT_VPN-9749 + } + } + source { + address 10.4.59.144 + } + } + rule 2636 { + action accept + description FWEF92E_5-AH-ALLOW-77.68.77.70 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol ah + source { + address 77.68.77.70 + } + } + rule 2637 { + action accept + description FWEF92E_5-AH-ALLOW-77.68.92.33 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol ah + source { + address 77.68.92.33 + } + } + rule 2638 { + action accept + description FWEF92E_5-AH-ALLOW-77.68.93.82 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol ah + source { + address 77.68.93.82 + } + } + rule 2639 { + action accept + description FWEF92E_6-AH-ALLOW-77.68.77.57 + destination { + group { + address-group DT_FWEF92E_6 + } + } + protocol ah + source { + address 77.68.77.57 + } + } + rule 2640 { + action accept + description FWEF92E_6-TCP-ALLOW-77.68.8.74 + destination { + group { + address-group DT_FWEF92E_6 + } + port 3389,445 + } + protocol tcp + source { + address 77.68.8.74 + } + } + rule 2641 { + action accept + description FWEF92E_5-AH-ALLOW-88.208.198.93 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol ah + source { + address 88.208.198.93 + } + } + rule 2642 { + action accept + description FWEF92E_7-TCP-ALLOW-87.224.33.215 + destination { + group { + address-group DT_FWEF92E_7 + } + port 3389,445 + } + protocol tcp + source { + address 87.224.33.215 + } + } + rule 2643 { + action accept + description FWEF92E_7-TCP-ALLOW-87.224.6.174 + destination { + group { + address-group DT_FWEF92E_7 + } + port 3389,445 + } + protocol tcp + source { + address 87.224.6.174 + } + } + rule 2644 { + action accept + description FWEF92E_5-TCP-ALLOW-109.228.37.19 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 109.228.37.19 + } + } + rule 2645 { + action accept + description FW49C3D_4-TCP-ALLOW-87.224.33.215 + destination { + group { + address-group DT_FW49C3D_4 + } + port 3389,445,80 + } + protocol tcp + source { + address 87.224.33.215 + } + } + rule 2646 { + action accept + description FW49C3D_4-TCP-ALLOW-82.0.198.226 + destination { + group { + address-group DT_FW49C3D_4 + } + port 3389,445 + } + protocol tcp + source { + address 82.0.198.226 + } + } + rule 2647 { + action accept + description FW49C3D_6-TCP-ALLOW-82.0.198.226 + destination { + group { + address-group DT_FW49C3D_6 + } + port 3389,445 + } + protocol tcp + source { + address 82.0.198.226 + } + } + rule 2648 { + action accept + description FW49C3D_6-TCP-ALLOW-83.100.136.74 + destination { + group { + address-group DT_FW49C3D_6 + } + port 3389,445 + } + protocol tcp + source { + address 83.100.136.74 + } + } + rule 2649 { + action accept + description FWEF92E_6-TCP-ALLOW-87.224.33.215 + destination { + group { + address-group DT_FWEF92E_6 + } + port 3389,445 + } + protocol tcp + source { + address 87.224.33.215 + } + } + rule 2650 { + action accept + description FWEF92E_5-TCP-ALLOW-194.145.189.162 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 194.145.189.162 + } + } + rule 2651 { + action accept + description FW3DBF8_9-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW3DBF8_9 + } + port 9000-10999 + } + protocol udp + } + rule 2652 { + action accept + description VPN-19807-ANY-ALLOW-10.4.86.172 + destination { + group { + address-group DT_VPN-19807 + } + } + source { + address 10.4.86.172 + } + } + rule 2653 { + action accept + description FWEEC75_1-TCP-ALLOW-82.8.245.40 + destination { + group { + address-group DT_FWEEC75_1 + } + port 21 + } + protocol tcp + source { + address 82.8.245.40 + } + } + rule 2654 { + action accept + description FW3AD6F_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW3AD6F_1 + } + port 53,465 + } + protocol tcp_udp + } + rule 2655 { + action accept + description FWCDBC7_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWCDBC7_1 + } + port 53 + } + protocol tcp_udp + } + rule 2656 { + action accept + description FWA373F_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA373F_1 + } + port 2087,2086,2083,2082 + } + protocol tcp + } + rule 2657 { + action accept + description FW2B279_4-TCP-ALLOW-94.155.221.50 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443,22 + } + protocol tcp + source { + address 94.155.221.50 + } + } + rule 2658 { + action accept + description FWC2D30_1-TCP-ALLOW-213.171.217.107 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443,22 + } + protocol tcp + source { + address 213.171.217.107 + } + } + rule 2659 { + action accept + description VPN-30791-ANY-ALLOW-10.4.88.215 + destination { + group { + address-group DT_VPN-30791 + } + } + source { + address 10.4.88.215 + } + } + rule 2660 { + action accept + description VPN-30791-ANY-ALLOW-10.4.89.215 + destination { + group { + address-group DT_VPN-30791 + } + } + source { + address 10.4.89.215 + } + } + rule 2661 { + action accept + description FW2EF2C_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW2EF2C_1 + } + port 10000,3478 + } + protocol udp + } + rule 2662 { + action accept + description FW32EFF_49-TCP-ALLOW-195.217.232.0_26 + destination { + group { + address-group DT_FW32EFF_49 + } + port 5589 + } + protocol tcp + source { + address 195.217.232.0/26 + } + } + rule 2663 { + action accept + description FW4AE7D_1-TCP-ALLOW-81.136.8.24 + destination { + group { + address-group DT_FW4AE7D_1 + } + port 3389 + } + protocol tcp + source { + address 81.136.8.24 + } + } + rule 2664 { + action accept + description FW2EF2C_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW2EF2C_1 + } + port 5222 + } + protocol tcp_udp + } + rule 2665 { + action accept + description FW48A55_2-TCP-ALLOW-86.29.225.60 + destination { + group { + address-group DT_FW48A55_2 + } + port 443,80,22 + } + protocol tcp + source { + address 86.29.225.60 + } + } + rule 2666 { + action accept + description FW48A55_2-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW48A55_2 + } + port 1337 + } + protocol udp + } + rule 2667 { + action accept + description VPN-11913-ANY-ALLOW-10.4.56.191 + destination { + group { + address-group DT_VPN-11913 + } + } + source { + address 10.4.56.191 + } + } + rule 2668 { + action accept + description FWEF92E_5-TCP-ALLOW-194.145.189.163 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 194.145.189.163 + } + } + rule 2669 { + action accept + description FW8AFF1_7-TCP-ALLOW-109.228.0.90 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433 + } + protocol tcp + source { + address 109.228.0.90 + } + } + rule 2670 { + action accept + description FW8AFF1_7-TCP-ALLOW-109.228.24.66 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433 + } + protocol tcp + source { + address 109.228.24.66 + } + } + rule 2671 { + action accept + description VPN-11913-ANY-ALLOW-10.4.57.191 + destination { + group { + address-group DT_VPN-11913 + } + } + source { + address 10.4.57.191 + } + } + rule 2672 { + action accept + description FW73573_2-TCP-ALLOW-86.9.185.195 + destination { + group { + address-group DT_FW73573_2 + } + port 22 + } + protocol tcp + source { + address 86.9.185.195 + } + } + rule 2673 { + action accept + description VPN-17558-ANY-ALLOW-10.4.87.143 + destination { + group { + address-group DT_VPN-17558 + } + } + source { + address 10.4.87.143 + } + } + rule 2674 { + action accept + description FW748B7_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW748B7_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2675 { + action accept + description FW16375_5-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW16375_5 + } + port 2082,2083,2086,2087 + } + protocol tcp + } + rule 2676 { + action accept + description FW5A77C_16-TCP-ALLOW-88.98.204.68 + destination { + group { + address-group DT_FW5A77C_16 + } + port 22 + } + protocol tcp + source { + address 88.98.204.68 + } + } + rule 2677 { + action accept + description FW73573_1-TCP-ALLOW-86.9.185.195 + destination { + group { + address-group DT_FW73573_1 + } + port 22 + } + protocol tcp + source { + address 86.9.185.195 + } + } + rule 2678 { + action accept + description FWEF92E_5-TCP-ALLOW-194.145.190.4 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 194.145.190.4 + } + } + rule 2679 { + action accept + description FWC2D30_1-TCP-ALLOW-140.82.112.0_20 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443 + } + protocol tcp + source { + address 140.82.112.0/20 + } + } + rule 2680 { + action accept + description FW62858_12-ICMP-ALLOW-77.68.122.41 + destination { + group { + address-group DT_FW62858_12 + } + } + protocol icmp + source { + address 77.68.122.41 + } + } + rule 2681 { + action accept + description FWB118A_1-TCP-ALLOW-147.148.96.136 + destination { + group { + address-group DT_FWB118A_1 + } + port 49152-65534,8447,8443,22,21,20 + } + protocol tcp + source { + address 147.148.96.136 + } + } + rule 2682 { + action accept + description FW5A77C_16-TCP-ALLOW-92.207.237.42 + destination { + group { + address-group DT_FW5A77C_16 + } + port 10000,22 + } + protocol tcp + source { + address 92.207.237.42 + } + } + rule 2683 { + action accept + description FW364CF_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW364CF_1 + } + port 4022,8099 + } + protocol tcp + } + rule 2684 { + action accept + description VPN-25822-ANY-ALLOW-10.4.54.42 + destination { + group { + address-group DT_VPN-25822 + } + } + source { + address 10.4.54.42 + } + } + rule 2685 { + action accept + description FW7F28A_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW7F28A_1 + } + port 10051,10050 + } + protocol tcp + } + rule 2686 { + action accept + description FW8AFF1_7-TCP-ALLOW-109.228.53.159 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433 + } + protocol tcp + source { + address 109.228.53.159 + } + } + rule 2687 { + action accept + description FWE47DA_1-TCP-ALLOW-185.22.211.0_24 + destination { + group { + address-group DT_FWE47DA_1 + } + port 22 + } + protocol tcp + source { + address 185.22.211.0/24 + } + } + rule 2688 { + action accept + description FWC6301_1-TCP-ALLOW-95.34.208.4 + destination { + group { + address-group DT_FWC6301_1 + } + port 22 + } + protocol tcp + source { + address 95.34.208.4 + } + } + rule 2689 { + action accept + description FW45000_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW45000_1 + } + port 990 + } + protocol tcp + } + rule 2690 { + action accept + description FW481D7_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW481D7_1 + } + port 6789 + } + protocol tcp + } + rule 2691 { + action accept + description VPN-8203-ANY-ALLOW-10.4.59.109 + destination { + group { + address-group DT_VPN-8203 + } + } + source { + address 10.4.59.109 + } + } + rule 2692 { + action accept + description VPN-3575-ANY-ALLOW-10.4.54.124 + destination { + group { + address-group DT_VPN-3575 + } + } + source { + address 10.4.54.124 + } + } + rule 2693 { + action accept + description VPN-3575-ANY-ALLOW-10.4.55.125 + destination { + group { + address-group DT_VPN-3575 + } + } + source { + address 10.4.55.125 + } + } + rule 2694 { + action accept + description FW42661_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW42661_3 + } + port 44445,25672,15672,9876,7770-7800 + } + protocol tcp + } + rule 2695 { + action accept + description FWBF494_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWBF494_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2696 { + action accept + description FWD0E22_4-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD0E22_4 + } + port 8000,19005 + } + protocol tcp + } + rule 2697 { + action accept + description FW98818_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW98818_1 + } + port 27015 + } + protocol udp + } + rule 2698 { + action accept + description FW62858_12-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW62858_12 + } + port 5001,5000 + } + protocol tcp + } + rule 2699 { + action accept + description VPN-34006-ANY-ALLOW-10.4.86.242 + destination { + group { + address-group DT_VPN-34006 + } + } + source { + address 10.4.86.242 + } + } + rule 2700 { + action accept + description VPN-34006-ANY-ALLOW-10.4.87.242 + destination { + group { + address-group DT_VPN-34006 + } + } + source { + address 10.4.87.242 + } + } + rule 2701 { + action accept + description FWF879C_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF879C_1 + } + port 8888 + } + protocol tcp + } + rule 2702 { + action accept + description FWEF92E_5-TCP-ALLOW-77.68.11.54 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 77.68.11.54 + } + } + rule 2703 { + action accept + description FWEF92E_5-TCP-ALLOW-77.68.74.89 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 77.68.74.89 + } + } + rule 2704 { + action accept + description FWEF92E_5-TCP-ALLOW-77.68.77.149 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 77.68.77.149 + } + } + rule 2705 { + action accept + description FW8A57A_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW8A57A_1 + } + port 49153,5666 + } + protocol tcp + } + rule 2706 { + action accept + description FW62858_12-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW62858_12 + } + port 5090,5061,5060 + } + protocol tcp_udp + } + rule 2707 { + action accept + description FW62858_12-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW62858_12 + } + port 9000-10999 + } + protocol udp + } + rule 2708 { + action accept + description FW0E2EE_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW0E2EE_1 + } + port 1024-65535 + } + protocol tcp_udp + } + rule 2709 { + action accept + description FWEEC75_1-TCP-ALLOW-82.5.80.210 + destination { + group { + address-group DT_FWEEC75_1 + } + port 22 + } + protocol tcp + source { + address 82.5.80.210 + } + } + rule 2710 { + action accept + description FW4F81F_4-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW4F81F_4 + } + port 26900,27005,27015,51000,51005,51030 + } + protocol tcp_udp + } + rule 2711 { + action accept + description VPN-7902-ANY-ALLOW-10.4.56.78 + destination { + group { + address-group DT_VPN-7902 + } + } + source { + address 10.4.56.78 + } + } + rule 2712 { + action accept + description VPN-7902-ANY-ALLOW-10.4.57.78 + destination { + group { + address-group DT_VPN-7902 + } + } + source { + address 10.4.57.78 + } + } + rule 2713 { + action accept + description FWB36A0_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWB36A0_1 + } + port 20-21,990 + } + protocol tcp_udp + } + rule 2714 { + action accept + description FWD2082_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD2082_1 + } + port 8001,8002 + } + protocol tcp + } + rule 2715 { + action accept + description FW8A3FC_3-TCP-ALLOW-212.8.242.171 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 212.8.242.171 + } + } + rule 2716 { + action accept + description FWB9699_11-TCP-ALLOW-213.171.217.184 + destination { + group { + address-group DT_FWB9699_11 + } + port 443,80,8800,22 + } + protocol tcp + source { + address 213.171.217.184 + } + } + rule 2717 { + action accept + description VPN-11083-ANY-ALLOW-10.4.54.186 + destination { + group { + address-group DT_VPN-11083 + } + } + source { + address 10.4.54.186 + } + } + rule 2718 { + action accept + description VPN-11083-ANY-ALLOW-10.4.55.187 + destination { + group { + address-group DT_VPN-11083 + } + } + source { + address 10.4.55.187 + } + } + rule 2719 { + action accept + description VPN-34583-ANY-ALLOW-10.4.86.243 + destination { + group { + address-group DT_VPN-34583 + } + } + source { + address 10.4.86.243 + } + } + rule 2720 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.84.155 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 123.231.84.155 + } + } + rule 2721 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.117 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.215.117 + } + } + rule 2722 { + action accept + description FW7A9B0_9-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW7A9B0_9 + } + port 11112 + } + protocol tcp + } + rule 2723 { + action accept + description FW3F465_1-TCP-ALLOW-77.68.127.177 + destination { + group { + address-group DT_FW3F465_1 + } + port 3306 + } + protocol tcp + source { + address 77.68.127.177 + } + } + rule 2724 { + action accept + description VPN-34583-ANY-ALLOW-10.4.87.243 + destination { + group { + address-group DT_VPN-34583 + } + } + source { + address 10.4.87.243 + } + } + rule 2725 { + action accept + description FW930F3_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW930F3_1 + } + port 9089,5900,5666,5272 + } + protocol tcp + } + rule 2726 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.165 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.165 + } + } + rule 2727 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.140 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.140 + } + } + rule 2728 { + action accept + description FW90AE3_1-TCP-ALLOW-82.11.114.136 + destination { + group { + address-group DT_FW90AE3_1 + } + port 3306,22 + } + protocol tcp + source { + address 82.11.114.136 + } + } + rule 2729 { + action accept + description FW73215_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW73215_1 + } + port 27015 + } + protocol tcp_udp + } + rule 2730 { + action accept + description FWC2EF2_1-TCP-ALLOW-18.130.156.250 + destination { + group { + address-group DT_FWC2EF2_1 + } + port 22 + } + protocol tcp + source { + address 18.130.156.250 + } + } + rule 2731 { + action accept + description FWC2EF2_1-TCP-ALLOW-90.251.221.19 + destination { + group { + address-group DT_FWC2EF2_1 + } + port 22 + } + protocol tcp + source { + address 90.251.221.19 + } + } + rule 2732 { + action accept + description FW90AE3_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW90AE3_1 + } + port 8765,8001,8000 + } + protocol tcp + } + rule 2733 { + action accept + description FWC2EF2_1-TCP-ALLOW-87.74.110.191 + destination { + group { + address-group DT_FWC2EF2_1 + } + port 8443 + } + protocol tcp + source { + address 87.74.110.191 + } + } + rule 2734 { + action accept + description FWEF92E_5-TCP-ALLOW-77.68.77.70 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 77.68.77.70 + } + } + rule 2735 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.93 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.93 + } + } + rule 2736 { + action accept + description FW81138_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW81138_1 + } + port 123 + } + protocol udp + } + rule 2737 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.208.64 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.208.64 + } + } + rule 2738 { + action accept + description FW03B35_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW03B35_1 + } + port 1-65535 + } + protocol tcp_udp + } + rule 2739 { + action accept + description VPN-19807-ANY-ALLOW-10.4.87.172 + destination { + group { + address-group DT_VPN-19807 + } + } + source { + address 10.4.87.172 + } + } + rule 2740 { + action accept + description FW5658C_1-TCP-ALLOW-94.12.73.154 + destination { + group { + address-group DT_FW5658C_1 + } + port 8447 + } + protocol tcp + source { + address 94.12.73.154 + } + } + rule 2741 { + action accept + description FW5658C_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW5658C_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2742 { + action accept + description FW0B352_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW0B352_1 + } + port 3443 + } + protocol tcp_udp + } + rule 2743 { + action accept + description FWEF92E_5-TCP-ALLOW-77.68.8.74 + destination { + group { + address-group DT_FWEF92E_5 + } + port 3389,445,443 + } + protocol tcp + source { + address 77.68.8.74 + } + } + rule 2744 { + action accept + description FWEF92E_5-TCP-ALLOW-77.68.92.33 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 77.68.92.33 + } + } + rule 2745 { + action accept + description FWEF92E_5-TCP-ALLOW-77.68.93.82 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 77.68.93.82 + } + } + rule 2746 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.44 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.214.44 + } + } + rule 2747 { + action accept + description FW34C91_3-TCP-ALLOW-188.220.176.104 + destination { + group { + address-group DT_FW34C91_3 + } + port 1433 + } + protocol tcp + source { + address 188.220.176.104 + } + } + rule 2748 { + action accept + description FW3F465_1-TCP-ALLOW-77.68.16.101 + destination { + group { + address-group DT_FW3F465_1 + } + port 3306 + } + protocol tcp + source { + address 77.68.16.101 + } + } + rule 2749 { + action accept + description FWEF92E_5-TCP-ALLOW-87.224.33.215 + destination { + group { + address-group DT_FWEF92E_5 + } + port 3389,445,443 + } + protocol tcp + source { + address 87.224.33.215 + } + } + rule 2750 { + action accept + description FW34C91_3-UDP-ALLOW-188.220.176.104 + destination { + group { + address-group DT_FW34C91_3 + } + port 1434 + } + protocol udp + source { + address 188.220.176.104 + } + } + rule 2751 { + action accept + description FWE47DA_1-TCP-ALLOW-185.22.208.0_25 + destination { + group { + address-group DT_FWE47DA_1 + } + port 22 + } + protocol tcp + source { + address 185.22.208.0/25 + } + } + rule 2752 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.187 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.214.187 + } + } + rule 2753 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.84 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.84 + } + } + rule 2754 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-116.206.246.52 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 116.206.246.52 + } + } + rule 2755 { + action accept + description FW8AFF1_7-TCP-ALLOW-77.68.92.154 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433 + } + protocol tcp + source { + address 77.68.92.154 + } + } + rule 2756 { + action accept + description FW8AFF1_7-TCP-ALLOW-77.68.93.156 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433 + } + protocol tcp + source { + address 77.68.93.156 + } + } + rule 2757 { + action accept + description VPN-24398-ANY-ALLOW-10.4.88.151 + destination { + group { + address-group DT_VPN-24398 + } + } + source { + address 10.4.88.151 + } + } + rule 2758 { + action accept + description VPN-24398-ANY-ALLOW-10.4.89.151 + destination { + group { + address-group DT_VPN-24398 + } + } + source { + address 10.4.89.151 + } + } + rule 2759 { + action accept + description VPN-24589-ANY-ALLOW-10.4.56.9 + destination { + group { + address-group DT_VPN-24589 + } + } + source { + address 10.4.56.9 + } + } + rule 2760 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.29 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.29 + } + } + rule 2761 { + action accept + description FWC7D36_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWC7D36_1 + } + port 27017,11080 + } + protocol tcp + } + rule 2762 { + action accept + description FWBB718_1-TCP_UDP-ALLOW-77.68.73.116 + destination { + group { + address-group DT_FWBB718_1 + } + port 1433 + } + protocol tcp_udp + source { + address 77.68.73.116 + } + } + rule 2763 { + action accept + description FWBB718_1-UDP-ALLOW-77.68.73.116 + destination { + group { + address-group DT_FWBB718_1 + } + port 1434 + } + protocol udp + source { + address 77.68.73.116 + } + } + rule 2764 { + action accept + description FWB9699_11-TCP-ALLOW-213.171.217.102 + destination { + group { + address-group DT_FWB9699_11 + } + port 22,80,443,8800 + } + protocol tcp + source { + address 213.171.217.102 + } + } + rule 2765 { + action accept + description FW18E6E_3-TCP-ALLOW-103.8.164.5 + destination { + group { + address-group DT_FW18E6E_3 + } + port 22 + } + protocol tcp + source { + address 103.8.164.5 + } + } + rule 2766 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.193 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.213.193 + } + } + rule 2768 { + action accept + description FW26F0A_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW26F0A_1 + } + port 53 + } + protocol tcp_udp + } + rule 2769 { + action accept + description FWCC18F_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWCC18F_2 + } + port 8883,1883 + } + protocol tcp + } + rule 2771 { + action accept + description FW633DD_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW633DD_1 + } + port 28967,14002,9984,9983,9982,9981,8888,8884 + } + protocol tcp + } + rule 2772 { + action accept + description FWDEDB9_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWDEDB9_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2773 { + action accept + description VPN-18646-ANY-ALLOW-10.4.88.109 + destination { + group { + address-group DT_VPN-18646 + } + } + source { + address 10.4.88.109 + } + } + rule 2774 { + action accept + description VPN-18646-ANY-ALLOW-10.4.89.109 + destination { + group { + address-group DT_VPN-18646 + } + } + source { + address 10.4.89.109 + } + } + rule 2775 { + action accept + description FWA0531_1-TCP-ALLOW-87.224.39.221 + destination { + group { + address-group DT_FWA0531_1 + } + port 8082,3003,22 + } + protocol tcp + source { + address 87.224.39.221 + } + } + rule 2776 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.94 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.94 + } + } + rule 2777 { + action accept + description FWA0531_1-TCP-ALLOW-92.237.97.92 + destination { + group { + address-group DT_FWA0531_1 + } + port 8082,3003,22 + } + protocol tcp + source { + address 92.237.97.92 + } + } + rule 2778 { + action accept + description VPN-25822-ANY-ALLOW-10.4.55.42 + destination { + group { + address-group DT_VPN-25822 + } + } + source { + address 10.4.55.42 + } + } + rule 2779 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.88 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.211.88 + } + } + rule 2780 { + action accept + description FWC2D30_1-TCP-ALLOW-143.55.64.0_20 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443 + } + protocol tcp + source { + address 143.55.64.0/20 + } + } + rule 2781 { + action accept + description FW18E6E_3-TCP-ALLOW-194.176.78.206 + destination { + group { + address-group DT_FW18E6E_3 + } + port 22 + } + protocol tcp + source { + address 194.176.78.206 + } + } + rule 2782 { + action accept + description FW18E6E_3-TCP-ALLOW-195.243.221.50 + destination { + group { + address-group DT_FW18E6E_3 + } + port 22 + } + protocol tcp + source { + address 195.243.221.50 + } + } + rule 2783 { + action accept + description FW18E6E_3-TCP-ALLOW-213.171.217.107 + destination { + group { + address-group DT_FW18E6E_3 + } + port 22 + } + protocol tcp + source { + address 213.171.217.107 + } + } + rule 2784 { + action accept + description FW18E6E_3-TCP-ALLOW-81.150.168.54 + destination { + group { + address-group DT_FW18E6E_3 + } + port 3306,22 + } + protocol tcp + source { + address 81.150.168.54 + } + } + rule 2785 { + action accept + description FW18E6E_3-TCP-ALLOW-89.197.133.235 + destination { + group { + address-group DT_FW18E6E_3 + } + port 22 + } + protocol tcp + source { + address 89.197.133.235 + } + } + rule 2786 { + action accept + description FW18E6E_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW18E6E_3 + } + port 60000-60100,873 + } + protocol tcp + } + rule 2787 { + action accept + description FW2BF20_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2BF20_3 + } + port 49152-65534,990 + } + protocol tcp + } + rule 2788 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.98 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.209.98 + } + } + rule 2789 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.65 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.213.65 + } + } + rule 2791 { + action accept + description FW197DB_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW197DB_1 + } + port 49152-65534 + } + protocol tcp + } + rule 2792 { + action accept + description FW1208C_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW1208C_1 + } + port 2087,2083,2096 + } + protocol tcp + } + rule 2793 { + action accept + description FW00D98_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW00D98_1 + } + port 4430 + } + protocol tcp + } + rule 2794 { + action accept + description FW03B35_1-ESP-ALLOW-ANY + destination { + group { + address-group DT_FW03B35_1 + } + } + protocol esp + } + rule 2795 { + action accept + description FW03B35_1-AH-ALLOW-ANY + destination { + group { + address-group DT_FW03B35_1 + } + } + protocol ah + } + rule 2796 { + action accept + description FWEF92E_5-TCP-ALLOW-87.224.6.174 + destination { + group { + address-group DT_FWEF92E_5 + } + port 3389,445,443 + } + protocol tcp + source { + address 87.224.6.174 + } + } + rule 2797 { + action accept + description FW825C8_19-TCP-ALLOW-159.253.51.74 + destination { + group { + address-group DT_FW825C8_19 + } + port 3389,1433,995 + } + protocol tcp + source { + address 159.253.51.74 + } + } + rule 2798 { + action accept + description FW825C8_19-TCP-ALLOW-77.68.76.111 + destination { + group { + address-group DT_FW825C8_19 + } + port 1433 + } + protocol tcp + source { + address 77.68.76.111 + } + } + rule 2799 { + action accept + description FW825C8_19-TCP-ALLOW-77.68.28.63 + destination { + group { + address-group DT_FW825C8_19 + } + port 995 + } + protocol tcp + source { + address 77.68.28.63 + } + } + rule 2801 { + action accept + description FW2EF2C_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2EF2C_1 + } + port 5349 + } + protocol tcp + } + rule 2802 { + action accept + description FWEF92E_5-TCP-ALLOW-88.208.198.93 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 88.208.198.93 + } + } + rule 2803 { + action accept + description FWC3921_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWC3921_1 + } + port 25000,25001-25005,26000-26006 + } + protocol tcp + } + rule 2804 { + action accept + description FWEF92E_5-UDP-ALLOW-109.228.37.19 + destination { + group { + address-group DT_FWEF92E_5 + } + port 500 + } + protocol udp + source { + address 109.228.37.19 + } + } + rule 2805 { + action accept + description FWEF92E_5-UDP-ALLOW-77.68.11.54 + destination { + group { + address-group DT_FWEF92E_5 + } + port 500 + } + protocol udp + source { + address 77.68.11.54 + } + } + rule 2806 { + action accept + description FW5AE10_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW5AE10_1 + } + port 53 + } + protocol tcp_udp + } + rule 2810 { + action accept + description FW45F87_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW45F87_1 + } + port 60000-60100 + } + protocol tcp + } + rule 2811 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.108.158 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 123.231.108.158 + } + } + rule 2813 { + action accept + description FW825C8_19-TCP-ALLOW-109.228.1.233 + destination { + group { + address-group DT_FW825C8_19 + } + port 1433 + } + protocol tcp + source { + address 109.228.1.233 + } + } + rule 2814 { + action accept + description FW20449_2-ICMP-ALLOW-3.10.221.168 + destination { + group { + address-group DT_FW20449_2 + } + } + protocol icmp + source { + address 3.10.221.168 + } + } + rule 2815 { + action accept + description FWB9699_7-TCP-ALLOW-213.171.217.100 + destination { + group { + address-group DT_FWB9699_7 + } + port 22 + } + protocol tcp + source { + address 213.171.217.100 + } + } + rule 2816 { + action accept + description FWB9699_7-TCP-ALLOW-213.171.217.180 + destination { + group { + address-group DT_FWB9699_7 + } + port 22 + } + protocol tcp + source { + address 213.171.217.180 + } + } + rule 2817 { + action accept + description FWB9699_7-TCP-ALLOW-213.171.217.184 + destination { + group { + address-group DT_FWB9699_7 + } + port 22 + } + protocol tcp + source { + address 213.171.217.184 + } + } + rule 2818 { + action accept + description FWB9699_7-TCP-ALLOW-213.171.217.185 + destination { + group { + address-group DT_FWB9699_7 + } + port 22 + } + protocol tcp + source { + address 213.171.217.185 + } + } + rule 2819 { + action accept + description FWB9699_7-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWB9699_7 + } + port 161 + } + protocol udp + } + rule 2820 { + action accept + description FWB9699_7-TCP-ALLOW-213.171.217.102 + destination { + group { + address-group DT_FWB9699_7 + } + port 22,8443 + } + protocol tcp + source { + address 213.171.217.102 + } + } + rule 2821 { + action accept + description FWB9699_7-TCP-ALLOW-213.171.217.103 + destination { + group { + address-group DT_FWB9699_7 + } + port 22 + } + protocol tcp + source { + address 213.171.217.103 + } + } + rule 2824 { + action accept + description FWE3E77_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE3E77_1 + } + port 10010,10009 + } + protocol tcp + } + rule 2825 { + action accept + description FW8A3FC_3-TCP-ALLOW-93.190.142.120 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 93.190.142.120 + } + } + rule 2826 { + action accept + description FW20449_2-ICMP-ALLOW-82.20.69.137 + destination { + group { + address-group DT_FW20449_2 + } + } + protocol icmp + source { + address 82.20.69.137 + } + } + rule 2827 { + action accept + description FW8A3FC_3-TCP-ALLOW-46.101.232.93 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 21-10000 + } + protocol tcp + source { + address 46.101.232.93 + } + } + rule 2828 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.5 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.213.5 + } + } + rule 2829 { + action accept + description FWD2440_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD2440_1 + } + port 1-65535 + } + protocol tcp + } + rule 2831 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.105 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.214.105 + } + } + rule 2833 { + action accept + description FW825C8_24-TCP-ALLOW-159.253.51.74 + destination { + group { + address-group DT_FW825C8_24 + } + port 3389,1433,995 + } + protocol tcp + source { + address 159.253.51.74 + } + } + rule 2834 { + action accept + description FW825C8_24-TCP-ALLOW-77.68.77.120 + destination { + group { + address-group DT_FW825C8_24 + } + port 1433 + } + protocol tcp + source { + address 77.68.77.120 + } + } + rule 2839 { + action accept + description FWD2440_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWD2440_1 + } + port 1-65535 + } + protocol udp + } + rule 2840 { + action accept + description FW1C8F2_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW1C8F2_1 + } + port 7000-10000,5554,5443,5080,1935,1111 + } + protocol tcp + } + rule 2843 { + action accept + description FWE7180_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWE7180_1 + } + port 443,53 + } + protocol tcp_udp + } + rule 2844 { + action accept + description FWC6301_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWC6301_1 + } + port 2456 + } + protocol tcp_udp + } + rule 2845 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.113 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.215.113 + } + } + rule 2846 { + action accept + description VPN-24589-ANY-ALLOW-10.4.57.9 + destination { + group { + address-group DT_VPN-24589 + } + } + source { + address 10.4.57.9 + } + } + rule 2847 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.237 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.212.237 + } + } + rule 2849 { + action accept + description FWFD9AF_9-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWFD9AF_9 + } + port 445 + } + protocol tcp_udp + } + rule 2850 { + action accept + description VPN-23209-ANY-ALLOW-10.4.58.8 + destination { + group { + address-group DT_VPN-23209 + } + } + source { + address 10.4.58.8 + } + } + rule 2851 { + action accept + description VPN-23209-ANY-ALLOW-10.4.59.8 + destination { + group { + address-group DT_VPN-23209 + } + } + source { + address 10.4.59.8 + } + } + rule 2853 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.29 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.215.29 + } + } + rule 2854 { + action accept + description FW16375_5-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW16375_5 + } + port 2096 + } + protocol tcp_udp + } + rule 2856 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.173 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.173 + } + } + rule 2858 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.208.35 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.208.35 + } + } + rule 2859 { + action accept + description FW73573_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW73573_1 + } + port 25 + } + protocol tcp_udp + } + rule 2860 { + action accept + description FW18E6E_3-TCP-ALLOW-148.253.173.242 + destination { + group { + address-group DT_FW18E6E_3 + } + port 3306 + } + protocol tcp + source { + address 148.253.173.242 + } + } + rule 2861 { + action accept + description FW8ECF4_1-TCP-ALLOW-77.68.2.215 + destination { + group { + address-group DT_FW8ECF4_1 + } + port 3306 + } + protocol tcp + source { + address 77.68.2.215 + } + } + rule 2862 { + action accept + description FW8A3FC_3-TCP_UDP-ALLOW-82.165.100.25 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 21-10000 + } + protocol tcp_udp + source { + address 82.165.100.25 + } + } + rule 2863 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.235 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.213.235 + } + } + rule 2864 { + action accept + description VPN-18647-ANY-ALLOW-10.4.86.114 + destination { + group { + address-group DT_VPN-18647 + } + } + source { + address 10.4.86.114 + } + } + rule 2865 { + action accept + description VPN-18647-ANY-ALLOW-10.4.87.114 + destination { + group { + address-group DT_VPN-18647 + } + } + source { + address 10.4.87.114 + } + } + rule 2867 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.107 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.215.107 + } + } + rule 2868 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.208.239 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.208.239 + } + } + rule 2869 { + action accept + description FWF699D_4-TCP-ALLOW-164.39.151.3 + destination { + group { + address-group DT_FWF699D_4 + } + port 3389 + } + protocol tcp + source { + address 164.39.151.3 + } + } + rule 2870 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.245 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.245 + } + } + rule 2873 { + action accept + description FWEF92E_6-TCP-ALLOW-87.224.6.174 + destination { + group { + address-group DT_FWEF92E_6 + } + port 3389,445 + } + protocol tcp + source { + address 87.224.6.174 + } + } + rule 2874 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.130 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.215.130 + } + } + rule 2875 { + action accept + description FW44BF9_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW44BF9_1 + } + port 49160-49200 + } + protocol tcp + } + rule 2876 { + action accept + description VPN-24591-ANY-ALLOW-10.4.86.4 + destination { + group { + address-group DT_VPN-24591 + } + } + source { + address 10.4.86.4 + } + } + rule 2877 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.60 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.214.60 + } + } + rule 2879 { + action accept + description FWEF92E_6-UDP-ALLOW-77.68.77.57 + destination { + group { + address-group DT_FWEF92E_6 + } + port 500 + } + protocol udp + source { + address 77.68.77.57 + } + } + rule 2880 { + action accept + description FWF699D_4-TCP-ALLOW-185.132.38.110 + destination { + group { + address-group DT_FWF699D_4 + } + port 3389 + } + protocol tcp + source { + address 185.132.38.110 + } + } + rule 2881 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.208.216 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.208.216 + } + } + rule 2882 { + action accept + description FWEF92E_5-UDP-ALLOW-77.68.77.149 + destination { + group { + address-group DT_FWEF92E_5 + } + port 500 + } + protocol udp + source { + address 77.68.77.149 + } + } + rule 2883 { + action accept + description FWA2FF8_4-TCP-ALLOW-80.229.18.102 + destination { + group { + address-group DT_FWA2FF8_4 + } + port 3306,21,22 + } + protocol tcp + source { + address 80.229.18.102 + } + } + rule 2884 { + action accept + description FWA2FF8_4-TCP-ALLOW-109.169.33.69 + destination { + group { + address-group DT_FWA2FF8_4 + } + port 3306,21,22 + } + protocol tcp + source { + address 109.169.33.69 + } + } + rule 2885 { + action accept + description FWA2FF8_4-TCP-ALLOW-46.102.209.35 + destination { + group { + address-group DT_FWA2FF8_4 + } + port 3306,21 + } + protocol tcp + source { + address 46.102.209.35 + } + } + rule 2886 { + action accept + description FWA2FF8_4-TCP-ALLOW-90.213.48.16 + destination { + group { + address-group DT_FWA2FF8_4 + } + port 3306,21 + } + protocol tcp + source { + address 90.213.48.16 + } + } + rule 2887 { + action accept + description FWA2FF8_4-TCP-ALLOW-77.68.76.129 + destination { + group { + address-group DT_FWA2FF8_4 + } + port 22 + } + protocol tcp + source { + address 77.68.76.129 + } + } + rule 2888 { + action accept + description FWA2FF8_4-TCP-ALLOW-109.228.50.145 + destination { + group { + address-group DT_FWA2FF8_4 + } + port 22 + } + protocol tcp + source { + address 109.228.50.145 + } + } + rule 2889 { + action accept + description FWA2FF8_4-TCP-ALLOW-77.68.76.231 + destination { + group { + address-group DT_FWA2FF8_4 + } + port 22 + } + protocol tcp + source { + address 77.68.76.231 + } + } + rule 2890 { + action accept + description FW4513E_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW4513E_1 + } + port 50000-50020,990 + } + protocol tcp + } + rule 2893 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.40.7 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 175.157.40.7 + } + } + rule 2894 { + action accept + description VPN-21876-ANY-ALLOW-10.4.88.96 + destination { + group { + address-group DT_VPN-21876 + } + } + source { + address 10.4.88.96 + } + } + rule 2895 { + action accept + description VPN-21876-ANY-ALLOW-10.4.89.96 + destination { + group { + address-group DT_VPN-21876 + } + } + source { + address 10.4.89.96 + } + } + rule 2896 { + action accept + description VPN-26124-ANY-ALLOW-10.4.54.75 + destination { + group { + address-group DT_VPN-26124 + } + } + source { + address 10.4.54.75 + } + } + rule 2897 { + action accept + description VPN-26124-ANY-ALLOW-10.4.55.76 + destination { + group { + address-group DT_VPN-26124 + } + } + source { + address 10.4.55.76 + } + } + rule 2898 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.21 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.210.21 + } + } + rule 2899 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.213 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.213 + } + } + rule 2901 { + action accept + description FWC6301_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWC6301_1 + } + port 5555 + } + protocol udp + } + rule 2902 { + action accept + description VPN-13261-ANY-ALLOW-10.4.56.173 + destination { + group { + address-group DT_VPN-13261 + } + } + source { + address 10.4.56.173 + } + } + rule 2903 { + action accept + description VPN-13261-ANY-ALLOW-10.4.57.173 + destination { + group { + address-group DT_VPN-13261 + } + } + source { + address 10.4.57.173 + } + } + rule 2909 { + action accept + description VPN-24591-ANY-ALLOW-10.4.87.4 + destination { + group { + address-group DT_VPN-24591 + } + } + source { + address 10.4.87.4 + } + } + rule 2911 { + action accept + description FWE7180_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE7180_1 + } + port 40110-40210,8090 + } + protocol tcp + } + rule 2914 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.247 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.247 + } + } + rule 2915 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.129 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.214.129 + } + } + rule 2916 { + action accept + description FWCB29D_1-TCP-ALLOW-51.146.16.162 + destination { + group { + address-group DT_FWCB29D_1 + } + port 8447,8443,22 + } + protocol tcp + source { + address 51.146.16.162 + } + } + rule 2917 { + action accept + description FW4E399_1-TCP-ALLOW-51.155.19.77 + destination { + group { + address-group DT_FW4E399_1 + } + port 3306 + } + protocol tcp + source { + address 51.155.19.77 + } + } + rule 2919 { + action accept + description FWC72E5_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWC72E5_1 + } + port 9000-9100,6667 + } + protocol tcp + } + rule 2922 { + action accept + description FW21A75_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW21A75_2 + } + port 3000 + } + protocol tcp + } + rule 2923 { + action accept + description FW3B068_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW3B068_2 + } + port 990,60000-65000 + } + protocol tcp + } + rule 2924 { + action accept + description FW48814_3-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW48814_3 + } + port 3306 + } + protocol tcp_udp + } + rule 2925 { + action accept + description FW48814_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW48814_3 + } + port 49152-65534 + } + protocol tcp + } + rule 2926 { + action accept + description FW2B279_4-TCP-ALLOW-178.128.39.210 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443 + } + protocol tcp + source { + address 178.128.39.210 + } + } + rule 2927 { + action accept + description FW2B279_4-TCP-ALLOW-82.165.232.19 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443 + } + protocol tcp + source { + address 82.165.232.19 + } + } + rule 2928 { + action accept + description FW2B279_4-TCP-ALLOW-84.64.186.31 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443 + } + protocol tcp + source { + address 84.64.186.31 + } + } + rule 2929 { + action accept + description FW1C8F2_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW1C8F2_1 + } + port 5000-65000 + } + protocol udp + } + rule 2930 { + action accept + description FW2B279_4-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2B279_4 + } + port 49152-65535 + } + protocol tcp + } + rule 2931 { + action accept + description FW608FA_1-TCP-ALLOW-195.10.106.114 + destination { + group { + address-group DT_FW608FA_1 + } + port 22 + } + protocol tcp + source { + address 195.10.106.114 + } + } + rule 2932 { + action accept + description FW608FA_1-TCP-ALLOW-213.137.25.134 + destination { + group { + address-group DT_FW608FA_1 + } + port 22 + } + protocol tcp + source { + address 213.137.25.134 + } + } + rule 2933 { + action accept + description FW608FA_1-TCP-ALLOW-92.39.202.189 + destination { + group { + address-group DT_FW608FA_1 + } + port 22 + } + protocol tcp + source { + address 92.39.202.189 + } + } + rule 2935 { + action accept + description FWC37B9_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWC37B9_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2936 { + action accept + description FW15C99_6-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW15C99_6 + } + port 32410-32414,1900 + } + protocol udp + } + rule 2937 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-116.206.244.146 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 116.206.244.146 + } + } + rule 2938 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.158 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.211.158 + } + } + rule 2939 { + action accept + description FW15C99_6-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW15C99_6 + } + port 32469,32400 + } + protocol tcp + } + rule 2940 { + action accept + description FW0192C_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0192C_1 + } + port 2053 + } + protocol tcp + } + rule 2941 { + action accept + description FW27949_2-TCP-ALLOW-86.179.23.119 + destination { + group { + address-group DT_FW27949_2 + } + port 443,80 + } + protocol tcp + source { + address 86.179.23.119 + } + } + rule 2942 { + action accept + description FW27949_2-TCP-ALLOW-92.15.208.193 + destination { + group { + address-group DT_FW27949_2 + } + port 443,80 + } + protocol tcp + source { + address 92.15.208.193 + } + } + rule 2943 { + action accept + description VPN-34122-ANY-ALLOW-10.4.56.122 + destination { + group { + address-group DT_VPN-34122 + } + } + source { + address 10.4.56.122 + } + } + rule 2944 { + action accept + description VPN-34122-ANY-ALLOW-10.4.57.122 + destination { + group { + address-group DT_VPN-34122 + } + } + source { + address 10.4.57.122 + } + } + rule 2945 { + action accept + description FWF323F_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWF323F_1 + } + port 25565,9999,8080,5001,3306 + } + protocol tcp_udp + } + rule 2946 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.132 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.213.132 + } + } + rule 2948 { + action accept + description VPN-30261-ANY-ALLOW-10.4.86.110 + destination { + group { + address-group DT_VPN-30261 + } + } + source { + address 10.4.86.110 + } + } + rule 2949 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.246 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.246 + } + } + rule 2951 { + action accept + description FWC2D30_1-TCP-ALLOW-157.231.100.222 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443 + } + protocol tcp + source { + address 157.231.100.222 + } + } + rule 2952 { + action accept + description FWC2D30_1-TCP-ALLOW-164.39.131.31 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443 + } + protocol tcp + source { + address 164.39.131.31 + } + } + rule 2953 { + action accept + description FWC2D30_1-TCP-ALLOW-185.199.108.0_22 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443 + } + protocol tcp + source { + address 185.199.108.0/22 + } + } + rule 2954 { + action accept + description FWC2D30_1-TCP-ALLOW-192.30.252.0_22 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443 + } + protocol tcp + source { + address 192.30.252.0/22 + } + } + rule 2955 { + action accept + description FWC2D30_1-TCP-ALLOW-80.252.78.202 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443 + } + protocol tcp + source { + address 80.252.78.202 + } + } + rule 2956 { + action accept + description FWC2D30_1-TCP-ALLOW-86.15.158.234 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443 + } + protocol tcp + source { + address 86.15.158.234 + } + } + rule 2957 { + action accept + description VPN-30261-ANY-ALLOW-10.4.87.110 + destination { + group { + address-group DT_VPN-30261 + } + } + source { + address 10.4.87.110 + } + } + rule 2958 { + action accept + description VPN-30262-ANY-ALLOW-10.4.88.36 + destination { + group { + address-group DT_VPN-30262 + } + } + source { + address 10.4.88.36 + } + } + rule 2961 { + action accept + description VPN-15950-ANY-ALLOW-10.4.88.89 + destination { + group { + address-group DT_VPN-15950 + } + } + source { + address 10.4.88.89 + } + } + rule 2962 { + action accept + description FWBFDED_1-TCP-ALLOW-78.141.24.164 + destination { + group { + address-group DT_FWBFDED_1 + } + port 3389 + } + protocol tcp + source { + address 78.141.24.164 + } + } + rule 2963 { + action accept + description VPN-30262-ANY-ALLOW-10.4.89.36 + destination { + group { + address-group DT_VPN-30262 + } + } + source { + address 10.4.89.36 + } + } + rule 2964 { + action accept + description FW1F126_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW1F126_1 + } + port 2087,2083 + } + protocol tcp + } + rule 2965 { + action accept + description FWA7A50_1-ANY-ALLOW-40.120.53.80 + destination { + group { + address-group DT_FWA7A50_1 + } + } + source { + address 40.120.53.80 + } + } + rule 2967 { + action accept + description VPN-23729-ANY-ALLOW-10.4.54.10 + destination { + group { + address-group DT_VPN-23729 + } + } + source { + address 10.4.54.10 + } + } + rule 2968 { + action accept + description VPN-23729-ANY-ALLOW-10.4.55.10 + destination { + group { + address-group DT_VPN-23729 + } + } + source { + address 10.4.55.10 + } + } + rule 2969 { + action accept + description VPN-23733-ANY-ALLOW-10.4.58.12 + destination { + group { + address-group DT_VPN-23733 + } + } + source { + address 10.4.58.12 + } + } + rule 2970 { + action accept + description VPN-23733-ANY-ALLOW-10.4.59.12 + destination { + group { + address-group DT_VPN-23733 + } + } + source { + address 10.4.59.12 + } + } + rule 2971 { + action accept + description VPN-23734-ANY-ALLOW-10.4.56.29 + destination { + group { + address-group DT_VPN-23734 + } + } + source { + address 10.4.56.29 + } + } + rule 2972 { + action accept + description VPN-23734-ANY-ALLOW-10.4.57.29 + destination { + group { + address-group DT_VPN-23734 + } + } + source { + address 10.4.57.29 + } + } + rule 2975 { + action accept + description VPN-23738-ANY-ALLOW-10.4.57.13 + destination { + group { + address-group DT_VPN-23738 + } + } + source { + address 10.4.57.13 + } + } + rule 2976 { + action accept + description FWD8DD1_2-TCP-ALLOW-77.153.164.226 + destination { + group { + address-group DT_FWD8DD1_2 + } + port 3306,22 + } + protocol tcp + source { + address 77.153.164.226 + } + } + rule 2977 { + action accept + description FWE012D_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWE012D_1 + } + port 143,25 + } + protocol tcp_udp + } + rule 2978 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.120.196 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 123.231.120.196 + } + } + rule 2981 { + action accept + description FW24AB7_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW24AB7_1 + } + port 40110-40210 + } + protocol tcp_udp + } + rule 2985 { + action accept + description FW2379F_14-TCP-ALLOW-194.72.140.178 + destination { + group { + address-group DT_FW2379F_14 + } + port 3389,21 + } + protocol tcp + source { + address 194.72.140.178 + } + } + rule 2986 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.97 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.97 + } + } + rule 2988 { + action accept + description FW883EB_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW883EB_1 + } + port 5005,5004,5003,5002,5001 + } + protocol tcp + } + rule 2992 { + action accept + description FW310C6_3-ANY-ALLOW-62.30.207.232 + destination { + group { + address-group DT_FW310C6_3 + } + } + source { + address 62.30.207.232 + } + } + rule 2993 { + action accept + description VPN-15950-ANY-ALLOW-10.4.89.89 + destination { + group { + address-group DT_VPN-15950 + } + } + source { + address 10.4.89.89 + } + } + rule 2994 { + action accept + description VPN-15960-ANY-ALLOW-10.4.88.90 + destination { + group { + address-group DT_VPN-15960 + } + } + source { + address 10.4.88.90 + } + } + rule 2995 { + action accept + description FWEF92E_7-UDP-ALLOW-77.68.77.57 + destination { + group { + address-group DT_FWEF92E_7 + } + port 500 + } + protocol udp + source { + address 77.68.77.57 + } + } + rule 2996 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.135 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.214.135 + } + } + rule 2998 { + action accept + description VPN-31002-ANY-ALLOW-10.4.88.126 + destination { + group { + address-group DT_VPN-31002 + } + } + source { + address 10.4.88.126 + } + } + rule 2999 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-116.206.246.110 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 116.206.246.110 + } + } + rule 3000 { + action accept + description FW08061_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW08061_1 + } + port 49152-65535 + } + protocol tcp + } + rule 3001 { + action accept + description VPN-15960-ANY-ALLOW-10.4.89.90 + destination { + group { + address-group DT_VPN-15960 + } + } + source { + address 10.4.89.90 + } + } + rule 3003 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.56 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.210.56 + } + } + rule 3004 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.47.47 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 175.157.47.47 + } + } + rule 3005 { + action accept + description FW10C3D_19-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW10C3D_19 + } + port 49152-65535,14147 + } + protocol tcp + } + rule 3006 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.136 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.210.136 + } + } + rule 3009 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.44.109 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 175.157.44.109 + } + } + rule 3010 { + action accept + description VPN-24592-ANY-ALLOW-10.4.88.9 + destination { + group { + address-group DT_VPN-24592 + } + } + source { + address 10.4.88.9 + } + } + rule 3011 { + action accept + description FW05AD0_2-TCP-ALLOW-213.171.209.161 + destination { + group { + address-group DT_FW05AD0_2 + } + port 3389,1433,21 + } + protocol tcp + source { + address 213.171.209.161 + } + } + rule 3012 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.86.254 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 123.231.86.254 + } + } + rule 3014 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.16 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.210.16 + } + } + rule 3018 { + action accept + description VPN-24592-ANY-ALLOW-10.4.89.9 + destination { + group { + address-group DT_VPN-24592 + } + } + source { + address 10.4.89.9 + } + } + rule 3019 { + action accept + description VPN-24593-ANY-ALLOW-10.4.54.6 + destination { + group { + address-group DT_VPN-24593 + } + } + source { + address 10.4.54.6 + } + } + rule 3020 { + action accept + description VPN-24593-ANY-ALLOW-10.4.55.6 + destination { + group { + address-group DT_VPN-24593 + } + } + source { + address 10.4.55.6 + } + } + rule 3021 { + action accept + description VPN-24594-ANY-ALLOW-10.4.58.6 + destination { + group { + address-group DT_VPN-24594 + } + } + source { + address 10.4.58.6 + } + } + rule 3022 { + action accept + description VPN-24594-ANY-ALLOW-10.4.59.6 + destination { + group { + address-group DT_VPN-24594 + } + } + source { + address 10.4.59.6 + } + } + rule 3023 { + action accept + description VPN-24595-ANY-ALLOW-10.4.56.14 + destination { + group { + address-group DT_VPN-24595 + } + } + source { + address 10.4.56.14 + } + } + rule 3024 { + action accept + description VPN-24595-ANY-ALLOW-10.4.57.14 + destination { + group { + address-group DT_VPN-24595 + } + } + source { + address 10.4.57.14 + } + } + rule 3025 { + action accept + description VPN-32528-ANY-ALLOW-10.4.58.67 + destination { + group { + address-group DT_VPN-32528 + } + } + source { + address 10.4.58.67 + } + } + rule 3026 { + action accept + description VPN-32528-ANY-ALLOW-10.4.59.67 + destination { + group { + address-group DT_VPN-32528 + } + } + source { + address 10.4.59.67 + } + } + rule 3027 { + action accept + description FW6187E_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW6187E_1 + } + port 51195 + } + protocol udp + } + rule 3028 { + action accept + description FW406AB_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW406AB_1 + } + port 37013,25461,8881,8080,2095,2082,1992 + } + protocol tcp_udp + } + rule 3029 { + action accept + description FWA86A4_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA86A4_1 + } + port 30333,5666 + } + protocol tcp + } + rule 3032 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.52 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.52 + } + } + rule 3033 { + action accept + description FWC055A_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWC055A_1 + } + port 2195 + } + protocol tcp + } + rule 3035 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.81 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.213.81 + } + } + rule 3039 { + action accept + description FW42BC7_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW42BC7_1 + } + port 53 + } + protocol tcp_udp + } + rule 3040 { + action accept + description FW42BC7_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW42BC7_1 + } + port 49152-65535 + } + protocol tcp + } + rule 3041 { + action accept + description FW310C6_3-ANY-ALLOW-88.208.198.39 + destination { + group { + address-group DT_FW310C6_3 + } + } + source { + address 88.208.198.39 + } + } + rule 3042 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.235 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.209.235 + } + } + rule 3043 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.205 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.212.205 + } + } + rule 3044 { + action accept + description FWBE878_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWBE878_1 + } + port 8989,5003,3000 + } + protocol tcp_udp + } + rule 3045 { + action accept + description VPN-30679-ANY-ALLOW-10.4.58.195 + destination { + group { + address-group DT_VPN-30679 + } + } + source { + address 10.4.58.195 + } + } + rule 3046 { + action accept + description FW6B9B9_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW6B9B9_1 + } + port 30006-65000,27017,7101,4200,2990-3009 + } + protocol tcp + } + rule 3047 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.212 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.212 + } + } + rule 3049 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.125.4 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 123.231.125.4 + } + } + rule 3050 { + action accept + description FW49C3D_4-TCP-ALLOW-83.100.136.74 + destination { + group { + address-group DT_FW49C3D_4 + } + port 3389,445 + } + protocol tcp + source { + address 83.100.136.74 + } + } + rule 3051 { + action accept + description FW49C3D_6-TCP-ALLOW-87.224.33.215 + destination { + group { + address-group DT_FW49C3D_6 + } + port 3389,445 + } + protocol tcp + source { + address 87.224.33.215 + } + } + rule 3053 { + action accept + description FW89619_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW89619_1 + } + port 9000-10999 + } + protocol udp + } + rule 3054 { + action accept + description FWBD9D0_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWBD9D0_1 + } + port 9090 + } + protocol tcp + } + rule 3055 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.47.236 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 175.157.47.236 + } + } + rule 3056 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.46.226 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 175.157.46.226 + } + } + rule 3058 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.205 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.211.205 + } + } + rule 3060 { + action accept + description FWF7B68_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF7B68_1 + } + port 49152-65535 + } + protocol tcp + } + rule 3061 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.253 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.210.253 + } + } + rule 3063 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.0 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.210.0 + } + } + rule 3065 { + action accept + description FW85619_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW85619_1 + } + port 6433 + } + protocol tcp + } + rule 3066 { + action accept + description FW5A5D7_3-TCP-ALLOW-188.66.79.94 + destination { + group { + address-group DT_FW5A5D7_3 + } + port 8172,3389 + } + protocol tcp + source { + address 188.66.79.94 + } + } + rule 3067 { + action accept + description FWF30BD_1-TCP-ALLOW-81.133.80.114 + destination { + group { + address-group DT_FWF30BD_1 + } + port 22 + } + protocol tcp + source { + address 81.133.80.114 + } + } + rule 3068 { + action accept + description FWF30BD_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF30BD_1 + } + port 5061,5015,5001 + } + protocol tcp + } + rule 3069 { + action accept + description FWBD9D0_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWBD9D0_1 + } + port 51820 + } + protocol udp + } + rule 3070 { + action accept + description FW7C4D9_14-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW7C4D9_14 + } + port 25565,2456-2458 + } + protocol tcp_udp + } + rule 3071 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.23 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.209.23 + } + } + rule 3072 { + action accept + description FWEEC75_1-TCP-ALLOW-81.96.100.32 + destination { + group { + address-group DT_FWEEC75_1 + } + port 8447 + } + protocol tcp + source { + address 81.96.100.32 + } + } + rule 3073 { + action accept + description FW8A3FC_3-TCP-ALLOW-95.168.164.208 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 95.168.164.208 + } + } + rule 3074 { + action accept + description VPN-19992-ANY-ALLOW-10.4.86.158 + destination { + group { + address-group DT_VPN-19992 + } + } + source { + address 10.4.86.158 + } + } + rule 3075 { + action accept + description FWF30BD_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWF30BD_1 + } + port 5090,5060 + } + protocol tcp_udp + } + rule 3076 { + action accept + description VPN-30679-ANY-ALLOW-10.4.59.195 + destination { + group { + address-group DT_VPN-30679 + } + } + source { + address 10.4.59.195 + } + } + rule 3077 { + action accept + description FW930F3_3-ANY-ALLOW-77.68.112.254 + destination { + group { + address-group DT_FW930F3_3 + } + } + source { + address 77.68.112.254 + } + } + rule 3078 { + action accept + description FW672AB_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW672AB_1 + } + port 5432 + } + protocol tcp + } + rule 3079 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.252 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.211.252 + } + } + rule 3080 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.86.192 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 123.231.86.192 + } + } + rule 3081 { + action accept + description VPN-33204-ANY-ALLOW-10.4.56.176 + destination { + group { + address-group DT_VPN-33204 + } + } + source { + address 10.4.56.176 + } + } + rule 3083 { + action accept + description FW1FA8E_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW1FA8E_1 + } + port 33434 + } + protocol udp + } + rule 3084 { + action accept + description FWD2440_1-ESP-ALLOW-ANY + destination { + group { + address-group DT_FWD2440_1 + } + } + protocol esp + } + rule 3085 { + action accept + description FWA0531_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA0531_1 + } + port 53 + } + protocol tcp_udp + } + rule 3090 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.70 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.70 + } + } + rule 3091 { + action accept + description FWF7BFA_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF7BFA_1 + } + port 8000,5901,5479,5478 + } + protocol tcp + } + rule 3092 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.212 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.214.212 + } + } + rule 3094 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.125 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.212.125 + } + } + rule 3096 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.89 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.89 + } + } + rule 3097 { + action accept + description FWD56A2_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD56A2_1 + } + port 8001,8000 + } + protocol tcp + } + rule 3098 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.109 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.109 + } + } + rule 3099 { + action accept + description FW36425_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW36425_1 + } + port 44445,7770-7800 + } + protocol tcp + } + rule 3100 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.238 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.214.238 + } + } + rule 3102 { + action accept + description FW6B39D_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW6B39D_1 + } + port 49216,49215 + } + protocol tcp_udp + } + rule 3103 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.121 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.213.121 + } + } + rule 3105 { + action accept + description FW2379F_14-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW2379F_14 + } + port 443 + } + protocol tcp_udp + } + rule 3107 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.38 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.38 + } + } + rule 3109 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.191 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.213.191 + } + } + rule 3111 { + action accept + description FW27947_1-TCP-ALLOW-213.229.100.148 + destination { + group { + address-group DT_FW27947_1 + } + port 3306 + } + protocol tcp + source { + address 213.229.100.148 + } + } + rule 3112 { + action accept + description FWD42CF_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD42CF_1 + } + port 5432,5001,5000 + } + protocol tcp + } + rule 3114 { + action accept + description FW3A12F_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW3A12F_1 + } + port 53 + } + protocol tcp_udp + } + rule 3116 { + action accept + description FW5A5D7_3-TCP-ALLOW-194.62.184.87 + destination { + group { + address-group DT_FW5A5D7_3 + } + port 3389 + } + protocol tcp + source { + address 194.62.184.87 + } + } + rule 3117 { + action accept + description FW5A5D7_3-TCP-ALLOW-51.219.31.78 + destination { + group { + address-group DT_FW5A5D7_3 + } + port 8172,3389 + } + protocol tcp + source { + address 51.219.31.78 + } + } + rule 3118 { + action accept + description VPN-26157-ANY-ALLOW-10.4.86.57 + destination { + group { + address-group DT_VPN-26157 + } + } + source { + address 10.4.86.57 + } + } + rule 3119 { + action accept + description VPN-26157-ANY-ALLOW-10.4.87.57 + destination { + group { + address-group DT_VPN-26157 + } + } + source { + address 10.4.87.57 + } + } + rule 3120 { + action accept + description FWA7625_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA7625_1 + } + port 943 + } + protocol tcp + } + rule 3121 { + action accept + description FWC96A1_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWC96A1_1 + } + port 1194 + } + protocol udp + } + rule 3122 { + action accept + description FWA7625_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA7625_1 + } + port 1194 + } + protocol udp + } + rule 3123 { + action accept + description FWA7625_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA7625_1 + } + port 32400,10108 + } + protocol tcp_udp + } + rule 3125 { + action accept + description FW8A3FC_3-TCP-ALLOW-185.173.161.154 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 185.173.161.154 + } + } + rule 3127 { + action accept + description FW05339_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW05339_1 + } + port 46961 + } + protocol udp + } + rule 3130 { + action accept + description FWA0AA0_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA0AA0_1 + } + port 1194 + } + protocol udp + } + rule 3132 { + action accept + description FWD8DD1_2-TCP_UDP-ALLOW-77.153.164.226 + destination { + group { + address-group DT_FWD8DD1_2 + } + port 443,80 + } + protocol tcp_udp + source { + address 77.153.164.226 + } + } + rule 3134 { + action accept + description FW19987_4-TCP-ALLOW-87.224.6.174 + destination { + group { + address-group DT_FW19987_4 + } + port 3389,445,443 + } + protocol tcp + source { + address 87.224.6.174 + } + } + rule 3135 { + action accept + description FW40AE4_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW40AE4_1 + } + port 53 + } + protocol tcp_udp + } + rule 3136 { + action accept + description VPN-33204-ANY-ALLOW-10.4.57.176 + destination { + group { + address-group DT_VPN-33204 + } + } + source { + address 10.4.57.176 + } + } + rule 3137 { + action accept + description FWF3A1B_1-TCP_UDP-ALLOW-86.132.125.4 + destination { + group { + address-group DT_FWF3A1B_1 + } + port 2222 + } + protocol tcp_udp + source { + address 86.132.125.4 + } + } + rule 3138 { + action accept + description FWF3A1B_1-TCP_UDP-ALLOW-91.205.173.51 + destination { + group { + address-group DT_FWF3A1B_1 + } + port 2222 + } + protocol tcp_udp + source { + address 91.205.173.51 + } + } + rule 3143 { + action accept + description FWA86ED_101-TCP-ALLOW-109.149.121.73 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 109.149.121.73 + } + } + rule 3144 { + action accept + description FWA0AA0_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA0AA0_1 + } + port 28083,28015-28016,1935 + } + protocol tcp_udp + } + rule 3146 { + action accept + description FWF3A1B_1-TCP_UDP-ALLOW-92.233.27.144 + destination { + group { + address-group DT_FWF3A1B_1 + } + port 2222 + } + protocol tcp_udp + source { + address 92.233.27.144 + } + } + rule 3148 { + action accept + description FWA86ED_101-TCP-ALLOW-151.228.194.190 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 151.228.194.190 + } + } + rule 3149 { + action accept + description FW9B6FB_1-ICMP-ALLOW-77.68.89.115_32 + destination { + group { + address-group DT_FW9B6FB_1 + } + } + protocol icmp + source { + address 77.68.89.115/32 + } + } + rule 3153 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.199 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.214.199 + } + } + rule 3155 { + action accept + description FW45F3D_1-ANY-ALLOW-195.224.110.168 + destination { + group { + address-group DT_FW45F3D_1 + } + } + source { + address 195.224.110.168 + } + } + rule 3156 { + action accept + description FWF8E67_1-TCP-ALLOW-82.14.188.35 + destination { + group { + address-group DT_FWF8E67_1 + } + port 22 + } + protocol tcp + source { + address 82.14.188.35 + } + } + rule 3157 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.58 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.215.58 + } + } + rule 3158 { + action accept + description VPN-19992-ANY-ALLOW-10.4.87.158 + destination { + group { + address-group DT_VPN-19992 + } + } + source { + address 10.4.87.158 + } + } + rule 3159 { + action accept + description FWA86ED_101-TCP-ALLOW-5.66.24.185 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 5.66.24.185 + } + } + rule 3160 { + action accept + description FWF8E67_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF8E67_1 + } + port 3001 + } + protocol tcp + } + rule 3161 { + action accept + description FWD2440_1-AH-ALLOW-ANY + destination { + group { + address-group DT_FWD2440_1 + } + } + protocol ah + } + rule 3166 { + action accept + description FW3EBC8_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW3EBC8_1 + } + port 9001-9900,9000 + } + protocol tcp + } + rule 3167 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.244 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.244 + } + } + rule 3168 { + action accept + description FWA0531_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA0531_1 + } + port 3000 + } + protocol tcp + } + rule 3170 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.137 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.215.137 + } + } + rule 3173 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.104 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.104 + } + } + rule 3176 { + action accept + description FW6906B_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW6906B_1 + } + port 4190 + } + protocol tcp + } + rule 3177 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-116.206.246.230 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 116.206.246.230 + } + } + rule 3178 { + action accept + description FW444AF_1-TCP-ALLOW-91.135.10.140 + destination { + group { + address-group DT_FW444AF_1 + } + port 27017 + } + protocol tcp + source { + address 91.135.10.140 + } + } + rule 3180 { + action accept + description FWA86ED_101-TCP-ALLOW-81.150.13.34 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 81.150.13.34 + } + } + rule 3181 { + action accept + description FWA86ED_101-TCP-ALLOW-82.10.14.73 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 82.10.14.73 + } + } + rule 3183 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.25 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.25 + } + } + rule 3184 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.224 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.215.224 + } + } + rule 3185 { + action accept + description FW9B6FB_1-TCP-ALLOW-77.68.89.115_32 + destination { + group { + address-group DT_FW9B6FB_1 + } + port 10050 + } + protocol tcp + source { + address 77.68.89.115/32 + } + } + rule 3186 { + action accept + description VPN-14673-ANY-ALLOW-10.4.89.44 + destination { + group { + address-group DT_VPN-14673 + } + } + source { + address 10.4.89.44 + } + } + rule 3187 { + action accept + description FWCA628_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWCA628_1 + } + port 2096,2095,2087,2086,2083,2082 + } + protocol tcp + } + rule 3189 { + action accept + description VPN-28484-ANY-ALLOW-10.4.58.159 + destination { + group { + address-group DT_VPN-28484 + } + } + source { + address 10.4.58.159 + } + } + rule 3190 { + action accept + description FW028C0_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW028C0_2 + } + port 44491-44498,44474 + } + protocol tcp + } + rule 3191 { + action accept + description VPN-28484-ANY-ALLOW-10.4.59.159 + destination { + group { + address-group DT_VPN-28484 + } + } + source { + address 10.4.59.159 + } + } + rule 3192 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.119 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.213.119 + } + } + rule 3194 { + action accept + description FWF699D_4-TCP-ALLOW-195.74.108.130 + destination { + group { + address-group DT_FWF699D_4 + } + port 3389 + } + protocol tcp + source { + address 195.74.108.130 + } + } + rule 3195 { + action accept + description FWF699D_4-TCP-ALLOW-31.54.149.143 + destination { + group { + address-group DT_FWF699D_4 + } + port 3389 + } + protocol tcp + source { + address 31.54.149.143 + } + } + rule 3196 { + action accept + description FWF699D_4-TCP-ALLOW-35.204.243.120 + destination { + group { + address-group DT_FWF699D_4 + } + port 3389 + } + protocol tcp + source { + address 35.204.243.120 + } + } + rule 3197 { + action accept + description FWF699D_4-TCP-ALLOW-81.150.55.65 + destination { + group { + address-group DT_FWF699D_4 + } + port 3389 + } + protocol tcp + source { + address 81.150.55.65 + } + } + rule 3198 { + action accept + description FWF699D_4-TCP-ALLOW-81.150.55.70 + destination { + group { + address-group DT_FWF699D_4 + } + port 3389 + } + protocol tcp + source { + address 81.150.55.70 + } + } + rule 3199 { + action accept + description FWF699D_4-TCP-ALLOW-86.142.112.4 + destination { + group { + address-group DT_FWF699D_4 + } + port 3389 + } + protocol tcp + source { + address 86.142.112.4 + } + } + rule 3200 { + action accept + description FWF699D_4-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWF699D_4 + } + port 8983 + } + protocol tcp_udp + } + rule 3201 { + action accept + description FWF699D_4-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF699D_4 + } + port 11009,10009 + } + protocol tcp + } + rule 3202 { + action accept + description VPN-2661-ANY-ALLOW-10.4.54.24 + destination { + group { + address-group DT_VPN-2661 + } + } + source { + address 10.4.54.24 + } + } + rule 3203 { + action accept + description VPN-2661-ANY-ALLOW-10.4.55.24 + destination { + group { + address-group DT_VPN-2661 + } + } + source { + address 10.4.55.24 + } + } + rule 3204 { + action accept + description VPN-9727-ANY-ALLOW-10.4.54.118 + destination { + group { + address-group DT_VPN-9727 + } + } + source { + address 10.4.54.118 + } + } + rule 3205 { + action accept + description VPN-9727-ANY-ALLOW-10.4.55.119 + destination { + group { + address-group DT_VPN-9727 + } + } + source { + address 10.4.55.119 + } + } + rule 3207 { + action accept + description FWF0221_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWF0221_1 + } + port 65000,8099,8080 + } + protocol tcp_udp + } + rule 3208 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.180 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.180 + } + } + rule 3209 { + action accept + description FWA86ED_101-TCP-ALLOW-82.5.189.5 + destination { + group { + address-group DT_FWA86ED_101 + } + port 443 + } + protocol tcp + source { + address 82.5.189.5 + } + } + rule 3210 { + action accept + description FW60FD6_5-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW60FD6_5 + } + port 1194 + } + protocol udp + } + rule 3211 { + action accept + description FW60FD6_5-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW60FD6_5 + } + port 9500,9191,9090,8090,2222 + } + protocol tcp + } + rule 3212 { + action accept + description FWA86ED_101-TCP-ALLOW-84.65.217.114 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 84.65.217.114 + } + } + rule 3213 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.43.21 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 175.157.43.21 + } + } + rule 3214 { + action accept + description FW45F3D_1-ANY-ALLOW-77.68.126.251 + destination { + group { + address-group DT_FW45F3D_1 + } + } + source { + address 77.68.126.251 + } + } + rule 3215 { + action accept + description FWA86ED_101-TCP-ALLOW-86.14.23.23 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 86.14.23.23 + } + } + rule 3217 { + action accept + description FW85E02_11-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW85E02_11 + } + port 9000-10999 + } + protocol udp + } + rule 3218 { + action accept + description FW5D0FA_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW5D0FA_1 + } + port 53 + } + protocol tcp_udp + } + rule 3222 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.141 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.141 + } + } + rule 3223 { + action accept + description FWCDD8B_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWCDD8B_1 + } + port 2222 + } + protocol tcp + } + rule 3224 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.185 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.214.185 + } + } + rule 3225 { + action accept + description FW06940_3-TCP_UDP-ALLOW-213.171.210.153 + destination { + group { + address-group DT_FW06940_3 + } + port 1-65535 + } + protocol tcp_udp + source { + address 213.171.210.153 + } + } + rule 3226 { + action accept + description FW06940_3-TCP_UDP-ALLOW-70.29.113.102 + destination { + group { + address-group DT_FW06940_3 + } + port 1-65535 + } + protocol tcp_udp + source { + address 70.29.113.102 + } + } + rule 3227 { + action accept + description FWC32BE_1-ANY-ALLOW-3.127.0.177 + destination { + group { + address-group DT_FWC32BE_1 + } + } + source { + address 3.127.0.177 + } + } + rule 3228 { + action accept + description FWA86ED_101-TCP-ALLOW-93.115.195.58 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 93.115.195.58 + } + } + rule 3229 { + action accept + description FWE32F2_8-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE32F2_8 + } + port 40120,30120,30110 + } + protocol tcp + } + rule 3230 { + action accept + description VPN-28515-ANY-ALLOW-10.4.56.162 + destination { + group { + address-group DT_VPN-28515 + } + } + source { + address 10.4.56.162 + } + } + rule 3231 { + action accept + description FW06940_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW06940_3 + } + port 30000-30400,8443-8447,445,80-110,21-25 + } + protocol tcp + } + rule 3232 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.134 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.211.134 + } + } + rule 3236 { + action accept + description VPN-28515-ANY-ALLOW-10.4.57.162 + destination { + group { + address-group DT_VPN-28515 + } + } + source { + address 10.4.57.162 + } + } + rule 3237 { + action accept + description FWF4063_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF4063_1 + } + port 3000 + } + protocol tcp + } + rule 3240 { + action accept + description FW06940_3-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW06940_3 + } + port 49152-65535,6379,5666,5432-5454 + } + protocol tcp_udp + } + rule 3242 { + action accept + description FW2E8D4_1-TCP-ALLOW-63.35.92.185 + destination { + group { + address-group DT_FW2E8D4_1 + } + port 3389 + } + protocol tcp + source { + address 63.35.92.185 + } + } + rule 3244 { + action accept + description FWF30BD_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWF30BD_1 + } + port 9000-10999 + } + protocol udp + } + rule 3245 { + action accept + description FWE30A1_4-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWE30A1_4 + } + port 65057 + } + protocol tcp_udp + } + rule 3246 { + action accept + description VPN-26772-ANY-ALLOW-10.4.54.123 + destination { + group { + address-group DT_VPN-26772 + } + } + source { + address 10.4.54.123 + } + } + rule 3249 { + action accept + description FW56496_1-ANY-ALLOW-77.68.82.49 + destination { + group { + address-group DT_FW56496_1 + } + } + source { + address 77.68.82.49 + } + } + rule 3251 { + action accept + description FWDA443_6-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWDA443_6 + } + port 30175,12050 + } + protocol tcp + } + rule 3253 { + action accept + description FW5A521_3-TCP-ALLOW-88.98.75.17 + destination { + group { + address-group DT_FW5A521_3 + } + port 22 + } + protocol tcp + source { + address 88.98.75.17 + } + } + rule 3254 { + action accept + description FW5A521_3-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW5A521_3 + } + port 161-162 + } + protocol udp + } + rule 3255 { + action accept + description FW5A521_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW5A521_3 + } + port 5900 + } + protocol tcp + } + rule 3259 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.178 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.214.178 + } + } + rule 3260 { + action accept + description VPN-26772-ANY-ALLOW-10.4.55.124 + destination { + group { + address-group DT_VPN-26772 + } + } + source { + address 10.4.55.124 + } + } + rule 3262 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.114 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.114 + } + } + rule 3272 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-116.206.246.30 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 116.206.246.30 + } + } + rule 3273 { + action accept + description FW2B4BA_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2B4BA_1 + } + port 30000-31000 + } + protocol tcp + } + rule 3284 { + action accept + description FW06940_3-TCP-ALLOW-213.171.217.107 + destination { + group { + address-group DT_FW06940_3 + } + port 8443 + } + protocol tcp + source { + address 213.171.217.107 + } + } + rule 3285 { + action accept + description FW0952B_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0952B_1 + } + port 9030,9001 + } + protocol tcp + } + rule 3286 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.85.35 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 123.231.85.35 + } + } + rule 3290 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.208.232 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.208.232 + } + } + rule 3294 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.21 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.21 + } + } + rule 3295 { + action accept + description FW0EA3F_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW0EA3F_1 + } + port 1-65535 + } + protocol tcp_udp + } + rule 3296 { + action accept + description FW9D5C7_1-TCP-ALLOW-209.97.176.108 + destination { + group { + address-group DT_FW9D5C7_1 + } + port 8447,8443,22 + } + protocol tcp + source { + address 209.97.176.108 + } + } + rule 3297 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.188 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.210.188 + } + } + rule 3298 { + action accept + description FW9D5C7_1-TCP-ALLOW-165.227.231.227 + destination { + group { + address-group DT_FW9D5C7_1 + } + port 9117,9113,9104,9100 + } + protocol tcp + source { + address 165.227.231.227 + } + } + rule 3299 { + action accept + description FW4DB0A_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW4DB0A_1 + } + port 953 + } + protocol tcp + } + rule 3300 { + action accept + description FW4DB0A_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW4DB0A_1 + } + port 953 + } + protocol udp + } + rule 3301 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.91 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.91 + } + } + rule 3303 { + action accept + description FW56496_1-TCP-ALLOW-176.255.93.149 + destination { + group { + address-group DT_FW56496_1 + } + port 3389 + } + protocol tcp + source { + address 176.255.93.149 + } + } + rule 3304 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.79 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.210.79 + } + } + rule 3305 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.43 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.215.43 + } + } + rule 3306 { + action accept + description FW310C6_3-ANY-ALLOW-88.208.198.40 + destination { + group { + address-group DT_FW310C6_3 + } + } + source { + address 88.208.198.40 + } + } + rule 3307 { + action accept + description FW597A6_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW597A6_1 + } + port 49152-65535,990 + } + protocol tcp + } + rule 3308 { + action accept + description FW597A6_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW597A6_1 + } + port 3306 + } + protocol tcp_udp + } + rule 3309 { + action accept + description FWBC280_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWBC280_1 + } + port 49152-65535,20-21 + } + protocol tcp + } + rule 3310 { + action accept + description VPN-31301-ANY-ALLOW-10.4.87.223 + destination { + group { + address-group DT_VPN-31301 + } + } + source { + address 10.4.87.223 + } + } + rule 3311 { + action accept + description FW18E6E_3-TCP-ALLOW-148.253.173.243 + destination { + group { + address-group DT_FW18E6E_3 + } + port 3306 + } + protocol tcp + source { + address 148.253.173.243 + } + } + rule 3312 { + action accept + description FW9EEDD_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW9EEDD_1 + } + port 990,197,20-23 + } + protocol tcp + } + rule 3313 { + action accept + description FW9EEDD_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW9EEDD_1 + } + port 49152-65535 + } + protocol tcp_udp + } + rule 3314 { + action accept + description VPN-31002-ANY-ALLOW-10.4.89.126 + destination { + group { + address-group DT_VPN-31002 + } + } + source { + address 10.4.89.126 + } + } + rule 3316 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.11 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.11 + } + } + rule 3317 { + action accept + description FW32EFF_49-TCP-ALLOW-195.59.191.128_25 + destination { + group { + address-group DT_FW32EFF_49 + } + port 5589 + } + protocol tcp + source { + address 195.59.191.128/25 + } + } + rule 3318 { + action accept + description FW32EFF_49-TCP-ALLOW-213.71.130.0_26 + destination { + group { + address-group DT_FW32EFF_49 + } + port 5589 + } + protocol tcp + source { + address 213.71.130.0/26 + } + } + rule 3319 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.88 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.215.88 + } + } + rule 3320 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.173 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.215.173 + } + } + rule 3321 { + action accept + description FW32EFF_49-TCP-ALLOW-84.19.45.82 + destination { + group { + address-group DT_FW32EFF_49 + } + port 5589 + } + protocol tcp + source { + address 84.19.45.82 + } + } + rule 3322 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.43.122 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 175.157.43.122 + } + } + rule 3323 { + action accept + description FWC1ACD_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWC1ACD_1 + } + port 28061,28060,8080 + } + protocol tcp_udp + } + rule 3324 { + action accept + description FWA5D67_1-TCP_UDP-ALLOW-84.74.32.74 + destination { + group { + address-group DT_FWA5D67_1 + } + port 3389 + } + protocol tcp_udp + source { + address 84.74.32.74 + } + } + rule 3325 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.169 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.213.169 + } + } + rule 3326 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.89 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.213.89 + } + } + rule 3329 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.35 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.35 + } + } + rule 3330 { + action accept + description FWCE020_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWCE020_1 + } + port 48402 + } + protocol udp + } + rule 3333 { + action accept + description FWF3574_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF3574_1 + } + port 8060,445,139 + } + protocol tcp + } + rule 3334 { + action accept + description FWE6AB2_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE6AB2_1 + } + port 44158,945,943 + } + protocol tcp + } + rule 3335 { + action accept + description FWBFC02_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWBFC02_1 + } + port 44158,945,943 + } + protocol tcp + } + rule 3336 { + action accept + description FWBFC02_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWBFC02_1 + } + port 1194 + } + protocol udp + } + rule 3337 { + action accept + description FWE6AB2_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWE6AB2_1 + } + port 1194 + } + protocol udp + } + rule 3338 { + action accept + description FWBC8A6_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWBC8A6_1 + } + port 44158,945,943 + } + protocol tcp + } + rule 3339 { + action accept + description FWBC8A6_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWBC8A6_1 + } + port 1194 + } + protocol udp + } + rule 3340 { + action accept + description FWA0AA0_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA0AA0_1 + } + port 2302 + } + protocol tcp + } + rule 3342 { + action accept + description FW56496_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW56496_1 + } + port 22 + } + protocol tcp_udp + } + rule 3343 { + action accept + description FW56496_1-TCP-ALLOW-157.231.178.162 + destination { + group { + address-group DT_FW56496_1 + } + port 21 + } + protocol tcp + source { + address 157.231.178.162 + } + } + rule 3344 { + action accept + description FW56496_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW56496_1 + } + port 2443,1022 + } + protocol tcp + } + rule 3345 { + action accept + description FW56496_1-TCP_UDP-ALLOW-46.16.211.142 + destination { + group { + address-group DT_FW56496_1 + } + port 3389,21 + } + protocol tcp_udp + source { + address 46.16.211.142 + } + } + rule 3347 { + action accept + description FW2379F_14-GRE-ALLOW-ANY + destination { + group { + address-group DT_FW2379F_14 + } + } + protocol gre + } + rule 3348 { + action accept + description FW0E383_9-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0E383_9 + } + port 52000 + } + protocol tcp + } + rule 3350 { + action accept + description FWB4438_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWB4438_2 + } + port 993-995,7 + } + protocol tcp + } + rule 3351 { + action accept + description FW1F3D0_6-TCP_UDP-ALLOW-82.165.207.109 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4567-4568 + } + protocol tcp_udp + source { + address 82.165.207.109 + } + } + rule 3352 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.77 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.210.77 + } + } + rule 3358 { + action accept + description FW46F4A_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW46F4A_1 + } + port 51820 + } + protocol udp + } + rule 3359 { + action accept + description FW53C72_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW53C72_1 + } + port 48402 + } + protocol udp + } + rule 3360 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.251 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.210.251 + } + } + rule 3362 { + action accept + description FWAA38E_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWAA38E_1 + } + port 1001-65535 + } + protocol tcp_udp + } + rule 3363 { + action accept + description FW138F8_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW138F8_1 + } + port 21,20 + } + protocol tcp_udp + } + rule 3364 { + action accept + description FW0BD92_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0BD92_3 + } + port 18081,18080 + } + protocol tcp + } + rule 3365 { + action accept + description FWFEF05_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWFEF05_1 + } + port 1935 + } + protocol tcp_udp + } + rule 3367 { + action accept + description FW26846_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW26846_1 + } + port 8000 + } + protocol tcp + } + rule 3368 { + action accept + description FWB4438_2-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWB4438_2 + } + port 53 + } + protocol tcp_udp + } + rule 3369 { + action accept + description FWA884B_5-TCP-ALLOW-51.146.16.162 + destination { + group { + address-group DT_FWA884B_5 + } + port 8447,8443,22 + } + protocol tcp + source { + address 51.146.16.162 + } + } + rule 3370 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.22 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.209.22 + } + } + rule 3371 { + action accept + description FWFDE34_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWFDE34_1 + } + port 18081,18080 + } + protocol tcp + } + rule 3373 { + action accept + description FWB6101_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWB6101_1 + } + port 2280 + } + protocol tcp + } + rule 3377 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.84.203 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 123.231.84.203 + } + } + rule 3378 { + action accept + description FW1D511_2-TCP-ALLOW-92.29.46.47 + destination { + group { + address-group DT_FW1D511_2 + } + port 9090 + } + protocol tcp + source { + address 92.29.46.47 + } + } + rule 3386 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.208.175 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.208.175 + } + } + rule 3387 { + action accept + description FW1ACD9_2-TCP-ALLOW-89.197.148.38 + destination { + group { + address-group DT_FW1ACD9_2 + } + port 5015,22 + } + protocol tcp + source { + address 89.197.148.38 + } + } + rule 3388 { + action accept + description FW1ACD9_2-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW1ACD9_2 + } + port 9000-10999,5090,5060 + } + protocol udp + } + rule 3389 { + action accept + description FW1ACD9_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW1ACD9_2 + } + port 5090,5060-5062 + } + protocol tcp + } + rule 3391 { + action accept + description FWA0B7F_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA0B7F_1 + } + port 53 + } + protocol tcp_udp + } + rule 3392 { + action accept + description FW56335_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW56335_2 + } + port 18081,18080 + } + protocol tcp + } + rule 3395 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.90 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.212.90 + } + } + rule 3396 { + action accept + description FW4D3E6_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW4D3E6_1 + } + port 18081,18080 + } + protocol tcp + } + rule 3397 { + action accept + description FWB118A_1-TCP-ALLOW-188.65.177.58 + destination { + group { + address-group DT_FWB118A_1 + } + port 49152-65534,8447,8443,22,21,20 + } + protocol tcp + source { + address 188.65.177.58 + } + } + rule 3398 { + action accept + description FWB118A_1-TCP-ALLOW-77.68.103.13 + destination { + group { + address-group DT_FWB118A_1 + } + port 49152-65534,8447,8443,22,21,20 + } + protocol tcp + source { + address 77.68.103.13 + } + } + rule 3399 { + action accept + description FWB118A_1-TCP-ALLOW-80.5.71.130 + destination { + group { + address-group DT_FWB118A_1 + } + port 49152-65534,8447,8443,22,21,20 + } + protocol tcp + source { + address 80.5.71.130 + } + } + rule 3402 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.205 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.213.205 + } + } + rule 3408 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.31 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.31 + } + } + rule 3409 { + action accept + description FW539FB_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW539FB_1 + } + port 389 + } + protocol tcp + } + rule 3411 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.185 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.213.185 + } + } + rule 3415 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-116.206.245.124 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 116.206.245.124 + } + } + rule 3416 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.75 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.213.75 + } + } + rule 3417 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.34 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.214.34 + } + } + rule 3418 { + action accept + description FWEF92E_5-UDP-ALLOW-77.68.77.70 + destination { + group { + address-group DT_FWEF92E_5 + } + port 500 + } + protocol udp + source { + address 77.68.77.70 + } + } + rule 3419 { + action accept + description FWEF92E_5-UDP-ALLOW-77.68.92.33 + destination { + group { + address-group DT_FWEF92E_5 + } + port 500 + } + protocol udp + source { + address 77.68.92.33 + } + } + rule 3420 { + action accept + description FWEF92E_5-UDP-ALLOW-77.68.93.82 + destination { + group { + address-group DT_FWEF92E_5 + } + port 500 + } + protocol udp + source { + address 77.68.93.82 + } + } + rule 3421 { + action accept + description FWEF92E_5-UDP-ALLOW-88.208.198.93 + destination { + group { + address-group DT_FWEF92E_5 + } + port 500 + } + protocol udp + source { + address 88.208.198.93 + } + } + rule 3422 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.94 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.214.94 + } + } + rule 3424 { + action accept + description FW18E6E_3-TCP-ALLOW-148.253.173.244 + destination { + group { + address-group DT_FW18E6E_3 + } + port 3306 + } + protocol tcp + source { + address 148.253.173.244 + } + } + rule 3425 { + action accept + description FW18E6E_3-TCP-ALLOW-148.253.173.246 + destination { + group { + address-group DT_FW18E6E_3 + } + port 3306 + } + protocol tcp + source { + address 148.253.173.246 + } + } + rule 3426 { + action accept + description FW18E6E_3-TCP-ALLOW-195.97.222.122 + destination { + group { + address-group DT_FW18E6E_3 + } + port 3306 + } + protocol tcp + source { + address 195.97.222.122 + } + } + rule 3431 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.111 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.111 + } + } + rule 3432 { + action accept + description FW06940_3-TCP_UDP-ALLOW-74.208.41.119 + destination { + group { + address-group DT_FW06940_3 + } + port 1-65535 + } + protocol tcp_udp + source { + address 74.208.41.119 + } + } + rule 3438 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.252 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.209.252 + } + } + rule 3440 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.118 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.214.118 + } + } + rule 3442 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.15 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.209.15 + } + } + rule 3446 { + action accept + description FWC32BE_1-ANY-ALLOW-3.65.3.75 + destination { + group { + address-group DT_FWC32BE_1 + } + } + source { + address 3.65.3.75 + } + } + rule 3447 { + action accept + description FWC32BE_1-TCP-ALLOW-217.155.2.52 + destination { + group { + address-group DT_FWC32BE_1 + } + port 22 + } + protocol tcp + source { + address 217.155.2.52 + } + } + rule 3448 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.243 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.214.243 + } + } + rule 3449 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.117 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.214.117 + } + } + rule 3450 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.4 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.210.4 + } + } + rule 3452 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.177 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.210.177 + } + } + rule 3454 { + action accept + description FWD498E_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD498E_1 + } + port 44158 + } + protocol tcp + } + rule 3455 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.147 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.147 + } + } + rule 8500 { + action drop + description "Deny traffic to any private address" + destination { + group { + network-group RFC1918 + } + } + source { + group { + address-group CLUSTER_ADDRESSES + } + } + } + rule 8510 { + action accept + description "Default allow rule" + destination { + group { + address-group !CLUSTER_ADDRESSES + } + } + source { + group { + address-group CLUSTER_ADDRESSES + } + } + } + } + name LOCAL-LAN { + default-action drop + rule 2 { + action accept + destination { + address 10.255.255.1 + } + protocol icmp + source { + group { + address-group CLUSTER_ADDRESSES + } + } + } + rule 4 { + action accept + destination { + group { + address-group LAN_ADDRESSES + } + } + source { + group { + address-group LAN_ADDRESSES + } + } + } + rule 10 { + action accept + description "Multicast para VRRP" + destination { + address 224.0.0.18 + } + source { + group { + address-group LAN_ADDRESSES + } + } + } + } + name LOCAL-SYNC { + default-action drop + rule 5 { + action accept + description "Permitir trafico sync entre nodos" + destination { + address 10.4.51.132/30 + } + source { + address 10.4.51.132/30 + } + } + } + name LOCAL-WAN { + default-action drop + description "External connections from VLAN2701 to this system" + rule 10 { + action accept + description "Allow intra-vlan connections" + destination { + address 109.228.63.128/25 + } + source { + address 109.228.63.128/25 + } + } + rule 20 { + action accept + description "Allow Arsys desktops to contact this system" + source { + group { + address-group MANAGEMENT_ADDRESSES + } + } + } + } + name WAN-INBOUND { + default-action drop + rule 10 { + action accept + description "Management from HN-ES" + source { + group { + address-group MANAGEMENT_ADDRESSES + } + } + } + rule 20 { + action accept + description "Connections from Load Balancer to Frontends - TCP Proxy" + destination { + group { + address-group CLUSTER_ADDRESSES + } + } + source { + group { + address-group NLB_ADDRESSES + } + } + } + rule 30 { + action accept + description "Allow external probes" + destination { + group { + address-group NAGIOS_PROBES + } + } + protocol icmp + } + rule 40 { + action accept + description "Allow Centreon servers traffic to VMs" + destination { + group { + address-group CLUSTER_ADDRESSES + } + } + source { + group { + address-group CENTREON_SERVERS + } + } + } + rule 50 { + action accept + description "Allow CMK to check dnscache servers - TCP" + destination { + group { + address-group DNSCACHE_SERVERS + } + port 22,53,6556 + } + protocol tcp + source { + group { + address-group CMK_SATELLITES + } + } + } + rule 65 { + action accept + description "Allow CMK to check dnscache servers - UDP" + destination { + group { + address-group DNSCACHE_SERVERS + } + port 53 + } + protocol udp + source { + group { + address-group CMK_SATELLITES + } + } + } + rule 70 { + action accept + description "Allow CMK to check dnscache servers - ICMP" + destination { + group { + address-group DNSCACHE_SERVERS + } + } + protocol icmp + source { + group { + address-group CMK_SATELLITES + } + } + } + rule 80 { + action accept + description "Allow CMK to check monitoring sensors - TCP" + destination { + group { + address-group NAGIOS_PROBES + } + port 6556 + } + protocol tcp + source { + group { + address-group CMK_SATELLITES + } + } + } + rule 90 { + action accept + description "Allow CMK to check monitoring sensors - ICMP" + destination { + group { + address-group NAGIOS_PROBES + } + } + protocol icmp + source { + group { + address-group CMK_SATELLITES + } + } + } + rule 2000 { + action accept + description "TOP port - SSH" + destination { + group { + address-group G-22-TCP + } + port ssh + } + protocol tcp + } + rule 2001 { + action accept + description "TOP port - RDESKTOP" + destination { + group { + address-group G-3389-TCP + } + port 3389 + } + protocol tcp + } + rule 2002 { + action accept + description "TOP port - HTTP" + destination { + group { + address-group G-80-TCP + } + port http + } + protocol tcp + } + rule 2003 { + action accept + description "TOP port - HTTPS" + destination { + group { + address-group G-443-TCP + } + port https + } + protocol tcp + } + rule 2004 { + action accept + description "TOP port - DOMAIN TCP" + destination { + group { + address-group G-53-TCP + } + port domain + } + protocol tcp + } + rule 2005 { + action accept + description "TOP port - DOMAIN UDP" + destination { + group { + address-group G-53-UDP + } + port domain + } + protocol udp + } + rule 2006 { + action accept + description "TOP port - SMTP" + destination { + group { + address-group G-25-TCP + } + port smtp + } + protocol tcp + } + rule 2007 { + action accept + description "TOP port - IMAP" + destination { + group { + address-group G-143-TCP + } + port imap2 + } + protocol tcp + } + rule 2008 { + action accept + description "TOP port - POP3" + destination { + group { + address-group G-110-TCP + } + port pop3 + } + protocol tcp + } + rule 2009 { + action accept + description "TOP port - MSSQL TCP" + destination { + group { + address-group G-1433-TCP + } + port ms-sql-s + } + protocol tcp + } + rule 2010 { + action accept + description "TOP port - MYSQL TCP" + destination { + group { + address-group G-3306-TCP + } + port mysql + } + protocol tcp + } + rule 2011 { + action accept + description "TOP port - FTPDATA" + destination { + group { + address-group G-20-TCP + } + port ftp-data + } + protocol tcp + } + rule 2012 { + action accept + description "TOP port - FTP" + destination { + group { + address-group G-21-TCP + } + port ftp + } + protocol tcp + } + rule 2013 { + action accept + description "TOP port - SSMTP" + destination { + group { + address-group G-465-TCP + } + port ssmtp + } + protocol tcp + } + rule 2014 { + action accept + description "TOP port - SMTPS" + destination { + group { + address-group G-587-TCP + } + port 587 + } + protocol tcp + } + rule 2015 { + action accept + description "TOP port - IMAPS" + destination { + group { + address-group G-993-TCP + } + port imaps + } + protocol tcp + } + rule 2016 { + action accept + description "TOP port - POP3S" + destination { + group { + address-group G-995-TCP + } + port pop3s + } + protocol tcp + } + rule 2017 { + action accept + description "TOP port - TOMCAT" + destination { + group { + address-group G-8080-TCP + } + port 8080 + } + protocol tcp + } + rule 2018 { + action accept + description "TOP port - Alternative HTTPS" + destination { + group { + address-group G-8443-TCP + } + port 8443 + } + protocol tcp + } + rule 2019 { + action accept + description "TOP port - 10000/TCP" + destination { + group { + address-group G-10000-TCP + } + port 10000 + } + protocol tcp + } + rule 2020 { + action accept + description "TOP port - 8447/TCP" + destination { + group { + address-group G-8447-TCP + } + port 8447 + } + protocol tcp + } + rule 2040 { + action accept + description "TOP port - All ports open" + destination { + group { + address-group G-ALL_OPEN + } + } + } + rule 2050 { + action accept + description "ICMP group" + destination { + group { + address-group G-ICMP + } + } + protocol icmp + } + rule 2100 { + action accept + description FW2BB8D_1-TCP-ALLOW-104.192.143.2 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 7999,22 + } + protocol tcp + source { + address 104.192.143.2 + } + } + rule 2101 { + action accept + description FW19987_4-TCP-ALLOW-77.68.74.54 + destination { + group { + address-group DT_FW19987_4 + } + port 443 + } + protocol tcp + source { + address 77.68.74.54 + } + } + rule 2102 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-109.72.210.46 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 109.72.210.46 + } + } + rule 2103 { + action accept + description FW5A77C_16-TCP-ALLOW-213.171.217.107 + destination { + group { + address-group DT_FW5A77C_16 + } + port 22 + } + protocol tcp + source { + address 213.171.217.107 + } + } + rule 2104 { + action accept + description FW826BA_3-TCP-ALLOW-164.177.156.192 + destination { + group { + address-group DT_FW826BA_3 + } + port 3389,1433,21 + } + protocol tcp + source { + address 164.177.156.192 + } + } + rule 2105 { + action accept + description FWDAA4F_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWDAA4F_1 + } + port 22335 + } + protocol tcp + } + rule 2106 { + action accept + description FW6D0CD_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW6D0CD_1 + } + port 6900,7000 + } + protocol tcp + } + rule 2107 { + action accept + description FW6D0CD_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW6D0CD_1 + } + port 9001 + } + protocol tcp_udp + } + rule 2108 { + action accept + description FW06176_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW06176_1 + } + port 5900 + } + protocol tcp + } + rule 2109 { + action accept + description FW19987_4-TCP-ALLOW-77.68.77.70 + destination { + group { + address-group DT_FW19987_4 + } + port 443 + } + protocol tcp + source { + address 77.68.77.70 + } + } + rule 2110 { + action accept + description FWF7B68_1-TCP-ALLOW-54.221.251.224 + destination { + group { + address-group DT_FWF7B68_1 + } + port 8443,3306,22,21,20 + } + protocol tcp + source { + address 54.221.251.224 + } + } + rule 2111 { + action accept + description FW05AD0_2-TCP-ALLOW-178.251.181.41 + destination { + group { + address-group DT_FW05AD0_2 + } + port 3389,1433,21 + } + protocol tcp + source { + address 178.251.181.41 + } + } + rule 2112 { + action accept + description FW05AD0_2-TCP-ALLOW-178.251.181.6 + destination { + group { + address-group DT_FW05AD0_2 + } + port 3389,1433,21 + } + protocol tcp + source { + address 178.251.181.6 + } + } + rule 2113 { + action accept + description VPN-7030-ANY-ALLOW-10.4.58.119 + destination { + group { + address-group DT_VPN-7030 + } + } + source { + address 10.4.58.119 + } + } + rule 2114 { + action accept + description FW58C69_4-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW58C69_4 + } + port 5666 + } + protocol tcp + } + rule 2115 { + action accept + description FW2BB8D_1-TCP-ALLOW-185.201.180.35 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 27017,5000,22 + } + protocol tcp + source { + address 185.201.180.35 + } + } + rule 2116 { + action accept + description FW19987_4-TCP-ALLOW-77.68.8.74 + destination { + group { + address-group DT_FW19987_4 + } + port 3389,445,443 + } + protocol tcp + source { + address 77.68.8.74 + } + } + rule 2117 { + action accept + description FW19987_4-TCP-ALLOW-87.224.33.215 + destination { + group { + address-group DT_FW19987_4 + } + port 3389,445,443 + } + protocol tcp + source { + address 87.224.33.215 + } + } + rule 2118 { + action accept + description FW5658C_1-TCP-ALLOW-212.159.160.65 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443,3389,3306,22,21 + } + protocol tcp + source { + address 212.159.160.65 + } + } + rule 2119 { + action accept + description FW5658C_1-TCP-ALLOW-79.78.20.149 + destination { + group { + address-group DT_FW5658C_1 + } + port 8447,8443,3389,3306,993,143,22,21 + } + protocol tcp + source { + address 79.78.20.149 + } + } + rule 2120 { + action accept + description FW5658C_1-TCP-ALLOW-77.68.77.185 + destination { + group { + address-group DT_FW5658C_1 + } + port 3306 + } + protocol tcp + source { + address 77.68.77.185 + } + } + rule 2121 { + action accept + description FW5658C_1-TCP-ALLOW-82.165.232.19 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443,3389 + } + protocol tcp + source { + address 82.165.232.19 + } + } + rule 2122 { + action accept + description FW2C5AE_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW2C5AE_1 + } + port 30303,5717 + } + protocol tcp_udp + } + rule 2123 { + action accept + description VPN-12899-ANY-ALLOW-10.4.58.207 + destination { + group { + address-group DT_VPN-12899 + } + } + source { + address 10.4.58.207 + } + } + rule 2124 { + action accept + description FW7648D_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW7648D_1 + } + port 8501,8050,7801,4444,1443 + } + protocol tcp + } + rule 2125 { + action accept + description FW0C2E6_4-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW0C2E6_4 + } + port 1194 + } + protocol udp + } + rule 2126 { + action accept + description FW5658C_1-TCP-ALLOW-39.37.175.132 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443 + } + protocol tcp + source { + address 39.37.175.132 + } + } + rule 2127 { + action accept + description FW826BA_3-TCP-ALLOW-165.255.242.223 + destination { + group { + address-group DT_FW826BA_3 + } + port 3389,1433,21 + } + protocol tcp + source { + address 165.255.242.223 + } + } + rule 2128 { + action accept + description VPN-10131-ANY-ALLOW-10.4.56.51 + destination { + group { + address-group DT_VPN-10131 + } + } + source { + address 10.4.56.51 + } + } + rule 2129 { + action accept + description FW2BB8D_1-TCP-ALLOW-212.227.84.142 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 22 + } + protocol tcp + source { + address 212.227.84.142 + } + } + rule 2130 { + action accept + description FW2BB8D_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW2BB8D_1 + } + port 53 + } + protocol tcp_udp + } + rule 2131 { + action accept + description FWFDD94_15-TCP-ALLOW-90.29.180.234 + destination { + group { + address-group DT_FWFDD94_15 + } + port 5683,1883 + } + protocol tcp + source { + address 90.29.180.234 + } + } + rule 2132 { + action accept + description VPN-10131-ANY-ALLOW-10.4.57.51 + destination { + group { + address-group DT_VPN-10131 + } + } + source { + address 10.4.57.51 + } + } + rule 2133 { + action accept + description FW2BB8D_1-TCP-ALLOW-109.228.49.193 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 5000 + } + protocol tcp + source { + address 109.228.49.193 + } + } + rule 2134 { + action accept + description FW81138_1-ICMP-ALLOW-3.10.221.168 + destination { + group { + address-group DT_FW81138_1 + } + } + protocol icmp + source { + address 3.10.221.168 + } + } + rule 2135 { + action accept + description FWB28B6_5-AH-ALLOW-77.68.36.46 + destination { + group { + address-group DT_FWB28B6_5 + } + } + protocol ah + source { + address 77.68.36.46 + } + } + rule 2136 { + action accept + description FWB28B6_5-ESP-ALLOW-77.68.36.46 + destination { + group { + address-group DT_FWB28B6_5 + } + } + protocol esp + source { + address 77.68.36.46 + } + } + rule 2137 { + action accept + description FW825C8_24-TCP-ALLOW-77.68.87.201 + destination { + group { + address-group DT_FW825C8_24 + } + port 1433 + } + protocol tcp + source { + address 77.68.87.201 + } + } + rule 2138 { + action accept + description FWB28B6_5-AH-ALLOW-213.171.196.146 + destination { + group { + address-group DT_FWB28B6_5 + } + } + protocol ah + source { + address 213.171.196.146 + } + } + rule 2139 { + action accept + description FWB28B6_5-ESP-ALLOW-213.171.196.146 + destination { + group { + address-group DT_FWB28B6_5 + } + } + protocol esp + source { + address 213.171.196.146 + } + } + rule 2140 { + action accept + description FWB28B6_5-UDP-ALLOW-213.171.196.146 + destination { + group { + address-group DT_FWB28B6_5 + } + port 500,4500 + } + protocol udp + source { + address 213.171.196.146 + } + } + rule 2141 { + action accept + description FWB28B6_5-TCP_UDP-ALLOW-213.171.196.146 + destination { + group { + address-group DT_FWB28B6_5 + } + port 1701 + } + protocol tcp_udp + source { + address 213.171.196.146 + } + } + rule 2142 { + action accept + description FWB28B6_5-TCP_UDP-ALLOW-77.68.36.46 + destination { + group { + address-group DT_FWB28B6_5 + } + port 1701 + } + protocol tcp_udp + source { + address 77.68.36.46 + } + } + rule 2143 { + action accept + description FWB28B6_5-UDP-ALLOW-77.68.36.46 + destination { + group { + address-group DT_FWB28B6_5 + } + port 500,4500 + } + protocol udp + source { + address 77.68.36.46 + } + } + rule 2144 { + action accept + description VPN-12899-ANY-ALLOW-10.4.59.207 + destination { + group { + address-group DT_VPN-12899 + } + } + source { + address 10.4.59.207 + } + } + rule 2145 { + action accept + description FWB28B6_5-TCP-ALLOW-81.130.141.175 + destination { + group { + address-group DT_FWB28B6_5 + } + port 3389 + } + protocol tcp + source { + address 81.130.141.175 + } + } + rule 2146 { + action accept + description FWB28B6_5-UDP-ALLOW-77.68.38.195 + destination { + group { + address-group DT_FWB28B6_5 + } + port 4500,500 + } + protocol udp + source { + address 77.68.38.195 + } + } + rule 2147 { + action accept + description FWB28B6_5-AH-ALLOW-77.68.38.195 + destination { + group { + address-group DT_FWB28B6_5 + } + } + protocol ah + source { + address 77.68.38.195 + } + } + rule 2148 { + action accept + description FWB28B6_5-ESP-ALLOW-77.68.38.195 + destination { + group { + address-group DT_FWB28B6_5 + } + } + protocol esp + source { + address 77.68.38.195 + } + } + rule 2149 { + action accept + description FWB28B6_5-TCP_UDP-ALLOW-77.68.38.195 + destination { + group { + address-group DT_FWB28B6_5 + } + port 1701 + } + protocol tcp_udp + source { + address 77.68.38.195 + } + } + rule 2150 { + action accept + description FW5658C_1-TCP-ALLOW-39.37.178.77 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443 + } + protocol tcp + source { + address 39.37.178.77 + } + } + rule 2151 { + action accept + description FW5A77C_16-TCP-ALLOW-51.241.139.56 + destination { + group { + address-group DT_FW5A77C_16 + } + port 22 + } + protocol tcp + source { + address 51.241.139.56 + } + } + rule 2152 { + action accept + description FWA86ED_101-TCP-ALLOW-150.143.57.138 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389 + } + protocol tcp + source { + address 150.143.57.138 + } + } + rule 2153 { + action accept + description FW6ECA4_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW6ECA4_1 + } + port 3939,3335,3334,3333,3000,999,444 + } + protocol tcp_udp + } + rule 2154 { + action accept + description FW5658C_1-TCP-ALLOW-39.45.13.20 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443 + } + protocol tcp + source { + address 39.45.13.20 + } + } + rule 2155 { + action accept + description FW481D7_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW481D7_1 + } + port 3478 + } + protocol tcp_udp + } + rule 2156 { + action accept + description FW5A5D7_3-GRE-ALLOW-51.219.222.28 + destination { + group { + address-group DT_FW5A5D7_3 + } + } + protocol gre + source { + address 51.219.222.28 + } + } + rule 2157 { + action accept + description FWA86ED_101-TCP-ALLOW-94.195.127.217 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 94.195.127.217 + } + } + rule 2158 { + action accept + description FW2E060_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2E060_1 + } + port 49152-65535,8443-8447 + } + protocol tcp + } + rule 2159 { + action accept + description FWFDD94_15-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWFDD94_15 + } + port 9090,5080,1935 + } + protocol tcp + } + rule 2160 { + action accept + description FW5658C_1-TCP-ALLOW-39.45.190.224 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443 + } + protocol tcp + source { + address 39.45.190.224 + } + } + rule 2161 { + action accept + description FW9E550_1-TCP-ALLOW-109.249.187.56 + destination { + group { + address-group DT_FW9E550_1 + } + port 3389 + } + protocol tcp + source { + address 109.249.187.56 + } + } + rule 2162 { + action accept + description FW89619_1-TCP-ALLOW-81.133.80.114 + destination { + group { + address-group DT_FW89619_1 + } + port 22 + } + protocol tcp + source { + address 81.133.80.114 + } + } + rule 2163 { + action accept + description FW8A3FC_3-TCP-ALLOW-212.227.72.218 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 212.227.72.218 + } + } + rule 2164 { + action accept + description FW0E383_9-TCP-ALLOW-151.229.59.51 + destination { + group { + address-group DT_FW0E383_9 + } + port 1433 + } + protocol tcp + source { + address 151.229.59.51 + } + } + rule 2165 { + action accept + description FW8AFF1_7-TCP-ALLOW-178.251.181.41 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433,21 + } + protocol tcp + source { + address 178.251.181.41 + } + } + rule 2166 { + action accept + description FW3CAAB_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW3CAAB_1 + } + port 49152-65535,30000-30400,8443-8447,5432,80-110,21-25 + } + protocol tcp + } + rule 2167 { + action accept + description FW91B7A_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW91B7A_1 + } + port 3389,80 + } + protocol tcp_udp + } + rule 2168 { + action accept + description FW40416_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW40416_1 + } + port 1-65535 + } + protocol tcp + } + rule 2169 { + action accept + description FW5A77C_16-TCP-ALLOW-81.151.24.216 + destination { + group { + address-group DT_FW5A77C_16 + } + port 10000,22 + } + protocol tcp + source { + address 81.151.24.216 + } + } + rule 2170 { + action accept + description VPN-7030-ANY-ALLOW-10.4.59.119 + destination { + group { + address-group DT_VPN-7030 + } + } + source { + address 10.4.59.119 + } + } + rule 2171 { + action accept + description FW0E383_9-TCP-ALLOW-62.252.94.138 + destination { + group { + address-group DT_FW0E383_9 + } + port 3389,1433 + } + protocol tcp + source { + address 62.252.94.138 + } + } + rule 2172 { + action accept + description FW89619_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW89619_1 + } + port 5015,5001,5000 + } + protocol tcp + } + rule 2173 { + action accept + description FW89619_1-TCP_UDP-ALLOW-167.98.162.142 + destination { + group { + address-group DT_FW89619_1 + } + port 5060 + } + protocol tcp_udp + source { + address 167.98.162.142 + } + } + rule 2174 { + action accept + description FW013EF_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW013EF_2 + } + port 44445,7770-7800,5090,5060-5070,5015,5001,2000-2500 + } + protocol tcp + } + rule 2175 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.12 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.214.12 + } + } + rule 2176 { + action accept + description VPN-15625-ANY-ALLOW-10.4.88.79 + destination { + group { + address-group DT_VPN-15625 + } + } + source { + address 10.4.88.79 + } + } + rule 2177 { + action accept + description FW1F3D0_6-TCP-ALLOW-109.228.53.128 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306,22 + } + protocol tcp + source { + address 109.228.53.128 + } + } + rule 2178 { + action accept + description FW8AFF1_7-TCP-ALLOW-178.251.181.6 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 3389,1433,21 + } + protocol tcp + source { + address 178.251.181.6 + } + } + rule 2179 { + action accept + description FW578BE_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW578BE_1 + } + port 23,1521,1522 + } + protocol tcp + } + rule 2180 { + action accept + description FWE012D_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE012D_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2181 { + action accept + description FW8AFF1_7-TCP-ALLOW-213.171.209.161 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 3389,1433,21 + } + protocol tcp + source { + address 213.171.209.161 + } + } + rule 2182 { + action accept + description VPN-8203-ANY-ALLOW-10.4.58.109 + destination { + group { + address-group DT_VPN-8203 + } + } + source { + address 10.4.58.109 + } + } + rule 2183 { + action accept + description VPN-9415-ANY-ALLOW-10.4.58.168 + destination { + group { + address-group DT_VPN-9415 + } + } + source { + address 10.4.58.168 + } + } + rule 2184 { + action accept + description VPN-9415-ANY-ALLOW-10.4.59.168 + destination { + group { + address-group DT_VPN-9415 + } + } + source { + address 10.4.59.168 + } + } + rule 2185 { + action accept + description FW27A8F_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW27A8F_1 + } + port 9990,8458,8090,6543,5432 + } + protocol tcp + } + rule 2186 { + action accept + description FW2BB8D_1-TCP-ALLOW-77.68.11.224 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 5000 + } + protocol tcp + source { + address 77.68.11.224 + } + } + rule 2187 { + action accept + description VPN-15625-ANY-ALLOW-10.4.89.79 + destination { + group { + address-group DT_VPN-15625 + } + } + source { + address 10.4.89.79 + } + } + rule 2188 { + action accept + description VPN-14649-ANY-ALLOW-10.4.86.35 + destination { + group { + address-group DT_VPN-14649 + } + } + source { + address 10.4.86.35 + } + } + rule 2189 { + action accept + description VPN-14649-ANY-ALLOW-10.4.87.35 + destination { + group { + address-group DT_VPN-14649 + } + } + source { + address 10.4.87.35 + } + } + rule 2190 { + action accept + description VPN-14657-ANY-ALLOW-10.4.86.38 + destination { + group { + address-group DT_VPN-14657 + } + } + source { + address 10.4.86.38 + } + } + rule 2191 { + action accept + description VPN-14657-ANY-ALLOW-10.4.87.38 + destination { + group { + address-group DT_VPN-14657 + } + } + source { + address 10.4.87.38 + } + } + rule 2192 { + action accept + description VPN-14658-ANY-ALLOW-10.4.88.38 + destination { + group { + address-group DT_VPN-14658 + } + } + source { + address 10.4.88.38 + } + } + rule 2193 { + action accept + description VPN-14658-ANY-ALLOW-10.4.89.38 + destination { + group { + address-group DT_VPN-14658 + } + } + source { + address 10.4.89.38 + } + } + rule 2194 { + action accept + description FW0BB22_1-GRE-ALLOW-ANY + destination { + group { + address-group DT_FW0BB22_1 + } + } + protocol gre + } + rule 2195 { + action accept + description FW0BB22_1-ESP-ALLOW-ANY + destination { + group { + address-group DT_FW0BB22_1 + } + } + protocol esp + } + rule 2196 { + action accept + description FW1CC15_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW1CC15_2 + } + port 8089,8085,990,81 + } + protocol tcp + } + rule 2197 { + action accept + description FW8AFF1_7-TCP-ALLOW-109.228.0.124 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433 + } + protocol tcp + source { + address 109.228.0.124 + } + } + rule 2198 { + action accept + description FW5A5D7_3-TCP-ALLOW-51.219.222.28 + destination { + group { + address-group DT_FW5A5D7_3 + } + port 8172,3389,1723,1701,47 + } + protocol tcp + source { + address 51.219.222.28 + } + } + rule 2199 { + action accept + description FW1CB16_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW1CB16_1 + } + port 3306,27017,53 + } + protocol tcp_udp + } + rule 2200 { + action accept + description FWE47DA_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE47DA_1 + } + port 7770-7800,44445 + } + protocol tcp + } + rule 2201 { + action accept + description FW37E59_5-TCP-ALLOW-77.68.20.244 + destination { + group { + address-group DT_FW37E59_5 + } + port 30303 + } + protocol tcp + source { + address 77.68.20.244 + } + } + rule 2202 { + action accept + description FW274FD_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW274FD_1 + } + port 49152-65534 + } + protocol tcp + } + rule 2203 { + action accept + description FW6CD7E_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW6CD7E_2 + } + port 49152-65535 + } + protocol tcp + } + rule 2204 { + action accept + description FW826BA_3-TCP-ALLOW-178.17.252.59 + destination { + group { + address-group DT_FW826BA_3 + } + port 21 + } + protocol tcp + source { + address 178.17.252.59 + } + } + rule 2205 { + action accept + description FW89619_1-TCP_UDP-ALLOW-185.83.64.108 + destination { + group { + address-group DT_FW89619_1 + } + port 5060 + } + protocol tcp_udp + source { + address 185.83.64.108 + } + } + rule 2206 { + action accept + description FW0937A_1-TCP-ALLOW-83.135.134.13 + destination { + group { + address-group DT_FW0937A_1 + } + port 22 + } + protocol tcp + source { + address 83.135.134.13 + } + } + rule 2207 { + action accept + description FW2BB8D_1-TCP-ALLOW-77.68.112.64 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 27017,5000 + } + protocol tcp + source { + address 77.68.112.64 + } + } + rule 2208 { + action accept + description FW6CD7E_2-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW6CD7E_2 + } + port 53 + } + protocol tcp_udp + } + rule 2209 { + action accept + description FW1F3D0_6-TCP-ALLOW-194.73.17.47 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306,22 + } + protocol tcp + source { + address 194.73.17.47 + } + } + rule 2210 { + action accept + description FW0E383_9-TCP-ALLOW-77.68.115.33 + destination { + group { + address-group DT_FW0E383_9 + } + port 1433 + } + protocol tcp + source { + address 77.68.115.33 + } + } + rule 2211 { + action accept + description FWA3EA3_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA3EA3_1 + } + port 943 + } + protocol tcp + } + rule 2212 { + action accept + description FW6863A_4-TCP-ALLOW-82.165.100.25 + destination { + group { + address-group DT_FW6863A_4 + } + port 21-10000 + } + protocol tcp + source { + address 82.165.100.25 + } + } + rule 2213 { + action accept + description FWECBFB_14-TCP-ALLOW-109.228.59.50 + destination { + group { + address-group DT_FWECBFB_14 + } + port 22 + } + protocol tcp + source { + address 109.228.59.50 + } + } + rule 2214 { + action accept + description FW2F868_6-TCP-ALLOW-213.171.217.100 + destination { + group { + address-group DT_FW2F868_6 + } + port 22 + } + protocol tcp + source { + address 213.171.217.100 + } + } + rule 2215 { + action accept + description FWD7EAB_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD7EAB_1 + } + port 60000-60100 + } + protocol tcp + } + rule 2216 { + action accept + description FWEB321_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWEB321_1 + } + port 113,4190 + } + protocol tcp + } + rule 2217 { + action accept + description FW9C682_3-TCP-ALLOW-195.206.180.132 + destination { + group { + address-group DT_FW9C682_3 + } + port 8443,22 + } + protocol tcp + source { + address 195.206.180.132 + } + } + rule 2218 { + action accept + description VPN-8159-ANY-ALLOW-10.4.58.91 + destination { + group { + address-group DT_VPN-8159 + } + } + source { + address 10.4.58.91 + } + } + rule 2219 { + action accept + description VPN-21673-ANY-ALLOW-10.4.88.187 + destination { + group { + address-group DT_VPN-21673 + } + } + source { + address 10.4.88.187 + } + } + rule 2220 { + action accept + description VPN-21673-ANY-ALLOW-10.4.89.187 + destination { + group { + address-group DT_VPN-21673 + } + } + source { + address 10.4.89.187 + } + } + rule 2221 { + action accept + description VPN-21821-ANY-ALLOW-10.4.88.49 + destination { + group { + address-group DT_VPN-21821 + } + } + source { + address 10.4.88.49 + } + } + rule 2222 { + action accept + description VPN-21821-ANY-ALLOW-10.4.89.49 + destination { + group { + address-group DT_VPN-21821 + } + } + source { + address 10.4.89.49 + } + } + rule 2223 { + action accept + description FWECBFB_14-TCP-ALLOW-81.133.80.58 + destination { + group { + address-group DT_FWECBFB_14 + } + port 22 + } + protocol tcp + source { + address 81.133.80.58 + } + } + rule 2224 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.238 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.238 + } + } + rule 2225 { + action accept + description FW826BA_3-TCP-ALLOW-185.212.168.51 + destination { + group { + address-group DT_FW826BA_3 + } + port 3389,1433,21 + } + protocol tcp + source { + address 185.212.168.51 + } + } + rule 2226 { + action accept + description FW8B21D_1-ANY-ALLOW-212.187.250.2 + destination { + group { + address-group DT_FW8B21D_1 + } + } + source { + address 212.187.250.2 + } + } + rule 2227 { + action accept + description FW35F7B_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW35F7B_1 + } + port 1434 + } + protocol tcp_udp + } + rule 2228 { + action accept + description FWD338A_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD338A_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2229 { + action accept + description FW35F7B_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW35F7B_1 + } + port 56791 + } + protocol tcp + } + rule 2230 { + action accept + description FW0E383_9-TCP-ALLOW-77.68.77.114 + destination { + group { + address-group DT_FW0E383_9 + } + port 1433 + } + protocol tcp + source { + address 77.68.77.114 + } + } + rule 2231 { + action accept + description FW90AE3_1-TCP-ALLOW-194.74.137.17 + destination { + group { + address-group DT_FW90AE3_1 + } + port 22 + } + protocol tcp + source { + address 194.74.137.17 + } + } + rule 2232 { + action accept + description FW52F6F_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW52F6F_1 + } + port 53 + } + protocol tcp_udp + } + rule 2233 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.23.109 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306,22 + } + protocol tcp + source { + address 77.68.23.109 + } + } + rule 2234 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.247 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.210.247 + } + } + rule 2235 { + action accept + description FW4E314_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW4E314_1 + } + port 53 + } + protocol tcp_udp + } + rule 2236 { + action accept + description FW73573_2-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW73573_2 + } + port 25 + } + protocol tcp_udp + } + rule 2237 { + action accept + description FW0E383_9-TCP-ALLOW-77.68.93.89 + destination { + group { + address-group DT_FW0E383_9 + } + port 1433 + } + protocol tcp + source { + address 77.68.93.89 + } + } + rule 2238 { + action accept + description FW856FA_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW856FA_1 + } + port 6003 + } + protocol tcp + } + rule 2239 { + action accept + description FWECBFB_14-TCP-ALLOW-81.19.214.155 + destination { + group { + address-group DT_FWECBFB_14 + } + port 22 + } + protocol tcp + source { + address 81.19.214.155 + } + } + rule 2240 { + action accept + description FW826BA_3-TCP-ALLOW-51.219.168.170 + destination { + group { + address-group DT_FW826BA_3 + } + port 3389,1433,21 + } + protocol tcp + source { + address 51.219.168.170 + } + } + rule 2241 { + action accept + description FW30D21_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW30D21_1 + } + port 2083-2087,53,2812,2096,25,993,587 + } + protocol tcp_udp + } + rule 2242 { + action accept + description FWA076E_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA076E_1 + } + port 2199,2197 + } + protocol tcp + } + rule 2243 { + action accept + description FWA076E_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA076E_1 + } + port 8000-8010 + } + protocol tcp_udp + } + rule 2244 { + action accept + description FW8A3FC_3-TCP-ALLOW-82.165.166.41 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 8447,8443,443,80,22 + } + protocol tcp + source { + address 82.165.166.41 + } + } + rule 2245 { + action accept + description FW2F868_6-TCP-ALLOW-213.171.217.180 + destination { + group { + address-group DT_FW2F868_6 + } + port 22,80 + } + protocol tcp + source { + address 213.171.217.180 + } + } + rule 2246 { + action accept + description FW2F868_6-TCP-ALLOW-213.171.217.184 + destination { + group { + address-group DT_FW2F868_6 + } + port 22 + } + protocol tcp + source { + address 213.171.217.184 + } + } + rule 2247 { + action accept + description FW2F868_6-TCP-ALLOW-213.171.217.185 + destination { + group { + address-group DT_FW2F868_6 + } + port 22 + } + protocol tcp + source { + address 213.171.217.185 + } + } + rule 2248 { + action accept + description FW2F868_6-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW2F868_6 + } + port 161 + } + protocol udp + } + rule 2249 { + action accept + description FW2F868_6-TCP-ALLOW-213.171.217.102 + destination { + group { + address-group DT_FW2F868_6 + } + port 22,24 + } + protocol tcp + source { + address 213.171.217.102 + } + } + rule 2250 { + action accept + description FW9C682_3-TCP-ALLOW-80.194.78.162 + destination { + group { + address-group DT_FW9C682_3 + } + port 8443,22 + } + protocol tcp + source { + address 80.194.78.162 + } + } + rule 2251 { + action accept + description VPN-21822-ANY-ALLOW-10.4.54.47 + destination { + group { + address-group DT_VPN-21822 + } + } + source { + address 10.4.54.47 + } + } + rule 2252 { + action accept + description FW825C8_19-TCP-ALLOW-77.68.75.244 + destination { + group { + address-group DT_FW825C8_19 + } + port 1433 + } + protocol tcp + source { + address 77.68.75.244 + } + } + rule 2253 { + action accept + description FW2B279_4-TCP-ALLOW-195.147.173.92 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443,22 + } + protocol tcp + source { + address 195.147.173.92 + } + } + rule 2254 { + action accept + description FW1D511_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW1D511_2 + } + port 8090 + } + protocol tcp + } + rule 2255 { + action accept + description FW8A3FC_3-TCP-ALLOW-85.17.25.47 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 85.17.25.47 + } + } + rule 2256 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.89.209 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306,22 + } + protocol tcp + source { + address 77.68.89.209 + } + } + rule 2257 { + action accept + description FWE2AB5_8-TCP-ALLOW-213.171.217.184 + destination { + group { + address-group DT_FWE2AB5_8 + } + port 7000 + } + protocol tcp + source { + address 213.171.217.184 + } + } + rule 2258 { + action accept + description FW0E383_9-TCP-ALLOW-77.68.94.177 + destination { + group { + address-group DT_FW0E383_9 + } + port 1433 + } + protocol tcp + source { + address 77.68.94.177 + } + } + rule 2259 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.95.129 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306,22 + } + protocol tcp + source { + address 77.68.95.129 + } + } + rule 2260 { + action accept + description FW1F3D0_6-TCP-ALLOW-109.104.118.136 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 109.104.118.136 + } + } + rule 2261 { + action accept + description FW1FA9E_1-TCP-ALLOW-78.88.254.99 + destination { + group { + address-group DT_FW1FA9E_1 + } + port 9000,8200,5601,4444 + } + protocol tcp + source { + address 78.88.254.99 + } + } + rule 2262 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.46.27 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 175.157.46.27 + } + } + rule 2263 { + action accept + description FWA7A50_1-TCP-ALLOW-81.110.192.198 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp + source { + address 81.110.192.198 + } + } + rule 2264 { + action accept + description VPN-21822-ANY-ALLOW-10.4.55.47 + destination { + group { + address-group DT_VPN-21822 + } + } + source { + address 10.4.55.47 + } + } + rule 2265 { + action accept + description FW2BB8D_1-TCP-ALLOW-77.68.31.195 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 27017,5000 + } + protocol tcp + source { + address 77.68.31.195 + } + } + rule 2266 { + action accept + description FW45BEB_1-TCP-ALLOW-62.3.71.238 + destination { + group { + address-group DT_FW45BEB_1 + } + port 3389 + } + protocol tcp + source { + address 62.3.71.238 + } + } + rule 2267 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.113 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.113 + } + } + rule 2268 { + action accept + description VPN-23946-ANY-ALLOW-10.4.58.13 + destination { + group { + address-group DT_VPN-23946 + } + } + source { + address 10.4.58.13 + } + } + rule 2269 { + action accept + description FW98818_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW98818_1 + } + port 27015 + } + protocol tcp + } + rule 2270 { + action accept + description VPN-23946-ANY-ALLOW-10.4.59.13 + destination { + group { + address-group DT_VPN-23946 + } + } + source { + address 10.4.59.13 + } + } + rule 2271 { + action accept + description VPN-28031-ANY-ALLOW-10.4.88.197 + destination { + group { + address-group DT_VPN-28031 + } + } + source { + address 10.4.88.197 + } + } + rule 2272 { + action accept + description FW1F3D0_6-TCP-ALLOW-109.104.118.231 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 109.104.118.231 + } + } + rule 2273 { + action accept + description FW5A5D7_3-TCP_UDP-ALLOW-51.219.222.28 + destination { + group { + address-group DT_FW5A5D7_3 + } + port 500 + } + protocol tcp_udp + source { + address 51.219.222.28 + } + } + rule 2274 { + action accept + description FW32EFF_25-TCP-ALLOW-185.106.220.231 + destination { + group { + address-group DT_FW32EFF_25 + } + port 443 + } + protocol tcp + source { + address 185.106.220.231 + } + } + rule 2275 { + action accept + description FW1F3D0_6-TCP-ALLOW-109.104.118.66 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 109.104.118.66 + } + } + rule 2276 { + action accept + description FW934AE_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW934AE_1 + } + port 1194 + } + protocol udp + } + rule 2277 { + action accept + description VPN-28031-ANY-ALLOW-10.4.89.197 + destination { + group { + address-group DT_VPN-28031 + } + } + source { + address 10.4.89.197 + } + } + rule 2278 { + action accept + description FW6863A_4-TCP_UDP-ALLOW-82.165.166.41 + destination { + group { + address-group DT_FW6863A_4 + } + port 21-10000 + } + protocol tcp_udp + source { + address 82.165.166.41 + } + } + rule 2279 { + action accept + description FW1F3D0_6-TCP-ALLOW-109.104.119.162 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 109.104.119.162 + } + } + rule 2280 { + action accept + description FW1F3D0_6-TCP-ALLOW-109.74.199.143 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 109.74.199.143 + } + } + rule 2281 { + action accept + description FW1F3D0_6-TCP-ALLOW-185.92.25.48 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 185.92.25.48 + } + } + rule 2282 { + action accept + description FW1F3D0_6-TCP-ALLOW-207.148.2.40 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 207.148.2.40 + } + } + rule 2283 { + action accept + description FW1F3D0_6-TCP-ALLOW-45.76.235.62 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 45.76.235.62 + } + } + rule 2284 { + action accept + description FW1F3D0_6-TCP-ALLOW-45.76.236.93 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 45.76.236.93 + } + } + rule 2285 { + action accept + description FW1F3D0_6-TCP-ALLOW-45.76.59.5 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 45.76.59.5 + } + } + rule 2286 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.15.134 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4444,3306 + } + protocol tcp + source { + address 77.68.15.134 + } + } + rule 2287 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.22.208 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4444,3306 + } + protocol tcp + source { + address 77.68.22.208 + } + } + rule 2288 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.23.108 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 77.68.23.108 + } + } + rule 2289 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.23.54 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 77.68.23.54 + } + } + rule 2290 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.30.45 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 77.68.30.45 + } + } + rule 2291 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.7.198 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 77.68.7.198 + } + } + rule 2292 { + action accept + description VPN-29631-ANY-ALLOW-10.4.54.76 + destination { + group { + address-group DT_VPN-29631 + } + } + source { + address 10.4.54.76 + } + } + rule 2293 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.89.200 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4444,3306 + } + protocol tcp + source { + address 77.68.89.200 + } + } + rule 2294 { + action accept + description FW1F3D0_6-TCP-ALLOW-77.68.91.50 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 77.68.91.50 + } + } + rule 2295 { + action accept + description FW1F3D0_6-TCP-ALLOW-82.165.206.230 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 82.165.206.230 + } + } + rule 2296 { + action accept + description FW1F3D0_6-TCP-ALLOW-82.165.207.109 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4444,3306 + } + protocol tcp + source { + address 82.165.207.109 + } + } + rule 2297 { + action accept + description FW1F3D0_6-TCP-ALLOW-94.196.156.5 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 3306 + } + protocol tcp + source { + address 94.196.156.5 + } + } + rule 2298 { + action accept + description FW1F3D0_6-TCP_UDP-ALLOW-77.68.15.134 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4567-4568 + } + protocol tcp_udp + source { + address 77.68.15.134 + } + } + rule 2299 { + action accept + description FW1F3D0_6-TCP_UDP-ALLOW-77.68.22.208 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4567-4568 + } + protocol tcp_udp + source { + address 77.68.22.208 + } + } + rule 2300 { + action accept + description FW1F3D0_6-TCP_UDP-ALLOW-77.68.23.109 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4567-4568 + } + protocol tcp_udp + source { + address 77.68.23.109 + } + } + rule 2301 { + action accept + description FW1F3D0_6-TCP_UDP-ALLOW-77.68.89.200 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4567-4568 + } + protocol tcp_udp + source { + address 77.68.89.200 + } + } + rule 2302 { + action accept + description FW05339_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW05339_1 + } + port 8085,5055,5013,5005,444 + } + protocol tcp + } + rule 2303 { + action accept + description FW32EFF_25-TCP-ALLOW-217.169.61.164 + destination { + group { + address-group DT_FW32EFF_25 + } + port 443 + } + protocol tcp + source { + address 217.169.61.164 + } + } + rule 2304 { + action accept + description FW89619_1-TCP_UDP-ALLOW-185.83.65.45 + destination { + group { + address-group DT_FW89619_1 + } + port 5060 + } + protocol tcp_udp + source { + address 185.83.65.45 + } + } + rule 2305 { + action accept + description VPN-13983-ANY-ALLOW-10.4.58.176 + destination { + group { + address-group DT_VPN-13983 + } + } + source { + address 10.4.58.176 + } + } + rule 2306 { + action accept + description FWDAF47_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWDAF47_1 + } + port 8090,7080,443,53 + } + protocol tcp_udp + } + rule 2307 { + action accept + description VPN-29631-ANY-ALLOW-10.4.55.77 + destination { + group { + address-group DT_VPN-29631 + } + } + source { + address 10.4.55.77 + } + } + rule 2308 { + action accept + description VPN-34309-ANY-ALLOW-10.4.58.142 + destination { + group { + address-group DT_VPN-34309 + } + } + source { + address 10.4.58.142 + } + } + rule 2309 { + action accept + description FW27949_2-TCP-ALLOW-138.124.142.180 + destination { + group { + address-group DT_FW27949_2 + } + port 443,80 + } + protocol tcp + source { + address 138.124.142.180 + } + } + rule 2310 { + action accept + description FWF8F85_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWF8F85_1 + } + port 3306 + } + protocol tcp_udp + } + rule 2311 { + action accept + description FWDAF47_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWDAF47_1 + } + port 40110-40210 + } + protocol tcp + } + rule 2312 { + action accept + description VPN-34309-ANY-ALLOW-10.4.59.142 + destination { + group { + address-group DT_VPN-34309 + } + } + source { + address 10.4.59.142 + } + } + rule 2313 { + action accept + description FWA0531_1-TCP-ALLOW-87.224.39.220 + destination { + group { + address-group DT_FWA0531_1 + } + port 22 + } + protocol tcp + source { + address 87.224.39.220 + } + } + rule 2314 { + action accept + description FW5A5D7_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW5A5D7_3 + } + port 1334 + } + protocol tcp + } + rule 2315 { + action accept + description FW8C927_1-TCP_UDP-ALLOW-84.92.125.78 + destination { + group { + address-group DT_FW8C927_1 + } + port 3306,22 + } + protocol tcp_udp + source { + address 84.92.125.78 + } + } + rule 2316 { + action accept + description FW8C927_1-TCP_UDP-ALLOW-88.208.238.152 + destination { + group { + address-group DT_FW8C927_1 + } + port 3306,22 + } + protocol tcp_udp + source { + address 88.208.238.152 + } + } + rule 2317 { + action accept + description FW81138_1-ICMP-ALLOW-82.165.232.19 + destination { + group { + address-group DT_FW81138_1 + } + } + protocol icmp + source { + address 82.165.232.19 + } + } + rule 2318 { + action accept + description FW28892_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW28892_1 + } + port 7000 + } + protocol tcp + } + rule 2319 { + action accept + description FWC96A1_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWC96A1_1 + } + port 222 + } + protocol tcp + } + rule 2320 { + action accept + description VPN-13983-ANY-ALLOW-10.4.59.176 + destination { + group { + address-group DT_VPN-13983 + } + } + source { + address 10.4.59.176 + } + } + rule 2321 { + action accept + description FW2FB61_1-TCP-ALLOW-5.183.104.15 + destination { + group { + address-group DT_FW2FB61_1 + } + port 22 + } + protocol tcp + source { + address 5.183.104.15 + } + } + rule 2322 { + action accept + description FW81138_1-ICMP-ALLOW-82.20.69.137 + destination { + group { + address-group DT_FW81138_1 + } + } + protocol icmp + source { + address 82.20.69.137 + } + } + rule 2323 { + action accept + description FW72F37_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW72F37_1 + } + port 7770-7800,44445 + } + protocol tcp + } + rule 2324 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-81.111.155.34 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 81.111.155.34 + } + } + rule 2325 { + action accept + description VPN-20306-ANY-ALLOW-10.4.88.173 + destination { + group { + address-group DT_VPN-20306 + } + } + source { + address 10.4.88.173 + } + } + rule 2326 { + action accept + description FW6C992_1-TCP-ALLOW-89.33.185.0_24 + destination { + group { + address-group DT_FW6C992_1 + } + port 8447,8443,22 + } + protocol tcp + source { + address 89.33.185.0/24 + } + } + rule 2327 { + action accept + description FW2FB61_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2FB61_1 + } + port 45000 + } + protocol tcp + } + rule 2328 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.46.202 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 175.157.46.202 + } + } + rule 2329 { + action accept + description FWF9C28_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF9C28_2 + } + port 7770-7800,44445 + } + protocol tcp + } + rule 2330 { + action accept + description FW3DBF8_9-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW3DBF8_9 + } + port 8088,8080,5090,5060,3478,1935 + } + protocol tcp_udp + } + rule 2331 { + action accept + description FW3DBF8_9-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW3DBF8_9 + } + port 5062,5061,5015,5001 + } + protocol tcp + } + rule 2332 { + action accept + description VPN-16402-ANY-ALLOW-10.4.88.60 + destination { + group { + address-group DT_VPN-16402 + } + } + source { + address 10.4.88.60 + } + } + rule 2333 { + action accept + description FWC1315_1-TCP-ALLOW-62.3.71.238 + destination { + group { + address-group DT_FWC1315_1 + } + port 3389 + } + protocol tcp + source { + address 62.3.71.238 + } + } + rule 2334 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA7A50_1 + } + port 8001,80 + } + protocol tcp_udp + } + rule 2335 { + action accept + description FWAFF0A_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWAFF0A_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2336 { + action accept + description FW2B279_4-TCP-ALLOW-195.20.253.19 + destination { + group { + address-group DT_FW2B279_4 + } + port 22 + } + protocol tcp + source { + address 195.20.253.19 + } + } + rule 2337 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.73 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.215.73 + } + } + rule 2338 { + action accept + description VPN-16402-ANY-ALLOW-10.4.89.60 + destination { + group { + address-group DT_VPN-16402 + } + } + source { + address 10.4.89.60 + } + } + rule 2339 { + action accept + description VPN-15951-ANY-ALLOW-10.4.86.90 + destination { + group { + address-group DT_VPN-15951 + } + } + source { + address 10.4.86.90 + } + } + rule 2340 { + action accept + description FW2BB8D_1-TCP-ALLOW-77.68.77.181 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 27017,5000 + } + protocol tcp + source { + address 77.68.77.181 + } + } + rule 2341 { + action accept + description FWE9F7D_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE9F7D_1 + } + port 4035 + } + protocol tcp + } + rule 2342 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.208.131 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.208.131 + } + } + rule 2343 { + action accept + description VPN-15951-ANY-ALLOW-10.4.87.90 + destination { + group { + address-group DT_VPN-15951 + } + } + source { + address 10.4.87.90 + } + } + rule 2344 { + action accept + description FW2BB8D_1-TCP-ALLOW-77.68.93.190 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 27017,5000 + } + protocol tcp + source { + address 77.68.93.190 + } + } + rule 2345 { + action accept + description VPN-8159-ANY-ALLOW-10.4.59.91 + destination { + group { + address-group DT_VPN-8159 + } + } + source { + address 10.4.59.91 + } + } + rule 2346 { + action accept + description VPN-12870-ANY-ALLOW-10.4.54.67 + destination { + group { + address-group DT_VPN-12870 + } + } + source { + address 10.4.54.67 + } + } + rule 2347 { + action accept + description FW930F3_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW930F3_1 + } + port 53 + } + protocol tcp_udp + } + rule 2348 { + action accept + description FW12C32_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW12C32_1 + } + port 465,53,25 + } + protocol tcp_udp + } + rule 2349 { + action accept + description FW28EC8_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW28EC8_1 + } + port 20443 + } + protocol tcp + } + rule 2350 { + action accept + description VPN-12870-ANY-ALLOW-10.4.55.68 + destination { + group { + address-group DT_VPN-12870 + } + } + source { + address 10.4.55.68 + } + } + rule 2351 { + action accept + description FW934AE_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW934AE_1 + } + port 32401,32400,8081 + } + protocol tcp_udp + } + rule 2352 { + action accept + description FW6863A_4-TCP-ALLOW-185.173.161.154 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 185.173.161.154 + } + } + rule 2353 { + action accept + description FW013EF_2-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW013EF_2 + } + port 10600-10998,9000-9398,5090,5060-5070 + } + protocol udp + } + rule 2354 { + action accept + description FW85040_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW85040_1 + } + port 3210 + } + protocol tcp_udp + } + rule 2355 { + action accept + description FW8B21D_1-TCP_UDP-ALLOW-131.153.100.98 + destination { + group { + address-group DT_FW8B21D_1 + } + port 22 + } + protocol tcp_udp + source { + address 131.153.100.98 + } + } + rule 2356 { + action accept + description FW8B21D_1-TCP_UDP-ALLOW-213.133.99.176 + destination { + group { + address-group DT_FW8B21D_1 + } + port 22 + } + protocol tcp_udp + source { + address 213.133.99.176 + } + } + rule 2357 { + action accept + description FW6EFD7_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW6EFD7_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2358 { + action accept + description FW8B21D_1-TCP_UDP-ALLOW-62.253.153.163 + destination { + group { + address-group DT_FW8B21D_1 + } + port 8443,22 + } + protocol tcp_udp + source { + address 62.253.153.163 + } + } + rule 2359 { + action accept + description FWCB0CF_7-TCP-ALLOW-212.159.153.201 + destination { + group { + address-group DT_FWCB0CF_7 + } + port 6443,5432-5434,5000-5100,3306-3308,990,989,22,21 + } + protocol tcp + source { + address 212.159.153.201 + } + } + rule 2360 { + action accept + description FW75CA4_6-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW75CA4_6 + } + port 51472,3747,3420 + } + protocol tcp + } + rule 2361 { + action accept + description FWF9C28_4-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF9C28_4 + } + port 23,7770-7800,44445,6109 + } + protocol tcp + } + rule 2362 { + action accept + description FW6B39D_1-TCP-ALLOW-120.72.95.88_29 + destination { + group { + address-group DT_FW6B39D_1 + } + port 3306 + } + protocol tcp + source { + address 120.72.95.88/29 + } + } + rule 2363 { + action accept + description FW934AE_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW934AE_1 + } + port 20000 + } + protocol tcp + } + rule 2364 { + action accept + description FW12C32_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW12C32_1 + } + port 2323,953 + } + protocol tcp + } + rule 2365 { + action accept + description FW49897_1-TCP-ALLOW-2.121.90.207 + destination { + group { + address-group DT_FW49897_1 + } + port 22 + } + protocol tcp + source { + address 2.121.90.207 + } + } + rule 2366 { + action accept + description FW6B39D_1-TCP-ALLOW-120.72.91.104_29 + destination { + group { + address-group DT_FW6B39D_1 + } + port 3306 + } + protocol tcp + source { + address 120.72.91.104/29 + } + } + rule 2367 { + action accept + description FW4F5EE_10-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW4F5EE_10 + } + port 83,86,82 + } + protocol tcp + } + rule 2368 { + action accept + description FWF791C_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF791C_1 + } + port 6001 + } + protocol tcp + } + rule 2369 { + action accept + description FWEF92E_5-ESP-ALLOW-109.228.37.19 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol esp + source { + address 109.228.37.19 + } + } + rule 2370 { + action accept + description FWE57AD_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE57AD_1 + } + port 57000-58000 + } + protocol tcp + } + rule 2371 { + action accept + description FWC0CE0_1-TCP-ALLOW-62.232.209.221 + destination { + group { + address-group DT_FWC0CE0_1 + } + port 49152-65535,8447,8443,22,21 + } + protocol tcp + source { + address 62.232.209.221 + } + } + rule 2372 { + action accept + description FW0192C_1-TCP-ALLOW-41.140.242.86 + destination { + group { + address-group DT_FW0192C_1 + } + port 3306,22 + } + protocol tcp + source { + address 41.140.242.86 + } + } + rule 2373 { + action accept + description FWEEC75_1-TCP-ALLOW-54.171.71.110 + destination { + group { + address-group DT_FWEEC75_1 + } + port 21 + } + protocol tcp + source { + address 54.171.71.110 + } + } + rule 2374 { + action accept + description FW8B21D_1-TCP_UDP-ALLOW-95.149.182.69 + destination { + group { + address-group DT_FW8B21D_1 + } + port 22 + } + protocol tcp_udp + source { + address 95.149.182.69 + } + } + rule 2375 { + action accept + description FW8B21D_1-TCP-ALLOW-185.201.16.0_22 + destination { + group { + address-group DT_FW8B21D_1 + } + port 25 + } + protocol tcp + source { + address 185.201.16.0/22 + } + } + rule 2376 { + action accept + description FW8B21D_1-TCP-ALLOW-213.133.99.176 + destination { + group { + address-group DT_FW8B21D_1 + } + port 25 + } + protocol tcp + source { + address 213.133.99.176 + } + } + rule 2377 { + action accept + description FW8B21D_1-TCP-ALLOW-95.211.160.147 + destination { + group { + address-group DT_FW8B21D_1 + } + port 25 + } + protocol tcp + source { + address 95.211.160.147 + } + } + rule 2378 { + action accept + description FW6863A_4-TCP-ALLOW-212.227.9.72 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 212.227.9.72 + } + } + rule 2379 { + action accept + description FW8B21D_1-ESP-ALLOW-ANY + destination { + group { + address-group DT_FW8B21D_1 + } + } + protocol esp + } + rule 2380 { + action accept + description FW8B21D_1-AH-ALLOW-ANY + destination { + group { + address-group DT_FW8B21D_1 + } + } + protocol ah + } + rule 2381 { + action accept + description FW8B21D_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW8B21D_1 + } + port 8181,4500,1194,993,941,500,53 + } + protocol tcp_udp + } + rule 2382 { + action accept + description FW6863A_4-TCP-ALLOW-85.17.25.47 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 85.17.25.47 + } + } + rule 2383 { + action accept + description FW6863A_4-TCP-ALLOW-91.232.105.39 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 91.232.105.39 + } + } + rule 2384 { + action accept + description FW6863A_4-TCP-ALLOW-93.190.142.120 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 93.190.142.120 + } + } + rule 2385 { + action accept + description FW6863A_4-TCP-ALLOW-95.168.171.130 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 95.168.171.130 + } + } + rule 2386 { + action accept + description FW6863A_4-TCP-ALLOW-95.168.171.157 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 95.168.171.157 + } + } + rule 2387 { + action accept + description FWD4A27_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD4A27_1 + } + port 32400 + } + protocol tcp + } + rule 2388 { + action accept + description FW2ACFF_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW2ACFF_1 + } + port 10299,60050-60055 + } + protocol tcp_udp + } + rule 2389 { + action accept + description FWCB0CF_7-TCP-ALLOW-193.248.62.45 + destination { + group { + address-group DT_FWCB0CF_7 + } + port 22 + } + protocol tcp + source { + address 193.248.62.45 + } + } + rule 2390 { + action accept + description FWCB0CF_7-TCP-ALLOW-78.249.208.17 + destination { + group { + address-group DT_FWCB0CF_7 + } + port 22 + } + protocol tcp + source { + address 78.249.208.17 + } + } + rule 2391 { + action accept + description FWC8E8E_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWC8E8E_1 + } + port 6000 + } + protocol tcp_udp + } + rule 2392 { + action accept + description FW30D21_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW30D21_1 + } + port 2476 + } + protocol tcp + } + rule 2393 { + action accept + description FW0192C_1-TCP-ALLOW-41.140.242.94 + destination { + group { + address-group DT_FW0192C_1 + } + port 3306,22 + } + protocol tcp + source { + address 41.140.242.94 + } + } + rule 2394 { + action accept + description FW59F39_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW59F39_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2395 { + action accept + description FWEF92E_7-ESP-ALLOW-77.68.77.57 + destination { + group { + address-group DT_FWEF92E_7 + } + } + protocol esp + source { + address 77.68.77.57 + } + } + rule 2396 { + action accept + description FW826BA_3-TCP-ALLOW-51.219.47.177 + destination { + group { + address-group DT_FW826BA_3 + } + port 3389,21 + } + protocol tcp + source { + address 51.219.47.177 + } + } + rule 2397 { + action accept + description FW826BA_3-TCP-ALLOW-86.172.128.50 + destination { + group { + address-group DT_FW826BA_3 + } + port 1433,21 + } + protocol tcp + source { + address 86.172.128.50 + } + } + rule 2398 { + action accept + description FW826BA_3-TCP-ALLOW-88.105.1.20 + destination { + group { + address-group DT_FW826BA_3 + } + port 21 + } + protocol tcp + source { + address 88.105.1.20 + } + } + rule 2399 { + action accept + description FW6863A_4-TCP-ALLOW-95.211.243.198 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 95.211.243.198 + } + } + rule 2400 { + action accept + description FW25843_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW25843_1 + } + port 9001,7070,5500,5488,5000,4500,4000,3500,3000,1883,1880 + } + protocol tcp + } + rule 2401 { + action accept + description FW89619_1-TCP_UDP-ALLOW-185.83.65.46 + destination { + group { + address-group DT_FW89619_1 + } + port 5060 + } + protocol tcp_udp + source { + address 185.83.65.46 + } + } + rule 2402 { + action accept + description FW5858F_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW5858F_1 + } + port 1883 + } + protocol tcp + } + rule 2403 { + action accept + description FW826BA_3-TCP-ALLOW-95.147.108.173 + destination { + group { + address-group DT_FW826BA_3 + } + port 21 + } + protocol tcp + source { + address 95.147.108.173 + } + } + rule 2404 { + action accept + description FW9C682_3-TCP-ALLOW-52.56.193.88 + destination { + group { + address-group DT_FW9C682_3 + } + port 3306 + } + protocol tcp + source { + address 52.56.193.88 + } + } + rule 2405 { + action accept + description FW0745F_5-TCP-ALLOW-109.228.63.82 + destination { + group { + address-group DT_FW0745F_5 + } + port 5666 + } + protocol tcp + source { + address 109.228.63.82 + } + } + rule 2406 { + action accept + description FWC0CE0_1-TCP-ALLOW-90.255.228.213 + destination { + group { + address-group DT_FWC0CE0_1 + } + port 49152-65535,8443,21 + } + protocol tcp + source { + address 90.255.228.213 + } + } + rule 2407 { + action accept + description FW210E2_8-AH-ALLOW-ANY + destination { + group { + address-group DT_FW210E2_8 + } + } + protocol ah + } + rule 2408 { + action accept + description FW210E2_8-ESP-ALLOW-ANY + destination { + group { + address-group DT_FW210E2_8 + } + } + protocol esp + } + rule 2409 { + action accept + description FW210E2_8-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW210E2_8 + } + port 41,62000,23,4500,50,9876,3391,88,135 + } + protocol tcp + } + rule 2410 { + action accept + description FW210E2_8-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW210E2_8 + } + port 500 + } + protocol udp + } + rule 2411 { + action accept + description VPN-8625-ANY-ALLOW-10.4.54.103 + destination { + group { + address-group DT_VPN-8625 + } + } + source { + address 10.4.54.103 + } + } + rule 2412 { + action accept + description VPN-8625-ANY-ALLOW-10.4.55.104 + destination { + group { + address-group DT_VPN-8625 + } + } + source { + address 10.4.55.104 + } + } + rule 2413 { + action accept + description FW73A64_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW73A64_1 + } + port 61616,8181,8161,8082,4244,4243,4242,4241 + } + protocol tcp + } + rule 2414 { + action accept + description VPN-19135-ANY-ALLOW-10.4.86.165 + destination { + group { + address-group DT_VPN-19135 + } + } + source { + address 10.4.86.165 + } + } + rule 2415 { + action accept + description FWCB0CF_7-TCP-ALLOW-82.65.107.3 + destination { + group { + address-group DT_FWCB0CF_7 + } + port 22 + } + protocol tcp + source { + address 82.65.107.3 + } + } + rule 2416 { + action accept + description FWCB0CF_7-TCP-ALLOW-195.2.139.221 + destination { + group { + address-group DT_FWCB0CF_7 + } + port 5432-5434,3306-3308 + } + protocol tcp + source { + address 195.2.139.221 + } + } + rule 2417 { + action accept + description VPN-19135-ANY-ALLOW-10.4.87.165 + destination { + group { + address-group DT_VPN-19135 + } + } + source { + address 10.4.87.165 + } + } + rule 2418 { + action accept + description FW2BB8D_1-TCP-ALLOW-87.75.109.83 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 27017,5000 + } + protocol tcp + source { + address 87.75.109.83 + } + } + rule 2419 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.83 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.83 + } + } + rule 2420 { + action accept + description FW2ED4D_2-TCP-ALLOW-84.92.65.192 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 22 + } + protocol tcp + source { + address 84.92.65.192 + } + } + rule 2421 { + action accept + description FW73A64_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW73A64_1 + } + port 9200,5601,4247,4246,4245 + } + protocol tcp_udp + } + rule 2422 { + action accept + description FW4735F_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW4735F_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2423 { + action accept + description FW2ED4D_2-TCP-ALLOW-109.176.154.238 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 7990,3389 + } + protocol tcp + source { + address 109.176.154.238 + } + } + rule 2424 { + action accept + description FW6863A_4-TCP-ALLOW-95.211.243.206 + destination { + group { + address-group DT_FW6863A_4 + } + port 465 + } + protocol tcp + source { + address 95.211.243.206 + } + } + rule 2425 { + action accept + description FW89619_1-TCP_UDP-ALLOW-81.133.80.114 + destination { + group { + address-group DT_FW89619_1 + } + port 5060 + } + protocol tcp_udp + source { + address 81.133.80.114 + } + } + rule 2426 { + action accept + description FW89619_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW89619_1 + } + port 5090 + } + protocol tcp_udp + } + rule 2427 { + action accept + description FW8A57A_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW8A57A_1 + } + port 49155,49154,7700,53,43 + } + protocol tcp_udp + } + rule 2428 { + action accept + description FW8C72E_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW8C72E_1 + } + port 500,4500 + } + protocol udp + } + rule 2429 { + action accept + description FW2ED4D_2-TCP-ALLOW-18.135.66.162 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 18.135.66.162 + } + } + rule 2430 { + action accept + description FW2C5AE_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2C5AE_1 + } + port 58080,58008,8545,7175 + } + protocol tcp + } + rule 2431 { + action accept + description FW2ED4D_2-TCP-ALLOW-80.209.144.52 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 80.209.144.52 + } + } + rule 2432 { + action accept + description FW2ED4D_2-TCP-ALLOW-82.153.21.103 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 7990,3389 + } + protocol tcp + source { + address 82.153.21.103 + } + } + rule 2433 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.41 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.215.41 + } + } + rule 2434 { + action accept + description FW0745F_5-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0745F_5 + } + port 32770,8001,7801 + } + protocol tcp + } + rule 2435 { + action accept + description FW85E02_11-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW85E02_11 + } + port 5090,5060 + } + protocol tcp_udp + } + rule 2436 { + action accept + description VPN-21982-ANY-ALLOW-10.4.58.43 + destination { + group { + address-group DT_VPN-21982 + } + } + source { + address 10.4.58.43 + } + } + rule 2437 { + action accept + description FW2ED4D_2-TCP-ALLOW-82.17.52.191 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 82.17.52.191 + } + } + rule 2438 { + action accept + description FW66347_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW66347_1 + } + port 53 + } + protocol tcp_udp + } + rule 2439 { + action accept + description FW11082_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW11082_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2440 { + action accept + description VPN-21982-ANY-ALLOW-10.4.59.43 + destination { + group { + address-group DT_VPN-21982 + } + } + source { + address 10.4.59.43 + } + } + rule 2441 { + action accept + description FW2BB8D_1-TCP-ALLOW-92.207.193.203 + destination { + group { + address-group DT_FW2BB8D_1 + } + port 5000 + } + protocol tcp + source { + address 92.207.193.203 + } + } + rule 2442 { + action accept + description FWC2D30_1-TCP-ALLOW-77.99.253.161 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443,22,21 + } + protocol tcp + source { + address 77.99.253.161 + } + } + rule 2443 { + action accept + description FW0E383_9-TCP-ALLOW-77.99.245.103 + destination { + group { + address-group DT_FW0E383_9 + } + port 3389 + } + protocol tcp + source { + address 77.99.245.103 + } + } + rule 2444 { + action accept + description FW2ED4D_2-TCP-ALLOW-82.19.19.52 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 7990,3389 + } + protocol tcp + source { + address 82.19.19.52 + } + } + rule 2445 { + action accept + description FWEF92E_7-AH-ALLOW-77.68.77.57 + destination { + group { + address-group DT_FWEF92E_7 + } + } + protocol ah + source { + address 77.68.77.57 + } + } + rule 2446 { + action accept + description VPN-16450-ANY-ALLOW-10.4.88.99 + destination { + group { + address-group DT_VPN-16450 + } + } + source { + address 10.4.88.99 + } + } + rule 2447 { + action accept + description FW2ED4D_2-TCP-ALLOW-82.2.186.129 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 82.2.186.129 + } + } + rule 2448 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.157 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.215.157 + } + } + rule 2449 { + action accept + description FW8EA04_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW8EA04_1 + } + port 1194 + } + protocol udp + } + rule 2450 { + action accept + description FW2ED4D_2-TCP-ALLOW-82.21.59.207 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 82.21.59.207 + } + } + rule 2451 { + action accept + description FWC2D30_1-TCP-ALLOW-82.9.22.158 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443,21 + } + protocol tcp + source { + address 82.9.22.158 + } + } + rule 2452 { + action accept + description FWF3A1B_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWF3A1B_1 + } + port 1981,53 + } + protocol tcp_udp + } + rule 2453 { + action accept + description FWEF92E_5-ESP-ALLOW-77.68.11.54 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol esp + source { + address 77.68.11.54 + } + } + rule 2454 { + action accept + description FW2ED4D_2-TCP-ALLOW-82.40.177.186 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 82.40.177.186 + } + } + rule 2455 { + action accept + description FW0C25B_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0C25B_1 + } + port 49152-65535,5224 + } + protocol tcp + } + rule 2456 { + action accept + description FW85A7C_1-TCP-ALLOW-82.24.242.137 + destination { + group { + address-group DT_FW85A7C_1 + } + port 22 + } + protocol tcp + source { + address 82.24.242.137 + } + } + rule 2457 { + action accept + description FW2ED4D_2-TCP-ALLOW-82.68.25.66 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 82.68.25.66 + } + } + rule 2458 { + action accept + description FW826BA_3-TCP-ALLOW-51.89.148.173 + destination { + group { + address-group DT_FW826BA_3 + } + port 1433 + } + protocol tcp + source { + address 51.89.148.173 + } + } + rule 2459 { + action accept + description FWA69A0_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA69A0_1 + } + port 48402 + } + protocol udp + } + rule 2460 { + action accept + description FW2ED4D_2-TCP-ALLOW-82.69.79.85 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 82.69.79.85 + } + } + rule 2461 { + action accept + description FWEF92E_5-ESP-ALLOW-77.68.77.149 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol esp + source { + address 77.68.77.149 + } + } + rule 2462 { + action accept + description FWEF92E_6-ESP-ALLOW-77.68.77.57 + destination { + group { + address-group DT_FWEF92E_6 + } + } + protocol esp + source { + address 77.68.77.57 + } + } + rule 2463 { + action accept + description FWEF92E_7-TCP-ALLOW-77.68.8.74 + destination { + group { + address-group DT_FWEF92E_7 + } + port 3389,445 + } + protocol tcp + source { + address 77.68.8.74 + } + } + rule 2464 { + action accept + description FW49C3D_4-TCP-ALLOW-77.68.8.74 + destination { + group { + address-group DT_FW49C3D_4 + } + port 3389,445,443,80 + } + protocol tcp + source { + address 77.68.8.74 + } + } + rule 2465 { + action accept + description FW49C3D_6-TCP-ALLOW-77.68.8.74 + destination { + group { + address-group DT_FW49C3D_6 + } + port 3389,445 + } + protocol tcp + source { + address 77.68.8.74 + } + } + rule 2466 { + action accept + description FW34C91_3-TCP-ALLOW-77.68.121.4 + destination { + group { + address-group DT_FW34C91_3 + } + port 1433 + } + protocol tcp + source { + address 77.68.121.4 + } + } + rule 2467 { + action accept + description VPN-16450-ANY-ALLOW-10.4.89.99 + destination { + group { + address-group DT_VPN-16450 + } + } + source { + address 10.4.89.99 + } + } + rule 2468 { + action accept + description FW0BB22_1-AH-ALLOW-ANY + destination { + group { + address-group DT_FW0BB22_1 + } + } + protocol ah + } + rule 2469 { + action accept + description FW2ED4D_2-TCP-ALLOW-86.139.57.116 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 86.139.57.116 + } + } + rule 2470 { + action accept + description FW9E550_1-TCP-ALLOW-86.142.67.13 + destination { + group { + address-group DT_FW9E550_1 + } + port 3389 + } + protocol tcp + source { + address 86.142.67.13 + } + } + rule 2471 { + action accept + description FW8B21D_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW8B21D_1 + } + port 2096,2095,2087,2086,2083,2082 + } + protocol tcp + } + rule 2472 { + action accept + description FW050AC_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW050AC_1 + } + port 2087 + } + protocol tcp + } + rule 2473 { + action accept + description FW1FA9E_1-TCP-ALLOW-109.228.50.206 + destination { + group { + address-group DT_FW1FA9E_1 + } + port 5432 + } + protocol tcp + source { + address 109.228.50.206 + } + } + rule 2474 { + action accept + description FW8A3FC_3-TCP-ALLOW-217.23.11.155 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 217.23.11.155 + } + } + rule 2475 { + action accept + description FW2ED4D_2-TCP-ALLOW-88.96.110.198 + destination { + group { + address-group DT_FW2ED4D_2 + } + port 3389 + } + protocol tcp + source { + address 88.96.110.198 + } + } + rule 2476 { + action accept + description FWEAE53_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWEAE53_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2477 { + action accept + description VPN-19474-ANY-ALLOW-10.4.88.161 + destination { + group { + address-group DT_VPN-19474 + } + } + source { + address 10.4.88.161 + } + } + rule 2478 { + action accept + description VPN-19474-ANY-ALLOW-10.4.89.161 + destination { + group { + address-group DT_VPN-19474 + } + } + source { + address 10.4.89.161 + } + } + rule 2479 { + action accept + description FW90AE3_1-TCP-ALLOW-68.33.220.233 + destination { + group { + address-group DT_FW90AE3_1 + } + port 22 + } + protocol tcp + source { + address 68.33.220.233 + } + } + rule 2480 { + action accept + description FWC2D30_1-TCP-ALLOW-86.10.163.127 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443,21 + } + protocol tcp + source { + address 86.10.163.127 + } + } + rule 2481 { + action accept + description FW2FB61_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW2FB61_1 + } + port 60182 + } + protocol udp + } + rule 2482 { + action accept + description FW85A7C_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW85A7C_1 + } + port 2457,2456 + } + protocol tcp_udp + } + rule 2483 { + action accept + description FWBED52_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWBED52_1 + } + port 1221,9000 + } + protocol tcp + } + rule 2484 { + action accept + description FWA86ED_101-TCP-ALLOW-90.250.2.109 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 90.250.2.109 + } + } + rule 2485 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.49 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.213.49 + } + } + rule 2486 { + action accept + description FWEF92E_5-ESP-ALLOW-77.68.77.70 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol esp + source { + address 77.68.77.70 + } + } + rule 2487 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.250 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.250 + } + } + rule 2488 { + action accept + description FW8A3FC_3-TCP-ALLOW-95.168.171.131 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 95.168.171.131 + } + } + rule 2489 { + action accept + description FW2379F_14-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2379F_14 + } + port 48030,10997,10993,10992,10991,10902,1723,1701 + } + protocol tcp + } + rule 2490 { + action accept + description FW8C927_1-TCP-ALLOW-84.92.125.78 + destination { + group { + address-group DT_FW8C927_1 + } + port 80 + } + protocol tcp + source { + address 84.92.125.78 + } + } + rule 2491 { + action accept + description FWC2D30_1-TCP-ALLOW-86.146.220.229 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443,21 + } + protocol tcp + source { + address 86.146.220.229 + } + } + rule 2492 { + action accept + description FW2B279_4-TCP-ALLOW-2.218.5.59 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443,22 + } + protocol tcp + source { + address 2.218.5.59 + } + } + rule 2493 { + action accept + description VPN-18830-ANY-ALLOW-10.4.86.156 + destination { + group { + address-group DT_VPN-18830 + } + } + source { + address 10.4.86.156 + } + } + rule 2494 { + action accept + description VPN-18830-ANY-ALLOW-10.4.87.156 + destination { + group { + address-group DT_VPN-18830 + } + } + source { + address 10.4.87.156 + } + } + rule 2495 { + action accept + description FWEF92E_5-ESP-ALLOW-77.68.92.33 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol esp + source { + address 77.68.92.33 + } + } + rule 2496 { + action accept + description FWA86ED_101-TCP-ALLOW-146.198.100.105 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 146.198.100.105 + } + } + rule 2497 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.55 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.211.55 + } + } + rule 2498 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.84.113 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 123.231.84.113 + } + } + rule 2499 { + action accept + description FW8C72E_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW8C72E_1 + } + port 60134,60135 + } + protocol tcp + } + rule 2500 { + action accept + description FWAB44B_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWAB44B_1 + } + port 3306 + } + protocol tcp_udp + } + rule 2501 { + action accept + description FW2379F_14-TCP-ALLOW-51.148.87.29 + destination { + group { + address-group DT_FW2379F_14 + } + port 3389,21 + } + protocol tcp + source { + address 51.148.87.29 + } + } + rule 2502 { + action accept + description VPN-23738-ANY-ALLOW-10.4.56.13 + destination { + group { + address-group DT_VPN-23738 + } + } + source { + address 10.4.56.13 + } + } + rule 2503 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.100 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.210.100 + } + } + rule 2504 { + action accept + description FW996B4_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW996B4_2 + } + port 43595,30160 + } + protocol tcp + } + rule 2505 { + action accept + description FW8871B_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW8871B_1 + } + port 15672,8083,8082,8081,5672 + } + protocol tcp + } + rule 2506 { + action accept + description FWAB44B_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWAB44B_1 + } + port 9090,8069,5432 + } + protocol tcp + } + rule 2507 { + action accept + description FW6187E_1-ICMP-ALLOW-85.214.201.250 + destination { + group { + address-group DT_FW6187E_1 + } + } + protocol icmp + source { + address 85.214.201.250 + } + } + rule 2508 { + action accept + description FW8A3FC_3-TCP-ALLOW-217.23.11.126 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 217.23.11.126 + } + } + rule 2509 { + action accept + description FW78137_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW78137_1 + } + port 1-65535 + } + protocol tcp + } + rule 2510 { + action accept + description FW32EFF_25-TCP-ALLOW-46.252.65.10 + destination { + group { + address-group DT_FW32EFF_25 + } + port 443 + } + protocol tcp + source { + address 46.252.65.10 + } + } + rule 2511 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.50 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.214.50 + } + } + rule 2512 { + action accept + description FW6A684_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW6A684_1 + } + port 53 + } + protocol tcp_udp + } + rule 2513 { + action accept + description FWF48EB_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF48EB_1 + } + port 9204,9202,3395 + } + protocol tcp + } + rule 2514 { + action accept + description FW44217_2-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW44217_2 + } + port 443,80 + } + protocol tcp_udp + } + rule 2515 { + action accept + description FW6187E_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW6187E_1 + } + port 2282 + } + protocol tcp + } + rule 2516 { + action accept + description FW8AFF1_7-TCP-ALLOW-109.228.0.58 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433 + } + protocol tcp + source { + address 109.228.0.58 + } + } + rule 2517 { + action accept + description VPN-34501-ANY-ALLOW-10.4.86.235 + destination { + group { + address-group DT_VPN-34501 + } + } + source { + address 10.4.86.235 + } + } + rule 2518 { + action accept + description FW1271A_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW1271A_2 + } + port 5090,5061,5060,5015,5001 + } + protocol tcp + } + rule 2519 { + action accept + description FW1271A_2-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW1271A_2 + } + port 9000-10999,5090,5060 + } + protocol udp + } + rule 2520 { + action accept + description FW1226C_3-TCP-ALLOW-216.113.160.71 + destination { + group { + address-group DT_FW1226C_3 + } + port 80,22 + } + protocol tcp + source { + address 216.113.160.71 + } + } + rule 2521 { + action accept + description FW32EFF_16-TCP-ALLOW-84.19.45.82 + destination { + group { + address-group DT_FW32EFF_16 + } + port 33888 + } + protocol tcp + source { + address 84.19.45.82 + } + } + rule 2522 { + action accept + description FW03F2E_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW03F2E_1 + } + port 1194 + } + protocol udp + } + rule 2523 { + action accept + description FW03F2E_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW03F2E_1 + } + port 4432,4431,4430 + } + protocol tcp + } + rule 2524 { + action accept + description FW1226C_3-TCP-ALLOW-216.113.162.65 + destination { + group { + address-group DT_FW1226C_3 + } + port 80,22 + } + protocol tcp + source { + address 216.113.162.65 + } + } + rule 2525 { + action accept + description VPN-20306-ANY-ALLOW-10.4.89.173 + destination { + group { + address-group DT_VPN-20306 + } + } + source { + address 10.4.89.173 + } + } + rule 2526 { + action accept + description FW8A49A_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW8A49A_1 + } + port 2525,8448-65535 + } + protocol tcp + } + rule 2527 { + action accept + description FWD3431_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD3431_2 + } + port 43595,30377,30289 + } + protocol tcp + } + rule 2528 { + action accept + description FW1226C_3-TCP-ALLOW-66.135.200.200 + destination { + group { + address-group DT_FW1226C_3 + } + port 80,22 + } + protocol tcp + source { + address 66.135.200.200 + } + } + rule 2529 { + action accept + description FW1226C_3-TCP-ALLOW-193.28.178.38 + destination { + group { + address-group DT_FW1226C_3 + } + port 80 + } + protocol tcp + source { + address 193.28.178.38 + } + } + rule 2530 { + action accept + description FWAE88B_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWAE88B_1 + } + port 65432,8080,7300,1195,1194,993,587,465,443,442,143,110,80,53,22 + } + protocol tcp_udp + } + rule 2531 { + action accept + description FW1226C_3-TCP-ALLOW-195.234.136.80 + destination { + group { + address-group DT_FW1226C_3 + } + port 80 + } + protocol tcp + source { + address 195.234.136.80 + } + } + rule 2532 { + action accept + description FW1226C_3-TCP-ALLOW-93.94.41.83 + destination { + group { + address-group DT_FW1226C_3 + } + port 80 + } + protocol tcp + source { + address 93.94.41.83 + } + } + rule 2533 { + action accept + description VPN-6103-ANY-ALLOW-10.4.56.102 + destination { + group { + address-group DT_VPN-6103 + } + } + source { + address 10.4.56.102 + } + } + rule 2534 { + action accept + description VPN-6103-ANY-ALLOW-10.4.57.102 + destination { + group { + address-group DT_VPN-6103 + } + } + source { + address 10.4.57.102 + } + } + rule 2535 { + action accept + description FW9E550_1-TCP-ALLOW-86.198.190.104 + destination { + group { + address-group DT_FW9E550_1 + } + port 3389 + } + protocol tcp + source { + address 86.198.190.104 + } + } + rule 2536 { + action accept + description FW34C91_3-TCP-ALLOW-81.149.71.244 + destination { + group { + address-group DT_FW34C91_3 + } + port 1433 + } + protocol tcp + source { + address 81.149.71.244 + } + } + rule 2537 { + action accept + description FW0BB22_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW0BB22_1 + } + port 27917,27017,9592,9092,1080,587 + } + protocol tcp_udp + } + rule 2538 { + action accept + description FWC2D30_1-TCP-ALLOW-89.213.26.156 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443,21 + } + protocol tcp + source { + address 89.213.26.156 + } + } + rule 2539 { + action accept + description FW34C91_3-UDP-ALLOW-81.149.71.244 + destination { + group { + address-group DT_FW34C91_3 + } + port 1434 + } + protocol udp + source { + address 81.149.71.244 + } + } + rule 2540 { + action accept + description VPN-17207-ANY-ALLOW-10.4.86.121 + destination { + group { + address-group DT_VPN-17207 + } + } + source { + address 10.4.86.121 + } + } + rule 2541 { + action accept + description FW0B352_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW0B352_1 + } + port 4500,500 + } + protocol udp + } + rule 2542 { + action accept + description FW85E02_11-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW85E02_11 + } + port 5854,5853,5061 + } + protocol tcp + } + rule 2543 { + action accept + description FW0BB22_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0BB22_1 + } + port 9200,8082 + } + protocol tcp + } + rule 2544 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.140 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.213.140 + } + } + rule 2545 { + action accept + description FWC2D30_1-TCP-ALLOW-91.125.244.28 + destination { + group { + address-group DT_FWC2D30_1 + } + port 21 + } + protocol tcp + source { + address 91.125.244.28 + } + } + rule 2546 { + action accept + description FWA86ED_101-TCP-ALLOW-86.172.252.221 + destination { + group { + address-group DT_FWA86ED_101 + } + port 80-3389 + } + protocol tcp + source { + address 86.172.252.221 + } + } + rule 2547 { + action accept + description FWC2D30_1-TCP-ALLOW-92.207.184.106 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443,21 + } + protocol tcp + source { + address 92.207.184.106 + } + } + rule 2548 { + action accept + description FW45F3D_1-ANY-ALLOW-146.255.0.198 + destination { + group { + address-group DT_FW45F3D_1 + } + } + source { + address 146.255.0.198 + } + } + rule 2549 { + action accept + description FWBFDED_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWBFDED_1 + } + port 1723,445 + } + protocol tcp + } + rule 2550 { + action accept + description FW8A3FC_3-TCP-ALLOW-212.227.9.72 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 212.227.9.72 + } + } + rule 2551 { + action accept + description FWE928F_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE928F_1 + } + port 2082,2083,2086,2087,2096 + } + protocol tcp + } + rule 2552 { + action accept + description FW5CBB2_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW5CBB2_1 + } + port 2082,2083,2086,2087 + } + protocol tcp + } + rule 2553 { + action accept + description FW63230_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW63230_1 + } + port 445,139 + } + protocol tcp_udp + } + rule 2554 { + action accept + description FW90AE3_1-TCP-ALLOW-71.244.176.5 + destination { + group { + address-group DT_FW90AE3_1 + } + port 22 + } + protocol tcp + source { + address 71.244.176.5 + } + } + rule 2555 { + action accept + description FWA4BC8_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA4BC8_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2556 { + action accept + description VPN-17207-ANY-ALLOW-10.4.87.121 + destination { + group { + address-group DT_VPN-17207 + } + } + source { + address 10.4.87.121 + } + } + rule 2557 { + action accept + description VPN-17558-ANY-ALLOW-10.4.86.143 + destination { + group { + address-group DT_VPN-17558 + } + } + source { + address 10.4.86.143 + } + } + rule 2558 { + action accept + description FWB2CD2_1-TCP-ALLOW-86.167.68.241 + destination { + group { + address-group DT_FWB2CD2_1 + } + port 21 + } + protocol tcp + source { + address 86.167.68.241 + } + } + rule 2559 { + action accept + description FW32EFF_25-TCP-ALLOW-84.19.45.82 + destination { + group { + address-group DT_FW32EFF_25 + } + port 33888,443 + } + protocol tcp + source { + address 84.19.45.82 + } + } + rule 2560 { + action accept + description FW44217_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW44217_2 + } + port 9001,7946,2376 + } + protocol tcp + } + rule 2561 { + action accept + description FW7DAE2_3-TCP-ALLOW-212.227.253.11 + destination { + group { + address-group DT_FW7DAE2_3 + } + port 25,22 + } + protocol tcp + source { + address 212.227.253.11 + } + } + rule 2562 { + action accept + description FW7DAE2_3-TCP-ALLOW-217.160.126.118 + destination { + group { + address-group DT_FW7DAE2_3 + } + port 25,22 + } + protocol tcp + source { + address 217.160.126.118 + } + } + rule 2563 { + action accept + description FWAF6E8_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWAF6E8_1 + } + port 2082,2083,2086,2087,2096 + } + protocol tcp + } + rule 2564 { + action accept + description FWCD7CE_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWCD7CE_1 + } + port 49152-65534 + } + protocol tcp + } + rule 2565 { + action accept + description FW32EFF_16-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW32EFF_16 + } + port 47779,47778,47777,47776 + } + protocol tcp + } + rule 2566 { + action accept + description FW0745F_5-TCP-ALLOW-77.68.117.222 + destination { + group { + address-group DT_FW0745F_5 + } + port 49170 + } + protocol tcp + source { + address 77.68.117.222 + } + } + rule 2567 { + action accept + description FWC2D30_1-TCP-ALLOW-92.207.199.107 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443,22,21 + } + protocol tcp + source { + address 92.207.199.107 + } + } + rule 2568 { + action accept + description FW8AFF1_7-TCP-ALLOW-109.228.0.89 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433 + } + protocol tcp + source { + address 109.228.0.89 + } + } + rule 2569 { + action accept + description FW8A3FC_3-TCP-ALLOW-190.2.130.41 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 190.2.130.41 + } + } + rule 2570 { + action accept + description FWFDCC7_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWFDCC7_1 + } + port 10000 + } + protocol tcp_udp + } + rule 2571 { + action accept + description FWF19FB_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF19FB_2 + } + port 43595,40001,30616-30631,30531,30204-30435 + } + protocol tcp + } + rule 2572 { + action accept + description FW2B279_4-TCP-ALLOW-213.171.217.107 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443,22 + } + protocol tcp + source { + address 213.171.217.107 + } + } + rule 2573 { + action accept + description FW4E314_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW4E314_1 + } + port 21543,888 + } + protocol tcp + } + rule 2574 { + action accept + description FW73215_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW73215_1 + } + port 4380 + } + protocol udp + } + rule 2575 { + action accept + description VPN-31301-ANY-ALLOW-10.4.86.223 + destination { + group { + address-group DT_VPN-31301 + } + } + source { + address 10.4.86.223 + } + } + rule 2576 { + action accept + description FW8428B_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW8428B_1 + } + port 48402 + } + protocol udp + } + rule 2577 { + action accept + description FWF3A1B_1-TCP_UDP-ALLOW-185.195.124.169 + destination { + group { + address-group DT_FWF3A1B_1 + } + port 2222 + } + protocol tcp_udp + source { + address 185.195.124.169 + } + } + rule 2578 { + action accept + description FW34C91_3-UDP-ALLOW-77.68.121.4 + destination { + group { + address-group DT_FW34C91_3 + } + port 1434 + } + protocol udp + source { + address 77.68.121.4 + } + } + rule 2579 { + action accept + description FW73215_1-TCP-ALLOW-82.38.58.135 + destination { + group { + address-group DT_FW73215_1 + } + port 10685 + } + protocol tcp + source { + address 82.38.58.135 + } + } + rule 2580 { + action accept + description FW52F6F_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW52F6F_1 + } + port 8888 + } + protocol tcp + } + rule 2581 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.86 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.213.86 + } + } + rule 2582 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.125.13 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 123.231.125.13 + } + } + rule 2583 { + action accept + description FWEE03C_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWEE03C_1 + } + port 2087,2083 + } + protocol tcp + } + rule 2584 { + action accept + description FW748B7_1-TCP-ALLOW-157.231.123.154 + destination { + group { + address-group DT_FW748B7_1 + } + port 22 + } + protocol tcp + source { + address 157.231.123.154 + } + } + rule 2585 { + action accept + description VPN-34501-ANY-ALLOW-10.4.87.235 + destination { + group { + address-group DT_VPN-34501 + } + } + source { + address 10.4.87.235 + } + } + rule 2586 { + action accept + description FWE47DA_1-TCP-ALLOW-81.134.85.245 + destination { + group { + address-group DT_FWE47DA_1 + } + port 22 + } + protocol tcp + source { + address 81.134.85.245 + } + } + rule 2587 { + action accept + description FWD61BF_1-ANY-ALLOW-193.237.81.213_32 + destination { + group { + address-group DT_FWD61BF_1 + } + } + source { + address 193.237.81.213/32 + } + } + rule 2588 { + action accept + description FW2B279_4-TCP-ALLOW-23.106.238.241 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443,3306,22 + } + protocol tcp + source { + address 23.106.238.241 + } + } + rule 2589 { + action accept + description FW2B279_4-TCP-ALLOW-35.204.202.196 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443,3306,22 + } + protocol tcp + source { + address 35.204.202.196 + } + } + rule 2590 { + action accept + description FW2B279_4-TCP-ALLOW-35.242.141.128 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443,3306,22 + } + protocol tcp + source { + address 35.242.141.128 + } + } + rule 2591 { + action accept + description FWC2EF2_2-TCP-ALLOW-90.251.221.19 + destination { + group { + address-group DT_FWC2EF2_2 + } + port 995,993,587,465,143,110,25,22 + } + protocol tcp + source { + address 90.251.221.19 + } + } + rule 2592 { + action accept + description VPN-14673-ANY-ALLOW-10.4.88.44 + destination { + group { + address-group DT_VPN-14673 + } + } + source { + address 10.4.88.44 + } + } + rule 2593 { + action accept + description FWA83DF_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA83DF_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2594 { + action accept + description FW31525_6-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW31525_6 + } + port 35467 + } + protocol tcp + } + rule 2595 { + action accept + description FW4293B_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW4293B_1 + } + port 9080,8888,8881,7815,8419 + } + protocol tcp + } + rule 2596 { + action accept + description FW4AE7D_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW4AE7D_1 + } + port 8083,81 + } + protocol tcp + } + rule 2597 { + action accept + description FWC2D30_1-TCP-ALLOW-143.52.53.22 + destination { + group { + address-group DT_FWC2D30_1 + } + port 22 + } + protocol tcp + source { + address 143.52.53.22 + } + } + rule 2598 { + action accept + description FW44217_2-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW44217_2 + } + port 7946,4789 + } + protocol udp + } + rule 2599 { + action accept + description FW2B279_4-TCP-ALLOW-46.249.82.162 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443,22 + } + protocol tcp + source { + address 46.249.82.162 + } + } + rule 2600 { + action accept + description FW27949_2-TCP-ALLOW-80.95.202.106 + destination { + group { + address-group DT_FW27949_2 + } + port 443,80 + } + protocol tcp + source { + address 80.95.202.106 + } + } + rule 2601 { + action accept + description FWEF92E_5-ESP-ALLOW-77.68.93.82 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol esp + source { + address 77.68.93.82 + } + } + rule 2602 { + action accept + description FW2ACFF_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2ACFF_1 + } + port 8082,5093 + } + protocol tcp + } + rule 2603 { + action accept + description FWC2EF2_2-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWC2EF2_2 + } + port 10000,953,53 + } + protocol tcp_udp + } + rule 2604 { + action accept + description FW0C8E1_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0C8E1_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2605 { + action accept + description FWA86ED_101-TCP_UDP-ALLOW-82.5.189.5 + destination { + group { + address-group DT_FWA86ED_101 + } + port 1-65535 + } + protocol tcp_udp + source { + address 82.5.189.5 + } + } + rule 2606 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.208.179 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.208.179 + } + } + rule 2607 { + action accept + description FWEF92E_5-ESP-ALLOW-88.208.198.93 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol esp + source { + address 88.208.198.93 + } + } + rule 2608 { + action accept + description FW5658C_1-TCP-ALLOW-39.45.43.109 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443 + } + protocol tcp + source { + address 39.45.43.109 + } + } + rule 2609 { + action accept + description FW5658C_1-TCP-ALLOW-5.67.3.195 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443 + } + protocol tcp + source { + address 5.67.3.195 + } + } + rule 2610 { + action accept + description FWDCA36_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWDCA36_3 + } + port 49152-65534,5901 + } + protocol tcp + } + rule 2611 { + action accept + description FWE928F_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWE928F_1 + } + port 53 + } + protocol tcp_udp + } + rule 2612 { + action accept + description FW69D6D_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW69D6D_2 + } + port 5001,5090,5060,5015 + } + protocol tcp + } + rule 2613 { + action accept + description FW69D6D_2-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW69D6D_2 + } + port 5090,5060,9000-9500 + } + protocol udp + } + rule 2614 { + action accept + description VPN-9765-ANY-ALLOW-10.4.56.45 + destination { + group { + address-group DT_VPN-9765 + } + } + source { + address 10.4.56.45 + } + } + rule 2615 { + action accept + description VPN-9765-ANY-ALLOW-10.4.57.45 + destination { + group { + address-group DT_VPN-9765 + } + } + source { + address 10.4.57.45 + } + } + rule 2616 { + action accept + description FW4C136_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW4C136_1 + } + port 1194 + } + protocol tcp_udp + } + rule 2617 { + action accept + description FW6F539_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW6F539_1 + } + port 49152-65534 + } + protocol tcp + } + rule 2618 { + action accept + description FWDD089_5-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWDD089_5 + } + port 5666-5667,12489 + } + protocol tcp_udp + } + rule 2619 { + action accept + description FWDD089_5-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWDD089_5 + } + port 161-162 + } + protocol tcp + } + rule 2620 { + action accept + description FWEF92E_5-AH-ALLOW-109.228.37.19 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol ah + source { + address 109.228.37.19 + } + } + rule 2621 { + action accept + description FW0A5C4_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0A5C4_1 + } + port 9000,6697,6667,5000 + } + protocol tcp + } + rule 2622 { + action accept + description FWEF92E_5-AH-ALLOW-77.68.11.54 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol ah + source { + address 77.68.11.54 + } + } + rule 2623 { + action accept + description FW2BB8D_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2BB8D_1 + } + port 7990 + } + protocol tcp + } + rule 2624 { + action accept + description FWAF6E8_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWAF6E8_1 + } + port 7770-7800,44445,53 + } + protocol tcp_udp + } + rule 2625 { + action accept + description FW81286_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW81286_1 + } + port 2082,2083,2086,2087,2096 + } + protocol tcp + } + rule 2626 { + action accept + description FW05064_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW05064_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2627 { + action accept + description FWD7382_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWD7382_1 + } + port 4500,1701,500 + } + protocol udp + } + rule 2628 { + action accept + description FWD7382_1-TCP-ALLOW-174.91.7.198 + destination { + group { + address-group DT_FWD7382_1 + } + port 3389 + } + protocol tcp + source { + address 174.91.7.198 + } + } + rule 2629 { + action accept + description VPN-9484-ANY-ALLOW-10.4.56.164 + destination { + group { + address-group DT_VPN-9484 + } + } + source { + address 10.4.56.164 + } + } + rule 2630 { + action accept + description VPN-9484-ANY-ALLOW-10.4.57.164 + destination { + group { + address-group DT_VPN-9484 + } + } + source { + address 10.4.57.164 + } + } + rule 2631 { + action accept + description VPN-9749-ANY-ALLOW-10.4.58.144 + destination { + group { + address-group DT_VPN-9749 + } + } + source { + address 10.4.58.144 + } + } + rule 2632 { + action accept + description FWEF92E_5-AH-ALLOW-77.68.77.149 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol ah + source { + address 77.68.77.149 + } + } + rule 2633 { + action accept + description FW10FEE_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW10FEE_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2634 { + action accept + description FW5658C_1-TCP-ALLOW-5.71.30.141 + destination { + group { + address-group DT_FW5658C_1 + } + port 8443 + } + protocol tcp + source { + address 5.71.30.141 + } + } + rule 2635 { + action accept + description VPN-9749-ANY-ALLOW-10.4.59.144 + destination { + group { + address-group DT_VPN-9749 + } + } + source { + address 10.4.59.144 + } + } + rule 2636 { + action accept + description FWEF92E_5-AH-ALLOW-77.68.77.70 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol ah + source { + address 77.68.77.70 + } + } + rule 2637 { + action accept + description FWEF92E_5-AH-ALLOW-77.68.92.33 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol ah + source { + address 77.68.92.33 + } + } + rule 2638 { + action accept + description FWEF92E_5-AH-ALLOW-77.68.93.82 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol ah + source { + address 77.68.93.82 + } + } + rule 2639 { + action accept + description FWEF92E_6-AH-ALLOW-77.68.77.57 + destination { + group { + address-group DT_FWEF92E_6 + } + } + protocol ah + source { + address 77.68.77.57 + } + } + rule 2640 { + action accept + description FWEF92E_6-TCP-ALLOW-77.68.8.74 + destination { + group { + address-group DT_FWEF92E_6 + } + port 3389,445 + } + protocol tcp + source { + address 77.68.8.74 + } + } + rule 2641 { + action accept + description FWEF92E_5-AH-ALLOW-88.208.198.93 + destination { + group { + address-group DT_FWEF92E_5 + } + } + protocol ah + source { + address 88.208.198.93 + } + } + rule 2642 { + action accept + description FWEF92E_7-TCP-ALLOW-87.224.33.215 + destination { + group { + address-group DT_FWEF92E_7 + } + port 3389,445 + } + protocol tcp + source { + address 87.224.33.215 + } + } + rule 2643 { + action accept + description FWEF92E_7-TCP-ALLOW-87.224.6.174 + destination { + group { + address-group DT_FWEF92E_7 + } + port 3389,445 + } + protocol tcp + source { + address 87.224.6.174 + } + } + rule 2644 { + action accept + description FWEF92E_5-TCP-ALLOW-109.228.37.19 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 109.228.37.19 + } + } + rule 2645 { + action accept + description FW49C3D_4-TCP-ALLOW-87.224.33.215 + destination { + group { + address-group DT_FW49C3D_4 + } + port 3389,445,80 + } + protocol tcp + source { + address 87.224.33.215 + } + } + rule 2646 { + action accept + description FW49C3D_4-TCP-ALLOW-82.0.198.226 + destination { + group { + address-group DT_FW49C3D_4 + } + port 3389,445 + } + protocol tcp + source { + address 82.0.198.226 + } + } + rule 2647 { + action accept + description FW49C3D_6-TCP-ALLOW-82.0.198.226 + destination { + group { + address-group DT_FW49C3D_6 + } + port 3389,445 + } + protocol tcp + source { + address 82.0.198.226 + } + } + rule 2648 { + action accept + description FW49C3D_6-TCP-ALLOW-83.100.136.74 + destination { + group { + address-group DT_FW49C3D_6 + } + port 3389,445 + } + protocol tcp + source { + address 83.100.136.74 + } + } + rule 2649 { + action accept + description FWEF92E_6-TCP-ALLOW-87.224.33.215 + destination { + group { + address-group DT_FWEF92E_6 + } + port 3389,445 + } + protocol tcp + source { + address 87.224.33.215 + } + } + rule 2650 { + action accept + description FWEF92E_5-TCP-ALLOW-194.145.189.162 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 194.145.189.162 + } + } + rule 2651 { + action accept + description FW3DBF8_9-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW3DBF8_9 + } + port 9000-10999 + } + protocol udp + } + rule 2652 { + action accept + description VPN-19807-ANY-ALLOW-10.4.86.172 + destination { + group { + address-group DT_VPN-19807 + } + } + source { + address 10.4.86.172 + } + } + rule 2653 { + action accept + description FWEEC75_1-TCP-ALLOW-82.8.245.40 + destination { + group { + address-group DT_FWEEC75_1 + } + port 21 + } + protocol tcp + source { + address 82.8.245.40 + } + } + rule 2654 { + action accept + description FW3AD6F_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW3AD6F_1 + } + port 53,465 + } + protocol tcp_udp + } + rule 2655 { + action accept + description FWCDBC7_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWCDBC7_1 + } + port 53 + } + protocol tcp_udp + } + rule 2656 { + action accept + description FWA373F_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA373F_1 + } + port 2087,2086,2083,2082 + } + protocol tcp + } + rule 2657 { + action accept + description FW2B279_4-TCP-ALLOW-94.155.221.50 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443,22 + } + protocol tcp + source { + address 94.155.221.50 + } + } + rule 2658 { + action accept + description FWC2D30_1-TCP-ALLOW-213.171.217.107 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443,22 + } + protocol tcp + source { + address 213.171.217.107 + } + } + rule 2659 { + action accept + description VPN-30791-ANY-ALLOW-10.4.88.215 + destination { + group { + address-group DT_VPN-30791 + } + } + source { + address 10.4.88.215 + } + } + rule 2660 { + action accept + description VPN-30791-ANY-ALLOW-10.4.89.215 + destination { + group { + address-group DT_VPN-30791 + } + } + source { + address 10.4.89.215 + } + } + rule 2661 { + action accept + description FW2EF2C_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW2EF2C_1 + } + port 10000,3478 + } + protocol udp + } + rule 2662 { + action accept + description FW32EFF_49-TCP-ALLOW-195.217.232.0_26 + destination { + group { + address-group DT_FW32EFF_49 + } + port 5589 + } + protocol tcp + source { + address 195.217.232.0/26 + } + } + rule 2663 { + action accept + description FW4AE7D_1-TCP-ALLOW-81.136.8.24 + destination { + group { + address-group DT_FW4AE7D_1 + } + port 3389 + } + protocol tcp + source { + address 81.136.8.24 + } + } + rule 2664 { + action accept + description FW2EF2C_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW2EF2C_1 + } + port 5222 + } + protocol tcp_udp + } + rule 2665 { + action accept + description FW48A55_2-TCP-ALLOW-86.29.225.60 + destination { + group { + address-group DT_FW48A55_2 + } + port 443,80,22 + } + protocol tcp + source { + address 86.29.225.60 + } + } + rule 2666 { + action accept + description FW48A55_2-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW48A55_2 + } + port 1337 + } + protocol udp + } + rule 2667 { + action accept + description VPN-11913-ANY-ALLOW-10.4.56.191 + destination { + group { + address-group DT_VPN-11913 + } + } + source { + address 10.4.56.191 + } + } + rule 2668 { + action accept + description FWEF92E_5-TCP-ALLOW-194.145.189.163 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 194.145.189.163 + } + } + rule 2669 { + action accept + description FW8AFF1_7-TCP-ALLOW-109.228.0.90 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433 + } + protocol tcp + source { + address 109.228.0.90 + } + } + rule 2670 { + action accept + description FW8AFF1_7-TCP-ALLOW-109.228.24.66 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433 + } + protocol tcp + source { + address 109.228.24.66 + } + } + rule 2671 { + action accept + description VPN-11913-ANY-ALLOW-10.4.57.191 + destination { + group { + address-group DT_VPN-11913 + } + } + source { + address 10.4.57.191 + } + } + rule 2672 { + action accept + description FW73573_2-TCP-ALLOW-86.9.185.195 + destination { + group { + address-group DT_FW73573_2 + } + port 22 + } + protocol tcp + source { + address 86.9.185.195 + } + } + rule 2673 { + action accept + description VPN-17558-ANY-ALLOW-10.4.87.143 + destination { + group { + address-group DT_VPN-17558 + } + } + source { + address 10.4.87.143 + } + } + rule 2674 { + action accept + description FW748B7_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW748B7_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2675 { + action accept + description FW16375_5-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW16375_5 + } + port 2082,2083,2086,2087 + } + protocol tcp + } + rule 2676 { + action accept + description FW5A77C_16-TCP-ALLOW-88.98.204.68 + destination { + group { + address-group DT_FW5A77C_16 + } + port 22 + } + protocol tcp + source { + address 88.98.204.68 + } + } + rule 2677 { + action accept + description FW73573_1-TCP-ALLOW-86.9.185.195 + destination { + group { + address-group DT_FW73573_1 + } + port 22 + } + protocol tcp + source { + address 86.9.185.195 + } + } + rule 2678 { + action accept + description FWEF92E_5-TCP-ALLOW-194.145.190.4 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 194.145.190.4 + } + } + rule 2679 { + action accept + description FWC2D30_1-TCP-ALLOW-140.82.112.0_20 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443 + } + protocol tcp + source { + address 140.82.112.0/20 + } + } + rule 2680 { + action accept + description FW62858_12-ICMP-ALLOW-77.68.122.41 + destination { + group { + address-group DT_FW62858_12 + } + } + protocol icmp + source { + address 77.68.122.41 + } + } + rule 2681 { + action accept + description FWB118A_1-TCP-ALLOW-147.148.96.136 + destination { + group { + address-group DT_FWB118A_1 + } + port 49152-65534,8447,8443,22,21,20 + } + protocol tcp + source { + address 147.148.96.136 + } + } + rule 2682 { + action accept + description FW5A77C_16-TCP-ALLOW-92.207.237.42 + destination { + group { + address-group DT_FW5A77C_16 + } + port 10000,22 + } + protocol tcp + source { + address 92.207.237.42 + } + } + rule 2683 { + action accept + description FW364CF_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW364CF_1 + } + port 4022,8099 + } + protocol tcp + } + rule 2684 { + action accept + description VPN-25822-ANY-ALLOW-10.4.54.42 + destination { + group { + address-group DT_VPN-25822 + } + } + source { + address 10.4.54.42 + } + } + rule 2685 { + action accept + description FW7F28A_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW7F28A_1 + } + port 10051,10050 + } + protocol tcp + } + rule 2686 { + action accept + description FW8AFF1_7-TCP-ALLOW-109.228.53.159 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433 + } + protocol tcp + source { + address 109.228.53.159 + } + } + rule 2687 { + action accept + description FWE47DA_1-TCP-ALLOW-185.22.211.0_24 + destination { + group { + address-group DT_FWE47DA_1 + } + port 22 + } + protocol tcp + source { + address 185.22.211.0/24 + } + } + rule 2688 { + action accept + description FWC6301_1-TCP-ALLOW-95.34.208.4 + destination { + group { + address-group DT_FWC6301_1 + } + port 22 + } + protocol tcp + source { + address 95.34.208.4 + } + } + rule 2689 { + action accept + description FW45000_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW45000_1 + } + port 990 + } + protocol tcp + } + rule 2690 { + action accept + description FW481D7_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW481D7_1 + } + port 6789 + } + protocol tcp + } + rule 2691 { + action accept + description VPN-8203-ANY-ALLOW-10.4.59.109 + destination { + group { + address-group DT_VPN-8203 + } + } + source { + address 10.4.59.109 + } + } + rule 2692 { + action accept + description VPN-3575-ANY-ALLOW-10.4.54.124 + destination { + group { + address-group DT_VPN-3575 + } + } + source { + address 10.4.54.124 + } + } + rule 2693 { + action accept + description VPN-3575-ANY-ALLOW-10.4.55.125 + destination { + group { + address-group DT_VPN-3575 + } + } + source { + address 10.4.55.125 + } + } + rule 2694 { + action accept + description FW42661_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW42661_3 + } + port 44445,25672,15672,9876,7770-7800 + } + protocol tcp + } + rule 2695 { + action accept + description FWBF494_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWBF494_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2696 { + action accept + description FWD0E22_4-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD0E22_4 + } + port 8000,19005 + } + protocol tcp + } + rule 2697 { + action accept + description FW98818_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW98818_1 + } + port 27015 + } + protocol udp + } + rule 2698 { + action accept + description FW62858_12-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW62858_12 + } + port 5001,5000 + } + protocol tcp + } + rule 2699 { + action accept + description VPN-34006-ANY-ALLOW-10.4.86.242 + destination { + group { + address-group DT_VPN-34006 + } + } + source { + address 10.4.86.242 + } + } + rule 2700 { + action accept + description VPN-34006-ANY-ALLOW-10.4.87.242 + destination { + group { + address-group DT_VPN-34006 + } + } + source { + address 10.4.87.242 + } + } + rule 2701 { + action accept + description FWF879C_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF879C_1 + } + port 8888 + } + protocol tcp + } + rule 2702 { + action accept + description FWEF92E_5-TCP-ALLOW-77.68.11.54 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 77.68.11.54 + } + } + rule 2703 { + action accept + description FWEF92E_5-TCP-ALLOW-77.68.74.89 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 77.68.74.89 + } + } + rule 2704 { + action accept + description FWEF92E_5-TCP-ALLOW-77.68.77.149 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 77.68.77.149 + } + } + rule 2705 { + action accept + description FW8A57A_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW8A57A_1 + } + port 49153,5666 + } + protocol tcp + } + rule 2706 { + action accept + description FW62858_12-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW62858_12 + } + port 5090,5061,5060 + } + protocol tcp_udp + } + rule 2707 { + action accept + description FW62858_12-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW62858_12 + } + port 9000-10999 + } + protocol udp + } + rule 2708 { + action accept + description FW0E2EE_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW0E2EE_1 + } + port 1024-65535 + } + protocol tcp_udp + } + rule 2709 { + action accept + description FWEEC75_1-TCP-ALLOW-82.5.80.210 + destination { + group { + address-group DT_FWEEC75_1 + } + port 22 + } + protocol tcp + source { + address 82.5.80.210 + } + } + rule 2710 { + action accept + description FW4F81F_4-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW4F81F_4 + } + port 26900,27005,27015,51000,51005,51030 + } + protocol tcp_udp + } + rule 2711 { + action accept + description VPN-7902-ANY-ALLOW-10.4.56.78 + destination { + group { + address-group DT_VPN-7902 + } + } + source { + address 10.4.56.78 + } + } + rule 2712 { + action accept + description VPN-7902-ANY-ALLOW-10.4.57.78 + destination { + group { + address-group DT_VPN-7902 + } + } + source { + address 10.4.57.78 + } + } + rule 2713 { + action accept + description FWB36A0_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWB36A0_1 + } + port 20-21,990 + } + protocol tcp_udp + } + rule 2714 { + action accept + description FWD2082_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD2082_1 + } + port 8001,8002 + } + protocol tcp + } + rule 2715 { + action accept + description FW8A3FC_3-TCP-ALLOW-212.8.242.171 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 212.8.242.171 + } + } + rule 2716 { + action accept + description FWB9699_11-TCP-ALLOW-213.171.217.184 + destination { + group { + address-group DT_FWB9699_11 + } + port 443,80,8800,22 + } + protocol tcp + source { + address 213.171.217.184 + } + } + rule 2717 { + action accept + description VPN-11083-ANY-ALLOW-10.4.54.186 + destination { + group { + address-group DT_VPN-11083 + } + } + source { + address 10.4.54.186 + } + } + rule 2718 { + action accept + description VPN-11083-ANY-ALLOW-10.4.55.187 + destination { + group { + address-group DT_VPN-11083 + } + } + source { + address 10.4.55.187 + } + } + rule 2719 { + action accept + description VPN-34583-ANY-ALLOW-10.4.86.243 + destination { + group { + address-group DT_VPN-34583 + } + } + source { + address 10.4.86.243 + } + } + rule 2720 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.84.155 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 123.231.84.155 + } + } + rule 2721 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.117 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.215.117 + } + } + rule 2722 { + action accept + description FW7A9B0_9-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW7A9B0_9 + } + port 11112 + } + protocol tcp + } + rule 2723 { + action accept + description FW3F465_1-TCP-ALLOW-77.68.127.177 + destination { + group { + address-group DT_FW3F465_1 + } + port 3306 + } + protocol tcp + source { + address 77.68.127.177 + } + } + rule 2724 { + action accept + description VPN-34583-ANY-ALLOW-10.4.87.243 + destination { + group { + address-group DT_VPN-34583 + } + } + source { + address 10.4.87.243 + } + } + rule 2725 { + action accept + description FW930F3_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW930F3_1 + } + port 9089,5900,5666,5272 + } + protocol tcp + } + rule 2726 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.165 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.165 + } + } + rule 2727 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.140 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.140 + } + } + rule 2728 { + action accept + description FW90AE3_1-TCP-ALLOW-82.11.114.136 + destination { + group { + address-group DT_FW90AE3_1 + } + port 3306,22 + } + protocol tcp + source { + address 82.11.114.136 + } + } + rule 2729 { + action accept + description FW73215_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW73215_1 + } + port 27015 + } + protocol tcp_udp + } + rule 2730 { + action accept + description FWC2EF2_1-TCP-ALLOW-18.130.156.250 + destination { + group { + address-group DT_FWC2EF2_1 + } + port 22 + } + protocol tcp + source { + address 18.130.156.250 + } + } + rule 2731 { + action accept + description FWC2EF2_1-TCP-ALLOW-90.251.221.19 + destination { + group { + address-group DT_FWC2EF2_1 + } + port 22 + } + protocol tcp + source { + address 90.251.221.19 + } + } + rule 2732 { + action accept + description FW90AE3_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW90AE3_1 + } + port 8765,8001,8000 + } + protocol tcp + } + rule 2733 { + action accept + description FWC2EF2_1-TCP-ALLOW-87.74.110.191 + destination { + group { + address-group DT_FWC2EF2_1 + } + port 8443 + } + protocol tcp + source { + address 87.74.110.191 + } + } + rule 2734 { + action accept + description FWEF92E_5-TCP-ALLOW-77.68.77.70 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 77.68.77.70 + } + } + rule 2735 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.93 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.93 + } + } + rule 2736 { + action accept + description FW81138_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW81138_1 + } + port 123 + } + protocol udp + } + rule 2737 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.208.64 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.208.64 + } + } + rule 2738 { + action accept + description FW03B35_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW03B35_1 + } + port 1-65535 + } + protocol tcp_udp + } + rule 2739 { + action accept + description VPN-19807-ANY-ALLOW-10.4.87.172 + destination { + group { + address-group DT_VPN-19807 + } + } + source { + address 10.4.87.172 + } + } + rule 2740 { + action accept + description FW5658C_1-TCP-ALLOW-94.12.73.154 + destination { + group { + address-group DT_FW5658C_1 + } + port 8447 + } + protocol tcp + source { + address 94.12.73.154 + } + } + rule 2741 { + action accept + description FW5658C_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW5658C_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2742 { + action accept + description FW0B352_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW0B352_1 + } + port 3443 + } + protocol tcp_udp + } + rule 2743 { + action accept + description FWEF92E_5-TCP-ALLOW-77.68.8.74 + destination { + group { + address-group DT_FWEF92E_5 + } + port 3389,445,443 + } + protocol tcp + source { + address 77.68.8.74 + } + } + rule 2744 { + action accept + description FWEF92E_5-TCP-ALLOW-77.68.92.33 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 77.68.92.33 + } + } + rule 2745 { + action accept + description FWEF92E_5-TCP-ALLOW-77.68.93.82 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 77.68.93.82 + } + } + rule 2746 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.44 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.214.44 + } + } + rule 2747 { + action accept + description FW34C91_3-TCP-ALLOW-188.220.176.104 + destination { + group { + address-group DT_FW34C91_3 + } + port 1433 + } + protocol tcp + source { + address 188.220.176.104 + } + } + rule 2748 { + action accept + description FW3F465_1-TCP-ALLOW-77.68.16.101 + destination { + group { + address-group DT_FW3F465_1 + } + port 3306 + } + protocol tcp + source { + address 77.68.16.101 + } + } + rule 2749 { + action accept + description FWEF92E_5-TCP-ALLOW-87.224.33.215 + destination { + group { + address-group DT_FWEF92E_5 + } + port 3389,445,443 + } + protocol tcp + source { + address 87.224.33.215 + } + } + rule 2750 { + action accept + description FW34C91_3-UDP-ALLOW-188.220.176.104 + destination { + group { + address-group DT_FW34C91_3 + } + port 1434 + } + protocol udp + source { + address 188.220.176.104 + } + } + rule 2751 { + action accept + description FWE47DA_1-TCP-ALLOW-185.22.208.0_25 + destination { + group { + address-group DT_FWE47DA_1 + } + port 22 + } + protocol tcp + source { + address 185.22.208.0/25 + } + } + rule 2752 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.187 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.214.187 + } + } + rule 2753 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.84 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.84 + } + } + rule 2754 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-116.206.246.52 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 116.206.246.52 + } + } + rule 2755 { + action accept + description FW8AFF1_7-TCP-ALLOW-77.68.92.154 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433 + } + protocol tcp + source { + address 77.68.92.154 + } + } + rule 2756 { + action accept + description FW8AFF1_7-TCP-ALLOW-77.68.93.156 + destination { + group { + address-group DT_FW8AFF1_7 + } + port 1433 + } + protocol tcp + source { + address 77.68.93.156 + } + } + rule 2757 { + action accept + description VPN-24398-ANY-ALLOW-10.4.88.151 + destination { + group { + address-group DT_VPN-24398 + } + } + source { + address 10.4.88.151 + } + } + rule 2758 { + action accept + description VPN-24398-ANY-ALLOW-10.4.89.151 + destination { + group { + address-group DT_VPN-24398 + } + } + source { + address 10.4.89.151 + } + } + rule 2759 { + action accept + description VPN-24589-ANY-ALLOW-10.4.56.9 + destination { + group { + address-group DT_VPN-24589 + } + } + source { + address 10.4.56.9 + } + } + rule 2760 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.29 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.29 + } + } + rule 2761 { + action accept + description FWC7D36_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWC7D36_1 + } + port 27017,11080 + } + protocol tcp + } + rule 2762 { + action accept + description FWBB718_1-TCP_UDP-ALLOW-77.68.73.116 + destination { + group { + address-group DT_FWBB718_1 + } + port 1433 + } + protocol tcp_udp + source { + address 77.68.73.116 + } + } + rule 2763 { + action accept + description FWBB718_1-UDP-ALLOW-77.68.73.116 + destination { + group { + address-group DT_FWBB718_1 + } + port 1434 + } + protocol udp + source { + address 77.68.73.116 + } + } + rule 2764 { + action accept + description FWB9699_11-TCP-ALLOW-213.171.217.102 + destination { + group { + address-group DT_FWB9699_11 + } + port 22,80,443,8800 + } + protocol tcp + source { + address 213.171.217.102 + } + } + rule 2765 { + action accept + description FW18E6E_3-TCP-ALLOW-103.8.164.5 + destination { + group { + address-group DT_FW18E6E_3 + } + port 22 + } + protocol tcp + source { + address 103.8.164.5 + } + } + rule 2766 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.193 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.213.193 + } + } + rule 2768 { + action accept + description FW26F0A_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW26F0A_1 + } + port 53 + } + protocol tcp_udp + } + rule 2769 { + action accept + description FWCC18F_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWCC18F_2 + } + port 8883,1883 + } + protocol tcp + } + rule 2771 { + action accept + description FW633DD_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW633DD_1 + } + port 28967,14002,9984,9983,9982,9981,8888,8884 + } + protocol tcp + } + rule 2772 { + action accept + description FWDEDB9_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWDEDB9_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2773 { + action accept + description VPN-18646-ANY-ALLOW-10.4.88.109 + destination { + group { + address-group DT_VPN-18646 + } + } + source { + address 10.4.88.109 + } + } + rule 2774 { + action accept + description VPN-18646-ANY-ALLOW-10.4.89.109 + destination { + group { + address-group DT_VPN-18646 + } + } + source { + address 10.4.89.109 + } + } + rule 2775 { + action accept + description FWA0531_1-TCP-ALLOW-87.224.39.221 + destination { + group { + address-group DT_FWA0531_1 + } + port 8082,3003,22 + } + protocol tcp + source { + address 87.224.39.221 + } + } + rule 2776 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.94 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.94 + } + } + rule 2777 { + action accept + description FWA0531_1-TCP-ALLOW-92.237.97.92 + destination { + group { + address-group DT_FWA0531_1 + } + port 8082,3003,22 + } + protocol tcp + source { + address 92.237.97.92 + } + } + rule 2778 { + action accept + description VPN-25822-ANY-ALLOW-10.4.55.42 + destination { + group { + address-group DT_VPN-25822 + } + } + source { + address 10.4.55.42 + } + } + rule 2779 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.88 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.211.88 + } + } + rule 2780 { + action accept + description FWC2D30_1-TCP-ALLOW-143.55.64.0_20 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443 + } + protocol tcp + source { + address 143.55.64.0/20 + } + } + rule 2781 { + action accept + description FW18E6E_3-TCP-ALLOW-194.176.78.206 + destination { + group { + address-group DT_FW18E6E_3 + } + port 22 + } + protocol tcp + source { + address 194.176.78.206 + } + } + rule 2782 { + action accept + description FW18E6E_3-TCP-ALLOW-195.243.221.50 + destination { + group { + address-group DT_FW18E6E_3 + } + port 22 + } + protocol tcp + source { + address 195.243.221.50 + } + } + rule 2783 { + action accept + description FW18E6E_3-TCP-ALLOW-213.171.217.107 + destination { + group { + address-group DT_FW18E6E_3 + } + port 22 + } + protocol tcp + source { + address 213.171.217.107 + } + } + rule 2784 { + action accept + description FW18E6E_3-TCP-ALLOW-81.150.168.54 + destination { + group { + address-group DT_FW18E6E_3 + } + port 3306,22 + } + protocol tcp + source { + address 81.150.168.54 + } + } + rule 2785 { + action accept + description FW18E6E_3-TCP-ALLOW-89.197.133.235 + destination { + group { + address-group DT_FW18E6E_3 + } + port 22 + } + protocol tcp + source { + address 89.197.133.235 + } + } + rule 2786 { + action accept + description FW18E6E_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW18E6E_3 + } + port 60000-60100,873 + } + protocol tcp + } + rule 2787 { + action accept + description FW2BF20_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2BF20_3 + } + port 49152-65534,990 + } + protocol tcp + } + rule 2788 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.98 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.209.98 + } + } + rule 2789 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.65 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.213.65 + } + } + rule 2791 { + action accept + description FW197DB_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW197DB_1 + } + port 49152-65534 + } + protocol tcp + } + rule 2792 { + action accept + description FW1208C_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW1208C_1 + } + port 2087,2083,2096 + } + protocol tcp + } + rule 2793 { + action accept + description FW00D98_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW00D98_1 + } + port 4430 + } + protocol tcp + } + rule 2794 { + action accept + description FW03B35_1-ESP-ALLOW-ANY + destination { + group { + address-group DT_FW03B35_1 + } + } + protocol esp + } + rule 2795 { + action accept + description FW03B35_1-AH-ALLOW-ANY + destination { + group { + address-group DT_FW03B35_1 + } + } + protocol ah + } + rule 2796 { + action accept + description FWEF92E_5-TCP-ALLOW-87.224.6.174 + destination { + group { + address-group DT_FWEF92E_5 + } + port 3389,445,443 + } + protocol tcp + source { + address 87.224.6.174 + } + } + rule 2797 { + action accept + description FW825C8_19-TCP-ALLOW-159.253.51.74 + destination { + group { + address-group DT_FW825C8_19 + } + port 3389,1433,995 + } + protocol tcp + source { + address 159.253.51.74 + } + } + rule 2798 { + action accept + description FW825C8_19-TCP-ALLOW-77.68.76.111 + destination { + group { + address-group DT_FW825C8_19 + } + port 1433 + } + protocol tcp + source { + address 77.68.76.111 + } + } + rule 2799 { + action accept + description FW825C8_19-TCP-ALLOW-77.68.28.63 + destination { + group { + address-group DT_FW825C8_19 + } + port 995 + } + protocol tcp + source { + address 77.68.28.63 + } + } + rule 2801 { + action accept + description FW2EF2C_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2EF2C_1 + } + port 5349 + } + protocol tcp + } + rule 2802 { + action accept + description FWEF92E_5-TCP-ALLOW-88.208.198.93 + destination { + group { + address-group DT_FWEF92E_5 + } + port 443 + } + protocol tcp + source { + address 88.208.198.93 + } + } + rule 2803 { + action accept + description FWC3921_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWC3921_1 + } + port 25000,25001-25005,26000-26006 + } + protocol tcp + } + rule 2804 { + action accept + description FWEF92E_5-UDP-ALLOW-109.228.37.19 + destination { + group { + address-group DT_FWEF92E_5 + } + port 500 + } + protocol udp + source { + address 109.228.37.19 + } + } + rule 2805 { + action accept + description FWEF92E_5-UDP-ALLOW-77.68.11.54 + destination { + group { + address-group DT_FWEF92E_5 + } + port 500 + } + protocol udp + source { + address 77.68.11.54 + } + } + rule 2806 { + action accept + description FW5AE10_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW5AE10_1 + } + port 53 + } + protocol tcp_udp + } + rule 2810 { + action accept + description FW45F87_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW45F87_1 + } + port 60000-60100 + } + protocol tcp + } + rule 2811 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.108.158 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 123.231.108.158 + } + } + rule 2813 { + action accept + description FW825C8_19-TCP-ALLOW-109.228.1.233 + destination { + group { + address-group DT_FW825C8_19 + } + port 1433 + } + protocol tcp + source { + address 109.228.1.233 + } + } + rule 2814 { + action accept + description FW20449_2-ICMP-ALLOW-3.10.221.168 + destination { + group { + address-group DT_FW20449_2 + } + } + protocol icmp + source { + address 3.10.221.168 + } + } + rule 2815 { + action accept + description FWB9699_7-TCP-ALLOW-213.171.217.100 + destination { + group { + address-group DT_FWB9699_7 + } + port 22 + } + protocol tcp + source { + address 213.171.217.100 + } + } + rule 2816 { + action accept + description FWB9699_7-TCP-ALLOW-213.171.217.180 + destination { + group { + address-group DT_FWB9699_7 + } + port 22 + } + protocol tcp + source { + address 213.171.217.180 + } + } + rule 2817 { + action accept + description FWB9699_7-TCP-ALLOW-213.171.217.184 + destination { + group { + address-group DT_FWB9699_7 + } + port 22 + } + protocol tcp + source { + address 213.171.217.184 + } + } + rule 2818 { + action accept + description FWB9699_7-TCP-ALLOW-213.171.217.185 + destination { + group { + address-group DT_FWB9699_7 + } + port 22 + } + protocol tcp + source { + address 213.171.217.185 + } + } + rule 2819 { + action accept + description FWB9699_7-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWB9699_7 + } + port 161 + } + protocol udp + } + rule 2820 { + action accept + description FWB9699_7-TCP-ALLOW-213.171.217.102 + destination { + group { + address-group DT_FWB9699_7 + } + port 22,8443 + } + protocol tcp + source { + address 213.171.217.102 + } + } + rule 2821 { + action accept + description FWB9699_7-TCP-ALLOW-213.171.217.103 + destination { + group { + address-group DT_FWB9699_7 + } + port 22 + } + protocol tcp + source { + address 213.171.217.103 + } + } + rule 2824 { + action accept + description FWE3E77_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE3E77_1 + } + port 10010,10009 + } + protocol tcp + } + rule 2825 { + action accept + description FW8A3FC_3-TCP-ALLOW-93.190.142.120 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 93.190.142.120 + } + } + rule 2826 { + action accept + description FW20449_2-ICMP-ALLOW-82.20.69.137 + destination { + group { + address-group DT_FW20449_2 + } + } + protocol icmp + source { + address 82.20.69.137 + } + } + rule 2827 { + action accept + description FW8A3FC_3-TCP-ALLOW-46.101.232.93 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 21-10000 + } + protocol tcp + source { + address 46.101.232.93 + } + } + rule 2828 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.5 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.213.5 + } + } + rule 2829 { + action accept + description FWD2440_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD2440_1 + } + port 1-65535 + } + protocol tcp + } + rule 2831 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.105 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.214.105 + } + } + rule 2833 { + action accept + description FW825C8_24-TCP-ALLOW-159.253.51.74 + destination { + group { + address-group DT_FW825C8_24 + } + port 3389,1433,995 + } + protocol tcp + source { + address 159.253.51.74 + } + } + rule 2834 { + action accept + description FW825C8_24-TCP-ALLOW-77.68.77.120 + destination { + group { + address-group DT_FW825C8_24 + } + port 1433 + } + protocol tcp + source { + address 77.68.77.120 + } + } + rule 2839 { + action accept + description FWD2440_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWD2440_1 + } + port 1-65535 + } + protocol udp + } + rule 2840 { + action accept + description FW1C8F2_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW1C8F2_1 + } + port 7000-10000,5554,5443,5080,1935,1111 + } + protocol tcp + } + rule 2843 { + action accept + description FWE7180_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWE7180_1 + } + port 443,53 + } + protocol tcp_udp + } + rule 2844 { + action accept + description FWC6301_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWC6301_1 + } + port 2456 + } + protocol tcp_udp + } + rule 2845 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.113 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.215.113 + } + } + rule 2846 { + action accept + description VPN-24589-ANY-ALLOW-10.4.57.9 + destination { + group { + address-group DT_VPN-24589 + } + } + source { + address 10.4.57.9 + } + } + rule 2847 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.237 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.212.237 + } + } + rule 2849 { + action accept + description FWFD9AF_9-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWFD9AF_9 + } + port 445 + } + protocol tcp_udp + } + rule 2850 { + action accept + description VPN-23209-ANY-ALLOW-10.4.58.8 + destination { + group { + address-group DT_VPN-23209 + } + } + source { + address 10.4.58.8 + } + } + rule 2851 { + action accept + description VPN-23209-ANY-ALLOW-10.4.59.8 + destination { + group { + address-group DT_VPN-23209 + } + } + source { + address 10.4.59.8 + } + } + rule 2853 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.29 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.215.29 + } + } + rule 2854 { + action accept + description FW16375_5-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW16375_5 + } + port 2096 + } + protocol tcp_udp + } + rule 2856 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.173 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.173 + } + } + rule 2858 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.208.35 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.208.35 + } + } + rule 2859 { + action accept + description FW73573_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW73573_1 + } + port 25 + } + protocol tcp_udp + } + rule 2860 { + action accept + description FW18E6E_3-TCP-ALLOW-148.253.173.242 + destination { + group { + address-group DT_FW18E6E_3 + } + port 3306 + } + protocol tcp + source { + address 148.253.173.242 + } + } + rule 2861 { + action accept + description FW8ECF4_1-TCP-ALLOW-77.68.2.215 + destination { + group { + address-group DT_FW8ECF4_1 + } + port 3306 + } + protocol tcp + source { + address 77.68.2.215 + } + } + rule 2862 { + action accept + description FW8A3FC_3-TCP_UDP-ALLOW-82.165.100.25 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 21-10000 + } + protocol tcp_udp + source { + address 82.165.100.25 + } + } + rule 2863 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.235 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.213.235 + } + } + rule 2864 { + action accept + description VPN-18647-ANY-ALLOW-10.4.86.114 + destination { + group { + address-group DT_VPN-18647 + } + } + source { + address 10.4.86.114 + } + } + rule 2865 { + action accept + description VPN-18647-ANY-ALLOW-10.4.87.114 + destination { + group { + address-group DT_VPN-18647 + } + } + source { + address 10.4.87.114 + } + } + rule 2867 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.107 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.215.107 + } + } + rule 2868 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.208.239 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.208.239 + } + } + rule 2869 { + action accept + description FWF699D_4-TCP-ALLOW-164.39.151.3 + destination { + group { + address-group DT_FWF699D_4 + } + port 3389 + } + protocol tcp + source { + address 164.39.151.3 + } + } + rule 2870 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.245 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.245 + } + } + rule 2873 { + action accept + description FWEF92E_6-TCP-ALLOW-87.224.6.174 + destination { + group { + address-group DT_FWEF92E_6 + } + port 3389,445 + } + protocol tcp + source { + address 87.224.6.174 + } + } + rule 2874 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.130 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.215.130 + } + } + rule 2875 { + action accept + description FW44BF9_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW44BF9_1 + } + port 49160-49200 + } + protocol tcp + } + rule 2876 { + action accept + description VPN-24591-ANY-ALLOW-10.4.86.4 + destination { + group { + address-group DT_VPN-24591 + } + } + source { + address 10.4.86.4 + } + } + rule 2877 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.60 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.214.60 + } + } + rule 2879 { + action accept + description FWEF92E_6-UDP-ALLOW-77.68.77.57 + destination { + group { + address-group DT_FWEF92E_6 + } + port 500 + } + protocol udp + source { + address 77.68.77.57 + } + } + rule 2880 { + action accept + description FWF699D_4-TCP-ALLOW-185.132.38.110 + destination { + group { + address-group DT_FWF699D_4 + } + port 3389 + } + protocol tcp + source { + address 185.132.38.110 + } + } + rule 2881 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.208.216 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.208.216 + } + } + rule 2882 { + action accept + description FWEF92E_5-UDP-ALLOW-77.68.77.149 + destination { + group { + address-group DT_FWEF92E_5 + } + port 500 + } + protocol udp + source { + address 77.68.77.149 + } + } + rule 2883 { + action accept + description FWA2FF8_4-TCP-ALLOW-80.229.18.102 + destination { + group { + address-group DT_FWA2FF8_4 + } + port 3306,21,22 + } + protocol tcp + source { + address 80.229.18.102 + } + } + rule 2884 { + action accept + description FWA2FF8_4-TCP-ALLOW-109.169.33.69 + destination { + group { + address-group DT_FWA2FF8_4 + } + port 3306,21,22 + } + protocol tcp + source { + address 109.169.33.69 + } + } + rule 2885 { + action accept + description FWA2FF8_4-TCP-ALLOW-46.102.209.35 + destination { + group { + address-group DT_FWA2FF8_4 + } + port 3306,21 + } + protocol tcp + source { + address 46.102.209.35 + } + } + rule 2886 { + action accept + description FWA2FF8_4-TCP-ALLOW-90.213.48.16 + destination { + group { + address-group DT_FWA2FF8_4 + } + port 3306,21 + } + protocol tcp + source { + address 90.213.48.16 + } + } + rule 2887 { + action accept + description FWA2FF8_4-TCP-ALLOW-77.68.76.129 + destination { + group { + address-group DT_FWA2FF8_4 + } + port 22 + } + protocol tcp + source { + address 77.68.76.129 + } + } + rule 2888 { + action accept + description FWA2FF8_4-TCP-ALLOW-109.228.50.145 + destination { + group { + address-group DT_FWA2FF8_4 + } + port 22 + } + protocol tcp + source { + address 109.228.50.145 + } + } + rule 2889 { + action accept + description FWA2FF8_4-TCP-ALLOW-77.68.76.231 + destination { + group { + address-group DT_FWA2FF8_4 + } + port 22 + } + protocol tcp + source { + address 77.68.76.231 + } + } + rule 2890 { + action accept + description FW4513E_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW4513E_1 + } + port 50000-50020,990 + } + protocol tcp + } + rule 2893 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.40.7 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 175.157.40.7 + } + } + rule 2894 { + action accept + description VPN-21876-ANY-ALLOW-10.4.88.96 + destination { + group { + address-group DT_VPN-21876 + } + } + source { + address 10.4.88.96 + } + } + rule 2895 { + action accept + description VPN-21876-ANY-ALLOW-10.4.89.96 + destination { + group { + address-group DT_VPN-21876 + } + } + source { + address 10.4.89.96 + } + } + rule 2896 { + action accept + description VPN-26124-ANY-ALLOW-10.4.54.75 + destination { + group { + address-group DT_VPN-26124 + } + } + source { + address 10.4.54.75 + } + } + rule 2897 { + action accept + description VPN-26124-ANY-ALLOW-10.4.55.76 + destination { + group { + address-group DT_VPN-26124 + } + } + source { + address 10.4.55.76 + } + } + rule 2898 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.21 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.210.21 + } + } + rule 2899 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.213 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.213 + } + } + rule 2901 { + action accept + description FWC6301_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWC6301_1 + } + port 5555 + } + protocol udp + } + rule 2902 { + action accept + description VPN-13261-ANY-ALLOW-10.4.56.173 + destination { + group { + address-group DT_VPN-13261 + } + } + source { + address 10.4.56.173 + } + } + rule 2903 { + action accept + description VPN-13261-ANY-ALLOW-10.4.57.173 + destination { + group { + address-group DT_VPN-13261 + } + } + source { + address 10.4.57.173 + } + } + rule 2909 { + action accept + description VPN-24591-ANY-ALLOW-10.4.87.4 + destination { + group { + address-group DT_VPN-24591 + } + } + source { + address 10.4.87.4 + } + } + rule 2911 { + action accept + description FWE7180_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE7180_1 + } + port 40110-40210,8090 + } + protocol tcp + } + rule 2914 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.247 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.247 + } + } + rule 2915 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.129 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.214.129 + } + } + rule 2916 { + action accept + description FWCB29D_1-TCP-ALLOW-51.146.16.162 + destination { + group { + address-group DT_FWCB29D_1 + } + port 8447,8443,22 + } + protocol tcp + source { + address 51.146.16.162 + } + } + rule 2917 { + action accept + description FW4E399_1-TCP-ALLOW-51.155.19.77 + destination { + group { + address-group DT_FW4E399_1 + } + port 3306 + } + protocol tcp + source { + address 51.155.19.77 + } + } + rule 2919 { + action accept + description FWC72E5_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWC72E5_1 + } + port 9000-9100,6667 + } + protocol tcp + } + rule 2922 { + action accept + description FW21A75_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW21A75_2 + } + port 3000 + } + protocol tcp + } + rule 2923 { + action accept + description FW3B068_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW3B068_2 + } + port 990,60000-65000 + } + protocol tcp + } + rule 2924 { + action accept + description FW48814_3-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW48814_3 + } + port 3306 + } + protocol tcp_udp + } + rule 2925 { + action accept + description FW48814_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW48814_3 + } + port 49152-65534 + } + protocol tcp + } + rule 2926 { + action accept + description FW2B279_4-TCP-ALLOW-178.128.39.210 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443 + } + protocol tcp + source { + address 178.128.39.210 + } + } + rule 2927 { + action accept + description FW2B279_4-TCP-ALLOW-82.165.232.19 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443 + } + protocol tcp + source { + address 82.165.232.19 + } + } + rule 2928 { + action accept + description FW2B279_4-TCP-ALLOW-84.64.186.31 + destination { + group { + address-group DT_FW2B279_4 + } + port 8443 + } + protocol tcp + source { + address 84.64.186.31 + } + } + rule 2929 { + action accept + description FW1C8F2_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW1C8F2_1 + } + port 5000-65000 + } + protocol udp + } + rule 2930 { + action accept + description FW2B279_4-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2B279_4 + } + port 49152-65535 + } + protocol tcp + } + rule 2931 { + action accept + description FW608FA_1-TCP-ALLOW-195.10.106.114 + destination { + group { + address-group DT_FW608FA_1 + } + port 22 + } + protocol tcp + source { + address 195.10.106.114 + } + } + rule 2932 { + action accept + description FW608FA_1-TCP-ALLOW-213.137.25.134 + destination { + group { + address-group DT_FW608FA_1 + } + port 22 + } + protocol tcp + source { + address 213.137.25.134 + } + } + rule 2933 { + action accept + description FW608FA_1-TCP-ALLOW-92.39.202.189 + destination { + group { + address-group DT_FW608FA_1 + } + port 22 + } + protocol tcp + source { + address 92.39.202.189 + } + } + rule 2935 { + action accept + description FWC37B9_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWC37B9_1 + } + port 49152-65535 + } + protocol tcp + } + rule 2936 { + action accept + description FW15C99_6-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW15C99_6 + } + port 32410-32414,1900 + } + protocol udp + } + rule 2937 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-116.206.244.146 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 116.206.244.146 + } + } + rule 2938 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.158 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.211.158 + } + } + rule 2939 { + action accept + description FW15C99_6-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW15C99_6 + } + port 32469,32400 + } + protocol tcp + } + rule 2940 { + action accept + description FW0192C_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0192C_1 + } + port 2053 + } + protocol tcp + } + rule 2941 { + action accept + description FW27949_2-TCP-ALLOW-86.179.23.119 + destination { + group { + address-group DT_FW27949_2 + } + port 443,80 + } + protocol tcp + source { + address 86.179.23.119 + } + } + rule 2942 { + action accept + description FW27949_2-TCP-ALLOW-92.15.208.193 + destination { + group { + address-group DT_FW27949_2 + } + port 443,80 + } + protocol tcp + source { + address 92.15.208.193 + } + } + rule 2943 { + action accept + description VPN-34122-ANY-ALLOW-10.4.56.122 + destination { + group { + address-group DT_VPN-34122 + } + } + source { + address 10.4.56.122 + } + } + rule 2944 { + action accept + description VPN-34122-ANY-ALLOW-10.4.57.122 + destination { + group { + address-group DT_VPN-34122 + } + } + source { + address 10.4.57.122 + } + } + rule 2945 { + action accept + description FWF323F_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWF323F_1 + } + port 25565,9999,8080,5001,3306 + } + protocol tcp_udp + } + rule 2946 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.132 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.213.132 + } + } + rule 2948 { + action accept + description VPN-30261-ANY-ALLOW-10.4.86.110 + destination { + group { + address-group DT_VPN-30261 + } + } + source { + address 10.4.86.110 + } + } + rule 2949 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.246 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.246 + } + } + rule 2951 { + action accept + description FWC2D30_1-TCP-ALLOW-157.231.100.222 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443 + } + protocol tcp + source { + address 157.231.100.222 + } + } + rule 2952 { + action accept + description FWC2D30_1-TCP-ALLOW-164.39.131.31 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443 + } + protocol tcp + source { + address 164.39.131.31 + } + } + rule 2953 { + action accept + description FWC2D30_1-TCP-ALLOW-185.199.108.0_22 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443 + } + protocol tcp + source { + address 185.199.108.0/22 + } + } + rule 2954 { + action accept + description FWC2D30_1-TCP-ALLOW-192.30.252.0_22 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443 + } + protocol tcp + source { + address 192.30.252.0/22 + } + } + rule 2955 { + action accept + description FWC2D30_1-TCP-ALLOW-80.252.78.202 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443 + } + protocol tcp + source { + address 80.252.78.202 + } + } + rule 2956 { + action accept + description FWC2D30_1-TCP-ALLOW-86.15.158.234 + destination { + group { + address-group DT_FWC2D30_1 + } + port 8443 + } + protocol tcp + source { + address 86.15.158.234 + } + } + rule 2957 { + action accept + description VPN-30261-ANY-ALLOW-10.4.87.110 + destination { + group { + address-group DT_VPN-30261 + } + } + source { + address 10.4.87.110 + } + } + rule 2958 { + action accept + description VPN-30262-ANY-ALLOW-10.4.88.36 + destination { + group { + address-group DT_VPN-30262 + } + } + source { + address 10.4.88.36 + } + } + rule 2961 { + action accept + description VPN-15950-ANY-ALLOW-10.4.88.89 + destination { + group { + address-group DT_VPN-15950 + } + } + source { + address 10.4.88.89 + } + } + rule 2962 { + action accept + description FWBFDED_1-TCP-ALLOW-78.141.24.164 + destination { + group { + address-group DT_FWBFDED_1 + } + port 3389 + } + protocol tcp + source { + address 78.141.24.164 + } + } + rule 2963 { + action accept + description VPN-30262-ANY-ALLOW-10.4.89.36 + destination { + group { + address-group DT_VPN-30262 + } + } + source { + address 10.4.89.36 + } + } + rule 2964 { + action accept + description FW1F126_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW1F126_1 + } + port 2087,2083 + } + protocol tcp + } + rule 2965 { + action accept + description FWA7A50_1-ANY-ALLOW-40.120.53.80 + destination { + group { + address-group DT_FWA7A50_1 + } + } + source { + address 40.120.53.80 + } + } + rule 2967 { + action accept + description VPN-23729-ANY-ALLOW-10.4.54.10 + destination { + group { + address-group DT_VPN-23729 + } + } + source { + address 10.4.54.10 + } + } + rule 2968 { + action accept + description VPN-23729-ANY-ALLOW-10.4.55.10 + destination { + group { + address-group DT_VPN-23729 + } + } + source { + address 10.4.55.10 + } + } + rule 2969 { + action accept + description VPN-23733-ANY-ALLOW-10.4.58.12 + destination { + group { + address-group DT_VPN-23733 + } + } + source { + address 10.4.58.12 + } + } + rule 2970 { + action accept + description VPN-23733-ANY-ALLOW-10.4.59.12 + destination { + group { + address-group DT_VPN-23733 + } + } + source { + address 10.4.59.12 + } + } + rule 2971 { + action accept + description VPN-23734-ANY-ALLOW-10.4.56.29 + destination { + group { + address-group DT_VPN-23734 + } + } + source { + address 10.4.56.29 + } + } + rule 2972 { + action accept + description VPN-23734-ANY-ALLOW-10.4.57.29 + destination { + group { + address-group DT_VPN-23734 + } + } + source { + address 10.4.57.29 + } + } + rule 2975 { + action accept + description VPN-23738-ANY-ALLOW-10.4.57.13 + destination { + group { + address-group DT_VPN-23738 + } + } + source { + address 10.4.57.13 + } + } + rule 2976 { + action accept + description FWD8DD1_2-TCP-ALLOW-77.153.164.226 + destination { + group { + address-group DT_FWD8DD1_2 + } + port 3306,22 + } + protocol tcp + source { + address 77.153.164.226 + } + } + rule 2977 { + action accept + description FWE012D_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWE012D_1 + } + port 143,25 + } + protocol tcp_udp + } + rule 2978 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.120.196 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 123.231.120.196 + } + } + rule 2981 { + action accept + description FW24AB7_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW24AB7_1 + } + port 40110-40210 + } + protocol tcp_udp + } + rule 2985 { + action accept + description FW2379F_14-TCP-ALLOW-194.72.140.178 + destination { + group { + address-group DT_FW2379F_14 + } + port 3389,21 + } + protocol tcp + source { + address 194.72.140.178 + } + } + rule 2986 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.97 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.97 + } + } + rule 2988 { + action accept + description FW883EB_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW883EB_1 + } + port 5005,5004,5003,5002,5001 + } + protocol tcp + } + rule 2992 { + action accept + description FW310C6_3-ANY-ALLOW-62.30.207.232 + destination { + group { + address-group DT_FW310C6_3 + } + } + source { + address 62.30.207.232 + } + } + rule 2993 { + action accept + description VPN-15950-ANY-ALLOW-10.4.89.89 + destination { + group { + address-group DT_VPN-15950 + } + } + source { + address 10.4.89.89 + } + } + rule 2994 { + action accept + description VPN-15960-ANY-ALLOW-10.4.88.90 + destination { + group { + address-group DT_VPN-15960 + } + } + source { + address 10.4.88.90 + } + } + rule 2995 { + action accept + description FWEF92E_7-UDP-ALLOW-77.68.77.57 + destination { + group { + address-group DT_FWEF92E_7 + } + port 500 + } + protocol udp + source { + address 77.68.77.57 + } + } + rule 2996 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.135 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.214.135 + } + } + rule 2998 { + action accept + description VPN-31002-ANY-ALLOW-10.4.88.126 + destination { + group { + address-group DT_VPN-31002 + } + } + source { + address 10.4.88.126 + } + } + rule 2999 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-116.206.246.110 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 116.206.246.110 + } + } + rule 3000 { + action accept + description FW08061_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW08061_1 + } + port 49152-65535 + } + protocol tcp + } + rule 3001 { + action accept + description VPN-15960-ANY-ALLOW-10.4.89.90 + destination { + group { + address-group DT_VPN-15960 + } + } + source { + address 10.4.89.90 + } + } + rule 3003 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.56 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.210.56 + } + } + rule 3004 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.47.47 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 175.157.47.47 + } + } + rule 3005 { + action accept + description FW10C3D_19-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW10C3D_19 + } + port 49152-65535,14147 + } + protocol tcp + } + rule 3006 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.136 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.210.136 + } + } + rule 3009 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.44.109 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 175.157.44.109 + } + } + rule 3010 { + action accept + description VPN-24592-ANY-ALLOW-10.4.88.9 + destination { + group { + address-group DT_VPN-24592 + } + } + source { + address 10.4.88.9 + } + } + rule 3011 { + action accept + description FW05AD0_2-TCP-ALLOW-213.171.209.161 + destination { + group { + address-group DT_FW05AD0_2 + } + port 3389,1433,21 + } + protocol tcp + source { + address 213.171.209.161 + } + } + rule 3012 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.86.254 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 123.231.86.254 + } + } + rule 3014 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.16 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.210.16 + } + } + rule 3018 { + action accept + description VPN-24592-ANY-ALLOW-10.4.89.9 + destination { + group { + address-group DT_VPN-24592 + } + } + source { + address 10.4.89.9 + } + } + rule 3019 { + action accept + description VPN-24593-ANY-ALLOW-10.4.54.6 + destination { + group { + address-group DT_VPN-24593 + } + } + source { + address 10.4.54.6 + } + } + rule 3020 { + action accept + description VPN-24593-ANY-ALLOW-10.4.55.6 + destination { + group { + address-group DT_VPN-24593 + } + } + source { + address 10.4.55.6 + } + } + rule 3021 { + action accept + description VPN-24594-ANY-ALLOW-10.4.58.6 + destination { + group { + address-group DT_VPN-24594 + } + } + source { + address 10.4.58.6 + } + } + rule 3022 { + action accept + description VPN-24594-ANY-ALLOW-10.4.59.6 + destination { + group { + address-group DT_VPN-24594 + } + } + source { + address 10.4.59.6 + } + } + rule 3023 { + action accept + description VPN-24595-ANY-ALLOW-10.4.56.14 + destination { + group { + address-group DT_VPN-24595 + } + } + source { + address 10.4.56.14 + } + } + rule 3024 { + action accept + description VPN-24595-ANY-ALLOW-10.4.57.14 + destination { + group { + address-group DT_VPN-24595 + } + } + source { + address 10.4.57.14 + } + } + rule 3025 { + action accept + description VPN-32528-ANY-ALLOW-10.4.58.67 + destination { + group { + address-group DT_VPN-32528 + } + } + source { + address 10.4.58.67 + } + } + rule 3026 { + action accept + description VPN-32528-ANY-ALLOW-10.4.59.67 + destination { + group { + address-group DT_VPN-32528 + } + } + source { + address 10.4.59.67 + } + } + rule 3027 { + action accept + description FW6187E_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW6187E_1 + } + port 51195 + } + protocol udp + } + rule 3028 { + action accept + description FW406AB_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW406AB_1 + } + port 37013,25461,8881,8080,2095,2082,1992 + } + protocol tcp_udp + } + rule 3029 { + action accept + description FWA86A4_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA86A4_1 + } + port 30333,5666 + } + protocol tcp + } + rule 3032 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.52 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.52 + } + } + rule 3033 { + action accept + description FWC055A_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWC055A_1 + } + port 2195 + } + protocol tcp + } + rule 3035 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.81 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.213.81 + } + } + rule 3039 { + action accept + description FW42BC7_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW42BC7_1 + } + port 53 + } + protocol tcp_udp + } + rule 3040 { + action accept + description FW42BC7_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW42BC7_1 + } + port 49152-65535 + } + protocol tcp + } + rule 3041 { + action accept + description FW310C6_3-ANY-ALLOW-88.208.198.39 + destination { + group { + address-group DT_FW310C6_3 + } + } + source { + address 88.208.198.39 + } + } + rule 3042 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.235 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.209.235 + } + } + rule 3043 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.205 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.212.205 + } + } + rule 3044 { + action accept + description FWBE878_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWBE878_1 + } + port 8989,5003,3000 + } + protocol tcp_udp + } + rule 3045 { + action accept + description VPN-30679-ANY-ALLOW-10.4.58.195 + destination { + group { + address-group DT_VPN-30679 + } + } + source { + address 10.4.58.195 + } + } + rule 3046 { + action accept + description FW6B9B9_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW6B9B9_1 + } + port 30006-65000,27017,7101,4200,2990-3009 + } + protocol tcp + } + rule 3047 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.212 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.212 + } + } + rule 3049 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.125.4 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 123.231.125.4 + } + } + rule 3050 { + action accept + description FW49C3D_4-TCP-ALLOW-83.100.136.74 + destination { + group { + address-group DT_FW49C3D_4 + } + port 3389,445 + } + protocol tcp + source { + address 83.100.136.74 + } + } + rule 3051 { + action accept + description FW49C3D_6-TCP-ALLOW-87.224.33.215 + destination { + group { + address-group DT_FW49C3D_6 + } + port 3389,445 + } + protocol tcp + source { + address 87.224.33.215 + } + } + rule 3053 { + action accept + description FW89619_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW89619_1 + } + port 9000-10999 + } + protocol udp + } + rule 3054 { + action accept + description FWBD9D0_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWBD9D0_1 + } + port 9090 + } + protocol tcp + } + rule 3055 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.47.236 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 175.157.47.236 + } + } + rule 3056 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.46.226 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 175.157.46.226 + } + } + rule 3058 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.205 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.211.205 + } + } + rule 3060 { + action accept + description FWF7B68_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF7B68_1 + } + port 49152-65535 + } + protocol tcp + } + rule 3061 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.253 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.210.253 + } + } + rule 3063 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.0 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.210.0 + } + } + rule 3065 { + action accept + description FW85619_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW85619_1 + } + port 6433 + } + protocol tcp + } + rule 3066 { + action accept + description FW5A5D7_3-TCP-ALLOW-188.66.79.94 + destination { + group { + address-group DT_FW5A5D7_3 + } + port 8172,3389 + } + protocol tcp + source { + address 188.66.79.94 + } + } + rule 3067 { + action accept + description FWF30BD_1-TCP-ALLOW-81.133.80.114 + destination { + group { + address-group DT_FWF30BD_1 + } + port 22 + } + protocol tcp + source { + address 81.133.80.114 + } + } + rule 3068 { + action accept + description FWF30BD_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF30BD_1 + } + port 5061,5015,5001 + } + protocol tcp + } + rule 3069 { + action accept + description FWBD9D0_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWBD9D0_1 + } + port 51820 + } + protocol udp + } + rule 3070 { + action accept + description FW7C4D9_14-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW7C4D9_14 + } + port 25565,2456-2458 + } + protocol tcp_udp + } + rule 3071 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.23 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.209.23 + } + } + rule 3072 { + action accept + description FWEEC75_1-TCP-ALLOW-81.96.100.32 + destination { + group { + address-group DT_FWEEC75_1 + } + port 8447 + } + protocol tcp + source { + address 81.96.100.32 + } + } + rule 3073 { + action accept + description FW8A3FC_3-TCP-ALLOW-95.168.164.208 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 95.168.164.208 + } + } + rule 3074 { + action accept + description VPN-19992-ANY-ALLOW-10.4.86.158 + destination { + group { + address-group DT_VPN-19992 + } + } + source { + address 10.4.86.158 + } + } + rule 3075 { + action accept + description FWF30BD_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWF30BD_1 + } + port 5090,5060 + } + protocol tcp_udp + } + rule 3076 { + action accept + description VPN-30679-ANY-ALLOW-10.4.59.195 + destination { + group { + address-group DT_VPN-30679 + } + } + source { + address 10.4.59.195 + } + } + rule 3077 { + action accept + description FW930F3_3-ANY-ALLOW-77.68.112.254 + destination { + group { + address-group DT_FW930F3_3 + } + } + source { + address 77.68.112.254 + } + } + rule 3078 { + action accept + description FW672AB_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW672AB_1 + } + port 5432 + } + protocol tcp + } + rule 3079 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.252 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.211.252 + } + } + rule 3080 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.86.192 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 123.231.86.192 + } + } + rule 3081 { + action accept + description VPN-33204-ANY-ALLOW-10.4.56.176 + destination { + group { + address-group DT_VPN-33204 + } + } + source { + address 10.4.56.176 + } + } + rule 3083 { + action accept + description FW1FA8E_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW1FA8E_1 + } + port 33434 + } + protocol udp + } + rule 3084 { + action accept + description FWD2440_1-ESP-ALLOW-ANY + destination { + group { + address-group DT_FWD2440_1 + } + } + protocol esp + } + rule 3085 { + action accept + description FWA0531_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA0531_1 + } + port 53 + } + protocol tcp_udp + } + rule 3090 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.70 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.70 + } + } + rule 3091 { + action accept + description FWF7BFA_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF7BFA_1 + } + port 8000,5901,5479,5478 + } + protocol tcp + } + rule 3092 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.212 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.214.212 + } + } + rule 3094 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.125 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.212.125 + } + } + rule 3096 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.89 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.89 + } + } + rule 3097 { + action accept + description FWD56A2_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD56A2_1 + } + port 8001,8000 + } + protocol tcp + } + rule 3098 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.109 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.109 + } + } + rule 3099 { + action accept + description FW36425_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW36425_1 + } + port 44445,7770-7800 + } + protocol tcp + } + rule 3100 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.238 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.214.238 + } + } + rule 3102 { + action accept + description FW6B39D_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW6B39D_1 + } + port 49216,49215 + } + protocol tcp_udp + } + rule 3103 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.121 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.213.121 + } + } + rule 3105 { + action accept + description FW2379F_14-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW2379F_14 + } + port 443 + } + protocol tcp_udp + } + rule 3107 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.38 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.38 + } + } + rule 3109 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.191 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.213.191 + } + } + rule 3111 { + action accept + description FW27947_1-TCP-ALLOW-213.229.100.148 + destination { + group { + address-group DT_FW27947_1 + } + port 3306 + } + protocol tcp + source { + address 213.229.100.148 + } + } + rule 3112 { + action accept + description FWD42CF_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD42CF_1 + } + port 5432,5001,5000 + } + protocol tcp + } + rule 3114 { + action accept + description FW3A12F_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW3A12F_1 + } + port 53 + } + protocol tcp_udp + } + rule 3116 { + action accept + description FW5A5D7_3-TCP-ALLOW-194.62.184.87 + destination { + group { + address-group DT_FW5A5D7_3 + } + port 3389 + } + protocol tcp + source { + address 194.62.184.87 + } + } + rule 3117 { + action accept + description FW5A5D7_3-TCP-ALLOW-51.219.31.78 + destination { + group { + address-group DT_FW5A5D7_3 + } + port 8172,3389 + } + protocol tcp + source { + address 51.219.31.78 + } + } + rule 3118 { + action accept + description VPN-26157-ANY-ALLOW-10.4.86.57 + destination { + group { + address-group DT_VPN-26157 + } + } + source { + address 10.4.86.57 + } + } + rule 3119 { + action accept + description VPN-26157-ANY-ALLOW-10.4.87.57 + destination { + group { + address-group DT_VPN-26157 + } + } + source { + address 10.4.87.57 + } + } + rule 3120 { + action accept + description FWA7625_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA7625_1 + } + port 943 + } + protocol tcp + } + rule 3121 { + action accept + description FWC96A1_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWC96A1_1 + } + port 1194 + } + protocol udp + } + rule 3122 { + action accept + description FWA7625_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA7625_1 + } + port 1194 + } + protocol udp + } + rule 3123 { + action accept + description FWA7625_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA7625_1 + } + port 32400,10108 + } + protocol tcp_udp + } + rule 3125 { + action accept + description FW8A3FC_3-TCP-ALLOW-185.173.161.154 + destination { + group { + address-group DT_FW8A3FC_3 + } + port 465 + } + protocol tcp + source { + address 185.173.161.154 + } + } + rule 3127 { + action accept + description FW05339_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW05339_1 + } + port 46961 + } + protocol udp + } + rule 3130 { + action accept + description FWA0AA0_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA0AA0_1 + } + port 1194 + } + protocol udp + } + rule 3132 { + action accept + description FWD8DD1_2-TCP_UDP-ALLOW-77.153.164.226 + destination { + group { + address-group DT_FWD8DD1_2 + } + port 443,80 + } + protocol tcp_udp + source { + address 77.153.164.226 + } + } + rule 3134 { + action accept + description FW19987_4-TCP-ALLOW-87.224.6.174 + destination { + group { + address-group DT_FW19987_4 + } + port 3389,445,443 + } + protocol tcp + source { + address 87.224.6.174 + } + } + rule 3135 { + action accept + description FW40AE4_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW40AE4_1 + } + port 53 + } + protocol tcp_udp + } + rule 3136 { + action accept + description VPN-33204-ANY-ALLOW-10.4.57.176 + destination { + group { + address-group DT_VPN-33204 + } + } + source { + address 10.4.57.176 + } + } + rule 3137 { + action accept + description FWF3A1B_1-TCP_UDP-ALLOW-86.132.125.4 + destination { + group { + address-group DT_FWF3A1B_1 + } + port 2222 + } + protocol tcp_udp + source { + address 86.132.125.4 + } + } + rule 3138 { + action accept + description FWF3A1B_1-TCP_UDP-ALLOW-91.205.173.51 + destination { + group { + address-group DT_FWF3A1B_1 + } + port 2222 + } + protocol tcp_udp + source { + address 91.205.173.51 + } + } + rule 3143 { + action accept + description FWA86ED_101-TCP-ALLOW-109.149.121.73 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 109.149.121.73 + } + } + rule 3144 { + action accept + description FWA0AA0_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA0AA0_1 + } + port 28083,28015-28016,1935 + } + protocol tcp_udp + } + rule 3146 { + action accept + description FWF3A1B_1-TCP_UDP-ALLOW-92.233.27.144 + destination { + group { + address-group DT_FWF3A1B_1 + } + port 2222 + } + protocol tcp_udp + source { + address 92.233.27.144 + } + } + rule 3148 { + action accept + description FWA86ED_101-TCP-ALLOW-151.228.194.190 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 151.228.194.190 + } + } + rule 3149 { + action accept + description FW9B6FB_1-ICMP-ALLOW-77.68.89.115_32 + destination { + group { + address-group DT_FW9B6FB_1 + } + } + protocol icmp + source { + address 77.68.89.115/32 + } + } + rule 3153 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.199 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.214.199 + } + } + rule 3155 { + action accept + description FW45F3D_1-ANY-ALLOW-195.224.110.168 + destination { + group { + address-group DT_FW45F3D_1 + } + } + source { + address 195.224.110.168 + } + } + rule 3156 { + action accept + description FWF8E67_1-TCP-ALLOW-82.14.188.35 + destination { + group { + address-group DT_FWF8E67_1 + } + port 22 + } + protocol tcp + source { + address 82.14.188.35 + } + } + rule 3157 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.58 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.215.58 + } + } + rule 3158 { + action accept + description VPN-19992-ANY-ALLOW-10.4.87.158 + destination { + group { + address-group DT_VPN-19992 + } + } + source { + address 10.4.87.158 + } + } + rule 3159 { + action accept + description FWA86ED_101-TCP-ALLOW-5.66.24.185 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 5.66.24.185 + } + } + rule 3160 { + action accept + description FWF8E67_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF8E67_1 + } + port 3001 + } + protocol tcp + } + rule 3161 { + action accept + description FWD2440_1-AH-ALLOW-ANY + destination { + group { + address-group DT_FWD2440_1 + } + } + protocol ah + } + rule 3166 { + action accept + description FW3EBC8_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW3EBC8_1 + } + port 9001-9900,9000 + } + protocol tcp + } + rule 3167 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.244 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.244 + } + } + rule 3168 { + action accept + description FWA0531_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA0531_1 + } + port 3000 + } + protocol tcp + } + rule 3170 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.137 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.215.137 + } + } + rule 3173 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.104 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.104 + } + } + rule 3176 { + action accept + description FW6906B_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW6906B_1 + } + port 4190 + } + protocol tcp + } + rule 3177 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-116.206.246.230 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 116.206.246.230 + } + } + rule 3178 { + action accept + description FW444AF_1-TCP-ALLOW-91.135.10.140 + destination { + group { + address-group DT_FW444AF_1 + } + port 27017 + } + protocol tcp + source { + address 91.135.10.140 + } + } + rule 3180 { + action accept + description FWA86ED_101-TCP-ALLOW-81.150.13.34 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 81.150.13.34 + } + } + rule 3181 { + action accept + description FWA86ED_101-TCP-ALLOW-82.10.14.73 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 82.10.14.73 + } + } + rule 3183 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.25 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.25 + } + } + rule 3184 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.224 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.215.224 + } + } + rule 3185 { + action accept + description FW9B6FB_1-TCP-ALLOW-77.68.89.115_32 + destination { + group { + address-group DT_FW9B6FB_1 + } + port 10050 + } + protocol tcp + source { + address 77.68.89.115/32 + } + } + rule 3186 { + action accept + description VPN-14673-ANY-ALLOW-10.4.89.44 + destination { + group { + address-group DT_VPN-14673 + } + } + source { + address 10.4.89.44 + } + } + rule 3187 { + action accept + description FWCA628_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWCA628_1 + } + port 2096,2095,2087,2086,2083,2082 + } + protocol tcp + } + rule 3189 { + action accept + description VPN-28484-ANY-ALLOW-10.4.58.159 + destination { + group { + address-group DT_VPN-28484 + } + } + source { + address 10.4.58.159 + } + } + rule 3190 { + action accept + description FW028C0_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW028C0_2 + } + port 44491-44498,44474 + } + protocol tcp + } + rule 3191 { + action accept + description VPN-28484-ANY-ALLOW-10.4.59.159 + destination { + group { + address-group DT_VPN-28484 + } + } + source { + address 10.4.59.159 + } + } + rule 3192 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.119 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.213.119 + } + } + rule 3194 { + action accept + description FWF699D_4-TCP-ALLOW-195.74.108.130 + destination { + group { + address-group DT_FWF699D_4 + } + port 3389 + } + protocol tcp + source { + address 195.74.108.130 + } + } + rule 3195 { + action accept + description FWF699D_4-TCP-ALLOW-31.54.149.143 + destination { + group { + address-group DT_FWF699D_4 + } + port 3389 + } + protocol tcp + source { + address 31.54.149.143 + } + } + rule 3196 { + action accept + description FWF699D_4-TCP-ALLOW-35.204.243.120 + destination { + group { + address-group DT_FWF699D_4 + } + port 3389 + } + protocol tcp + source { + address 35.204.243.120 + } + } + rule 3197 { + action accept + description FWF699D_4-TCP-ALLOW-81.150.55.65 + destination { + group { + address-group DT_FWF699D_4 + } + port 3389 + } + protocol tcp + source { + address 81.150.55.65 + } + } + rule 3198 { + action accept + description FWF699D_4-TCP-ALLOW-81.150.55.70 + destination { + group { + address-group DT_FWF699D_4 + } + port 3389 + } + protocol tcp + source { + address 81.150.55.70 + } + } + rule 3199 { + action accept + description FWF699D_4-TCP-ALLOW-86.142.112.4 + destination { + group { + address-group DT_FWF699D_4 + } + port 3389 + } + protocol tcp + source { + address 86.142.112.4 + } + } + rule 3200 { + action accept + description FWF699D_4-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWF699D_4 + } + port 8983 + } + protocol tcp_udp + } + rule 3201 { + action accept + description FWF699D_4-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF699D_4 + } + port 11009,10009 + } + protocol tcp + } + rule 3202 { + action accept + description VPN-2661-ANY-ALLOW-10.4.54.24 + destination { + group { + address-group DT_VPN-2661 + } + } + source { + address 10.4.54.24 + } + } + rule 3203 { + action accept + description VPN-2661-ANY-ALLOW-10.4.55.24 + destination { + group { + address-group DT_VPN-2661 + } + } + source { + address 10.4.55.24 + } + } + rule 3204 { + action accept + description VPN-9727-ANY-ALLOW-10.4.54.118 + destination { + group { + address-group DT_VPN-9727 + } + } + source { + address 10.4.54.118 + } + } + rule 3205 { + action accept + description VPN-9727-ANY-ALLOW-10.4.55.119 + destination { + group { + address-group DT_VPN-9727 + } + } + source { + address 10.4.55.119 + } + } + rule 3207 { + action accept + description FWF0221_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWF0221_1 + } + port 65000,8099,8080 + } + protocol tcp_udp + } + rule 3208 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.180 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.180 + } + } + rule 3209 { + action accept + description FWA86ED_101-TCP-ALLOW-82.5.189.5 + destination { + group { + address-group DT_FWA86ED_101 + } + port 443 + } + protocol tcp + source { + address 82.5.189.5 + } + } + rule 3210 { + action accept + description FW60FD6_5-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW60FD6_5 + } + port 1194 + } + protocol udp + } + rule 3211 { + action accept + description FW60FD6_5-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW60FD6_5 + } + port 9500,9191,9090,8090,2222 + } + protocol tcp + } + rule 3212 { + action accept + description FWA86ED_101-TCP-ALLOW-84.65.217.114 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 84.65.217.114 + } + } + rule 3213 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.43.21 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 175.157.43.21 + } + } + rule 3214 { + action accept + description FW45F3D_1-ANY-ALLOW-77.68.126.251 + destination { + group { + address-group DT_FW45F3D_1 + } + } + source { + address 77.68.126.251 + } + } + rule 3215 { + action accept + description FWA86ED_101-TCP-ALLOW-86.14.23.23 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 86.14.23.23 + } + } + rule 3217 { + action accept + description FW85E02_11-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW85E02_11 + } + port 9000-10999 + } + protocol udp + } + rule 3218 { + action accept + description FW5D0FA_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW5D0FA_1 + } + port 53 + } + protocol tcp_udp + } + rule 3222 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.141 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.141 + } + } + rule 3223 { + action accept + description FWCDD8B_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWCDD8B_1 + } + port 2222 + } + protocol tcp + } + rule 3224 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.185 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.214.185 + } + } + rule 3225 { + action accept + description FW06940_3-TCP_UDP-ALLOW-213.171.210.153 + destination { + group { + address-group DT_FW06940_3 + } + port 1-65535 + } + protocol tcp_udp + source { + address 213.171.210.153 + } + } + rule 3226 { + action accept + description FW06940_3-TCP_UDP-ALLOW-70.29.113.102 + destination { + group { + address-group DT_FW06940_3 + } + port 1-65535 + } + protocol tcp_udp + source { + address 70.29.113.102 + } + } + rule 3227 { + action accept + description FWC32BE_1-ANY-ALLOW-3.127.0.177 + destination { + group { + address-group DT_FWC32BE_1 + } + } + source { + address 3.127.0.177 + } + } + rule 3228 { + action accept + description FWA86ED_101-TCP-ALLOW-93.115.195.58 + destination { + group { + address-group DT_FWA86ED_101 + } + port 3389,443 + } + protocol tcp + source { + address 93.115.195.58 + } + } + rule 3229 { + action accept + description FWE32F2_8-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE32F2_8 + } + port 40120,30120,30110 + } + protocol tcp + } + rule 3230 { + action accept + description VPN-28515-ANY-ALLOW-10.4.56.162 + destination { + group { + address-group DT_VPN-28515 + } + } + source { + address 10.4.56.162 + } + } + rule 3231 { + action accept + description FW06940_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW06940_3 + } + port 30000-30400,8443-8447,445,80-110,21-25 + } + protocol tcp + } + rule 3232 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.134 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.211.134 + } + } + rule 3236 { + action accept + description VPN-28515-ANY-ALLOW-10.4.57.162 + destination { + group { + address-group DT_VPN-28515 + } + } + source { + address 10.4.57.162 + } + } + rule 3237 { + action accept + description FWF4063_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF4063_1 + } + port 3000 + } + protocol tcp + } + rule 3240 { + action accept + description FW06940_3-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW06940_3 + } + port 49152-65535,6379,5666,5432-5454 + } + protocol tcp_udp + } + rule 3242 { + action accept + description FW2E8D4_1-TCP-ALLOW-63.35.92.185 + destination { + group { + address-group DT_FW2E8D4_1 + } + port 3389 + } + protocol tcp + source { + address 63.35.92.185 + } + } + rule 3244 { + action accept + description FWF30BD_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWF30BD_1 + } + port 9000-10999 + } + protocol udp + } + rule 3245 { + action accept + description FWE30A1_4-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWE30A1_4 + } + port 65057 + } + protocol tcp_udp + } + rule 3246 { + action accept + description VPN-26772-ANY-ALLOW-10.4.54.123 + destination { + group { + address-group DT_VPN-26772 + } + } + source { + address 10.4.54.123 + } + } + rule 3249 { + action accept + description FW56496_1-ANY-ALLOW-77.68.82.49 + destination { + group { + address-group DT_FW56496_1 + } + } + source { + address 77.68.82.49 + } + } + rule 3251 { + action accept + description FWDA443_6-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWDA443_6 + } + port 30175,12050 + } + protocol tcp + } + rule 3253 { + action accept + description FW5A521_3-TCP-ALLOW-88.98.75.17 + destination { + group { + address-group DT_FW5A521_3 + } + port 22 + } + protocol tcp + source { + address 88.98.75.17 + } + } + rule 3254 { + action accept + description FW5A521_3-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW5A521_3 + } + port 161-162 + } + protocol udp + } + rule 3255 { + action accept + description FW5A521_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW5A521_3 + } + port 5900 + } + protocol tcp + } + rule 3259 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.178 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.214.178 + } + } + rule 3260 { + action accept + description VPN-26772-ANY-ALLOW-10.4.55.124 + destination { + group { + address-group DT_VPN-26772 + } + } + source { + address 10.4.55.124 + } + } + rule 3262 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.114 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.114 + } + } + rule 3272 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-116.206.246.30 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 116.206.246.30 + } + } + rule 3273 { + action accept + description FW2B4BA_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW2B4BA_1 + } + port 30000-31000 + } + protocol tcp + } + rule 3284 { + action accept + description FW06940_3-TCP-ALLOW-213.171.217.107 + destination { + group { + address-group DT_FW06940_3 + } + port 8443 + } + protocol tcp + source { + address 213.171.217.107 + } + } + rule 3285 { + action accept + description FW0952B_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0952B_1 + } + port 9030,9001 + } + protocol tcp + } + rule 3286 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.85.35 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 123.231.85.35 + } + } + rule 3290 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.208.232 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.208.232 + } + } + rule 3294 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.21 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.21 + } + } + rule 3295 { + action accept + description FW0EA3F_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW0EA3F_1 + } + port 1-65535 + } + protocol tcp_udp + } + rule 3296 { + action accept + description FW9D5C7_1-TCP-ALLOW-209.97.176.108 + destination { + group { + address-group DT_FW9D5C7_1 + } + port 8447,8443,22 + } + protocol tcp + source { + address 209.97.176.108 + } + } + rule 3297 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.188 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.210.188 + } + } + rule 3298 { + action accept + description FW9D5C7_1-TCP-ALLOW-165.227.231.227 + destination { + group { + address-group DT_FW9D5C7_1 + } + port 9117,9113,9104,9100 + } + protocol tcp + source { + address 165.227.231.227 + } + } + rule 3299 { + action accept + description FW4DB0A_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW4DB0A_1 + } + port 953 + } + protocol tcp + } + rule 3300 { + action accept + description FW4DB0A_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW4DB0A_1 + } + port 953 + } + protocol udp + } + rule 3301 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.91 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.91 + } + } + rule 3303 { + action accept + description FW56496_1-TCP-ALLOW-176.255.93.149 + destination { + group { + address-group DT_FW56496_1 + } + port 3389 + } + protocol tcp + source { + address 176.255.93.149 + } + } + rule 3304 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.79 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.210.79 + } + } + rule 3305 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.43 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.215.43 + } + } + rule 3306 { + action accept + description FW310C6_3-ANY-ALLOW-88.208.198.40 + destination { + group { + address-group DT_FW310C6_3 + } + } + source { + address 88.208.198.40 + } + } + rule 3307 { + action accept + description FW597A6_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW597A6_1 + } + port 49152-65535,990 + } + protocol tcp + } + rule 3308 { + action accept + description FW597A6_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW597A6_1 + } + port 3306 + } + protocol tcp_udp + } + rule 3309 { + action accept + description FWBC280_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWBC280_1 + } + port 49152-65535,20-21 + } + protocol tcp + } + rule 3310 { + action accept + description VPN-31301-ANY-ALLOW-10.4.87.223 + destination { + group { + address-group DT_VPN-31301 + } + } + source { + address 10.4.87.223 + } + } + rule 3311 { + action accept + description FW18E6E_3-TCP-ALLOW-148.253.173.243 + destination { + group { + address-group DT_FW18E6E_3 + } + port 3306 + } + protocol tcp + source { + address 148.253.173.243 + } + } + rule 3312 { + action accept + description FW9EEDD_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW9EEDD_1 + } + port 990,197,20-23 + } + protocol tcp + } + rule 3313 { + action accept + description FW9EEDD_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW9EEDD_1 + } + port 49152-65535 + } + protocol tcp_udp + } + rule 3314 { + action accept + description VPN-31002-ANY-ALLOW-10.4.89.126 + destination { + group { + address-group DT_VPN-31002 + } + } + source { + address 10.4.89.126 + } + } + rule 3316 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.11 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.11 + } + } + rule 3317 { + action accept + description FW32EFF_49-TCP-ALLOW-195.59.191.128_25 + destination { + group { + address-group DT_FW32EFF_49 + } + port 5589 + } + protocol tcp + source { + address 195.59.191.128/25 + } + } + rule 3318 { + action accept + description FW32EFF_49-TCP-ALLOW-213.71.130.0_26 + destination { + group { + address-group DT_FW32EFF_49 + } + port 5589 + } + protocol tcp + source { + address 213.71.130.0/26 + } + } + rule 3319 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.88 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.215.88 + } + } + rule 3320 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.215.173 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.215.173 + } + } + rule 3321 { + action accept + description FW32EFF_49-TCP-ALLOW-84.19.45.82 + destination { + group { + address-group DT_FW32EFF_49 + } + port 5589 + } + protocol tcp + source { + address 84.19.45.82 + } + } + rule 3322 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-175.157.43.122 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 175.157.43.122 + } + } + rule 3323 { + action accept + description FWC1ACD_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWC1ACD_1 + } + port 28061,28060,8080 + } + protocol tcp_udp + } + rule 3324 { + action accept + description FWA5D67_1-TCP_UDP-ALLOW-84.74.32.74 + destination { + group { + address-group DT_FWA5D67_1 + } + port 3389 + } + protocol tcp_udp + source { + address 84.74.32.74 + } + } + rule 3325 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.169 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.213.169 + } + } + rule 3326 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.89 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.213.89 + } + } + rule 3329 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.35 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.35 + } + } + rule 3330 { + action accept + description FWCE020_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWCE020_1 + } + port 48402 + } + protocol udp + } + rule 3333 { + action accept + description FWF3574_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWF3574_1 + } + port 8060,445,139 + } + protocol tcp + } + rule 3334 { + action accept + description FWE6AB2_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWE6AB2_1 + } + port 44158,945,943 + } + protocol tcp + } + rule 3335 { + action accept + description FWBFC02_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWBFC02_1 + } + port 44158,945,943 + } + protocol tcp + } + rule 3336 { + action accept + description FWBFC02_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWBFC02_1 + } + port 1194 + } + protocol udp + } + rule 3337 { + action accept + description FWE6AB2_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWE6AB2_1 + } + port 1194 + } + protocol udp + } + rule 3338 { + action accept + description FWBC8A6_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWBC8A6_1 + } + port 44158,945,943 + } + protocol tcp + } + rule 3339 { + action accept + description FWBC8A6_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FWBC8A6_1 + } + port 1194 + } + protocol udp + } + rule 3340 { + action accept + description FWA0AA0_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWA0AA0_1 + } + port 2302 + } + protocol tcp + } + rule 3342 { + action accept + description FW56496_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW56496_1 + } + port 22 + } + protocol tcp_udp + } + rule 3343 { + action accept + description FW56496_1-TCP-ALLOW-157.231.178.162 + destination { + group { + address-group DT_FW56496_1 + } + port 21 + } + protocol tcp + source { + address 157.231.178.162 + } + } + rule 3344 { + action accept + description FW56496_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW56496_1 + } + port 2443,1022 + } + protocol tcp + } + rule 3345 { + action accept + description FW56496_1-TCP_UDP-ALLOW-46.16.211.142 + destination { + group { + address-group DT_FW56496_1 + } + port 3389,21 + } + protocol tcp_udp + source { + address 46.16.211.142 + } + } + rule 3347 { + action accept + description FW2379F_14-GRE-ALLOW-ANY + destination { + group { + address-group DT_FW2379F_14 + } + } + protocol gre + } + rule 3348 { + action accept + description FW0E383_9-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0E383_9 + } + port 52000 + } + protocol tcp + } + rule 3350 { + action accept + description FWB4438_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWB4438_2 + } + port 993-995,7 + } + protocol tcp + } + rule 3351 { + action accept + description FW1F3D0_6-TCP_UDP-ALLOW-82.165.207.109 + destination { + group { + address-group DT_FW1F3D0_6 + } + port 4567-4568 + } + protocol tcp_udp + source { + address 82.165.207.109 + } + } + rule 3352 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.77 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.210.77 + } + } + rule 3358 { + action accept + description FW46F4A_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW46F4A_1 + } + port 51820 + } + protocol udp + } + rule 3359 { + action accept + description FW53C72_1-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW53C72_1 + } + port 48402 + } + protocol udp + } + rule 3360 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.251 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.210.251 + } + } + rule 3362 { + action accept + description FWAA38E_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWAA38E_1 + } + port 1001-65535 + } + protocol tcp_udp + } + rule 3363 { + action accept + description FW138F8_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FW138F8_1 + } + port 21,20 + } + protocol tcp_udp + } + rule 3364 { + action accept + description FW0BD92_3-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW0BD92_3 + } + port 18081,18080 + } + protocol tcp + } + rule 3365 { + action accept + description FWFEF05_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWFEF05_1 + } + port 1935 + } + protocol tcp_udp + } + rule 3367 { + action accept + description FW26846_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW26846_1 + } + port 8000 + } + protocol tcp + } + rule 3368 { + action accept + description FWB4438_2-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWB4438_2 + } + port 53 + } + protocol tcp_udp + } + rule 3369 { + action accept + description FWA884B_5-TCP-ALLOW-51.146.16.162 + destination { + group { + address-group DT_FWA884B_5 + } + port 8447,8443,22 + } + protocol tcp + source { + address 51.146.16.162 + } + } + rule 3370 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.22 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.209.22 + } + } + rule 3371 { + action accept + description FWFDE34_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWFDE34_1 + } + port 18081,18080 + } + protocol tcp + } + rule 3373 { + action accept + description FWB6101_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWB6101_1 + } + port 2280 + } + protocol tcp + } + rule 3377 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-123.231.84.203 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 123.231.84.203 + } + } + rule 3378 { + action accept + description FW1D511_2-TCP-ALLOW-92.29.46.47 + destination { + group { + address-group DT_FW1D511_2 + } + port 9090 + } + protocol tcp + source { + address 92.29.46.47 + } + } + rule 3386 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.208.175 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.208.175 + } + } + rule 3387 { + action accept + description FW1ACD9_2-TCP-ALLOW-89.197.148.38 + destination { + group { + address-group DT_FW1ACD9_2 + } + port 5015,22 + } + protocol tcp + source { + address 89.197.148.38 + } + } + rule 3388 { + action accept + description FW1ACD9_2-UDP-ALLOW-ANY + destination { + group { + address-group DT_FW1ACD9_2 + } + port 9000-10999,5090,5060 + } + protocol udp + } + rule 3389 { + action accept + description FW1ACD9_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW1ACD9_2 + } + port 5090,5060-5062 + } + protocol tcp + } + rule 3391 { + action accept + description FWA0B7F_1-TCP_UDP-ALLOW-ANY + destination { + group { + address-group DT_FWA0B7F_1 + } + port 53 + } + protocol tcp_udp + } + rule 3392 { + action accept + description FW56335_2-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW56335_2 + } + port 18081,18080 + } + protocol tcp + } + rule 3395 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.90 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.212.90 + } + } + rule 3396 { + action accept + description FW4D3E6_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW4D3E6_1 + } + port 18081,18080 + } + protocol tcp + } + rule 3397 { + action accept + description FWB118A_1-TCP-ALLOW-188.65.177.58 + destination { + group { + address-group DT_FWB118A_1 + } + port 49152-65534,8447,8443,22,21,20 + } + protocol tcp + source { + address 188.65.177.58 + } + } + rule 3398 { + action accept + description FWB118A_1-TCP-ALLOW-77.68.103.13 + destination { + group { + address-group DT_FWB118A_1 + } + port 49152-65534,8447,8443,22,21,20 + } + protocol tcp + source { + address 77.68.103.13 + } + } + rule 3399 { + action accept + description FWB118A_1-TCP-ALLOW-80.5.71.130 + destination { + group { + address-group DT_FWB118A_1 + } + port 49152-65534,8447,8443,22,21,20 + } + protocol tcp + source { + address 80.5.71.130 + } + } + rule 3402 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.205 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.213.205 + } + } + rule 3408 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.211.31 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.211.31 + } + } + rule 3409 { + action accept + description FW539FB_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FW539FB_1 + } + port 389 + } + protocol tcp + } + rule 3411 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.185 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.213.185 + } + } + rule 3415 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-116.206.245.124 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 116.206.245.124 + } + } + rule 3416 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.213.75 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.213.75 + } + } + rule 3417 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.34 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.214.34 + } + } + rule 3418 { + action accept + description FWEF92E_5-UDP-ALLOW-77.68.77.70 + destination { + group { + address-group DT_FWEF92E_5 + } + port 500 + } + protocol udp + source { + address 77.68.77.70 + } + } + rule 3419 { + action accept + description FWEF92E_5-UDP-ALLOW-77.68.92.33 + destination { + group { + address-group DT_FWEF92E_5 + } + port 500 + } + protocol udp + source { + address 77.68.92.33 + } + } + rule 3420 { + action accept + description FWEF92E_5-UDP-ALLOW-77.68.93.82 + destination { + group { + address-group DT_FWEF92E_5 + } + port 500 + } + protocol udp + source { + address 77.68.93.82 + } + } + rule 3421 { + action accept + description FWEF92E_5-UDP-ALLOW-88.208.198.93 + destination { + group { + address-group DT_FWEF92E_5 + } + port 500 + } + protocol udp + source { + address 88.208.198.93 + } + } + rule 3422 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.94 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.214.94 + } + } + rule 3424 { + action accept + description FW18E6E_3-TCP-ALLOW-148.253.173.244 + destination { + group { + address-group DT_FW18E6E_3 + } + port 3306 + } + protocol tcp + source { + address 148.253.173.244 + } + } + rule 3425 { + action accept + description FW18E6E_3-TCP-ALLOW-148.253.173.246 + destination { + group { + address-group DT_FW18E6E_3 + } + port 3306 + } + protocol tcp + source { + address 148.253.173.246 + } + } + rule 3426 { + action accept + description FW18E6E_3-TCP-ALLOW-195.97.222.122 + destination { + group { + address-group DT_FW18E6E_3 + } + port 3306 + } + protocol tcp + source { + address 195.97.222.122 + } + } + rule 3431 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.111 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.209.111 + } + } + rule 3432 { + action accept + description FW06940_3-TCP_UDP-ALLOW-74.208.41.119 + destination { + group { + address-group DT_FW06940_3 + } + port 1-65535 + } + protocol tcp_udp + source { + address 74.208.41.119 + } + } + rule 3438 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.252 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.209.252 + } + } + rule 3440 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.118 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.214.118 + } + } + rule 3442 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.209.15 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.209.15 + } + } + rule 3446 { + action accept + description FWC32BE_1-ANY-ALLOW-3.65.3.75 + destination { + group { + address-group DT_FWC32BE_1 + } + } + source { + address 3.65.3.75 + } + } + rule 3447 { + action accept + description FWC32BE_1-TCP-ALLOW-217.155.2.52 + destination { + group { + address-group DT_FWC32BE_1 + } + port 22 + } + protocol tcp + source { + address 217.155.2.52 + } + } + rule 3448 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.243 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.214.243 + } + } + rule 3449 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.214.117 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000,3389 + } + protocol tcp_udp + source { + address 112.134.214.117 + } + } + rule 3450 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.4 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.210.4 + } + } + rule 3452 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.210.177 + destination { + group { + address-group DT_FWA7A50_1 + } + port 9000 + } + protocol tcp_udp + source { + address 112.134.210.177 + } + } + rule 3454 { + action accept + description FWD498E_1-TCP-ALLOW-ANY + destination { + group { + address-group DT_FWD498E_1 + } + port 44158 + } + protocol tcp + } + rule 3455 { + action accept + description FWA7A50_1-TCP_UDP-ALLOW-112.134.212.147 + destination { + group { + address-group DT_FWA7A50_1 + } + port 3389 + } + protocol tcp_udp + source { + address 112.134.212.147 + } + } + } + receive-redirects disable + send-redirects disable + source-validation disable + state-policy { + established { + action accept + } + invalid { + action drop + } + related { + action accept + } + } + syn-cookies enable + twa-hazards-protection disable +} +high-availability { + vrrp { + group eth3-90 { + advertise-interval 3 + authentication { + password Ng-1p90 + type plaintext-password + } + interface eth3 + preempt-delay 30 + priority 10 + virtual-address 10.255.255.1/32 + virtual-address 169.254.169.254/32 + vrid 90 + } + sync-group VRRP-GROUP { + member eth3-90 + } + } +} +interfaces { + ethernet eth0 { + address 10.4.35.105/24 + description Management + duplex auto + smp-affinity auto + speed auto + } + ethernet eth1 { + description MicroVLANs + duplex auto + smp-affinity auto + speed auto + vif 3201 { + address 109.228.63.251/25 + description "MicroVLAN publica" + firewall { + in { + name WAN-INBOUND + } + local { + name LOCAL-WAN + } + } + } + } + ethernet eth2 { + address 10.4.51.133/30 + description Sync + duplex auto + firewall { + local { + name LOCAL-SYNC + } + } + smp-affinity auto + speed auto + } + ethernet eth3 { + address 10.255.255.2/20 + description "Customers LAN" + duplex auto + firewall { + in { + name LAN-INBOUND + } + local { + name LOCAL-LAN + } + } + smp-affinity auto + speed auto + } + loopback lo { + address 10.4.35.105/32 + } +} +nat { + destination { + rule 5 { + description cloud-init + destination { + address 169.254.169.254 + port http + } + inbound-interface eth3 + protocol tcp + translation { + address 82.223.45.35 + } + } + rule 20 { + description "TEMPORARY NAT for dnscache removal in favor of anycns" + destination { + address 77.68.76.12 + port domain + } + inbound-interface eth3 + protocol tcp_udp + translation { + address 212.227.123.16 + } + } + rule 25 { + description "TEMPORARY NAT for dnscache removal in favor of anycns" + destination { + address 77.68.77.12 + port domain + } + inbound-interface eth3 + protocol tcp_udp + translation { + address 212.227.123.17 + } + } + } +} +policy { + community-list 100 { + rule 10 { + action permit + regex 65500:1001 + } + } + community-list 200 { + rule 10 { + action permit + regex "65500:10**" + } + } + prefix-list Service-NETs { + rule 1 { + action permit + ge 32 + prefix 0.0.0.0/0 + } + } + route-map Any-Site-1 { + rule 10 { + action permit + match { + community { + community-list 200 + } + } + } + rule 20 { + action deny + } + } + route-map CLOUD-Service-NETs { + rule 10 { + action permit + match { + ip { + address { + prefix-list Service-NETs + } + } + } + set { + community 65500:1027 + } + } + rule 20 { + action deny + } + } + route-map None { + rule 10 { + action deny + } + } +} +protocols { + bgp 8560 { + address-family { + ipv4-unicast { + redistribute { + static { + } + } + } + } + neighbor 109.228.63.134 { + address-family { + ipv4-unicast { + route-map { + export CLOUD-Service-NETs + import Any-Site-1 + } + weight 150 + } + } + description RouteServer1-vyos + password VyOS123 + remote-as 8560 + timers { + holdtime 5 + keepalive 1 + } + } + neighbor 109.228.63.135 { + address-family { + ipv4-unicast { + route-map { + export CLOUD-Service-NETs + import Any-Site-1 + } + weight 125 + } + } + description RouteServer2-quagga + password VyOS123 + remote-as 8560 + } + neighbor 109.228.63.136 { + address-family { + ipv4-unicast { + route-map { + export CLOUD-Service-NETs + import Any-Site-1 + } + weight 100 + } + } + description RouteServer3-bird + password VyOS123 + remote-as 8560 + } + parameters { + log-neighbor-changes + router-id 10.4.35.105 + } + } + static { + interface-route 77.68.2.215/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.3.52/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.3.61/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.3.80/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.3.121/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.3.144/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.3.161/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.3.194/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.3.247/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.4.22/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.4.24/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.4.25/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.4.39/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.4.57/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.4.74/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.4.80/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.4.111/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.4.136/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.4.180/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.4.242/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.4.252/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.5.95/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.5.125/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.5.155/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.5.166/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.5.187/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.5.241/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.6.32/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.6.105/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.6.110/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.6.119/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.6.202/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.6.210/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.7.67/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.7.114/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.7.123/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.7.160/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.7.172/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.7.186/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.7.222/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.7.227/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.8.144/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.9.75/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.9.186/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.10.142/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.10.152/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.10.170/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.11.140/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.12.45/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.12.195/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.12.250/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.13.76/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.13.137/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.14.88/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.15.95/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.16.247/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.17.26/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.17.186/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.17.200/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.20.161/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.20.217/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.20.231/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.21.78/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.21.171/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.22.146/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.23.35/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.23.64/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.23.112/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.23.158/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.24.59/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.24.63/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.24.112/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.24.134/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.24.172/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.24.220/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.25.124/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.25.130/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.25.146/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.26.166/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.26.216/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.26.221/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.26.228/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.27.18/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.27.27/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.27.28/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.27.54/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.27.57/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.27.211/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.28.139/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.28.145/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.28.147/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.28.207/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.29.65/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.29.178/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.30.133/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.30.164/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.31.96/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.31.144/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.32.31/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.32.43/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.32.83/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.32.86/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.32.89/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.32.118/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.32.254/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.33.24/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.33.37/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.33.48/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.33.68/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.33.171/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.33.197/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.33.216/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.34.26/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.34.28/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.34.50/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.34.138/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.34.139/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.35.116/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.48.14/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.48.81/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.48.89/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.48.105/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.48.202/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.49.4/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.49.12/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.49.126/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.49.152/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.49.159/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.49.160/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.49.161/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.49.178/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.50.90/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.50.91/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.50.142/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.50.193/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.50.198/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.51.202/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.51.214/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.72.202/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.72.254/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.73.73/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.74.39/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.74.85/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.74.152/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.74.209/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.74.232/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.75.45/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.75.64/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.75.113/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.75.245/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.75.253/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.12/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.13/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.14/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.16/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.19/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.20/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.21/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.22/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.23/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.25/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.26/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.29/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.30/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.31/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.33/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.35/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.37/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.38/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.39/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.40/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.42/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.44/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.45/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.47/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.48/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.49/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.50/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.54/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.55/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.57/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.58/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.59/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.60/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.61/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.74/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.75/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.76/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.77/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.80/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.88/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.91/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.92/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.93/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.94/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.95/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.96/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.99/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.102/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.104/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.105/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.107/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.108/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.110/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.111/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.112/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.114/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.115/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.116/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.118/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.120/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.122/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.123/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.124/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.126/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.127/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.136/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.137/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.138/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.139/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.141/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.142/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.145/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.148/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.149/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.150/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.152/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.157/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.158/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.160/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.161/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.164/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.165/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.169/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.171/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.176/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.177/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.181/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.183/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.185/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.187/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.191/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.195/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.197/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.198/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.200/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.202/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.203/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.208/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.209/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.211/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.212/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.217/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.219/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.220/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.228/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.229/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.231/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.234/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.235/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.239/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.241/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.243/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.244/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.245/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.247/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.248/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.249/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.250/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.251/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.252/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.253/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.76.254/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.12/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.13/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.14/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.16/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.19/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.21/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.22/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.24/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.26/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.29/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.30/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.32/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.33/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.37/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.38/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.42/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.43/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.44/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.46/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.49/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.50/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.53/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.54/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.56/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.57/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.59/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.62/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.63/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.65/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.67/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.68/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.69/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.70/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.71/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.72/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.74/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.75/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.76/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.77/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.79/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.81/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.85/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.88/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.90/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.92/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.95/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.97/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.99/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.100/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.102/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.103/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.105/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.107/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.108/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.114/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.115/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.117/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.120/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.124/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.128/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.129/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.130/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.132/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.137/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.139/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.140/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.141/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.144/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.145/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.149/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.150/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.151/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.152/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.156/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.157/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.159/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.160/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.161/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.163/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.165/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.171/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.174/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.176/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.178/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.181/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.185/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.190/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.192/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.199/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.200/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.201/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.202/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.203/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.204/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.205/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.207/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.208/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.209/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.211/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.212/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.214/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.215/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.219/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.221/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.222/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.227/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.228/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.231/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.233/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.234/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.236/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.238/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.239/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.240/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.243/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.247/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.248/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.249/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.251/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.253/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.77.254/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.78.73/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.78.113/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.78.229/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.79.82/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.79.89/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.79.206/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.80.26/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.80.97/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.81.44/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.81.141/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.81.218/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.82.147/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.82.157/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.83.41/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.84.147/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.84.155/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.85.18/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.85.27/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.85.73/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.85.115/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.85.172/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.86.40/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.86.148/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.87.164/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.87.212/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.88.100/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.88.164/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.89.72/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.89.183/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.89.247/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.90.106/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.90.132/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.91.22/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.91.128/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.91.195/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.92.92/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.92.186/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.93.125/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.93.164/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.93.246/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.94.181/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.95.42/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.95.212/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.100.77/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.100.132/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.100.134/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.100.150/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.100.167/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.101.64/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.101.124/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.101.125/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.102.5/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.102.129/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.103.19/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.103.56/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.103.120/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.103.147/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.103.227/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.112.75/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.112.83/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.112.90/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.112.91/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.112.167/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.112.175/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.112.184/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.112.213/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.112.248/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.113.117/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.113.164/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.114.93/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.114.136/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.114.183/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.114.205/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.114.234/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.114.237/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.115.17/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.115.142/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.116.36/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.116.52/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.116.84/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.116.119/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.116.183/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.116.220/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.116.221/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.116.232/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.117.29/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.117.45/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.117.51/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.117.142/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.117.173/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.117.202/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.117.214/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.117.222/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.118.15/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.118.17/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.118.86/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.118.88/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.118.102/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.118.104/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.118.120/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.119.14/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.119.92/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.119.188/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.120.26/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.120.31/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.120.45/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.120.146/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.120.218/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.120.229/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.120.241/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.120.249/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.121.94/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.121.106/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.121.119/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.121.127/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.122.89/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.122.195/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.122.241/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.123.177/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.123.250/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.125.32/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.125.60/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.125.218/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.126.14/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.126.22/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.126.51/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.126.101/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.126.160/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.127.151/32 { + next-hop-interface eth3 { + } + } + interface-route 77.68.127.172/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.196.91/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.196.92/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.196.123/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.196.154/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.197.10/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.197.23/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.197.60/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.197.118/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.197.129/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.197.135/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.197.150/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.197.155/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.197.160/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.197.208/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.198.39/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.198.64/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.198.66/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.198.69/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.198.92/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.198.251/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.199.46/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.199.141/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.199.233/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.199.249/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.212.31/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.212.94/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.212.182/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.212.188/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.215.19/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.215.61/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.215.62/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.215.121/32 { + next-hop-interface eth3 { + } + } + interface-route 88.208.215.157/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.35.84/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.35.110/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.36.37/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.36.79/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.36.119/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.36.174/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.36.194/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.36.229/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.37.10/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.37.114/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.37.174/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.37.187/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.37.240/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.38.117/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.38.171/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.38.201/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.39.41/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.39.151/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.39.157/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.39.249/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.40.194/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.40.195/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.40.207/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.40.222/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.40.226/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.40.247/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.42.232/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.46.81/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.46.196/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.47.223/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.48.249/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.52.186/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.53.243/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.55.82/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.56.26/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.56.97/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.56.185/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.56.242/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.58.134/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.59.247/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.60.215/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.61.31/32 { + next-hop-interface eth3 { + } + } + interface-route 109.228.61.37/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.36.7/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.36.17/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.36.24/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.36.56/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.36.60/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.36.142/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.36.148/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.37.23/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.37.47/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.37.83/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.37.101/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.37.102/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.37.133/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.38.95/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.38.114/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.38.142/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.38.182/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.38.216/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.38.248/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.39.37/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.39.44/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.39.68/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.39.99/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.39.109/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.39.129/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.39.145/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.39.219/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.40.11/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.40.56/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.40.90/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.40.124/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.40.152/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.40.166/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.40.244/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.41.72/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.41.73/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.41.148/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.41.240/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.43.6/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.43.28/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.43.71/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.43.98/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.43.113/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.43.157/32 { + next-hop-interface eth3 { + } + } + interface-route 185.132.43.164/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.208.40/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.208.58/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.208.176/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.209.217/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.210.19/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.210.25/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.210.59/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.210.155/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.210.177/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.211.128/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.212.71/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.212.89/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.212.90/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.212.114/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.212.136/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.212.171/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.212.172/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.212.203/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.213.31/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.213.41/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.213.42/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.213.97/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.213.175/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.213.242/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.214.96/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.214.102/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.214.167/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.214.234/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.215.43/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.215.184/32 { + next-hop-interface eth3 { + } + } + interface-route 213.171.215.252/32 { + next-hop-interface eth3 { + } + } + route 0.0.0.0/0 { + next-hop 109.228.63.129 { + } + } + route 10.0.0.0/8 { + next-hop 10.4.35.1 { + } + } + route 10.7.197.0/24 { + next-hop 109.228.63.240 { + } + } + route 172.16.0.0/12 { + next-hop 10.4.35.1 { + } + } + route 192.168.0.0/16 { + next-hop 10.4.35.1 { + } + } + } +} +service { + lldp { + legacy-protocols { + cdp + } + snmp { + enable + } + } + snmp { + community 1Trpq25 { + authorization ro + } + contact network@arsys.es + description gb-glo-sg4ng1fw27-01 + listen-address 10.4.35.105 { + port 161 + } + location NGCS + trap-target 10.4.36.64 { + community 1Trpq25 + port 162 + } + trap-target 172.21.15.200 { + community 1Trpq25 + port 162 + } + } + ssh { + listen-address 10.4.35.105 + listen-address 10.4.51.133 + port 22 + } +} +system { + config-management { + commit-revisions 20 + } + conntrack { + expect-table-size 8192 + hash-size 262144 + modules { + sip { + disable + } + } + table-size 2097152 + timeout { + icmp 30 + other 120 + tcp { + close 10 + close-wait 60 + established 3600 + fin-wait 30 + last-ack 30 + syn-recv 5 + syn-sent 5 + time-wait 5 + } + udp { + other 10 + stream 10 + } + } + } + console { + device ttyS0 { + speed 115200 + } + } + host-name gb-glo-sg4ng1fw27-01 + ip { + arp { + table-size 2048 + } + } + ipv6 { + disable + } + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + } + } + name-server 10.4.36.16 + name-server 10.4.37.16 + ntp { + server glo-ntp1.por-ngcs.lan { + } + server glo-ntp2.por-ngcs.lan { + } + } + syslog { + global { + facility all { + level notice + } + facility protocols { + level info + } + } + host 10.4.36.23 { + facility all { + level all + } + facility protocols { + level info + } + facility user { + level err + } + } + user all { + facility all { + level emerg + } + } + } + time-zone Europe/Madrid +} + + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@10:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.6-S1 */ + diff --git a/smoketest/configs.no-load/pki-ipsec b/smoketest/configs.no-load/pki-ipsec new file mode 100644 index 0000000..6fc239d --- /dev/null +++ b/smoketest/configs.no-load/pki-ipsec @@ -0,0 +1,148 @@ +interfaces { + dummy dum0 { + address 172.20.0.1/30 + } + ethernet eth0 { + address 192.168.150.1/24 + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + } + } + ntp { + server time1.vyos.net { + } + server time2.vyos.net { + } + server time3.vyos.net { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} +vpn { + ipsec { + esp-group MyESPGroup { + proposal 1 { + encryption aes128 + hash sha1 + } + } + ike-group MyIKEGroup { + proposal 1 { + dh-group 2 + encryption aes128 + hash sha1 + } + } + ipsec-interfaces { + interface eth0 + } + site-to-site { + peer 192.168.150.2 { + authentication { + mode x509 + x509 { + ca-cert-file ovpn_test_ca.pem + cert-file ovpn_test_server.pem + key { + file ovpn_test_server.key + } + } + } + default-esp-group MyESPGroup + ike-group MyIKEGroup + local-address 192.168.150.1 + tunnel 0 { + local { + prefix 172.20.0.0/24 + } + remote { + prefix 172.21.0.0/24 + } + } + } + peer 192.168.150.3 { + authentication { + mode rsa + pre-shared-secret MYSECRETKEY + rsa-key-name peer2 + } + default-esp-group MyESPGroup + ike-group MyIKEGroup + local-address 192.168.150.1 + tunnel 0 { + local { + prefix 172.20.0.0/24 + } + remote { + prefix 172.22.0.0/24 + } + } + } + } + } + l2tp { + remote-access { + authentication { + local-users { + username alice { + password notsecure + } + } + mode local + } + client-ip-pool { + start 192.168.255.2 + stop 192.168.255.254 + } + ipsec-settings { + authentication { + mode x509 + x509 { + ca-cert-file /config/auth/ovpn_test_ca.pem + server-cert-file /config/auth/ovpn_test_server.pem + server-key-file /config/auth/ovpn_test_server.key + } + } + } + outside-address 192.168.150.1 + } + } + rsa-keys { + local-key { + file /config/auth/ovpn_test_server.key + } + rsa-key-name peer2 { + rsa-key 0sAwEAAbudt5WQZSW2plbixjpgx4yVN/WMHdYRIZhyypJWO4ujQ/UQS9j3oTBgV2+RLtQ0YQ7eocwIfkvJVUnnZVMyZ4asQMOarQgbQ5nFGliCcDOMtNXRxHlMsvmjLx4o6FWbGukwgoxsT2x915n0XMn4XJNNSIEQotxj2GWFhEfBSPHyOM++kODk0lkbE7mLeHMMFq02vQhoczzEPWxjUUoY3jywhmHMfb4PdAKLFyt9x40znmPCYh+NSMQmpBXtD3gjGtX62bgrqKuP3BJU44x1gLlv8rJAJ4SY74YKnFUZ8m5GSbnVapwPOrp65lJZFKOGs2XXjAp5leoR+wmSYyqbDJM= + } + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "bgp@1:broadcast-relay@1:cluster@1:config-management@1:conntrack@2:conntrack-sync@2:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@6:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:nat66@1:ntp@1:policy@1:pppoe-server@5:pptp@2:qos@1:quagga@9:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrf@2:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.4-rolling-202106290839 diff --git a/smoketest/configs.no-load/vrf-bgp b/smoketest/configs.no-load/vrf-bgp new file mode 100644 index 0000000..4ad372a --- /dev/null +++ b/smoketest/configs.no-load/vrf-bgp @@ -0,0 +1,166 @@ +interfaces { + ethernet eth0 { + address 192.0.2.1/24 + } + ethernet eth1 { + vrf black + } + ethernet eth2 { + vrf black + } +} +protocols { + ospf { + area 0 { + network 192.0.2.0/24 + } + interface eth0 { + authentication { + md5 { + key-id 10 { + md5-key ospfkey + } + } + } + } + log-adjacency-changes { + } + parameters { + abr-type cisco + router-id 1.2.3.4 + } + passive-interface default + passive-interface-exclude eth0 + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + nt + ntp { + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } + time-zone Europe/Berlin +} +vrf { + name black { + protocols { + bgp 65000 { + address-family { + ipv4-unicast { + network 10.0.150.0/23 { + } + } + ipv6-unicast { + network 2001:db8:200::/40 { + } + } + } + neighbor 10.0.151.222 { + disable-send-community { + extended + standard + } + address-family { + ipv4-unicast { + default-originate { + } + soft-reconfiguration { + inbound + } + } + } + capability { + dynamic + } + remote-as 65010 + } + neighbor 10.0.151.252 { + peer-group VYOSv4 + } + neighbor 10.0.151.254 { + peer-group VYOSv4 + } + neighbor 2001:db8:200:ffff::3 { + peer-group VYOSv6 + } + neighbor 2001:db8:200:ffff::a { + peer-group VYOSv6 + } + neighbor 2001:db8:200:ff::101:2 { + remote-as 65010 + } + parameters { + default { + no-ipv4-unicast + } + log-neighbor-changes + router-id 10.0.151.251 + } + peer-group VYOSv4 { + address-family { + ipv4-unicast { + nexthop-self { + } + } + } + capability { + dynamic + } + remote-as 65000 + update-source dum0 + } + peer-group VYOSv6 { + address-family { + ipv6-unicast { + nexthop-self { + } + } + } + capability { + dynamic + } + remote-as 65000 + update-source dum0 + } + } + + } + table 2000 + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@2:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@20:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:nat66@1:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@9:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@20:vrf@2:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.4-rolling-202103130218 diff --git a/smoketest/configs/basic-api-service b/smoketest/configs/basic-api-service new file mode 100644 index 0000000..d5364d3 --- /dev/null +++ b/smoketest/configs/basic-api-service @@ -0,0 +1,85 @@ +interfaces { + ethernet eth0 { + address 192.0.2.1/31 + address 2001:db8::1234/64 + } + loopback lo { + } +} +service { + https { + api { + keys { + id 1 { + key S3cur3 + } + } + socket + } + virtual-host bar { + allow-client { + address 172.16.0.0/12 + } + listen-port 5555 + server-name bar + } + virtual-host baz { + allow-client { + address 192.168.0.0/16 + } + listen-address "*" + listen-port 6666 + server-name baz + } + virtual-host foo { + allow-client { + address 10.0.0.0/8 + address 2001:db8::/32 + } + listen-port 7777 + server-name foo + } + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + } + } + ntp { + server time1.vyos.net { + } + server time2.vyos.net { + } + server time3.vyos.net { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@13:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@6:salt@1:snmp@2:ssh@2:sstp@3:system@19:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1" +// Release version: 1.3-rolling-202010241631 diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos new file mode 100644 index 0000000..e95d745 --- /dev/null +++ b/smoketest/configs/basic-vyos @@ -0,0 +1,240 @@ +interfaces { + ethernet eth0 { + address 192.168.0.1/24 + address fe88::1/56 + duplex auto + smp-affinity auto + speed auto + } + ethernet eth1 { + duplex auto + smp-affinity auto + speed auto + } + ethernet eth2 { + duplex auto + smp-affinity auto + speed auto + vif 100 { + address 100.100.0.1/24 + } + vif-s 200 { + address 100.64.200.254/24 + vif-c 201 { + address 100.64.201.254/24 + address fe89::1/56 + } + vif-c 202 { + address 100.64.202.254/24 + } + } + } + loopback lo { + } +} +protocols { + static { + arp 192.168.0.20 { + hwaddr 00:50:00:00:00:20 + } + arp 192.168.0.30 { + hwaddr 00:50:00:00:00:30 + } + arp 192.168.0.40 { + hwaddr 00:50:00:00:00:40 + } + arp 100.100.0.2 { + hwaddr 00:50:00:00:02:02 + } + arp 100.100.0.3 { + hwaddr 00:50:00:00:02:03 + } + arp 100.100.0.4 { + hwaddr 00:50:00:00:02:04 + } + arp 100.64.200.1 { + hwaddr 00:50:00:00:00:01 + } + arp 100.64.200.2 { + hwaddr 00:50:00:00:00:02 + } + arp 100.64.201.10 { + hwaddr 00:50:00:00:00:10 + } + arp 100.64.201.20 { + hwaddr 00:50:00:00:00:20 + } + arp 100.64.202.30 { + hwaddr 00:50:00:00:00:30 + } + arp 100.64.202.40 { + hwaddr 00:50:00:00:00:40 + } + route 0.0.0.0/0 { + next-hop 100.64.0.1 { + } + } + } +} +service { + dhcp-server { + shared-network-name LAN { + authoritative + subnet 192.168.0.0/24 { + default-router 192.168.0.1 + dns-server 192.168.0.1 + domain-name vyos.net + domain-search vyos.net + range LANDynamic { + start 192.168.0.30 + stop 192.168.0.240 + } + static-mapping TEST1-1 { + ip-address 192.168.0.11 + mac-address 00:01:02:03:04:05 + } + static-mapping TEST1-2 { + ip-address 192.168.0.12 + mac-address 00:01:02:03:04:05 + } + static-mapping TEST2-1 { + ip-address 192.168.0.21 + mac-address 00:01:02:03:04:21 + } + static-mapping TEST2-2 { + ip-address 192.168.0.21 + mac-address 00:01:02:03:04:22 + } + } + } + } + dhcpv6-server { + shared-network-name LAN6 { + subnet fe88::/56 { + address-range { + prefix fe88::/60 { + temporary + } + start fe88:0000:0000:fe:: { + stop fe88:0000:0000:ff:: + } + } + domain-search vyos.net + name-server fe88::1 + prefix-delegation { + start fe88:0000:0000:0001:: { + prefix-length 64 + stop fe88:0000:0000:0010:: + } + } + } + subnet fe89::/56 { + address-range { + prefix fe89::/60 { + temporary + } + start fe89:0000:0000:fe:: { + stop fe89:0000:0000:ff:: + } + } + domain-search vyos.net + name-server fe89::1 + prefix-delegation { + start fe89:0000:0000:0001:: { + prefix-length 64 + stop fe89:0000:0000:0010:: + } + } + } + } + } + dns { + forwarding { + allow-from 192.168.0.0/16 + cache-size 10000 + dnssec off + listen-address 192.168.0.1 + } + } + ssh { + ciphers aes128-ctr,aes192-ctr,aes256-ctr + ciphers chacha20-poly1305@openssh.com,rijndael-cbc@lysator.liu.se + listen-address 192.168.0.1 + key-exchange curve25519-sha256@libssh.org + key-exchange diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group-exchange-sha256 + port 22 + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + conntrack { + ignore { + rule 1 { + destination { + address 192.0.2.2 + } + source { + address 192.0.2.1 + } + } + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + name-server 192.168.0.1 + syslog { + console { + facility all { + level emerg + } + facility mail { + level info + } + } + global { + facility all { + level info + } + facility protocols { + level debug + } + facility security { + level info + } + preserve-fqdn + } + host syslog.vyos.net { + facility local7 { + level notice + } + facility protocols { + level alert + } + facility security { + level warning + } + format { + octet-counted + } + port 8000 + } + } + time-zone Europe/Berlin +} +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.6 */ diff --git a/smoketest/configs/bgp-azure-ipsec-gateway b/smoketest/configs/bgp-azure-ipsec-gateway new file mode 100644 index 0000000..5803e8c --- /dev/null +++ b/smoketest/configs/bgp-azure-ipsec-gateway @@ -0,0 +1,461 @@ +firewall { + all-ping enable + broadcast-ping disable + config-trap disable + ipv6-receive-redirects disable + ipv6-src-route disable + ip-src-route disable + log-martians disable + options { + interface vti31 { + adjust-mss 1350 + } + interface vti32 { + adjust-mss 1350 + } + interface vti41 { + adjust-mss 1350 + } + interface vti42 { + adjust-mss 1350 + } + interface vti51 { + adjust-mss 1350 + } + interface vti52 { + adjust-mss 1350 + } + } + receive-redirects disable + send-redirects enable + source-validation disable + syn-cookies enable + twa-hazards-protection disable +} +high-availability { + vrrp { + group DMZ-VLAN-3962 { + interface eth1 + preempt-delay 180 + priority 200 + virtual-address 192.168.34.36/27 + vrid 62 + } + } +} +interfaces { + ethernet eth0 { + address 192.0.2.189/27 + duplex auto + smp-affinity auto + speed auto + } + ethernet eth1 { + address 192.168.34.37/27 + duplex auto + smp-affinity auto + speed auto + } + loopback lo { + } + vti vti31 { + } + vti vti32 { + } + vti vti41 { + } + vti vti42 { + } + vti vti51 { + } + vti vti52 { + } +} +policy { + prefix-list AZURE-BGP-IPv4-in { + description "Prefixes received from Azure" + rule 100 { + action permit + le 32 + prefix 100.64.0.0/10 + } + } + prefix-list ONPREM-BGP-IPv4-out { + description "Prefixes allowed to be announced into Azure" + rule 100 { + action permit + prefix 10.0.0.0/8 + } + rule 200 { + action permit + prefix 172.16.0.0/12 + } + rule 300 { + action permit + prefix 192.168.0.0/16 + } + } +} +protocols { + bgp 65522 { + address-family { + ipv4-unicast { + network 10.0.0.0/8 { + } + network 172.16.0.0/12 { + } + network 192.168.0.0/16 { + } + } + } + neighbor 100.66.8.36 { + peer-group AZURE + remote-as 64517 + } + neighbor 100.66.8.37 { + peer-group AZURE + remote-as 64517 + } + neighbor 100.66.24.36 { + peer-group AZURE + remote-as 64513 + } + neighbor 100.66.24.37 { + peer-group AZURE + remote-as 64513 + } + neighbor 100.66.40.36 { + peer-group AZURE + remote-as 64515 + } + neighbor 100.66.40.37 { + peer-group AZURE + remote-as 64515 + } + neighbor 192.168.34.38 { + address-family { + ipv4-unicast { + nexthop-self + soft-reconfiguration { + inbound + } + } + } + capability { + dynamic + } + password VyOSR0xx123 + remote-as 65522 + update-source eth1 + } + peer-group AZURE { + address-family { + ipv4-unicast { + maximum-prefix 50 + prefix-list { + export ONPREM-BGP-IPv4-out + import AZURE-BGP-IPv4-in + } + } + } + ebgp-multihop 2 + update-source eth1 + } + timers { + holdtime 30 + keepalive 5 + } + } + static { + interface-route 100.66.8.36/32 { + next-hop-interface vti31 { + } + next-hop-interface vti32 { + } + } + interface-route 100.66.8.37/32 { + next-hop-interface vti31 { + } + next-hop-interface vti32 { + } + } + interface-route 100.66.24.36/32 { + next-hop-interface vti41 { + } + next-hop-interface vti42 { + } + } + interface-route 100.66.24.37/32 { + next-hop-interface vti41 { + } + next-hop-interface vti42 { + } + } + interface-route 100.66.40.36/32 { + next-hop-interface vti51 { + } + next-hop-interface vti52 { + } + } + interface-route 100.66.40.37/32 { + next-hop-interface vti51 { + } + next-hop-interface vti52 { + } + } + route 0.0.0.0/0 { + next-hop 192.168.34.33 { + } + } + route 51.105.0.0/16 { + next-hop 192.0.2.161 { + } + } + route 52.143.0.0/16 { + next-hop 192.0.2.161 { + } + } + route 195.137.175.0/24 { + next-hop 192.0.2.161 { + } + } + route 212.23.159.0/26 { + next-hop 192.0.2.161 { + } + } + } +} +service { + snmp { + v3 { + engineid 0xff42 + group default { + mode ro + seclevel priv + view default + } + user VyOS { + auth { + encrypted-key 0x1ad73f4620b8c0dd2de066622f875b161a14adad + type sha + } + group default + privacy { + encrypted-key 0x1ad73f4620b8c0dd2de066622f875b16 + type aes + } + } + view default { + oid 1 { + } + } + } + } + ssh { + disable-host-validation + port 22 + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.net + flow-accounting { + interface eth1 + interface vti31 + interface vti32 + interface vti41 + interface vti42 + interface vti51 + interface vti52 + netflow { + server 10.0.1.1 { + port 2055 + } + source-ip 192.168.34.37 + version 10 + } + syslog-facility daemon + } + host-name azure-gw-01 + login { + radius-server 192.0.2.253 { + port 1812 + secret secret1234 + timeout 2 + } + radius-server 192.0.2.254 { + port 1812 + secret secret1234 + timeout 2 + } + radius-source-address 192.168.34.37 + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + name-server 192.0.2.254 + ntp { + server 192.0.2.254 { + } + } + syslog { + global { + archive { + file 10 + size 20480 + } + facility all { + level info + } + facility protocols { + level debug + } + } + host 10.0.9.188 { + facility all { + level info + protocol udp + } + } + } + time-zone Europe/Berlin +} +vpn { + ipsec { + auto-update 120 + esp-group ESP-AZURE { + compression disable + lifetime 27000 + mode tunnel + pfs disable + proposal 1 { + encryption aes256 + hash sha1 + } + } + ike-group IKE-AZURE { + close-action none + dead-peer-detection { + action restart + interval 2 + timeout 15 + } + ikev2-reauth no + key-exchange ikev2 + lifetime 27000 + proposal 1 { + dh-group 2 + encryption aes256 + hash sha1 + } + } + ipsec-interfaces { + interface eth0 + } + logging { + log-level 2 + log-modes ike + } + site-to-site { + peer 51.105.0.1 { + authentication { + mode pre-shared-secret + pre-shared-secret averysecretpsktowardsazure + } + connection-type respond + default-esp-group ESP-AZURE + ike-group IKE-AZURE + ikev2-reauth inherit + local-address 192.0.2.189 + vti { + bind vti51 + } + } + peer 51.105.0.2 { + authentication { + mode pre-shared-secret + pre-shared-secret averysecretpsktowardsazure + } + connection-type respond + default-esp-group ESP-AZURE + ike-group IKE-AZURE + ikev2-reauth inherit + local-address 192.0.2.189 + vti { + bind vti52 + } + } + peer 51.105.0.3 { + authentication { + mode pre-shared-secret + pre-shared-secret averysecretpsktowardsazure + } + connection-type respond + ike-group IKE-AZURE + ikev2-reauth inherit + local-address 192.0.2.189 + vti { + bind vti32 + esp-group ESP-AZURE + } + } + peer 51.105.0.4 { + authentication { + mode pre-shared-secret + pre-shared-secret averysecretpsktowardsazure + } + connection-type respond + ike-group IKE-AZURE + ikev2-reauth inherit + local-address 192.0.2.189 + vti { + bind vti31 + esp-group ESP-AZURE + } + } + peer 51.105.0.5 { + authentication { + mode pre-shared-secret + pre-shared-secret averysecretpsktowardsazure + } + connection-type respond + ike-group IKE-AZURE + ikev2-reauth inherit + local-address 192.0.2.189 + vti { + bind vti42 + esp-group ESP-AZURE + } + } + peer 51.105.0.6 { + authentication { + mode pre-shared-secret + pre-shared-secret averysecretpsktowardsazure + } + connection-type respond + ike-group IKE-AZURE + ikev2-reauth inherit + local-address 192.0.2.189 + vti { + bind vti41 + esp-group ESP-AZURE + } + } + } + } +} + + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.5 */ diff --git a/smoketest/configs/bgp-bfd-communities b/smoketest/configs/bgp-bfd-communities new file mode 100644 index 0000000..3b3056a --- /dev/null +++ b/smoketest/configs/bgp-bfd-communities @@ -0,0 +1,533 @@ +interfaces { + ethernet eth0 { + address 192.0.2.100/25 + address 2001:db8::ffff/64 + } + loopback lo { + } +} +policy { + large-community-list ANYCAST_ALL { + rule 10 { + action permit + description "Allow all anycast from anywhere" + regex "4242420696:100:.*" + } + } + large-community-list ANYCAST_INT { + rule 10 { + action permit + description "Allow all anycast from int" + regex 4242420696:100:1 + } + } + prefix-list BGP-BACKBONE-IN { + description "Inbound backbone routes from other sites" + rule 10 { + action deny + description "Block default route" + prefix 0.0.0.0/0 + } + rule 20 { + action deny + description "Block int primary" + ge 21 + prefix 192.168.0.0/20 + } + rule 30 { + action deny + description "Block loopbacks" + ge 25 + prefix 192.168.253.0/24 + } + rule 40 { + action deny + description "Block backbone peering" + ge 25 + prefix 192.168.254.0/24 + } + rule 999 { + action permit + description "Allow everything else" + ge 1 + prefix 0.0.0.0/0 + } + } + prefix-list BGP-BACKBONE-OUT { + description "Outbound backbone routes to other sites" + rule 10 { + action permit + description "Int primary" + ge 23 + prefix 192.168.0.0/20 + } + } + prefix-list GLOBAL { + description "Globally redistributed routes" + rule 10 { + action permit + prefix 192.168.100.1/32 + } + rule 20 { + action permit + prefix 192.168.7.128/25 + } + } + prefix-list6 BGP-BACKBONE-IN-V6 { + description "Inbound backbone routes from other sites" + rule 10 { + action deny + description "Block default route" + prefix ::/0 + } + rule 20 { + action deny + description "Block int primary" + ge 53 + prefix fd52:d62e:8011::/52 + } + rule 30 { + action deny + description "Block peering and stuff" + ge 53 + prefix fd52:d62e:8011:f000::/52 + } + rule 999 { + action permit + description "Allow everything else" + ge 1 + prefix ::/0 + } + } + prefix-list6 BGP-BACKBONE-OUT-V6 { + description "Outbound backbone routes to other sites" + rule 10 { + action permit + ge 64 + prefix fd52:d62e:8011::/52 + } + } + prefix-list6 GLOBAL-V6 { + description "Globally redistributed routes" + rule 10 { + action permit + ge 64 + prefix fd52:d62e:8011:2::/63 + } + } + route-map BGP-REDISTRIBUTE { + rule 10 { + action permit + description "Prepend AS and allow VPN and modem" + match { + ip { + address { + prefix-list GLOBAL + } + } + } + set { + as-path-prepend 4242420666 + } + } + rule 20 { + action permit + description "Allow VPN" + match { + ipv6 { + address { + prefix-list GLOBAL-V6 + } + } + } + } + } + route-map BGP-BACKBONE-IN { + rule 10 { + action permit + match { + ip { + address { + prefix-list BGP-BACKBONE-IN + } + } + } + } + rule 20 { + action permit + match { + ipv6 { + address { + prefix-list BGP-BACKBONE-IN-V6 + } + } + } + } + rule 30 { + action permit + match { + large-community { + large-community-list ANYCAST_ALL + } + } + } + } + route-map BGP-BACKBONE-OUT { + rule 10 { + action permit + match { + ip { + address { + prefix-list BGP-BACKBONE-OUT + } + } + } + } + rule 20 { + action permit + match { + ipv6 { + address { + prefix-list BGP-BACKBONE-OUT-V6 + } + } + } + } + rule 30 { + action permit + match { + large-community { + large-community-list ANYCAST_INT + } + } + set { + as-path-prepend 4242420666 + } + } + } +} +protocols { + bfd { + peer 192.168.253.1 { + interval { + receive 50 + transmit 50 + } + multihop + source { + address 192.168.253.3 + } + } + peer 192.168.253.2 { + interval { + receive 50 + transmit 50 + } + multihop + source { + address 192.168.253.3 + } + } + peer 192.168.253.6 { + interval { + receive 50 + transmit 50 + } + multihop + source { + address 192.168.253.3 + } + } + peer 192.168.253.7 { + interval { + receive 50 + transmit 50 + } + multihop + source { + address 192.168.253.3 + } + } + peer 192.168.253.12 { + interval { + receive 100 + transmit 100 + } + multihop + source { + address 192.168.253.3 + } + } + peer fd52:d62e:8011:fffe:192:168:253:1 { + interval { + receive 50 + transmit 50 + } + multihop + source { + address fd52:d62e:8011:fffe:192:168:253:3 + } + } + peer fd52:d62e:8011:fffe:192:168:253:2 { + interval { + receive 50 + transmit 50 + } + multihop + source { + address fd52:d62e:8011:fffe:192:168:253:3 + } + } + peer fd52:d62e:8011:fffe:192:168:253:6 { + interval { + receive 50 + transmit 50 + } + multihop + source { + address fd52:d62e:8011:fffe:192:168:253:3 + } + } + peer fd52:d62e:8011:fffe:192:168:253:7 { + interval { + receive 50 + transmit 50 + } + multihop + source { + address fd52:d62e:8011:fffe:192:168:253:3 + } + } + peer fd52:d62e:8011:fffe:192:168:253:12 { + interval { + receive 100 + transmit 100 + } + multihop + source { + address fd52:d62e:8011:fffe:192:168:253:3 + } + } + } + bgp 4242420666 { + address-family { + ipv4-unicast { + redistribute { + connected { + route-map BGP-REDISTRIBUTE + } + static { + route-map BGP-REDISTRIBUTE + } + } + } + ipv6-unicast { + redistribute { + connected { + route-map BGP-REDISTRIBUTE + } + } + } + } + neighbor 192.168.253.1 { + peer-group INT + } + neighbor 192.168.253.2 { + peer-group INT + } + neighbor 192.168.253.6 { + peer-group DAL13 + } + neighbor 192.168.253.7 { + peer-group DAL13 + } + neighbor 192.168.253.12 { + address-family { + ipv4-unicast { + route-map { + export BGP-BACKBONE-OUT + import BGP-BACKBONE-IN + } + soft-reconfiguration { + inbound + } + } + } + bfd { + } + ebgp-multihop 2 + remote-as 4242420669 + update-source dum0 + } + neighbor fd52:d62e:8011:fffe:192:168:253:1 { + address-family { + ipv6-unicast { + peer-group INTv6 + } + } + } + neighbor fd52:d62e:8011:fffe:192:168:253:2 { + address-family { + ipv6-unicast { + peer-group INTv6 + } + } + } + neighbor fd52:d62e:8011:fffe:192:168:253:6 { + address-family { + ipv6-unicast { + peer-group DAL13v6 + } + } + } + neighbor fd52:d62e:8011:fffe:192:168:253:7 { + address-family { + ipv6-unicast { + peer-group DAL13v6 + } + } + } + neighbor fd52:d62e:8011:fffe:192:168:253:12 { + address-family { + ipv6-unicast { + route-map { + export BGP-BACKBONE-OUT + import BGP-BACKBONE-IN + } + soft-reconfiguration { + inbound + } + } + } + bfd { + } + ebgp-multihop 2 + remote-as 4242420669 + update-source dum0 + } + parameters { + confederation { + identifier 4242420696 + peers 4242420668 + peers 4242420669 + } + default { + no-ipv4-unicast + } + distance { + global { + external 220 + internal 220 + local 220 + } + } + graceful-restart { + } + } + peer-group DAL13 { + address-family { + ipv4-unicast { + route-map { + export BGP-BACKBONE-OUT + import BGP-BACKBONE-IN + } + soft-reconfiguration { + inbound + } + } + } + bfd + ebgp-multihop 2 + remote-as 4242420668 + update-source dum0 + } + peer-group DAL13v6 { + address-family { + ipv6-unicast { + route-map { + export BGP-BACKBONE-OUT + import BGP-BACKBONE-IN + } + soft-reconfiguration { + inbound + } + } + } + bfd + ebgp-multihop 2 + remote-as 4242420668 + update-source dum0 + } + peer-group INT { + address-family { + ipv4-unicast { + default-originate { + } + soft-reconfiguration { + inbound + } + } + } + bfd + remote-as 4242420666 + update-source dum0 + } + peer-group INTv6 { + address-family { + ipv6-unicast { + default-originate { + } + soft-reconfiguration { + inbound + } + } + } + bfd + remote-as 4242420666 + update-source dum0 + } + } +} +system { + config-management { + commit-revisions 200 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + level admin + } + } + ntp { + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } + time-zone Europe/Berlin +} + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@10:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.6-S1 */ diff --git a/smoketest/configs/bgp-big-as-cloud b/smoketest/configs/bgp-big-as-cloud new file mode 100644 index 0000000..6581925 --- /dev/null +++ b/smoketest/configs/bgp-big-as-cloud @@ -0,0 +1,1966 @@ +firewall { + all-ping enable + broadcast-ping disable + config-trap disable + group { + address-group bgp-peers-4 { + address 192.0.68.3 + address 192.0.68.2 + address 192.0.176.193 + address 192.0.52.0-192.0.52.255 + address 192.0.53.0-192.0.53.255 + address 192.0.16.209 + address 192.0.192.0-192.0.192.255 + address 192.0.193.0-192.0.193.255 + address 192.0.194.0-192.0.194.255 + address 192.0.195.0-192.0.195.255 + address 192.0.196.0-192.0.196.255 + address 192.0.197.0-192.0.197.255 + address 192.0.198.0-192.0.198.255 + address 192.0.199.0-192.0.199.255 + } + address-group vrrp-peers-4 { + address 192.0.68.3 + address 192.0.160.3 + address 192.0.98.3 + address 192.0.71.131 + address 192.0.84.67 + address 192.0.71.195 + address 192.0.71.115 + address 192.0.70.195 + address 192.0.70.179 + address 192.0.70.163 + address 192.0.70.147 + address 192.0.70.131 + address 192.0.70.19 + address 192.0.70.3 + address 192.0.71.99 + address 192.0.68.67 + address 192.0.71.67 + address 192.0.71.3 + address 192.0.68.35 + address 192.0.68.131 + address 192.0.69.2 + address 192.0.70.35 + address 192.0.70.67 + } + ipv6-address-group bgp-peers-6 { + address 2001:db8:c::3 + address 2001:db8:1000::2e9 + address 2001:db8:24::fb + address 2001:db8:24::fc + address 2001:db8:24::fd + address 2001:db8:24::2e + address 2001:db8:24::3d + address 2001:db8:24::4a + address 2001:db8:24::5e + address 2001:db8:24::7 + address 2001:db8:24::11 + address 2001:db8:24::18 + address 2001:db8:24::20 + address 2001:db8:24::22 + address 2001:db8:24::31 + address 2001:db8:24::58 + address 2001:db8:24::64 + address 2001:db8:24::a5 + address 2001:db8:24::aa + address 2001:db8:24::ab + address 2001:db8:24::b0 + address 2001:db8:24::b3 + address 2001:db8:24::bd + address 2001:db8:24::c + address 2001:db8:24::d2 + address 2001:db8:24::d3 + address 2001:db8:838::1 + address 2001:db8::1a27:5051:c09d + address 2001:db8::1a27:5051:c19d + address 2001:db8::20ad:0:1 + address 2001:db8::2306:0:1 + address 2001:db8::2ca:0:1 + address 2001:db8::2ca:0:2 + address 2001:db8::2ca:0:3 + address 2001:db8::2ca:0:4 + } + ipv6-address-group vrrp-peers-6 { + address fe80::fe89:15cf + } + ipv6-network-group AS64512-6 { + network 2001::/29 + } + network-group AS64512-4 { + network 192.0.68.0/22 + network 192.0.98.0/24 + network 192.0.160.0/24 + network 192.0.84.0/22 + } + } + ipv6-name management-to-local-6 { + default-action reject + enable-default-log + } + ipv6-name management-to-peers-6 { + default-action reject + enable-default-log + } + ipv6-name management-to-servers-6 { + default-action reject + enable-default-log + } + ipv6-name peers-to-local-6 { + default-action reject + enable-default-log + rule 500 { + action accept + protocol icmpv6 + } + rule 501 { + action accept + protocol vrrp + source { + group { + address-group vrrp-peers-6 + } + } + } + rule 502 { + action accept + destination { + port bgp + } + protocol tcp + source { + group { + address-group bgp-peers-6 + } + } + } + rule 503 { + action accept + protocol tcp + source { + group { + address-group bgp-peers-6 + } + port bgp + } + } + } + ipv6-name peers-to-management-6 { + default-action reject + enable-default-log + } + ipv6-name peers-to-servers-6 { + default-action reject + enable-default-log + rule 9990 { + action reject + source { + group { + network-group AS64512-6 + } + } + } + rule 9999 { + action accept + destination { + group { + network-group AS64512-6 + } + } + } + } + ipv6-name servers-to-local-6 { + default-action reject + enable-default-log + rule 500 { + action accept + protocol icmpv6 + } + rule 501 { + action accept + protocol vrrp + source { + group { + address-group vrrp-peers-6 + } + } + } + rule 511 { + action accept + protocol tcp_udp + source { + port 53 + } + } + } + ipv6-name servers-to-management-6 { + default-action reject + enable-default-log + } + ipv6-name servers-to-peers-6 { + default-action reject + enable-default-log + rule 51 { + action accept + source { + group { + network-group AS64512-6 + } + } + } + } + ipv6-receive-redirects disable + ipv6-src-route disable + ip-src-route disable + log-martians enable + name management-to-local-4 { + default-action reject + enable-default-log + rule 500 { + action accept + protocol icmp + } + rule 501 { + action accept + destination { + port 22 + } + protocol tcp + } + rule 502 { + action accept + destination { + port snmp + } + protocol udp + } + } + name management-to-peers-4 { + default-action reject + enable-default-log + } + name management-to-servers-4 { + default-action reject + enable-default-log + } + name peers-to-local-4 { + default-action reject + enable-default-log + rule 500 { + action accept + protocol icmp + } + rule 501 { + action accept + protocol vrrp + source { + group { + address-group vrrp-peers-4 + } + } + } + rule 502 { + action accept + destination { + port bgp + } + protocol tcp + source { + group { + address-group bgp-peers-4 + } + } + } + rule 503 { + action accept + protocol tcp + source { + group { + address-group bgp-peers-4 + } + port bgp + } + } + } + name peers-to-management-4 { + default-action reject + enable-default-log + } + name peers-to-servers-4 { + default-action reject + enable-default-log + rule 9990 { + action reject + source { + group { + network-group AS64512-4 + } + } + } + rule 9999 { + action accept + destination { + group { + network-group AS64512-4 + } + } + } + } + name servers-to-local-4 { + default-action reject + enable-default-log + rule 500 { + action accept + protocol icmp + } + rule 501 { + action accept + protocol vrrp + source { + group { + address-group vrrp-peers-4 + } + } + } + rule 511 { + action accept + protocol tcp_udp + source { + port 53 + } + } + } + name servers-to-management-4 { + default-action reject + enable-default-log + } + name servers-to-peers-4 { + default-action reject + enable-default-log + rule 51 { + action accept + source { + group { + network-group AS64512-4 + } + } + } + } + receive-redirects disable + send-redirects enable + source-validation disable + syn-cookies enable + twa-hazards-protection disable +} +high-availability { + vrrp { + group 11-4 { + interface eth0.11 + priority 200 + virtual-address 192.0.68.1/27 + vrid 4 + } + group 11-6 { + interface eth0.11 + priority 200 + virtual-address 2001:db8:c::1/64 + vrid 6 + } + group 102-4 { + interface eth0.102 + priority 200 + virtual-address 192.0.98.1/24 + vrid 4 + } + group 102-6 { + interface eth0.102 + priority 200 + virtual-address 2001:db8:0:102::1/64 + vrid 6 + } + group 105-4 { + interface eth0.105 + priority 200 + virtual-address 192.0.160.1/24 + vrid 4 + } + group 105-6 { + interface eth0.105 + priority 200 + virtual-address 2001:db8:0:105::1/64 + vrid 6 + } + group 1001-4 { + interface eth0.1001 + priority 200 + virtual-address 192.0.68.33/27 + vrid 4 + } + group 1001-6 { + interface eth0.1001 + priority 200 + virtual-address 2001:db8:0:1001::1/64 + vrid 6 + } + group 1002-4 { + interface eth0.1002 + priority 200 + virtual-address 192.0.68.65/26 + vrid 4 + } + group 1002-6 { + interface eth0.1002 + priority 200 + virtual-address 2001:db8:0:1002::1/64 + vrid 6 + } + group 1003-4 { + interface eth0.1003 + priority 200 + virtual-address 192.0.68.129/25 + vrid 4 + } + group 1003-6 { + interface eth0.1003 + priority 200 + virtual-address 2001:db8:0:1003::1/64 + vrid 6 + } + group 1004-4 { + interface eth0.1004 + priority 200 + virtual-address 192.0.69.1/24 + vrid 4 + } + group 1004-6 { + interface eth0.1004 + priority 200 + virtual-address 2001:db8:0:1004::1/64 + vrid 6 + } + group 1005-4 { + interface eth0.1005 + priority 200 + virtual-address 192.0.70.1/28 + vrid 4 + } + group 1005-6 { + interface eth0.1005 + priority 200 + virtual-address 2001:db8:0:1005::1/64 + vrid 6 + } + group 1006-4 { + interface eth0.1006 + priority 200 + virtual-address 192.0.70.17/28 + vrid 4 + } + group 1006-6 { + interface eth0.1006 + priority 200 + virtual-address 2001:db8:0:1006::1/64 + vrid 6 + } + group 1007-4 { + interface eth0.1007 + priority 200 + virtual-address 192.0.70.33/27 + vrid 4 + } + group 1007-6 { + interface eth0.1007 + priority 200 + virtual-address 2001:db8:0:1007::1/64 + vrid 6 + } + group 1008-4 { + interface eth0.1008 + priority 200 + virtual-address 192.0.70.65/26 + vrid 4 + } + group 1008-6 { + interface eth0.1008 + priority 200 + virtual-address 2001:db8:0:1008::1/64 + vrid 6 + } + group 1009-4 { + interface eth0.1009 + priority 200 + virtual-address 192.0.70.129/28 + vrid 4 + } + group 1009-6 { + interface eth0.1009 + priority 200 + virtual-address 2001:db8:0:1009::1/64 + vrid 6 + } + group 1010-4 { + interface eth0.1010 + priority 200 + virtual-address 192.0.70.145/28 + vrid 4 + } + group 1010-6 { + interface eth0.1010 + priority 200 + virtual-address 2001:db8:0:1010::1/64 + vrid 6 + } + group 1011-4 { + interface eth0.1011 + priority 200 + virtual-address 192.0.70.161/28 + vrid 4 + } + group 1011-6 { + interface eth0.1011 + priority 200 + virtual-address 2001:db8:0:1011::1/64 + vrid 6 + } + group 1012-4 { + interface eth0.1012 + priority 200 + virtual-address 192.0.70.177/28 + vrid 4 + } + group 1012-6 { + interface eth0.1012 + priority 200 + virtual-address 2001:db8:0:1012::1/64 + vrid 6 + } + group 1013-4 { + interface eth0.1013 + priority 200 + virtual-address 192.0.70.193/27 + vrid 4 + } + group 1013-6 { + interface eth0.1013 + priority 200 + virtual-address 2001:db8:0:1013::1/64 + vrid 6 + } + group 1014-4 { + interface eth0.1014 + priority 200 + virtual-address 192.0.84.65/26 + vrid 4 + } + group 1014-6 { + interface eth0.1014 + priority 200 + virtual-address 2001:db8:0:1014::1/64 + vrid 6 + } + group 1015-4 { + interface eth0.1015 + priority 200 + virtual-address 192.0.71.1/26 + vrid 4 + } + group 1015-6 { + interface eth0.1015 + priority 200 + virtual-address 2001:db8:0:1015::1/64 + vrid 6 + } + group 1016-4 { + interface eth0.1016 + priority 200 + virtual-address 192.0.71.65/27 + vrid 4 + } + group 1016-6 { + interface eth0.1016 + priority 200 + virtual-address 2001:db8:0:1016::1/64 + vrid 6 + } + group 1017-4 { + interface eth0.1017 + priority 200 + virtual-address 192.0.71.97/28 + vrid 4 + } + group 1017-6 { + interface eth0.1017 + priority 200 + virtual-address 2001:db8:0:1017::1/64 + vrid 6 + } + group 1018-4 { + interface eth0.1018 + priority 200 + virtual-address 192.0.71.113/28 + vrid 4 + } + group 1018-6 { + interface eth0.1018 + priority 200 + virtual-address 2001:db8:0:1018::1/64 + vrid 6 + } + group 1019-4 { + interface eth0.1019 + priority 200 + virtual-address 192.0.71.129/26 + vrid 4 + } + group 1019-6 { + interface eth0.1019 + priority 200 + virtual-address 2001:db8:0:1019::1/64 + vrid 6 + } + group 1020-4 { + interface eth0.1020 + priority 200 + virtual-address 192.0.71.193/26 + vrid 4 + } + group 1020-6 { + interface eth0.1020 + priority 200 + virtual-address 2001:db8:0:1020::1/64 + vrid 6 + } + } +} +interfaces { + ethernet eth0 { + address 192.0.0.11/16 + duplex auto + smp-affinity auto + speed auto + vif 11 { + address 192.0.68.2/27 + address 2001:db8:c::2/64 + } + vif 102 { + address 192.0.98.2/24 + address 2001:db8:0:102::2/64 + } + vif 105 { + address 192.0.160.2/24 + address 2001:db8:0:105::2/64 + } + vif 838 { + address 192.0.16.210/30 + address 2001:db8:838::2/64 + } + vif 886 { + address 192.0.193.224/21 + address 2001:db8::3:669:0:1/64 + } + vif 1001 { + address 192.0.68.34/27 + address 2001:db8:0:1001::2/64 + } + vif 1002 { + address 192.0.68.66/26 + address 2001:db8:0:1002::2/64 + } + vif 1003 { + address 192.0.68.130/25 + address 2001:db8:0:1003::2/64 + } + vif 1004 { + address 192.0.69.2/24 + address 2001:db8:0:1004::2/64 + } + vif 1005 { + address 192.0.70.2/28 + address 2001:db8:0:1005::2/64 + } + vif 1006 { + address 192.0.70.18/28 + address 2001:db8:0:1006::2/64 + } + vif 1007 { + address 192.0.70.34/27 + address 2001:db8:0:1007::2/64 + } + vif 1008 { + address 192.0.70.66/26 + address 2001:db8:0:1008::2/64 + } + vif 1009 { + address 192.0.70.130/28 + address 2001:db8:0:1009::2/64 + } + vif 1010 { + address 192.0.70.146/28 + address 2001:db8:0:1010::2/64 + } + vif 1011 { + address 192.0.70.162/28 + address 2001:db8:0:1011::2/64 + } + vif 1012 { + address 192.0.70.178/28 + address 2001:db8:0:1012::2/64 + } + vif 1013 { + address 192.0.70.194/27 + address 2001:db8:0:1013::3/64 + } + vif 1014 { + address 192.0.84.66/26 + address 2001:db8:0:1014::2/64 + } + vif 1015 { + address 192.0.71.2/26 + address 2001:db8:0:1015::2/64 + } + vif 1016 { + address 192.0.71.66/27 + address 2001:db8:0:1016::2/64 + } + vif 1017 { + address 192.0.71.98/28 + address 2001:db8:0:1017::2/64 + } + vif 1018 { + address 192.0.71.114/28 + address 2001:db8:0:1018::2/64 + } + vif 1019 { + address 192.0.71.130/26 + address 2001:db8:0:1019::2/64 + } + vif 1020 { + address 192.0.71.194/26 + address 2001:db8:0:1020::2/64 + } + vif 4088 { + address 2001:db8:24::c7/64 + address 192.0.52.199/23 + } + vif 4089 { + address 192.0.176.194/30 + address 2001:db8:1000::2ea/126 + } + } + loopback lo { + } +} +policy { + as-path-list AS64513-AS64514 { + rule 10 { + action permit + regex "^64513 64514$" + } + } + as-path-list AS64512 { + rule 10 { + action permit + regex ^$ + } + } + prefix-list defaultV4 { + rule 10 { + action permit + prefix 0.0.0.0/0 + } + } + prefix-list hostrouteV4 { + rule 10 { + action permit + ge 32 + prefix 192.0.160.0/24 + } + rule 20 { + action permit + ge 32 + prefix 192.0.98.0/24 + } + rule 30 { + action permit + ge 32 + prefix 192.0.68.0/22 + } + rule 40 { + action permit + ge 32 + prefix 192.0.84.0/22 + } + } + prefix-list vyosV4 { + rule 10 { + action permit + prefix 192.0.160.0/24 + } + rule 20 { + action permit + prefix 192.0.98.0/24 + } + rule 30 { + action permit + prefix 192.0.68.0/22 + } + rule 40 { + action permit + prefix 192.0.84.0/22 + } + } + prefix-list privateV4 { + rule 10 { + action permit + le 32 + prefix 192.0.0.0/8 + } + rule 20 { + action permit + le 32 + prefix 192.0.0.0/12 + } + rule 30 { + action permit + le 32 + prefix 192.0.0.0/16 + } + } + prefix-list6 all6 { + rule 10 { + action permit + ge 4 + prefix 2000::/3 + } + } + prefix-list6 hostrouteV6 { + rule 20 { + action permit + ge 128 + prefix 2001:db8::/29 + } + } + prefix-list6 vyosV6 { + rule 20 { + action permit + prefix 2001:db8::/29 + } + } + prefix-list6 privateV6 { + rule 10 { + action permit + prefix fc00::/7 + } + } + route-map ExportRouteMap { + rule 5 { + action permit + match { + as-path AS64512 + ip { + address { + prefix-list hostrouteV4 + } + } + } + set { + community 65000:666 + } + } + rule 10 { + action permit + match { + as-path AS64512 + ip { + address { + prefix-list vyosV4 + } + } + } + } + rule 15 { + action permit + match { + as-path AS64512 + ipv6 { + address { + prefix-list hostrouteV6 + } + } + } + set { + community 65000:666 + } + } + rule 20 { + action permit + match { + as-path AS64512 + ipv6 { + address { + prefix-list vyosV6 + } + } + } + } + rule 100 { + action deny + } + } + route-map ExportRouteMapAS64515 { + rule 10 { + action permit + match { + ipv6 { + address { + prefix-list all6 + } + } + } + } + rule 20 { + action deny + match { + ip { + address { + prefix-list defaultV4 + } + } + } + } + rule 100 { + action deny + } + } + route-map ExportRouteMapAS64516 { + rule 5 { + action permit + match { + as-path AS64512 + ip { + address { + prefix-list hostrouteV4 + } + } + } + set { + community 65000:666 + } + } + rule 10 { + action permit + match { + as-path AS64512 + ip { + address { + prefix-list vyosV4 + } + } + } + } + rule 15 { + action permit + match { + as-path AS64512 + ipv6 { + address { + prefix-list hostrouteV6 + } + } + } + set { + community 65000:666 + } + } + rule 20 { + action permit + match { + as-path AS64512 + ipv6 { + address { + prefix-list vyosV6 + } + } + } + set { + as-path-exclude "100 200 300" + as-path-prepend "64512 64512 64512" + } + } + rule 100 { + action deny + } + } + route-map ExportRouteMapAS64517 { + rule 5 { + action permit + match { + as-path AS64512 + ip { + address { + prefix-list hostrouteV4 + } + } + } + set { + community 64517:666 + } + } + rule 10 { + action permit + match { + as-path AS64512 + ip { + address { + prefix-list vyosV4 + } + } + } + } + rule 15 { + action permit + match { + as-path AS64512 + ipv6 { + address { + prefix-list hostrouteV6 + } + } + } + set { + community 64517:666 + } + } + rule 20 { + action permit + match { + as-path AS64512 + ipv6 { + address { + prefix-list vyosV6 + } + } + } + } + rule 100 { + action deny + } + } + route-map ExportRouteMapAS64513 { + rule 5 { + action permit + match { + as-path AS64512 + ip { + address { + prefix-list hostrouteV4 + } + } + } + set { + community 64513:666 + } + } + rule 10 { + action permit + match { + as-path AS64512 + ip { + address { + prefix-list vyosV4 + } + } + } + } + rule 15 { + action permit + match { + as-path AS64512 + ipv6 { + address { + prefix-list hostrouteV6 + } + } + } + set { + community 64513:666 + } + } + rule 20 { + action permit + match { + as-path AS64512 + ipv6 { + address { + prefix-list vyosV6 + } + } + } + } + rule 100 { + action deny + } + } + route-map ImportRouteMap { + rule 10 { + action deny + match { + ip { + address { + prefix-list privateV4 + } + } + } + } + rule 15 { + action deny + match { + ipv6 { + address { + prefix-list privateV6 + } + } + } + } + rule 20 { + action deny + match { + ip { + address { + prefix-list vyosV4 + } + } + } + } + rule 30 { + action deny + match { + ipv6 { + address { + prefix-list vyosV6 + } + } + } + } + rule 40 { + action deny + match { + as-path AS64512 + } + } + rule 50 { + action permit + match { + as-path AS64513-AS64514 + } + set { + weight 10001 + } + } + rule 65535 { + action permit + } + } +} +protocols { + bgp 64500 { + address-family { + ipv4-unicast { + network 192.0.98.0/24 { + } + network 192.0.160.0/24 { + } + network 192.0.68.0/22 { + } + network 192.0.84.0/22 { + } + redistribute { + static { + route-map ExportRouteMap + } + } + } + ipv6-unicast { + network 2001:db8::/29 { + } + redistribute { + static { + route-map ExportRouteMap + } + } + } + } + maximum-paths { + ebgp 8 + ibgp 16 + } + neighbor 192.0.16.209 { + address-family { + ipv4-unicast { + route-map { + export ExportRouteMapAS64516 + import ImportRouteMap + } + } + } + remote-as 64501 + } + neighbor 192.0.192.6 { + address-family { + ipv4-unicast { + maximum-prefix 100 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64502 + } + neighbor 192.0.192.157 { + address-family { + ipv4-unicast { + maximum-prefix 350000 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64503 + } + neighbor 192.0.192.228 { + address-family { + ipv4-unicast { + maximum-prefix 10000 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64504 + } + neighbor 192.0.193.157 { + address-family { + ipv4-unicast { + maximum-prefix 350000 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64505 + } + neighbor 192.0.193.202 { + address-family { + ipv4-unicast { + maximum-prefix 10000 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64506 + } + neighbor 192.0.193.223 { + address-family { + ipv4-unicast { + maximum-prefix 10000 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64507 + } + neighbor 192.0.194.161 { + address-family { + ipv4-unicast { + maximum-prefix 10000 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64508 + } + neighbor 192.0.194.171 { + address-family { + ipv4-unicast { + maximum-prefix 10000 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64509 + } + neighbor 192.0.176.193 { + address-family { + ipv4-unicast { + route-map { + export ExportRouteMapAS64516 + import ImportRouteMap + } + } + } + remote-as 64510 + } + neighbor 192.0.52.12 { + address-family { + ipv4-unicast { + maximum-prefix 300 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64511 + } + neighbor 192.0.52.17 { + address-family { + ipv4-unicast { + maximum-prefix 75 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + password vyosvyos + remote-as 64512 + } + neighbor 192.0.52.24 { + address-family { + ipv4-unicast { + maximum-prefix 300 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64513 + } + neighbor 192.0.52.32 { + address-family { + ipv4-unicast { + maximum-prefix 50 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + password vyosfoooo + remote-as 64514 + } + neighbor 192.0.52.34 { + address-family { + ipv4-unicast { + maximum-prefix 10 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64515 + } + neighbor 192.0.52.46 { + address-family { + ipv4-unicast { + maximum-prefix 10 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64516 + } + neighbor 192.0.52.49 { + address-family { + ipv4-unicast { + maximum-prefix 75 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + password secret + remote-as 64517 + } + neighbor 192.0.52.74 { + address-family { + ipv4-unicast { + maximum-prefix 15000 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + password secretvyos + remote-as 64518 + } + neighbor 192.0.52.94 { + address-family { + ipv4-unicast { + maximum-prefix 250 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64519 + } + neighbor 192.0.52.100 { + address-family { + ipv4-unicast { + maximum-prefix 50 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64520 + } + neighbor 192.0.52.119 { + address-family { + ipv4-unicast { + maximum-prefix 30 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64521 + } + neighbor 192.0.52.165 { + address-family { + ipv4-unicast { + maximum-prefix 50 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64522 + } + neighbor 192.0.52.170 { + address-family { + ipv4-unicast { + maximum-prefix 150000 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64523 + } + neighbor 192.0.52.171 { + address-family { + ipv4-unicast { + maximum-prefix 10000 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64524 + } + neighbor 192.0.52.179 { + address-family { + ipv4-unicast { + maximum-prefix 20 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64525 + } + neighbor 192.0.52.189 { + address-family { + ipv4-unicast { + maximum-prefix 1000 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64526 + } + neighbor 192.0.52.210 { + address-family { + ipv4-unicast { + maximum-prefix 15 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64527 + } + neighbor 192.0.52.211 { + address-family { + ipv4-unicast { + maximum-prefix 15 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64528 + } + neighbor 192.0.52.251 { + address-family { + ipv4-unicast { + route-map { + export ExportRouteMap + import ImportRouteMap + } + weight 1010 + } + } + remote-as 64529 + } + neighbor 192.0.52.252 { + address-family { + ipv4-unicast { + route-map { + export ExportRouteMap + } + weight 1010 + } + } + remote-as 64530 + } + neighbor 192.0.52.253 { + address-family { + ipv4-unicast { + route-map { + export ExportRouteMapAS64515 + import ImportRouteMap + } + } + } + passive + remote-as 64531 + } + neighbor 192.0.68.3 { + address-family { + ipv4-unicast { + nexthop-self + soft-reconfiguration { + inbound + } + } + } + remote-as 64532 + update-source 192.0.68.2 + } + neighbor 2001:db8:838::1 { + address-family { + ipv6-unicast { + route-map { + export ExportRouteMapAS64516 + import ImportRouteMap + } + } + } + remote-as 64533 + } + neighbor 2001:db8:c::3 { + address-family { + ipv6-unicast { + nexthop-self + soft-reconfiguration { + inbound + } + } + } + remote-as 64534 + update-source 2001:db8:c::2 + } + neighbor 2001:db8:24::2e { + address-family { + ipv6-unicast { + maximum-prefix 5 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + password vyossecret + remote-as 64535 + } + neighbor 2001:db8:24::4a { + address-family { + ipv6-unicast { + maximum-prefix 1000 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64536 + } + neighbor 2001:db8:24::5e { + address-family { + ipv6-unicast { + maximum-prefix 200 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64537 + } + neighbor 2001:db8:24::11 { + address-family { + ipv6-unicast { + maximum-prefix 20 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64538 + } + neighbor 2001:db8:24::18 { + address-family { + ipv6-unicast { + maximum-prefix 300 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64539 + } + neighbor 2001:db8:24::20 { + address-family { + ipv6-unicast { + maximum-prefix 10 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64540 + } + neighbor 2001:db8:24::22 { + address-family { + ipv6-unicast { + maximum-prefix 5 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64541 + } + neighbor 2001:db8:24::31 { + address-family { + ipv6-unicast { + maximum-prefix 20 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64542 + } + neighbor 2001:db8:24::58 { + address-family { + ipv6-unicast { + maximum-prefix 15 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64543 + } + neighbor 2001:db8:24::64 { + address-family { + ipv6-unicast { + maximum-prefix 10 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + password geheim + remote-as 64544 + } + neighbor 2001:db8:24::a5 { + address-family { + ipv6-unicast { + maximum-prefix 10 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64545 + } + neighbor 2001:db8:24::aa { + address-family { + ipv6-unicast { + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64546 + } + neighbor 2001:db8:24::ab { + address-family { + ipv6-unicast { + maximum-prefix 1800 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + remote-as 64547 + } + neighbor 2001:db8:24::b0 { + address-family { + ipv6-unicast { + maximum-prefix 5 + route-map { + export ExportRouteMap + import ImportRouteMap + } + } + } + password secret123 + remote-as 64548 + } + parameters { + default { + no-ipv4-unicast + } + log-neighbor-changes + router-id 192.0.68.2 + } + } + static { + route 192.0.98.0/24 { + blackhole { + } + } + route 192.0.160.0/24 { + blackhole { + } + } + route 192.0.68.0/22 { + blackhole { + } + } + route 192.0.84.0/22 { + blackhole { + } + } + route6 2001:db8::/29 { + blackhole { + } + } + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + flow-accounting { + disable-imt + interface eth0.4088 + interface eth0.4089 + netflow { + engine-id 1 + server 192.0.2.55 { + port 2055 + } + version 9 + } + sflow { + agent-address auto + server 1.2.3.4 { + port 1234 + } + } + syslog-facility daemon + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + } + } + name-server 2001:db8::1 + name-server 2001:db8::2 + name-server 192.0.2.1 + name-server 192.0.2.2 + ntp { + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + syslog { + global { + facility all { + level all + } + preserve-fqdn + } + } + time-zone Europe/Zurich +} +zone-policy { + zone local { + default-action drop + from management { + firewall { + ipv6-name management-to-local-6 + name management-to-local-4 + } + } + from peers { + firewall { + ipv6-name peers-to-local-6 + name peers-to-local-4 + } + } + from servers { + firewall { + ipv6-name servers-to-local-6 + name servers-to-local-4 + } + } + local-zone + } + zone management { + default-action reject + from peers { + firewall { + ipv6-name peers-to-management-6 + name peers-to-management-4 + } + } + from servers { + firewall { + ipv6-name servers-to-management-6 + name servers-to-management-4 + } + } + interface eth0 + } + zone peers { + default-action reject + from management { + firewall { + ipv6-name management-to-peers-6 + name management-to-peers-4 + } + } + from servers { + firewall { + ipv6-name servers-to-peers-6 + name servers-to-peers-4 + } + } + interface eth0.4088 + interface eth0.4089 + interface eth0.11 + interface eth0.838 + interface eth0.886 + } + zone servers { + default-action reject + from management { + firewall { + ipv6-name management-to-servers-6 + name management-to-servers-4 + } + } + from peers { + firewall { + ipv6-name peers-to-servers-6 + name peers-to-servers-4 + } + } + interface eth0.1001 + interface eth0.105 + interface eth0.102 + interface eth0.1019 + interface eth0.1014 + interface eth0.1020 + interface eth0.1018 + interface eth0.1013 + interface eth0.1012 + interface eth0.1011 + interface eth0.1010 + interface eth0.1009 + interface eth0.1006 + interface eth0.1005 + interface eth0.1017 + interface eth0.1016 + interface eth0.1002 + interface eth0.1015 + interface eth0.1003 + interface eth0.1004 + interface eth0.1007 + interface eth0.1008 + } +} + + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.5 */ diff --git a/smoketest/configs/bgp-dmvpn-hub b/smoketest/configs/bgp-dmvpn-hub new file mode 100644 index 0000000..fc0be5e --- /dev/null +++ b/smoketest/configs/bgp-dmvpn-hub @@ -0,0 +1,177 @@ +interfaces { + ethernet eth0 { + address 100.64.10.1/31 + speed auto + duplex auto + } + ethernet eth1 { + speed auto + duplex auto + } + loopback lo { + } + tunnel tun0 { + address 192.168.254.62/26 + encapsulation gre + multicast enable + parameters { + ip { + key 1 + } + } + source-address 100.64.10.1 + } +} +protocols { + bgp 65000 { + address-family { + ipv4-unicast { + network 172.20.0.0/16 { + } + } + } + neighbor 192.168.254.1 { + peer-group DMVPN + remote-as 65001 + } + neighbor 192.168.254.2 { + peer-group DMVPN + remote-as 65002 + } + neighbor 192.168.254.3 { + peer-group DMVPN + remote-as 65003 + } + parameters { + default { + no-ipv4-unicast + } + log-neighbor-changes + } + peer-group DMVPN { + address-family { + ipv4-unicast { + } + } + } + timers { + holdtime 30 + keepalive 10 + } + } + nhrp { + tunnel tun0 { + cisco-authentication secret + holding-time 300 + multicast dynamic + redirect + shortcut + } + } + static { + route 0.0.0.0/0 { + next-hop 100.64.10.0 { + } + } + route 172.20.0.0/16 { + blackhole { + distance 200 + } + } + } +} +system { + config-management { + commit-revisions 100 + } + conntrack { + modules { + ftp + h323 + nfs + pptp + sip + sqlnet + tftp + } + } + console { + device ttyS0 { + speed 115200 + } + } + host-name cpe-4 + login { + user vyos { + authentication { + encrypted-password $6$r/Yw/07NXNY$/ZB.Rjf9jxEV.BYoDyLdH.kH14rU52pOBtrX.4S34qlPt77chflCHvpTCq9a6huLzwaMR50rEICzA5GoIRZlM0 + plaintext-password "" + } + } + } + name-server 1.1.1.1 + name-server 8.8.8.8 + name-server 9.9.9.9 + ntp { + server time1.vyos.net { + } + server time2.vyos.net { + } + server time3.vyos.net { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} +vpn { + ipsec { + esp-group ESP-DMVPN { + compression disable + lifetime 1800 + mode transport + pfs dh-group2 + proposal 1 { + encryption aes256 + hash sha1 + } + } + ike-group IKE-DMVPN { + close-action none + ikev2-reauth no + key-exchange ikev1 + lifetime 3600 + proposal 1 { + dh-group 2 + encryption aes256 + hash sha1 + } + } + ipsec-interfaces { + interface eth0 + } + profile NHRPVPN { + authentication { + mode pre-shared-secret + pre-shared-secret VyOS-topsecret + } + bind { + tunnel tun0 + } + esp-group ESP-DMVPN + ike-group IKE-DMVPN + } + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.0-epa3 diff --git a/smoketest/configs/bgp-dmvpn-spoke b/smoketest/configs/bgp-dmvpn-spoke new file mode 100644 index 0000000..39b64b9 --- /dev/null +++ b/smoketest/configs/bgp-dmvpn-spoke @@ -0,0 +1,201 @@ +interfaces { + ethernet eth0 { + vif 7 { + description PPPoE-UPLINK + } + } + ethernet eth1 { + address 172.17.1.1/24 + } + loopback lo { + } + pppoe pppoe1 { + authentication { + password cpe-1 + user cpe-1 + } + no-peer-dns + source-interface eth0.7 + } + tunnel tun0 { + address 192.168.254.1/26 + encapsulation gre + multicast enable + parameters { + ip { + key 1 + } + } + source-address 0.0.0.0 + } +} +nat { + source { + rule 10 { + log + outbound-interface pppoe1 + source { + address 172.17.0.0/16 + } + translation { + address masquerade + } + } + } +} +protocols { + bgp 65001 { + address-family { + ipv4-unicast { + network 172.17.0.0/16 { + } + } + } + neighbor 192.168.254.62 { + address-family { + ipv4-unicast { + } + } + remote-as 65000 + } + parameters { + default { + no-ipv4-unicast + } + log-neighbor-changes + } + timers { + holdtime 30 + keepalive 10 + } + } + nhrp { + tunnel tun0 { + cisco-authentication secret + holding-time 300 + map 192.168.254.62/26 { + nbma-address 100.64.10.1 + register + } + multicast nhs + redirect + shortcut + } + } + static { + route 172.17.0.0/16 { + blackhole { + distance 200 + } + } + } +} +service { + dhcp-server { + shared-network-name LAN-3 { + subnet 172.17.1.0/24 { + default-router 172.17.1.1 + name-server 172.17.1.1 + range 0 { + start 172.17.1.100 + stop 172.17.1.200 + } + } + } + } +} +system { + config-management { + commit-revisions 100 + } + conntrack { + modules { + ftp + h323 + nfs + pptp + sip + sqlnet + tftp + } + } + console { + device ttyS0 { + speed 115200 + } + } + host-name cpe-1 + login { + user vyos { + authentication { + encrypted-password $6$r/Yw/07NXNY$/ZB.Rjf9jxEV.BYoDyLdH.kH14rU52pOBtrX.4S34qlPt77chflCHvpTCq9a6huLzwaMR50rEICzA5GoIRZlM0 + plaintext-password "" + } + } + } + name-server 1.1.1.1 + name-server 8.8.8.8 + name-server 9.9.9.9 + ntp { + server time1.vyos.net { + } + server time2.vyos.net { + } + server time3.vyos.net { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} +vpn { + ipsec { + esp-group ESP-DMVPN { + compression disable + lifetime 1800 + mode transport + pfs dh-group2 + proposal 1 { + encryption aes256 + hash sha1 + } + } + ike-group IKE-DMVPN { + close-action none + ikev2-reauth no + key-exchange ikev1 + lifetime 3600 + proposal 1 { + dh-group 2 + encryption aes256 + hash sha1 + } + } + ipsec-interfaces { + interface pppoe1 + } + profile NHRPVPN { + authentication { + mode pre-shared-secret + pre-shared-secret VyOS-topsecret + } + bind { + tunnel tun0 + } + esp-group ESP-DMVPN + ike-group IKE-DMVPN + } + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.0-epa3 diff --git a/smoketest/configs/bgp-evpn-l2vpn-leaf b/smoketest/configs/bgp-evpn-l2vpn-leaf new file mode 100644 index 0000000..ab46fbb --- /dev/null +++ b/smoketest/configs/bgp-evpn-l2vpn-leaf @@ -0,0 +1,148 @@ +interfaces { + bridge br100 { + member { + interface eth3 { + } + interface vxlan100 { + } + } + } + dummy dum0 { + address 172.29.0.1/32 + } + ethernet eth0 { + description "Out-of-Band Managament Port" + address 2001:db8::41/64 + address 192.0.2.41/27 + vrf MGMT + } + ethernet eth1 { + address 172.29.1.1/31 + mtu 1600 + } + ethernet eth2 { + address 172.29.2.1/31 + mtu 1600 + } + ethernet eth3 { + } + loopback lo { + } + vxlan vxlan100 { + mtu 1500 + parameters { + nolearning + } + source-address 172.29.0.1 + vni 100 + } +} +protocols { + bgp 65010 { + address-family { + ipv4-unicast { + maximum-paths { + ibgp 4 + } + redistribute { + connected { + } + } + } + l2vpn-evpn { + advertise-all-vni + } + } + neighbor 172.29.1.0 { + peer-group evpn + } + neighbor 172.29.2.0 { + peer-group evpn + } + parameters { + log-neighbor-changes + } + peer-group evpn { + address-family { + ipv4-unicast { + nexthop-self { + } + } + l2vpn-evpn { + nexthop-self { + } + } + } + remote-as 65010 + } + } + vrf MGMT { + static { + route 0.0.0.0/0 { + next-hop 192.0.2.62 { + } + } + route6 ::/0 { + next-hop 2001:db8::1 { + } + } + } + } +} +service { + lldp { + interface all { + } + } + ssh { + disable-host-validation + vrf MGMT + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + ntp { + listen-address 192.0.2.41 + listen-address 2001:db8::41 + server 0.de.pool.ntp.org { + prefer + } + vrf MGMT + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} +vrf { + name MGMT { + table 1000 + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@2:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@20:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:nat66@1:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@20:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.4-rolling-202103091038 diff --git a/smoketest/configs/bgp-evpn-l2vpn-spine b/smoketest/configs/bgp-evpn-l2vpn-spine new file mode 100644 index 0000000..5dafc2f --- /dev/null +++ b/smoketest/configs/bgp-evpn-l2vpn-spine @@ -0,0 +1,128 @@ +interfaces { + ethernet eth0 { + description "Out-of-Band Managament Port" + address 192.0.2.51/27 + address 2001:db8::51/64 + vrf MGMT + } + ethernet eth1 { + address 172.29.1.0/31 + mtu 1600 + } + ethernet eth2 { + address 172.29.1.2/31 + mtu 1600 + } + ethernet eth3 { + address 172.29.1.4/31 + mtu 1600 + } + loopback lo { + } +} +protocols { + bgp 65010 { + address-family { + ipv4-unicast { + maximum-paths { + ibgp 4 + } + redistribute { + connected { + } + } + } + } + listen { + range 172.29.1.0/24 { + peer-group evpn + } + } + parameters { + log-neighbor-changes + } + peer-group evpn { + address-family { + ipv4-unicast { + route-reflector-client + } + l2vpn-evpn { + route-reflector-client + } + } + capability { + dynamic + } + remote-as 65010 + } + } + vrf MGMT { + static { + route 0.0.0.0/0 { + next-hop 192.0.2.62 { + } + } + route6 ::/0 { + next-hop 2001:db8::1 { + } + } + } + } +} +service { + lldp { + interface all { + } + } + ssh { + disable-host-validation + vrf MGMT + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + ntp { + listen-address 192.0.2.51 + listen-address 2001:db8::51 + server 0.de.pool.ntp.org { + prefer + } + vrf MGMT + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} +vrf { + name MGMT { + table 1000 + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@2:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@20:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:nat66@1:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@20:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.4-rolling-202103091038 diff --git a/smoketest/configs/bgp-evpn-l3vpn-pe-router b/smoketest/configs/bgp-evpn-l3vpn-pe-router new file mode 100644 index 0000000..c676463 --- /dev/null +++ b/smoketest/configs/bgp-evpn-l3vpn-pe-router @@ -0,0 +1,312 @@ +interfaces { + bridge br2000 { + address 10.1.1.1/24 + description "customer blue" + member { + interface eth4 { + } + interface vxlan2000 { + } + } + vrf blue + } + bridge br3000 { + address 10.2.1.1/24 + description "customer red" + member { + interface eth5 { + } + interface vxlan3000 { + } + } + vrf red + } + bridge br4000 { + address 10.3.1.1/24 + description "customer green" + member { + interface eth6 { + } + interface vxlan4000 { + } + } + vrf green + } + dummy dum0 { + address 172.29.255.1/32 + } + ethernet eth0 { + address 192.0.2.59/27 + address 2001:db8:ffff::59/64 + description "Out-of-Band Managament Port" + vrf mgmt + } + ethernet eth1 { + address 172.29.0.2/31 + description "link to pe2" + mtu 1600 + } + ethernet eth2 { + disable + } + ethernet eth3 { + address 172.29.0.6/31 + description "link to pe3" + mtu 1600 + } + ethernet eth4 { + description "customer blue" + } + ethernet eth5 { + description "customer red" + } + ethernet eth6 { + description "customer green" + } + loopback lo { + } + vxlan vxlan2000 { + mtu 1500 + parameters { + nolearning + } + port 4789 + source-address 172.29.255.1 + vni 2000 + } + vxlan vxlan3000 { + mtu 1500 + parameters { + nolearning + } + port 4789 + source-address 172.29.255.1 + vni 3000 + } + vxlan vxlan4000 { + mtu 1500 + parameters { + nolearning + } + port 4789 + source-address 172.29.255.1 + vni 4000 + } +} +protocols { + bgp { + address-family { + l2vpn-evpn { + advertise { + ipv4 { + unicast { + } + } + } + advertise-all-vni + } + } + local-as 100 + neighbor 172.29.255.2 { + peer-group ibgp + } + neighbor 172.29.255.3 { + peer-group ibgp + } + parameters { + default { + no-ipv4-unicast + } + log-neighbor-changes + router-id 172.29.255.1 + } + peer-group ibgp { + address-family { + l2vpn-evpn { + } + } + remote-as 100 + update-source dum0 + } + } + ospf { + area 0 { + network 172.29.0.2/31 + network 172.29.0.6/31 + } + interface eth1 { + network point-to-point + } + interface eth3 { + network point-to-point + } + log-adjacency-changes { + detail + } + parameters { + abr-type cisco + router-id 172.29.255.1 + } + passive-interface default + passive-interface-exclude eth1 + passive-interface-exclude eth3 + redistribute { + connected { + } + } + } +} +service { + lldp { + interface all { + } + } + ssh { + disable-host-validation + port 22 + vrf mgmt + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.net + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + name-server 192.0.2.251 + name-server 192.0.2.252 + name-server 2001:db8::1 + ntp { + listen-address 192.0.2.59 + listen-address 2001:db8:ffff::59 + server 192.0.2.251 { + } + server 192.0.2.252 { + } + server 2001:db8::251 { + } + server 2001:db8::252 { + } + vrf mgmt + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} +vrf { + name blue { + protocols { + bgp { + address-family { + ipv4-unicast { + redistribute { + connected { + } + } + } + l2vpn-evpn { + advertise { + ipv4 { + unicast { + } + } + } + } + } + local-as 100 + } + } + table 2000 + vni 2000 + } + name green { + protocols { + bgp { + address-family { + ipv4-unicast { + redistribute { + connected { + } + } + } + l2vpn-evpn { + advertise { + ipv4 { + unicast { + } + } + } + } + } + local-as 100 + } + } + table 4000 + vni 4000 + } + name mgmt { + protocols { + static { + route 0.0.0.0/0 { + next-hop 192.0.2.62 { + } + } + route6 ::/0 { + next-hop 2001:db8:ffff::1 { + } + } + } + } + table 1000 + } + name red { + protocols { + bgp { + address-family { + ipv4-unicast { + redistribute { + connected { + } + } + } + l2vpn-evpn { + advertise { + ipv4 { + unicast { + } + } + } + } + } + local-as 100 + } + } + table 3000 + vni 3000 + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "bgp@1:broadcast-relay@1:cluster@1:config-management@1:conntrack@2:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@20:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:nat66@1:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@9:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@20:vrf@2:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.4-rolling-202104091411 diff --git a/smoketest/configs/bgp-medium-confederation b/smoketest/configs/bgp-medium-confederation new file mode 100644 index 0000000..dfb944d --- /dev/null +++ b/smoketest/configs/bgp-medium-confederation @@ -0,0 +1,247 @@ +interfaces { + dummy dum0 { + address 1.1.1.1/32 + address 2001:db8::1/128 + } + ethernet eth0 { + address 192.168.253.1/24 + address fd52:100:200:fffe::1/64 + } + ethernet eth1 { + } + ethernet eth2 { + } +} +policy { + route-map BGP-IN { + rule 10 { + action permit + } + } + route-map BGP-OUT { + rule 10 { + action permit + } + } + route-map BGP-REDISTRIBUTE { + rule 10 { + action deny + } + } + route-map DEFAULT-ZEBRA-IN { + rule 10 { + action deny + } + } +} +protocols { + bgp 670 { + address-family { + ipv4-unicast { + redistribute { + connected { + route-map BGP-REDISTRIBUTE + } + static { + route-map BGP-REDISTRIBUTE + } + } + } + ipv6-unicast { + redistribute { + connected { + route-map BGP-REDISTRIBUTE + } + } + } + } + neighbor 192.168.253.14 { + peer-group WDC07 + } + neighbor 192.168.253.16 { + peer-group WDC07 + } + neighbor 192.168.253.17 { + peer-group WDC07 + } + neighbor 192.168.253.18 { + peer-group WDC07 + } + neighbor 192.168.253.19 { + peer-group WDC07 + } + neighbor eth1 { + interface { + v6only { + peer-group BACKBONE + remote-as 666 + } + } + } + neighbor eth2 { + interface { + v6only { + peer-group BACKBONE + remote-as 666 + } + } + } + neighbor fd52:100:200:fffe::14 { + address-family { + ipv6-unicast { + peer-group WDC07v6 + } + } + } + neighbor fd52:100:200:fffe::16 { + address-family { + ipv6-unicast { + peer-group WDC07v6 + } + } + } + neighbor fd52:100:200:fffe::17 { + address-family { + ipv6-unicast { + peer-group WDC07v6 + } + } + } + neighbor fd52:100:200:fffe::18 { + address-family { + ipv6-unicast { + peer-group WDC07v6 + } + } + } + neighbor fd52:100:200:fffe::19 { + address-family { + ipv6-unicast { + peer-group WDC07v6 + } + } + } + parameters { + bestpath { + as-path { + confed + multipath-relax + } + } + confederation { + identifier 696 + peers 668 + peers 669 + peers 666 + } + default { + no-ipv4-unicast + } + graceful-restart { + } + router-id 192.168.253.15 + } + peer-group BACKBONE { + address-family { + ipv4-unicast { + nexthop-self { + } + route-map { + export BGP-OUT + import BGP-IN + } + soft-reconfiguration { + inbound + } + } + ipv6-unicast { + nexthop-self { + } + route-map { + export BGP-OUT + import BGP-IN + } + soft-reconfiguration { + inbound + } + } + } + capability { + extended-nexthop + } + } + peer-group WDC07 { + address-family { + ipv4-unicast { + default-originate { + } + nexthop-self { + } + route-map { + export BGP-OUT + import BGP-IN + } + soft-reconfiguration { + inbound + } + } + } + remote-as 670 + update-source dum0 + } + peer-group WDC07v6 { + address-family { + ipv6-unicast { + default-originate { + } + nexthop-self { + } + route-map { + export BGP-OUT + import BGP-IN + } + soft-reconfiguration { + inbound + } + } + } + remote-as 670 + update-source dum0 + } + route-map DEFAULT-ZEBRA-IN + } +} +system { + config-management { + commit-revisions 200 + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.net + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + syslog { + global { + facility all { + level notice + } + facility protocols { + level debug + } + } + } +} + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:container@1:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.5 diff --git a/smoketest/configs/bgp-rpki b/smoketest/configs/bgp-rpki new file mode 100644 index 0000000..5588f15 --- /dev/null +++ b/smoketest/configs/bgp-rpki @@ -0,0 +1,124 @@ +interfaces { + ethernet eth0 { + address 192.0.2.100/25 + address 2001:db8::ffff/64 + } + ethernet eth1 { + address 100.64.0.1/24 + } + loopback lo { + } +} +policy { + route-map ebgp-transit-rpki { + rule 10 { + action deny + match { + rpki invalid + } + } + rule 20 { + action permit + match { + rpki notfound + } + set { + local-preference 20 + } + } + rule 30 { + action permit + match { + rpki valid + } + set { + local-preference 100 + } + } + rule 40 { + action permit + set { + extcommunity-rt 192.0.2.100:100 + extcommunity-soo 64500:100 + } + } + } +} +protocols { + bgp 64500 { + neighbor 1.2.3.4 { + address-family { + ipv4-unicast { + nexthop-self { + } + route-map { + import ebgp-transit-rpki + } + } + } + remote-as 10 + } + } + rpki { + cache routinator { + address 192.0.2.10 + port 3323 + } + } + static { + route 0.0.0.0/0 { + next-hop 192.0.2.1 { + } + } + route6 ::/0 { + next-hop 2001:db8::1 { + } + } + } +} +service { + ssh { + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + } + } + ntp { + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@13:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@6:salt@1:snmp@2:ssh@2:sstp@3:system@19:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1" +// Release version: 1.3-rolling-202010241631 diff --git a/smoketest/configs/bgp-small-internet-exchange b/smoketest/configs/bgp-small-internet-exchange new file mode 100644 index 0000000..c9da8fa --- /dev/null +++ b/smoketest/configs/bgp-small-internet-exchange @@ -0,0 +1,496 @@ +interfaces { + ethernet eth0 { + address 192.0.2.100/25 + address 2001:db8:aaaa::ffff/64 + } + ethernet eth1 { + address 192.0.2.200/25 + address 2001:db8:bbbb::ffff/64 + } + loopback lo { + } +} +policy { + as-path-list bogon-asns { + rule 10 { + action permit + description "RFC 7607" + regex _0_ + } + rule 20 { + action permit + description "RFC 4893" + regex _23456_ + } + rule 30 { + action permit + description "RFC 5398/6996/7300" + regex _6449[6-9]_|_65[0-4][0-9][0-9]_|_655[0-4][0-9]_|_6555[0-1]_ + } + rule 40 { + action permit + description "IANA reserved" + regex _6555[2-9]_|_655[6-9][0-9]_|_65[6-9][0-9][0-9]_|_6[6-9][0-9][0-9][0-]_|_[7-9][0-9][0-9][0-9][0-9]_|_1[0-2][0-9][0-9][0-9][0-9]_|_130[0-9][0-9][0-9]_|_1310[0-6][0-9]_|_13107[01]_ + } + } + prefix-list bogon-v4 { + rule 10 { + action permit + le 32 + prefix 0.0.0.0/8 + } + rule 20 { + action permit + le 32 + prefix 10.0.0.0/8 + } + rule 30 { + action permit + le 32 + prefix 100.64.0.0/10 + } + rule 40 { + action permit + le 32 + prefix 127.0.0.0/8 + } + rule 50 { + action permit + le 32 + prefix 169.254.0.0/16 + } + rule 60 { + action permit + le 32 + prefix 172.16.0.0/12 + } + rule 70 { + action permit + le 32 + prefix 192.0.2.0/24 + } + rule 80 { + action permit + le 32 + prefix 192.88.99.0/24 + } + rule 90 { + action permit + le 32 + prefix 192.168.0.0/16 + } + rule 100 { + action permit + le 32 + prefix 198.18.0.0/15 + } + rule 110 { + action permit + le 32 + prefix 198.51.100.0/24 + } + rule 120 { + action permit + le 32 + prefix 203.0.113.0/24 + } + rule 130 { + action permit + le 32 + prefix 224.0.0.0/4 + } + rule 140 { + action permit + le 32 + prefix 240.0.0.0/4 + } + } + prefix-list IX-out-v4 { + rule 10 { + action permit + prefix 10.0.0.0/23 + } + rule 20 { + action permit + prefix 10.0.128.0/23 + } + } + prefix-list prefix-filter-v4 { + rule 10 { + action permit + ge 25 + prefix 0.0.0.0/0 + } + } + prefix-list6 bogon-v6 { + rule 10 { + action permit + description "RFC 4291 IPv4-compatible, loopback, et al" + le 128 + prefix ::/8 + } + rule 20 { + action permit + description "RFC 6666 Discard-Only" + le 128 + prefix 0100::/64 + } + rule 30 { + action permit + description "RFC 5180 BMWG" + le 128 + prefix 2001:2::/48 + } + rule 40 { + action permit + description "RFC 4843 ORCHID" + le 128 + prefix 2001:10::/28 + } + rule 50 { + action permit + description "RFC 3849 documentation" + le 128 + prefix 2001:db8::/32 + } + rule 60 { + action permit + description "RFC 7526 6to4 anycast relay" + le 128 + prefix 2002::/16 + } + rule 70 { + action permit + description "RFC 3701 old 6bone" + le 128 + prefix 3ffe::/16 + } + rule 80 { + action permit + description "RFC 4193 unique local unicast" + le 128 + prefix fc00::/7 + } + rule 90 { + action permit + description "RFC 4291 link local unicast" + le 128 + prefix fe80::/10 + } + rule 100 { + action permit + description "RFC 3879 old site local unicast" + le 128 + prefix fec0::/10 + } + rule 110 { + action permit + description "RFC 4291 multicast" + le 128 + prefix ff00::/8 + } + } + prefix-list6 prefix-filter-v6 { + rule 10 { + action permit + ge 49 + prefix ::/0 + } + } + prefix-list6 IX-out-v6 { + rule 10 { + action permit + prefix 2001:db8:100::/40 + } + rule 20 { + action permit + prefix 2001:db8:200::/40 + } + } + route-map eBGP-IN-v4 { + rule 10 { + action deny + match { + as-path bogon-asns + } + } + rule 20 { + action deny + match { + ip { + address { + prefix-list bogon-v4 + } + } + } + } + rule 30 { + action deny + match { + ip { + address { + prefix-list prefix-filter-v4 + } + } + } + } + rule 40 { + action permit + set { + local-preference 100 + metric 0 + } + } + } + route-map eBGP-IN-v6 { + rule 10 { + action deny + match { + as-path bogon-asns + } + } + rule 20 { + action deny + match { + ipv6 { + address { + prefix-list bogon-v6 + } + } + } + } + rule 30 { + action deny + match { + ipv6 { + address { + prefix-list prefix-filter-v6 + } + } + } + } + rule 31 { + action deny + match { + ipv6 { + nexthop 2001:db8::1 + } + } + } + rule 40 { + action permit + set { + local-preference 100 + metric 0 + } + } + } + route-map IX-in-v4 { + rule 5 { + action permit + call eBGP-IN-v4 + on-match { + next + } + } + rule 10 { + action permit + } + } + route-map IX-out-v4 { + rule 10 { + action permit + match { + ip { + address { + prefix-list IX-out-v4 + } + } + } + } + } + route-map IX-in-v6 { + rule 5 { + action permit + call eBGP-IN-v6 + on-match { + next + } + } + rule 10 { + action permit + } + } + route-map IX-out-v6 { + rule 10 { + action permit + match { + ipv6 { + address { + prefix-list IX-out-v6 + } + } + } + } + } +} +protocols { + bgp 65000 { + address-family { + ipv4-unicast { + network 10.0.0.0/23 { + } + network 10.0.128.0/23 { + } + } + ipv6-unicast { + network 2001:db8:100::/40 { + } + network 2001:db8:200::/40 { + } + } + } + neighbor 192.0.2.1 { + description "Peering: IX-1 (Route Server)" + peer-group IXPeeringIPv4 + remote-as 65020 + } + neighbor 192.0.2.2 { + description "Peering: IX-1 (Route Server)" + peer-group IXPeeringIPv4 + remote-as 65020 + } + neighbor 192.0.2.3 { + description "Peering: IX-1 (Route Server)" + peer-group IXPeeringIPv4 + remote-as 65020 + } + neighbor 192.0.2.129 { + description "Peering: IX-2 (Route Server)" + peer-group IXPeeringIPv4 + remote-as 65030 + } + neighbor 192.0.2.130 { + description "Peering: IX-2 (Route Server)" + peer-group IXPeeringIPv4 + remote-as 65030 + } + neighbor 2001:db8:aaaa::1 { + description "Peering: IX-1 (Route Server)" + peer-group IXPeeringIPv6 + remote-as 65020 + } + neighbor 2001:db8:aaaa::2 { + description "Peering: IX-1 (Route Server)" + peer-group IXPeeringIPv6 + remote-as 65020 + } + neighbor 2001:db8:bbbb::1 { + description "Peering: IX-2 (Route Server)" + peer-group IXPeeringIPv6 + remote-as 65030 + } + neighbor 2001:db8:bbbb::2 { + description "Peering: IX-2 (Route Server)" + peer-group IXPeeringIPv6 + remote-as 65030 + } + parameters { + default { + no-ipv4-unicast + } + } + peer-group IXPeeringIPv4 { + address-family { + ipv4-unicast { + route-map { + export IX-out-v4 + } + soft-reconfiguration { + inbound + } + } + } + } + peer-group IXPeeringIPv6 { + address-family { + ipv6-unicast { + route-map { + export IX-out-v6 + } + soft-reconfiguration { + inbound + } + } + } + } + } + static { + route 10.0.0.0/23 { + blackhole { + distance 250 + } + } + route 10.0.128.0/23 { + blackhole { + distance 250 + } + } + route6 2001:db8:100::/40 { + blackhole { + distance 250 + } + } + route6 2001:db8:200::/40 { + blackhole { + distance 250 + } + } + } +} +service { + ssh { + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + } + } + ntp { + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@13:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@6:salt@1:snmp@2:ssh@2:sstp@3:system@19:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1" +// Release version: 1.3-rolling-202010241631 diff --git a/smoketest/configs/bgp-small-ipv4-unicast b/smoketest/configs/bgp-small-ipv4-unicast new file mode 100644 index 0000000..83f1eff --- /dev/null +++ b/smoketest/configs/bgp-small-ipv4-unicast @@ -0,0 +1,77 @@ +interfaces { + ethernet eth0 { + address 192.0.2.1/24 + address 2001:db8::1/64 + } + loopback lo { + } +} +protocols { + bgp 65001 { + address-family { + ipv4-unicast { + network 10.0.150.0/23 { + } + } + ipv6-unicast { + network 2001:db8:200::/40 { + } + } + } + neighbor 192.0.2.10 { + remote-as 65010 + } + neighbor 192.0.2.11 { + remote-as 65011 + } + neighbor 2001:db8::10 { + remote-as 65010 + } + neighbor 2001:db8::11 { + remote-as 65011 + } + parameters { + log-neighbor-changes + } + } +} +service { + ssh { + disable-host-validation + port 22 + } +} +system { + config-management { + commit-revisions 200 + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.net + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + syslog { + global { + facility all { + level notice + } + facility protocols { + level debug + } + } + } +} + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.5 */ diff --git a/smoketest/configs/cluster-basic b/smoketest/configs/cluster-basic new file mode 100644 index 0000000..1e34999 --- /dev/null +++ b/smoketest/configs/cluster-basic @@ -0,0 +1,62 @@ +cluster { + dead-interval 500 + group VyOS { + auto-failback true + primary vyos1 + secondary vyos2 + service 192.0.2.10/24/eth1 + service 192.0.2.20/24 + } + interface eth1 + keepalive-interval 100 + monitor-dead-interval 420 + pre-shared-secret qwerty +} +interfaces { + ethernet eth0 { + duplex auto + smp-affinity auto + speed auto + } + ethernet eth1 { + address 192.0.2.1/24 + duplex auto + smp-affinity auto + speed auto + } + loopback lo { + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } + time-zone Antarctica/South_Pole +} +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:container@1:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.3 diff --git a/smoketest/configs/container-simple b/smoketest/configs/container-simple new file mode 100644 index 0000000..b98a440 --- /dev/null +++ b/smoketest/configs/container-simple @@ -0,0 +1,52 @@ +container { + name c01 { + allow-host-networks + cap-add net-bind-service + cap-add net-raw + image busybox:stable + } + name c02 { + allow-host-networks + allow-host-pid + cap-add sys-time + image busybox:stable + sysctl { + parameter kernel.msgmax { + value "8192" + } + } + } +} +interfaces { + ethernet eth0 { + duplex auto + speed auto + } + ethernet eth1 { + duplex auto + speed auto + } +} +system { + config-management { + commit-revisions 50 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$r/Yw/07NXNY$/ZB.Rjf9jxEV.BYoDyLdH.kH14rU52pOBtrX.4S34qlPt77chflCHvpTCq9a6huLzwaMR50rEICzA5GoIRZlM0 + plaintext-password "" + } + } + } +} + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:container@1:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@23:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.6 diff --git a/smoketest/configs/dialup-router-complex b/smoketest/configs/dialup-router-complex new file mode 100644 index 0000000..aa9837f --- /dev/null +++ b/smoketest/configs/dialup-router-complex @@ -0,0 +1,1706 @@ +firewall { + all-ping enable + broadcast-ping disable + config-trap disable + group { + address-group MEDIA-STREAMING-CLIENTS { + address 172.16.35.241 + address 172.16.35.242 + address 172.16.35.243 + } + address-group DMZ-WEBSERVER { + address 172.16.36.10 + address 172.16.36.40 + address 172.16.36.20 + } + address-group DMZ-RDP-SERVER { + address 172.16.33.40 + } + address-group DOMAIN-CONTROLLER { + address 172.16.100.10 + address 172.16.100.20 + } + address-group AUDIO-STREAM { + address 172.16.35.20 + address 172.16.35.21 + address 172.16.35.22 + address 172.16.35.23 + } + ipv6-network-group LOCAL-ADDRESSES { + network ff02::/64 + network fe80::/10 + } + network-group SSH-IN-ALLOW { + network 192.0.2.0/24 + network 10.0.0.0/8 + network 172.16.0.0/12 + network 192.168.0.0/16 + } + port-group SMART-TV-PORTS { + port 5005-5006 + port 80 + port 443 + port 3722 + } + } + ipv6-name ALLOW-ALL-6 { + default-action accept + } + ipv6-name ALLOW-BASIC-6 { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + state { + invalid enable + } + } + rule 10 { + action accept + protocol icmpv6 + } + rule 15 { + action accept + icmpv6 { + type 1 + } + protocol icmpv6 + } + rule 16 { + action accept + icmpv6 { + type 1/1 + } + protocol icmpv6 + } + rule 17 { + action accept + icmpv6 { + type destination-unreachable + } + protocol icmpv6 + } + } + ipv6-name ALLOW-ESTABLISHED-6 { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + state { + invalid enable + } + } + rule 10 { + action accept + destination { + group { + network-group LOCAL-ADDRESSES + } + } + protocol icmpv6 + source { + address fe80::/10 + } + } + rule 20 { + action accept + icmpv6 { + type echo-request + } + protocol icmpv6 + } + rule 21 { + action accept + icmpv6 { + type destination-unreachable + } + protocol icmpv6 + } + rule 22 { + action accept + icmpv6 { + type packet-too-big + } + protocol icmpv6 + } + rule 23 { + action accept + icmpv6 { + type time-exceeded + } + protocol icmpv6 + } + rule 24 { + action accept + icmpv6 { + type parameter-problem + } + protocol icmpv6 + } + } + ipv6-name WAN-LOCAL-6 { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + state { + invalid enable + } + } + rule 10 { + action accept + destination { + address ff02::/64 + } + protocol icmpv6 + source { + address fe80::/10 + } + } + rule 50 { + action accept + description DHCPv6 + destination { + address fe80::/10 + port 546 + } + protocol udp + source { + address fe80::/10 + port 547 + } + } + } + ipv6-receive-redirects disable + ipv6-src-route disable + ip-src-route disable + log-martians enable + name DMZ-GUEST { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + } + name DMZ-LAN { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + description "NTP and LDAP to AD DC" + destination { + group { + address-group DOMAIN-CONTROLLER + } + port 123,389,636 + } + protocol tcp_udp + } + rule 300 { + action accept + destination { + group { + address-group DMZ-RDP-SERVER + } + port 3389 + } + protocol tcp_udp + source { + address 172.16.36.20 + } + } + } + name DMZ-LOCAL { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 50 { + action accept + destination { + address 172.16.254.30 + port 53 + } + protocol tcp_udp + } + rule 123 { + action accept + destination { + port 123 + } + protocol udp + } + rule 800 { + action drop + description "SSH anti brute force" + destination { + port ssh + } + log enable + protocol tcp + recent { + count 4 + time 60 + } + state { + new enable + } + } + } + name DMZ-WAN { + default-action accept + } + name GUEST-DMZ { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + destination { + port 80,443 + } + protocol tcp + } + } + name GUEST-IOT { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + description "MEDIA-STREAMING-CLIENTS Devices to GUEST" + destination { + group { + address-group MEDIA-STREAMING-CLIENTS + } + } + protocol tcp_udp + } + rule 110 { + action accept + description "AUDIO-STREAM Devices to GUEST" + destination { + group { + address-group AUDIO-STREAM + } + } + protocol tcp_udp + } + rule 200 { + action accept + description "MCAST relay" + destination { + address 224.0.0.251 + port 5353 + } + protocol udp + } + rule 300 { + action accept + description "BCAST relay" + destination { + port 1900 + } + protocol udp + } + } + name GUEST-LAN { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + } + name GUEST-LOCAL { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 10 { + action accept + description DNS + destination { + address 172.31.0.254 + port 53 + } + protocol tcp_udp + } + rule 11 { + action accept + description DHCP + destination { + port 67 + } + protocol udp + } + rule 15 { + action accept + destination { + address 172.31.0.254 + } + protocol icmp + } + rule 200 { + action accept + description "MCAST relay" + destination { + address 224.0.0.251 + port 5353 + } + protocol udp + } + rule 210 { + action accept + description "AUDIO-STREAM Broadcast" + destination { + port 1900 + } + protocol udp + } + } + name GUEST-WAN { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 25 { + action accept + description SMTP + destination { + port 25,587 + } + protocol tcp + } + rule 53 { + action accept + destination { + port 53 + } + protocol tcp_udp + } + rule 60 { + action accept + source { + address 172.31.0.200 + } + } + rule 80 { + action accept + source { + address 172.31.0.200 + } + } + rule 100 { + action accept + protocol icmp + } + rule 110 { + action accept + description POP3 + destination { + port 110,995 + } + limit { + rate "10/minute" + } + protocol tcp + } + rule 123 { + action accept + description "NTP Client" + destination { + port 123 + } + protocol udp + } + rule 143 { + action accept + description IMAP + destination { + port 143,993 + } + protocol tcp + } + rule 200 { + action accept + destination { + port 80,443 + } + protocol tcp + } + rule 500 { + action accept + description "L2TP IPSec" + destination { + port 500,4500 + } + protocol udp + } + rule 600 { + action accept + destination { + port 5222-5224 + } + protocol tcp + } + rule 601 { + action accept + destination { + port 3478-3497,4500,16384-16387,16393-16402 + } + protocol udp + } + rule 1000 { + action accept + source { + address 172.31.0.184 + } + } + } + name IOT-GUEST { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + description "MEDIA-STREAMING-CLIENTS Devices to IOT" + protocol tcp_udp + source { + group { + address-group MEDIA-STREAMING-CLIENTS + } + } + } + rule 110 { + action accept + description "AUDIO-STREAM Devices to IOT" + protocol tcp_udp + source { + group { + address-group AUDIO-STREAM + } + } + } + rule 200 { + action accept + description "MCAST relay" + destination { + address 224.0.0.251 + port 5353 + } + protocol udp + } + rule 300 { + action accept + description "BCAST relay" + destination { + port 1900 + } + protocol udp + } + } + name IOT-LAN { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + description "AppleTV to LAN" + destination { + group { + port-group SMART-TV-PORTS + } + } + protocol tcp_udp + source { + group { + address-group MEDIA-STREAMING-CLIENTS + } + } + } + rule 110 { + action accept + description "AUDIO-STREAM Devices to LAN" + protocol tcp_udp + source { + group { + address-group AUDIO-STREAM + } + } + } + } + name IOT-LOCAL { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 10 { + action accept + description DNS + destination { + address 172.16.254.30 + port 53 + } + protocol tcp_udp + } + rule 11 { + action accept + description DHCP + destination { + port 67 + } + protocol udp + } + rule 15 { + action accept + destination { + address 172.16.35.254 + } + protocol icmp + } + rule 200 { + action accept + description "MCAST relay" + destination { + address 224.0.0.251 + port 5353 + } + protocol udp + } + rule 201 { + action accept + description "MCAST relay" + destination { + address 172.16.35.254 + port 5353 + } + protocol udp + } + rule 210 { + action accept + description "AUDIO-STREAM Broadcast" + destination { + port 1900,1902,6969 + } + protocol udp + } + } + name IOT-WAN { + default-action accept + } + name LAN-DMZ { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 22 { + action accept + description "SSH into DMZ" + destination { + port 22 + } + protocol tcp + } + rule 100 { + action accept + destination { + group { + address-group DMZ-WEBSERVER + } + port 22,80,443 + } + protocol tcp + } + } + name LAN-GUEST { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + } + name LAN-IOT { + default-action accept + } + name LAN-LOCAL { + default-action accept + } + name LAN-WAN { + default-action accept + } + name LOCAL-DMZ { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + } + name LOCAL-GUEST { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 5 { + action accept + protocol icmp + } + rule 200 { + action accept + description "MCAST relay" + destination { + address 224.0.0.251 + port 5353 + } + protocol udp + } + rule 300 { + action accept + description "BCAST relay" + destination { + port 1900 + } + protocol udp + } + } + name LOCAL-IOT { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 5 { + action accept + protocol icmp + } + rule 200 { + action accept + description "MCAST relay" + destination { + address 224.0.0.251 + port 5353 + } + protocol udp + } + rule 300 { + action accept + description "BCAST relay" + destination { + port 1900,6969 + } + protocol udp + } + } + name LOCAL-LAN { + default-action accept + } + name LOCAL-WAN { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 10 { + action accept + protocol icmp + } + rule 50 { + action accept + description DNS + destination { + port 53 + } + protocol tcp_udp + } + rule 80 { + action accept + destination { + port 80,443 + } + protocol tcp + } + rule 123 { + action accept + description NTP + destination { + port 123 + } + protocol udp + } + } + name WAN-DMZ { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + destination { + address 172.16.36.10 + port 80,443 + } + protocol tcp + } + } + name WAN-GUEST { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 1000 { + action accept + destination { + address 172.31.0.184 + } + } + rule 8000 { + action accept + destination { + address 172.31.0.200 + port 10000 + } + protocol udp + } + } + name WAN-IOT { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + } + name WAN-LAN { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 1000 { + action accept + destination { + address 172.16.33.40 + port 3389 + } + protocol tcp + source { + group { + network-group SSH-IN-ALLOW + } + } + } + } + name WAN-LOCAL { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 22 { + action accept + destination { + port 22 + } + protocol tcp + source { + group { + network-group SSH-IN-ALLOW + } + } + } + } + options { + interface pppoe0 { + adjust-mss 1452 + adjust-mss6 1432 + } + interface eth0.10 { + adjust-mss 1320 + adjust-mss6 1300 + } + } + receive-redirects disable + send-redirects enable + source-validation disable + syn-cookies enable + twa-hazards-protection disable +} +interfaces { + dummy dum0 { + address 172.16.254.30/32 + } + ethernet eth0 { + duplex auto + speed auto + vif 5 { + address 172.16.37.254/24 + } + vif 10 { + address 172.16.33.254/24 + } + vif 20 { + address 172.31.0.254/24 + } + vif 35 { + address 172.16.35.254/24 + } + vif 50 { + address 172.16.36.254/24 + } + vif 100 { + address 172.16.100.254/24 + } + vif 201 { + address 172.18.201.254/24 + } + vif 202 { + address 172.18.202.254/24 + } + vif 203 { + address 172.18.203.254/24 + } + vif 204 { + address 172.18.204.254/24 + } + } + ethernet eth1 { + vif 7 { + description FTTH-PPPoE + } + } + loopback lo { + address 172.16.254.30/32 + } + pppoe pppoe0 { + authentication { + password vyos + user vyos + } + default-route auto + description "FTTH 100/50MBit" + dhcpv6-options { + pd 0 { + interface eth0.10 { + address 1 + sla-id 10 + } + interface eth0.20 { + address 1 + sla-id 20 + } + length 56 + } + } + ipv6 { + address { + autoconf + } + } + mtu 1492 + no-peer-dns + source-interface eth1.7 + } +} +nat { + destination { + rule 100 { + description HTTP(S) + destination { + port 80,443 + } + inbound-interface pppoe0 + log + protocol tcp + translation { + address 172.16.36.10 + } + } + rule 1000 { + destination { + port 3389 + } + disable + inbound-interface pppoe0 + protocol tcp + translation { + address 172.16.33.40 + } + } + rule 8000 { + destination { + port 10000 + } + inbound-interface pppoe0 + log + protocol udp + translation { + address 172.31.0.200 + } + } + } + source { + rule 100 { + log + outbound-interface pppoe0 + source { + address 172.16.32.0/19 + } + translation { + address masquerade + } + } + rule 200 { + outbound-interface pppoe0 + source { + address 172.16.100.0/24 + } + translation { + address masquerade + } + } + rule 300 { + outbound-interface pppoe0 + source { + address 172.31.0.0/24 + } + translation { + address masquerade + } + } + rule 400 { + outbound-interface pppoe0 + source { + address 172.18.200.0/21 + } + translation { + address masquerade + } + } + } +} +protocols { + static { + interface-route6 2000::/3 { + next-hop-interface pppoe0 { + } + } + route 10.0.0.0/8 { + blackhole { + distance 254 + } + } + route 169.254.0.0/16 { + blackhole { + distance 254 + } + } + route 172.16.0.0/12 { + blackhole { + distance 254 + } + } + route 192.168.0.0/16 { + blackhole { + distance 254 + } + } + } +} +service { + dhcp-server { + shared-network-name BACKBONE { + authoritative + subnet 172.16.37.0/24 { + default-router 172.16.37.254 + dns-server 172.16.254.30 + domain-name vyos.net + domain-search vyos.net + lease 86400 + ntp-server 172.16.254.30 + range 0 { + start 172.16.37.120 + stop 172.16.37.149 + } + static-mapping AP1.wue3 { + ip-address 172.16.37.231 + mac-address 18:e8:29:6c:c3:a5 + } + } + } + shared-network-name GUEST { + authoritative + subnet 172.31.0.0/24 { + default-router 172.31.0.254 + dns-server 172.31.0.254 + domain-name vyos.net + domain-search vyos.net + lease 86400 + range 0 { + start 172.31.0.100 + stop 172.31.0.199 + } + static-mapping host01 { + ip-address 172.31.0.200 + mac-address 00:50:00:00:00:01 + } + static-mapping host02 { + ip-address 172.31.0.184 + mac-address 00:50:00:00:00:02 + } + } + } + shared-network-name IOT { + authoritative + subnet 172.16.35.0/24 { + default-router 172.16.35.254 + dns-server 172.16.254.30 + domain-name vyos.net + domain-search vyos.net + lease 86400 + ntp-server 172.16.254.30 + range 0 { + start 172.16.35.101 + stop 172.16.35.149 + } + } + } + shared-network-name LAN { + authoritative + subnet 172.16.33.0/24 { + default-router 172.16.33.254 + dns-server 172.16.254.30 + domain-name vyos.net + domain-search vyos.net + lease 86400 + ntp-server 172.16.254.30 + range 0 { + start 172.16.33.100 + stop 172.16.33.189 + } + } + } + } + dns { + forwarding { + allow-from 172.16.0.0/12 + cache-size 0 + domain 16.172.in-addr.arpa { + addnta + recursion-desired + server 172.16.100.10 + server 172.16.100.20 + server 172.16.110.30 + } + domain 18.172.in-addr.arpa { + addnta + recursion-desired + server 172.16.100.10 + server 172.16.100.20 + server 172.16.110.30 + } + domain vyos.net { + addnta + recursion-desired + server 172.16.100.20 + server 172.16.100.10 + server 172.16.110.30 + } + ignore-hosts-file + listen-address 172.16.254.30 + listen-address 172.31.0.254 + negative-ttl 60 + } + } + lldp { + legacy-protocols { + cdp + } + snmp { + enable + } + } + mdns { + repeater { + interface eth0.35 + interface eth0.10 + } + } + router-advert { + interface eth0.10 { + prefix ::/64 { + preferred-lifetime 2700 + valid-lifetime 5400 + } + } + interface eth0.20 { + prefix ::/64 { + preferred-lifetime 2700 + valid-lifetime 5400 + } + } + } + snmp { + community fooBar { + authorization ro + network 172.16.100.0/24 + } + contact "VyOS maintainers and contributors <maintainers@vyos.io>" + listen-address 172.16.254.30 { + port 161 + } + location "The Internet" + } + ssh { + disable-host-validation + port 22 + } +} +system { + config-management { + commit-revisions 200 + } + conntrack { + expect-table-size 2048 + hash-size 32768 + modules { + sip { + disable + } + } + table-size 262144 + timeout { + icmp 30 + other 600 + udp { + other 300 + stream 300 + } + } + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.net + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + } + } + name-server 172.16.254.30 + ntp { + allow-clients { + address 172.16.0.0/12 + } + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + option { + ctrl-alt-delete ignore + reboot-on-panic + startup-beep + } + syslog { + global { + facility all { + level debug + } + facility protocols { + level debug + } + } + host 172.16.100.1 { + facility all { + level warning + } + } + } + time-zone Europe/Berlin +} +traffic-policy { + shaper QoS { + bandwidth 50mbit + default { + bandwidth 100% + burst 15k + queue-limit 1000 + queue-type fq-codel + } + } +} +zone-policy { + zone DMZ { + default-action drop + from GUEST { + firewall { + name GUEST-DMZ + } + } + from LAN { + firewall { + name LAN-DMZ + } + } + from LOCAL { + firewall { + name LOCAL-DMZ + } + } + from WAN { + firewall { + name WAN-DMZ + } + } + interface eth0.50 + } + zone GUEST { + default-action drop + from DMZ { + firewall { + name DMZ-GUEST + } + } + from IOT { + firewall { + name IOT-GUEST + } + } + from LAN { + firewall { + name LAN-GUEST + } + } + from LOCAL { + firewall { + ipv6-name ALLOW-ALL-6 + name LOCAL-GUEST + } + } + from WAN { + firewall { + ipv6-name ALLOW-ESTABLISHED-6 + name WAN-GUEST + } + } + interface eth0.20 + } + zone IOT { + default-action drop + from GUEST { + firewall { + name GUEST-IOT + } + } + from LAN { + firewall { + name LAN-IOT + } + } + from LOCAL { + firewall { + name LOCAL-IOT + } + } + from WAN { + firewall { + name WAN-IOT + } + } + interface eth0.35 + } + zone LAN { + default-action drop + from DMZ { + firewall { + name DMZ-LAN + } + } + from GUEST { + firewall { + name GUEST-LAN + } + } + from IOT { + firewall { + name IOT-LAN + } + } + from LOCAL { + firewall { + ipv6-name ALLOW-ALL-6 + name LOCAL-LAN + } + } + from WAN { + firewall { + ipv6-name ALLOW-ESTABLISHED-6 + name WAN-LAN + } + } + interface eth0.5 + interface eth0.10 + interface eth0.100 + interface eth0.201 + interface eth0.202 + interface eth0.203 + interface eth0.204 + } + zone LOCAL { + default-action drop + from DMZ { + firewall { + name DMZ-LOCAL + } + } + from GUEST { + firewall { + ipv6-name ALLOW-ESTABLISHED-6 + name GUEST-LOCAL + } + } + from IOT { + firewall { + name IOT-LOCAL + } + } + from LAN { + firewall { + ipv6-name ALLOW-ALL-6 + name LAN-LOCAL + } + } + from WAN { + firewall { + ipv6-name WAN-LOCAL-6 + name WAN-LOCAL + } + } + local-zone + } + zone WAN { + default-action drop + from DMZ { + firewall { + name DMZ-WAN + } + } + from GUEST { + firewall { + ipv6-name ALLOW-ALL-6 + name GUEST-WAN + } + } + from IOT { + firewall { + name IOT-WAN + } + } + from LAN { + firewall { + ipv6-name ALLOW-ALL-6 + name LAN-WAN + } + } + from LOCAL { + firewall { + ipv6-name ALLOW-ALL-6 + name LOCAL-WAN + } + } + interface pppoe0 + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@18:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@6:salt@1:snmp@2:ssh@2:sstp@3:system@20:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3-beta-202101091250 diff --git a/smoketest/configs/dialup-router-medium-vpn b/smoketest/configs/dialup-router-medium-vpn new file mode 100644 index 0000000..5032800 --- /dev/null +++ b/smoketest/configs/dialup-router-medium-vpn @@ -0,0 +1,718 @@ +firewall { + all-ping enable + broadcast-ping disable + config-trap disable + ipv6-receive-redirects disable + ipv6-src-route disable + ip-src-route disable + log-martians enable + name test_tcp_flags { + rule 1 { + action drop + protocol tcp + tcp { + flags SYN,ACK,!RST,!FIN + } + } + } + options { + interface vtun0 { + adjust-mss 1380 + } + interface vtun1 { + adjust-mss 1380 + } + interface vtun2 { + adjust-mss 1380 + } + interface wg0 { + adjust-mss 1380 + } + interface wg1 { + adjust-mss 1380 + } + } + receive-redirects disable + send-redirects enable + source-validation disable + syn-cookies disable + twa-hazards-protection enable +} +high-availability { + vrrp { + group LAN { + hello-source-address 192.168.0.250 + interface eth1 + peer-address 192.168.0.251 + priority 200 + virtual-address 192.168.0.1/24 + vrid 1 + } + sync-group failover-group { + member LAN + } + } +} +interfaces { + ethernet eth0 { + duplex auto + mtu 9000 + offload-options { + generic-receive on + generic-segmentation on + scatter-gather on + tcp-segmentation on + } + pppoe 0 { + default-route auto + mtu 1500 + name-server auto + password password + user-id vyos + password vyos + } + smp-affinity auto + speed auto + } + ethernet eth1 { + address 192.168.0.250/24 + duplex auto + ip { + source-validation strict + } + mtu 9000 + offload-options { + generic-receive on + generic-segmentation on + scatter-gather on + tcp-segmentation on + } + policy { + route LAN-POLICY-BASED-ROUTING + ipv6-route LAN6-POLICY-BASED-ROUTING + } + smp-affinity auto + speed auto + } + loopback lo { + } + openvpn vtun0 { + encryption aes256 + hash sha512 + ip { + source-validation strict + } + keep-alive { + failure-count 3 + interval 30 + } + mode client + openvpn-option "comp-lzo adaptive" + openvpn-option fast-io + openvpn-option persist-key + openvpn-option "reneg-sec 86400" + persistent-tunnel + remote-host 192.0.2.10 + tls { + ca-cert-file /config/auth/ovpn_test_chain.pem + cert-file /config/auth/ovpn_test_server.pem + crl-file /config/auth/ovpn_test_ca.crl + key-file /config/auth/ovpn_test_server.key + auth-file /config/auth/ovpn_test_tls_auth.key + } + } + openvpn vtun1 { + authentication { + password vyos1 + username vyos1 + } + encryption aes256 + hash sha1 + keep-alive { + failure-count 3 + interval 30 + } + mode client + openvpn-option "comp-lzo adaptive" + openvpn-option "tun-mtu 1500" + openvpn-option "tun-mtu-extra 32" + openvpn-option "mssfix 1300" + openvpn-option persist-key + openvpn-option "mute 10" + openvpn-option route-nopull + openvpn-option fast-io + openvpn-option "reneg-sec 86400" + persistent-tunnel + protocol udp + remote-host 01.foo.com + remote-port 1194 + tls { + ca-cert-file /config/auth/ovpn_test_chain.pem + auth-file /config/auth/ovpn_test_tls_auth.key + } + } + openvpn vtun2 { + authentication { + password vyos2 + username vyos2 + } + disable + encryption aes256 + hash sha512 + keep-alive { + failure-count 3 + interval 30 + } + mode client + openvpn-option "tun-mtu 1500" + openvpn-option "tun-mtu-extra 32" + openvpn-option "mssfix 1300" + openvpn-option persist-key + openvpn-option "mute 10" + openvpn-option route-nopull + openvpn-option fast-io + openvpn-option remote-random + openvpn-option "reneg-sec 86400" + persistent-tunnel + protocol udp + remote-host 01.myvpn.com + remote-host 02.myvpn.com + remote-host 03.myvpn.com + remote-port 1194 + tls { + ca-cert-file /config/auth/ovpn_test_ca.pem + auth-file /config/auth/ovpn_test_tls_auth.key + } + } + wireguard wg0 { + address 192.168.10.1/24 + peer red { + allowed-ips 192.168.10.4/32 + persistent-keepalive 20 + preshared-key CumyXX7osvUT9AwnS+m2TEfCaL0Ptc2LfuZ78Sujuk8= + pubkey ALGWvMJCKpHF2tVH3hEIHqUe9iFfAmZATUUok/WQzks= + } + peer green { + allowed-ips 192.168.10.21/32 + persistent-keepalive 25 + preshared-key LQ9qmlTh9G4nZu4UgElxRUwg7JB/qoV799aADJOijnY= + pubkey 5iQUD3VoCDBTPXAPHOwUJ0p7xzKGHEY/wQmgvBVmaFI= + } + peer blue { + allowed-ips 192.168.10.3/32 + persistent-keepalive 20 + preshared-key ztFDOY9UyaDvn8N3X97SFMDwIfv7EEfuUIPP2yab6UI= + pubkey G4pZishpMRrLmd96Kr6V7LIuNGdcUb81gWaYZ+FWkG0= + } + peer pink { + allowed-ips 192.168.10.14/32 + allowed-ips 192.168.10.16/32 + persistent-keepalive 25 + preshared-key Qi9Odyx0/5itLPN5C5bEy3uMX+tmdl15QbakxpKlWqQ= + pubkey i4qNPmxyy9EETL4tIoZOLKJF4p7IlVmpAE15gglnAk4= + } + port 7777 + } + wireguard wg1 { + address 10.89.90.2/30 + peer sam { + allowed-ips 10.1.1.0/24 + allowed-ips 10.89.90.1/32 + endpoint 192.0.2.45:1200 + persistent-keepalive 20 + preshared-key XpFtzx2Z+nR8pBv9/sSf7I94OkZkVYTz0AeU5Q/QQUE= + pubkey v5zfKGvH6W/lfDXJ0en96lvKo1gfFxMUWxe02+Fj5BU= + } + port 7778 + } +} +nat { + destination { + rule 50 { + destination { + port 49371 + } + inbound-interface pppoe0 + protocol tcp_udp + translation { + address 192.168.0.5 + } + } + rule 51 { + destination { + port 58050-58051 + } + inbound-interface pppoe0 + protocol tcp + translation { + address 192.168.0.5 + } + } + rule 52 { + destination { + port 22067-22070 + } + inbound-interface pppoe0 + protocol tcp + translation { + address 192.168.0.5 + } + } + rule 53 { + destination { + port 34342 + } + inbound-interface pppoe0 + protocol tcp_udp + translation { + address 192.168.0.121 + } + } + rule 54 { + destination { + port 45459 + } + inbound-interface pppoe0 + protocol tcp_udp + translation { + address 192.168.0.120 + } + } + rule 55 { + destination { + port 22 + } + inbound-interface pppoe0 + protocol tcp + translation { + address 192.168.0.5 + } + } + rule 56 { + destination { + port 8920 + } + inbound-interface pppoe0 + protocol tcp + translation { + address 192.168.0.5 + } + } + rule 60 { + destination { + port 80,443 + } + inbound-interface pppoe0 + protocol tcp + translation { + address 192.168.0.5 + } + } + rule 70 { + destination { + port 5001 + } + inbound-interface pppoe0 + protocol tcp + translation { + address 192.168.0.5 + } + } + rule 80 { + destination { + port 25 + } + inbound-interface pppoe0 + protocol tcp + translation { + address 192.168.0.5 + } + } + rule 90 { + destination { + port 8123 + } + inbound-interface pppoe0 + protocol tcp + translation { + address 192.168.0.7 + } + } + rule 91 { + destination { + port 1880 + } + inbound-interface pppoe0 + protocol tcp + translation { + address 192.168.0.7 + } + } + rule 500 { + destination { + address !192.168.0.0/24 + port 53 + } + inbound-interface eth1 + protocol tcp_udp + source { + address !192.168.0.1-192.168.0.5 + } + translation { + address 192.168.0.1 + } + } + } + source { + rule 1000 { + outbound-interface pppoe0 + translation { + address masquerade + } + } + rule 2000 { + outbound-interface vtun0 + source { + address 192.168.0.0/16 + } + translation { + address masquerade + } + } + rule 3000 { + outbound-interface vtun1 + translation { + address masquerade + } + } + } +} +policy { + ipv6-route LAN6-POLICY-BASED-ROUTING { + rule 10 { + destination { + } + disable + set { + table 10 + } + source { + address 2002::1 + } + } + rule 20 { + destination { + } + set { + table 100 + } + source { + address 2008::f + } + } + } + prefix-list user2-routes { + rule 1 { + action permit + prefix 10.1.1.0/24 + } + } + prefix-list user1-routes { + rule 1 { + action permit + prefix 192.168.0.0/24 + } + } + route LAN-POLICY-BASED-ROUTING { + rule 10 { + destination { + } + disable + set { + table 10 + } + source { + address 192.168.0.119/32 + } + } + rule 20 { + destination { + } + set { + table 100 + } + source { + address 192.168.0.240 + } + } + } + route-map rm-static-to-bgp { + rule 10 { + action permit + match { + ip { + address { + prefix-list user1-routes + } + } + } + } + rule 100 { + action deny + } + } +} +protocols { + bgp 64590 { + address-family { + ipv4-unicast { + redistribute { + connected { + route-map rm-static-to-bgp + } + } + } + } + neighbor 10.89.90.1 { + address-family { + ipv4-unicast { + nexthop-self + prefix-list { + export user1-routes + import user2-routes + } + soft-reconfiguration { + inbound + } + } + } + password ericandre2020 + remote-as 64589 + } + parameters { + log-neighbor-changes + router-id 10.89.90.2 + } + } + static { + interface-route 100.64.160.23/32 { + next-hop-interface pppoe0 { + } + } + interface-route 100.64.165.25/32 { + next-hop-interface pppoe0 { + } + } + interface-route 100.64.165.26/32 { + next-hop-interface pppoe0 { + } + } + interface-route 100.64.198.0/24 { + next-hop-interface vtun0 { + } + } + table 10 { + interface-route 0.0.0.0/0 { + next-hop-interface vtun1 { + } + } + } + table 100 { + route 0.0.0.0/0 { + next-hop 192.168.10.5 { + } + } + } + } +} +service { + conntrack-sync { + accept-protocol tcp,udp,icmp + disable-external-cache + event-listen-queue-size 8 + expect-sync all + failover-mechanism { + vrrp { + sync-group failover-group + } + } + interface eth1 { + peer 192.168.0.251 + } + sync-queue-size 8 + } + dhcp-server { + shared-network-name LAN { + authoritative + subnet 192.168.0.0/24 { + default-router 192.168.0.1 + dns-server 192.168.0.1 + domain-name vyos.net + domain-search vyos.net + failover { + local-address 192.168.0.250 + name DHCP02 + peer-address 192.168.0.251 + status primary + } + lease 86400 + range LANDynamic { + start 192.168.0.200 + stop 192.168.0.240 + } + static-mapping IPTV { + ip-address 192.168.0.104 + mac-address 00:50:01:31:b5:f6 + } + static-mapping McPrintus { + ip-address 192.168.0.60 + mac-address 00:50:01:58:ac:95 + static-mapping-parameters "option domain-name-servers 192.168.0.6,192.168.0.17;" + } + static-mapping Audio { + ip-address 192.168.0.107 + mac-address 00:50:01:dc:91:14 + } + static-mapping Mobile01 { + ip-address 192.168.0.109 + mac-address 00:50:01:bc:ac:51 + static-mapping-parameters "option domain-name-servers 192.168.0.6,192.168.0.17;" + } + static-mapping sand { + ip-address 192.168.0.110 + mac-address 00:50:01:af:c5:d2 + } + static-mapping pearTV { + ip-address 192.168.0.101 + mac-address 00:50:01:ba:62:79 + } + static-mapping camera1 { + ip-address 192.168.0.11 + mac-address 00:50:01:70:b9:4d + static-mapping-parameters "option domain-name-servers 192.168.0.6,192.168.0.17;" + } + static-mapping camera2 { + ip-address 192.168.0.12 + mac-address 00:50:01:70:b7:4f + static-mapping-parameters "option domain-name-servers 192.168.0.6,192.168.0.17;" + } + } + } + } + dns { + forwarding { + allow-from 192.168.0.0/16 + cache-size 8192 + dnssec off + listen-address 192.168.0.1 + name-server 100.64.0.1 + name-server 100.64.0.2 + } + } + snmp { + community AwesomeCommunity { + authorization ro + client 127.0.0.1 + network 192.168.0.0/24 + } + } + ssh { + access-control { + allow { + user vyos + } + } + client-keepalive-interval 60 + listen-address 192.168.0.1 + listen-address 192.168.10.1 + listen-address 192.168.0.250 + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + ip { + arp { + table-size 1024 + } + } + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + name-server 192.168.0.1 + name-servers-dhcp pppoe0 + ntp { + allow-clients { + address 192.168.0.0/16 + } + listen-address 192.168.0.1 + listen-address 192.168.0.250 + server nz.pool.ntp.org { + prefer + } + } + options { + beep-if-fully-booted + ctrl-alt-del-action ignore + reboot-on-panic true + } + static-host-mapping { + host-name host104.vyos.net { + inet 192.168.0.104 + } + host-name host60.vyos.net { + inet 192.168.0.60 + } + host-name host107.vyos.net { + inet 192.168.0.107 + } + host-name host109.vyos.net { + inet 192.168.0.109 + } + } + sysctl { + custom net.core.default_qdisc { + value fq + } + custom net.ipv4.tcp_congestion_control { + value bbr + } + } + syslog { + global { + facility all { + level info + } + } + host 192.168.0.252 { + facility all { + level debug + protocol udp + } + } + } + task-scheduler { + task Update-Blacklists { + executable { + path /config/scripts/vyos-foo-update.script + } + interval 3h + } + } + time-zone Pacific/Auckland +} +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.6 */ diff --git a/smoketest/configs/dialup-router-wireguard-ipv6 b/smoketest/configs/dialup-router-wireguard-ipv6 new file mode 100644 index 0000000..0585821 --- /dev/null +++ b/smoketest/configs/dialup-router-wireguard-ipv6 @@ -0,0 +1,1645 @@ +firewall { + all-ping enable + broadcast-ping disable + config-trap disable + group { + address-group DMZ-WEBSERVER { + description "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + address 172.16.36.10 + address 172.16.36.40 + address 172.16.36.20 + } + address-group DMZ-RDP-SERVER { + description "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + address 172.16.33.40 + } + address-group DOMAIN-CONTROLLER { + address 172.16.100.10 + address 172.16.100.20 + address 172.16.110.30 + } + address-group VIDEO { + address 172.16.33.211 + address 172.16.33.212 + address 172.16.33.213 + address 172.16.33.214 + } + ipv6-network-group LOCAL-ADDRESSES { + network ff02::/64 + network fe80::/10 + } + network-group SSH-IN-ALLOW { + network 100.65.150.0/23 + network 100.64.69.205/32 + network 100.64.8.67/32 + network 100.64.55.1/32 + } + } + ipv6-name ALLOW-ALL-6 { + default-action accept + description "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + } + ipv6-name ALLOW-BASIC-6 { + default-action drop + enable-default-log + description "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + rule 1 { + description "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + action accept + state { + established enable + related enable + } + } + rule 2 { + description "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + action drop + state { + invalid enable + } + } + rule 10 { + action accept + protocol icmpv6 + } + } + ipv6-name ALLOW-ESTABLISHED-6 { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + state { + invalid enable + } + } + rule 10 { + action accept + destination { + group { + network-group LOCAL-ADDRESSES + } + } + protocol icmpv6 + source { + address fe80::/10 + } + } + rule 20 { + action accept + icmpv6 { + type echo-request + } + protocol icmpv6 + } + rule 21 { + action accept + icmpv6 { + type destination-unreachable + } + protocol icmpv6 + } + rule 22 { + action accept + icmpv6 { + type packet-too-big + } + protocol icmpv6 + } + rule 23 { + action accept + icmpv6 { + type time-exceeded + } + protocol icmpv6 + } + rule 24 { + action accept + icmpv6 { + type parameter-problem + } + protocol icmpv6 + } + } + ipv6-name WAN-LOCAL-6 { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + state { + invalid enable + } + } + rule 10 { + action accept + destination { + address ff02::/64 + } + protocol icmpv6 + source { + address fe80::/10 + } + } + rule 50 { + action accept + destination { + address fe80::/10 + port 546 + } + protocol udp + source { + address fe80::/10 + port 547 + } + } + } + ipv6-receive-redirects disable + ipv6-src-route disable + ip-src-route disable + log-martians enable + name DMZ-GUEST { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + } + name DMZ-LAN { + default-action drop + enable-default-log + description "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + rule 1 { + description "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + description "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + log enable + state { + invalid enable + } + } + rule 100 { + action accept + description "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + destination { + group { + address-group DOMAIN-CONTROLLER + } + port 123,389,636 + } + protocol tcp_udp + } + rule 300 { + description "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + action accept + destination { + group { + address-group DMZ-RDP-SERVER + } + port 3389 + } + protocol tcp_udp + source { + address 172.16.36.20 + } + } + } + name DMZ-LOCAL { + default-action drop + enable-default-log + description "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 50 { + description "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + action accept + destination { + address 172.16.254.30 + port 53 + } + protocol tcp_udp + } + rule 123 { + action accept + destination { + port 123 + } + protocol udp + } + } + name DMZ-WAN { + default-action accept + description "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + } + name GUEST-DMZ { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + } + name GUEST-LAN { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + } + name GUEST-LOCAL { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 10 { + action accept + destination { + address 172.31.0.254 + port 53 + } + protocol tcp_udp + } + rule 11 { + action accept + destination { + port 67 + } + protocol udp + } + rule 15 { + action accept + destination { + address 172.31.0.254 + } + protocol icmp + } + rule 100 { + action accept + destination { + address 172.31.0.254 + port 80,443 + } + protocol tcp + } + } + name GUEST-WAN { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 25 { + action accept + destination { + port 25,587 + } + protocol tcp + } + rule 53 { + action accept + destination { + port 53 + } + protocol tcp_udp + } + rule 60 { + action accept + source { + address 172.31.0.200 + } + } + rule 80 { + action accept + source { + address 172.31.0.200 + } + } + rule 100 { + action accept + protocol icmp + } + rule 110 { + action accept + destination { + port 110,995 + } + protocol tcp + } + rule 123 { + action accept + destination { + port 123 + } + protocol udp + } + rule 143 { + action accept + destination { + port 143,993 + } + protocol tcp + } + rule 200 { + action accept + destination { + port 80,443 + } + protocol tcp + } + rule 500 { + action accept + destination { + port 500,4500 + } + protocol udp + } + rule 600 { + action accept + destination { + port 5222-5224 + } + protocol tcp + } + rule 601 { + action accept + destination { + port 3478-3497,4500,16384-16387,16393-16402 + } + protocol udp + } + rule 1000 { + action accept + source { + address 172.31.0.184 + } + } + } + name LAN-DMZ { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 22 { + action accept + destination { + port 22 + } + protocol tcp + } + rule 100 { + action accept + destination { + group { + address-group DMZ-WEBSERVER + } + port 22 + } + protocol tcp + } + } + name LAN-GUEST { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + } + name LAN-LOCAL { + default-action accept + } + name LAN-WAN { + default-action accept + rule 90 { + action accept + destination { + address 100.65.150.0/23 + port 25 + } + protocol tcp_udp + source { + group { + address-group VIDEO + } + } + } + rule 100 { + action drop + source { + group { + address-group VIDEO + } + } + } + } + name LOCAL-DMZ { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + destination { + address 172.16.36.40 + port 80,443 + } + protocol tcp + } + } + name LOCAL-GUEST { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 5 { + action accept + protocol icmp + } + rule 300 { + action accept + destination { + port 1900 + } + protocol udp + } + } + name LOCAL-LAN { + default-action accept + } + name LOCAL-WAN { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 10 { + action accept + protocol icmp + } + rule 50 { + action accept + destination { + port 53 + } + protocol tcp_udp + } + rule 80 { + action accept + destination { + port 80,443 + } + protocol tcp + } + rule 123 { + action accept + destination { + port 123 + } + protocol udp + } + rule 800 { + action accept + destination { + address 100.65.151.213 + } + protocol udp + } + rule 805 { + action accept + destination { + address 100.65.151.2 + } + protocol all + } + rule 1010 { + action accept + destination { + address 100.64.69.205 + port 7705 + } + protocol udp + source { + port 7705 + } + } + rule 1990 { + action accept + destination { + address 100.64.55.1 + port 10666 + } + protocol udp + } + rule 2000 { + action accept + destination { + address 100.64.39.249 + } + } + rule 10200 { + action accept + destination { + address 100.64.89.98 + port 10200 + } + protocol udp + source { + port 10200 + } + } + } + name WAN-DMZ { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + destination { + address 172.16.36.10 + port 80,443 + } + protocol tcp + } + } + name WAN-GUEST { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 1000 { + action accept + destination { + address 172.31.0.184 + } + } + rule 8000 { + action accept + destination { + address 172.31.0.200 + port 10000 + } + protocol udp + } + } + name WAN-LAN { + default-action drop + description "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + enable-default-log + rule 1 { + action accept + description "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 1000 { + action accept + destination { + address 172.16.33.40 + port 3389 + } + protocol tcp + source { + group { + network-group SSH-IN-ALLOW + } + } + } + } + name WAN-LOCAL { + default-action drop + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 22 { + action accept + destination { + port 22 + } + protocol tcp + source { + group { + network-group SSH-IN-ALLOW + } + } + } + rule 1990 { + action accept + destination { + port 10666 + } + protocol udp + source { + address 100.64.55.1 + } + } + rule 10000 { + action accept + destination { + port 80,443 + } + protocol tcp + } + rule 10100 { + action accept + destination { + port 10100 + } + protocol udp + source { + port 10100 + } + } + rule 10200 { + action accept + destination { + port 10200 + } + protocol udp + source { + address 100.64.89.98 + port 10200 + } + } + } + options { + interface pppoe0 { + adjust-mss 1452 + adjust-mss6 1432 + } + } + receive-redirects disable + send-redirects enable + source-validation disable + syn-cookies enable + twa-hazards-protection disable +} +interfaces { + dummy dum0 { + address 172.16.254.30/32 + } + ethernet eth0 { + duplex auto + offload { + gro + gso + sg + tso + } + ring-buffer { + rx 256 + tx 256 + } + speed auto + vif 5 { + address 172.16.37.254/24 + ip { + ospf { + authentication { + md5 { + key-id 10 { + md5-key ospf + } + } + } + dead-interval 40 + hello-interval 10 + priority 1 + retransmit-interval 5 + transmit-delay 1 + } + } + } + vif 10 { + address 172.16.33.254/24 + address 172.16.40.254/24 + } + vif 50 { + address 172.16.36.254/24 + } + } + ethernet eth1 { + duplex auto + offload { + gro + gso + sg + tso + } + speed auto + vif 20 { + address 172.31.0.254/24 + } + } + ethernet eth2 { + disable + duplex auto + offload { + gro + gso + sg + tso + } + speed auto + } + ethernet eth3 { + duplex auto + offload { + gro + gso + sg + tso + } + ring-buffer { + rx 256 + tx 256 + } + speed auto + vif 7 { + } + } + loopback lo { + address 172.16.254.30/32 + } + pppoe pppoe0 { + authentication { + password vyos + user vyos + } + default-route force + dhcpv6-options { + pd 0 { + interface eth0.10 { + address 1 + sla-id 10 + } + interface eth1.20 { + address 1 + sla-id 20 + } + length 56 + } + } + ipv6 { + address { + autoconf + } + } + no-peer-dns + source-interface eth3.7 + } + wireguard wg100 { + address 172.16.252.128/31 + mtu 1500 + peer HR6 { + address 100.65.151.213 + allowed-ips 0.0.0.0/0 + port 10100 + pubkey yLpi+UZuI019bmWH2h5fX3gStbpPPPLgEoYMyrdkOnQ= + } + port 10100 + } + wireguard wg200 { + address 172.16.252.130/31 + mtu 1500 + peer WH56 { + address 80.151.69.205 + allowed-ips 0.0.0.0/0 + port 10200 + pubkey XQbkj6vnKKBJfJQyThXysU0iGxCvEOEb31kpaZgkrD8= + } + port 10200 + } + wireguard wg666 { + address 172.29.0.1/31 + mtu 1500 + peer WH34 { + address 100.65.55.1 + allowed-ips 0.0.0.0/0 + port 10666 + pubkey yaTN4+xAafKM04D+Baeg5GWfbdaw35TE9HQivwRgAk0= + } + port 10666 + } +} +nat { + destination { + rule 8000 { + destination { + port 10000 + } + inbound-interface pppoe0 + protocol udp + translation { + address 172.31.0.200 + } + } + } + source { + rule 50 { + outbound-interface pppoe0 + source { + address 100.64.0.0/24 + } + translation { + address masquerade + } + } + rule 100 { + outbound-interface pppoe0 + source { + address 172.16.32.0/21 + } + translation { + address masquerade + } + } + rule 200 { + outbound-interface pppoe0 + source { + address 172.16.100.0/24 + } + translation { + address masquerade + } + } + rule 300 { + outbound-interface pppoe0 + source { + address 172.31.0.0/24 + } + translation { + address masquerade + } + } + rule 400 { + outbound-interface pppoe0 + source { + address 172.18.200.0/21 + } + translation { + address masquerade + } + } + rule 1000 { + destination { + address 192.168.189.0/24 + } + outbound-interface wg666 + source { + address 172.16.32.0/21 + } + translation { + address 172.29.0.1 + } + } + rule 1001 { + destination { + address 192.168.189.0/24 + } + outbound-interface wg666 + source { + address 172.16.100.0/24 + } + translation { + address 172.29.0.1 + } + } + } +} +policy { + route-map MAP-OSPF-CONNECTED { + rule 1 { + action deny + match { + interface eth1.20 + } + } + rule 20 { + action permit + match { + interface eth0.10 + } + } + rule 40 { + action permit + match { + interface eth0.50 + } + } + } +} +protocols { + bfd { + peer 172.16.252.129 { + } + peer 172.16.252.131 { + } + peer 172.18.254.201 { + } + } + bgp 64503 { + address-family { + ipv4-unicast { + network 172.16.32.0/21 { + } + network 172.16.100.0/24 { + } + network 172.16.252.128/31 { + } + network 172.16.252.130/31 { + } + network 172.16.254.30/32 { + } + network 172.18.0.0/16 { + } + } + } + neighbor 172.16.252.129 { + peer-group WIREGUARD + } + neighbor 172.16.252.131 { + peer-group WIREGUARD + } + neighbor 172.18.254.201 { + address-family { + ipv4-unicast { + nexthop-self { + } + } + } + bfd { + } + remote-as 64503 + update-source dum0 + } + parameters { + default { + no-ipv4-unicast + } + log-neighbor-changes + } + peer-group WIREGUARD { + address-family { + ipv4-unicast { + soft-reconfiguration { + inbound + } + } + } + bfd + remote-as external + } + timers { + holdtime 30 + keepalive 10 + } + } + ospf { + area 0 { + network 172.16.254.30/32 + network 172.16.37.0/24 + network 172.18.201.0/24 + network 172.18.202.0/24 + network 172.18.203.0/24 + network 172.18.204.0/24 + } + default-information { + originate { + always + metric-type 2 + } + } + log-adjacency-changes { + detail + } + parameters { + abr-type cisco + router-id 172.16.254.30 + } + passive-interface default + passive-interface-exclude eth0.5 + redistribute { + connected { + metric-type 2 + route-map MAP-OSPF-CONNECTED + } + } + } + static { + interface-route6 2000::/3 { + next-hop-interface pppoe0 { + } + } + route 10.0.0.0/8 { + blackhole { + distance 254 + } + } + route 169.254.0.0/16 { + blackhole { + distance 254 + } + } + route 172.16.0.0/12 { + blackhole { + distance 254 + } + } + route 172.16.32.0/21 { + blackhole { + } + } + route 172.18.0.0/16 { + blackhole { + } + } + route 172.29.0.2/31 { + next-hop 172.29.0.0 { + } + } + route 192.168.0.0/16 { + blackhole { + distance 254 + } + } + route 192.168.189.0/24 { + next-hop 172.29.0.0 { + } + } + } +} +service { + dhcp-server { + shared-network-name BACKBONE { + authoritative + subnet 172.16.37.0/24 { + default-router 172.16.37.254 + domain-name vyos.net + domain-search vyos.net + lease 86400 + name-server 172.16.254.30 + ntp-server 172.16.254.30 + range 0 { + start 172.16.37.120 + stop 172.16.37.149 + } + static-mapping AP1 { + ip-address 172.16.37.231 + mac-address 02:00:00:00:ee:18 + } + static-mapping AP2 { + ip-address 172.16.37.232 + mac-address 02:00:00:00:52:84 + } + static-mapping AP3 { + ip-address 172.16.37.233 + mac-address 02:00:00:00:51:c0 + } + static-mapping AP4 { + ip-address 172.16.37.234 + mac-address 02:00:00:00:e6:fc + } + static-mapping AP5 { + ip-address 172.16.37.235 + mac-address 02:00:00:00:c3:50 + } + } + } + shared-network-name GUEST { + authoritative + subnet 172.31.0.0/24 { + default-router 172.31.0.254 + domain-name vyos.net + domain-search vyos.net + lease 86400 + name-server 172.31.0.254 + range 0 { + start 172.31.0.101 + stop 172.31.0.199 + } + } + } + shared-network-name LAN { + authoritative + subnet 172.16.33.0/24 { + default-router 172.16.33.254 + domain-name vyos.net + domain-search vyos.net + lease 86400 + name-server 172.16.254.30 + ntp-server 172.16.254.30 + range 0 { + start 172.16.33.100 + stop 172.16.33.189 + } + static-mapping one { + ip-address 172.16.33.221 + mac-address 02:00:00:00:eb:a6 + } + static-mapping two { + ip-address 172.16.33.211 + mac-address 02:00:00:00:58:90 + } + static-mapping three { + ip-address 172.16.33.212 + mac-address 02:00:00:00:12:c7 + } + static-mapping four { + ip-address 172.16.33.214 + mac-address 02:00:00:00:c4:33 + } + } + } + } + dns { + dynamic { + interface pppoe0 { + service vyos { + host-name r1.vyos.net + login vyos-vyos + password vyos + protocol dyndns2 + server dyndns.vyos.io + } + } + } + forwarding { + allow-from 172.16.0.0/12 + domain 16.172.in-addr.arpa { + addnta + recursion-desired + server 172.16.100.10 + server 172.16.100.20 + } + domain 18.172.in-addr.arpa { + addnta + recursion-desired + server 172.16.100.10 + server 172.16.100.20 + } + domain vyos.net { + addnta + recursion-desired + server 172.16.100.20 + server 172.16.100.10 + } + ignore-hosts-file + listen-address 172.16.254.30 + listen-address 172.31.0.254 + negative-ttl 60 + } + } + lldp { + legacy-protocols { + cdp + edp + fdp + sonmp + } + snmp { + enable + } + } + router-advert { + interface eth0.10 { + prefix ::/64 { + preferred-lifetime 2700 + valid-lifetime 5400 + } + } + interface eth1.20 { + prefix ::/64 { + preferred-lifetime 2700 + valid-lifetime 5400 + } + } + } + snmp { + community ro-community { + authorization ro + network 172.16.100.0/24 + } + contact "VyOS" + listen-address 172.16.254.30 { + port 161 + } + location "CLOUD" + } + ssh { + disable-host-validation + port 22 + } +} +system { + config-management { + commit-revisions 200 + } + conntrack { + expect-table-size 2048 + hash-size 32768 + modules { + ftp + h323 + nfs + pptp + sqlnet + tftp + } + table-size 262144 + timeout { + icmp 30 + other 600 + udp { + other 300 + stream 300 + } + } + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.net + host-name r1 + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + } + } + name-server 172.16.254.30 + ntp { + allow-clients { + address 172.16.0.0/12 + } + server time1.vyos.net { + } + server time2.vyos.net { + } + } + option { + ctrl-alt-delete ignore + performance latency + reboot-on-panic + startup-beep + } + syslog { + global { + facility all { + level debug + } + facility protocols { + level debug + } + } + host 172.16.100.1 { + facility all { + level warning + } + } + } + time-zone Europe/Berlin +} +traffic-policy { + shaper QoS { + bandwidth 50mbit + default { + bandwidth 100% + burst 15k + queue-limit 1000 + queue-type fq-codel + } + } +} +zone-policy { + zone DMZ { + default-action drop + from GUEST { + firewall { + name GUEST-DMZ + } + } + from LAN { + firewall { + name LAN-DMZ + } + } + from LOCAL { + firewall { + name LOCAL-DMZ + } + } + from WAN { + firewall { + name WAN-DMZ + } + } + interface eth0.50 + } + zone GUEST { + default-action drop + from DMZ { + firewall { + name DMZ-GUEST + } + } + from LAN { + firewall { + name LAN-GUEST + } + } + from LOCAL { + firewall { + ipv6-name ALLOW-ALL-6 + name LOCAL-GUEST + } + } + from WAN { + firewall { + ipv6-name ALLOW-ESTABLISHED-6 + name WAN-GUEST + } + } + interface eth1.20 + } + zone LAN { + default-action drop + from DMZ { + firewall { + name DMZ-LAN + } + } + from GUEST { + firewall { + name GUEST-LAN + } + } + from LOCAL { + firewall { + ipv6-name ALLOW-ALL-6 + name LOCAL-LAN + } + } + from WAN { + firewall { + ipv6-name ALLOW-ESTABLISHED-6 + name WAN-LAN + } + } + interface eth0.5 + interface eth0.10 + interface wg100 + interface wg200 + } + zone LOCAL { + default-action drop + from DMZ { + firewall { + name DMZ-LOCAL + } + } + from GUEST { + firewall { + ipv6-name ALLOW-ESTABLISHED-6 + name GUEST-LOCAL + } + } + from LAN { + firewall { + ipv6-name ALLOW-ALL-6 + name LAN-LOCAL + } + } + from WAN { + firewall { + ipv6-name WAN-LOCAL-6 + name WAN-LOCAL + } + } + local-zone + } + zone WAN { + default-action drop + from DMZ { + firewall { + name DMZ-WAN + } + } + from GUEST { + firewall { + ipv6-name ALLOW-ALL-6 + name GUEST-WAN + } + } + from LAN { + firewall { + ipv6-name ALLOW-ALL-6 + name LAN-WAN + } + } + from LOCAL { + firewall { + ipv6-name ALLOW-ALL-6 + name LOCAL-WAN + } + } + interface pppoe0 + interface wg666 + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:container@1:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.4 diff --git a/smoketest/configs/egp-igp-route-maps b/smoketest/configs/egp-igp-route-maps new file mode 100644 index 0000000..ca36691 --- /dev/null +++ b/smoketest/configs/egp-igp-route-maps @@ -0,0 +1,127 @@ +interfaces { + ethernet eth0 { + address 192.0.2.1/25 + duplex auto + smp-affinity auto + speed auto + } + ethernet eth1 { + address 192.0.2.129/25 + address 2001:db8::1234/64 + duplex auto + smp-affinity auto + speed auto + } + loopback lo { + } +} +policy { + route-map zebra-bgp { + rule 10 { + action permit + } + } + route-map zebra-isis { + rule 10 { + action permit + } + } + route-map zebra-ospf { + rule 10 { + action permit + } + } + route-map zebra-ospfv3 { + rule 10 { + action permit + } + } + route-map zebra-ripng { + rule 10 { + action permit + } + } + route-map zebra-static { + rule 10 { + action permit + } + } +} +protocols { + bgp 100 { + route-map zebra-bgp + } + isis { + interface eth0 { + } + net 49.0001.1921.6800.1002.00 + route-map zebra-isis + } + ospf { + area 0 { + network 192.0.2.0/25 + network 192.0.2.128/25 + } + log-adjacency-changes { + } + parameters { + abr-type cisco + router-id 1.1.1.1 + } + passive-interface default + passive-interface-exclude eth0 + passive-interface-exclude eth1 + route-map zebra-ospf + } + ospfv3 { + area 0 { + interface eth1 + } + parameters { + router-id 1.1.1.1 + } + route-map zebra-ospfv3 + } + ripng { + interface eth1 + route-map zebra-ripng + } + static { + route-map zebra-static + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + name-server 192.168.0.1 + syslog { + global { + archive { + file 5 + size 512 + } + facility all { + level info + } + } + } + time-zone Europe/Berlin +} +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.2 diff --git a/smoketest/configs/igmp-pim-small b/smoketest/configs/igmp-pim-small new file mode 100644 index 0000000..f433ff8 --- /dev/null +++ b/smoketest/configs/igmp-pim-small @@ -0,0 +1,84 @@ +interfaces { + ethernet eth0 { + duplex auto + speed auto + } + ethernet eth1 { + address 100.64.0.1/24 + duplex auto + speed auto + } + ethernet eth2 { + address 172.16.0.2/24 + duplex auto + speed auto + } +} +protocols { + igmp { + interface eth1 { + join 224.1.0.0 { + source 1.1.1.1 + source 1.1.1.2 + } + query-interval 1000 + query-max-response-time 30 + version 2 + } + } + pim { + interface eth1 { + } + interface eth2 { + } + rp { + address 172.16.255.1 { + group 224.0.0.0/4 + } + } + } +} +system { + config-management { + commit-revisions 200 + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.io + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + } + } + ntp { + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } + time-zone Europe/Berlin +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@18:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@7:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@20:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.0 diff --git a/smoketest/configs/ipoe-server b/smoketest/configs/ipoe-server new file mode 100644 index 0000000..fdd554b --- /dev/null +++ b/smoketest/configs/ipoe-server @@ -0,0 +1,115 @@ +interfaces { + ethernet eth0 { + address dhcp + } + ethernet eth1 { + address 192.168.0.1/24 + } + ethernet eth2 { + } + loopback lo { + } +} +nat { + source { + rule 100 { + outbound-interface eth0 + source { + address 192.168.0.0/24 + } + translation { + address masquerade + } + } + } +} +service { + ipoe-server { + authentication { + interface eth1 { + mac-address 08:00:27:2f:d8:06 { + rate-limit { + download 1000 + upload 500 + } + vlan-id 100 + } + } + interface eth2 { + mac-address 08:00:27:2f:d8:06 { + } + } + mode local + } + client-ip-pool { + name POOL1 { + gateway-address 192.0.2.1 + subnet 192.0.2.0/24 + } + } + client-ipv6-pool { + delegate 2001:db8:1::/48 { + delegation-prefix 56 + } + prefix 2001:db8::/48 { + mask 64 + } + } + interface eth1 { + network vlan + network-mode L3 + vlan-id 100 + vlan-id 200 + vlan-range 1000-2000 + vlan-range 2500-2700 + } + name-server 10.10.1.1 + name-server 10.10.1.2 + name-server 2001:db8:aaa:: + name-server 2001:db8:bbb:: + } + ssh { + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + ntp { + server time1.vyos.net { + } + server time2.vyos.net { + } + server time3.vyos.net { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@13:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@6:salt@1:snmp@2:ssh@2:sstp@3:system@19:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1" +// Release version: 1.3.1 diff --git a/smoketest/configs/ipv6-disable b/smoketest/configs/ipv6-disable new file mode 100644 index 0000000..da41e90 --- /dev/null +++ b/smoketest/configs/ipv6-disable @@ -0,0 +1,83 @@ +interfaces { + ethernet eth0 { + duplex auto + smp-affinity auto + speed auto + vif 201 { + address 172.18.201.10/24 + } + vif 202 { + address 172.18.202.10/24 + } + vif 203 { + address 172.18.203.10/24 + } + vif 204 { + address 172.18.204.10/24 + } + } +} +protocols { + static { + route 0.0.0.0/0 { + next-hop 172.18.201.254 { + distance 10 + } + next-hop 172.18.202.254 { + distance 20 + } + next-hop 172.18.203.254 { + distance 30 + } + next-hop 172.18.204.254 { + distance 40 + } + } + } +} +system { + config-management { + commit-revisions 200 + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.net + host-name vyos + ipv6 { + disable + } + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + level admin + } + } + name-server 172.16.254.20 + name-server 172.16.254.30 + ntp { + server 172.16.254.20 { + } + server 172.16.254.30 { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.6 */ diff --git a/smoketest/configs/isis-small b/smoketest/configs/isis-small new file mode 100644 index 0000000..79a2f04 --- /dev/null +++ b/smoketest/configs/isis-small @@ -0,0 +1,130 @@ +interfaces { + dummy dum0 { + address 203.0.113.1/24 + } + ethernet eth0 { + duplex auto + offload { + sg + tso + } + speed auto + } + ethernet eth1 { + address 192.0.2.1/24 + duplex auto + offload { + sg + tso + } + speed auto + } + ethernet eth2 { + duplex auto + offload { + sg + tso + } + speed auto + } + ethernet eth3 { + duplex auto + offload { + sg + tso + } + speed auto + } +} +policy { + prefix-list EXPORT-ISIS { + rule 10 { + action permit + prefix 203.0.113.0/24 + } + } + route-map EXPORT-ISIS { + rule 10 { + action permit + match { + ip { + address { + prefix-list EXPORT-ISIS + } + } + } + } + } +} +protocols { + isis { + interface eth1 { + bfd + } + net 49.0001.1921.6800.1002.00 + redistribute { + ipv4 { + connected { + level-2 { + route-map EXPORT-ISIS + } + } + } + } + } +} +system { + config-management { + commit-revisions 200 + } + conntrack { + modules { + ftp + h323 + nfs + pptp + sip + sqlnet + tftp + } + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.io + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + } + } + ntp { + server time1.vyos.net { + } + server time2.vyos.net { + } + server time3.vyos.net { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } + time-zone Europe/Berlin +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.0 diff --git a/smoketest/configs/nat-basic b/smoketest/configs/nat-basic new file mode 100644 index 0000000..52f369f --- /dev/null +++ b/smoketest/configs/nat-basic @@ -0,0 +1,256 @@ +interfaces { + bonding bond10 { + hash-policy "layer3+4" + member { + interface "eth2" + interface "eth3" + } + mode "802.3ad" + vif 50 { + address "192.168.189.1/24" + } + } + ethernet eth0 { + disable + offload { + gro + gso + rps + sg + tso + } + } + ethernet eth1 { + offload { + gro + gso + rps + sg + tso + } + } + ethernet eth2 { + offload { + gro + gso + rps + sg + tso + } + } + ethernet eth3 { + offload { + gro + gso + rps + sg + tso + } + } + loopback lo { + } + pppoe pppoe7 { + authentication { + password "vyos" + username "vyos" + } + dhcpv6-options { + pd 0 { + interface bond10.50 { + address "1" + } + length "56" + } + } + ip { + adjust-mss "1452" + } + ipv6 { + address { + autoconf + } + adjust-mss "1432" + } + mtu "1492" + no-peer-dns + source-interface "eth1" + } +} +nat { + destination { + rule 1000 { + destination { + port "3389" + } + inbound-interface { + name "pppoe7" + } + protocol "tcp" + translation { + address "192.168.189.5" + port "3389" + } + } + rule 10022 { + destination { + port "10022" + } + inbound-interface { + name "pppoe7" + } + protocol "tcp" + translation { + address "192.168.189.2" + port "22" + } + } + rule 10300 { + destination { + port "10300" + } + inbound-interface { + name "pppoe7" + } + protocol "udp" + translation { + address "192.168.189.2" + port "10300" + } + } + } + source { + rule 10 { + outbound-interface { + name "eth1" + } + source { + address "192.168.189.0/24" + } + translation { + address "masquerade" + options { + port-mapping fully-random + } + } + } + rule 50 { + outbound-interface { + name "pppoe7" + } + protocol "udp" + source { + address "192.168.189.2" + port "10300" + } + translation { + address "masquerade" + port "10300" + } + } + rule 100 { + outbound-interface { + name "pppoe7" + } + source { + address "192.168.189.0/24" + } + translation { + address "masquerade" + } + } + } +} +service { + dhcp-server { + shared-network-name LAN { + subnet 192.168.189.0/24 { + default-router "192.168.189.1" + domain-name "vyos.net" + lease "604800" + name-server "1.1.1.1" + name-server "9.9.9.9" + range 0 { + start "192.168.189.20" + stop "192.168.189.254" + } + } + } + } + lldp { + interface all { + } + interface eth1 { + disable + } + } + ntp { + allow-client { + address "192.168.189.0/24" + } + listen-address "192.168.189.1" + server time1.vyos.net { + } + server time2.vyos.net { + } + } + router-advert { + interface bond10.50 { + prefix ::/64 { + preferred-lifetime "2700" + valid-lifetime "5400" + } + } + } + ssh { + disable-host-validation + dynamic-protection { + } + } +} +system { + config-management { + commit-revisions "100" + } + conntrack { + modules { + ftp + h323 + nfs + pptp + sip + sqlnet + tftp + } + } + console { + device ttyS0 { + speed "115200" + } + } + domain-name "vyos.net" + host-name "R1" + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + } + } + name-server "1.1.1.1" + name-server "9.9.9.9" + syslog { + global { + facility all { + level "info" + } + facility local7 { + level "debug" + } + } + } +} + +// Warning: Do not remove the following line. +// vyos-config-version: "bgp@5:broadcast-relay@1:cluster@2:config-management@1:conntrack@5:conntrack-sync@2:container@2:dhcp-relay@2:dhcp-server@8:dhcpv6-server@1:dns-dynamic@4:dns-forwarding@4:firewall@15:flow-accounting@1:https@6:ids@1:interfaces@32:ipoe-server@3:ipsec@13:isis@3:l2tp@9:lldp@2:mdns@1:monitoring@1:nat@7:nat66@3:ntp@3:openconnect@3:ospf@2:pim@1:policy@8:pppoe-server@10:pptp@5:qos@2:quagga@11:rip@1:rpki@2:salt@1:snmp@3:ssh@2:sstp@6:system@27:vrf@3:vrrp@4:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2" +// Release version: 1.4.0-epa3 diff --git a/smoketest/configs/ospf-simple b/smoketest/configs/ospf-simple new file mode 100644 index 0000000..0427062 --- /dev/null +++ b/smoketest/configs/ospf-simple @@ -0,0 +1,81 @@ +interfaces { + ethernet eth0 { + vif 20 { + address 193.201.42.173/28 + ip { + ospf { + cost 999 + dead-interval 4 + hello-interval 1 + priority 255 + retransmit-interval 5 + transmit-delay 1 + } + } + } + vif 666 { + address 10.66.66.1/24 + } + } + ethernet eth1 { + } + ethernet eth2 { + } + loopback lo { + } +} +protocols { + ospf { + area 0 { + area-type { + normal + } + network 193.201.42.160/28 + network 10.66.66.0/24 + } + log-adjacency-changes { + detail + } + passive-interface eth0.666 + } + static { + route 0.0.0.0/0 { + next-hop 193.201.42.170 { + distance 130 + } + } + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name lab-vyos-r1 + login { + user vyos { + authentication { + encrypted-password $6$R.OnGzfXSfl6J$Iba/hl9bmjBs0VPtZ2zdW.Snh/nHuvxUwi0R6ruypgW63iKEbicJH.uUst8xZCyByURblxRtjAC1lAnYfIt.b0 + plaintext-password "" + } + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:container@1:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.4 diff --git a/smoketest/configs/ospf-small b/smoketest/configs/ospf-small new file mode 100644 index 0000000..c55627b --- /dev/null +++ b/smoketest/configs/ospf-small @@ -0,0 +1,192 @@ +interfaces { + dummy dum0 { + address 172.18.254.200/32 + } + ethernet eth0 { + duplex auto + smp-affinity auto + speed auto + vif 201 { + address 172.18.201.9/24 + ip { + ospf { + authentication { + md5 { + key-id 10 { + md5-key OSPFVyOSNET + } + } + } + dead-interval 40 + hello-interval 10 + priority 1 + retransmit-interval 5 + transmit-delay 1 + } + } + ipv6 { + ospfv3 { + bfd + cost 40 + } + } + } + vif 202 { + address 172.18.202.9/24 + ip { + ospf { + authentication { + md5 { + key-id 10 { + md5-key OSPFVyOSNET + } + } + } + dead-interval 40 + hello-interval 10 + priority 1 + retransmit-interval 5 + transmit-delay 1 + } + } + ipv6 { + ospfv3 { + bfd + cost 40 + } + } + } + vif 203 { + address 172.18.203.9/24 + ip { + ospf { + authentication { + md5 { + key-id 10 { + md5-key OSPFVyOSNET + } + } + } + dead-interval 40 + hello-interval 10 + priority 1 + retransmit-interval 5 + transmit-delay 1 + } + } + ipv6 { + ospfv3 { + bfd + cost 40 + } + } + } + } + ethernet eth1 { + duplex auto + smp-affinity auto + speed auto + ipv6 { + ospfv3 { + bfd + cost 60 + mtu-ignore + network broadcast + priority 20 + } + } + } +} +protocols { + ospf { + area 0 { + network 172.18.201.0/24 + network 172.18.202.0/24 + network 172.18.203.0/24 + network 172.18.254.200/32 + } + log-adjacency-changes { + } + parameters { + abr-type cisco + router-id 172.18.254.200 + } + passive-interface default + passive-interface-exclude eth0.201 + passive-interface-exclude eth0.202 + passive-interface-exclude eth0.203 + } + ospfv3 { + area 0.0.0.0 { + interface eth0.201 + interface eth0.202 + interface eth0.203 + interface eth1 + } + } +} +service { + ssh { + disable-host-validation + port 22 + } +} +system { + config-management { + commit-revisions 200 + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.net + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + level admin + } + } + name-server 172.16.254.30 + ntp { + server time1.vyos.net { + } + server time2.vyos.net { + } + } + sysctl { + all net.ipv4.conf.eth0.tag { + value 1 + } + all net.ipv4.conf.eth1.tag { + value 1 + } + custom net.mpls.default_ttl { + value 10 + } + custom net.mpls.ip_ttl_propagate { + value 0 + } + net.ipv4.igmp_max_memberships 5 + net.ipv4.ipfrag_time 4 + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } + time-zone Europe/Berlin +} + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.6 */ diff --git a/smoketest/configs/pppoe-server b/smoketest/configs/pppoe-server new file mode 100644 index 0000000..a01a451 --- /dev/null +++ b/smoketest/configs/pppoe-server @@ -0,0 +1,105 @@ +interfaces { + ethernet eth0 { + address dhcp + } + ethernet eth1 { + address 192.168.0.1/24 + speed auto + duplex auto + } + ethernet eth2 { + speed auto + duplex auto + } + loopback lo { + } +} +nat { + source { + rule 100 { + outbound-interface eth0 + source { + address 192.168.0.0/24 + } + translation { + address masquerade + } + } + } +} +service { + pppoe-server { + access-concentrator ACN + authentication { + local-users { + username foo { + password bar + rate-limit { + download 20480 + upload 10240 + } + } + } + mode local + } + client-ip-pool { + subnet 10.0.0.0/24 + subnet 10.0.1.0/24 + subnet 10.0.2.0/24 + } + gateway-address 192.168.0.2 + interface eth1 { + } + interface eth2 { + vlan-id 10 + vlan-id 20 + vlan-range 30-40 + vlan-range 50-60 + } + name-server 192.168.0.1 + } + ssh { + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + ntp { + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@13:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@6:salt@1:snmp@2:ssh@2:sstp@3:system@19:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1" +// Release version: 1.3-rolling-202010260127 diff --git a/smoketest/configs/qos-basic b/smoketest/configs/qos-basic new file mode 100644 index 0000000..65a888d --- /dev/null +++ b/smoketest/configs/qos-basic @@ -0,0 +1,191 @@ +interfaces { + ethernet eth0 { + address 10.1.1.100/24 + traffic-policy { + out FS + } + } + ethernet eth1 { + address 10.2.1.1/24 + traffic-policy { + out ISPC + } + } + ethernet eth2 { + address 10.9.9.1/24 + traffic-policy { + out MY-HTB + } + vif 200 { + traffic-policy { + out foo-emulate + } + } + } + loopback lo { + } +} +system { + config-management { + commit-revisions 10 + } + conntrack { + modules { + ftp + h323 + nfs + pptp + sip + sqlnet + tftp + } + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$r/Yw/07NXNY$/ZB.Rjf9jxEV.BYoDyLdH.kH14rU52pOBtrX.4S34qlPt77chflCHvpTCq9a6huLzwaMR50rEICzA5GoIRZlM0 + plaintext-password "" + } + } + } + ntp { + server time1.vyos.net { + } + server time2.vyos.net { + } + server time3.vyos.net { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} +traffic-policy { + shaper ISPC { + bandwidth 600Mbit + default { + bandwidth 50% + burst 768k + ceiling 100% + queue-type fq-codel + } + description "Outbound Traffic Shaper - ISPC" + } + shaper MY-HTB { + bandwidth 10mbit + class 30 { + bandwidth 10% + burst 15k + ceiling 50% + match ADDRESS30 { + ip { + source { + address 10.1.1.0/24 + } + } + } + priority 5 + queue-type fair-queue + } + class 40 { + bandwidth 90% + burst 15k + ceiling 100% + match ADDRESS40 { + ip { + source { + address 10.2.1.0/24 + } + } + } + priority 5 + queue-type fair-queue + } + class 50 { + bandwidth 100% + burst 15k + match ADDRESS50 { + ipv6 { + source { + address "2001:db8::1/64" + } + } + } + queue-type fair-queue + } + default { + bandwidth 10% + burst 15k + ceiling 100% + priority 7 + queue-type fair-queue + } + } + shaper FS { + bandwidth auto + class 10 { + bandwidth 100% + burst 15k + match ADDRESS10 { + ip { + source { + address 172.17.1.2/32 + } + } + } + queue-type fair-queue + } + class 20 { + bandwidth 100% + burst 15k + match ADDRESS20 { + ip { + source { + address 172.17.1.3/32 + } + } + } + queue-type fair-queue + } + class 30 { + bandwidth 100% + burst 15k + match ADDRESS30 { + ip { + source { + address 172.17.1.4/32 + } + } + } + queue-type fair-queue + } + default { + bandwidth 10% + burst 15k + ceiling 100% + priority 7 + queue-type fair-queue + } + } + network-emulator foo-emulate { + bandwidth 300mbit + burst 20000 + } +} +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.1 + diff --git a/smoketest/configs/rip-router b/smoketest/configs/rip-router new file mode 100644 index 0000000..09cb11a --- /dev/null +++ b/smoketest/configs/rip-router @@ -0,0 +1,267 @@ +interfaces { + dummy dum0 { + address 192.0.2.0/32 + } + ethernet eth0 { + duplex auto + ip { + rip { + authentication { + md5 1 { + password VyOSsecure + } + } + split-horizon { + poison-reverse + } + } + } + ipv6 { + ripng { + split-horizon { + poison-reverse + } + } + } + smp-affinity auto + speed auto + address 172.18.202.10/24 + } + ethernet eth1 { + duplex auto + smp-affinity auto + speed auto + vif 20 { + ip { + rip { + authentication { + plaintext-password VyOSsecure + } + split-horizon { + poison-reverse + } + } + } + ipv6 { + ripng { + split-horizon { + disable + } + } + } + } + vif-s 200 { + ip { + rip { + authentication { + md5 1 { + password VyOSsecure + } + } + split-horizon { + disable + } + } + } + ipv6 { + ripng { + split-horizon { + poison-reverse + } + } + } + vif-c 2000 { + ip { + rip { + authentication { + md5 1 { + password VyOSsecure + } + } + } + } + } + vif-c 3000 { + ip { + rip { + split-horizon { + disable + } + } + } + ipv6 { + ripng { + split-horizon { + poison-reverse + } + } + } + } + } + } +} +policy { + access-list6 198 { + rule 10 { + action permit + source { + any + } + } + } + access-list6 199 { + rule 20 { + action deny + source { + any + } + } + } + prefix-list6 bar-prefix { + rule 200 { + action deny + prefix 2001:db8::/32 + } + } + prefix-list6 foo-prefix { + rule 100 { + action permit + prefix 2001:db8::/32 + } + } + route-map FooBar123 { + rule 10 { + action permit + } + } +} +protocols { + rip { + default-distance 20 + default-information { + originate + } + interface eth0 + interface eth1.20 + interface eth1.200 + interface eth1.200.2000 + interface eth1.200.3000 + network 192.168.0.0/24 + redistribute { + connected { + } + } + } + ripng { + aggregate-address 2001:db8:1000::/48 + default-information { + originate + } + default-metric 8 + distribute-list { + access-list { + in 198 + out 199 + } + interface eth0 { + access-list { + in 198 + out 199 + } + prefix-list { + in foo-prefix + out bar-prefix + } + } + interface eth1 { + access-list { + in 198 + out 199 + } + prefix-list { + in foo-prefix + out bar-prefix + } + } + interface eth2 { + access-list { + in 198 + out 199 + } + prefix-list { + in foo-prefix + out bar-prefix + } + } + prefix-list { + in foo-prefix + out bar-prefix + } + } + interface eth0 + interface eth1 + interface eth2 + network 2001:db8:1000::/64 + network 2001:db8:1001::/64 + network 2001:db8:2000::/64 + network 2001:db8:2001::/64 + passive-interface default + redistribute { + connected { + metric 8 + route-map FooBar123 + } + static { + metric 8 + route-map FooBar123 + } + } + route 2001:db8:1000::/64 + } +} +service { + ssh { + port 22 + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + ntp { + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@10:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.6-S1 */ diff --git a/smoketest/configs/rpki-only b/smoketest/configs/rpki-only new file mode 100644 index 0000000..98e9892 --- /dev/null +++ b/smoketest/configs/rpki-only @@ -0,0 +1,122 @@ +interfaces { + ethernet eth0 { + duplex auto + speed auto + address 192.0.2.1/24 + address 2001:db8::1/64 + } + loopback lo { + } +} +policy { + route-map ROUTES-IN { + rule 10 { + action permit + match { + rpki valid + } + set { + local-preference 300 + } + } + rule 20 { + action permit + match { + rpki notfound + } + set { + local-preference 125 + } + } + rule 30 { + action deny + match { + rpki invalid + } + } + } +} +protocols { + bgp 100 { + neighbor 192.0.2.200 { + address-family { + ipv4-unicast { + route-map { + import ROUTES-IN + } + } + } + remote-as 200 + } + neighbor 2001:db8::200 { + address-family { + ipv6-unicast { + route-map { + import ROUTES-IN + } + } + } + remote-as 200 + } + } + rpki { + cache 1.2.3.4 { + port 3323 + preference 10 + } + cache 5.6.7.8 { + port 2222 + preference 20 + ssh { + known-hosts-file "/config/known-hosts-file" + private-key-file "/config/id_rsa" + public-key-file "/config/id_rsa.pub" + username vyos + } + } + } +} +system { + config-management { + commit-revisions 200 + } + console { + device ttyS0 { + speed 115200 + } + } + conntrack { + modules { + ftp + h323 + nfs + pptp + sip + sqlnet + tftp + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$r/Yw/07NXNY$/ZB.Rjf9jxEV.BYoDyLdH.kH14rU52pOBtrX.4S34qlPt77chflCHvpTCq9a6huLzwaMR50rEICzA5GoIRZlM0 + plaintext-password "" + } + } + } + syslog { + global { + facility all { + level debug + } + facility protocols { + level debug + } + } + } +} + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:container@1:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.5 diff --git a/smoketest/configs/tunnel-broker b/smoketest/configs/tunnel-broker new file mode 100644 index 0000000..9a1e797 --- /dev/null +++ b/smoketest/configs/tunnel-broker @@ -0,0 +1,135 @@ +interfaces { + dummy dum0 { + address 192.0.2.0/32 + } + dummy dum1 { + address 192.0.2.1/32 + } + dummy dum2 { + address 192.0.2.2/32 + } + dummy dum3 { + address 192.0.2.3/32 + } + dummy dum4 { + address 192.0.2.4/32 + } + ethernet eth0 { + duplex auto + smp-affinity auto + speed auto + address 172.18.202.10/24 + } + l2tpv3 l2tpeth10 { + destination-port 5010 + encapsulation ip + local-ip 172.18.202.10 + peer-session-id 110 + peer-tunnel-id 10 + remote-ip 172.18.202.110 + session-id 110 + source-port 5010 + tunnel-id 10 + } + l2tpv3 l2tpeth20 { + destination-port 5020 + encapsulation ip + local-ip 172.18.202.10 + peer-session-id 120 + peer-tunnel-id 20 + remote-ip 172.18.202.120 + session-id 120 + source-port 5020 + tunnel-id 20 + } + l2tpv3 l2tpeth30 { + destination-port 5030 + encapsulation ip + local-ip 172.18.202.10 + peer-session-id 130 + peer-tunnel-id 30 + remote-ip 172.18.202.130 + session-id 130 + source-port 5030 + tunnel-id 30 + } + tunnel tun100 { + address 172.16.0.1/30 + encapsulation gre-bridge + local-ip 192.0.2.1 + remote-ip 192.0.2.100 + } + tunnel tun200 { + address 172.16.0.5/30 + encapsulation gre + dhcp-interface eth0 + remote-ip 192.0.2.101 + } + tunnel tun300 { + address 172.16.0.9/30 + encapsulation ipip + local-ip 192.0.2.2 + remote-ip 192.0.2.102 + } + tunnel tun400 { + address 172.16.0.13/30 + encapsulation gre-bridge + local-ip 192.0.2.3 + remote-ip 192.0.2.103 + } + tunnel tun500 { + address 172.16.0.17/30 + encapsulation gre + local-ip 192.0.2.4 + remote-ip 192.0.2.104 + } +} +protocols { + static { + route 0.0.0.0/0 { + next-hop 172.18.202.254 { + } + } + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + ntp { + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@10:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.6-S1 */ diff --git a/smoketest/configs/vpn-openconnect-sstp b/smoketest/configs/vpn-openconnect-sstp new file mode 100644 index 0000000..59a26f5 --- /dev/null +++ b/smoketest/configs/vpn-openconnect-sstp @@ -0,0 +1,90 @@ +interfaces { + ethernet eth0 { + address 192.168.150.1/24 + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + } + } + ntp { + server time1.vyos.net { + } + server time2.vyos.net { + } + server time3.vyos.net { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} +vpn { + openconnect { + authentication { + local-users { + username test { + password test + } + } + mode local + } + network-settings { + client-ip-settings { + subnet 192.168.160.0/24 + } + } + ssl { + ca-cert-file /config/auth/ovpn_test_ca.pem + cert-file /config/auth/ovpn_test_server.pem + key-file /config/auth/ovpn_test_server.key + } + } + sstp { + authentication { + local-users { + username test { + password test + } + } + mode local + protocols mschap-v2 + } + client-ip-pool { + subnet 192.168.170.0/24 + } + gateway-address 192.168.150.1 + port 8443 + ssl { + ca-cert-file /config/auth/ovpn_test_ca.pem + cert-file /config/auth/ovpn_test_server.pem + key-file /config/auth/ovpn_test_server.key + } + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "bgp@1:broadcast-relay@1:cluster@1:config-management@1:conntrack@2:conntrack-sync@2:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@6:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:nat66@1:ntp@1:policy@1:pppoe-server@5:pptp@2:qos@1:quagga@9:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrf@2:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.4-rolling-202106290839 diff --git a/smoketest/configs/vrf-basic b/smoketest/configs/vrf-basic new file mode 100644 index 0000000..20ac7a9 --- /dev/null +++ b/smoketest/configs/vrf-basic @@ -0,0 +1,230 @@ +interfaces { + ethernet eth0 { + address 192.0.2.1/24 + } + ethernet eth1 { + duplex auto + speed auto + vrf green + } + ethernet eth2 { + vrf red + } +} +protocols { + static { + route 0.0.0.0/0 { + next-hop 192.0.2.254 { + distance 10 + } + } + table 10 { + interface-route 1.0.0.0/8 { + next-hop-interface eth0 { + distance 20 + } + } + interface-route 2.0.0.0/8 { + next-hop-interface eth0 { + distance 20 + } + } + interface-route 3.0.0.0/8 { + next-hop-interface eth0 { + distance 20 + } + } + } + table 20 { + interface-route 4.0.0.0/8 { + next-hop-interface eth0 { + distance 20 + } + } + interface-route 5.0.0.0/8 { + next-hop-interface eth0 { + distance 50 + } + } + interface-route 6.0.0.0/8 { + next-hop-interface eth0 { + distance 60 + } + } + interface-route6 2001:db8:100::/40 { + next-hop-interface eth1 { + distance 20 + } + } + interface-route6 2001:db8::/40 { + next-hop-interface eth1 { + distance 10 + } + } + route 11.0.0.0/8 { + next-hop 1.1.1.1 { + next-hop-interface eth0 + } + } + route 12.0.0.0/8 { + next-hop 1.1.1.1 { + next-hop-interface eth0 + } + } + route 13.0.0.0/8 { + next-hop 1.1.1.1 { + next-hop-interface eth0 + } + } + } + table 30 { + interface-route6 2001:db8:200::/40 { + next-hop-interface eth1 { + distance 20 + } + } + route 14.0.0.0/8 { + next-hop 2.2.1.1 { + next-hop-interface eth1 + } + } + route 15.0.0.0/8 { + next-hop 2.2.1.1 { + next-hop-interface eth1 + } + } + } + } + vrf green { + static { + interface-route 100.0.0.0/8 { + next-hop-interface eth0 { + distance 200 + next-hop-vrf default + } + } + interface-route 101.0.0.0/8 { + next-hop-interface eth0 { + next-hop-vrf default + } + next-hop-interface eth1 { + } + } + interface-route6 2001:db8:300::/40 { + next-hop-interface eth1 { + distance 20 + next-hop-vrf default + } + } + route 20.0.0.0/8 { + next-hop 1.1.1.1 { + next-hop-interface eth1 + next-hop-vrf default + } + } + route 21.0.0.0/8 { + next-hop 2.2.1.1 { + next-hop-interface eth1 + next-hop-vrf default + } + } + route6 2001:db8:100::/40 { + next-hop fe80::1 { + interface eth0 + next-hop-vrf default + } + } + } + } + vrf red { + static { + interface-route 103.0.0.0/8 { + next-hop-interface eth0 { + distance 201 + next-hop-vrf default + } + } + interface-route 104.0.0.0/8 { + next-hop-interface eth0 { + next-hop-vrf default + } + next-hop-interface eth1 { + next-hop-vrf default + } + } + interface-route6 2001:db8:400::/40 { + next-hop-interface eth1 { + distance 24 + next-hop-vrf default + } + } + route 30.0.0.0/8 { + next-hop 1.1.1.1 { + next-hop-interface eth1 + } + } + route 40.0.0.0/8 { + next-hop 2.2.1.1 { + next-hop-interface eth1 + next-hop-vrf default + } + } + route6 2001:db8:100::/40 { + next-hop fe80::1 { + interface eth0 + next-hop-vrf default + } + } + } + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + ntp { + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } + time-zone Europe/Berlin +} +vrf { + name green { + table 1000 + } + name red { + table 2000 + } +} + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@18:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@6:salt@1:snmp@2:ssh@2:sstp@3:system@20:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3-beta-202101231023 diff --git a/smoketest/configs/vrf-bgp-pppoe-underlay b/smoketest/configs/vrf-bgp-pppoe-underlay new file mode 100644 index 0000000..cba35ea --- /dev/null +++ b/smoketest/configs/vrf-bgp-pppoe-underlay @@ -0,0 +1,473 @@ +interfaces { + bridge br50 { + address 192.168.0.1/24 + member { + interface eth0.50 { + } + interface eth2 { + } + interface eth3 { + } + } + } + dummy dum0 { + address 100.64.51.252/32 + address 2001:db8:200:ffff::1/128 + vrf vyos-test-01 + } + ethernet eth0 { + offload { + gro + gso + rps + sg + tso + } + ring-buffer { + rx 256 + tx 256 + } + vif 5 { + address 2001:db8:200:f0::114/64 + address 100.64.50.121/28 + vrf vyos-test-01 + } + vif 10 { + address 2001:db8:200:10::ffff/64 + address 2001:db8:200::ffff/64 + address 100.64.50.62/26 + vrf vyos-test-01 + } + vif 15 { + address 100.64.50.78/28 + address 2001:db8:200:15::ffff/64 + vrf vyos-test-01 + } + vif 50 { + description "Member of bridge br50" + } + vif 110 { + address 100.64.51.190/27 + address 100.64.51.158/28 + address 2001:db8:200:101::ffff/64 + vrf vyos-test-01 + } + vif 410 { + address 100.64.51.206/28 + address 2001:db8:200:104::ffff/64 + vrf vyos-test-01 + } + vif 500 { + address 100.64.51.238/28 + address 2001:db8:200:50::ffff/64 + vrf vyos-test-01 + } + vif 520 { + address 100.64.50.190/28 + address 2001:db8:200:520::ffff/64 + vrf vyos-test-01 + } + vif 666 { + address 2001:db8:200:ff::101:1/112 + address 100.64.51.223/31 + vrf vyos-test-01 + } + vif 800 { + address 2001:db8:200:ff::104:1/112 + address 100.64.51.212/31 + vrf vyos-test-01 + } + vif 810 { + address 100.64.51.30/27 + address 2001:db8:200:102::ffff/64 + vrf vyos-test-01 + } + } + ethernet eth1 { + offload { + gro + gso + rps + sg + tso + } + ring-buffer { + rx 256 + tx 256 + } + } + ethernet eth2 { + offload { + gro + gso + sg + tso + } + } + ethernet eth3 { + offload { + gro + gso + sg + tso + } + } + loopback lo { + } + pppoe pppoe7 { + authentication { + password vyos + username vyos + } + dhcpv6-options { + pd 0 { + interface br50 { + address 1 + } + length 56 + } + } + ip { + adjust-mss 1452 + } + ipv6 { + address { + autoconf + } + adjust-mss 1432 + } + mtu 1492 + no-peer-dns + source-interface eth1 + } + virtual-ethernet veth0 { + address 100.64.51.220/31 + address 2001:db8:200:ff::105:1/112 + description "Core: connect vyos-test-01 and default VRF" + peer-name veth1 + } + virtual-ethernet veth1 { + address 100.64.51.221/31 + address 2001:db8:200:ff::105:2/112 + description "Core: connect vyos-test-01 and default VRF" + peer-name veth0 + vrf vyos-test-01 + } + wireguard wg500 { + address 100.64.51.209/31 + mtu 1500 + peer A { + address 192.0.2.1 + allowed-ips 0.0.0.0/0 + port 5500 + public-key KGSXF4QckzGe7f7CT+r6VZ5brOD/pVYk8yvrxOQ+X0Y= + } + port 5500 + private-key iLJh6Me6AdPJtNv3dgGhUbtyFxExxmNU4v0Fs6YE2Xc= + vrf vyos-test-01 + } + wireguard wg501 { + address 2001:db8:200:ff::102:2/112 + mtu 1500 + peer A { + address 2001:db8:300::1 + allowed-ips ::/0 + port 5501 + public-key OF+1OJ+VfQ0Yw1mgVtQ2ion4CnAdy8Bvx7yEiO4+Pn8= + } + port 5501 + private-key 0MP5X0PW58O4q2LDpuIXgZ0ySyAoWH8/kdpvQccCbUU= + vrf vyos-test-01 + } + wireguard wg666 { + address 172.29.0.0/31 + mtu 1500 + peer B { + allowed-ips 0.0.0.0/0 + public-key 2HT+RfwcqJMYNYzdmtmpem8Ht0dL37o31APHVwmh024= + } + port 50666 + private-key zvPnp2MLAoX7SotuHLFLDyy4sdlD7ttbD1xNEqA3mkU= + } +} +nat { + source { + rule 100 { + outbound-interface pppoe7 + source { + address 192.168.0.0/24 + } + translation { + address masquerade + } + } + } +} +policy { + prefix-list AS100-origin-v4 { + rule 10 { + action permit + prefix 100.64.0.0/12 + } + rule 100 { + action permit + prefix 0.0.0.0/0 + } + } + prefix-list AS200-origin-v4 { + rule 10 { + action permit + prefix 10.0.0.0/8 + } + rule 20 { + action permit + prefix 172.16.0.0/12 + } + + } + prefix-list6 AS100-origin-v6 { + rule 10 { + action permit + prefix 2001:db8:200::/40 + } + } + prefix-list6 AS200-origin-v6 { + rule 10 { + action permit + prefix 2001:db8:100::/40 + } + } +} +protocols { + static { + route 192.0.2.255/32 { + interface pppoe7 { + } + } + route 100.64.50.0/23 { + next-hop 100.64.51.221 { + } + } + route6 2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128 { + interface pppoe7 { + } + } + } +} +qos { + interface pppoe7 { + egress isp-out + } + policy { + shaper isp-out { + bandwidth 38mbit + default { + bandwidth 100% + burst 15k + queue-limit 1000 + queue-type fq-codel + } + } + } +} +service { + router-advert { + interface br50 { + prefix ::/64 { + preferred-lifetime 2700 + valid-lifetime 5400 + } + } + interface eth0.500 { + default-preference high + name-server 2001:db8:200::1 + name-server 2001:db8:200::2 + prefix 2001:db8:200:50::/64 { + valid-lifetime infinity + } + } + interface eth0.520 { + default-preference high + name-server 2001:db8:200::1 + name-server 2001:db8:200::2 + prefix 2001:db8:200:520::/64 { + valid-lifetime infinity + } + } + } + ssh { + disable-host-validation + dynamic-protection { + allow-from 100.64.0.0/10 + allow-from 2001:db8:200::/40 + } + } +} +system { + config-management { + commit-revisions 100 + } + conntrack { + modules { + ftp + h323 + nfs + pptp + sip + sqlnet + tftp + } + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.net + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + name-server 192.168.0.1 + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } + time-zone Europe/Berlin +} +vrf { + bind-to-all + name vyos-test-01 { + protocols { + bgp { + address-family { + ipv4-unicast { + network 100.64.50.0/23 { + } + } + ipv6-unicast { + network 2001:db8:200:ffff::1/128 { + } + } + } + neighbor 100.64.51.208 { + peer-group AS100v4 + } + neighbor 100.64.51.222 { + address-family { + ipv4-unicast { + default-originate { + } + maximum-prefix 10 + prefix-list { + export AS100-origin-v4 + import AS200-origin-v4 + } + soft-reconfiguration { + inbound + } + } + } + capability { + dynamic + } + remote-as 200 + } + neighbor 100.64.51.251 { + peer-group AS100v4 + shutdown + } + neighbor 100.64.51.254 { + peer-group AS100v4 + shutdown + } + neighbor 2001:db8:200:ffff::2 { + peer-group AS100v6 + shutdown + } + neighbor 2001:db8:200:ffff::a { + peer-group AS100v6 + } + neighbor 2001:db8:200:ff::101:2 { + address-family { + ipv6-unicast { + maximum-prefix 10 + prefix-list { + export AS100-origin-v6 + import AS200-origin-v6 + } + soft-reconfiguration { + inbound + } + } + } + capability { + dynamic + } + remote-as 200 + } + peer-group AS100v4 { + address-family { + ipv4-unicast { + nexthop-self { + } + } + } + capability { + dynamic + } + remote-as internal + update-source dum0 + } + peer-group AS100v6 { + address-family { + ipv6-unicast { + nexthop-self { + } + } + } + capability { + dynamic + } + remote-as internal + update-source dum0 + } + system-as 100 + } + static { + route 192.168.0.0/24 { + next-hop 100.64.51.220 { + } + } + route 100.64.50.0/23 { + blackhole { + } + } + route 100.64.51.32/27 { + next-hop 100.64.51.5 { + } + } + route6 2001:db8:2fe:ffff::/64 { + next-hop 2001:db8:200:102::5 { + } + } + } + } + table 1000 + } +} + +// Warning: Do not remove the following line. +// vyos-config-version: "bgp@3:broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:container@1:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@9:flow-accounting@1:https@4:ids@1:interfaces@28:ipoe-server@1:ipsec@12:isis@2:l2tp@4:lldp@1:mdns@1:monitoring@1:nat@5:nat66@1:ntp@2:openconnect@2:ospf@1:policy@5:pppoe-server@6:pptp@2:qos@2:quagga@10:rpki@1:salt@1:snmp@3:ssh@2:sstp@4:system@25:vrf@3:vrrp@3:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2" +// Release version: 1.4-rolling-202303160317 diff --git a/smoketest/configs/vrf-ospf b/smoketest/configs/vrf-ospf new file mode 100644 index 0000000..aae6afb --- /dev/null +++ b/smoketest/configs/vrf-ospf @@ -0,0 +1,144 @@ +interfaces { + ethernet eth0 { + address 192.0.2.1/24 + } + ethernet eth1 { + vrf red + } + ethernet eth2 { + vrf blue + } +} +protocols { + ospf { + area 0 { + network 192.0.2.0/24 + } + interface eth0 { + authentication { + md5 { + key-id 10 { + md5-key ospfkey + } + } + } + } + log-adjacency-changes { + } + parameters { + abr-type cisco + router-id 1.2.3.4 + } + passive-interface default + passive-interface-exclude eth0 + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + ntp { + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } + time-zone Europe/Berlin +} +vrf { + name blue { + protocols { + ospf { + area 0 { + network 172.18.201.0/24 + } + interface eth2 { + authentication { + md5 { + key-id 30 { + md5-key vyoskey456 + } + } + } + dead-interval 40 + hello-interval 10 + priority 1 + retransmit-interval 5 + transmit-delay 1 + } + log-adjacency-changes { + } + parameters { + abr-type cisco + router-id 5.6.7.8 + } + passive-interface default + passive-interface-exclude eth2 + } + } + table 2000 + } + name red { + protocols { + ospf { + area 0 { + network 172.18.202.0/24 + } + interface eth1 { + authentication { + md5 { + key-id 20 { + md5-key vyoskey123 + } + } + } + dead-interval 40 + hello-interval 10 + priority 1 + retransmit-interval 5 + transmit-delay 1 + } + log-adjacency-changes { + } + parameters { + abr-type cisco + router-id 9.10.11.12 + } + passive-interface default + passive-interface-exclude eth1 + } + } + table 1000 + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@2:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@20:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:nat66@1:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@9:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@20:vrf@2:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.4-rolling-202103130218 diff --git a/smoketest/configs/wireless-basic b/smoketest/configs/wireless-basic new file mode 100644 index 0000000..9cc34f5 --- /dev/null +++ b/smoketest/configs/wireless-basic @@ -0,0 +1,66 @@ +interfaces { + ethernet eth0 { + duplex "auto" + speed "auto" + } + ethernet eth1 { + duplex "auto" + speed "auto" + } + wireless wlan0 { + address 192.168.0.1/24 + channel 1 + country-code es + mode n + security { + wpa { + cipher CCMP + mode wpa2 + passphrase 12345678 + } + } + ssid VyOS + type access-point + } + wireless wlan1 { + address 192.168.1.1/24 + channel 2 + country-code de + mode n + ssid VyOS-PUBLIC + type access-point + } +} +system { + config-management { + commit-revisions "200" + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name "dev.vyos.net" + host-name "WR1" + login { + user vyos { + authentication { + encrypted-password "$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0" + } + } + } + syslog { + global { + facility all { + level "info" + } + facility local7 { + level "debug" + } + } + } +} + +// Warning: Do not remove the following line. +// vyos-config-version: "bgp@5:broadcast-relay@1:cluster@2:config-management@1:conntrack@5:conntrack-sync@2:container@2:dhcp-relay@2:dhcp-server@8:dhcpv6-server@1:dns-dynamic@4:dns-forwarding@4:firewall@15:flow-accounting@1:https@6:ids@1:interfaces@32:ipoe-server@3:ipsec@13:isis@3:l2tp@9:lldp@2:mdns@1:monitoring@1:nat@8:nat66@3:ntp@3:openconnect@3:ospf@2:pim@1:policy@8:pppoe-server@10:pptp@5:qos@2:quagga@11:reverse-proxy@1:rip@1:rpki@2:salt@1:snmp@3:ssh@2:sstp@6:system@27:vrf@3:vrrp@4:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2" +// Release version: 1.4.0 diff --git a/smoketest/scripts/cli/base_accel_ppp_test.py b/smoketest/scripts/cli/base_accel_ppp_test.py new file mode 100644 index 0000000..c6f6cb8 --- /dev/null +++ b/smoketest/scripts/cli/base_accel_ppp_test.py @@ -0,0 +1,648 @@ +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re + +from base_vyostest_shim import VyOSUnitTestSHIM +from configparser import ConfigParser + +from vyos.configsession import ConfigSessionError +from vyos.template import is_ipv4 +from vyos.utils.cpu import get_core_count +from vyos.utils.process import process_named_running +from vyos.utils.process import cmd + +class BasicAccelPPPTest: + class TestCase(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + cls._process_name = "accel-pppd" + + super(BasicAccelPPPTest.TestCase, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, cls._base_path) + + def setUp(self): + self._gateway = "192.0.2.1" + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + self.cli_delete(self._base_path) + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(self._process_name)) + + self.cli_delete(self._base_path) + self.cli_commit() + + # Check for running process + self.assertFalse(process_named_running(self._process_name)) + + def set(self, path): + self.cli_set(self._base_path + path) + + def delete(self, path): + self.cli_delete(self._base_path + path) + + def basic_protocol_specific_config(self): + """ + An astract method. + Initialize protocol scpecific configureations. + """ + self.assertFalse(True, msg="Function must be defined") + + def initial_auth_config(self): + """ + Initialization of default authentication for all protocols + """ + self.set( + [ + "authentication", + "local-users", + "username", + "vyos", + "password", + "vyos", + ] + ) + self.set(["authentication", "mode", "local"]) + + def initial_gateway_config(self): + """ + Initialization of default gateway + """ + self.set(["gateway-address", self._gateway]) + + def initial_pool_config(self): + """ + Initialization of default client ip pool + """ + first_pool = "SIMPLE-POOL" + self.set(["client-ip-pool", first_pool, "range", "192.0.2.0/24"]) + self.set(["default-pool", first_pool]) + + def basic_config(self, is_auth=True, is_gateway=True, is_client_pool=True): + """ + Initialization of basic configuration + :param is_auth: authentication initialization + :type is_auth: bool + :param is_gateway: gateway initialization + :type is_gateway: bool + :param is_client_pool: client ip pool initialization + :type is_client_pool: bool + """ + self.basic_protocol_specific_config() + if is_auth: + self.initial_auth_config() + if is_gateway: + self.initial_gateway_config() + if is_client_pool: + self.initial_pool_config() + + def getConfig(self, start, end="cli"): + """ + Return part of configuration from line + where the first injection of start keyword to the line + where the first injection of end keyowrd + :param start: start keyword + :type start: str + :param end: end keyword + :type end: str + :return: part of config + :rtype: str + """ + command = f'cat {self._config_file} | sed -n "/^\[{start}/,/^\[{end}/p"' + out = cmd(command) + return out + + def verify(self, conf): + self.assertEqual(conf["core"]["thread-count"], str(get_core_count())) + + def test_accel_name_servers(self): + # Verify proper Name-Server configuration for IPv4 and IPv6 + self.basic_config() + + nameserver = ["192.0.2.1", "192.0.2.2", "2001:db8::1"] + for ns in nameserver: + self.set(["name-server", ns]) + + # commit changes + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False) + conf.read(self._config_file) + + # IPv4 and IPv6 nameservers must be checked individually + for ns in nameserver: + if is_ipv4(ns): + self.assertIn(ns, [conf["dns"]["dns1"], conf["dns"]["dns2"]]) + else: + self.assertEqual(conf["ipv6-dns"][ns], None) + + def test_accel_local_authentication(self): + # Test configuration of local authentication + self.basic_config() + + # upload / download limit + user = "test" + password = "test2" + static_ip = "100.100.100.101" + upload = "5000" + download = "10000" + self.set( + [ + "authentication", + "local-users", + "username", + user, + "password", + password, + ] + ) + self.set( + [ + "authentication", + "local-users", + "username", + user, + "static-ip", + static_ip, + ] + ) + self.set( + [ + "authentication", + "local-users", + "username", + user, + "rate-limit", + "upload", + upload, + ] + ) + + # upload rate-limit requires also download rate-limit + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.set( + [ + "authentication", + "local-users", + "username", + user, + "rate-limit", + "download", + download, + ] + ) + + # commit changes + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False) + conf.read(self._config_file) + + # check proper path to chap-secrets file + self.assertEqual(conf["chap-secrets"]["chap-secrets"], self._chap_secrets) + + # basic verification + self.verify(conf) + + # check local users + tmp = cmd(f"sudo cat {self._chap_secrets}") + regex = f"{user}\s+\*\s+{password}\s+{static_ip}\s+{download}/{upload}" + tmp = re.findall(regex, tmp) + self.assertTrue(tmp) + + # Check local-users default value(s) + self.delete( + ["authentication", "local-users", "username", user, "static-ip"] + ) + # commit changes + self.cli_commit() + + # check local users + tmp = cmd(f"sudo cat {self._chap_secrets}") + regex = f"{user}\s+\*\s+{password}\s+\*\s+{download}/{upload}" + tmp = re.findall(regex, tmp) + self.assertTrue(tmp) + + def test_accel_radius_authentication(self): + # Test configuration of RADIUS authentication for PPPoE server + self.basic_config() + + radius_server = "192.0.2.22" + radius_key = "secretVyOS" + radius_port = "2000" + radius_port_acc = "3000" + acct_interim_jitter = '10' + acct_interim_interval = '10' + acct_timeout = '30' + + self.set(["authentication", "mode", "radius"]) + self.set( + ["authentication", "radius", "server", radius_server, "key", radius_key] + ) + self.set( + [ + "authentication", + "radius", + "server", + radius_server, + "port", + radius_port, + ] + ) + self.set( + [ + "authentication", + "radius", + "server", + radius_server, + "acct-port", + radius_port_acc, + ] + ) + self.set( + [ + "authentication", + "radius", + "acct-interim-jitter", + acct_interim_jitter, + ] + ) + self.set( + [ + "authentication", + "radius", + "accounting-interim-interval", + acct_interim_interval, + ] + ) + self.set( + [ + "authentication", + "radius", + "acct-timeout", + acct_timeout, + ] + ) + + coa_server = "4.4.4.4" + coa_key = "testCoA" + self.set( + ["authentication", "radius", "dynamic-author", "server", coa_server] + ) + self.set(["authentication", "radius", "dynamic-author", "key", coa_key]) + + nas_id = "VyOS-PPPoE" + nas_ip = "7.7.7.7" + self.set(["authentication", "radius", "nas-identifier", nas_id]) + self.set(["authentication", "radius", "nas-ip-address", nas_ip]) + + source_address = "1.2.3.4" + self.set(["authentication", "radius", "source-address", source_address]) + + # commit changes + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False) + conf.read(self._config_file) + + # basic verification + self.verify(conf) + + # check auth + self.assertTrue(conf["radius"].getboolean("verbose")) + self.assertEqual(conf["radius"]["acct-timeout"], acct_timeout) + self.assertEqual(conf["radius"]["acct-interim-interval"], acct_interim_interval) + self.assertEqual(conf["radius"]["acct-interim-jitter"], acct_interim_jitter) + self.assertEqual(conf["radius"]["timeout"], "3") + self.assertEqual(conf["radius"]["max-try"], "3") + + self.assertEqual( + conf["radius"]["dae-server"], f"{coa_server}:1700,{coa_key}" + ) + self.assertEqual(conf["radius"]["nas-identifier"], nas_id) + self.assertEqual(conf["radius"]["nas-ip-address"], nas_ip) + self.assertEqual(conf["radius"]["bind"], source_address) + + server = conf["radius"]["server"].split(",") + self.assertEqual(radius_server, server[0]) + self.assertEqual(radius_key, server[1]) + self.assertEqual(f"auth-port={radius_port}", server[2]) + self.assertEqual(f"acct-port={radius_port_acc}", server[3]) + self.assertEqual(f"req-limit=0", server[4]) + self.assertEqual(f"fail-time=0", server[5]) + + # + # Disable Radius Accounting + # + self.delete( + ["authentication", "radius", "server", radius_server, "acct-port"] + ) + self.set( + [ + "authentication", + "radius", + "server", + radius_server, + "disable-accounting", + ] + ) + + self.set( + [ + "authentication", + "radius", + "server", + radius_server, + "backup", + ] + ) + + self.set( + [ + "authentication", + "radius", + "server", + radius_server, + "priority", + "10", + ] + ) + + # commit changes + self.cli_commit() + + conf.read(self._config_file) + + server = conf["radius"]["server"].split(",") + self.assertEqual(radius_server, server[0]) + self.assertEqual(radius_key, server[1]) + self.assertEqual(f"auth-port={radius_port}", server[2]) + self.assertEqual(f"acct-port=0", server[3]) + self.assertEqual(f"req-limit=0", server[4]) + self.assertEqual(f"fail-time=0", server[5]) + self.assertIn('weight=10', server) + self.assertIn('backup', server) + + def test_accel_ipv4_pool(self): + self.basic_config(is_gateway=False, is_client_pool=False) + gateway = "192.0.2.1" + subnet = "172.16.0.0/24" + first_pool = "POOL1" + second_pool = "POOL2" + range = "192.0.2.10-192.0.2.20" + range_config = "192.0.2.10-20" + + self.set(["gateway-address", gateway]) + self.set(["client-ip-pool", first_pool, "range", subnet]) + self.set(["client-ip-pool", first_pool, "next-pool", second_pool]) + self.set(["client-ip-pool", second_pool, "range", range]) + self.set(["default-pool", first_pool]) + # commit changes + + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False) + conf.read(self._config_file) + + self.assertEqual( + f"{first_pool},next={second_pool}", conf["ip-pool"][f"{subnet},name"] + ) + self.assertEqual(second_pool, conf["ip-pool"][f"{range_config},name"]) + self.assertEqual(gateway, conf["ip-pool"]["gw-ip-address"]) + self.assertEqual(first_pool, conf[self._protocol_section]["ip-pool"]) + + def test_accel_next_pool(self): + # T5099 required specific order + self.basic_config(is_gateway=False, is_client_pool=False) + + gateway = "192.0.2.1" + first_pool = "VyOS-pool1" + first_subnet = "192.0.2.0/25" + second_pool = "Vyos-pool2" + second_subnet = "203.0.113.0/25" + third_pool = "Vyos-pool3" + third_subnet = "198.51.100.0/24" + + self.set(["gateway-address", gateway]) + self.set(["client-ip-pool", first_pool, "range", first_subnet]) + self.set(["client-ip-pool", first_pool, "next-pool", second_pool]) + self.set(["client-ip-pool", second_pool, "range", second_subnet]) + self.set(["client-ip-pool", second_pool, "next-pool", third_pool]) + self.set(["client-ip-pool", third_pool, "range", third_subnet]) + + # commit changes + self.cli_commit() + + config = self.getConfig("ip-pool") + + pool_config = f"""gw-ip-address={gateway} +{third_subnet},name={third_pool} +{second_subnet},name={second_pool},next={third_pool} +{first_subnet},name={first_pool},next={second_pool}""" + self.assertIn(pool_config, config) + + def test_accel_ipv6_pool(self): + # Test configuration of IPv6 client pools + self.basic_config(is_gateway=False, is_client_pool=False) + + # Enable IPv6 + allow_ipv6 = 'allow' + self.set(['ppp-options', 'ipv6', allow_ipv6]) + + pool_name = 'ipv6_test_pool' + prefix_1 = '2001:db8:fffe::/56' + prefix_mask = '64' + prefix_2 = '2001:db8:ffff::/56' + client_prefix_1 = f'{prefix_1},{prefix_mask}' + client_prefix_2 = f'{prefix_2},{prefix_mask}' + self.set( + ['client-ipv6-pool', pool_name, 'prefix', prefix_1, 'mask', + prefix_mask]) + self.set( + ['client-ipv6-pool', pool_name, 'prefix', prefix_2, 'mask', + prefix_mask]) + + delegate_1_prefix = '2001:db8:fff1::/56' + delegate_2_prefix = '2001:db8:fff2::/56' + delegate_mask = '64' + self.set( + ['client-ipv6-pool', pool_name, 'delegate', delegate_1_prefix, + 'delegation-prefix', delegate_mask]) + self.set( + ['client-ipv6-pool', pool_name, 'delegate', delegate_2_prefix, + 'delegation-prefix', delegate_mask]) + + # commit changes + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True, delimiters='=', + strict=False) + conf.read(self._config_file) + + for tmp in ['ipv6pool', 'ipv6_nd', 'ipv6_dhcp']: + self.assertEqual(conf['modules'][tmp], None) + + self.assertEqual(conf['ppp']['ipv6'], allow_ipv6) + + config = self.getConfig("ipv6-pool") + pool_config = f"""{client_prefix_1},name={pool_name} +{client_prefix_2},name={pool_name} +delegate={delegate_1_prefix},{delegate_mask},name={pool_name} +delegate={delegate_2_prefix},{delegate_mask},name={pool_name}""" + self.assertIn(pool_config, config) + + def test_accel_ppp_options(self): + # Test configuration of local authentication for PPPoE server + self.basic_config() + + # other settings + mppe = 'require' + self.set(['ppp-options', 'disable-ccp']) + self.set(['ppp-options', 'mppe', mppe]) + + # min-mtu + min_mtu = '1400' + self.set(['ppp-options', 'min-mtu', min_mtu]) + + # mru + mru = '9000' + self.set(['ppp-options', 'mru', mru]) + + # interface-cache + interface_cache = '128000' + self.set(['ppp-options', 'interface-cache', interface_cache]) + + # ipv6 + allow_ipv6 = 'allow' + allow_ipv4 = 'require' + random = 'random' + lcp_failure = '4' + lcp_interval = '40' + lcp_timeout = '100' + self.set(['ppp-options', 'ipv4', allow_ipv4]) + self.set(['ppp-options', 'ipv6', allow_ipv6]) + self.set(['ppp-options', 'ipv6-interface-id', random]) + self.set(['ppp-options', 'ipv6-accept-peer-interface-id']) + self.set(['ppp-options', 'ipv6-peer-interface-id', random]) + self.set(['ppp-options', 'lcp-echo-failure', lcp_failure]) + self.set(['ppp-options', 'lcp-echo-interval', lcp_interval]) + self.set(['ppp-options', 'lcp-echo-timeout', lcp_timeout]) + # commit changes + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True, delimiters='=') + conf.read(self._config_file) + + self.assertEqual(conf['chap-secrets']['gw-ip-address'], self._gateway) + + # check ppp + self.assertEqual(conf['ppp']['mppe'], mppe) + self.assertEqual(conf['ppp']['min-mtu'], min_mtu) + self.assertEqual(conf['ppp']['mru'], mru) + + self.assertEqual(conf['ppp']['ccp'],'0') + + # check interface-cache + self.assertEqual(conf['ppp']['unit-cache'], interface_cache) + + #check ipv6 + for tmp in ['ipv6pool', 'ipv6_nd', 'ipv6_dhcp']: + self.assertEqual(conf['modules'][tmp], None) + + self.assertEqual(conf['ppp']['ipv6'], allow_ipv6) + self.assertEqual(conf['ppp']['ipv6-intf-id'], random) + self.assertEqual(conf['ppp']['ipv6-peer-intf-id'], random) + self.assertTrue(conf['ppp'].getboolean('ipv6-accept-peer-intf-id')) + self.assertEqual(conf['ppp']['lcp-echo-failure'], lcp_failure) + self.assertEqual(conf['ppp']['lcp-echo-interval'], lcp_interval) + self.assertEqual(conf['ppp']['lcp-echo-timeout'], lcp_timeout) + + + def test_accel_wins_server(self): + self.basic_config() + winsservers = ["192.0.2.1", "192.0.2.2"] + for wins in winsservers: + self.set(["wins-server", wins]) + self.cli_commit() + conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False) + conf.read(self._config_file) + for ws in winsservers: + self.assertIn(ws, [conf["wins"]["wins1"], conf["wins"]["wins2"]]) + + def test_accel_snmp(self): + self.basic_config() + self.set(['snmp', 'master-agent']) + self.cli_commit() + conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False) + conf.read(self._config_file) + self.assertEqual(conf['modules']['net-snmp'], None) + self.assertEqual(conf['snmp']['master'],'1') + + def test_accel_shaper(self): + self.basic_config() + fwmark = '2' + self.set(['shaper', 'fwmark', fwmark]) + self.cli_commit() + conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False) + conf.read(self._config_file) + self.assertEqual(conf['modules']['shaper'], None) + self.assertEqual(conf['shaper']['verbose'], '1') + self.assertEqual(conf['shaper']['down-limiter'], 'tbf') + self.assertEqual(conf['shaper']['fwmark'], fwmark) + + def test_accel_limits(self): + self.basic_config() + burst = '100' + timeout = '20' + limits = '1/min' + self.set(['limits', 'connection-limit', limits]) + self.set(['limits', 'timeout', timeout]) + self.set(['limits', 'burst', burst]) + self.cli_commit() + conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False) + conf.read(self._config_file) + self.assertEqual(conf['modules']['connlimit'], None) + self.assertEqual(conf['connlimit']['limit'], limits) + self.assertEqual(conf['connlimit']['burst'], burst) + self.assertEqual(conf['connlimit']['timeout'], timeout) + + def test_accel_log_level(self): + self.basic_config() + self.cli_commit() + + # check default value + conf = ConfigParser(allow_no_value=True) + conf.read(self._config_file) + self.assertEqual(conf['log']['level'], '3') + + for log_level in range(0, 5): + self.set(['log', 'level', str(log_level)]) + self.cli_commit() + # Validate configuration values + conf = ConfigParser(allow_no_value=True) + conf.read(self._config_file) + + self.assertEqual(conf['log']['level'], str(log_level)) diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py new file mode 100644 index 0000000..593b4b4 --- /dev/null +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -0,0 +1,1320 @@ +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re + +from netifaces import AF_INET +from netifaces import AF_INET6 +from netifaces import ifaddresses + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.defaults import directories +from vyos.ifconfig import Interface +from vyos.ifconfig import Section +from vyos.pki import CERT_BEGIN +from vyos.utils.file import read_file +from vyos.utils.dict import dict_search +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.network import get_interface_config +from vyos.utils.network import get_interface_vrf +from vyos.utils.network import get_vrf_tableid +from vyos.utils.network import interface_exists +from vyos.utils.network import is_intf_addr_assigned +from vyos.utils.network import is_ipv6_link_local +from vyos.utils.network import get_nft_vrf_zone_mapping +from vyos.xml_ref import cli_defined + +dhclient_base_dir = directories['isc_dhclient_dir'] +dhclient_process_name = 'dhclient' +dhcp6c_base_dir = directories['dhcp6_client_dir'] +dhcp6c_process_name = 'dhcp6c' + +server_ca_root_cert_data = """ +MIIBcTCCARagAwIBAgIUDcAf1oIQV+6WRaW7NPcSnECQ/lUwCgYIKoZIzj0EAwIw +HjEcMBoGA1UEAwwTVnlPUyBzZXJ2ZXIgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjBa +Fw0zMjAyMTUxOTQxMjBaMB4xHDAaBgNVBAMME1Z5T1Mgc2VydmVyIHJvb3QgQ0Ew +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ0y24GzKQf4aM2Ir12tI9yITOIzAUj +ZXyJeCmYI6uAnyAMqc4Q4NKyfq3nBi4XP87cs1jlC1P2BZ8MsjL5MdGWozIwMDAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRwC/YaieMEnjhYa7K3Flw/o0SFuzAK +BggqhkjOPQQDAgNJADBGAiEAh3qEj8vScsjAdBy5shXzXDVVOKWCPTdGrPKnu8UW +a2cCIQDlDgkzWmn5ujc5ATKz1fj+Se/aeqwh4QyoWCVTFLIxhQ== +""" + +server_ca_intermediate_cert_data = """ +MIIBmTCCAT+gAwIBAgIUNzrtHzLmi3QpPK57tUgCnJZhXXQwCgYIKoZIzj0EAwIw +HjEcMBoGA1UEAwwTVnlPUyBzZXJ2ZXIgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjFa +Fw0zMjAyMTUxOTQxMjFaMCYxJDAiBgNVBAMMG1Z5T1Mgc2VydmVyIGludGVybWVk +aWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEl2nJ1CzoqPV6hWII2m +eGN/uieU6wDMECTk/LgG8CCCSYb488dibUiFN/1UFsmoLIdIhkx/6MUCYh62m8U2 +WNujUzBRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMV3YwH88I5gFsFUibbQ +kMR0ECPsMB8GA1UdIwQYMBaAFHAL9hqJ4wSeOFhrsrcWXD+jRIW7MAoGCCqGSM49 +BAMCA0gAMEUCIQC/ahujD9dp5pMMCd3SZddqGC9cXtOwMN0JR3e5CxP13AIgIMQm +jMYrinFoInxmX64HfshYqnUY8608nK9D2BNPOHo= +""" + +client_ca_root_cert_data = """ +MIIBcDCCARagAwIBAgIUZmoW2xVdwkZSvglnkCq0AHKa6zIwCgYIKoZIzj0EAwIw +HjEcMBoGA1UEAwwTVnlPUyBjbGllbnQgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjFa +Fw0zMjAyMTUxOTQxMjFaMB4xHDAaBgNVBAMME1Z5T1MgY2xpZW50IHJvb3QgQ0Ew +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATUpKXzQk2NOVKDN4VULk2yw4mOKPvn +mg947+VY7lbpfOfAUD0QRg95qZWCw899eKnXp/U4TkAVrmEKhUb6OJTFozIwMDAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTXu6xGWUl25X3sBtrhm3BJSICIATAK +BggqhkjOPQQDAgNIADBFAiEAnTzEwuTI9bz2Oae3LZbjP6f/f50KFJtjLZFDbQz7 +DpYCIDNRHV8zBUibC+zg5PqMpQBKd/oPfNU76nEv6xkp/ijO +""" + +client_ca_intermediate_cert_data = """ +MIIBmDCCAT+gAwIBAgIUJEMdotgqA7wU4XXJvEzDulUAGqgwCgYIKoZIzj0EAwIw +HjEcMBoGA1UEAwwTVnlPUyBjbGllbnQgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjJa +Fw0zMjAyMTUxOTQxMjJaMCYxJDAiBgNVBAMMG1Z5T1MgY2xpZW50IGludGVybWVk +aWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGyIVIi217s9j3O+WQ2b +6R65/Z0ZjQpELxPjBRc0CA0GFCo+pI5EvwI+jNFArvTAJ5+ZdEWUJ1DQhBKDDQdI +avCjUzBRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOUS8oNJjChB1Rb9Blcl +ETvziHJ9MB8GA1UdIwQYMBaAFNe7rEZZSXblfewG2uGbcElIgIgBMAoGCCqGSM49 +BAMCA0cAMEQCIArhaxWgRsAUbEeNHD/ULtstLHxw/P97qPUSROLQld53AiBjgiiz +9pDfISmpekZYz6bIDWRIR0cXUToZEMFNzNMrQg== +""" + +client_cert_data = """ +MIIBmTCCAUCgAwIBAgIUV5T77XdE/tV82Tk4Vzhp5BIFFm0wCgYIKoZIzj0EAwIw +JjEkMCIGA1UEAwwbVnlPUyBjbGllbnQgaW50ZXJtZWRpYXRlIENBMB4XDTIyMDIx +NzE5NDEyMloXDTMyMDIxNTE5NDEyMlowIjEgMB4GA1UEAwwXVnlPUyBjbGllbnQg +Y2VydGlmaWNhdGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARuyynqfc/qJj5e +KJ03oOH8X4Z8spDeAPO9WYckMM0ldPj+9kU607szFzPwjaPWzPdgyIWz3hcN8yAh +CIhytmJao1AwTjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTIFKrxZ+PqOhYSUqnl +TGCUmM7wTjAfBgNVHSMEGDAWgBTlEvKDSYwoQdUW/QZXJRE784hyfTAKBggqhkjO +PQQDAgNHADBEAiAvO8/jvz05xqmP3OXD53XhfxDLMIxzN4KPoCkFqvjlhQIgIHq2 +/geVx3rAOtSps56q/jiDouN/aw01TdpmGKVAa9U= +""" + +client_key_data = """ +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgxaxAQsJwjoOCByQE ++qSYKtKtJzbdbOnTsKNSrfgkFH6hRANCAARuyynqfc/qJj5eKJ03oOH8X4Z8spDe +APO9WYckMM0ldPj+9kU607szFzPwjaPWzPdgyIWz3hcN8yAhCIhytmJa +""" + +def get_wpa_supplicant_value(interface, key): + tmp = read_file(f'/run/wpa_supplicant/{interface}.conf') + tmp = re.findall(r'\n?{}=(.*)'.format(key), tmp) + return tmp[0] + +def get_certificate_count(interface, cert_type): + tmp = read_file(f'/run/wpa_supplicant/{interface}_{cert_type}.pem') + return tmp.count(CERT_BEGIN) + +def is_mirrored_to(interface, mirror_if, qdisc): + """ + Ask TC if we are mirroring traffic to a discrete interface. + + interface: source interface + mirror_if: destination where we mirror our data to + qdisc: must be ffff or 1 for ingress/egress + """ + if qdisc not in ['ffff', '1']: + raise ValueError() + + ret_val = False + tmp = cmd(f'tc -s -p filter ls dev {interface} parent {qdisc}: | grep mirred') + tmp = tmp.lower() + if mirror_if in tmp: + ret_val = True + return ret_val +class BasicInterfaceTest: + class TestCase(VyOSUnitTestSHIM.TestCase): + _test_dhcp = False + _test_eapol = False + _test_ip = False + _test_mtu = False + _test_vlan = False + _test_qinq = False + _test_ipv6 = False + _test_ipv6_pd = False + _test_ipv6_dhcpc6 = False + _test_mirror = False + _test_vrf = False + _base_path = [] + + _options = {} + _interfaces = [] + _qinq_range = ['10', '20', '30'] + _vlan_range = ['100', '200', '300', '2000'] + _test_addr = ['192.0.2.1/26', '192.0.2.255/31', '192.0.2.64/32', + '2001:db8:1::ffff/64', '2001:db8:101::1/112'] + + _mirror_interfaces = [] + # choose IPv6 minimum MTU value for tests - this must always work + _mtu = '1280' + + @classmethod + def setUpClass(cls): + super(BasicInterfaceTest.TestCase, cls).setUpClass() + + # XXX the case of test_vif_8021q_mtu_limits, below, shows that + # we should extend cli_defined to support more complex queries + cls._test_vlan = cli_defined(cls._base_path, 'vif') + cls._test_qinq = cli_defined(cls._base_path, 'vif-s') + cls._test_dhcp = cli_defined(cls._base_path, 'dhcp-options') + cls._test_eapol = cli_defined(cls._base_path, 'eapol') + cls._test_ip = cli_defined(cls._base_path, 'ip') + cls._test_ipv6 = cli_defined(cls._base_path, 'ipv6') + cls._test_ipv6_dhcpc6 = cli_defined(cls._base_path, 'dhcpv6-options') + cls._test_ipv6_pd = cli_defined(cls._base_path + ['dhcpv6-options'], 'pd') + cls._test_mtu = cli_defined(cls._base_path, 'mtu') + cls._test_vrf = cli_defined(cls._base_path, 'vrf') + + # Setup mirror interfaces for SPAN (Switch Port Analyzer) + for span in cls._mirror_interfaces: + section = Section.section(span) + cls.cli_set(cls, ['interfaces', section, span]) + + @classmethod + def tearDownClass(cls): + # Tear down mirror interfaces for SPAN (Switch Port Analyzer) + for span in cls._mirror_interfaces: + section = Section.section(span) + cls.cli_delete(cls, ['interfaces', section, span]) + + super(BasicInterfaceTest.TestCase, cls).tearDownClass() + + def tearDown(self): + self.cli_delete(self._base_path) + self.cli_commit() + + # Verify that no previously interface remained on the system + ct_map = get_nft_vrf_zone_mapping() + for intf in self._interfaces: + self.assertFalse(interface_exists(intf)) + for map_entry in ct_map: + self.assertNotEqual(intf, map_entry['interface']) + + # No daemon started during tests should remain running + for daemon in ['dhcp6c', 'dhclient']: + # if _interface list is populated do a more fine grained search + # by also checking the cmd arguments passed to the daemon + if self._interfaces: + for tmp in self._interfaces: + self.assertFalse(process_named_running(daemon, tmp)) + else: + self.assertFalse(process_named_running(daemon)) + + def test_dhcp_disable_interface(self): + if not self._test_dhcp: + self.skipTest('not supported') + + # When interface is configured as admin down, it must be admin down + # even when dhcpc starts on the given interface + for interface in self._interfaces: + self.cli_set(self._base_path + [interface, 'disable']) + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + + # Also enable DHCP (ISC DHCP always places interface in admin up + # state so we check that we do not start DHCP client. + # https://vyos.dev/T2767 + self.cli_set(self._base_path + [interface, 'address', 'dhcp']) + + self.cli_commit() + + # Validate interface state + for interface in self._interfaces: + flags = read_file(f'/sys/class/net/{interface}/flags') + self.assertEqual(int(flags, 16) & 1, 0) + + def test_dhcp_client_options(self): + if not self._test_dhcp or not self._test_vrf: + self.skipTest('not supported') + + client_id = 'VyOS-router' + distance = '100' + hostname = 'vyos' + vendor_class_id = 'vyos-vendor' + user_class = 'vyos' + + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + + self.cli_set(self._base_path + [interface, 'address', 'dhcp']) + self.cli_set(self._base_path + [interface, 'dhcp-options', 'client-id', client_id]) + self.cli_set(self._base_path + [interface, 'dhcp-options', 'default-route-distance', distance]) + self.cli_set(self._base_path + [interface, 'dhcp-options', 'host-name', hostname]) + self.cli_set(self._base_path + [interface, 'dhcp-options', 'vendor-class-id', vendor_class_id]) + self.cli_set(self._base_path + [interface, 'dhcp-options', 'user-class', user_class]) + + self.cli_commit() + + for interface in self._interfaces: + # Check if dhclient process runs + dhclient_pid = process_named_running(dhclient_process_name, cmdline=interface, timeout=10) + self.assertTrue(dhclient_pid) + + dhclient_config = read_file(f'{dhclient_base_dir}/dhclient_{interface}.conf') + self.assertIn(f'request subnet-mask, broadcast-address, routers, domain-name-servers', dhclient_config) + self.assertIn(f'require subnet-mask;', dhclient_config) + self.assertIn(f'send host-name "{hostname}";', dhclient_config) + self.assertIn(f'send dhcp-client-identifier "{client_id}";', dhclient_config) + self.assertIn(f'send vendor-class-identifier "{vendor_class_id}";', dhclient_config) + self.assertIn(f'send user-class "{user_class}";', dhclient_config) + + # and the commandline has the appropriate options + cmdline = read_file(f'/proc/{dhclient_pid}/cmdline') + self.assertIn(f'-e\x00IF_METRIC={distance}', cmdline) + + def test_dhcp_vrf(self): + if not self._test_dhcp or not self._test_vrf: + self.skipTest('not supported') + + vrf_name = 'purple4' + self.cli_set(['vrf', 'name', vrf_name, 'table', '65000']) + + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + + self.cli_set(self._base_path + [interface, 'address', 'dhcp']) + self.cli_set(self._base_path + [interface, 'vrf', vrf_name]) + + self.cli_commit() + + # Validate interface state + for interface in self._interfaces: + tmp = get_interface_vrf(interface) + self.assertEqual(tmp, vrf_name) + + # Check if dhclient process runs + dhclient_pid = process_named_running(dhclient_process_name, cmdline=interface, timeout=10) + self.assertTrue(dhclient_pid) + # .. inside the appropriate VRF instance + vrf_pids = cmd(f'ip vrf pids {vrf_name}') + self.assertIn(str(dhclient_pid), vrf_pids) + # and the commandline has the appropriate options + cmdline = read_file(f'/proc/{dhclient_pid}/cmdline') + self.assertIn('-e\x00IF_METRIC=210', cmdline) # 210 is the default value + + self.cli_delete(['vrf', 'name', vrf_name]) + + def test_dhcpv6_vrf(self): + if not self._test_ipv6_dhcpc6 or not self._test_vrf: + self.skipTest('not supported') + + vrf_name = 'purple6' + self.cli_set(['vrf', 'name', vrf_name, 'table', '65001']) + + # When interface is configured as admin down, it must be admin down + # even when dhcpc starts on the given interface + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + + self.cli_set(self._base_path + [interface, 'address', 'dhcpv6']) + self.cli_set(self._base_path + [interface, 'vrf', vrf_name]) + + self.cli_commit() + + # Validate interface state + for interface in self._interfaces: + tmp = get_interface_vrf(interface) + self.assertEqual(tmp, vrf_name) + + # Check if dhclient process runs + tmp = process_named_running(dhcp6c_process_name, cmdline=interface, timeout=10) + self.assertTrue(tmp) + # .. inside the appropriate VRF instance + vrf_pids = cmd(f'ip vrf pids {vrf_name}') + self.assertIn(str(tmp), vrf_pids) + + self.cli_delete(['vrf', 'name', vrf_name]) + + def test_move_interface_between_vrf_instances(self): + if not self._test_vrf: + self.skipTest('not supported') + + vrf1_name = 'smoketest_mgmt1' + vrf1_table = '5424' + vrf2_name = 'smoketest_mgmt2' + vrf2_table = '7412' + + self.cli_set(['vrf', 'name', vrf1_name, 'table', vrf1_table]) + self.cli_set(['vrf', 'name', vrf2_name, 'table', vrf2_table]) + + # move interface into first VRF + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + self.cli_set(self._base_path + [interface, 'vrf', vrf1_name]) + + self.cli_commit() + + # check that interface belongs to proper VRF + for interface in self._interfaces: + tmp = get_interface_vrf(interface) + self.assertEqual(tmp, vrf1_name) + + tmp = get_interface_config(vrf1_name) + self.assertEqual(int(vrf1_table), get_vrf_tableid(interface)) + + # move interface into second VRF + for interface in self._interfaces: + self.cli_set(self._base_path + [interface, 'vrf', vrf2_name]) + + self.cli_commit() + + # check that interface belongs to proper VRF + for interface in self._interfaces: + tmp = get_interface_vrf(interface) + self.assertEqual(tmp, vrf2_name) + + tmp = get_interface_config(vrf2_name) + self.assertEqual(int(vrf2_table), get_vrf_tableid(interface)) + + self.cli_delete(['vrf', 'name', vrf1_name]) + self.cli_delete(['vrf', 'name', vrf2_name]) + + def test_add_to_invalid_vrf(self): + if not self._test_vrf: + self.skipTest('not supported') + + # move interface into first VRF + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + self.cli_set(self._base_path + [interface, 'vrf', 'invalid']) + + # check validate() - can not use a non-existing VRF + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + for interface in self._interfaces: + self.cli_delete(self._base_path + [interface, 'vrf', 'invalid']) + self.cli_set(self._base_path + [interface, 'description', 'test_add_to_invalid_vrf']) + + def test_span_mirror(self): + if not self._mirror_interfaces: + self.skipTest('not supported') + + # Check the two-way mirror rules of ingress and egress + for mirror in self._mirror_interfaces: + for interface in self._interfaces: + self.cli_set(self._base_path + [interface, 'mirror', 'ingress', mirror]) + self.cli_set(self._base_path + [interface, 'mirror', 'egress', mirror]) + + self.cli_commit() + + # Verify config + for mirror in self._mirror_interfaces: + for interface in self._interfaces: + self.assertTrue(is_mirrored_to(interface, mirror, 'ffff')) + self.assertTrue(is_mirrored_to(interface, mirror, '1')) + + def test_interface_disable(self): + # Check if description can be added to interface and + # can be read back + for intf in self._interfaces: + self.cli_set(self._base_path + [intf, 'disable']) + for option in self._options.get(intf, []): + self.cli_set(self._base_path + [intf] + option.split()) + + self.cli_commit() + + # Validate interface description + for intf in self._interfaces: + self.assertEqual(Interface(intf).get_admin_state(), 'down') + + def test_interface_description(self): + # Check if description can be added to interface and + # can be read back + for intf in self._interfaces: + test_string=f'Description-Test-{intf}' + self.cli_set(self._base_path + [intf, 'description', test_string]) + for option in self._options.get(intf, []): + self.cli_set(self._base_path + [intf] + option.split()) + + self.cli_commit() + + # Validate interface description + for intf in self._interfaces: + test_string=f'Description-Test-{intf}' + tmp = read_file(f'/sys/class/net/{intf}/ifalias') + self.assertEqual(tmp, test_string) + self.assertEqual(Interface(intf).get_alias(), test_string) + self.cli_delete(self._base_path + [intf, 'description']) + + self.cli_commit() + + # Validate remove interface description "empty" + for intf in self._interfaces: + tmp = read_file(f'/sys/class/net/{intf}/ifalias') + self.assertEqual(tmp, str()) + self.assertEqual(Interface(intf).get_alias(), str()) + + # Test maximum interface description lengt (255 characters) + test_string='abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789___' + for intf in self._interfaces: + + self.cli_set(self._base_path + [intf, 'description', test_string]) + for option in self._options.get(intf, []): + self.cli_set(self._base_path + [intf] + option.split()) + + self.cli_commit() + + # Validate interface description + for intf in self._interfaces: + tmp = read_file(f'/sys/class/net/{intf}/ifalias') + self.assertEqual(tmp, test_string) + self.assertEqual(Interface(intf).get_alias(), test_string) + + def test_add_single_ip_address(self): + addr = '192.0.2.0/31' + for intf in self._interfaces: + self.cli_set(self._base_path + [intf, 'address', addr]) + for option in self._options.get(intf, []): + self.cli_set(self._base_path + [intf] + option.split()) + + self.cli_commit() + + for intf in self._interfaces: + self.assertTrue(is_intf_addr_assigned(intf, addr)) + self.assertEqual(Interface(intf).get_admin_state(), 'up') + + def test_add_multiple_ip_addresses(self): + # Add address + for intf in self._interfaces: + for option in self._options.get(intf, []): + self.cli_set(self._base_path + [intf] + option.split()) + for addr in self._test_addr: + self.cli_set(self._base_path + [intf, 'address', addr]) + + self.cli_commit() + + # Validate address + for intf in self._interfaces: + for af in AF_INET, AF_INET6: + for addr in ifaddresses(intf)[af]: + # checking link local addresses makes no sense + if is_ipv6_link_local(addr['addr']): + continue + + self.assertTrue(is_intf_addr_assigned(intf, addr['addr'])) + + def test_ipv6_link_local_address(self): + # Common function for IPv6 link-local address assignemnts + if not self._test_ipv6: + self.skipTest('not supported') + + for interface in self._interfaces: + base = self._base_path + [interface] + # just set the interface base without any option - some interfaces + # (VTI) do not require any option to be brought up + self.cli_set(base) + for option in self._options.get(interface, []): + self.cli_set(base + option.split()) + + # after commit we must have an IPv6 link-local address + self.cli_commit() + + for interface in self._interfaces: + self.assertIn(AF_INET6, ifaddresses(interface)) + for addr in ifaddresses(interface)[AF_INET6]: + self.assertTrue(is_ipv6_link_local(addr['addr'])) + + # disable IPv6 link-local address assignment + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['ipv6', 'address', 'no-default-link-local']) + + # after commit we must have no IPv6 link-local address + self.cli_commit() + + for interface in self._interfaces: + self.assertNotIn(AF_INET6, ifaddresses(interface)) + + def test_interface_mtu(self): + if not self._test_mtu: + self.skipTest('not supported') + + for intf in self._interfaces: + base = self._base_path + [intf] + self.cli_set(base + ['mtu', self._mtu]) + for option in self._options.get(intf, []): + self.cli_set(base + option.split()) + + # commit interface changes + self.cli_commit() + + # verify changed MTU + for intf in self._interfaces: + tmp = get_interface_config(intf) + self.assertEqual(tmp['mtu'], int(self._mtu)) + + def test_mtu_1200_no_ipv6_interface(self): + # Testcase if MTU can be changed to 1200 on non IPv6 + # enabled interfaces + if not self._test_mtu: + self.skipTest('not supported') + + old_mtu = self._mtu + self._mtu = '1200' + + for intf in self._interfaces: + base = self._base_path + [intf] + for option in self._options.get(intf, []): + self.cli_set(base + option.split()) + self.cli_set(base + ['mtu', self._mtu]) + + # check validate() - can not set low MTU if 'no-default-link-local' + # is not set on CLI + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + for intf in self._interfaces: + base = self._base_path + [intf] + self.cli_set(base + ['ipv6', 'address', 'no-default-link-local']) + + # commit interface changes + self.cli_commit() + + # verify changed MTU + for intf in self._interfaces: + tmp = get_interface_config(intf) + self.assertEqual(tmp['mtu'], int(self._mtu)) + + self._mtu = old_mtu + + def test_vif_8021q_interfaces(self): + # XXX: This testcase is not allowed to run as first testcase, reason + # is the Wireless test will first load the wifi kernel hwsim module + # which creates a wlan0 and wlan1 interface which will fail the + # tearDown() test in the end that no interface is allowed to survive! + if not self._test_vlan: + self.skipTest('not supported') + + for interface in self._interfaces: + base = self._base_path + [interface] + for option in self._options.get(interface, []): + self.cli_set(base + option.split()) + + for vlan in self._vlan_range: + base = self._base_path + [interface, 'vif', vlan] + for address in self._test_addr: + self.cli_set(base + ['address', address]) + + self.cli_commit() + + for intf in self._interfaces: + for vlan in self._vlan_range: + vif = f'{intf}.{vlan}' + for address in self._test_addr: + self.assertTrue(is_intf_addr_assigned(vif, address)) + + self.assertEqual(Interface(vif).get_admin_state(), 'up') + + # T4064: Delete interface addresses, keep VLAN interface + for interface in self._interfaces: + base = self._base_path + [interface] + for vlan in self._vlan_range: + base = self._base_path + [interface, 'vif', vlan] + self.cli_delete(base + ['address']) + + self.cli_commit() + + # Verify no IP address is assigned + for interface in self._interfaces: + for vlan in self._vlan_range: + vif = f'{intf}.{vlan}' + for address in self._test_addr: + self.assertFalse(is_intf_addr_assigned(vif, address)) + + + def test_vif_8021q_mtu_limits(self): + # XXX: This testcase is not allowed to run as first testcase, reason + # is the Wireless test will first load the wifi kernel hwsim module + # which creates a wlan0 and wlan1 interface which will fail the + # tearDown() test in the end that no interface is allowed to survive! + if not self._test_vlan or not self._test_mtu: + self.skipTest('not supported') + + mtu_1500 = '1500' + mtu_9000 = '9000' + + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['mtu', mtu_1500]) + for option in self._options.get(interface, []): + self.cli_set(base + option.split()) + if 'source-interface' in option: + iface = option.split()[-1] + iface_type = Section.section(iface) + self.cli_set(['interfaces', iface_type, iface, 'mtu', mtu_9000]) + + for vlan in self._vlan_range: + base = self._base_path + [interface, 'vif', vlan] + self.cli_set(base + ['mtu', mtu_9000]) + + # check validate() - Interface MTU "9000" too high, parent interface MTU is "1500"! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # Change MTU on base interface to be the same as on the VIF interface + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['mtu', mtu_9000]) + + self.cli_commit() + + # Verify MTU on base and VIF interfaces + for interface in self._interfaces: + tmp = get_interface_config(interface) + self.assertEqual(tmp['mtu'], int(mtu_9000)) + + for vlan in self._vlan_range: + tmp = get_interface_config(f'{interface}.{vlan}') + self.assertEqual(tmp['mtu'], int(mtu_9000)) + + + def test_vif_8021q_qos_change(self): + # XXX: This testcase is not allowed to run as first testcase, reason + # is the Wireless test will first load the wifi kernel hwsim module + # which creates a wlan0 and wlan1 interface which will fail the + # tearDown() test in the end that no interface is allowed to survive! + if not self._test_vlan: + self.skipTest('not supported') + + for interface in self._interfaces: + base = self._base_path + [interface] + for option in self._options.get(interface, []): + self.cli_set(base + option.split()) + + for vlan in self._vlan_range: + base = self._base_path + [interface, 'vif', vlan] + self.cli_set(base + ['ingress-qos', '0:1']) + self.cli_set(base + ['egress-qos', '1:6']) + + self.cli_commit() + + for intf in self._interfaces: + for vlan in self._vlan_range: + vif = f'{intf}.{vlan}' + tmp = get_interface_config(f'{vif}') + + tmp2 = dict_search('linkinfo.info_data.ingress_qos', tmp) + for item in tmp2 if tmp2 else []: + from_key = item['from'] + to_key = item['to'] + self.assertEqual(from_key, 0) + self.assertEqual(to_key, 1) + + tmp2 = dict_search('linkinfo.info_data.egress_qos', tmp) + for item in tmp2 if tmp2 else []: + from_key = item['from'] + to_key = item['to'] + self.assertEqual(from_key, 1) + self.assertEqual(to_key, 6) + + self.assertEqual(Interface(vif).get_admin_state(), 'up') + + new_ingress_qos_from = 1 + new_ingress_qos_to = 6 + new_egress_qos_from = 2 + new_egress_qos_to = 7 + for interface in self._interfaces: + base = self._base_path + [interface] + for vlan in self._vlan_range: + base = self._base_path + [interface, 'vif', vlan] + self.cli_set(base + ['ingress-qos', f'{new_ingress_qos_from}:{new_ingress_qos_to}']) + self.cli_set(base + ['egress-qos', f'{new_egress_qos_from}:{new_egress_qos_to}']) + + self.cli_commit() + + for intf in self._interfaces: + for vlan in self._vlan_range: + vif = f'{intf}.{vlan}' + tmp = get_interface_config(f'{vif}') + + tmp2 = dict_search('linkinfo.info_data.ingress_qos', tmp) + if tmp2: + from_key = tmp2[0]['from'] + to_key = tmp2[0]['to'] + self.assertEqual(from_key, new_ingress_qos_from) + self.assertEqual(to_key, new_ingress_qos_to) + + tmp2 = dict_search('linkinfo.info_data.egress_qos', tmp) + if tmp2: + from_key = tmp2[0]['from'] + to_key = tmp2[0]['to'] + self.assertEqual(from_key, new_egress_qos_from) + self.assertEqual(to_key, new_egress_qos_to) + + def test_vif_8021q_lower_up_down(self): + # Testcase for https://vyos.dev/T3349 + if not self._test_vlan: + self.skipTest('not supported') + + for interface in self._interfaces: + base = self._base_path + [interface] + for option in self._options.get(interface, []): + self.cli_set(base + option.split()) + + # disable the lower interface + self.cli_set(base + ['disable']) + + for vlan in self._vlan_range: + vlan_base = self._base_path + [interface, 'vif', vlan] + # disable the vlan interface + self.cli_set(vlan_base + ['disable']) + + self.cli_commit() + + # re-enable all lower interfaces + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_delete(base + ['disable']) + + self.cli_commit() + + # verify that the lower interfaces are admin up and the vlan + # interfaces are all admin down + for interface in self._interfaces: + self.assertEqual(Interface(interface).get_admin_state(), 'up') + + for vlan in self._vlan_range: + ifname = f'{interface}.{vlan}' + self.assertEqual(Interface(ifname).get_admin_state(), 'down') + + + def test_vif_s_8021ad_vlan_interfaces(self): + # XXX: This testcase is not allowed to run as first testcase, reason + # is the Wireless test will first load the wifi kernel hwsim module + # which creates a wlan0 and wlan1 interface which will fail the + # tearDown() test in the end that no interface is allowed to survive! + if not self._test_qinq: + self.skipTest('not supported') + + for interface in self._interfaces: + base = self._base_path + [interface] + for option in self._options.get(interface, []): + self.cli_set(base + option.split()) + + for vif_s in self._qinq_range: + for vif_c in self._vlan_range: + base = self._base_path + [interface, 'vif-s', vif_s, 'vif-c', vif_c] + self.cli_set(base + ['mtu', self._mtu]) + for address in self._test_addr: + self.cli_set(base + ['address', address]) + + self.cli_commit() + + for interface in self._interfaces: + for vif_s in self._qinq_range: + tmp = get_interface_config(f'{interface}.{vif_s}') + self.assertEqual(dict_search('linkinfo.info_data.protocol', tmp), '802.1ad') + + for vif_c in self._vlan_range: + vif = f'{interface}.{vif_s}.{vif_c}' + # For an unknown reason this regularely fails on the QEMU builds, + # thus the test for reading back IP addresses is temporary + # disabled. There is no big deal here, as this uses the same + # methods on 802.1q and here it works and is verified. +# for address in self._test_addr: +# self.assertTrue(is_intf_addr_assigned(vif, address)) + + tmp = get_interface_config(vif) + self.assertEqual(tmp['mtu'], int(self._mtu)) + + + # T4064: Delete interface addresses, keep VLAN interface + for interface in self._interfaces: + base = self._base_path + [interface] + for vif_s in self._qinq_range: + for vif_c in self._vlan_range: + self.cli_delete(self._base_path + [interface, 'vif-s', vif_s, 'vif-c', vif_c, 'address']) + + self.cli_commit() + # Verify no IP address is assigned + for interface in self._interfaces: + base = self._base_path + [interface] + for vif_s in self._qinq_range: + for vif_c in self._vlan_range: + vif = f'{interface}.{vif_s}.{vif_c}' + for address in self._test_addr: + self.assertFalse(is_intf_addr_assigned(vif, address)) + + # T3972: remove vif-c interfaces from vif-s + for interface in self._interfaces: + base = self._base_path + [interface] + for vif_s in self._qinq_range: + base = self._base_path + [interface, 'vif-s', vif_s, 'vif-c'] + self.cli_delete(base) + + self.cli_commit() + + + def test_vif_s_protocol_change(self): + # XXX: This testcase is not allowed to run as first testcase, reason + # is the Wireless test will first load the wifi kernel hwsim module + # which creates a wlan0 and wlan1 interface which will fail the + # tearDown() test in the end that no interface is allowed to survive! + if not self._test_qinq: + self.skipTest('not supported') + + for interface in self._interfaces: + base = self._base_path + [interface] + for option in self._options.get(interface, []): + self.cli_set(base + option.split()) + + for vif_s in self._qinq_range: + for vif_c in self._vlan_range: + base = self._base_path + [interface, 'vif-s', vif_s, 'vif-c', vif_c] + for address in self._test_addr: + self.cli_set(base + ['address', address]) + + self.cli_commit() + + for interface in self._interfaces: + for vif_s in self._qinq_range: + tmp = get_interface_config(f'{interface}.{vif_s}') + # check for the default value + self.assertEqual(tmp['linkinfo']['info_data']['protocol'], '802.1ad') + + # T3532: now change ethertype + new_protocol = '802.1q' + for interface in self._interfaces: + for vif_s in self._qinq_range: + base = self._base_path + [interface, 'vif-s', vif_s] + self.cli_set(base + ['protocol', new_protocol]) + + self.cli_commit() + + # Verify new ethertype configuration + for interface in self._interfaces: + for vif_s in self._qinq_range: + tmp = get_interface_config(f'{interface}.{vif_s}') + self.assertEqual(tmp['linkinfo']['info_data']['protocol'], new_protocol.upper()) + + def test_interface_ip_options(self): + if not self._test_ip: + self.skipTest('not supported') + + arp_tmo = '300' + mss = '1420' + + for interface in self._interfaces: + path = self._base_path + [interface] + for option in self._options.get(interface, []): + self.cli_set(path + option.split()) + + # Options + if cli_defined(self._base_path + ['ip'], 'adjust-mss'): + self.cli_set(path + ['ip', 'adjust-mss', mss]) + + if cli_defined(self._base_path + ['ip'], 'arp-cache-timeout'): + self.cli_set(path + ['ip', 'arp-cache-timeout', arp_tmo]) + + if cli_defined(self._base_path + ['ip'], 'disable-arp-filter'): + self.cli_set(path + ['ip', 'disable-arp-filter']) + + if cli_defined(self._base_path + ['ip'], 'disable-forwarding'): + self.cli_set(path + ['ip', 'disable-forwarding']) + + if cli_defined(self._base_path + ['ip'], 'enable-directed-broadcast'): + self.cli_set(path + ['ip', 'enable-directed-broadcast']) + + if cli_defined(self._base_path + ['ip'], 'enable-arp-accept'): + self.cli_set(path + ['ip', 'enable-arp-accept']) + + if cli_defined(self._base_path + ['ip'], 'enable-arp-announce'): + self.cli_set(path + ['ip', 'enable-arp-announce']) + + if cli_defined(self._base_path + ['ip'], 'enable-arp-ignore'): + self.cli_set(path + ['ip', 'enable-arp-ignore']) + + if cli_defined(self._base_path + ['ip'], 'enable-proxy-arp'): + self.cli_set(path + ['ip', 'enable-proxy-arp']) + + if cli_defined(self._base_path + ['ip'], 'proxy-arp-pvlan'): + self.cli_set(path + ['ip', 'proxy-arp-pvlan']) + + if cli_defined(self._base_path + ['ip'], 'source-validation'): + self.cli_set(path + ['ip', 'source-validation', 'loose']) + + self.cli_commit() + + for interface in self._interfaces: + if cli_defined(self._base_path + ['ip'], 'adjust-mss'): + base_options = f'oifname "{interface}"' + out = cmd('sudo nft list chain raw VYOS_TCP_MSS') + for line in out.splitlines(): + if line.startswith(base_options): + self.assertIn(f'tcp option maxseg size set {mss}', line) + + if cli_defined(self._base_path + ['ip'], 'arp-cache-timeout'): + tmp = read_file(f'/proc/sys/net/ipv4/neigh/{interface}/base_reachable_time_ms') + self.assertEqual(tmp, str((int(arp_tmo) * 1000))) # tmo value is in milli seconds + + proc_base = f'/proc/sys/net/ipv4/conf/{interface}' + + if cli_defined(self._base_path + ['ip'], 'disable-arp-filter'): + tmp = read_file(f'{proc_base}/arp_filter') + self.assertEqual('0', tmp) + + if cli_defined(self._base_path + ['ip'], 'enable-arp-accept'): + tmp = read_file(f'{proc_base}/arp_accept') + self.assertEqual('1', tmp) + + if cli_defined(self._base_path + ['ip'], 'enable-arp-announce'): + tmp = read_file(f'{proc_base}/arp_announce') + self.assertEqual('1', tmp) + + if cli_defined(self._base_path + ['ip'], 'enable-arp-ignore'): + tmp = read_file(f'{proc_base}/arp_ignore') + self.assertEqual('1', tmp) + + if cli_defined(self._base_path + ['ip'], 'disable-forwarding'): + tmp = read_file(f'{proc_base}/forwarding') + self.assertEqual('0', tmp) + + if cli_defined(self._base_path + ['ip'], 'enable-directed-broadcast'): + tmp = read_file(f'{proc_base}/bc_forwarding') + self.assertEqual('1', tmp) + + if cli_defined(self._base_path + ['ip'], 'enable-proxy-arp'): + tmp = read_file(f'{proc_base}/proxy_arp') + self.assertEqual('1', tmp) + + if cli_defined(self._base_path + ['ip'], 'proxy-arp-pvlan'): + tmp = read_file(f'{proc_base}/proxy_arp_pvlan') + self.assertEqual('1', tmp) + + if cli_defined(self._base_path + ['ip'], 'source-validation'): + base_options = f'iifname "{interface}"' + out = cmd('sudo nft list chain ip raw vyos_rpfilter') + for line in out.splitlines(): + if line.startswith(base_options): + self.assertIn('fib saddr oif 0', line) + self.assertIn('drop', line) + + def test_interface_ipv6_options(self): + if not self._test_ipv6: + self.skipTest('not supported') + + mss = '1400' + dad_transmits = '10' + accept_dad = '0' + source_validation = 'strict' + + for interface in self._interfaces: + path = self._base_path + [interface] + for option in self._options.get(interface, []): + self.cli_set(path + option.split()) + + # Options + if cli_defined(self._base_path + ['ipv6'], 'adjust-mss'): + self.cli_set(path + ['ipv6', 'adjust-mss', mss]) + + if cli_defined(self._base_path + ['ipv6'], 'accept-dad'): + self.cli_set(path + ['ipv6', 'accept-dad', accept_dad]) + + if cli_defined(self._base_path + ['ipv6'], 'dup-addr-detect-transmits'): + self.cli_set(path + ['ipv6', 'dup-addr-detect-transmits', dad_transmits]) + + if cli_defined(self._base_path + ['ipv6'], 'disable-forwarding'): + self.cli_set(path + ['ipv6', 'disable-forwarding']) + + if cli_defined(self._base_path + ['ipv6'], 'source-validation'): + self.cli_set(path + ['ipv6', 'source-validation', source_validation]) + + self.cli_commit() + + for interface in self._interfaces: + proc_base = f'/proc/sys/net/ipv6/conf/{interface}' + if cli_defined(self._base_path + ['ipv6'], 'adjust-mss'): + base_options = f'oifname "{interface}"' + out = cmd('sudo nft list chain ip6 raw VYOS_TCP_MSS') + for line in out.splitlines(): + if line.startswith(base_options): + self.assertIn(f'tcp option maxseg size set {mss}', line) + + if cli_defined(self._base_path + ['ipv6'], 'accept-dad'): + tmp = read_file(f'{proc_base}/accept_dad') + self.assertEqual(accept_dad, tmp) + + if cli_defined(self._base_path + ['ipv6'], 'dup-addr-detect-transmits'): + tmp = read_file(f'{proc_base}/dad_transmits') + self.assertEqual(dad_transmits, tmp) + + if cli_defined(self._base_path + ['ipv6'], 'disable-forwarding'): + tmp = read_file(f'{proc_base}/forwarding') + self.assertEqual('0', tmp) + + if cli_defined(self._base_path + ['ipv6'], 'source-validation'): + base_options = f'iifname "{interface}"' + out = cmd('sudo nft list chain ip6 raw vyos_rpfilter') + for line in out.splitlines(): + if line.startswith(base_options): + self.assertIn('fib saddr . iif oif 0', line) + self.assertIn('drop', line) + + def test_dhcpv6_client_options(self): + if not self._test_ipv6_dhcpc6: + self.skipTest('not supported') + + duid_base = 10 + for interface in self._interfaces: + duid = '00:01:00:01:27:71:db:f0:00:50:00:00:00:{}'.format(duid_base) + path = self._base_path + [interface] + for option in self._options.get(interface, []): + self.cli_set(path + option.split()) + + # Enable DHCPv6 client + self.cli_set(path + ['address', 'dhcpv6']) + self.cli_set(path + ['dhcpv6-options', 'no-release']) + self.cli_set(path + ['dhcpv6-options', 'rapid-commit']) + self.cli_set(path + ['dhcpv6-options', 'parameters-only']) + self.cli_set(path + ['dhcpv6-options', 'duid', duid]) + duid_base += 1 + + self.cli_commit() + + duid_base = 10 + for interface in self._interfaces: + duid = '00:01:00:01:27:71:db:f0:00:50:00:00:00:{}'.format(duid_base) + dhcpc6_config = read_file(f'{dhcp6c_base_dir}/dhcp6c.{interface}.conf') + self.assertIn(f'interface {interface} ' + '{', dhcpc6_config) + self.assertIn(f' request domain-name-servers;', dhcpc6_config) + self.assertIn(f' request domain-name;', dhcpc6_config) + self.assertIn(f' information-only;', dhcpc6_config) + self.assertIn(f' send ia-na 0;', dhcpc6_config) + self.assertIn(f' send rapid-commit;', dhcpc6_config) + self.assertIn(f' send client-id {duid};', dhcpc6_config) + self.assertIn('};', dhcpc6_config) + duid_base += 1 + + # Better ask the process about it's commandline in the future + pid = process_named_running(dhcp6c_process_name, cmdline=interface, timeout=10) + self.assertTrue(pid) + + dhcp6c_options = read_file(f'/proc/{pid}/cmdline') + self.assertIn('-n', dhcp6c_options) + + def test_dhcpv6pd_auto_sla_id(self): + if not self._test_ipv6_pd: + self.skipTest('not supported') + + prefix_len = '56' + sla_len = str(64 - int(prefix_len)) + + # Create delegatee interfaces first to avoid any confusion by dhcpc6 + # this is mainly an "issue" with virtual-ethernet interfaces + delegatees = ['dum2340', 'dum2341', 'dum2342', 'dum2343', 'dum2344'] + for delegatee in delegatees: + section = Section.section(delegatee) + self.cli_set(['interfaces', section, delegatee]) + + self.cli_commit() + + for interface in self._interfaces: + path = self._base_path + [interface] + for option in self._options.get(interface, []): + self.cli_set(path + option.split()) + + address = '1' + # prefix delegation stuff + pd_base = path + ['dhcpv6-options', 'pd', '0'] + self.cli_set(pd_base + ['length', prefix_len]) + + for delegatee in delegatees: + self.cli_set(pd_base + ['interface', delegatee, 'address', address]) + # increment interface address + address = str(int(address) + 1) + + self.cli_commit() + + for interface in self._interfaces: + dhcpc6_config = read_file(f'{dhcp6c_base_dir}/dhcp6c.{interface}.conf') + + # verify DHCPv6 prefix delegation + self.assertIn(f'prefix ::/{prefix_len} infinity;', dhcpc6_config) + + address = '1' + sla_id = '0' + for delegatee in delegatees: + self.assertIn(f'prefix-interface {delegatee}' + r' {', dhcpc6_config) + self.assertIn(f'ifid {address};', dhcpc6_config) + self.assertIn(f'sla-id {sla_id};', dhcpc6_config) + self.assertIn(f'sla-len {sla_len};', dhcpc6_config) + + # increment sla-id + sla_id = str(int(sla_id) + 1) + # increment interface address + address = str(int(address) + 1) + + # Check for running process + self.assertTrue(process_named_running(dhcp6c_process_name, cmdline=interface, timeout=10)) + + for delegatee in delegatees: + # we can already cleanup the test delegatee interface here + # as until commit() is called, nothing happens + section = Section.section(delegatee) + self.cli_delete(['interfaces', section, delegatee]) + + def test_dhcpv6pd_manual_sla_id(self): + if not self._test_ipv6_pd: + self.skipTest('not supported') + + prefix_len = '56' + sla_len = str(64 - int(prefix_len)) + + # Create delegatee interfaces first to avoid any confusion by dhcpc6 + # this is mainly an "issue" with virtual-ethernet interfaces + delegatees = ['dum3340', 'dum3341', 'dum3342', 'dum3343', 'dum3344'] + for delegatee in delegatees: + section = Section.section(delegatee) + self.cli_set(['interfaces', section, delegatee]) + + self.cli_commit() + + for interface in self._interfaces: + path = self._base_path + [interface] + for option in self._options.get(interface, []): + self.cli_set(path + option.split()) + + # prefix delegation stuff + address = '1' + sla_id = '1' + pd_base = path + ['dhcpv6-options', 'pd', '0'] + self.cli_set(pd_base + ['length', prefix_len]) + + for delegatee in delegatees: + self.cli_set(pd_base + ['interface', delegatee, 'address', address]) + self.cli_set(pd_base + ['interface', delegatee, 'sla-id', sla_id]) + + # increment interface address + address = str(int(address) + 1) + sla_id = str(int(sla_id) + 1) + + self.cli_commit() + + # Verify dhcpc6 client configuration + for interface in self._interfaces: + address = '1' + sla_id = '1' + dhcpc6_config = read_file(f'{dhcp6c_base_dir}/dhcp6c.{interface}.conf') + + # verify DHCPv6 prefix delegation + self.assertIn(f'prefix ::/{prefix_len} infinity;', dhcpc6_config) + + for delegatee in delegatees: + self.assertIn(f'prefix-interface {delegatee}' + r' {', dhcpc6_config) + self.assertIn(f'ifid {address};', dhcpc6_config) + self.assertIn(f'sla-id {sla_id};', dhcpc6_config) + self.assertIn(f'sla-len {sla_len};', dhcpc6_config) + + # increment sla-id + sla_id = str(int(sla_id) + 1) + # increment interface address + address = str(int(address) + 1) + + # Check for running process + self.assertTrue(process_named_running(dhcp6c_process_name, cmdline=interface, timeout=10)) + + for delegatee in delegatees: + # we can already cleanup the test delegatee interface here + # as until commit() is called, nothing happens + section = Section.section(delegatee) + self.cli_delete(['interfaces', section, delegatee]) + + def test_eapol(self): + if not self._test_eapol: + self.skipTest('not supported') + + cfg_dir = '/run/wpa_supplicant' + + ca_certs = { + 'eapol-server-ca-root': server_ca_root_cert_data, + 'eapol-server-ca-intermediate': server_ca_intermediate_cert_data, + 'eapol-client-ca-root': client_ca_root_cert_data, + 'eapol-client-ca-intermediate': client_ca_intermediate_cert_data, + } + cert_name = 'eapol-client' + + for name, data in ca_certs.items(): + self.cli_set(['pki', 'ca', name, 'certificate', data.replace('\n','')]) + + self.cli_set(['pki', 'certificate', cert_name, 'certificate', client_cert_data.replace('\n','')]) + self.cli_set(['pki', 'certificate', cert_name, 'private', 'key', client_key_data.replace('\n','')]) + + for interface in self._interfaces: + path = self._base_path + [interface] + for option in self._options.get(interface, []): + self.cli_set(path + option.split()) + + # Enable EAPoL + self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-server-ca-intermediate']) + self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-client-ca-intermediate']) + self.cli_set(self._base_path + [interface, 'eapol', 'certificate', cert_name]) + + self.cli_commit() + + # Test multiple CA chains + self.assertEqual(get_certificate_count(interface, 'ca'), 4) + + for interface in self._interfaces: + self.cli_delete(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-client-ca-intermediate']) + + self.cli_commit() + + # Validate interface config + for interface in self._interfaces: + tmp = get_wpa_supplicant_value(interface, 'key_mgmt') + self.assertEqual('IEEE8021X', tmp) + + tmp = get_wpa_supplicant_value(interface, 'eap') + self.assertEqual('TLS', tmp) + + tmp = get_wpa_supplicant_value(interface, 'eapol_flags') + self.assertEqual('0', tmp) + + tmp = get_wpa_supplicant_value(interface, 'ca_cert') + self.assertEqual(f'"{cfg_dir}/{interface}_ca.pem"', tmp) + + tmp = get_wpa_supplicant_value(interface, 'client_cert') + self.assertEqual(f'"{cfg_dir}/{interface}_cert.pem"', tmp) + + tmp = get_wpa_supplicant_value(interface, 'private_key') + self.assertEqual(f'"{cfg_dir}/{interface}_cert.key"', tmp) + + mac = read_file(f'/sys/class/net/{interface}/address') + tmp = get_wpa_supplicant_value(interface, 'identity') + self.assertEqual(f'"{mac}"', tmp) + + # Check certificate files have the full chain + self.assertEqual(get_certificate_count(interface, 'ca'), 2) + self.assertEqual(get_certificate_count(interface, 'cert'), 3) + + # Check for running process + self.assertTrue(process_named_running('wpa_supplicant', cmdline=f'-i{interface}')) + + # Remove EAPoL configuration + for interface in self._interfaces: + self.cli_delete(self._base_path + [interface, 'eapol']) + + # Commit and check that process is no longer running + self.cli_commit() + self.assertFalse(process_named_running('wpa_supplicant')) + + for name in ca_certs: + self.cli_delete(['pki', 'ca', name]) + self.cli_delete(['pki', 'certificate', cert_name]) diff --git a/smoketest/scripts/cli/base_vyostest_shim.py b/smoketest/scripts/cli/base_vyostest_shim.py new file mode 100644 index 0000000..940306a --- /dev/null +++ b/smoketest/scripts/cli/base_vyostest_shim.py @@ -0,0 +1,162 @@ +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest +import paramiko +import pprint + +from time import sleep +from typing import Type + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos import ConfigError +from vyos.defaults import commit_lock +from vyos.utils.process import cmd +from vyos.utils.process import run + +save_config = '/tmp/vyos-smoketest-save' + +# This class acts as shim between individual Smoketests developed for VyOS and +# the Python UnitTest framework. Before every test is loaded, we dump the current +# system configuration and reload it after the test - despite the test results. +# +# Using this approach we can not render a live system useless while running any +# kind of smoketest. In addition it adds debug capabilities like printing the +# command used to execute the test. +class VyOSUnitTestSHIM: + class TestCase(unittest.TestCase): + # if enabled in derived class, print out each and every set/del command + # on the CLI. This is usefull to grap all the commands required to + # trigger the certain failure condition. + # Use "self.debug = True" in derived classes setUp() method + debug = False + + @classmethod + def setUpClass(cls): + cls._session = ConfigSession(os.getpid()) + cls._session.save_config(save_config) + if os.path.exists('/tmp/vyos.smoketest.debug'): + cls.debug = True + pass + + @classmethod + def tearDownClass(cls): + # discard any pending changes which might caused a messed up config + cls._session.discard() + # ... and restore the initial state + cls._session.migrate_and_load_config(save_config) + + try: + cls._session.commit() + except (ConfigError, ConfigSessionError): + cls._session.discard() + cls.fail(cls) + + def cli_set(self, config): + if self.debug: + print('set ' + ' '.join(config)) + self._session.set(config) + + def cli_delete(self, config): + if self.debug: + print('del ' + ' '.join(config)) + self._session.delete(config) + + def cli_discard(self): + if self.debug: + print('DISCARD') + self._session.discard() + + def cli_commit(self): + if self.debug: + print('commit') + self._session.commit() + # during a commit there is a process opening commit_lock, and run() returns 0 + while run(f'sudo lsof -nP {commit_lock}') == 0: + sleep(0.250) + + def op_mode(self, path : list) -> None: + """ + Execute OP-mode command and return stdout + """ + if self.debug: + print('commit') + path = ' '.join(path) + out = cmd(f'/opt/vyatta/bin/vyatta-op-cmd-wrapper {path}') + if self.debug: + print(f'\n\ncommand "{path}" returned:\n') + pprint.pprint(out) + return out + + def getFRRconfig(self, string=None, end='$', endsection='^!', daemon=''): + """ Retrieve current "running configuration" from FRR """ + command = f'vtysh -c "show run {daemon} no-header"' + if string: command += f' | sed -n "/^{string}{end}/,/{endsection}/p"' + out = cmd(command) + if self.debug: + print(f'\n\ncommand "{command}" returned:\n') + pprint.pprint(out) + return out + + @staticmethod + def ssh_send_cmd(command, username, password, hostname='localhost'): + """ SSH command execution helper """ + # Try to login via SSH + ssh_client = paramiko.SSHClient() + ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh_client.connect(hostname=hostname, username=username, password=password) + _, stdout, stderr = ssh_client.exec_command(command) + output = stdout.read().decode().strip() + error = stderr.read().decode().strip() + ssh_client.close() + return output, error + + # Verify nftables output + def verify_nftables(self, nftables_search, table, inverse=False, args=''): + nftables_output = cmd(f'sudo nft {args} list table {table}') + + for search in nftables_search: + matched = False + for line in nftables_output.split("\n"): + if all(item in line for item in search): + matched = True + break + self.assertTrue(not matched if inverse else matched, msg=search) + + def verify_nftables_chain(self, nftables_search, table, chain, inverse=False, args=''): + nftables_output = cmd(f'sudo nft {args} list chain {table} {chain}') + + for search in nftables_search: + matched = False + for line in nftables_output.split("\n"): + if all(item in line for item in search): + matched = True + break + self.assertTrue(not matched if inverse else matched, msg=search) + +# standard construction; typing suggestion: https://stackoverflow.com/a/70292317 +def ignore_warning(warning: Type[Warning]): + import warnings + from functools import wraps + + def inner(f): + @wraps(f) + def wrapped(*args, **kwargs): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=warning) + return f(*args, **kwargs) + return wrapped + return inner diff --git a/smoketest/scripts/cli/test_backslash_escape.py b/smoketest/scripts/cli/test_backslash_escape.py new file mode 100644 index 0000000..e94e9ab --- /dev/null +++ b/smoketest/scripts/cli/test_backslash_escape.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configtree import ConfigTree + +base_path = ['interfaces', 'ethernet', 'eth0', 'description'] + +cases_word = [r'fo\o', r'fo\\o', r'foço\o', r'foço\\o'] +# legacy CLI output quotes only if whitespace present; this is a notable +# difference that confounds the translation legacy -> modern, hence +# determines the regex used in function replace_backslash +cases_phrase = [r'some fo\o', r'some fo\\o', r'some foço\o', r'some foço\\o'] + +case_save_config = '/tmp/smoketest-case-save' + +class TestBackslashEscape(VyOSUnitTestSHIM.TestCase): + def test_backslash_escape_word(self): + for case in cases_word: + self.cli_set(base_path + [case]) + self.cli_commit() + # save_config tests translation though subsystems: + # legacy output -> config -> configtree -> file + self._session.save_config(case_save_config) + # reload to configtree and confirm: + with open(case_save_config) as f: + config_string = f.read() + ct = ConfigTree(config_string) + res = ct.return_value(base_path) + self.assertEqual(case, res, msg=res) + print(f'description: {res}') + self.cli_delete(base_path) + self.cli_commit() + + def test_backslash_escape_phrase(self): + for case in cases_phrase: + self.cli_set(base_path + [case]) + self.cli_commit() + # save_config tests translation though subsystems: + # legacy output -> config -> configtree -> file + self._session.save_config(case_save_config) + # reload to configtree and confirm: + with open(case_save_config) as f: + config_string = f.read() + ct = ConfigTree(config_string) + res = ct.return_value(base_path) + self.assertEqual(case, res, msg=res) + print(f'description: {res}') + self.cli_delete(base_path) + self.cli_commit() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_cgnat.py b/smoketest/scripts/cli/test_cgnat.py new file mode 100644 index 0000000..02dad3d --- /dev/null +++ b/smoketest/scripts/cli/test_cgnat.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError + + +base_path = ['nat', 'cgnat'] +nftables_cgnat_config = '/run/nftables-cgnat.nft' + + +class TestCGNAT(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestCGNAT, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + self.assertFalse(os.path.exists(nftables_cgnat_config)) + + def test_cgnat(self): + internal_name = 'vyos-int-01' + external_name = 'vyos-ext-01' + internal_net = '100.64.0.0/29' + external_net = '192.0.2.1-192.0.2.2' + external_ports = '40000-60000' + ports_per_subscriber = '5000' + rule = '100' + + nftables_search = [ + ['map tcp_nat_map'], + ['map udp_nat_map'], + ['map icmp_nat_map'], + ['map other_nat_map'], + ['100.64.0.0 : 192.0.2.1 . 40000-44999'], + ['100.64.0.1 : 192.0.2.1 . 45000-49999'], + ['100.64.0.2 : 192.0.2.1 . 50000-54999'], + ['100.64.0.3 : 192.0.2.1 . 55000-59999'], + ['100.64.0.4 : 192.0.2.2 . 40000-44999'], + ['100.64.0.5 : 192.0.2.2 . 45000-49999'], + ['100.64.0.6 : 192.0.2.2 . 50000-54999'], + ['100.64.0.7 : 192.0.2.2 . 55000-59999'], + ['chain POSTROUTING'], + ['type nat hook postrouting priority srcnat'], + ['ip protocol tcp counter snat ip to ip saddr map @tcp_nat_map'], + ['ip protocol udp counter snat ip to ip saddr map @udp_nat_map'], + ['ip protocol icmp counter snat ip to ip saddr map @icmp_nat_map'], + ['counter snat ip to ip saddr map @other_nat_map'], + ] + + self.cli_set(base_path + ['pool', 'external', external_name, 'external-port-range', external_ports]) + self.cli_set(base_path + ['pool', 'external', external_name, 'range', external_net]) + + # allocation out of the available ports + with self.assertRaises(ConfigSessionError): + self.cli_set(base_path + ['pool', 'external', external_name, 'per-user-limit', 'port', '8000']) + self.cli_commit() + self.cli_set(base_path + ['pool', 'external', external_name, 'per-user-limit', 'port', ports_per_subscriber]) + + # internal pool not set + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['pool', 'internal', internal_name, 'range', internal_net]) + + self.cli_set(base_path + ['rule', rule, 'source', 'pool', internal_name]) + # non-exist translation pool + with self.assertRaises(ConfigSessionError): + self.cli_set(base_path + ['rule', rule, 'translation', 'pool', 'fake-pool']) + self.cli_commit() + + self.cli_set(base_path + ['rule', rule, 'translation', 'pool', external_name]) + self.cli_commit() + + self.verify_nftables(nftables_search, 'ip cgnat', inverse=False, args='-s') + + + def test_cgnat_sequence(self): + internal_name = 'earth' + external_name = 'milky_way' + internal_net = '100.64.0.0/28' + + ext_addr_alpha_proxima = '192.0.2.121/32' + ext_addr_beta_cygni = '198.51.100.23/32' + ext_addr_gamma_leonis = '203.0.113.102/32' + + ext_seq_beta_cygni = '3' + ext_seq_gamma_leonis = '10' + + external_ports = '1024-65535' + ports_per_subscriber = '10000' + rule = '100' + + nftables_search = [ + ['100.64.0.0 : 198.51.100.23 . 1024-11023, 100.64.0.1 : 198.51.100.23 . 11024-21023'], + ['100.64.0.4 : 198.51.100.23 . 41024-51023, 100.64.0.5 : 198.51.100.23 . 51024-61023'], + ['100.64.0.6 : 203.0.113.102 . 1024-11023, 100.64.0.7 : 203.0.113.102 . 11024-21023'], + ['100.64.0.8 : 203.0.113.102 . 21024-31023, 100.64.0.9 : 203.0.113.102 . 31024-41023'], + ['100.64.0.10 : 203.0.113.102 . 41024-51023, 100.64.0.11 : 203.0.113.102 . 51024-61023'], + ['100.64.0.12 : 192.0.2.121 . 1024-11023, 100.64.0.13 : 192.0.2.121 . 11024-21023'], + ['100.64.0.14 : 192.0.2.121 . 21024-31023, 100.64.0.15 : 192.0.2.121 . 31024-41023'], + ] + + self.cli_set(base_path + ['pool', 'external', external_name, 'external-port-range', external_ports]) + self.cli_set(base_path + ['pool', 'external', external_name, 'per-user-limit', 'port', ports_per_subscriber]) + self.cli_set(base_path + ['pool', 'external', external_name, 'range', ext_addr_alpha_proxima]) + self.cli_set(base_path + ['pool', 'external', external_name, 'range', ext_addr_beta_cygni, 'seq', ext_seq_beta_cygni]) + self.cli_set(base_path + ['pool', 'external', external_name, 'range', ext_addr_gamma_leonis, 'seq', ext_seq_gamma_leonis]) + self.cli_set(base_path + ['pool', 'internal', internal_name, 'range', internal_net]) + self.cli_set(base_path + ['rule', rule, 'source', 'pool', internal_name]) + self.cli_set(base_path + ['rule', rule, 'translation', 'pool', external_name]) + self.cli_commit() + + self.verify_nftables(nftables_search, 'ip cgnat', inverse=False, args='-s') + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_config_dependency.py b/smoketest/scripts/cli/test_config_dependency.py new file mode 100644 index 0000000..99e807a --- /dev/null +++ b/smoketest/scripts/cli/test_config_dependency.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + + +import unittest +from time import sleep + +from vyos.utils.process import is_systemd_service_running +from vyos.utils.process import cmd +from vyos.configsession import ConfigSessionError + +from base_vyostest_shim import VyOSUnitTestSHIM + + +class TestConfigDep(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + # smoketests are run without configd in 1.4; with configd in 1.5 + # the tests below check behavior under configd: + # test_configdep_error checks for regression under configd (T6559) + # test_configdep_prio_queue checks resolution under configd (T6671) + cls.running_state = is_systemd_service_running('vyos-configd.service') + + if not cls.running_state: + cmd('sudo systemctl start vyos-configd.service') + # allow time for init + sleep(1) + + super(TestConfigDep, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestConfigDep, cls).tearDownClass() + + # return to running_state + if not cls.running_state: + cmd('sudo systemctl stop vyos-configd.service') + + def test_configdep_error(self): + address_group = 'AG' + address = '192.168.137.5' + nat_base = ['nat', 'source', 'rule', '10'] + interface = 'eth1' + + self.cli_set(['firewall', 'group', 'address-group', address_group, + 'address', address]) + self.cli_set(nat_base + ['outbound-interface', 'name', interface]) + self.cli_set(nat_base + ['source', 'group', 'address-group', address_group]) + self.cli_set(nat_base + ['translation', 'address', 'masquerade']) + self.cli_commit() + + self.cli_delete(['firewall']) + # check error in call to dependent script (nat) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # clean up remaining + self.cli_delete(['nat']) + self.cli_commit() + + def test_configdep_prio_queue(self): + # confirm that that a dependency (in this case, conntrack -> + # conntrack-sync) is not immediately called if the target is + # scheduled in the priority queue, indicating that it may require an + # intermediate activitation (bond0) + bonding_base = ['interfaces', 'bonding'] + bond_interface = 'bond0' + bond_address = '192.0.2.1/24' + vrrp_group_base = ['high-availability', 'vrrp', 'group'] + vrrp_sync_group_base = ['high-availability', 'vrrp', 'sync-group'] + vrrp_group = 'ETH2' + vrrp_sync_group = 'GROUP' + conntrack_sync_base = ['service', 'conntrack-sync'] + conntrack_peer = '192.0.2.77' + + # simple set to trigger in-session conntrack -> conntrack-sync + # dependency; note that this is triggered on boot in 1.4 due to + # default 'system conntrack modules' + self.cli_set(['system', 'conntrack', 'table-size', '524288']) + + self.cli_set(['interfaces', 'ethernet', 'eth2', 'address', + '198.51.100.2/24']) + + self.cli_set(bonding_base + [bond_interface, 'address', + bond_address]) + self.cli_set(bonding_base + [bond_interface, 'member', 'interface', + 'eth3']) + + self.cli_set(vrrp_group_base + [vrrp_group, 'address', + '198.51.100.200/24']) + self.cli_set(vrrp_group_base + [vrrp_group, 'hello-source-address', + '198.51.100.2']) + self.cli_set(vrrp_group_base + [vrrp_group, 'interface', 'eth2']) + self.cli_set(vrrp_group_base + [vrrp_group, 'priority', '200']) + self.cli_set(vrrp_group_base + [vrrp_group, 'vrid', '22']) + self.cli_set(vrrp_sync_group_base + [vrrp_sync_group, 'member', + vrrp_group]) + + self.cli_set(conntrack_sync_base + ['failover-mechanism', 'vrrp', + 'sync-group', vrrp_sync_group]) + + self.cli_set(conntrack_sync_base + ['interface', bond_interface, + 'peer', conntrack_peer]) + + self.cli_commit() + + # clean up + self.cli_delete(bonding_base) + self.cli_delete(vrrp_group_base) + self.cli_delete(vrrp_sync_group_base) + self.cli_delete(conntrack_sync_base) + self.cli_delete(['interfaces', 'ethernet', 'eth2', 'address']) + self.cli_delete(['system', 'conntrack', 'table-size']) + self.cli_commit() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_configd_init.py b/smoketest/scripts/cli/test_configd_init.py new file mode 100644 index 0000000..245c038 --- /dev/null +++ b/smoketest/scripts/cli/test_configd_init.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest +from time import sleep + +from vyos.utils.process import is_systemd_service_running +from vyos.utils.process import cmd + +class TestConfigdInit(unittest.TestCase): + def setUp(self): + self.running_state = is_systemd_service_running('vyos-configd.service') + + def test_configd_init(self): + if not self.running_state: + cmd('sudo systemctl start vyos-configd.service') + # allow time for init to succeed/fail + sleep(2) + self.assertTrue(is_systemd_service_running('vyos-configd.service')) + + def tearDown(self): + if not self.running_state: + cmd('sudo systemctl stop vyos-configd.service') + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py new file mode 100644 index 0000000..c03b9eb --- /dev/null +++ b/smoketest/scripts/cli/test_container.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest +import glob +import json + +from base_vyostest_shim import VyOSUnitTestSHIM +from ipaddress import ip_interface + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running + +base_path = ['container'] +cont_image = 'busybox:stable' # busybox is included in vyos-build +PROCESS_NAME = 'conmon' +PROCESS_PIDFILE = '/run/vyos-container-{0}.service.pid' + +busybox_image_path = '/usr/share/vyos/busybox-stable.tar' + +def cmd_to_json(command): + c = cmd(command + ' --format=json') + data = json.loads(c)[0] + return data + +class TestContainer(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestContainer, cls).setUpClass() + + # Load image for smoketest provided in vyos-build + try: + cmd(f'cat {busybox_image_path} | sudo podman load') + except: + cls.skipTest(cls, reason='busybox image not available') + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + @classmethod + def tearDownClass(cls): + super(TestContainer, cls).tearDownClass() + + # Cleanup podman image + cmd(f'sudo podman image rm -f {cont_image}') + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + # Ensure no container process remains + self.assertIsNone(process_named_running(PROCESS_NAME)) + + # Ensure systemd units are removed + units = glob.glob('/run/systemd/system/vyos-container-*') + self.assertEqual(units, []) + + def test_basic(self): + cont_name = 'c1' + + self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', '10.0.2.15/24']) + self.cli_set(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '10.0.2.2']) + self.cli_set(['system', 'name-server', '1.1.1.1']) + self.cli_set(['system', 'name-server', '8.8.8.8']) + + self.cli_set(base_path + ['name', cont_name, 'image', cont_image]) + self.cli_set(base_path + ['name', cont_name, 'allow-host-networks']) + self.cli_set(base_path + ['name', cont_name, 'sysctl', 'parameter', 'kernel.msgmax', 'value', '4096']) + + # commit changes + self.cli_commit() + + pid = 0 + with open(PROCESS_PIDFILE.format(cont_name), 'r') as f: + pid = int(f.read()) + + # Check for running process + self.assertEqual(process_named_running(PROCESS_NAME), pid) + + # verify + tmp = cmd(f'sudo podman exec -it {cont_name} sysctl kernel.msgmax') + self.assertEqual(tmp, 'kernel.msgmax = 4096') + + def test_cpu_limit(self): + cont_name = 'c2' + + self.cli_set(base_path + ['name', cont_name, 'allow-host-networks']) + self.cli_set(base_path + ['name', cont_name, 'image', cont_image]) + self.cli_set(base_path + ['name', cont_name, 'cpu-quota', '1.25']) + + self.cli_commit() + + pid = 0 + with open(PROCESS_PIDFILE.format(cont_name), 'r') as f: + pid = int(f.read()) + + # Check for running process + self.assertEqual(process_named_running(PROCESS_NAME), pid) + + def test_ipv4_network(self): + prefix = '192.0.2.0/24' + base_name = 'ipv4' + net_name = 'NET01' + + self.cli_set(base_path + ['network', net_name, 'prefix', prefix]) + + for ii in range(1, 6): + name = f'{base_name}-{ii}' + self.cli_set(base_path + ['name', name, 'image', cont_image]) + self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + ii)]) + + # verify() - first IP address of a prefix can not be used by a container + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + tmp = f'{base_name}-1' + self.cli_delete(base_path + ['name', tmp]) + self.cli_commit() + + n = cmd_to_json(f'sudo podman network inspect {net_name}') + self.assertEqual(n['subnets'][0]['subnet'], prefix) + + # skipt first container, it was never created + for ii in range(2, 6): + name = f'{base_name}-{ii}' + c = cmd_to_json(f'sudo podman container inspect {name}') + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['Gateway'] , str(ip_interface(prefix).ip + 1)) + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPAddress'], str(ip_interface(prefix).ip + ii)) + + def test_ipv6_network(self): + prefix = '2001:db8::/64' + base_name = 'ipv6' + net_name = 'NET02' + + self.cli_set(base_path + ['network', net_name, 'prefix', prefix]) + + for ii in range(1, 6): + name = f'{base_name}-{ii}' + self.cli_set(base_path + ['name', name, 'image', cont_image]) + self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + ii)]) + + # verify() - first IP address of a prefix can not be used by a container + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + tmp = f'{base_name}-1' + self.cli_delete(base_path + ['name', tmp]) + self.cli_commit() + + n = cmd_to_json(f'sudo podman network inspect {net_name}') + self.assertEqual(n['subnets'][0]['subnet'], prefix) + + # skipt first container, it was never created + for ii in range(2, 6): + name = f'{base_name}-{ii}' + c = cmd_to_json(f'sudo podman container inspect {name}') + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPv6Gateway'] , str(ip_interface(prefix).ip + 1)) + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['GlobalIPv6Address'], str(ip_interface(prefix).ip + ii)) + + def test_dual_stack_network(self): + prefix4 = '192.0.2.0/24' + prefix6 = '2001:db8::/64' + base_name = 'dual-stack' + net_name = 'net-4-6' + + self.cli_set(base_path + ['network', net_name, 'prefix', prefix4]) + self.cli_set(base_path + ['network', net_name, 'prefix', prefix6]) + + for ii in range(1, 6): + name = f'{base_name}-{ii}' + self.cli_set(base_path + ['name', name, 'image', cont_image]) + self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix4).ip + ii)]) + self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix6).ip + ii)]) + + # verify() - first IP address of a prefix can not be used by a container + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + tmp = f'{base_name}-1' + self.cli_delete(base_path + ['name', tmp]) + self.cli_commit() + + n = cmd_to_json(f'sudo podman network inspect {net_name}') + self.assertEqual(n['subnets'][0]['subnet'], prefix4) + self.assertEqual(n['subnets'][1]['subnet'], prefix6) + + # skipt first container, it was never created + for ii in range(2, 6): + name = f'{base_name}-{ii}' + c = cmd_to_json(f'sudo podman container inspect {name}') + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPv6Gateway'] , str(ip_interface(prefix6).ip + 1)) + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['GlobalIPv6Address'], str(ip_interface(prefix6).ip + ii)) + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['Gateway'] , str(ip_interface(prefix4).ip + 1)) + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPAddress'] , str(ip_interface(prefix4).ip + ii)) + + def test_no_name_server(self): + prefix = '192.0.2.0/24' + base_name = 'ipv4' + net_name = 'NET01' + + self.cli_set(base_path + ['network', net_name, 'prefix', prefix]) + self.cli_set(base_path + ['network', net_name, 'no-name-server']) + + name = f'{base_name}-2' + self.cli_set(base_path + ['name', name, 'image', cont_image]) + self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + 2)]) + self.cli_commit() + + n = cmd_to_json(f'sudo podman network inspect {net_name}') + self.assertEqual(n['dns_enabled'], False) + + def test_uid_gid(self): + cont_name = 'uid-test' + gid = '100' + uid = '1001' + + self.cli_set(base_path + ['name', cont_name, 'allow-host-networks']) + self.cli_set(base_path + ['name', cont_name, 'image', cont_image]) + self.cli_set(base_path + ['name', cont_name, 'gid', gid]) + + # verify() - GID can only be set if UID is set + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['name', cont_name, 'uid', uid]) + + self.cli_commit() + + # verify + tmp = cmd(f'sudo podman exec -it {cont_name} id -u') + self.assertEqual(tmp, uid) + tmp = cmd(f'sudo podman exec -it {cont_name} id -g') + self.assertEqual(tmp, gid) + + def test_api_socket(self): + base_name = 'api-test' + container_list = range(1, 5) + + for ii in container_list: + name = f'{base_name}-{ii}' + self.cli_set(base_path + ['name', name, 'image', cont_image]) + self.cli_set(base_path + ['name', name, 'allow-host-networks']) + + self.cli_commit() + + # Query API about running containers + tmp = cmd("sudo curl --unix-socket /run/podman/podman.sock -H 'content-type: application/json' -sf http://localhost/containers/json") + tmp = json.loads(tmp) + + # We expect the same amount of containers from the API that we started above + self.assertEqual(len(container_list), len(tmp)) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py new file mode 100644 index 0000000..3e9ec29 --- /dev/null +++ b/smoketest/scripts/cli/test_firewall.py @@ -0,0 +1,1167 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from glob import glob +from time import sleep + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import run +from vyos.utils.file import read_file + +sysfs_config = { + 'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'default': '0', 'test_value': 'disable'}, + 'broadcast_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts', 'default': '1', 'test_value': 'enable'}, + 'directed_broadcast': {'sysfs': '/proc/sys/net/ipv4/conf/all/bc_forwarding', 'default': '1', 'test_value': 'disable'}, + 'ip_src_route': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_source_route', 'default': '0', 'test_value': 'enable'}, + 'ipv6_receive_redirects': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_redirects', 'default': '0', 'test_value': 'enable'}, + 'ipv6_src_route': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_source_route', 'default': '-1', 'test_value': 'enable'}, + 'log_martians': {'sysfs': '/proc/sys/net/ipv4/conf/all/log_martians', 'default': '1', 'test_value': 'disable'}, + 'receive_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_redirects', 'default': '0', 'test_value': 'enable'}, + 'send_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/send_redirects', 'default': '1', 'test_value': 'disable'}, + 'syn_cookies': {'sysfs': '/proc/sys/net/ipv4/tcp_syncookies', 'default': '1', 'test_value': 'disable'}, + 'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337', 'default': '0', 'test_value': 'enable'} +} + +def get_sysctl(parameter): + tmp = parameter.replace(r'.', r'/') + return read_file(f'/proc/sys/{tmp}') + +class TestFirewall(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestFirewall, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, ['firewall']) + + @classmethod + def tearDownClass(cls): + super(TestFirewall, cls).tearDownClass() + + def tearDown(self): + self.cli_delete(['firewall']) + self.cli_commit() + + # Verify chains/sets are cleaned up from nftables + nftables_search = [ + ['set M_smoketest_mac'], + ['set N_smoketest_network'], + ['set P_smoketest_port'], + ['set D_smoketest_domain'], + ['set RECENT_smoketest_4'], + ['chain NAME_smoketest'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_filter', inverse=True) + + def wait_for_domain_resolver(self, table, set_name, element, max_wait=10): + # Resolver no longer blocks commit, need to wait for daemon to populate set + count = 0 + while count < max_wait: + code = run(f'sudo nft get element {table} {set_name} {{ {element} }}') + if code == 0: + return True + count += 1 + sleep(1) + return False + + def test_geoip(self): + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'se']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'gb']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'de']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'fr']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'inverse-match']) + + self.cli_commit() + + nftables_search = [ + ['ip saddr @GEOIP_CC_name_smoketest_1', 'drop'], + ['ip saddr != @GEOIP_CC_name_smoketest_2', 'accept'] + ] + + # -t prevents 1000+ GeoIP elements being returned + self.verify_nftables(nftables_search, 'ip vyos_filter', args='-t') + + def test_groups(self): + hostmap_path = ['system', 'static-host-mapping', 'host-name'] + example_org = ['192.0.2.8', '192.0.2.10', '192.0.2.11'] + + self.cli_set(hostmap_path + ['example.com', 'inet', '192.0.2.5']) + for ips in example_org: + self.cli_set(hostmap_path + ['example.org', 'inet', ips]) + + self.cli_commit() + + self.cli_set(['firewall', 'group', 'mac-group', 'smoketest_mac', 'mac-address', '00:01:02:03:04:05']) + self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24']) + self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '53']) + self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '123']) + self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.com']) + self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.org']) + self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'eth0']) + self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'vtun0']) + + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'destination', 'address', '172.16.10.10']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '2', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '2', 'source', 'group', 'mac-group', 'smoketest_mac']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'source', 'group', 'domain-group', 'smoketest_domain']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'outbound-interface', 'group', '!smoketest_interface']) + + self.cli_commit() + + self.wait_for_domain_resolver('ip vyos_filter', 'D_smoketest_domain', '192.0.2.5') + + nftables_search = [ + ['ip saddr @N_smoketest_network', 'ip daddr 172.16.10.10', 'th dport @P_smoketest_port', 'accept'], + ['elements = { 172.16.99.0/24 }'], + ['elements = { 53, 123 }'], + ['ether saddr @M_smoketest_mac', 'accept'], + ['elements = { 00:01:02:03:04:05 }'], + ['set D_smoketest_domain'], + ['elements = { 192.0.2.5, 192.0.2.8,'], + ['192.0.2.10, 192.0.2.11 }'], + ['ip saddr @D_smoketest_domain', 'accept'], + ['oifname != @I_smoketest_interface', 'accept'] + ] + self.verify_nftables(nftables_search, 'ip vyos_filter') + + self.cli_delete(['system', 'static-host-mapping']) + self.cli_commit() + + def test_nested_groups(self): + self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24']) + self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'network', '172.16.101.0/24']) + self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'include', 'smoketest_network']) + self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '53']) + self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port1', 'port', '123']) + self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port1', 'include', 'smoketest_port']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network1']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port1']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp']) + + self.cli_commit() + + # Test circular includes + self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'include', 'smoketest_network1']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete(['firewall', 'group', 'network-group', 'smoketest_network', 'include', 'smoketest_network1']) + + nftables_search = [ + ['ip saddr @N_smoketest_network1', 'th dport @P_smoketest_port1', 'accept'], + ['elements = { 172.16.99.0/24, 172.16.101.0/24 }'], + ['elements = { 53, 123 }'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_filter') + + def test_ipv4_basic_rules(self): + name = 'smoketest' + interface = 'eth0' + interface_inv = '!eth0' + interface_wc = 'l2tp*' + mss_range = '501-1460' + conn_mark = '555' + + self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'default-log']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'source', 'address', '172.16.20.10']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address', '172.16.10.10']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'log']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'log-options', 'level', 'debug']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'ttl', 'eq', '15']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'reject']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'destination', 'port', '8888']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'log']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'log-options', 'level', 'err']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'tcp', 'flags', 'syn']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'tcp', 'flags', 'not', 'ack']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'ttl', 'gt', '102']) + + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-log']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'destination', 'port', '22']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'limit', 'rate', '5/minute']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'log']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'destination', 'port', '22']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'recent', 'count', '10']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'recent', 'time', 'minute']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'packet-type', 'host']) + + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'tcp', 'flags', 'syn']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'tcp', 'mss', mss_range]) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'packet-type', 'broadcast']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'inbound-interface', 'name', interface_wc]) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'action', 'return']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'protocol', 'gre']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'connection-mark', conn_mark]) + + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'default-log']) + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'protocol', 'gre']) + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'outbound-interface', 'name', interface_inv]) + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'action', 'return']) + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'protocol', 'icmp']) + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'connection-mark', conn_mark]) + + self.cli_set(['firewall', 'ipv4', 'output', 'raw', 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'output', 'raw', 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'output', 'raw', 'rule', '1', 'protocol', 'udp']) + + self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'action', 'notrack']) + self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'destination', 'port', '23']) + + self.cli_commit() + + mark_hex = "{0:#010x}".format(int(conn_mark)) + + nftables_search = [ + ['chain VYOS_FORWARD_filter'], + ['type filter hook forward priority filter; policy accept;'], + ['tcp dport 22', 'limit rate 5/minute', 'accept'], + ['tcp dport 22', 'add @RECENT_FWD_filter_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'meta pkttype host', 'drop'], + ['log prefix "[ipv4-FWD-filter-default-D]"','FWD-filter default-action drop', 'drop'], + ['chain VYOS_INPUT_filter'], + ['type filter hook input priority filter; policy accept;'], + ['tcp flags & syn == syn', f'tcp option maxseg size {mss_range}', f'iifname "{interface_wc}"', 'meta pkttype broadcast', 'accept'], + ['meta l4proto gre', f'ct mark {mark_hex}', 'return'], + ['INP-filter default-action accept', 'accept'], + ['chain VYOS_OUTPUT_filter'], + ['type filter hook output priority filter; policy accept;'], + ['meta l4proto gre', f'oifname != "{interface}"', 'drop'], + ['meta l4proto icmp', f'ct mark {mark_hex}', 'return'], + ['log prefix "[ipv4-OUT-filter-default-D]"','OUT-filter default-action drop', 'drop'], + ['chain VYOS_OUTPUT_raw'], + ['type filter hook output priority raw; policy accept;'], + ['udp', 'accept'], + ['OUT-raw default-action drop', 'drop'], + ['chain VYOS_PREROUTING_raw'], + ['type filter hook prerouting priority raw; policy accept;'], + ['tcp dport 23', 'notrack'], + ['PRE-raw default-action accept', 'accept'], + ['chain NAME_smoketest'], + ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[ipv4-NAM-smoketest-1-A]" log level debug', 'ip ttl 15', 'accept'], + ['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[ipv4-NAM-smoketest-2-R]" log level err', 'ip ttl > 102', 'reject'], + ['log prefix "[ipv4-NAM-smoketest-default-D]"','smoketest default-action', 'drop'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_filter') + + def test_ipv4_advanced(self): + name = 'smoketest-adv' + name2 = 'smoketest-adv2' + interface = 'eth0' + + self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'default-log']) + + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '64']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '512']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '1024']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'dscp', '17']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'dscp', '52']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'group', '66']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'snapshot-length', '6666']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'queue-threshold','32000']) + + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'packet-length', '1-30000']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'packet-length-exclude', '60000-65535']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'dscp', '3-11']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'dscp-exclude', '21-25']) + + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'source', 'address', '198.51.100.1-198.51.100.50']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'mark', '1010']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'jump-target', name]) + + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'mark', '!98765']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'action', 'queue']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'queue', '3']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'protocol', 'udp']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'action', 'queue']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue-options', 'fanout']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue-options', 'bypass']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue', '0-15']) + + self.cli_commit() + + nftables_search = [ + ['chain VYOS_FORWARD_filter'], + ['type filter hook forward priority filter; policy accept;'], + ['ip saddr 198.51.100.1-198.51.100.50', 'meta mark 0x000003f2', f'jump NAME_{name}'], + ['FWD-filter default-action drop', 'drop'], + ['chain VYOS_INPUT_filter'], + ['type filter hook input priority filter; policy accept;'], + ['meta mark != 0x000181cd', 'meta l4proto tcp','queue to 3'], + ['meta l4proto udp','queue flags bypass,fanout to 0-15'], + ['INP-filter default-action accept', 'accept'], + [f'chain NAME_{name}'], + ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', f'log prefix "[ipv4-NAM-{name}-6-A]" log group 66 snaplen 6666 queue-threshold 32000', 'accept'], + ['ip length 1-30000', 'ip length != 60000-65535', 'ip dscp 0x03-0x0b', 'ip dscp != 0x15-0x19', 'accept'], + [f'log prefix "[ipv4-NAM-{name}-default-D]"', 'drop'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_filter') + + def test_ipv4_synproxy(self): + tcp_mss = '1460' + tcp_wscale = '7' + dport = '22' + + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'destination', 'port', dport]) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'synproxy', 'tcp', 'mss', tcp_mss]) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'synproxy', 'tcp', 'window-scale', tcp_wscale]) + + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'action', 'synproxy']) + + self.cli_commit() + + nftables_search = [ + [f'tcp dport {dport} ct state invalid,untracked', f'synproxy mss {tcp_mss} wscale {tcp_wscale} timestamp sack-perm'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_filter') + + + def test_ipv4_mask(self): + name = 'smoketest-mask' + interface = 'eth0' + + self.cli_set(['firewall', 'group', 'address-group', 'mask_group', 'address', '1.1.1.1']) + + self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'default-log']) + + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address', '0.0.1.2']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address-mask', '0.0.255.255']) + + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'source', 'address', '!0.0.3.4']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'source', 'address-mask', '0.0.255.255']) + + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'source', 'group', 'address-group', 'mask_group']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'source', 'address-mask', '0.0.255.255']) + + self.cli_commit() + + nftables_search = [ + [f'daddr & 0.0.255.255 == 0.0.1.2'], + [f'saddr & 0.0.255.255 != 0.0.3.4'], + [f'saddr & 0.0.255.255 == @A_mask_group'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_filter') + + def test_ipv4_dynamic_groups(self): + group01 = 'knock01' + group02 = 'allowed' + + self.cli_set(['firewall', 'group', 'dynamic-group', 'address-group', group01]) + self.cli_set(['firewall', 'group', 'dynamic-group', 'address-group', group02]) + + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'destination', 'port', '5151']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'add-address-to-group', 'source-address', 'address-group', group01]) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'add-address-to-group', 'source-address', 'timeout', '30s']) + + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'destination', 'port', '7272']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'source', 'group', 'dynamic-address-group', group01]) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'add-address-to-group', 'source-address', 'address-group', group02]) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'add-address-to-group', 'source-address', 'timeout', '5m']) + + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '30', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '30', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '30', 'destination', 'port', '22']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '30', 'source', 'group', 'dynamic-address-group', group02]) + + self.cli_commit() + + nftables_search = [ + [f'DA_{group01}'], + [f'DA_{group02}'], + ['type ipv4_addr'], + ['flags dynamic,timeout'], + ['chain VYOS_INPUT_filter {'], + ['type filter hook input priority filter', 'policy accept'], + ['tcp dport 5151', f'update @DA_{group01}', '{ ip saddr timeout 30s }', 'drop'], + ['tcp dport 7272', f'ip saddr @DA_{group01}', f'update @DA_{group02}', '{ ip saddr timeout 5m }', 'drop'], + ['tcp dport 22', f'ip saddr @DA_{group02}', 'accept'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_filter') + + def test_ipv6_basic_rules(self): + name = 'v6-smoketest' + interface = 'eth0' + + self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept']) + self.cli_set(['firewall', 'global-options', 'state-policy', 'related', 'action', 'accept']) + self.cli_set(['firewall', 'global-options', 'state-policy', 'invalid', 'action', 'drop']) + + self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'default-log']) + + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'source', 'address', '2002::1-2002::10']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address', '2002::1:1']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'log']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'log-options', 'level', 'crit']) + + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'default-action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'default-log']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'action', 'reject']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'protocol', 'tcp_udp']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'destination', 'port', '8888']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'inbound-interface', 'name', interface]) + + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'protocol', 'udp']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'source', 'address', '2002::1:2']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'inbound-interface', 'name', interface]) + + self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'default-log']) + self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'action', 'return']) + self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'protocol', 'gre']) + self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'outbound-interface', 'name', interface]) + + self.cli_set(['firewall', 'ipv6', 'output', 'raw', 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'output', 'raw', 'rule', '1', 'action', 'notrack']) + self.cli_set(['firewall', 'ipv6', 'output', 'raw', 'rule', '1', 'protocol', 'udp']) + + self.cli_set(['firewall', 'ipv6', 'prerouting', 'raw', 'rule', '1', 'action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'prerouting', 'raw', 'rule', '1', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv6', 'prerouting', 'raw', 'rule', '1', 'destination', 'port', '23']) + + self.cli_commit() + + nftables_search = [ + ['chain VYOS_IPV6_FORWARD_filter'], + ['type filter hook forward priority filter; policy accept;'], + ['meta l4proto { tcp, udp }', 'th dport 8888', f'iifname "{interface}"', 'reject'], + ['log prefix "[ipv6-FWD-filter-default-A]"','FWD-filter default-action accept', 'accept'], + ['chain VYOS_IPV6_INPUT_filter'], + ['type filter hook input priority filter; policy accept;'], + ['meta l4proto udp', 'ip6 saddr 2002::1:2', f'iifname "{interface}"', 'accept'], + ['INP-filter default-action accept', 'accept'], + ['chain VYOS_IPV6_OUTPUT_filter'], + ['type filter hook output priority filter; policy accept;'], + ['meta l4proto gre', f'oifname "{interface}"', 'return'], + ['log prefix "[ipv6-OUT-filter-default-D]"','OUT-filter default-action drop', 'drop'], + ['chain VYOS_IPV6_OUTPUT_raw'], + ['type filter hook output priority raw; policy accept;'], + ['udp', 'notrack'], + ['OUT-raw default-action drop', 'drop'], + ['chain VYOS_IPV6_PREROUTING_raw'], + ['type filter hook prerouting priority raw; policy accept;'], + ['tcp dport 23', 'drop'], + ['PRE-raw default-action accept', 'accept'], + [f'chain NAME6_{name}'], + ['saddr 2002::1-2002::10', 'daddr 2002::1:1', 'log prefix "[ipv6-NAM-v6-smoketest-1-A]" log level crit', 'accept'], + [f'"NAM-{name} default-action drop"', f'log prefix "[ipv6-NAM-{name}-default-D]"', 'drop'], + ['jump VYOS_STATE_POLICY6'], + ['chain VYOS_STATE_POLICY6'], + ['ct state established', 'accept'], + ['ct state invalid', 'drop'], + ['ct state related', 'accept'] + ] + + self.verify_nftables(nftables_search, 'ip6 vyos_filter') + + def test_ipv6_advanced(self): + name = 'v6-smoke-adv' + + self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'default-log']) + + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '65']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '513']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '1025']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'dscp', '18']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'dscp', '53']) + + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'packet-length', '1-1999']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'packet-length-exclude', '60000-65535']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'dscp', '4-14']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'dscp-exclude', '31-35']) + + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'default-action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'source', 'address', '2001:db8::/64']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'mark', '!6655-7766']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'jump-target', name]) + + self.cli_commit() + + nftables_search = [ + ['chain VYOS_IPV6_FORWARD_filter'], + ['type filter hook forward priority filter; policy accept;'], + ['ip6 length 1-1999', 'ip6 length != 60000-65535', 'ip6 dscp 0x04-0x0e', 'ip6 dscp != 0x1f-0x23', 'accept'], + ['chain VYOS_IPV6_INPUT_filter'], + ['type filter hook input priority filter; policy accept;'], + ['ip6 saddr 2001:db8::/64', 'meta mark != 0x000019ff-0x00001e56', f'jump NAME6_{name}'], + [f'chain NAME6_{name}'], + ['ip6 length { 65, 513, 1025 }', 'ip6 dscp { af21, 0x35 }', 'accept'], + [f'log prefix "[ipv6-NAM-{name}-default-D]"', 'drop'] + ] + + self.verify_nftables(nftables_search, 'ip6 vyos_filter') + + def test_ipv6_mask(self): + name = 'v6-smoketest-mask' + interface = 'eth0' + + self.cli_set(['firewall', 'group', 'ipv6-address-group', 'mask_group', 'address', '::beef']) + + self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'default-log']) + + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address', '::1111:2222:3333:4444']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address-mask', '::ffff:ffff:ffff:ffff']) + + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'source', 'address', '!::aaaa:bbbb:cccc:dddd']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'source', 'address-mask', '::ffff:ffff:ffff:ffff']) + + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'source', 'group', 'address-group', 'mask_group']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'source', 'address-mask', '::ffff:ffff:ffff:ffff']) + + self.cli_commit() + + nftables_search = [ + ['daddr & ::ffff:ffff:ffff:ffff == ::1111:2222:3333:4444'], + ['saddr & ::ffff:ffff:ffff:ffff != ::aaaa:bbbb:cccc:dddd'], + ['saddr & ::ffff:ffff:ffff:ffff == @A6_mask_group'] + ] + + self.verify_nftables(nftables_search, 'ip6 vyos_filter') + + def test_ipv6_dynamic_groups(self): + group01 = 'knock01' + group02 = 'allowed' + + self.cli_set(['firewall', 'group', 'dynamic-group', 'ipv6-address-group', group01]) + self.cli_set(['firewall', 'group', 'dynamic-group', 'ipv6-address-group', group02]) + + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'destination', 'port', '5151']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'add-address-to-group', 'source-address', 'address-group', group01]) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'add-address-to-group', 'source-address', 'timeout', '30s']) + + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'destination', 'port', '7272']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'source', 'group', 'dynamic-address-group', group01]) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'add-address-to-group', 'source-address', 'address-group', group02]) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'add-address-to-group', 'source-address', 'timeout', '5m']) + + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '30', 'action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '30', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '30', 'destination', 'port', '22']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '30', 'source', 'group', 'dynamic-address-group', group02]) + + self.cli_commit() + + nftables_search = [ + [f'DA6_{group01}'], + [f'DA6_{group02}'], + ['type ipv6_addr'], + ['flags dynamic,timeout'], + ['chain VYOS_IPV6_INPUT_filter {'], + ['type filter hook input priority filter', 'policy accept'], + ['tcp dport 5151', f'update @DA6_{group01}', '{ ip6 saddr timeout 30s }', 'drop'], + ['tcp dport 7272', f'ip6 saddr @DA6_{group01}', f'update @DA6_{group02}', '{ ip6 saddr timeout 5m }', 'drop'], + ['tcp dport 22', f'ip6 saddr @DA6_{group02}', 'accept'] + ] + + self.verify_nftables(nftables_search, 'ip6 vyos_filter') + + def test_ipv4_global_state(self): + self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept']) + self.cli_set(['firewall', 'global-options', 'state-policy', 'related', 'action', 'accept']) + self.cli_set(['firewall', 'global-options', 'state-policy', 'invalid', 'action', 'drop']) + + self.cli_commit() + + nftables_search = [ + ['jump VYOS_STATE_POLICY'], + ['chain VYOS_STATE_POLICY'], + ['ct state established', 'accept'], + ['ct state invalid', 'drop'], + ['ct state related', 'accept'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_filter') + + # Check conntrack is enabled from state-policy + self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') + self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK') + + def test_ipv4_state_and_status_rules(self): + name = 'smoketest-state' + + self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'state', 'established']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'state', 'related']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'reject']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'state', 'invalid']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'state', 'new']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'connection-status', 'nat', 'destination']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'state', 'new']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'state', 'established']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'connection-status', 'nat', 'source']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '5', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '5', 'state', 'related']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '5', 'conntrack-helper', 'ftp']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '5', 'conntrack-helper', 'pptp']) + + self.cli_commit() + + nftables_search = [ + ['ct state { established, related }', 'accept'], + ['ct state invalid', 'reject'], + ['ct state new', 'ct status dnat', 'accept'], + ['ct state { established, new }', 'ct status snat', 'accept'], + ['ct state related', 'ct helper { "ftp", "pptp" }', 'accept'], + ['drop', f'comment "NAM-{name} default-action drop"'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_filter') + + # Check conntrack + self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') + self.verify_nftables_chain([['return']], 'ip6 vyos_conntrack', 'FW_CONNTRACK') + + def test_bridge_firewall(self): + name = 'smoketest' + interface_in = 'eth0' + mac_address = '00:53:00:00:00:01' + vlan_id = '12' + vlan_prior = '3' + + # Check bridge-nf-call-iptables default value: 0 + self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-iptables'), '0') + self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-ip6tables'), '0') + + self.cli_set(['firewall', 'group', 'ipv6-address-group', 'AGV6', 'address', '2001:db1::1']) + self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept']) + self.cli_set(['firewall', 'global-options', 'apply-to-bridged-traffic', 'ipv4']) + self.cli_set(['firewall', 'global-options', 'apply-to-bridged-traffic', 'invalid-connections']) + + self.cli_set(['firewall', 'bridge', 'name', name, 'default-action', 'accept']) + self.cli_set(['firewall', 'bridge', 'name', name, 'default-log']) + self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'source', 'mac-address', mac_address]) + self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'inbound-interface', 'name', interface_in]) + self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'log']) + self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'log-options', 'level', 'crit']) + + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'default-action', 'drop']) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'default-log']) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'vlan', 'id', vlan_id]) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'vlan', 'ethernet-type', 'ipv4']) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'action', 'jump']) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'jump-target', name]) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'vlan', 'priority', vlan_prior]) + + self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'inbound-interface', 'name', interface_in]) + self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'source', 'address', '192.0.2.2']) + self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'state', 'new']) + + self.cli_set(['firewall', 'bridge', 'prerouting', 'filter', 'rule', '1', 'action', 'notrack']) + self.cli_set(['firewall', 'bridge', 'prerouting', 'filter', 'rule', '1', 'destination', 'group', 'ipv6-address-group', 'AGV6']) + self.cli_set(['firewall', 'bridge', 'prerouting', 'filter', 'rule', '2', 'ethernet-type', 'arp']) + self.cli_set(['firewall', 'bridge', 'prerouting', 'filter', 'rule', '2', 'action', 'accept']) + + + self.cli_commit() + + nftables_search = [ + ['set A6_AGV6'], + ['type ipv6_addr'], + ['elements', '2001:db1::1'], + ['chain VYOS_FORWARD_filter'], + ['type filter hook forward priority filter; policy accept;'], + ['jump VYOS_STATE_POLICY'], + [f'vlan id {vlan_id}', 'vlan type ip', 'accept'], + [f'vlan pcp {vlan_prior}', f'jump NAME_{name}'], + ['log prefix "[bri-FWD-filter-default-D]"', 'drop', 'FWD-filter default-action drop'], + [f'chain NAME_{name}'], + [f'ether saddr {mac_address}', f'iifname "{interface_in}"', f'log prefix "[bri-NAM-{name}-1-A]" log level crit', 'accept'], + ['accept', f'{name} default-action accept'], + ['chain VYOS_INPUT_filter'], + ['type filter hook input priority filter; policy accept;'], + ['ct state new', 'ip saddr 192.0.2.2', f'iifname "{interface_in}"', 'accept'], + ['chain VYOS_OUTPUT_filter'], + ['type filter hook output priority filter; policy accept;'], + ['ct state invalid', 'udp sport 67', 'udp dport 68', 'accept'], + ['ct state invalid', 'ether type arp', 'accept'], + ['chain VYOS_PREROUTING_filter'], + ['type filter hook prerouting priority filter; policy accept;'], + ['ip6 daddr @A6_AGV6', 'notrack'], + ['ether type arp', 'accept'] + ] + + self.verify_nftables(nftables_search, 'bridge vyos_filter') + ## Check bridge-nf-call-iptables is set to 1, and for ipv6 remains on default 0 + self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-iptables'), '1') + self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-ip6tables'), '0') + + def test_source_validation(self): + # Strict + self.cli_set(['firewall', 'global-options', 'source-validation', 'strict']) + self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'strict']) + self.cli_commit() + + nftables_strict_search = [ + ['fib saddr . iif oif 0', 'drop'] + ] + + self.verify_nftables_chain(nftables_strict_search, 'ip raw', 'vyos_global_rpfilter') + self.verify_nftables_chain(nftables_strict_search, 'ip6 raw', 'vyos_global_rpfilter') + + # Loose + self.cli_set(['firewall', 'global-options', 'source-validation', 'loose']) + self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'loose']) + self.cli_commit() + + nftables_loose_search = [ + ['fib saddr oif 0', 'drop'] + ] + + self.verify_nftables_chain(nftables_loose_search, 'ip raw', 'vyos_global_rpfilter') + self.verify_nftables_chain(nftables_loose_search, 'ip6 raw', 'vyos_global_rpfilter') + + def test_sysfs(self): + for name, conf in sysfs_config.items(): + paths = glob(conf['sysfs']) + for path in paths: + with open(path, 'r') as f: + self.assertEqual(f.read().strip(), conf['default'], msg=path) + + self.cli_set(['firewall', 'global-options', name.replace("_", "-"), conf['test_value']]) + + self.cli_commit() + + for name, conf in sysfs_config.items(): + paths = glob(conf['sysfs']) + for path in paths: + with open(path, 'r') as f: + self.assertNotEqual(f.read().strip(), conf['default'], msg=path) + + def test_timeout_sysctl(self): + timeout_config = { + 'net.netfilter.nf_conntrack_icmp_timeout' :{ + 'cli' : ['global-options', 'timeout', 'icmp'], + 'test_value' : '180', + 'default_value' : '30', + }, + 'net.netfilter.nf_conntrack_generic_timeout' :{ + 'cli' : ['global-options', 'timeout', 'other'], + 'test_value' : '1200', + 'default_value' : '600', + }, + 'net.netfilter.nf_conntrack_tcp_timeout_close_wait' :{ + 'cli' : ['global-options', 'timeout', 'tcp', 'close-wait'], + 'test_value' : '30', + 'default_value' : '60', + }, + 'net.netfilter.nf_conntrack_tcp_timeout_close' :{ + 'cli' : ['global-options', 'timeout', 'tcp', 'close'], + 'test_value' : '20', + 'default_value' : '10', + }, + 'net.netfilter.nf_conntrack_tcp_timeout_established' :{ + 'cli' : ['global-options', 'timeout', 'tcp', 'established'], + 'test_value' : '1000', + 'default_value' : '432000', + }, + 'net.netfilter.nf_conntrack_tcp_timeout_fin_wait' :{ + 'cli' : ['global-options', 'timeout', 'tcp', 'fin-wait'], + 'test_value' : '240', + 'default_value' : '120', + }, + 'net.netfilter.nf_conntrack_tcp_timeout_last_ack' :{ + 'cli' : ['global-options', 'timeout', 'tcp', 'last-ack'], + 'test_value' : '300', + 'default_value' : '30', + }, + 'net.netfilter.nf_conntrack_tcp_timeout_syn_recv' :{ + 'cli' : ['global-options', 'timeout', 'tcp', 'syn-recv'], + 'test_value' : '100', + 'default_value' : '60', + }, + 'net.netfilter.nf_conntrack_tcp_timeout_syn_sent' :{ + 'cli' : ['global-options', 'timeout', 'tcp', 'syn-sent'], + 'test_value' : '300', + 'default_value' : '120', + }, + 'net.netfilter.nf_conntrack_tcp_timeout_time_wait' :{ + 'cli' : ['global-options', 'timeout', 'tcp', 'time-wait'], + 'test_value' : '303', + 'default_value' : '120', + }, + 'net.netfilter.nf_conntrack_udp_timeout' :{ + 'cli' : ['global-options', 'timeout', 'udp', 'other'], + 'test_value' : '90', + 'default_value' : '30', + }, + 'net.netfilter.nf_conntrack_udp_timeout_stream' :{ + 'cli' : ['global-options', 'timeout', 'udp', 'stream'], + 'test_value' : '200', + 'default_value' : '180', + }, + } + + for parameter, parameter_config in timeout_config.items(): + self.cli_set(['firewall'] + parameter_config['cli'] + [parameter_config['test_value']]) + + # commit changes + self.cli_commit() + + # validate configuration + for parameter, parameter_config in timeout_config.items(): + tmp = parameter_config['test_value'] + self.assertEqual(get_sysctl(f'{parameter}'), tmp) + + # delete all configuration options and revert back to defaults + self.cli_delete(['firewall', 'global-options', 'timeout']) + self.cli_commit() + + # validate configuration + for parameter, parameter_config in timeout_config.items(): + self.assertEqual(get_sysctl(f'{parameter}'), parameter_config['default_value']) + +### Zone + def test_zone_basic(self): + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'name', 'smoketestv6', 'default-action', 'drop']) + self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'interface', 'eth0']) + self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'from', 'smoketest-local', 'firewall', 'name', 'smoketest']) + self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'intra-zone-filtering', 'firewall', 'ipv6-name', 'smoketestv6']) + self.cli_set(['firewall', 'zone', 'smoketest-local', 'local-zone']) + self.cli_set(['firewall', 'zone', 'smoketest-local', 'from', 'smoketest-eth0', 'firewall', 'name', 'smoketest']) + self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept']) + self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'log']) + self.cli_set(['firewall', 'global-options', 'state-policy', 'related', 'action', 'accept']) + self.cli_set(['firewall', 'global-options', 'state-policy', 'invalid', 'action', 'drop']) + + self.cli_commit() + + nftables_search = [ + ['chain VYOS_ZONE_FORWARD'], + ['type filter hook forward priority filter + 1'], + ['chain VYOS_ZONE_OUTPUT'], + ['type filter hook output priority filter + 1'], + ['chain VYOS_ZONE_LOCAL'], + ['type filter hook input priority filter + 1'], + ['chain VZONE_smoketest-eth0'], + ['chain VZONE_smoketest-local_IN'], + ['chain VZONE_smoketest-local_OUT'], + ['oifname "eth0"', 'jump VZONE_smoketest-eth0'], + ['jump VZONE_smoketest-local_IN'], + ['jump VZONE_smoketest-local_OUT'], + ['iifname "eth0"', 'jump NAME_smoketest'], + ['oifname "eth0"', 'jump NAME_smoketest'], + ['jump VYOS_STATE_POLICY'], + ['chain VYOS_STATE_POLICY'], + ['ct state established', 'log prefix "[STATE-POLICY-EST-A]"', 'accept'], + ['ct state invalid', 'drop'], + ['ct state related', 'accept'] + ] + + nftables_search_v6 = [ + ['chain VYOS_ZONE_FORWARD'], + ['type filter hook forward priority filter + 1'], + ['chain VYOS_ZONE_OUTPUT'], + ['type filter hook output priority filter + 1'], + ['chain VYOS_ZONE_LOCAL'], + ['type filter hook input priority filter + 1'], + ['chain VZONE_smoketest-eth0'], + ['chain VZONE_smoketest-local_IN'], + ['chain VZONE_smoketest-local_OUT'], + ['oifname "eth0"', 'jump VZONE_smoketest-eth0'], + ['jump VZONE_smoketest-local_IN'], + ['jump VZONE_smoketest-local_OUT'], + ['iifname "eth0"', 'jump NAME6_smoketestv6'], + ['jump VYOS_STATE_POLICY6'], + ['chain VYOS_STATE_POLICY6'], + ['ct state established', 'log prefix "[STATE-POLICY-EST-A]"', 'accept'], + ['ct state invalid', 'drop'], + ['ct state related', 'accept'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_filter') + self.verify_nftables(nftables_search_v6, 'ip6 vyos_filter') + + def test_flow_offload(self): + self.cli_set(['interfaces', 'ethernet', 'eth0', 'vif', '10']) + self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0.10']) + self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'hardware']) + + # QEMU virtual NIC does not support hw-tc-offload + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'software']) + + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'offload']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'offload-target', 'smoketest']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'state', 'established']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'state', 'related']) + + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'action', 'offload']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'offload-target', 'smoketest']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'state', 'established']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'state', 'related']) + + self.cli_commit() + + nftables_search = [ + ['flowtable VYOS_FLOWTABLE_smoketest'], + ['hook ingress priority filter'], + ['devices = { eth0.10 }'], + ['ct state { established, related }', 'meta l4proto { tcp, udp }', 'flow add @VYOS_FLOWTABLE_smoketest'], + ] + + self.verify_nftables(nftables_search, 'ip vyos_filter') + self.verify_nftables(nftables_search, 'ip6 vyos_filter') + + # Check conntrack + self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') + self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK') + + def test_zone_flow_offload(self): + self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0']) + self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'hardware']) + + # QEMU virtual NIC does not support hw-tc-offload + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'software']) + + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'offload']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'offload-target', 'smoketest']) + + self.cli_set(['firewall', 'ipv6', 'name', 'smoketest', 'rule', '1', 'action', 'offload']) + self.cli_set(['firewall', 'ipv6', 'name', 'smoketest', 'rule', '1', 'offload-target', 'smoketest']) + + self.cli_commit() + + nftables_search = [ + ['chain NAME_smoketest'], + ['flow add @VYOS_FLOWTABLE_smoketest'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_filter') + + nftables_search = [ + ['chain NAME6_smoketest'], + ['flow add @VYOS_FLOWTABLE_smoketest'] + ] + + self.verify_nftables(nftables_search, 'ip6 vyos_filter') + + # Check conntrack + self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') + self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK') + + def test_ipsec_metadata_match(self): + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in4', 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in4', 'rule', '1', 'ipsec', 'match-ipsec-in']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in4', 'rule', '2', 'action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in4', 'rule', '2', 'ipsec', 'match-none-in']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-out4', 'rule', '1', 'action', 'continue']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-out4', 'rule', '1', 'ipsec', 'match-ipsec-out']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-out4', 'rule', '2', 'action', 'reject']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-out4', 'rule', '2', 'ipsec', 'match-none-out']) + self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-in6', 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-in6', 'rule', '1', 'ipsec', 'match-ipsec-in']) + self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-in6', 'rule', '2', 'action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-in6', 'rule', '2', 'ipsec', 'match-none-in']) + self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-out6', 'rule', '1', 'action', 'continue']) + self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-out6', 'rule', '1', 'ipsec', 'match-ipsec-out']) + self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-out6', 'rule', '2', 'action', 'reject']) + self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-out6', 'rule', '2', 'ipsec', 'match-none-out']) + + self.cli_commit() + + nftables_search = [ + ['meta ipsec exists', 'accept comment'], + ['meta ipsec missing', 'drop comment'], + ['rt ipsec exists', 'continue comment'], + ['rt ipsec missing', 'reject comment'], + ] + + self.verify_nftables(nftables_search, 'ip vyos_filter') + self.verify_nftables(nftables_search, 'ip6 vyos_filter') + + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-in4']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-in4']) + self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'jump-target', 'smoketest-ipsec-in4']) + + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-out4']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-out4']) + + # All valid directional usage of ipsec matches + self.cli_commit() + + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in-indirect', 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in-indirect', 'rule', '1', 'jump-target', 'smoketest-ipsec-in4']) + + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-in-indirect']) + + # nft does not support ANY usage of 'meta ipsec' under an output hook, it will fail to load cfg + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_cyclic_jump_validation(self): + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-1', 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-1', 'rule', '1', 'jump-target', 'smoketest-cycle-2']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-2', 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-2', 'rule', '1', 'jump-target', 'smoketest-cycle-3']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-3', 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-3', 'rule', '1', 'log']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '1', 'jump-target', 'smoketest-cycle-1']) + + # Multi-level jumps are unwise but allowed + self.cli_commit() + + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-3', 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-3', 'rule', '1', 'jump-target', 'smoketest-cycle-1']) + + # nft will fail to load cyclic jumps in any form, whether the rule is reachable or not. + # It should be caught by conf validation. + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_gre_match(self): + name = 'smoketest-gre' + + self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'return']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'protocol', 'gre']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'gre', 'flags', 'key']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'gre', 'flags', 'checksum', 'unset']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'gre', 'key', '1234']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'log']) + + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '2', 'action', 'continue']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '2', 'protocol', 'gre']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '2', 'gre', 'inner-proto', '0x6558']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '2', 'log']) + + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'protocol', 'gre']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'gre', 'flags', 'checksum']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'gre', 'key', '4321']) + + self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '4', 'action', 'reject']) + self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '4', 'protocol', 'gre']) + self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '4', 'gre', 'version', 'pptp']) + + self.cli_commit() + + nftables_search_v4 = [ + ['gre protocol 0x6558', 'continue comment'], + ['gre flags & 5 == 4 @th,32,32 0x4d2', 'accept comment'], + ] + + nftables_search_v6 = [ + ['gre flags & 5 == 5 @th,64,32 0x10e1', 'drop comment'], + ['gre version 1', 'reject comment'], + ] + + self.verify_nftables(nftables_search_v4, 'ip vyos_filter') + self.verify_nftables(nftables_search_v6, 'ip6 vyos_filter') + + # GRE match will only work with protocol GRE + self.cli_delete(['firewall', 'ipv4', 'name', name, 'rule', '1', 'protocol', 'gre']) + + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_discard() + + # GREv1 (PPTP) does not include a key field, match not available + self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '4', 'gre', 'flags', 'checksum', 'unset']) + self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '4', 'gre', 'key', '1234']) + + with self.assertRaises(ConfigSessionError): + self.cli_commit() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_high-availability_virtual-server.py b/smoketest/scripts/cli/test_high-availability_virtual-server.py new file mode 100644 index 0000000..2dbf4a5 --- /dev/null +++ b/smoketest/scripts/cli/test_high-availability_virtual-server.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.ifconfig.vrrp import VRRP +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +PROCESS_NAME = 'keepalived' +KEEPALIVED_CONF = VRRP.location['config'] +base_path = ['high-availability'] +vrrp_interface = 'eth1' + +class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(['interfaces', 'ethernet', vrrp_interface, 'address']) + self.cli_delete(base_path) + self.cli_commit() + + # Process must be terminated after deleting the config + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_ha_virtual_server(self): + algo = 'least-connection' + delay = '10' + method = 'nat' + persistence_timeout = '600' + vs = 'serv-one' + vip = '203.0.113.111' + vport = '2222' + rservers = ['192.0.2.21', '192.0.2.22', '192.0.2.23'] + rport = '22' + proto = 'tcp' + connection_timeout = '30' + + vserver_base = base_path + ['virtual-server'] + + self.cli_set(vserver_base + [vs, 'address', vip]) + self.cli_set(vserver_base + [vs, 'algorithm', algo]) + self.cli_set(vserver_base + [vs, 'delay-loop', delay]) + self.cli_set(vserver_base + [vs, 'forward-method', method]) + self.cli_set(vserver_base + [vs, 'persistence-timeout', persistence_timeout]) + self.cli_set(vserver_base + [vs, 'port', vport]) + self.cli_set(vserver_base + [vs, 'protocol', proto]) + for rs in rservers: + self.cli_set(vserver_base + [vs, 'real-server', rs, 'connection-timeout', connection_timeout]) + self.cli_set(vserver_base + [vs, 'real-server', rs, 'port', rport]) + + # commit changes + self.cli_commit() + + config = read_file(KEEPALIVED_CONF) + + self.assertIn(f'virtual_server {vip} {vport}', config) + self.assertIn(f'delay_loop {delay}', config) + self.assertIn(f'lb_algo lc', config) + self.assertIn(f'lb_kind {method.upper()}', config) + self.assertIn(f'persistence_timeout {persistence_timeout}', config) + self.assertIn(f'protocol {proto.upper()}', config) + for rs in rservers: + self.assertIn(f'real_server {rs} {rport}', config) + self.assertIn(f'{proto.upper()}_CHECK', config) + self.assertIn(f'connect_timeout {connection_timeout}', config) + + def test_02_ha_virtual_server_and_vrrp(self): + algo = 'least-connection' + delay = '15' + method = 'nat' + persistence_timeout = '300' + vs = 'serv-two' + vip = '203.0.113.222' + vport = '22322' + rservers = ['192.0.2.11', '192.0.2.12'] + rport = '222' + proto = 'tcp' + connection_timeout = '23' + group = 'VyOS' + vrid = '99' + + vrrp_base = base_path + ['vrrp', 'group'] + vserver_base = base_path + ['virtual-server'] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'address', '203.0.113.10/24']) + + # VRRP config + self.cli_set(vrrp_base + [group, 'description', group]) + self.cli_set(vrrp_base + [group, 'interface', vrrp_interface]) + self.cli_set(vrrp_base + [group, 'address', vip + '/24']) + self.cli_set(vrrp_base + [group, 'vrid', vrid]) + + # Virtual-server config + self.cli_set(vserver_base + [vs, 'address', vip]) + self.cli_set(vserver_base + [vs, 'algorithm', algo]) + self.cli_set(vserver_base + [vs, 'delay-loop', delay]) + self.cli_set(vserver_base + [vs, 'forward-method', method]) + self.cli_set(vserver_base + [vs, 'persistence-timeout', persistence_timeout]) + self.cli_set(vserver_base + [vs, 'port', vport]) + self.cli_set(vserver_base + [vs, 'protocol', proto]) + for rs in rservers: + self.cli_set(vserver_base + [vs, 'real-server', rs, 'connection-timeout', connection_timeout]) + self.cli_set(vserver_base + [vs, 'real-server', rs, 'port', rport]) + + # commit changes + self.cli_commit() + + config = read_file(KEEPALIVED_CONF) + + # Keepalived vrrp + self.assertIn(f'# {group}', config) + self.assertIn(f'interface {vrrp_interface}', config) + self.assertIn(f'virtual_router_id {vrid}', config) + self.assertIn(f'priority 100', config) # default value + self.assertIn(f'advert_int 1', config) # default value + self.assertIn(f'preempt_delay 0', config) # default value + + # Keepalived virtual-server + self.assertIn(f'virtual_server {vip} {vport}', config) + self.assertIn(f'delay_loop {delay}', config) + self.assertIn(f'lb_algo lc', config) + self.assertIn(f'lb_kind {method.upper()}', config) + self.assertIn(f'persistence_timeout {persistence_timeout}', config) + self.assertIn(f'protocol {proto.upper()}', config) + for rs in rservers: + self.assertIn(f'real_server {rs} {rport}', config) + self.assertIn(f'{proto.upper()}_CHECK', config) + self.assertIn(f'connect_timeout {connection_timeout}', config) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_high-availability_vrrp.py b/smoketest/scripts/cli/test_high-availability_vrrp.py new file mode 100644 index 0000000..aa9fa43 --- /dev/null +++ b/smoketest/scripts/cli/test_high-availability_vrrp.py @@ -0,0 +1,320 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig.vrrp import VRRP +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.template import inc_ip + +PROCESS_NAME = 'keepalived' +KEEPALIVED_CONF = VRRP.location['config'] +base_path = ['high-availability'] + +vrrp_interface = 'eth1' +groups = ['VLAN77', 'VLAN78', 'VLAN201'] + +def getConfig(string, end='}'): + command = f'cat {KEEPALIVED_CONF} | sed -n "/^{string}/,/^{end}/p"' + out = cmd(command) + return out + +class TestVRRP(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + for group in groups: + vlan_id = group.lstrip('VLAN') + self.cli_delete(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id]) + + self.cli_delete(base_path) + self.cli_commit() + + # Process must be terminated after deleting the config + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_default_values(self): + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + group_base = base_path + ['vrrp', 'group', group] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) + + self.cli_set(group_base + ['description', group]) + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['vrid', vlan_id]) + + # commit changes + self.cli_commit() + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + + config = getConfig(f'vrrp_instance {group}') + + self.assertIn(f'# {group}', config) + self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) + self.assertIn(f'virtual_router_id {vlan_id}', config) + self.assertIn(f'priority 100', config) # default value + self.assertIn(f'advert_int 1', config) # default value + self.assertIn(f'preempt_delay 0', config) # default value + self.assertNotIn(f'use_vmac', config) + self.assertIn(f' {vip}', config) + + def test_02_simple_options(self): + advertise_interval = '77' + priority = '123' + preempt_delay = '400' + startup_delay = '120' + garp_master_delay = '2' + garp_master_repeat = '3' + garp_master_refresh = '4' + garp_master_refresh_repeat = '5' + garp_interval = '1.5' + group_garp_master_delay = '12' + group_garp_master_repeat = '13' + group_garp_master_refresh = '14' + vrrp_version = '3' + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + group_base = base_path + ['vrrp', 'group', group] + global_param_base = base_path + ['vrrp', 'global-parameters'] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) + + self.cli_set(group_base + ['description', group]) + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['vrid', vlan_id]) + + self.cli_set(group_base + ['advertise-interval', advertise_interval]) + self.cli_set(group_base + ['priority', priority]) + self.cli_set(group_base + ['preempt-delay', preempt_delay]) + + self.cli_set(group_base + ['rfc3768-compatibility']) + + # Authentication + self.cli_set(group_base + ['authentication', 'type', 'plaintext-password']) + self.cli_set(group_base + ['authentication', 'password', f'{group}']) + + # GARP + self.cli_set(group_base + ['garp', 'master-delay', group_garp_master_delay]) + self.cli_set(group_base + ['garp', 'master-repeat', group_garp_master_repeat]) + self.cli_set(group_base + ['garp', 'master-refresh', group_garp_master_refresh]) + + # Global parameters + #config = getConfig(f'global_defs') + self.cli_set(global_param_base + ['startup-delay', f'{startup_delay}']) + self.cli_set(global_param_base + ['garp', 'interval', f'{garp_interval}']) + self.cli_set(global_param_base + ['garp', 'master-delay', f'{garp_master_delay}']) + self.cli_set(global_param_base + ['garp', 'master-repeat', f'{garp_master_repeat}']) + self.cli_set(global_param_base + ['garp', 'master-refresh', f'{garp_master_refresh}']) + self.cli_set(global_param_base + ['garp', 'master-refresh-repeat', f'{garp_master_refresh_repeat}']) + self.cli_set(global_param_base + ['version', vrrp_version]) + + # commit changes + self.cli_commit() + + # Check Global parameters + config = getConfig(f'global_defs') + self.assertIn(f'vrrp_startup_delay {startup_delay}', config) + self.assertIn(f'vrrp_garp_interval {garp_interval}', config) + self.assertIn(f'vrrp_garp_master_delay {garp_master_delay}', config) + self.assertIn(f'vrrp_garp_master_repeat {garp_master_repeat}', config) + self.assertIn(f'vrrp_garp_master_refresh {garp_master_refresh}', config) + self.assertIn(f'vrrp_garp_master_refresh_repeat {garp_master_refresh_repeat}', config) + self.assertIn(f'vrrp_version {vrrp_version}', config) + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + + config = getConfig(f'vrrp_instance {group}') + self.assertIn(f'# {group}', config) + self.assertIn(f'state BACKUP', config) + self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) + self.assertIn(f'virtual_router_id {vlan_id}', config) + self.assertIn(f'priority {priority}', config) + self.assertIn(f'advert_int {advertise_interval}', config) + self.assertIn(f'preempt_delay {preempt_delay}', config) + self.assertIn(f'use_vmac {vrrp_interface}.{vlan_id}v{vlan_id}', config) + self.assertIn(f' {vip}', config) + + # Authentication + self.assertIn(f'auth_pass "{group}"', config) + self.assertIn(f'auth_type PASS', config) + + #GARP + self.assertIn(f'garp_master_delay {group_garp_master_delay}', config) + self.assertIn(f'garp_master_refresh {group_garp_master_refresh}', config) + self.assertIn(f'garp_master_repeat {group_garp_master_repeat}', config) + + def test_03_sync_group(self): + sync_group = 'VyOS' + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + group_base = base_path + ['vrrp', 'group', group] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) + + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['vrid', vlan_id]) + + self.cli_set(base_path + ['vrrp', 'sync-group', sync_group, 'member', group]) + + # commit changes + self.cli_commit() + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + config = getConfig(f'vrrp_instance {group}') + + self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) + self.assertIn(f'virtual_router_id {vlan_id}', config) + self.assertNotIn(f'use_vmac', config) + self.assertIn(f' {vip}', config) + + config = getConfig(f'vrrp_sync_group {sync_group}') + self.assertIn(r'group {', config) + for group in groups: + self.assertIn(f'{group}', config) + + def test_04_exclude_vrrp_interface(self): + group = 'VyOS-WAN' + none_vrrp_interface = 'eth2' + vlan_id = '24' + vip = '100.64.24.1/24' + vip_dev = '192.0.2.2/24' + vrid = '150' + group_base = base_path + ['vrrp', 'group', group] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', '100.64.24.11/24']) + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['address', vip_dev, 'interface', none_vrrp_interface]) + self.cli_set(group_base + ['track', 'exclude-vrrp-interface']) + self.cli_set(group_base + ['track', 'interface', none_vrrp_interface]) + self.cli_set(group_base + ['vrid', vrid]) + + # commit changes + self.cli_commit() + + config = getConfig(f'vrrp_instance {group}') + + self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) + self.assertIn(f'virtual_router_id {vrid}', config) + self.assertIn(f'dont_track_primary', config) + self.assertIn(f' {vip}', config) + self.assertIn(f' {vip_dev} dev {none_vrrp_interface}', config) + self.assertIn(f'track_interface', config) + self.assertIn(f' {none_vrrp_interface}', config) + + def test_05_set_multiple_peer_address(self): + group = 'VyOS-WAN' + vlan_id = '24' + vip = '100.64.24.1/24' + peer_address_1 = '192.0.2.1' + peer_address_2 = '192.0.2.2' + vrid = '150' + group_base = base_path + ['vrrp', 'group', group] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', '100.64.24.11/24']) + self.cli_set(group_base + ['interface', vrrp_interface]) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['peer-address', peer_address_1]) + self.cli_set(group_base + ['peer-address', peer_address_2]) + self.cli_set(group_base + ['vrid', vrid]) + + # commit changes + self.cli_commit() + + config = getConfig(f'vrrp_instance {group}') + + self.assertIn(f'interface {vrrp_interface}', config) + self.assertIn(f'virtual_router_id {vrid}', config) + self.assertIn(f'unicast_peer', config) + self.assertIn(f' {peer_address_1}', config) + self.assertIn(f' {peer_address_2}', config) + + def test_check_health_script(self): + sync_group = 'VyOS' + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + group_base = base_path + ['vrrp', 'group', group] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) + + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['vrid', vlan_id]) + + self.cli_set(group_base + ['health-check', 'ping', '127.0.0.1']) + + # commit changes + self.cli_commit() + + for group in groups: + config = getConfig(f'vrrp_instance {group}') + self.assertIn(f'track_script', config) + + self.cli_set(base_path + ['vrrp', 'sync-group', sync_group, 'member', groups[0]]) + + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete(base_path + ['vrrp', 'group', groups[0], 'health-check']) + self.cli_commit() + + for group in groups[1:]: + config = getConfig(f'vrrp_instance {group}') + self.assertIn(f'track_script', config) + + config = getConfig(f'vrrp_instance {groups[0]}') + self.assertNotIn(f'track_script', config) + + config = getConfig(f'vrrp_sync_group {sync_group}') + self.assertNotIn(f'track_script', config) + + self.cli_set(base_path + ['vrrp', 'sync-group', sync_group, 'health-check', 'ping', '127.0.0.1']) + + # commit changes + self.cli_commit() + + config = getConfig(f'vrrp_instance {groups[0]}') + self.assertNotIn(f'track_script', config) + + config = getConfig(f'vrrp_sync_group {sync_group}') + self.assertIn(f'track_script', config) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py new file mode 100644 index 0000000..f436424 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_bonding.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from base_interfaces_test import BasicInterfaceTest + +from vyos.ifconfig import Section +from vyos.ifconfig.interface import Interface +from vyos.configsession import ConfigSessionError +from vyos.utils.network import get_interface_config +from vyos.utils.file import read_file + +class BondingInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'bonding'] + cls._mirror_interfaces = ['dum21354'] + cls._members = [] + + # we need to filter out VLAN interfaces identified by a dot (.) + # in their name - just in case! + if 'TEST_ETH' in os.environ: + cls._members = os.environ['TEST_ETH'].split() + else: + for tmp in Section.interfaces('ethernet', vlan=False): + cls._members.append(tmp) + + cls._options = {'bond0' : []} + for member in cls._members: + cls._options['bond0'].append(f'member interface {member}') + cls._interfaces = list(cls._options) + + # call base-classes classmethod + super(BondingInterfaceTest, cls).setUpClass() + + def test_add_single_ip_address(self): + super().test_add_single_ip_address() + + for interface in self._interfaces: + slaves = read_file(f'/sys/class/net/{interface}/bonding/slaves').split() + self.assertListEqual(slaves, self._members) + + def test_vif_8021q_interfaces(self): + super().test_vif_8021q_interfaces() + + for interface in self._interfaces: + slaves = read_file(f'/sys/class/net/{interface}/bonding/slaves').split() + self.assertListEqual(slaves, self._members) + + def test_bonding_remove_member(self): + # T2515: when removing a bond member the previously enslaved/member + # interface must be in its former admin-up/down state. Here we ensure + # that it is admin-up as it was admin-up before. + + # configure member interfaces + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + + self.cli_commit() + + # remove single bond member port + for interface in self._interfaces: + remove_member = self._members[0] + self.cli_delete(self._base_path + [interface, 'member', 'interface', remove_member]) + + self.cli_commit() + + # removed member port must be admin-up + for interface in self._interfaces: + remove_member = self._members[0] + state = Interface(remove_member).get_admin_state() + self.assertEqual('up', state) + + def test_bonding_min_links(self): + # configure member interfaces + min_links = len(self._interfaces) + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + + self.cli_set(self._base_path + [interface, 'min-links', str(min_links)]) + + self.cli_commit() + + # verify config + for interface in self._interfaces: + tmp = get_interface_config(interface) + + self.assertEqual(min_links, tmp['linkinfo']['info_data']['min_links']) + # check LACP default rate + self.assertEqual('slow', tmp['linkinfo']['info_data']['ad_lacp_rate']) + + def test_bonding_lacp_rate(self): + # configure member interfaces + lacp_rate = 'fast' + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + + self.cli_set(self._base_path + [interface, 'lacp-rate', lacp_rate]) + + self.cli_commit() + + # verify config + for interface in self._interfaces: + tmp = get_interface_config(interface) + + # check LACP minimum links (default value) + self.assertEqual(0, tmp['linkinfo']['info_data']['min_links']) + self.assertEqual(lacp_rate, tmp['linkinfo']['info_data']['ad_lacp_rate']) + + def test_bonding_hash_policy(self): + # Define available bonding hash policies + hash_policies = ['layer2', 'layer2+3', 'encap2+3', 'encap3+4'] + for hash_policy in hash_policies: + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + + self.cli_set(self._base_path + [interface, 'hash-policy', hash_policy]) + + self.cli_commit() + + # verify config + for interface in self._interfaces: + defined_policy = read_file(f'/sys/class/net/{interface}/bonding/xmit_hash_policy').split() + self.assertEqual(defined_policy[0], hash_policy) + + def test_bonding_mii_monitoring_interval(self): + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + + self.cli_commit() + + # verify default + for interface in self._interfaces: + tmp = read_file(f'/sys/class/net/{interface}/bonding/miimon').split() + self.assertIn('100', tmp) + + mii_mon = '250' + for interface in self._interfaces: + self.cli_set(self._base_path + [interface, 'mii-mon-interval', mii_mon]) + + self.cli_commit() + + # verify new CLI value + for interface in self._interfaces: + tmp = read_file(f'/sys/class/net/{interface}/bonding/miimon').split() + self.assertIn(mii_mon, tmp) + + def test_bonding_multi_use_member(self): + # Define available bonding hash policies + for interface in ['bond10', 'bond20']: + for member in self._members: + self.cli_set(self._base_path + [interface, 'member', 'interface', member]) + + # check validate() - can not use the same member interfaces multiple times + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete(self._base_path + ['bond20']) + + self.cli_commit() + + def test_bonding_source_interface(self): + # Re-use member interface that is already a source-interface + bond = 'bond99' + pppoe = 'pppoe98756' + member = next(iter(self._members)) + + self.cli_set(self._base_path + [bond, 'member', 'interface', member]) + self.cli_set(['interfaces', 'pppoe', pppoe, 'source-interface', member]) + + # check validate() - can not add interface to bond, it is the source-interface of ... + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete(['interfaces', 'pppoe', pppoe]) + self.cli_commit() + + # verify config + slaves = read_file(f'/sys/class/net/{bond}/bonding/slaves').split() + self.assertIn(member, slaves) + + def test_bonding_source_bridge_interface(self): + # Re-use member interface that is already a source-interface + bond = 'bond1097' + bridge = 'br6327' + member = next(iter(self._members)) + + self.cli_set(self._base_path + [bond, 'member', 'interface', member]) + self.cli_set(['interfaces', 'bridge', bridge, 'member', 'interface', member]) + + # check validate() - can not add interface to bond, it is a member of bridge ... + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete(['interfaces', 'bridge', bridge]) + self.cli_commit() + + # verify config + slaves = read_file(f'/sys/class/net/{bond}/bonding/slaves').split() + self.assertIn(member, slaves) + + def test_bonding_uniq_member_description(self): + ethernet_path = ['interfaces', 'ethernet'] + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + + self.cli_commit() + + # Add any changes on bonding members + # For example add description on separate ethX interfaces + for interface in self._interfaces: + for member in self._members: + self.cli_set(ethernet_path + [member, 'description', member + '_interface']) + + self.cli_commit() + + # verify config + for interface in self._interfaces: + slaves = read_file(f'/sys/class/net/{interface}/bonding/slaves').split() + for member in self._members: + self.assertIn(member, slaves) + + def test_bonding_system_mac(self): + # configure member interfaces and system-mac + default_system_mac = '00:00:00:00:00:00' # default MAC is all zeroes + system_mac = '00:50:ab:cd:ef:11' + + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + + self.cli_set(self._base_path + [interface, 'system-mac', system_mac]) + + self.cli_commit() + + # verify config + for interface in self._interfaces: + tmp = read_file(f'/sys/class/net/{interface}/bonding/ad_actor_system') + self.assertIn(tmp, system_mac) + + for interface in self._interfaces: + self.cli_delete(self._base_path + [interface, 'system-mac']) + + self.cli_commit() + + # verify default value + for interface in self._interfaces: + tmp = read_file(f'/sys/class/net/{interface}/bonding/ad_actor_system') + self.assertIn(tmp, default_system_mac) + + def test_bonding_evpn_multihoming(self): + id = '5' + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + + self.cli_set(self._base_path + [interface, 'evpn', 'es-id', id]) + self.cli_set(self._base_path + [interface, 'evpn', 'es-df-pref', id]) + self.cli_set(self._base_path + [interface, 'evpn', 'es-sys-mac', f'00:12:34:56:78:0{id}']) + self.cli_set(self._base_path + [interface, 'evpn', 'uplink']) + + id = int(id) + 1 + + self.cli_commit() + + id = '5' + for interface in self._interfaces: + frrconfig = self.getFRRconfig(f'interface {interface}', daemon='zebra') + + self.assertIn(f' evpn mh es-id {id}', frrconfig) + self.assertIn(f' evpn mh es-df-pref {id}', frrconfig) + self.assertIn(f' evpn mh es-sys-mac 00:12:34:56:78:0{id}', frrconfig) + self.assertIn(f' evpn mh uplink', frrconfig) + + id = int(id) + 1 + + for interface in self._interfaces: + self.cli_delete(self._base_path + [interface, 'evpn', 'es-id']) + self.cli_delete(self._base_path + [interface, 'evpn', 'es-df-pref']) + + self.cli_commit() + + id = '5' + for interface in self._interfaces: + frrconfig = self.getFRRconfig(f'interface {interface}', daemon='zebra') + self.assertIn(f' evpn mh es-sys-mac 00:12:34:56:78:0{id}', frrconfig) + self.assertIn(f' evpn mh uplink', frrconfig) + + id = int(id) + 1 + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py new file mode 100644 index 0000000..54c981a --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_bridge.py @@ -0,0 +1,498 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import json +import unittest + +from base_interfaces_test import BasicInterfaceTest +from copy import deepcopy +from glob import glob + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.template import ip_from_cidr +from vyos.utils.process import cmd +from vyos.utils.file import read_file +from vyos.utils.network import get_interface_config +from vyos.utils.network import interface_exists + +class BridgeInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'bridge'] + cls._mirror_interfaces = ['dum21354'] + cls._members = [] + + # we need to filter out VLAN interfaces identified by a dot (.) + # in their name - just in case! + if 'TEST_ETH' in os.environ: + cls._members = os.environ['TEST_ETH'].split() + else: + for tmp in Section.interfaces('ethernet', vlan=False): + cls._members.append(tmp) + + cls._options['br0'] = [] + for member in cls._members: + cls._options['br0'].append(f'member interface {member}') + cls._interfaces = list(cls._options) + + # call base-classes classmethod + super(BridgeInterfaceTest, cls).setUpClass() + + def tearDown(self): + for intf in self._interfaces: + self.cli_delete(self._base_path + [intf]) + + super().tearDown() + + def test_isolated_interfaces(self): + # Add member interfaces to bridge and set STP cost/priority + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['stp']) + + # assign members to bridge interface + for member in self._members: + base_member = base + ['member', 'interface', member] + self.cli_set(base_member + ['isolated']) + + # commit config + self.cli_commit() + + for interface in self._interfaces: + tmp = get_interface_config(interface) + # STP must be enabled as configured above + self.assertEqual(1, tmp['linkinfo']['info_data']['stp_state']) + + # validate member interface configuration + for member in self._members: + tmp = get_interface_config(member) + # verify member is assigned to the bridge + self.assertEqual(interface, tmp['master']) + # Isolated must be enabled as configured above + self.assertTrue(tmp['linkinfo']['info_slave_data']['isolated']) + + def test_igmp_querier_snooping(self): + # Add member interfaces to bridge + for interface in self._interfaces: + base = self._base_path + [interface] + + # assign members to bridge interface + for member in self._members: + base_member = base + ['member', 'interface', member] + self.cli_set(base_member) + + # commit config + self.cli_commit() + + for interface in self._interfaces: + # Verify IGMP default configuration + tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_snooping') + self.assertEqual(tmp, '0') + tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_querier') + self.assertEqual(tmp, '0') + + # Enable IGMP snooping + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['igmp', 'snooping']) + + # commit config + self.cli_commit() + + for interface in self._interfaces: + # Verify IGMP snooping configuration + # Verify IGMP default configuration + tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_snooping') + self.assertEqual(tmp, '1') + tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_querier') + self.assertEqual(tmp, '0') + + # Enable IGMP querieer + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['igmp', 'querier']) + + # commit config + self.cli_commit() + + for interface in self._interfaces: + # Verify IGMP snooping & querier configuration + tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_snooping') + self.assertEqual(tmp, '1') + tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_querier') + self.assertEqual(tmp, '1') + + # Disable IGMP + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_delete(base + ['igmp']) + + # commit config + self.cli_commit() + + for interface in self._interfaces: + # Verify IGMP snooping & querier configuration + tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_snooping') + self.assertEqual(tmp, '0') + tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_querier') + self.assertEqual(tmp, '0') + + # validate member interface configuration + for member in self._members: + tmp = get_interface_config(member) + # verify member is assigned to the bridge + self.assertEqual(interface, tmp['master']) + + + def test_add_remove_bridge_member(self): + # Add member interfaces to bridge and set STP cost/priority + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['stp']) + self.cli_set(base + ['address', '192.0.2.1/24']) + + cost = 1000 + priority = 10 + # assign members to bridge interface + for member in self._members: + base_member = base + ['member', 'interface', member] + self.cli_set(base_member + ['cost', str(cost)]) + self.cli_set(base_member + ['priority', str(priority)]) + cost += 1 + priority += 1 + + # commit config + self.cli_commit() + + # Add member interfaces to bridge and set STP cost/priority + for interface in self._interfaces: + cost = 1000 + priority = 10 + + tmp = get_interface_config(interface) + self.assertEqual('802.1Q', tmp['linkinfo']['info_data']['vlan_protocol']) # default VLAN protocol + + for member in self._members: + tmp = get_interface_config(member) + self.assertEqual(interface, tmp['master']) + self.assertFalse( tmp['linkinfo']['info_slave_data']['isolated']) + self.assertEqual(cost, tmp['linkinfo']['info_slave_data']['cost']) + self.assertEqual(priority, tmp['linkinfo']['info_slave_data']['priority']) + + cost += 1 + priority += 1 + + + def test_vif_8021q_interfaces(self): + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['enable-vlan']) + super().test_vif_8021q_interfaces() + + def test_vif_8021q_lower_up_down(self): + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['enable-vlan']) + super().test_vif_8021q_lower_up_down() + + def test_vif_8021q_qos_change(self): + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['enable-vlan']) + super().test_vif_8021q_qos_change() + + def test_vif_8021q_mtu_limits(self): + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['enable-vlan']) + super().test_vif_8021q_mtu_limits() + + def test_bridge_vlan_filter(self): + vifs = ['10', '20', '30', '40'] + native_vlan = '20' + + # Add member interface to bridge and set VLAN filter + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['enable-vlan']) + self.cli_set(base + ['address', '192.0.2.1/24']) + + for vif in vifs: + self.cli_set(base + ['vif', vif, 'address', f'192.0.{vif}.1/24']) + self.cli_set(base + ['vif', vif, 'mtu', self._mtu]) + + for member in self._members: + base_member = base + ['member', 'interface', member] + self.cli_set(base_member + ['native-vlan', native_vlan]) + for vif in vifs: + self.cli_set(base_member + ['allowed-vlan', vif]) + + # commit config + self.cli_commit() + + def _verify_members(interface, members) -> None: + # check member interfaces are added on the bridge + bridge_members = [] + for tmp in glob(f'/sys/class/net/{interface}/lower_*'): + bridge_members.append(os.path.basename(tmp).replace('lower_', '')) + + self.assertListEqual(sorted(members), sorted(bridge_members)) + + def _check_vlan_filter(interface, vifs) -> None: + configured_vlan_ids = [] + + bridge_json = cmd(f'bridge -j vlan show dev {interface}') + bridge_json = json.loads(bridge_json) + self.assertIsNotNone(bridge_json) + + for tmp in bridge_json: + self.assertIn('vlans', tmp) + + for vlan in tmp['vlans']: + self.assertIn('vlan', vlan) + configured_vlan_ids.append(str(vlan['vlan'])) + + # Verify native VLAN ID has 'PVID' flag set on individual member ports + if not interface.startswith('br') and str(vlan['vlan']) == native_vlan: + self.assertIn('flags', vlan) + self.assertIn('PVID', vlan['flags']) + + self.assertListEqual(sorted(configured_vlan_ids), sorted(vifs)) + + # Verify correct setting of VLAN filter function + for interface in self._interfaces: + tmp = read_file(f'/sys/class/net/{interface}/bridge/vlan_filtering') + self.assertEqual(tmp, '1') + + # Obtain status information and verify proper VLAN filter setup. + # First check if all members are present, second check if all VLANs + # are assigned on the parend bridge interface, third verify all the + # VLANs are properly setup on the downstream "member" ports + for interface in self._interfaces: + # check member interfaces are added on the bridge + _verify_members(interface, self._members) + + # Check if all VLAN ids are properly set up. Bridge interface always + # has native VLAN 1 + tmp = deepcopy(vifs) + tmp.append('1') + _check_vlan_filter(interface, tmp) + + for member in self._members: + _check_vlan_filter(member, vifs) + + # change member interface description to trigger config update, + # VLANs must still exist (T4565) + for interface in self._interfaces: + for member in self._members: + self.cli_set(['interfaces', Section.section(member), member, 'description', f'foo {member}']) + + # commit config + self.cli_commit() + + # Obtain status information and verify proper VLAN filter setup. + # First check if all members are present, second check if all VLANs + # are assigned on the parend bridge interface, third verify all the + # VLANs are properly setup on the downstream "member" ports + for interface in self._interfaces: + # check member interfaces are added on the bridge + _verify_members(interface, self._members) + + # Check if all VLAN ids are properly set up. Bridge interface always + # has native VLAN 1 + tmp = deepcopy(vifs) + tmp.append('1') + _check_vlan_filter(interface, tmp) + + for member in self._members: + _check_vlan_filter(member, vifs) + + # delete all members + for interface in self._interfaces: + self.cli_delete(self._base_path + [interface, 'member']) + + # commit config + self.cli_commit() + + # verify member interfaces are no longer assigned on the bridge + for interface in self._interfaces: + bridge_members = [] + for tmp in glob(f'/sys/class/net/{interface}/lower_*'): + bridge_members.append(os.path.basename(tmp).replace('lower_', '')) + + self.assertNotEqual(len(self._members), len(bridge_members)) + for member in self._members: + self.assertNotIn(member, bridge_members) + + def test_bridge_vif_members(self): + # T2945: ensure that VIFs are not dropped from bridge + vifs = ['300', '400'] + for interface in self._interfaces: + for member in self._members: + for vif in vifs: + self.cli_set(['interfaces', 'ethernet', member, 'vif', vif]) + self.cli_set(['interfaces', 'bridge', interface, 'member', 'interface', f'{member}.{vif}']) + + self.cli_commit() + + # Verify config + for interface in self._interfaces: + for member in self._members: + for vif in vifs: + # member interface must be assigned to the bridge + self.assertTrue(os.path.exists(f'/sys/class/net/{interface}/lower_{member}.{vif}')) + + # delete all members + for interface in self._interfaces: + for member in self._members: + for vif in vifs: + self.cli_delete(['interfaces', 'ethernet', member, 'vif', vif]) + self.cli_delete(['interfaces', 'bridge', interface, 'member', 'interface', f'{member}.{vif}']) + + def test_bridge_vif_s_vif_c_members(self): + # T2945: ensure that VIFs are not dropped from bridge + vifs = ['300', '400'] + vifc = ['301', '401'] + for interface in self._interfaces: + for member in self._members: + for vif_s in vifs: + for vif_c in vifc: + self.cli_set(['interfaces', 'ethernet', member, 'vif-s', vif_s, 'vif-c', vif_c]) + self.cli_set(['interfaces', 'bridge', interface, 'member', 'interface', f'{member}.{vif_s}.{vif_c}']) + + self.cli_commit() + + # Verify config + for interface in self._interfaces: + for member in self._members: + for vif_s in vifs: + for vif_c in vifc: + # member interface must be assigned to the bridge + self.assertTrue(os.path.exists(f'/sys/class/net/{interface}/lower_{member}.{vif_s}.{vif_c}')) + + # delete all members + for interface in self._interfaces: + for member in self._members: + for vif_s in vifs: + self.cli_delete(['interfaces', 'ethernet', member, 'vif-s', vif_s]) + for vif_c in vifc: + self.cli_delete(['interfaces', 'bridge', interface, 'member', 'interface', f'{member}.{vif_s}.{vif_c}']) + + def test_bridge_tunnel_vxlan_multicast(self): + # Testcase for T6043 running VXLAN over gretap + br_if = 'br0' + tunnel_if = 'tun0' + eth_if = 'eth1' + vxlan_if = 'vxlan0' + multicast_group = '239.0.0.241' + vni = '123' + eth0_addr = '192.0.2.2/30' + + self.cli_set(['interfaces', 'bridge', br_if, 'member', 'interface', eth_if]) + self.cli_set(['interfaces', 'bridge', br_if, 'member', 'interface', vxlan_if]) + + self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', eth0_addr]) + + self.cli_set(['interfaces', 'tunnel', tunnel_if, 'address', '10.0.0.2/24']) + self.cli_set(['interfaces', 'tunnel', tunnel_if, 'enable-multicast']) + self.cli_set(['interfaces', 'tunnel', tunnel_if, 'encapsulation', 'gretap']) + self.cli_set(['interfaces', 'tunnel', tunnel_if, 'mtu', '1500']) + self.cli_set(['interfaces', 'tunnel', tunnel_if, 'parameters', 'ip', 'ignore-df']) + self.cli_set(['interfaces', 'tunnel', tunnel_if, 'parameters', 'ip', 'key', '1']) + self.cli_set(['interfaces', 'tunnel', tunnel_if, 'parameters', 'ip', 'no-pmtu-discovery']) + self.cli_set(['interfaces', 'tunnel', tunnel_if, 'parameters', 'ip', 'ttl', '0']) + self.cli_set(['interfaces', 'tunnel', tunnel_if, 'remote', '203.0.113.2']) + self.cli_set(['interfaces', 'tunnel', tunnel_if, 'source-address', ip_from_cidr(eth0_addr)]) + + self.cli_set(['interfaces', 'vxlan', vxlan_if, 'group', multicast_group]) + self.cli_set(['interfaces', 'vxlan', vxlan_if, 'mtu', '1426']) + self.cli_set(['interfaces', 'vxlan', vxlan_if, 'source-interface', tunnel_if]) + self.cli_set(['interfaces', 'vxlan', vxlan_if, 'vni', vni]) + + self.cli_commit() + + self.assertTrue(interface_exists(eth_if)) + self.assertTrue(interface_exists(vxlan_if)) + self.assertTrue(interface_exists(tunnel_if)) + + tmp = get_interface_config(vxlan_if) + self.assertEqual(tmp['ifname'], vxlan_if) + self.assertEqual(tmp['linkinfo']['info_data']['link'], tunnel_if) + self.assertEqual(tmp['linkinfo']['info_data']['group'], multicast_group) + self.assertEqual(tmp['linkinfo']['info_data']['id'], int(vni)) + + bridge_members = [] + for tmp in glob(f'/sys/class/net/{br_if}/lower_*'): + bridge_members.append(os.path.basename(tmp).replace('lower_', '')) + self.assertIn(eth_if, bridge_members) + self.assertIn(vxlan_if, bridge_members) + + self.cli_delete(['interfaces', 'bridge', br_if]) + self.cli_delete(['interfaces', 'vxlan', vxlan_if]) + self.cli_delete(['interfaces', 'tunnel', tunnel_if]) + self.cli_delete(['interfaces', 'ethernet', 'eth0', 'address', eth0_addr]) + + def test_bridge_vlan_protocol(self): + protocol = '802.1ad' + + # Add member interface to bridge and set VLAN filter + for interface in self._interfaces: + self.cli_set(self._base_path + [interface, 'protocol', protocol]) + + # commit config + self.cli_commit() + + for interface in self._interfaces: + tmp = get_interface_config(interface) + self.assertEqual(protocol, tmp['linkinfo']['info_data']['vlan_protocol']) + + def test_bridge_delete_with_vxlan_heighbor_suppress(self): + vxlan_if = 'vxlan0' + vni = '123' + br_if = 'br0' + eth0_addr = '192.0.2.2/30' + + self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', eth0_addr]) + self.cli_set(['interfaces', 'vxlan', vxlan_if, 'parameters', 'neighbor-suppress']) + self.cli_set(['interfaces', 'vxlan', vxlan_if, 'mtu', '1426']) + self.cli_set(['interfaces', 'vxlan', vxlan_if, 'source-address', ip_from_cidr(eth0_addr)]) + self.cli_set(['interfaces', 'vxlan', vxlan_if, 'vni', vni]) + + self.cli_set(['interfaces', 'bridge', br_if, 'member', 'interface', vxlan_if]) + + self.cli_commit() + + self.assertTrue(interface_exists(vxlan_if)) + self.assertTrue(interface_exists(br_if)) + + # cannot delete bridge interface if "neighbor-suppress" parameter is configured for VXLAN interface + self.cli_delete(['interfaces', 'bridge', br_if]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(['interfaces', 'vxlan', vxlan_if, 'parameters', 'neighbor-suppress']) + + self.cli_commit() + + self.assertFalse(interface_exists(br_if)) + + self.cli_delete(['interfaces', 'vxlan', vxlan_if]) + self.cli_delete(['interfaces', 'ethernet', 'eth0', 'address', eth0_addr]) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_dummy.py b/smoketest/scripts/cli/test_interfaces_dummy.py new file mode 100644 index 0000000..d96ec2c --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_dummy.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_interfaces_test import BasicInterfaceTest + +class DummyInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'dummy'] + cls._interfaces = ['dum435', 'dum8677', 'dum0931', 'dum089'] + # call base-classes classmethod + super(DummyInterfaceTest, cls).setUpClass() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py new file mode 100644 index 0000000..3d12364 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from glob import glob +from json import loads + +from netifaces import AF_INET +from netifaces import AF_INET6 +from netifaces import ifaddresses + +from base_interfaces_test import BasicInterfaceTest +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import cmd +from vyos.utils.process import popen +from vyos.utils.file import read_file +from vyos.utils.network import is_ipv6_link_local + +class EthernetInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'ethernet'] + cls._mirror_interfaces = ['dum21354'] + + # We only test on physical interfaces and not VLAN (sub-)interfaces + if 'TEST_ETH' in os.environ: + tmp = os.environ['TEST_ETH'].split() + cls._interfaces = tmp + else: + for tmp in Section.interfaces('ethernet', vlan=False): + cls._interfaces.append(tmp) + + cls._macs = {} + for interface in cls._interfaces: + cls._macs[interface] = read_file(f'/sys/class/net/{interface}/address') + + # call base-classes classmethod + super(EthernetInterfaceTest, cls).setUpClass() + + def tearDown(self): + for interface in self._interfaces: + # when using a dedicated interface to test via TEST_ETH environment + # variable only this one will be cleared in the end - usable to test + # ethernet interfaces via SSH + self.cli_delete(self._base_path + [interface]) + self.cli_set(self._base_path + [interface, 'duplex', 'auto']) + self.cli_set(self._base_path + [interface, 'speed', 'auto']) + self.cli_set(self._base_path + [interface, 'hw-id', self._macs[interface]]) + + self.cli_commit() + + # Verify that no address remains on the system as this is an eternal + # interface. + for interface in self._interfaces: + self.assertNotIn(AF_INET, ifaddresses(interface)) + # required for IPv6 link-local address + self.assertIn(AF_INET6, ifaddresses(interface)) + for addr in ifaddresses(interface)[AF_INET6]: + # checking link local addresses makes no sense + if is_ipv6_link_local(addr['addr']): + continue + self.assertFalse(is_intf_addr_assigned(interface, addr['addr'])) + # Ensure no VLAN interfaces are left behind + tmp = [x for x in Section.interfaces('ethernet') if x.startswith(f'{interface}.')] + self.assertListEqual(tmp, []) + + def test_offloading_rps(self): + # enable RPS on all available CPUs, RPS works with a CPU bitmask, + # where each bit represents a CPU (core/thread). The formula below + # expands to rps_cpus = 255 for a 8 core system + rps_cpus = (1 << os.cpu_count()) -1 + + # XXX: we should probably reserve one core when the system is under + # high preasure so we can still have a core left for housekeeping. + # This is done by masking out the lowst bit so CPU0 is spared from + # receive packet steering. + rps_cpus &= ~1 + + for interface in self._interfaces: + self.cli_set(self._base_path + [interface, 'offload', 'rps']) + + self.cli_commit() + + for interface in self._interfaces: + cpus = read_file(f'/sys/class/net/{interface}/queues/rx-0/rps_cpus') + # remove the nasty ',' separation on larger strings + cpus = cpus.replace(',','') + cpus = int(cpus, 16) + + self.assertEqual(f'{cpus:x}', f'{rps_cpus:x}') + + def test_offloading_rfs(self): + global_rfs_flow = 32768 + rfs_flow = global_rfs_flow + + for interface in self._interfaces: + self.cli_set(self._base_path + [interface, 'offload', 'rfs']) + + self.cli_commit() + + for interface in self._interfaces: + queues = len(glob(f'/sys/class/net/{interface}/queues/rx-*')) + rfs_flow = int(global_rfs_flow/queues) + for i in range(0, queues): + tmp = read_file(f'/sys/class/net/{interface}/queues/rx-{i}/rps_flow_cnt') + self.assertEqual(int(tmp), rfs_flow) + + tmp = read_file(f'/proc/sys/net/core/rps_sock_flow_entries') + self.assertEqual(int(tmp), global_rfs_flow) + + # delete configuration of RFS and check all values returned to default "0" + for interface in self._interfaces: + self.cli_delete(self._base_path + [interface, 'offload', 'rfs']) + + self.cli_commit() + + for interface in self._interfaces: + queues = len(glob(f'/sys/class/net/{interface}/queues/rx-*')) + rfs_flow = int(global_rfs_flow/queues) + for i in range(0, queues): + tmp = read_file(f'/sys/class/net/{interface}/queues/rx-{i}/rps_flow_cnt') + self.assertEqual(int(tmp), 0) + + + def test_non_existing_interface(self): + unknonw_interface = self._base_path + ['eth667'] + self.cli_set(unknonw_interface) + + # check validate() - interface does not exist + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # we need to remove this wrong interface from the configuration + # manually, else tearDown() will have problem in commit() + self.cli_delete(unknonw_interface) + + def test_speed_duplex_verify(self): + for interface in self._interfaces: + self.cli_set(self._base_path + [interface, 'speed', '1000']) + + # check validate() - if either speed or duplex is not auto, the + # other one must be manually configured, too + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'speed', 'auto']) + self.cli_commit() + + def test_ethtool_ring_buffer(self): + for interface in self._interfaces: + # We do not use vyos.ethtool here to not have any chance + # for invalid testcases. Re-gain data by hand + tmp = cmd(f'sudo ethtool --json --show-ring {interface}') + tmp = loads(tmp) + max_rx = str(tmp[0]['rx-max']) + max_tx = str(tmp[0]['tx-max']) + + self.cli_set(self._base_path + [interface, 'ring-buffer', 'rx', max_rx]) + self.cli_set(self._base_path + [interface, 'ring-buffer', 'tx', max_tx]) + + self.cli_commit() + + for interface in self._interfaces: + tmp = cmd(f'sudo ethtool --json --show-ring {interface}') + tmp = loads(tmp) + max_rx = str(tmp[0]['rx-max']) + max_tx = str(tmp[0]['tx-max']) + rx = str(tmp[0]['rx']) + tx = str(tmp[0]['tx']) + + # validate if the above change was carried out properly and the + # ring-buffer size got increased + self.assertEqual(max_rx, rx) + self.assertEqual(max_tx, tx) + + def test_ethtool_flow_control(self): + for interface in self._interfaces: + # Disable flow-control + self.cli_set(self._base_path + [interface, 'disable-flow-control']) + # Check current flow-control state on ethernet interface + out, err = popen(f'sudo ethtool --json --show-pause {interface}') + # Flow-control not supported - test if it bails out with a proper + # this is a dynamic path where err = 1 on VMware, but err = 0 on + # a physical box. + if bool(err): + with self.assertRaises(ConfigSessionError): + self.cli_commit() + else: + out = loads(out) + # Flow control is on + self.assertTrue(out[0]['autonegotiate']) + + # commit change on CLI to disable-flow-control and re-test + self.cli_commit() + + out, err = popen(f'sudo ethtool --json --show-pause {interface}') + out = loads(out) + self.assertFalse(out[0]['autonegotiate']) + + def test_ethtool_evpn_uplink_tarcking(self): + for interface in self._interfaces: + self.cli_set(self._base_path + [interface, 'evpn', 'uplink']) + + self.cli_commit() + + for interface in self._interfaces: + frrconfig = self.getFRRconfig(f'interface {interface}', daemon='zebra') + self.assertIn(f' evpn mh uplink', frrconfig) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_geneve.py b/smoketest/scripts/cli/test_interfaces_geneve.py new file mode 100644 index 0000000..5f8fae9 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_geneve.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from vyos.ifconfig import Interface +from vyos.utils.network import get_interface_config + +from base_interfaces_test import BasicInterfaceTest + +class GeneveInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'geneve'] + cls._options = { + 'gnv0': ['vni 10', 'remote 127.0.1.1'], + 'gnv1': ['vni 20', 'remote 127.0.1.2'], + 'gnv1': ['vni 30', 'remote 2001:db8::1', 'parameters ipv6 flowlabel 0x1000'], + } + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(GeneveInterfaceTest, cls).setUpClass() + + def test_geneve_parameters(self): + tos = '40' + ttl = 20 + for intf in self._interfaces: + for option in self._options.get(intf, []): + self.cli_set(self._base_path + [intf] + option.split()) + + self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'df', 'set']) + self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'tos', tos]) + self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'innerproto']) + self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'ttl', str(ttl)]) + ttl += 10 + + self.cli_commit() + + ttl = 20 + for interface in self._interfaces: + options = get_interface_config(interface) + + vni = options['linkinfo']['info_data']['id'] + self.assertIn(f'vni {vni}', self._options[interface]) + + if any('remote' in s for s in self._options[interface]): + key = 'remote' + if 'remote6' in options['linkinfo']['info_data']: + key = 'remote6' + + remote = options['linkinfo']['info_data'][key] + self.assertIn(f'remote {remote}', self._options[interface]) + + if any('flowlabel' in s for s in self._options[interface]): + label = options['linkinfo']['info_data']['label'] + self.assertIn(f'parameters ipv6 flowlabel {label}', self._options[interface]) + + if any('innerproto' in s for s in self._options[interface]): + inner = options['linkinfo']['info_data']['innerproto'] + self.assertIn(f'parameters ip {inner}', self._options[interface]) + + + self.assertEqual('geneve', options['linkinfo']['info_kind']) + self.assertEqual('set', options['linkinfo']['info_data']['df']) + self.assertEqual(f'0x{tos}', options['linkinfo']['info_data']['tos']) + self.assertEqual(ttl, options['linkinfo']['info_data']['ttl']) + self.assertEqual(Interface(interface).get_admin_state(), 'up') + ttl += 10 + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_input.py b/smoketest/scripts/cli/test_interfaces_input.py new file mode 100644 index 0000000..3ddf860 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_input.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from vyos.utils.file import read_file +from vyos.ifconfig import Interface +from base_vyostest_shim import VyOSUnitTestSHIM + +base_path = ['interfaces', 'input'] + +# add a classmethod to setup a temporaray PPPoE server for "proper" validation +class InputInterfaceTest(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(InputInterfaceTest, cls).setUpClass() + cls._interfaces = ['ifb10', 'ifb20', 'ifb30'] + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_01_description(self): + # Check if PPPoE dialer can be configured and runs + for interface in self._interfaces: + self.cli_set(base_path + [interface, 'description', f'foo-{interface}']) + + # commit changes + self.cli_commit() + + # Validate remove interface description "empty" + for interface in self._interfaces: + tmp = read_file(f'/sys/class/net/{interface}/ifalias') + self.assertEqual(tmp, f'foo-{interface}') + self.assertEqual(Interface(interface).get_alias(), f'foo-{interface}') + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_l2tpv3.py b/smoketest/scripts/cli/test_interfaces_l2tpv3.py new file mode 100644 index 0000000..2816573 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_l2tpv3.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json +import unittest + +from base_interfaces_test import BasicInterfaceTest +from vyos.utils.process import cmd +from vyos.utils.kernel import unload_kmod +class L2TPv3InterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'l2tpv3'] + cls._options = { + 'l2tpeth10': ['source-address 127.0.0.1', 'remote 127.10.10.10', + 'tunnel-id 100', 'peer-tunnel-id 10', + 'session-id 100', 'peer-session-id 10', + 'source-port 1010', 'destination-port 10101'], + 'l2tpeth20': ['source-address 127.0.0.1', 'peer-session-id 20', + 'peer-tunnel-id 200', 'remote 127.20.20.20', + 'session-id 20', 'tunnel-id 200', + 'source-port 2020', 'destination-port 20202'], + } + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(L2TPv3InterfaceTest, cls).setUpClass() + + def test_add_single_ip_address(self): + super().test_add_single_ip_address() + + command = 'sudo ip -j l2tp show session' + json_out = json.loads(cmd(command)) + for interface in self._options: + for config in json_out: + if config['interface'] == interface: + # convert list with configuration items into a dict + dict = {} + for opt in self._options[interface]: + dict.update({opt.split()[0].replace('-','_'): opt.split()[1]}) + + for key in ['peer_session_id', 'peer_tunnel_id', + 'session_id', 'tunnel_id']: + self.assertEqual(str(config[key]), dict[key]) + + +if __name__ == '__main__': + # when re-running this test, cleanup loaded modules first so they are + # reloaded on demand - not needed but test more and more features + for module in ['l2tp_ip6', 'l2tp_ip', 'l2tp_eth', 'l2tp_eth', + 'l2tp_netlink', 'l2tp_core']: + unload_kmod(module) + + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_loopback.py b/smoketest/scripts/cli/test_interfaces_loopback.py new file mode 100644 index 0000000..0454dc6 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_loopback.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_interfaces_test import BasicInterfaceTest +from netifaces import interfaces + +from vyos.utils.network import is_intf_addr_assigned + +loopbacks = ['127.0.0.1', '::1'] + +class LoopbackInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'loopback'] + cls._interfaces = ['lo'] + # call base-classes classmethod + super(LoopbackInterfaceTest, cls).setUpClass() + + # we need to override tearDown() as loopback interfaces are ephemeral and + # will always be present on the system - the base class check will fail as + # the loopback interface will still exist. + def tearDown(self): + self.cli_delete(self._base_path) + self.cli_commit() + + # loopback interface must persist! + for intf in self._interfaces: + self.assertIn(intf, interfaces()) + + def test_add_single_ip_address(self): + super().test_add_single_ip_address() + for addr in loopbacks: + self.assertTrue(is_intf_addr_assigned('lo', addr)) + + def test_add_multiple_ip_addresses(self): + super().test_add_multiple_ip_addresses() + for addr in loopbacks: + self.assertTrue(is_intf_addr_assigned('lo', addr)) + + def test_interface_disable(self): + self.skipTest('not supported') + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py new file mode 100644 index 0000000..d73895b --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_macsec.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import unittest + +from base_interfaces_test import BasicInterfaceTest + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.file import read_file +from vyos.utils.network import get_interface_config +from vyos.utils.network import interface_exists +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'wpa_supplicant' + +def get_config_value(interface, key): + tmp = read_file(f'/run/wpa_supplicant/{interface}.conf') + tmp = re.findall(r'\n?{}=(.*)'.format(key), tmp) + return tmp[0] + +class MACsecInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'macsec'] + cls._options = { 'macsec0': ['source-interface eth0', 'security cipher gcm-aes-128'] } + + # if we have a physical eth1 interface, add a second macsec instance + if 'eth1' in Section.interfaces('ethernet'): + macsec = { 'macsec1': [f'source-interface eth1', 'security cipher gcm-aes-128'] } + cls._options.update(macsec) + + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(MACsecInterfaceTest, cls).setUpClass() + + def tearDown(self): + super().tearDown() + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_macsec_encryption(self): + # MACsec can be operating in authentication and encryption mode - both + # using different mandatory settings, lets test encryption as the basic + # authentication test has been performed using the base class tests + + mak_cak = '232e44b7fda6f8e2d88a07bf78a7aff4' + mak_ckn = '40916f4b23e3d548ad27eedd2d10c6f98c2d21684699647d63d41b500dfe8836' + replay_window = '64' + + for interface, option_value in self._options.items(): + for option in option_value: + if option.split()[0] == 'source-interface': + src_interface = option.split()[1] + + self.cli_set(self._base_path + [interface] + option.split()) + + # Encrypt link + self.cli_set(self._base_path + [interface, 'security', 'encrypt']) + + # check validate() - Physical source interface MTU must be higher then our MTU + self.cli_set(self._base_path + [interface, 'mtu', '1500']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'mtu']) + + # check validate() - MACsec security keys mandartory when encryption is enabled + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'mka', 'cak', mak_cak]) + + # check validate() - MACsec security keys mandartory when encryption is enabled + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'mka', 'ckn', mak_ckn]) + + self.cli_set(self._base_path + [interface, 'security', 'replay-window', replay_window]) + + # final commit of settings + self.cli_commit() + + tmp = get_config_value(src_interface, 'macsec_integ_only') + self.assertIn("0", tmp) + + tmp = get_config_value(src_interface, 'mka_cak') + self.assertIn(mak_cak, tmp) + + tmp = get_config_value(src_interface, 'mka_ckn') + self.assertIn(mak_ckn, tmp) + + # check that the default priority of 255 is programmed + tmp = get_config_value(src_interface, 'mka_priority') + self.assertIn("255", tmp) + + tmp = get_config_value(src_interface, 'macsec_replay_window') + self.assertIn(replay_window, tmp) + + tmp = read_file(f'/sys/class/net/{interface}/mtu') + self.assertEqual(tmp, '1460') + + # Encryption enabled? + tmp = get_interface_config(interface) + self.assertTrue(tmp['linkinfo']['info_data']['encrypt']) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_macsec_gcm_aes_128(self): + src_interface = 'eth0' + interface = 'macsec1' + cipher = 'gcm-aes-128' + self.cli_set(self._base_path + [interface]) + + # check validate() - source interface is mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'source-interface', src_interface]) + + # check validate() - cipher is mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'cipher', cipher]) + + # final commit and verify + self.cli_commit() + self.assertTrue(interface_exists(interface)) + + # Verify proper cipher suite (T4537) + tmp = get_interface_config(interface) + self.assertEqual(cipher, tmp['linkinfo']['info_data']['cipher_suite'].lower()) + + def test_macsec_gcm_aes_256(self): + src_interface = 'eth0' + interface = 'macsec4' + cipher = 'gcm-aes-256' + self.cli_set(self._base_path + [interface]) + + # check validate() - source interface is mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'source-interface', src_interface]) + + # check validate() - cipher is mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'cipher', cipher]) + + # final commit and verify + self.cli_commit() + self.assertTrue(interface_exists(interface)) + + # Verify proper cipher suite (T4537) + tmp = get_interface_config(interface) + self.assertEqual(cipher, tmp['linkinfo']['info_data']['cipher_suite'].lower()) + + def test_macsec_source_interface(self): + # Ensure source-interface can bot be part of any other bond or bridge + base_bridge = ['interfaces', 'bridge', 'br200'] + base_bond = ['interfaces', 'bonding', 'bond200'] + + for interface, option_value in self._options.items(): + for option in option_value: + self.cli_set(self._base_path + [interface] + option.split()) + if option.split()[0] == 'source-interface': + src_interface = option.split()[1] + + self.cli_set(base_bridge + ['member', 'interface', src_interface]) + # check validate() - Source interface must not already be a member of a bridge + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_bridge) + + self.cli_set(base_bond + ['member', 'interface', src_interface]) + # check validate() - Source interface must not already be a member of a bridge + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_bond) + + # final commit and verify + self.cli_commit() + self.assertTrue(interface_exists(interface)) + + def test_macsec_static_keys(self): + src_interface = 'eth0' + interface = 'macsec5' + cipher1 = 'gcm-aes-128' + cipher2 = 'gcm-aes-256' + tx_key_1 = '71a82a48eddfa12c08a19792ca20c4bb' + tx_key_2 = 'dd487b2958e855ea35a5d43a5ecb3dcfbe7889ffcb877770252feb13b734478d' + rx_key_1 = '0022d00f57e75241a230cdf7118dfcc5' + rx_key_2 = 'b7d6d7ad075e02323fdeb845217b884d3f93ff36b2cdaf6b07eeb189b877245f' + peer_mac = '00:11:22:33:44:55' + self.cli_set(self._base_path + [interface]) + + # Encrypt link + self.cli_set(self._base_path + [interface, 'security', 'encrypt']) + + # check validate() - source interface is mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'source-interface', src_interface]) + + # check validate() - cipher is mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'cipher', cipher1]) + + # check validate() - only static or mka config is allowed + self.cli_set(self._base_path + [interface, 'security', 'static']) + self.cli_set(self._base_path + [interface, 'security', 'mka', 'cak', tx_key_1]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'security', 'mka']) + + # check validate() - key required + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # check validate() - key length must match cipher + self.cli_set(self._base_path + [interface, 'security', 'static', 'key', tx_key_2]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'static', 'key', tx_key_1]) + + # check validate() - at least one peer must be defined + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # check validate() - enabled peer must have both key and MAC defined + self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'mac', peer_mac]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'mac']) + self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'key', rx_key_1]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'mac', peer_mac]) + + # check validate() - peer key length must match cipher + self.cli_set(self._base_path + [interface, 'security', 'cipher', cipher2]) + self.cli_set(self._base_path + [interface, 'security', 'static', 'key', tx_key_2]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'key', rx_key_2]) + + # final commit and verify + self.cli_commit() + + self.assertTrue(interface_exists(interface)) + tmp = get_interface_config(interface) + self.assertEqual(cipher2, tmp['linkinfo']['info_data']['cipher_suite'].lower()) + # Encryption enabled? + self.assertTrue(tmp['linkinfo']['info_data']['encrypt']) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py new file mode 100644 index 0000000..e087b87 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_openvpn.py @@ -0,0 +1,868 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from glob import glob +from ipaddress import IPv4Network +from netifaces import interfaces + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.template import address_from_cidr +from vyos.template import inc_ip +from vyos.template import last_host_address +from vyos.template import netmask_from_cidr + +PROCESS_NAME = 'openvpn' + +base_path = ['interfaces', 'openvpn'] + +cert_data = """ +MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIw +WTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv +bWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIx +MDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNV +BAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlP +UzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3 +QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu ++JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3Lftz +ngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93 ++dm/LDnp7C0= +""" + +key_data = """ +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx +2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7 +u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww +""" + +dh_data = """ +MIIBCAKCAQEApzGAPcQlLJiOyfGZgl1qxNgufXkdpjG7lMaOrO4TGr1giFe3jIFO +FxJNC/G9Dn+KSukaWssVVR+Jwr/JesZFPawihS03wC7cZsccykNRIjiteqJDwYJZ +UHieOxyCuCeY4pqOUCl1uswRGjLvIFtwynpnXKKuz2YtjNifma90PEgv/vVWKix+ +Q0TAbdbzJzO5xp8UVn9DuYfSr10k3LbDqDM7w5ezHZxFk24S5pN/yoOpdbxB8TS6 +7q3IYXxR3F+RseKu4J3AvkxXSP1j7COXddPpLnvbJT/SW8NrjuC/n0eKGvmeyqNv +108Y89jnT79MxMMRQk66iwlsd1m4pa/OYwIBAg== +""" + +ovpn_key_data = """ +443f2a710ac411c36894b2531e62c4550b079b8f3f08997f4be57c64abfdaaa4 +31d2396b01ecec3a2c0618959e8186d99f489742d25673ffb3268841ebb2e704 +2a2daabe584e79d51d2b1d7409bf8840f7e42efa3e660a521719b04ee88b9043 +e6315ae12da7c9abd55f67eeed71a9ee8c6e163b5d2661fc332cf90cb45658b4 +adf892f79537d37d3a3d90da283ce885adf325ffd2b5be92067cdf0345c7712c +9d36b642c170351b6d9ce9f6230c7a2617b0c181121bce7d5373404fb68e6521 +0b36e6d40ef2769cf8990503859f6f2db3c85ba74420430a6250d6a74ca51ece +4b85124bfdfec0c8a530cefa7350378d81a4539f74bed832a902ae4798142e4a +""" + +remote_port = '1194' +protocol = 'udp' +path = [] +interface = '' +remote_host = '' +vrf_name = 'orange' +dummy_if = 'dum1301' + +def get_vrf(interface): + for upper in glob(f'/sys/class/net/{interface}/upper*'): + # an upper interface could be named: upper_bond0.1000.1100, thus + # we need top drop the upper_ prefix + tmp = os.path.basename(upper) + tmp = tmp.replace('upper_', '') + return tmp + +class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestInterfacesOpenVPN, cls).setUpClass() + + cls.cli_set(cls, ['interfaces', 'dummy', dummy_if, 'address', '192.0.2.1/32']) + cls.cli_set(cls, ['vrf', 'name', vrf_name, 'table', '12345']) + + cls.cli_set(cls, ['pki', 'ca', 'ovpn_test', 'certificate', cert_data.replace('\n','')]) + cls.cli_set(cls, ['pki', 'certificate', 'ovpn_test', 'certificate', cert_data.replace('\n','')]) + cls.cli_set(cls, ['pki', 'certificate', 'ovpn_test', 'private', 'key', key_data.replace('\n','')]) + cls.cli_set(cls, ['pki', 'dh', 'ovpn_test', 'parameters', dh_data.replace('\n','')]) + cls.cli_set(cls, ['pki', 'openvpn', 'shared-secret', 'ovpn_test', 'key', ovpn_key_data.replace('\n','')]) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'dummy', dummy_if]) + cls.cli_delete(cls, ['vrf']) + + super(TestInterfacesOpenVPN, cls).tearDownClass() + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_openvpn_client_verify(self): + # Create OpenVPN client interface and test verify() steps. + interface = 'vtun2000' + path = base_path + [interface] + self.cli_set(path + ['mode', 'client']) + self.cli_set(path + ['encryption', 'data-ciphers', 'aes192gcm']) + + # check validate() - cannot specify local-port in client mode + self.cli_set(path + ['local-port', '5000']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['local-port']) + + # check validate() - cannot specify local-host in client mode + self.cli_set(path + ['local-host', '127.0.0.1']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['local-host']) + + # check validate() - cannot specify protocol tcp-passive in client mode + self.cli_set(path + ['protocol', 'tcp-passive']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['protocol']) + + # check validate() - remote-host must be set in client mode + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(path + ['remote-host', '192.0.9.9']) + + # check validate() - cannot specify "tls dh-params" in client mode + self.cli_set(path + ['tls', 'dh-params', 'ovpn_test']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['tls']) + + # check validate() - must specify one of "shared-secret-key" and "tls" + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(path + ['shared-secret-key', 'ovpn_test']) + + # check validate() - must specify one of "shared-secret-key" and "tls" + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['shared-secret-key', 'ovpn_test']) + + # check validate() - cannot specify "encryption cipher" in client mode + self.cli_set(path + ['encryption', 'cipher', 'aes192gcm']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['encryption', 'cipher']) + + self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test']) + self.cli_set(path + ['tls', 'certificate', 'ovpn_test']) + + # check validate() - can not have auth username without a password + self.cli_set(path + ['authentication', 'username', 'vyos']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(path + ['authentication', 'password', 'vyos']) + + # client commit must pass + self.cli_commit() + + self.assertTrue(process_named_running(PROCESS_NAME)) + self.assertIn(interface, interfaces()) + + + def test_openvpn_client_interfaces(self): + # Create OpenVPN client interfaces connecting to different + # server IP addresses. Validate configuration afterwards. + num_range = range(10, 15) + for ii in num_range: + interface = f'vtun{ii}' + remote_host = f'192.0.2.{ii}' + path = base_path + [interface] + auth_hash = 'sha1' + + self.cli_set(path + ['device-type', 'tun']) + self.cli_set(path + ['encryption', 'data-ciphers', 'aes256']) + self.cli_set(path + ['hash', auth_hash]) + self.cli_set(path + ['mode', 'client']) + self.cli_set(path + ['persistent-tunnel']) + self.cli_set(path + ['protocol', protocol]) + self.cli_set(path + ['remote-host', remote_host]) + self.cli_set(path + ['remote-port', remote_port]) + self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test']) + self.cli_set(path + ['tls', 'certificate', 'ovpn_test']) + self.cli_set(path + ['vrf', vrf_name]) + self.cli_set(path + ['authentication', 'username', interface+'user']) + self.cli_set(path + ['authentication', 'password', interface+'secretpw']) + + self.cli_commit() + + for ii in num_range: + interface = f'vtun{ii}' + remote_host = f'192.0.2.{ii}' + config_file = f'/run/openvpn/{interface}.conf' + pw_file = f'/run/openvpn/{interface}.pw' + config = read_file(config_file) + + self.assertIn(f'dev {interface}', config) + self.assertIn(f'dev-type tun', config) + self.assertIn(f'persist-key', config) + self.assertIn(f'proto {protocol}', config) + self.assertIn(f'rport {remote_port}', config) + self.assertIn(f'remote {remote_host}', config) + self.assertIn(f'persist-tun', config) + self.assertIn(f'auth {auth_hash}', config) + self.assertIn(f'data-ciphers AES-256-CBC', config) + + # TLS options + self.assertIn(f'ca /run/openvpn/{interface}_ca.pem', config) + self.assertIn(f'cert /run/openvpn/{interface}_cert.pem', config) + self.assertIn(f'key /run/openvpn/{interface}_cert.key', config) + + self.assertTrue(process_named_running(PROCESS_NAME)) + self.assertEqual(get_vrf(interface), vrf_name) + self.assertIn(interface, interfaces()) + + pw = cmd(f'sudo cat {pw_file}') + self.assertIn(f'{interface}user', pw) + self.assertIn(f'{interface}secretpw', pw) + + # check that no interface remained after deleting them + self.cli_delete(base_path) + self.cli_commit() + + for ii in num_range: + interface = f'vtun{ii}' + self.assertNotIn(interface, interfaces()) + + def test_openvpn_client_ip_version(self): + # Test the client mode behavior combined with different IP protocol versions + + interface = 'vtun10' + remote_host = '192.0.2.10' + remote_host_v6 = 'fd00::2:10' + path = base_path + [interface] + auth_hash = 'sha1' + + # Default behavior: client uses uspecified protocol version (udp) + self.cli_set(path + ['device-type', 'tun']) + self.cli_set(path + ['encryption', 'data-ciphers', 'aes256']) + self.cli_set(path + ['hash', auth_hash]) + self.cli_set(path + ['mode', 'client']) + self.cli_set(path + ['persistent-tunnel']) + self.cli_set(path + ['protocol', 'udp']) + self.cli_set(path + ['remote-host', remote_host]) + self.cli_set(path + ['remote-port', remote_port]) + self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test']) + self.cli_set(path + ['tls', 'certificate', 'ovpn_test']) + self.cli_set(path + ['vrf', vrf_name]) + self.cli_set(path + ['authentication', 'username', interface+'user']) + self.cli_set(path + ['authentication', 'password', interface+'secretpw']) + + self.cli_commit() + + config_file = f'/run/openvpn/{interface}.conf' + config = read_file(config_file) + + self.assertIn(f'dev vtun10', config) + self.assertIn(f'dev-type tun', config) + self.assertIn(f'persist-key', config) + self.assertIn(f'proto udp', config) + self.assertIn(f'rport {remote_port}', config) + self.assertIn(f'remote {remote_host}', config) + self.assertIn(f'persist-tun', config) + + # IPv4 only: client usees udp4 protocol + self.cli_set(path + ['ip-version', 'ipv4']) + self.cli_commit() + + config = read_file(config_file) + self.assertIn(f'proto udp4', config) + + # IPv6 only: client uses udp6 protocol + self.cli_set(path + ['ip-version', 'ipv6']) + self.cli_delete(path + ['remote-host', remote_host]) + self.cli_set(path + ['remote-host', remote_host_v6]) + self.cli_commit() + + config = read_file(config_file) + self.assertIn(f'proto udp6', config) + + # IPv6 dual-stack: not allowed in client mode + self.cli_set(path + ['ip-version', 'dual-stack']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete(base_path) + self.cli_commit() + + def test_openvpn_server_verify(self): + # Create one OpenVPN server interface and check required verify() stages + interface = 'vtun5000' + path = base_path + [interface] + + # check validate() - must speciy operating mode + self.cli_set(path) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(path + ['mode', 'server']) + + # check validate() - cannot specify protocol tcp-active in server mode + self.cli_set(path + ['protocol', 'tcp-active']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['protocol']) + + # check validate() - cannot specify local-port in client mode + self.cli_set(path + ['remote-port', '5000']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['remote-port']) + + # check validate() - cannot specify local-host in client mode + self.cli_set(path + ['remote-host', '127.0.0.1']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['remote-host']) + + # check validate() - must specify "tls dh-params" when not using EC keys + # in server mode + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(path + ['tls', 'dh-params', 'ovpn_test']) + + # check validate() - must specify "server subnet" or add interface to + # bridge in server mode + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # check validate() - server client-ip-pool is too large + # [100.64.0.4 -> 100.127.255.251 = 4194295], maximum is 65536 addresses. + self.cli_set(path + ['server', 'subnet', '100.64.0.0/10']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # check validate() - cannot specify more than 1 IPv4 and 1 IPv6 server subnet + self.cli_set(path + ['server', 'subnet', '100.64.0.0/20']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['server', 'subnet', '100.64.0.0/10']) + + # check validate() - must specify "tls ca-certificate" + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test']) + + # check validate() - must specify "tls certificate" + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(path + ['tls', 'certificate', 'ovpn_test']) + + # check validate() - cannot specify "tls role" in client-server mode' + self.cli_set(path + ['tls', 'role', 'active']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # check validate() - cannot specify "tls role" in client-server mode' + self.cli_set(path + ['tls', 'auth-key', 'ovpn_test']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # check validate() - cannot specify "tcp-passive" when "tls role" is "active" + self.cli_set(path + ['protocol', 'tcp-passive']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['protocol']) + + # check validate() - cannot specify "tls dh-params" when "tls role" is "active" + self.cli_set(path + ['tls', 'dh-params', 'ovpn_test']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['tls', 'dh-params']) + + # check validate() - cannot specify "encryption cipher" in server mode + self.cli_set(path + ['encryption', 'cipher', 'aes256']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['encryption', 'cipher']) + + # Now test the other path with tls role passive + self.cli_set(path + ['tls', 'role', 'passive']) + # check validate() - cannot specify "tcp-active" when "tls role" is "passive" + self.cli_set(path + ['protocol', 'tcp-active']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['protocol']) + + self.cli_set(path + ['tls', 'dh-params', 'ovpn_test']) + + self.cli_commit() + + self.assertTrue(process_named_running(PROCESS_NAME)) + self.assertIn(interface, interfaces()) + + def test_openvpn_server_subnet_topology(self): + # Create OpenVPN server interfaces using different client subnets. + # Validate configuration afterwards. + + auth_hash = 'sha256' + num_range = range(20, 25) + port = '' + client1_routes = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'] + for ii in num_range: + interface = f'vtun{ii}' + subnet = f'192.0.{ii}.0/24' + client_ip = inc_ip(subnet, '5') + path = base_path + [interface] + port = str(2000 + ii) + + self.cli_set(path + ['device-type', 'tun']) + self.cli_set(path + ['encryption', 'data-ciphers', 'aes192']) + self.cli_set(path + ['hash', auth_hash]) + self.cli_set(path + ['mode', 'server']) + self.cli_set(path + ['local-port', port]) + self.cli_set(path + ['server', 'mfa', 'totp']) + self.cli_set(path + ['server', 'subnet', subnet]) + self.cli_set(path + ['server', 'topology', 'subnet']) + self.cli_set(path + ['keep-alive', 'failure-count', '5']) + self.cli_set(path + ['keep-alive', 'interval', '5']) + + # clients + self.cli_set(path + ['server', 'client', 'client1', 'ip', client_ip]) + for route in client1_routes: + self.cli_set(path + ['server', 'client', 'client1', 'subnet', route]) + + self.cli_set(path + ['replace-default-route']) + self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test']) + self.cli_set(path + ['tls', 'certificate', 'ovpn_test']) + self.cli_set(path + ['tls', 'dh-params', 'ovpn_test']) + self.cli_set(path + ['vrf', vrf_name]) + + self.cli_commit() + + for ii in num_range: + interface = f'vtun{ii}' + plugin = f'plugin "/usr/lib/openvpn/openvpn-otp.so" "otp_secrets=/config/auth/openvpn/{interface}-otp-secrets otp_slop=180 totp_t0=0 totp_step=30 totp_digits=6 password_is_cr=1"' + subnet = f'192.0.{ii}.0/24' + + start_addr = inc_ip(subnet, '2') + stop_addr = last_host_address(subnet) + + client_ip = inc_ip(subnet, '5') + client_netmask = netmask_from_cidr(subnet) + + port = str(2000 + ii) + + config_file = f'/run/openvpn/{interface}.conf' + client_config_file = f'/run/openvpn/ccd/{interface}/client1' + config = read_file(config_file) + + self.assertIn(f'dev {interface}', config) + self.assertIn(f'dev-type tun', config) + self.assertIn(f'persist-key', config) + self.assertIn(f'proto udp', config) # default protocol + self.assertIn(f'auth {auth_hash}', config) + self.assertIn(f'data-ciphers AES-192-CBC', config) + self.assertIn(f'topology subnet', config) + self.assertIn(f'lport {port}', config) + self.assertIn(f'push "redirect-gateway def1"', config) + self.assertIn(f'{plugin}', config) + self.assertIn(f'keepalive 5 25', config) + + # TLS options + self.assertIn(f'ca /run/openvpn/{interface}_ca.pem', config) + self.assertIn(f'cert /run/openvpn/{interface}_cert.pem', config) + self.assertIn(f'key /run/openvpn/{interface}_cert.key', config) + self.assertIn(f'dh /run/openvpn/{interface}_dh.pem', config) + + # IP pool configuration + netmask = IPv4Network(subnet).netmask + network = IPv4Network(subnet).network_address + self.assertIn(f'server {network} {netmask}', config) + + # Verify client + client_config = read_file(client_config_file) + + self.assertIn(f'ifconfig-push {client_ip} {client_netmask}', client_config) + for route in client1_routes: + self.assertIn('iroute {} {}'.format(address_from_cidr(route), netmask_from_cidr(route)), client_config) + + self.assertTrue(process_named_running(PROCESS_NAME)) + self.assertEqual(get_vrf(interface), vrf_name) + self.assertIn(interface, interfaces()) + + # check that no interface remained after deleting them + self.cli_delete(base_path) + self.cli_commit() + + for ii in num_range: + interface = f'vtun{ii}' + self.assertNotIn(interface, interfaces()) + + def test_openvpn_server_ip_version(self): + # Test the server mode behavior combined with each IP protocol version + + auth_hash = 'sha256' + port = '2000' + + interface = 'vtun20' + subnet = '192.0.20.0/24' + path = base_path + [interface] + + # Default behavior: client uses uspecified protocol version (udp) + self.cli_set(path + ['device-type', 'tun']) + self.cli_set(path + ['encryption', 'data-ciphers', 'aes192']) + self.cli_set(path + ['hash', auth_hash]) + self.cli_set(path + ['mode', 'server']) + self.cli_set(path + ['local-port', port]) + self.cli_set(path + ['server', 'subnet', subnet]) + self.cli_set(path + ['server', 'topology', 'subnet']) + + self.cli_set(path + ['replace-default-route']) + self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test']) + self.cli_set(path + ['tls', 'certificate', 'ovpn_test']) + self.cli_set(path + ['tls', 'dh-params', 'ovpn_test']) + + self.cli_commit() + + start_addr = inc_ip(subnet, '2') + stop_addr = last_host_address(subnet) + + config_file = f'/run/openvpn/{interface}.conf' + config = read_file(config_file) + + self.assertIn(f'dev {interface}', config) + self.assertIn(f'dev-type tun', config) + self.assertIn(f'persist-key', config) + self.assertIn(f'proto udp', config) # default protocol + self.assertIn(f'auth {auth_hash}', config) + self.assertIn(f'data-ciphers AES-192-CBC', config) + self.assertIn(f'topology subnet', config) + self.assertIn(f'lport {port}', config) + self.assertIn(f'push "redirect-gateway def1"', config) + + # IPv4 only: server usees udp4 protocol + self.cli_set(path + ['ip-version', 'ipv4']) + self.cli_commit() + + config = read_file(config_file) + self.assertIn(f'proto udp4', config) + + # IPv6 only: server uses udp6 protocol + bind ipv6only + self.cli_set(path + ['ip-version', 'ipv6']) + self.cli_commit() + + config = read_file(config_file) + self.assertIn(f'proto udp6', config) + self.assertIn(f'bind ipv6only', config) + + # IPv6 dual-stack: server uses udp6 protocol without bind ipv6only + self.cli_set(path + ['ip-version', 'dual-stack']) + self.cli_commit() + + config = read_file(config_file) + self.assertIn(f'proto udp6', config) + self.assertNotIn(f'bind ipv6only', config) + + self.cli_delete(base_path) + self.cli_commit() + + def test_openvpn_site2site_verify(self): + # Create one OpenVPN site2site interface and check required + # verify() stages + + interface = 'vtun5000' + path = base_path + [interface] + + self.cli_set(path + ['mode', 'site-to-site']) + + # check validate() - cipher negotiation cannot be enabled in site-to-site mode + self.cli_set(path + ['encryption', 'data-ciphers', 'aes192gcm']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['encryption']) + + # check validate() - must specify "local-address" or add interface to bridge + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(path + ['local-address', '10.0.0.1']) + self.cli_set(path + ['local-address', '2001:db8:1::1']) + + # check validate() - cannot specify more than 1 IPv4 local-address + self.cli_set(path + ['local-address', '10.0.0.2']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['local-address', '10.0.0.2']) + + # check validate() - cannot specify more than 1 IPv6 local-address + self.cli_set(path + ['local-address', '2001:db8:1::2']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['local-address', '2001:db8:1::2']) + + # check validate() - IPv4 "local-address" requires IPv4 "remote-address" + # or IPv4 "local-address subnet" + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(path + ['remote-address', '192.168.0.1']) + self.cli_set(path + ['remote-address', '2001:db8:ffff::1']) + + # check validate() - Cannot specify more than 1 IPv4 "remote-address" + self.cli_set(path + ['remote-address', '192.168.0.2']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['remote-address', '192.168.0.2']) + + # check validate() - Cannot specify more than 1 IPv6 "remote-address" + self.cli_set(path + ['remote-address', '2001:db8:ffff::2']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['remote-address', '2001:db8:ffff::2']) + + # check validate() - Must specify one of "shared-secret-key" and "tls" + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(path + ['shared-secret-key', 'ovpn_test']) + + self.cli_commit() + + def test_openvpn_options(self): + # Ensure OpenVPN process restart on openvpn-option CLI node change + + interface = 'vtun5001' + path = base_path + [interface] + encryption_cipher = 'aes256' + + self.cli_set(path + ['mode', 'site-to-site']) + self.cli_set(path + ['local-address', '10.0.0.2']) + self.cli_set(path + ['remote-address', '192.168.0.3']) + self.cli_set(path + ['shared-secret-key', 'ovpn_test']) + self.cli_set(path + ['encryption', 'cipher', encryption_cipher]) + + self.cli_commit() + + # Now verify the OpenVPN "raw" option passing. Once an openvpn-option is + # added, modified or deleted from the CLI, OpenVPN daemon must be restarted + cur_pid = process_named_running('openvpn') + self.cli_set(path + ['openvpn-option', '--persist-tun']) + self.cli_commit() + + # PID must be different as OpenVPN Must be restarted + new_pid = process_named_running('openvpn') + self.assertNotEqual(cur_pid, new_pid) + cur_pid = new_pid + + self.cli_set(path + ['openvpn-option', '--persist-key']) + self.cli_commit() + + # PID must be different as OpenVPN Must be restarted + new_pid = process_named_running('openvpn') + self.assertNotEqual(cur_pid, new_pid) + cur_pid = new_pid + + self.cli_delete(path + ['openvpn-option']) + self.cli_commit() + + # PID must be different as OpenVPN Must be restarted + new_pid = process_named_running('openvpn') + self.assertNotEqual(cur_pid, new_pid) + cur_pid = new_pid + + def test_openvpn_site2site_interfaces_tun(self): + # Create two OpenVPN site-to-site interfaces + + num_range = range(30, 35) + port = '' + local_address = '' + remote_address = '' + encryption_cipher = 'aes256' + + for ii in num_range: + interface = f'vtun{ii}' + local_address = f'192.0.{ii}.1' + local_address_subnet = '255.255.255.252' + remote_address = f'172.16.{ii}.1' + path = base_path + [interface] + port = str(3000 + ii) + + self.cli_set(path + ['local-address', local_address]) + + # even numbers use tun type, odd numbers use tap type + if ii % 2 == 0: + self.cli_set(path + ['device-type', 'tun']) + else: + self.cli_set(path + ['device-type', 'tap']) + self.cli_set(path + ['local-address', local_address, 'subnet-mask', local_address_subnet]) + + self.cli_set(path + ['mode', 'site-to-site']) + self.cli_set(path + ['local-port', port]) + self.cli_set(path + ['remote-port', port]) + self.cli_set(path + ['shared-secret-key', 'ovpn_test']) + self.cli_set(path + ['remote-address', remote_address]) + self.cli_set(path + ['encryption', 'cipher', encryption_cipher]) + self.cli_set(path + ['vrf', vrf_name]) + + self.cli_commit() + + for ii in num_range: + interface = f'vtun{ii}' + local_address = f'192.0.{ii}.1' + remote_address = f'172.16.{ii}.1' + port = str(3000 + ii) + + config_file = f'/run/openvpn/{interface}.conf' + config = read_file(config_file) + + # even numbers use tun type, odd numbers use tap type + if ii % 2 == 0: + self.assertIn(f'dev-type tun', config) + self.assertIn(f'ifconfig {local_address} {remote_address}', config) + else: + self.assertIn(f'dev-type tap', config) + self.assertIn(f'ifconfig {local_address} {local_address_subnet}', config) + + self.assertIn(f'dev {interface}', config) + self.assertIn(f'secret /run/openvpn/{interface}_shared.key', config) + self.assertIn(f'lport {port}', config) + self.assertIn(f'rport {port}', config) + + + self.assertTrue(process_named_running(PROCESS_NAME)) + self.assertEqual(get_vrf(interface), vrf_name) + self.assertIn(interface, interfaces()) + + + # check that no interface remained after deleting them + self.cli_delete(base_path) + self.cli_commit() + + for ii in num_range: + interface = f'vtun{ii}' + self.assertNotIn(interface, interfaces()) + + + def test_openvpn_site2site_ip_version(self): + # Test the site-to-site mode behavior combined with each IP protocol version + + encryption_cipher = 'aes256' + + interface = 'vtun30' + local_address = '192.0.30.1' + local_address_subnet = '255.255.255.252' + remote_address = '172.16.30.1' + path = base_path + [interface] + port = '3030' + + self.cli_set(path + ['local-address', local_address]) + self.cli_set(path + ['device-type', 'tun']) + self.cli_set(path + ['mode', 'site-to-site']) + self.cli_set(path + ['local-port', port]) + self.cli_set(path + ['remote-port', port]) + self.cli_set(path + ['shared-secret-key', 'ovpn_test']) + self.cli_set(path + ['remote-address', remote_address]) + self.cli_set(path + ['encryption', 'cipher', encryption_cipher]) + + self.cli_commit() + + config_file = f'/run/openvpn/{interface}.conf' + config = read_file(config_file) + + self.assertIn(f'dev-type tun', config) + self.assertIn(f'ifconfig {local_address} {remote_address}', config) + self.assertIn(f'proto udp', config) + self.assertIn(f'dev {interface}', config) + self.assertIn(f'secret /run/openvpn/{interface}_shared.key', config) + self.assertIn(f'lport {port}', config) + self.assertIn(f'rport {port}', config) + + # IPv4 only: server usees udp4 protocol + self.cli_set(path + ['ip-version', 'ipv4']) + self.cli_commit() + + config = read_file(config_file) + self.assertIn(f'proto udp4', config) + + # IPv6 only: server uses udp6 protocol + bind ipv6only + self.cli_set(path + ['ip-version', 'ipv6']) + self.cli_commit() + + config = read_file(config_file) + self.assertIn(f'proto udp6', config) + self.assertIn(f'bind ipv6only', config) + + # IPv6 dual-stack: not allowed in site-to-site mode + self.cli_set(path + ['ip-version', 'dual-stack']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete(base_path) + self.cli_commit() + + def test_openvpn_server_server_bridge(self): + # Create OpenVPN server interface using bridge. + # Validate configuration afterwards. + br_if = 'br0' + vtun_if = 'vtun5010' + auth_hash = 'sha256' + path = base_path + [vtun_if] + start_subnet = "192.168.0.100" + stop_subnet = "192.168.0.200" + mask_subnet = "255.255.255.0" + gw_subnet = "192.168.0.1" + + self.cli_set(['interfaces', 'bridge', br_if, 'member', 'interface', vtun_if]) + self.cli_set(path + ['device-type', 'tap']) + self.cli_set(path + ['encryption', 'data-ciphers', 'aes192']) + self.cli_set(path + ['hash', auth_hash]) + self.cli_set(path + ['mode', 'server']) + self.cli_set(path + ['server', 'bridge', 'gateway', gw_subnet]) + self.cli_set(path + ['server', 'bridge', 'start', start_subnet]) + self.cli_set(path + ['server', 'bridge', 'stop', stop_subnet]) + self.cli_set(path + ['server', 'bridge', 'subnet-mask', mask_subnet]) + self.cli_set(path + ['keep-alive', 'failure-count', '5']) + self.cli_set(path + ['keep-alive', 'interval', '5']) + self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test']) + self.cli_set(path + ['tls', 'certificate', 'ovpn_test']) + self.cli_set(path + ['tls', 'dh-params', 'ovpn_test']) + + self.cli_commit() + + config_file = f'/run/openvpn/{vtun_if}.conf' + config = read_file(config_file) + self.assertIn(f'dev {vtun_if}', config) + self.assertIn(f'dev-type tap', config) + self.assertIn(f'proto udp', config) # default protocol + self.assertIn(f'auth {auth_hash}', config) + self.assertIn(f'data-ciphers AES-192-CBC', config) + self.assertIn(f'mode server', config) + self.assertIn(f'server-bridge {gw_subnet} {mask_subnet} {start_subnet} {stop_subnet}', config) + self.assertIn(f'keepalive 5 25', config) + + # TLS options + self.assertIn(f'ca /run/openvpn/{vtun_if}_ca.pem', config) + self.assertIn(f'cert /run/openvpn/{vtun_if}_cert.pem', config) + self.assertIn(f'key /run/openvpn/{vtun_if}_cert.key', config) + self.assertIn(f'dh /run/openvpn/{vtun_if}_dh.pem', config) + + # check that no interface remained after deleting them + self.cli_delete(['interfaces', 'bridge', br_if, 'member', 'interface', vtun_if]) + self.cli_delete(base_path) + self.cli_commit() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_pppoe.py b/smoketest/scripts/cli/test_interfaces_pppoe.py new file mode 100644 index 0000000..2683a31 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_pppoe.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from psutil import process_iter +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.xml_ref import default_value + +config_file = '/etc/ppp/peers/{}' +base_path = ['interfaces', 'pppoe'] + +def get_config_value(interface, key): + with open(config_file.format(interface), 'r') as f: + for line in f: + if line.startswith(key): + return list(line.split()) + return [] + +# add a classmethod to setup a temporaray PPPoE server for "proper" validation +class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(PPPoEInterfaceTest, cls).setUpClass() + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + cls._interfaces = ['pppoe10', 'pppoe20', 'pppoe30'] + cls._source_interface = 'eth0' + + def tearDown(self): + # Validate PPPoE client process + for interface in self._interfaces: + running = False + for proc in process_iter(): + if interface in proc.cmdline(): + running = True + break + self.assertTrue(running) + + self.cli_delete(base_path) + self.cli_commit() + + def test_pppoe_client(self): + # Check if PPPoE dialer can be configured and runs + for interface in self._interfaces: + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' + mtu = '1400' + + self.cli_set(base_path + [interface, 'authentication', 'username', user]) + self.cli_set(base_path + [interface, 'authentication', 'password', passwd]) + self.cli_set(base_path + [interface, 'mtu', mtu]) + self.cli_set(base_path + [interface, 'no-peer-dns']) + + # check validate() - a source-interface is required + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) + + # commit changes + self.cli_commit() + + # verify configuration file(s) + for interface in self._interfaces: + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' + + tmp = get_config_value(interface, 'mtu')[1] + self.assertEqual(tmp, mtu) + # MRU must default to MTU if not specified on CLI + tmp = get_config_value(interface, 'mru')[1] + self.assertEqual(tmp, mtu) + tmp = get_config_value(interface, 'user')[1].replace('"', '') + self.assertEqual(tmp, user) + tmp = get_config_value(interface, 'password')[1].replace('"', '') + self.assertEqual(tmp, passwd) + tmp = get_config_value(interface, 'ifname')[1] + self.assertEqual(tmp, interface) + + def test_pppoe_client_disabled_interface(self): + # Check if PPPoE Client can be disabled + for interface in self._interfaces: + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' + + self.cli_set(base_path + [interface, 'authentication', 'username', user]) + self.cli_set(base_path + [interface, 'authentication', 'password', passwd]) + self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) + self.cli_set(base_path + [interface, 'disable']) + + self.cli_commit() + + # Validate PPPoE client process - must not run as interfaces are disabled + for interface in self._interfaces: + running = False + for proc in process_iter(): + if interface in proc.cmdline(): + running = True + break + self.assertFalse(running) + + # enable PPPoE interfaces + for interface in self._interfaces: + self.cli_delete(base_path + [interface, 'disable']) + + self.cli_commit() + + + def test_pppoe_authentication(self): + # When username or password is set - so must be the other + for interface in self._interfaces: + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' + + self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) + self.cli_set(base_path + [interface, 'ipv6', 'address', 'autoconf']) + + self.cli_set(base_path + [interface, 'authentication', 'username', user]) + # check validate() - if user is set, so must be the password + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + [interface, 'authentication', 'password', passwd]) + + self.cli_commit() + + def test_pppoe_dhcpv6pd(self): + # Check if PPPoE dialer can be configured with DHCPv6-PD + address = '1' + sla_id = '0' + sla_len = '8' + + for interface in self._interfaces: + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' + + self.cli_set(base_path + [interface, 'authentication', 'username', user]) + self.cli_set(base_path + [interface, 'authentication', 'password', passwd]) + self.cli_set(base_path + [interface, 'no-default-route']) + self.cli_set(base_path + [interface, 'no-peer-dns']) + self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) + self.cli_set(base_path + [interface, 'ipv6', 'address', 'autoconf']) + + # prefix delegation stuff + dhcpv6_pd_base = base_path + [interface, 'dhcpv6-options', 'pd', '0'] + self.cli_set(dhcpv6_pd_base + ['length', '56']) + self.cli_set(dhcpv6_pd_base + ['interface', self._source_interface, 'address', address]) + self.cli_set(dhcpv6_pd_base + ['interface', self._source_interface, 'sla-id', sla_id]) + + # commit changes + self.cli_commit() + + for interface in self._interfaces: + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' + mtu_default = default_value(base_path + [interface, 'mtu']) + + tmp = get_config_value(interface, 'mtu')[1] + self.assertEqual(tmp, mtu_default) + tmp = get_config_value(interface, 'user')[1].replace('"', '') + self.assertEqual(tmp, user) + tmp = get_config_value(interface, 'password')[1].replace('"', '') + self.assertEqual(tmp, passwd) + tmp = get_config_value(interface, '+ipv6 ipv6cp-use-ipaddr') + self.assertListEqual(tmp, ['+ipv6', 'ipv6cp-use-ipaddr']) + + def test_pppoe_options(self): + # Check if PPPoE dialer can be configured with DHCPv6-PD + for interface in self._interfaces: + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' + ac_name = f'AC{interface}' + service_name = f'SRV{interface}' + host_uniq = 'cafebeefBABE123456' + + self.cli_set(base_path + [interface, 'authentication', 'username', user]) + self.cli_set(base_path + [interface, 'authentication', 'password', passwd]) + self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) + + self.cli_set(base_path + [interface, 'access-concentrator', ac_name]) + self.cli_set(base_path + [interface, 'service-name', service_name]) + self.cli_set(base_path + [interface, 'host-uniq', host_uniq]) + + # commit changes + self.cli_commit() + + for interface in self._interfaces: + ac_name = f'AC{interface}' + service_name = f'SRV{interface}' + host_uniq = 'cafebeefBABE123456' + + tmp = get_config_value(interface, 'pppoe-ac')[1] + self.assertEqual(tmp, f'"{ac_name}"') + tmp = get_config_value(interface, 'pppoe-service')[1] + self.assertEqual(tmp, f'"{service_name}"') + tmp = get_config_value(interface, 'pppoe-host-uniq')[1] + self.assertEqual(tmp, f'"{host_uniq}"') + + def test_pppoe_mtu_mru(self): + # Check if PPPoE dialer can be configured and runs + for interface in self._interfaces: + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' + mtu = '1400' + mru = '1300' + + self.cli_set(base_path + [interface, 'authentication', 'username', user]) + self.cli_set(base_path + [interface, 'authentication', 'password', passwd]) + self.cli_set(base_path + [interface, 'mtu', mtu]) + self.cli_set(base_path + [interface, 'mru', '9000']) + + # check validate() - a source-interface is required + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) + + # check validate() - MRU needs to be less or equal then MTU + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + [interface, 'mru', mru]) + + # commit changes + self.cli_commit() + + # verify configuration file(s) + for interface in self._interfaces: + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' + + tmp = get_config_value(interface, 'mtu')[1] + self.assertEqual(tmp, mtu) + tmp = get_config_value(interface, 'mru')[1] + self.assertEqual(tmp, mru) + tmp = get_config_value(interface, 'user')[1].replace('"', '') + self.assertEqual(tmp, user) + tmp = get_config_value(interface, 'password')[1].replace('"', '') + self.assertEqual(tmp, passwd) + tmp = get_config_value(interface, 'ifname')[1] + self.assertEqual(tmp, interface) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_pseudo-ethernet.py b/smoketest/scripts/cli/test_interfaces_pseudo-ethernet.py new file mode 100644 index 0000000..0d6f5bc --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_pseudo-ethernet.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from vyos.ifconfig import Section +from base_interfaces_test import BasicInterfaceTest + +class PEthInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'pseudo-ethernet'] + + cls._options = {} + # we need to filter out VLAN interfaces identified by a dot (.) + # in their name - just in case! + if 'TEST_ETH' in os.environ: + for tmp in os.environ['TEST_ETH'].split(): + cls._options.update({f'p{tmp}' : [f'source-interface {tmp}']}) + + else: + for tmp in Section.interfaces('ethernet'): + if '.' in tmp: + continue + cls._options.update({f'p{tmp}' : [f'source-interface {tmp}']}) + + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(PEthInterfaceTest, cls).setUpClass() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py new file mode 100644 index 0000000..dd9f1d2 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_tunnel.py @@ -0,0 +1,413 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_interfaces_test import BasicInterfaceTest + +from vyos.configsession import ConfigSessionError +from vyos.utils.network import get_interface_config +from vyos.template import inc_ip + +remote_ip4 = '192.0.2.100' +remote_ip6 = '2001:db8::ffff' +source_if = 'dum2222' +mtu = 1476 + +class TunnelInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'tunnel'] + cls.local_v4 = '192.0.2.1' + cls.local_v6 = '2001:db8::1' + cls._options = { + 'tun10': ['encapsulation ipip', 'remote 192.0.2.10', 'source-address ' + cls.local_v4], + 'tun20': ['encapsulation gre', 'remote 192.0.2.20', 'source-address ' + cls.local_v4], + } + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(TunnelInterfaceTest, cls).setUpClass() + + # create some test interfaces + cls.cli_set(cls, ['interfaces', 'dummy', source_if, 'address', cls.local_v4 + '/32']) + cls.cli_set(cls, ['interfaces', 'dummy', source_if, 'address', cls.local_v6 + '/128']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'dummy', source_if]) + super().tearDownClass() + + def test_ipv4_encapsulations(self): + # When running tests ensure that for certain encapsulation types the + # local and remote IP address is actually an IPv4 address + + interface = f'tun1000' + local_if_addr = f'10.10.200.1/24' + for encapsulation in ['ipip', 'sit', 'gre', 'gretap']: + self.cli_set(self._base_path + [interface, 'address', local_if_addr]) + self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.cli_set(self._base_path + [interface, 'source-address', self.local_v6]) + self.cli_set(self._base_path + [interface, 'remote', remote_ip6]) + + # Encapsulation mode requires IPv4 source-address + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'source-address', self.local_v4]) + + # Encapsulation mode requires IPv4 remote + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'remote', remote_ip4]) + self.cli_set(self._base_path + [interface, 'source-interface', source_if]) + + # Source interface can not be used with sit and gretap + if encapsulation in ['sit', 'gretap']: + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'source-interface']) + + # Check if commit is ok + self.cli_commit() + + conf = get_interface_config(interface) + if encapsulation not in ['sit', 'gretap']: + self.assertEqual(source_if, conf['link']) + + self.assertEqual(interface, conf['ifname']) + self.assertEqual(mtu, conf['mtu']) + self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) + self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) + self.assertTrue(conf['linkinfo']['info_data']['pmtudisc']) + + # cleanup this instance + self.cli_delete(self._base_path + [interface]) + self.cli_commit() + + def test_ipv6_encapsulations(self): + # When running tests ensure that for certain encapsulation types the + # local and remote IP address is actually an IPv6 address + + interface = f'tun1010' + local_if_addr = f'10.10.200.1/24' + for encapsulation in ['ipip6', 'ip6ip6', 'ip6gre', 'ip6gretap']: + self.cli_set(self._base_path + [interface, 'address', local_if_addr]) + self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.cli_set(self._base_path + [interface, 'source-address', self.local_v4]) + self.cli_set(self._base_path + [interface, 'remote', remote_ip4]) + + # Encapsulation mode requires IPv6 source-address + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'source-address', self.local_v6]) + + # Encapsulation mode requires IPv6 remote + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'remote', remote_ip6]) + + # Configure Tunnel Source interface + self.cli_set(self._base_path + [interface, 'source-interface', source_if]) + # Source interface can not be used with ip6gretap + if encapsulation in ['ip6gretap']: + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'source-interface']) + + # Check if commit is ok + self.cli_commit() + + conf = get_interface_config(interface) + if encapsulation not in ['ip6gretap']: + self.assertEqual(source_if, conf['link']) + + self.assertEqual(interface, conf['ifname']) + self.assertEqual(mtu, conf['mtu']) + + # Not applicable for ip6gre + if 'proto' in conf['linkinfo']['info_data']: + self.assertEqual(encapsulation, conf['linkinfo']['info_data']['proto']) + + # remap encapsulation protocol(s) only for ipip6, ip6ip6 + if encapsulation in ['ipip6', 'ip6ip6']: + encapsulation = 'ip6tnl' + + self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) + self.assertEqual(self.local_v6, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip6, conf['linkinfo']['info_data']['remote']) + + # cleanup this instance + self.cli_delete(self._base_path + [interface]) + self.cli_commit() + + def test_tunnel_parameters_gre(self): + interface = f'tun1030' + gre_key = '10' + encapsulation = 'gre' + tos = '20' + + self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.cli_set(self._base_path + [interface, 'source-address', self.local_v4]) + self.cli_set(self._base_path + [interface, 'remote', remote_ip4]) + + self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'no-pmtu-discovery']) + self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'key', gre_key]) + self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'tos', tos]) + self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'ttl', '0']) + + # Check if commit is ok + self.cli_commit() + + conf = get_interface_config(interface) + self.assertEqual(mtu, conf['mtu']) + self.assertEqual(interface, conf['ifname']) + self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) + self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) + self.assertEqual(0, conf['linkinfo']['info_data']['ttl']) + self.assertFalse( conf['linkinfo']['info_data']['pmtudisc']) + + def test_gretap_parameters_change(self): + interface = f'tun1040' + gre_key = '10' + encapsulation = 'gretap' + tos = '20' + + self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.cli_set(self._base_path + [interface, 'source-address', self.local_v4]) + self.cli_set(self._base_path + [interface, 'remote', remote_ip4]) + + # Check if commit is ok + self.cli_commit() + + conf = get_interface_config(interface) + self.assertEqual(mtu, conf['mtu']) + self.assertEqual(interface, conf['ifname']) + self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) + self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) + self.assertEqual(64, conf['linkinfo']['info_data']['ttl']) + + # Change remote ip address (inc host by 2 + new_remote = inc_ip(remote_ip4, 2) + self.cli_set(self._base_path + [interface, 'remote', new_remote]) + # Check if commit is ok + self.cli_commit() + + conf = get_interface_config(interface) + self.assertEqual(new_remote, conf['linkinfo']['info_data']['remote']) + + def test_erspan_v1(self): + interface = f'tun1070' + encapsulation = 'erspan' + ip_key = '77' + idx = '20' + + self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.cli_set(self._base_path + [interface, 'source-address', self.local_v4]) + self.cli_set(self._base_path + [interface, 'remote', remote_ip4]) + + self.cli_set(self._base_path + [interface, 'parameters', 'erspan', 'index', idx]) + + # ERSPAN requires ip key parameter + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'key', ip_key]) + + # Check if commit is ok + self.cli_commit() + + conf = get_interface_config(interface) + self.assertEqual(mtu, conf['mtu']) + self.assertEqual(interface, conf['ifname']) + self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) + self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) + self.assertEqual(64, conf['linkinfo']['info_data']['ttl']) + self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['ikey']) + self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['okey']) + self.assertEqual(int(idx), conf['linkinfo']['info_data']['erspan_index']) + # version defaults to 1 + self.assertEqual(1, conf['linkinfo']['info_data']['erspan_ver']) + self.assertTrue( conf['linkinfo']['info_data']['iseq']) + self.assertTrue( conf['linkinfo']['info_data']['oseq']) + + # Change remote ip address (inc host by 2 + new_remote = inc_ip(remote_ip4, 2) + self.cli_set(self._base_path + [interface, 'remote', new_remote]) + # Check if commit is ok + self.cli_commit() + + conf = get_interface_config(interface) + self.assertEqual(new_remote, conf['linkinfo']['info_data']['remote']) + + def test_ip6erspan_v2(self): + interface = f'tun1070' + encapsulation = 'ip6erspan' + ip_key = '77' + erspan_ver = 2 + direction = 'ingress' + + self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.cli_set(self._base_path + [interface, 'source-address', self.local_v6]) + self.cli_set(self._base_path + [interface, 'remote', remote_ip6]) + + # ERSPAN requires ip key parameter + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'key', ip_key]) + + self.cli_set(self._base_path + [interface, 'parameters', 'erspan', 'version', str(erspan_ver)]) + + # ERSPAN index is not valid on version 2 + self.cli_set(self._base_path + [interface, 'parameters', 'erspan', 'index', '10']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'parameters', 'erspan', 'index']) + + # ERSPAN requires direction to be set + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'parameters', 'erspan', 'direction', direction]) + + # Check if commit is ok + self.cli_commit() + + conf = get_interface_config(interface) + self.assertEqual(mtu, conf['mtu']) + self.assertEqual(interface, conf['ifname']) + self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) + self.assertEqual(self.local_v6, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip6, conf['linkinfo']['info_data']['remote']) + self.assertEqual(64, conf['linkinfo']['info_data']['ttl']) + self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['ikey']) + self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['okey']) + self.assertEqual(erspan_ver, conf['linkinfo']['info_data']['erspan_ver']) + self.assertEqual(direction, conf['linkinfo']['info_data']['erspan_dir']) + self.assertTrue( conf['linkinfo']['info_data']['iseq']) + self.assertTrue( conf['linkinfo']['info_data']['oseq']) + + # Change remote ip address (inc host by 2 + new_remote = inc_ip(remote_ip6, 2) + self.cli_set(self._base_path + [interface, 'remote', new_remote]) + # Check if commit is ok + self.cli_commit() + + conf = get_interface_config(interface) + self.assertEqual(new_remote, conf['linkinfo']['info_data']['remote']) + + def test_tunnel_src_any_gre_key(self): + interface = f'tun1280' + encapsulation = 'gre' + src_addr = '0.0.0.0' + key = '127' + + self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.cli_set(self._base_path + [interface, 'source-address', src_addr]) + # GRE key must be supplied with a 0.0.0.0 source address + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'key', key]) + + self.cli_commit() + + def test_multiple_gre_tunnel_same_remote(self): + tunnels = { + 'tun10' : { + 'encapsulation' : 'gre', + 'source_interface' : source_if, + 'remote' : '1.2.3.4', + }, + 'tun20' : { + 'encapsulation' : 'gre', + 'source_interface' : source_if, + 'remote' : '1.2.3.4', + }, + } + + for tunnel, tunnel_config in tunnels.items(): + self.cli_set(self._base_path + [tunnel, 'encapsulation', tunnel_config['encapsulation']]) + if 'source_interface' in tunnel_config: + self.cli_set(self._base_path + [tunnel, 'source-interface', tunnel_config['source_interface']]) + if 'remote' in tunnel_config: + self.cli_set(self._base_path + [tunnel, 'remote', tunnel_config['remote']]) + + # GRE key must be supplied when two or more tunnels are formed to the same desitnation + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for tunnel, tunnel_config in tunnels.items(): + self.cli_set(self._base_path + [tunnel, 'parameters', 'ip', 'key', tunnel.lstrip('tun')]) + + self.cli_commit() + + for tunnel, tunnel_config in tunnels.items(): + conf = get_interface_config(tunnel) + ip_key = tunnel.lstrip('tun') + + self.assertEqual(tunnel_config['source_interface'], conf['link']) + self.assertEqual(tunnel_config['encapsulation'], conf['linkinfo']['info_kind']) + self.assertEqual(tunnel_config['remote'], conf['linkinfo']['info_data']['remote']) + self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['ikey']) + self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['okey']) + + def test_multiple_gre_tunnel_different_remote(self): + tunnels = { + 'tun10' : { + 'encapsulation' : 'gre', + 'source_interface' : source_if, + 'remote' : '1.2.3.4', + }, + 'tun20' : { + 'encapsulation' : 'gre', + 'source_interface' : source_if, + 'remote' : '1.2.3.5', + }, + } + + for tunnel, tunnel_config in tunnels.items(): + self.cli_set(self._base_path + [tunnel, 'encapsulation', tunnel_config['encapsulation']]) + if 'source_interface' in tunnel_config: + self.cli_set(self._base_path + [tunnel, 'source-interface', tunnel_config['source_interface']]) + if 'remote' in tunnel_config: + self.cli_set(self._base_path + [tunnel, 'remote', tunnel_config['remote']]) + + self.cli_commit() + + for tunnel, tunnel_config in tunnels.items(): + conf = get_interface_config(tunnel) + + self.assertEqual(tunnel_config['source_interface'], conf['link']) + self.assertEqual(tunnel_config['encapsulation'], conf['linkinfo']['info_kind']) + self.assertEqual(tunnel_config['remote'], conf['linkinfo']['info_data']['remote']) + + def test_tunnel_invalid_source_interface(self): + encapsulation = 'gre' + remote = '192.0.2.1' + interface = 'tun7543' + + self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.cli_set(self._base_path + [interface, 'remote', remote]) + + for dynamic_interface in ['l2tp0', 'ppp4220', 'sstpc0', 'ipoe654']: + self.cli_set(self._base_path + [interface, 'source-interface', dynamic_interface]) + # verify() - we can not source from dynamic interfaces + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'source-interface', 'eth0']) + self.cli_commit() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_virtual-ethernet.py b/smoketest/scripts/cli/test_interfaces_virtual-ethernet.py new file mode 100644 index 0000000..c6a4613 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_virtual-ethernet.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from netifaces import interfaces + +from vyos.utils.process import process_named_running +from base_interfaces_test import BasicInterfaceTest + +class VEthInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'virtual-ethernet'] + cls._options = { + 'veth0': ['peer-name veth1'], + 'veth1': ['peer-name veth0'], + } + + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(VEthInterfaceTest, cls).setUpClass() + + def test_vif_8021q_mtu_limits(self): + self.skipTest('not supported') + + # As we always need a pair of veth interfaces, we can not rely on the base + # class check to determine if there is a dhcp6c or dhclient instance running. + # This test will always fail as there is an instance running on the peer + # interface. + def tearDown(self): + self.cli_delete(self._base_path) + self.cli_commit() + + # Verify that no previously interface remained on the system + for intf in self._interfaces: + self.assertNotIn(intf, interfaces()) + + @classmethod + def tearDownClass(cls): + # No daemon started during tests should remain running + for daemon in ['dhcp6c', 'dhclient']: + cls.assertFalse(cls, process_named_running(daemon)) + + super(VEthInterfaceTest, cls).tearDownClass() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_vti.py b/smoketest/scripts/cli/test_interfaces_vti.py new file mode 100644 index 0000000..8d90ca5 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_vti.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_interfaces_test import BasicInterfaceTest + +from vyos.ifconfig import Interface +from vyos.utils.network import is_intf_addr_assigned + +class VTIInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'vti'] + cls._interfaces = ['vti10', 'vti20', 'vti30'] + + # call base-classes classmethod + super(VTIInterfaceTest, cls).setUpClass() + + def test_add_single_ip_address(self): + addr = '192.0.2.0/31' + for intf in self._interfaces: + self.cli_set(self._base_path + [intf, 'address', addr]) + for option in self._options.get(intf, []): + self.cli_set(self._base_path + [intf] + option.split()) + + self.cli_commit() + + # VTI interfaces are default down and only brought up when an + # IPSec connection is configured to use them + for intf in self._interfaces: + self.assertTrue(is_intf_addr_assigned(intf, addr)) + self.assertEqual(Interface(intf).get_admin_state(), 'down') + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py new file mode 100644 index 0000000..b2076b4 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_vxlan.py @@ -0,0 +1,366 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Interface +from vyos.ifconfig import Section +from vyos.utils.network import get_bridge_fdb +from vyos.utils.network import get_interface_config +from vyos.utils.network import interface_exists +from vyos.utils.network import get_vxlan_vlan_tunnels +from vyos.utils.network import get_vxlan_vni_filter +from vyos.template import is_ipv6 +from base_interfaces_test import BasicInterfaceTest + +def convert_to_list(ranges_to_convert): + result_list = [] + for r in ranges_to_convert: + ranges = r.split('-') + result_list.extend([str(i) for i in range(int(ranges[0]), int(ranges[1]) + 1)]) + return result_list + +class VXLANInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'vxlan'] + cls._options = { + 'vxlan10': ['vni 10', 'remote 127.0.0.2'], + 'vxlan20': ['vni 20', 'group 239.1.1.1', 'source-interface eth0', 'mtu 1450'], + 'vxlan30': ['vni 30', 'remote 2001:db8:2000::1', 'source-address 2001:db8:1000::1', 'parameters ipv6 flowlabel 0x1000'], + 'vxlan40': ['vni 40', 'remote 127.0.0.2', 'remote 127.0.0.3'], + 'vxlan50': ['vni 50', 'remote 2001:db8:2000::1', 'remote 2001:db8:2000::2', 'parameters ipv6 flowlabel 0x1000'], + } + cls._interfaces = list(cls._options) + cls._mtu = '1450' + # call base-classes classmethod + super(VXLANInterfaceTest, cls).setUpClass() + + def test_vxlan_parameters(self): + tos = '40' + ttl = 20 + for intf in self._interfaces: + for option in self._options.get(intf, []): + self.cli_set(self._base_path + [intf] + option.split()) + + self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'df', 'set']) + self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'tos', tos]) + self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'ttl', str(ttl)]) + ttl += 10 + + self.cli_commit() + + ttl = 20 + for interface in self._interfaces: + options = get_interface_config(interface) + bridge = get_bridge_fdb(interface) + + vni = options['linkinfo']['info_data']['id'] + self.assertIn(f'vni {vni}', self._options[interface]) + + if any('source-interface' in s for s in self._options[interface]): + link = options['linkinfo']['info_data']['link'] + self.assertIn(f'source-interface {link}', self._options[interface]) + + # Verify source-address setting was properly configured on the Kernel + if any('source-address' in s for s in self._options[interface]): + for s in self._options[interface]: + if 'source-address' in s: + address = s.split()[-1] + if is_ipv6(address): + tmp = options['linkinfo']['info_data']['local6'] + else: + tmp = options['linkinfo']['info_data']['local'] + self.assertIn(f'source-address {tmp}', self._options[interface]) + + # Verify remote setting was properly configured on the Kernel + if any('remote' in s for s in self._options[interface]): + for s in self._options[interface]: + if 'remote' in s: + for fdb in bridge: + if 'mac' in fdb and fdb['mac'] == '00:00:00:00:00:00': + remote = fdb['dst'] + self.assertIn(f'remote {remote}', self._options[interface]) + + if any('group' in s for s in self._options[interface]): + group = options['linkinfo']['info_data']['group'] + self.assertIn(f'group {group}', self._options[interface]) + + if any('flowlabel' in s for s in self._options[interface]): + label = options['linkinfo']['info_data']['label'] + self.assertIn(f'parameters ipv6 flowlabel {label}', self._options[interface]) + + if any('external' in s for s in self._options[interface]): + self.assertTrue(options['linkinfo']['info_data']['external']) + + self.assertEqual('vxlan', options['linkinfo']['info_kind']) + self.assertEqual('set', options['linkinfo']['info_data']['df']) + self.assertEqual(f'0x{tos}', options['linkinfo']['info_data']['tos']) + self.assertEqual(ttl, options['linkinfo']['info_data']['ttl']) + self.assertEqual(Interface(interface).get_admin_state(), 'up') + ttl += 10 + + def test_vxlan_external(self): + interface = 'vxlan0' + source_address = '192.0.2.1' + self.cli_set(self._base_path + [interface, 'parameters', 'external']) + self.cli_set(self._base_path + [interface, 'source-address', source_address]) + + # Both 'VNI' and 'external' can not be specified at the same time. + self.cli_set(self._base_path + [interface, 'vni', '111']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'vni']) + + # Now add some more interfaces - this must fail and a CLI error needs + # to be generated as Linux can only handle one VXLAN tunnel when using + # external mode. + for intf in self._interfaces: + for option in self._options.get(intf, []): + self.cli_set(self._base_path + [intf] + option.split()) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # Remove those test interfaces again + for intf in self._interfaces: + self.cli_delete(self._base_path + [intf]) + + self.cli_commit() + + options = get_interface_config(interface) + self.assertTrue(options['linkinfo']['info_data']['external']) + self.assertEqual('vxlan', options['linkinfo']['info_kind']) + + def test_vxlan_vlan_vni_mapping(self): + bridge = 'br0' + interface = 'vxlan0' + source_address = '192.0.2.99' + + vlan_to_vni = { + '10': '10010', + '11': '10011', + '12': '10012', + '13': '10013', + '20': '10020', + '30': '10030', + '31': '10031', + } + + vlan_to_vni_ranges = { + '40-43': '10040-10043', + '45-47': '10045-10047' + } + + self.cli_set(self._base_path + [interface, 'parameters', 'external']) + self.cli_set(self._base_path + [interface, 'source-address', source_address]) + + for vlan, vni in vlan_to_vni.items(): + self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni]) + + # This must fail as this VXLAN interface is not associated with any bridge + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(['interfaces', 'bridge', bridge, 'member', 'interface', interface]) + + # It is not allowed to use duplicate VNIs + self.cli_set(self._base_path + [interface, 'vlan-to-vni', '11', 'vni', vlan_to_vni['10']]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + # restore VLAN - VNI mappings + for vlan, vni in vlan_to_vni.items(): + self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni]) + + # commit configuration + self.cli_commit() + + self.assertTrue(interface_exists(bridge)) + self.assertTrue(interface_exists(interface)) + + tmp = get_interface_config(interface) + self.assertEqual(tmp['master'], bridge) + self.assertFalse(tmp['linkinfo']['info_slave_data']['neigh_suppress']) + + tmp = get_vxlan_vlan_tunnels('vxlan0') + self.assertEqual(tmp, list(vlan_to_vni)) + + # add ranged VLAN - VNI mapping + for vlan, vni in vlan_to_vni_ranges.items(): + self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni]) + self.cli_commit() + + tmp = get_vxlan_vlan_tunnels('vxlan0') + vlans_list = convert_to_list(vlan_to_vni_ranges.keys()) + self.assertEqual(tmp, list(vlan_to_vni) + vlans_list) + + # check validate() - cannot map VNI range to a single VLAN id + self.cli_set(self._base_path + [interface, 'vlan-to-vni', '100', 'vni', '100-102']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'vlan-to-vni', '100']) + + # check validate() - cannot map VLAN to VNI with different ranges + self.cli_set(self._base_path + [interface, 'vlan-to-vni', '100-102', 'vni', '100-105']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete(['interfaces', 'bridge', bridge]) + + def test_vxlan_neighbor_suppress(self): + bridge = 'br555' + interface = 'vxlan555' + source_interface = 'dum0' + + self.cli_set(['interfaces', Section.section(source_interface), source_interface, 'mtu', '9000']) + + self.cli_set(self._base_path + [interface, 'parameters', 'external']) + self.cli_set(self._base_path + [interface, 'source-interface', source_interface]) + self.cli_set(self._base_path + [interface, 'parameters', 'neighbor-suppress']) + + # This must fail as this VXLAN interface is not associated with any bridge + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(['interfaces', 'bridge', bridge, 'member', 'interface', interface]) + + # commit configuration + self.cli_commit() + + self.assertTrue(interface_exists(bridge)) + self.assertTrue(interface_exists(interface)) + + tmp = get_interface_config(interface) + self.assertEqual(tmp['master'], bridge) + self.assertTrue(tmp['linkinfo']['info_slave_data']['neigh_suppress']) + self.assertFalse(tmp['linkinfo']['info_slave_data']['learning']) + + # Remove neighbor suppress configuration and re-test + self.cli_delete(self._base_path + [interface, 'parameters', 'neighbor-suppress']) + # commit configuration + self.cli_commit() + + tmp = get_interface_config(interface) + self.assertEqual(tmp['master'], bridge) + self.assertFalse(tmp['linkinfo']['info_slave_data']['neigh_suppress']) + self.assertTrue(tmp['linkinfo']['info_slave_data']['learning']) + + self.cli_delete(['interfaces', 'bridge', bridge]) + self.cli_delete(['interfaces', Section.section(source_interface), source_interface]) + + def test_vxlan_vni_filter(self): + interfaces = ['vxlan987', 'vxlan986', 'vxlan985'] + source_address = '192.0.2.77' + + for interface in interfaces: + self.cli_set(self._base_path + [interface, 'parameters', 'external']) + self.cli_set(self._base_path + [interface, 'source-address', source_address]) + + # This must fail as there can only be one "external" VXLAN device unless "vni-filter" is defined + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # Enable "vni-filter" on the first VXLAN interface + self.cli_set(self._base_path + [interfaces[0], 'parameters', 'vni-filter']) + + # This must fail as if it's enabled on one VXLAN interface, it must be enabled on all + # VXLAN interfaces + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for interface in interfaces: + self.cli_set(self._base_path + [interface, 'parameters', 'vni-filter']) + + # commit configuration + self.cli_commit() + + for interface in interfaces: + self.assertTrue(interface_exists(interface)) + + tmp = get_interface_config(interface) + self.assertTrue(tmp['linkinfo']['info_data']['vnifilter']) + + def test_vxlan_vni_filter_add_remove(self): + interface = 'vxlan987' + source_address = '192.0.2.66' + bridge = 'br0' + + self.cli_set(self._base_path + [interface, 'parameters', 'external']) + self.cli_set(self._base_path + [interface, 'source-address', source_address]) + self.cli_set(self._base_path + [interface, 'parameters', 'vni-filter']) + + # commit configuration + self.cli_commit() + + # Check if VXLAN interface got created + self.assertTrue(interface_exists(interface)) + + # VNI filter configured? + tmp = get_interface_config(interface) + self.assertTrue(tmp['linkinfo']['info_data']['vnifilter']) + + # Now create some VLAN mappings and VNI filter + vlan_to_vni = { + '50': '10050', + '51': '10051', + '52': '10052', + '53': '10053', + '54': '10054', + '60': '10060', + '69': '10069', + } + + vlan_to_vni_ranges = { + '70-73': '10070-10073', + '75-77': '10075-10077' + } + + for vlan, vni in vlan_to_vni.items(): + self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni]) + # we need a bridge ... + self.cli_set(['interfaces', 'bridge', bridge, 'member', 'interface', interface]) + # commit configuration + self.cli_commit() + + # All VNIs configured? + tmp = get_vxlan_vni_filter(interface) + self.assertListEqual(list(vlan_to_vni.values()), tmp) + + # + # Delete a VLAN mappings and check if all VNIs are properly set up + # + vlan_to_vni.popitem() + self.cli_delete(self._base_path + [interface, 'vlan-to-vni']) + for vlan, vni in vlan_to_vni.items(): + self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni]) + + # commit configuration + self.cli_commit() + + # All VNIs configured? + tmp = get_vxlan_vni_filter(interface) + self.assertListEqual(list(vlan_to_vni.values()), tmp) + + # add ranged VLAN - VNI mapping + for vlan, vni in vlan_to_vni_ranges.items(): + self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni]) + self.cli_commit() + + tmp = get_vxlan_vni_filter(interface) + vnis_list = convert_to_list(vlan_to_vni_ranges.values()) + self.assertListEqual(list(vlan_to_vni.values()) + vnis_list, tmp) + + self.cli_delete(['interfaces', 'bridge', bridge]) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_wireguard.py b/smoketest/scripts/cli/test_interfaces_wireguard.py new file mode 100644 index 0000000..4b994a6 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_wireguard.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError +from vyos.utils.file import read_file +from vyos.utils.process import cmd + +base_path = ['interfaces', 'wireguard'] + +class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(WireGuardInterfaceTest, cls).setUpClass() + + cls._test_addr = ['192.0.2.1/26', '192.0.2.255/31', '192.0.2.64/32', + '2001:db8:1::ffff/64', '2001:db8:101::1/112'] + cls._interfaces = ['wg0', 'wg1'] + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_01_wireguard_peer(self): + # Create WireGuard interfaces with associated peers + for intf in self._interfaces: + peer = 'foo-' + intf + privkey = '6ISOkASm6VhHOOSz/5iIxw+Q9adq9zA17iMM4X40dlc=' + psk = 'u2xdA70hkz0S1CG0dZlOh0aq2orwFXRIVrKo4DCvHgM=' + pubkey = 'n6ZZL7ph/QJUJSUUTyu19c77my1dRCDHkMzFQUO9Z3A=' + + for addr in self._test_addr: + self.cli_set(base_path + [intf, 'address', addr]) + + self.cli_set(base_path + [intf, 'private-key', privkey]) + + self.cli_set(base_path + [intf, 'peer', peer, 'address', '127.0.0.1']) + self.cli_set(base_path + [intf, 'peer', peer, 'port', '1337']) + + # Allow different prefixes to traverse the tunnel + allowed_ips = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'] + for ip in allowed_ips: + self.cli_set(base_path + [intf, 'peer', peer, 'allowed-ips', ip]) + + self.cli_set(base_path + [intf, 'peer', peer, 'preshared-key', psk]) + self.cli_set(base_path + [intf, 'peer', peer, 'public-key', pubkey]) + self.cli_commit() + + self.assertTrue(os.path.isdir(f'/sys/class/net/{intf}')) + + def test_02_wireguard_add_remove_peer(self): + # T2939: Create WireGuard interfaces with associated peers. + # Remove one of the configured peers. + # T4774: Test prevention of duplicate peer public keys + interface = 'wg0' + port = '12345' + privkey = '6ISOkASm6VhHOOSz/5iIxw+Q9adq9zA17iMM4X40dlc=' + pubkey_1 = 'n1CUsmR0M2LUUsyicBd6blZICwUqqWWHbu4ifZ2/9gk=' + pubkey_2 = 'ebFx/1G0ti8tvuZd94sEIosAZZIznX+dBAKG/8DFm0I=' + + self.cli_set(base_path + [interface, 'address', '172.16.0.1/24']) + self.cli_set(base_path + [interface, 'private-key', privkey]) + + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'public-key', pubkey_1]) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'port', port]) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'allowed-ips', '10.205.212.10/32']) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'address', '192.0.2.1']) + + self.cli_set(base_path + [interface, 'peer', 'PEER02', 'public-key', pubkey_1]) + self.cli_set(base_path + [interface, 'peer', 'PEER02', 'port', port]) + self.cli_set(base_path + [interface, 'peer', 'PEER02', 'allowed-ips', '10.205.212.11/32']) + self.cli_set(base_path + [interface, 'peer', 'PEER02', 'address', '192.0.2.2']) + + # Duplicate pubkey_1 + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + [interface, 'peer', 'PEER02', 'public-key', pubkey_2]) + + # Commit peers + self.cli_commit() + + self.assertTrue(os.path.isdir(f'/sys/class/net/{interface}')) + + # Delete second peer + self.cli_delete(base_path + [interface, 'peer', 'PEER01']) + self.cli_commit() + + def test_03_wireguard_same_public_key(self): + # T5413: Test prevention of equality interface public key and peer's + # public key + interface = 'wg0' + port = '12345' + privkey = 'OOjcXGfgQlAuM6q8Z9aAYduCua7pxf7UKYvIqoUPoGQ=' + pubkey_fail = 'eiVeYKq66mqKLbrZLzlckSP9voaw8jSFyVNiNTdZDjU=' + pubkey_ok = 'ebFx/1G0ti8tvuZd94sEIosAZZIznX+dBAKG/8DFm0I=' + + self.cli_set(base_path + [interface, 'address', '172.16.0.1/24']) + self.cli_set(base_path + [interface, 'private-key', privkey]) + + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'public-key', pubkey_fail]) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'port', port]) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'allowed-ips', '10.205.212.10/32']) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'address', '192.0.2.1']) + + # The same pubkey as the interface wg0 + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'public-key', pubkey_ok]) + + # Commit peers + self.cli_commit() + + self.assertTrue(os.path.isdir(f'/sys/class/net/{interface}')) + + def test_04_wireguard_threaded(self): + # T5409: Test adding threaded option on interface. + # Test prevention for adding threaded + # if no enabled peer is configured. + interface = 'wg0' + port = '12345' + privkey = 'OOjcXGfgQlAuM6q8Z9aAYduCua7pxf7UKYvIqoUPoGQ=' + pubkey = 'ebFx/1G0ti8tvuZd94sEIosAZZIznX+dBAKG/8DFm0I=' + + self.cli_set(base_path + [interface, 'address', '172.16.0.1/24']) + self.cli_set(base_path + [interface, 'private-key', privkey]) + + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'port', port]) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'public-key', pubkey]) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'allowed-ips', '10.205.212.10/32']) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'address', '192.0.2.1']) + self.cli_set(base_path + [interface, 'per-client-thread']) + + # Commit peers + self.cli_commit() + tmp = read_file(f'/sys/class/net/{interface}/threaded') + self.assertTrue(tmp, "1") + + def test_05_wireguard_peer_pubkey_change(self): + # T5707 changing WireGuard CLI public key of a peer - it's not removed + + def get_peers(interface) -> list: + tmp = cmd(f'sudo wg show {interface} dump') + first_line = True + peers = [] + for line in tmp.split('\n'): + if not line: + continue # Skip empty lines and last line + items = line.split('\t') + if first_line: + self.assertEqual(privkey, items[0]) + first_line = False + else: + peers.append(items[0]) + return peers + + + interface = 'wg1337' + port = '1337' + privkey = 'iJi4lb2HhkLx2KSAGOjji2alKkYsJjSPkHkrcpxgEVU=' + pubkey_1 = 'srQ8VF6z/LDjKCzpxBzFpmaNUOeuHYzIfc2dcmoc/h4=' + pubkey_2 = '8pbMHiQ7NECVP7F65Mb2W8+4ldGG2oaGvDSpSEsOBn8=' + + self.cli_set(base_path + [interface, 'address', '172.16.0.1/24']) + self.cli_set(base_path + [interface, 'port', port]) + self.cli_set(base_path + [interface, 'private-key', privkey]) + + self.cli_set(base_path + [interface, 'peer', 'VyOS', 'public-key', pubkey_1]) + self.cli_set(base_path + [interface, 'peer', 'VyOS', 'allowed-ips', '10.205.212.10/32']) + + self.cli_commit() + + peers = get_peers(interface) + self.assertIn(pubkey_1, peers) + self.assertNotIn(pubkey_2, peers) + + # Now change the public key of our peer + self.cli_set(base_path + [interface, 'peer', 'VyOS', 'public-key', pubkey_2]) + self.cli_commit() + + # Verify config + peers = get_peers(interface) + self.assertNotIn(pubkey_1, peers) + self.assertIn(pubkey_2, peers) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py new file mode 100644 index 0000000..b8b18f3 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_wireless.py @@ -0,0 +1,635 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import re +import unittest + +from base_interfaces_test import BasicInterfaceTest +from glob import glob + +from vyos.configsession import ConfigSessionError +from vyos.utils.file import read_file +from vyos.utils.kernel import check_kmod +from vyos.utils.network import interface_exists +from vyos.utils.process import process_named_running +from vyos.utils.process import call +from vyos.xml_ref import default_value + +def get_config_value(interface, key): + tmp = read_file(f'/run/hostapd/{interface}.conf') + tmp = re.findall(f'{key}=+(.*)', tmp) + return tmp[0] + +wifi_cc_path = ['system', 'wireless', 'country-code'] +country = 'se' +class WirelessInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'wireless'] + cls._options = { + 'wlan0': ['physical-device phy0', + 'ssid VyOS-WIFI-0', + 'type station', + 'address 192.0.2.1/30'], + 'wlan1': ['physical-device phy0', + 'ssid VyOS-WIFI-1', + 'type access-point', + 'address 192.0.2.5/30', + 'channel 0'], + 'wlan10': ['physical-device phy1', + 'ssid VyOS-WIFI-2', + 'type station', + 'address 192.0.2.9/30'], + 'wlan11': ['physical-device phy1', + 'ssid VyOS-WIFI-3', + 'type access-point', + 'address 192.0.2.13/30', + 'channel 0'], + } + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(WirelessInterfaceTest, cls).setUpClass() + + # T5245 - currently testcases are disabled + cls._test_ipv6 = False + cls._test_vlan = False + + cls.cli_set(cls, wifi_cc_path + [country]) + + + def test_wireless_add_single_ip_address(self): + # derived method to check if member interfaces are enslaved properly + super().test_add_single_ip_address() + + for option, option_value in self._options.items(): + if 'type access-point' in option_value: + # Check for running process + self.assertTrue(process_named_running('hostapd')) + elif 'type station' in option_value: + # Check for running process + self.assertTrue(process_named_running('wpa_supplicant')) + else: + self.assertTrue(False) + + def test_wireless_hostapd_config(self): + # Only set the hostapd (access-point) options + interface = self._interfaces[1] # wlan1 + ssid = 'ssid' + + self.cli_set(self._base_path + [interface, 'ssid', ssid]) + self.cli_set(self._base_path + [interface, 'type', 'access-point']) + + # auto-powersave is special + self.cli_set(self._base_path + [interface, 'capabilities', 'ht', 'auto-powersave']) + + ht_opt = { + # VyOS CLI option hostapd - ht_capab setting + '40mhz-incapable' : '[40-INTOLERANT]', + 'delayed-block-ack' : '[DELAYED-BA]', + 'greenfield' : '[GF]', + 'ldpc' : '[LDPC]', + 'lsig-protection' : '[LSIG-TXOP-PROT]', + 'channel-set-width ht40+' : '[HT40+]', + 'stbc tx' : '[TX-STBC]', + 'stbc rx 123' : '[RX-STBC-123]', + 'max-amsdu 7935' : '[MAX-AMSDU-7935]', + 'smps static' : '[SMPS-STATIC]', + } + for key in ht_opt: + self.cli_set(self._base_path + [interface, 'capabilities', 'ht'] + key.split()) + + vht_opt = { + # VyOS CLI option hostapd - ht_capab setting + 'channel-set-width 3' : '[VHT160-80PLUS80]', + 'stbc tx' : '[TX-STBC-2BY1]', + 'stbc rx 12' : '[RX-STBC-12]', + 'ldpc' : '[RXLDPC]', + 'tx-powersave' : '[VHT-TXOP-PS]', + 'vht-cf' : '[HTC-VHT]', + 'antenna-pattern-fixed' : '[RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]', + 'max-mpdu 11454' : '[MAX-MPDU-11454]', + 'max-mpdu-exp 2' : '[MAX-A-MPDU-LEN-EXP-2]', + 'link-adaptation both' : '[VHT-LINK-ADAPT3]', + 'short-gi 80' : '[SHORT-GI-80]', + 'short-gi 160' : '[SHORT-GI-160]', + } + for key in vht_opt: + self.cli_set(self._base_path + [interface, 'capabilities', 'vht'] + key.split()) + + self.cli_commit() + + # + # Validate Config + # + tmp = get_config_value(interface, 'interface') + self.assertEqual(interface, tmp) + + # ssid + tmp = get_config_value(interface, 'ssid') + self.assertEqual(ssid, tmp) + + # channel + tmp = get_config_value(interface, 'channel') + cli_default = default_value(self._base_path + [interface, 'channel']) + self.assertEqual(cli_default, tmp) + + # auto-powersave is special + tmp = get_config_value(interface, 'uapsd_advertisement_enabled') + self.assertEqual('1', tmp) + + tmp = get_config_value(interface, 'ht_capab') + for key, value in ht_opt.items(): + self.assertIn(value, tmp) + + tmp = get_config_value(interface, 'vht_capab') + for key, value in vht_opt.items(): + self.assertIn(value, tmp) + + # Check for running process + self.assertTrue(process_named_running('hostapd')) + + def test_wireless_hostapd_vht_mu_beamformer_config(self): + # Multi-User-Beamformer + interface = self._interfaces[1] # wlan1 + ssid = 'vht_mu-beamformer' + antennas = '3' + + self.cli_set(self._base_path + [interface, 'ssid', ssid]) + self.cli_set(self._base_path + [interface, 'type', 'access-point']) + self.cli_set(self._base_path + [interface, 'channel', '36']) + + ht_opt = { + # VyOS CLI option hostapd - ht_capab setting + 'channel-set-width ht20' : '[HT20]', + 'channel-set-width ht40-' : '[HT40-]', + 'channel-set-width ht40+' : '[HT40+]', + 'dsss-cck-40' : '[DSSS_CCK-40]', + 'short-gi 20' : '[SHORT-GI-20]', + 'short-gi 40' : '[SHORT-GI-40]', + 'max-amsdu 7935' : '[MAX-AMSDU-7935]', + } + for key in ht_opt: + self.cli_set(self._base_path + [interface, 'capabilities', 'ht'] + key.split()) + + vht_opt = { + # VyOS CLI option hostapd - ht_capab setting + 'max-mpdu 11454' : '[MAX-MPDU-11454]', + 'max-mpdu-exp 2' : '[MAX-A-MPDU-LEN-EXP-2]', + 'stbc tx' : '[TX-STBC-2BY1]', + 'stbc rx 12' : '[RX-STBC-12]', + 'ldpc' : '[RXLDPC]', + 'tx-powersave' : '[VHT-TXOP-PS]', + 'vht-cf' : '[HTC-VHT]', + 'antenna-pattern-fixed' : '[RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]', + 'link-adaptation both' : '[VHT-LINK-ADAPT3]', + 'short-gi 80' : '[SHORT-GI-80]', + 'short-gi 160' : '[SHORT-GI-160]', + 'beamform multi-user-beamformer' : '[MU-BEAMFORMER][BF-ANTENNA-3][SOUNDING-DIMENSION-3]', + } + + self.cli_set(self._base_path + [interface, 'capabilities', 'vht', 'channel-set-width', '1']) + self.cli_set(self._base_path + [interface, 'capabilities', 'vht', 'center-channel-freq', 'freq-1', '42']) + self.cli_set(self._base_path + [interface, 'capabilities', 'vht', 'antenna-count', antennas]) + for key in vht_opt: + self.cli_set(self._base_path + [interface, 'capabilities', 'vht'] + key.split()) + + self.cli_commit() + + # + # Validate Config + # + tmp = get_config_value(interface, 'interface') + self.assertEqual(interface, tmp) + + # ssid + tmp = get_config_value(interface, 'ssid') + self.assertEqual(ssid, tmp) + + # channel + tmp = get_config_value(interface, 'channel') + self.assertEqual('36', tmp) + + tmp = get_config_value(interface, 'ht_capab') + for key, value in ht_opt.items(): + self.assertIn(value, tmp) + + tmp = get_config_value(interface, 'vht_capab') + for key, value in vht_opt.items(): + self.assertIn(value, tmp) + + def test_wireless_hostapd_vht_su_beamformer_config(self): + # Single-User-Beamformer + interface = self._interfaces[1] # wlan1 + ssid = 'vht_su-beamformer' + antennas = '3' + + self.cli_set(self._base_path + [interface, 'ssid', ssid]) + self.cli_set(self._base_path + [interface, 'type', 'access-point']) + self.cli_set(self._base_path + [interface, 'channel', '36']) + + ht_opt = { + # VyOS CLI option hostapd - ht_capab setting + 'channel-set-width ht20' : '[HT20]', + 'channel-set-width ht40-' : '[HT40-]', + 'channel-set-width ht40+' : '[HT40+]', + 'dsss-cck-40' : '[DSSS_CCK-40]', + 'short-gi 20' : '[SHORT-GI-20]', + 'short-gi 40' : '[SHORT-GI-40]', + 'max-amsdu 7935' : '[MAX-AMSDU-7935]', + } + for key in ht_opt: + self.cli_set(self._base_path + [interface, 'capabilities', 'ht'] + key.split()) + + vht_opt = { + # VyOS CLI option hostapd - ht_capab setting + 'max-mpdu 11454' : '[MAX-MPDU-11454]', + 'max-mpdu-exp 2' : '[MAX-A-MPDU-LEN-EXP-2]', + 'stbc tx' : '[TX-STBC-2BY1]', + 'stbc rx 12' : '[RX-STBC-12]', + 'ldpc' : '[RXLDPC]', + 'tx-powersave' : '[VHT-TXOP-PS]', + 'vht-cf' : '[HTC-VHT]', + 'antenna-pattern-fixed' : '[RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]', + 'link-adaptation both' : '[VHT-LINK-ADAPT3]', + 'short-gi 80' : '[SHORT-GI-80]', + 'short-gi 160' : '[SHORT-GI-160]', + 'beamform single-user-beamformer' : '[SU-BEAMFORMER][BF-ANTENNA-2][SOUNDING-DIMENSION-2]', + } + + self.cli_set(self._base_path + [interface, 'capabilities', 'vht', 'channel-set-width', '1']) + self.cli_set(self._base_path + [interface, 'capabilities', 'vht', 'center-channel-freq', 'freq-1', '42']) + self.cli_set(self._base_path + [interface, 'capabilities', 'vht', 'antenna-count', antennas]) + for key in vht_opt: + self.cli_set(self._base_path + [interface, 'capabilities', 'vht'] + key.split()) + + self.cli_commit() + + # + # Validate Config + # + tmp = get_config_value(interface, 'interface') + self.assertEqual(interface, tmp) + + # ssid + tmp = get_config_value(interface, 'ssid') + self.assertEqual(ssid, tmp) + + # channel + tmp = get_config_value(interface, 'channel') + self.assertEqual('36', tmp) + + tmp = get_config_value(interface, 'ht_capab') + for key, value in ht_opt.items(): + self.assertIn(value, tmp) + + tmp = get_config_value(interface, 'vht_capab') + for key, value in vht_opt.items(): + self.assertIn(value, tmp) + + def test_wireless_hostapd_he_2ghz_config(self): + # Only set the hostapd (access-point) options - HE mode for 802.11ax at 2.4GHz + interface = self._interfaces[1] # wlan1 + ssid = 'ssid' + channel = '1' + sae_pw = 'VyOSVyOSVyOS' + bss_color = '13' + channel_set_width = '81' + + self.cli_set(self._base_path + [interface, 'ssid', ssid]) + self.cli_set(self._base_path + [interface, 'type', 'access-point']) + self.cli_set(self._base_path + [interface, 'channel', channel]) + self.cli_set(self._base_path + [interface, 'mode', 'ax']) + self.cli_set(self._base_path + [interface, 'security', 'wpa', 'mode', 'wpa2']) + self.cli_set(self._base_path + [interface, 'security', 'wpa', 'passphrase', sae_pw]) + self.cli_set(self._base_path + [interface, 'security', 'wpa', 'cipher', 'CCMP']) + self.cli_set(self._base_path + [interface, 'security', 'wpa', 'cipher', 'GCMP']) + self.cli_set(self._base_path + [interface, 'capabilities', 'ht', '40mhz-incapable']) + self.cli_set(self._base_path + [interface, 'capabilities', 'ht', 'channel-set-width', 'ht20']) + self.cli_set(self._base_path + [interface, 'capabilities', 'ht', 'channel-set-width', 'ht40+']) + self.cli_set(self._base_path + [interface, 'capabilities', 'ht', 'channel-set-width', 'ht40-']) + self.cli_set(self._base_path + [interface, 'capabilities', 'ht', 'short-gi', '20']) + self.cli_set(self._base_path + [interface, 'capabilities', 'ht', 'short-gi', '40']) + self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'bss-color', bss_color]) + self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'channel-set-width', channel_set_width]) + self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'beamform', 'multi-user-beamformer']) + self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'beamform', 'single-user-beamformer']) + self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'beamform', 'single-user-beamformee']) + + self.cli_commit() + + # + # Validate Config + # + tmp = get_config_value(interface, 'interface') + self.assertEqual(interface, tmp) + + # ssid + tmp = get_config_value(interface, 'ssid') + self.assertEqual(ssid, tmp) + + # mode of operation resulting from [interface, 'mode', 'ax'] + tmp = get_config_value(interface, 'hw_mode') + self.assertEqual('g', tmp) + tmp = get_config_value(interface, 'ieee80211h') + self.assertEqual('1', tmp) + tmp = get_config_value(interface, 'ieee80211ax') + self.assertEqual('1', tmp) + + # channel and channel width + tmp = get_config_value(interface, 'channel') + self.assertEqual(channel, tmp) + tmp = get_config_value(interface, 'op_class') + self.assertEqual(channel_set_width, tmp) + + # BSS coloring + tmp = get_config_value(interface, 'he_bss_color') + self.assertEqual(bss_color, tmp) + + # sae_password + tmp = get_config_value(interface, 'wpa_passphrase') + self.assertEqual(sae_pw, tmp) + + # WPA3 and dependencies + tmp = get_config_value(interface, 'wpa') + self.assertEqual('2', tmp) + tmp = get_config_value(interface, 'rsn_pairwise') + self.assertEqual('CCMP GCMP', tmp) + tmp = get_config_value(interface, 'wpa_key_mgmt') + self.assertEqual('WPA-PSK WPA-PSK-SHA256', tmp) + + # beamforming + tmp = get_config_value(interface, 'he_mu_beamformer') + self.assertEqual('1', tmp) + tmp = get_config_value(interface, 'he_su_beamformee') + self.assertEqual('1', tmp) + tmp = get_config_value(interface, 'he_mu_beamformer') + self.assertEqual('1', tmp) + + # Check for running process + self.assertTrue(process_named_running('hostapd')) + + def test_wireless_hostapd_he_6ghz_config(self): + # Only set the hostapd (access-point) options - HE mode for 802.11ax at 6GHz + interface = self._interfaces[1] # wlan1 + ssid = 'ssid' + channel = '1' + sae_pw = 'VyOSVyOSVyOS' + bss_color = '37' + channel_set_width = '134' + center_channel_freq_1 = '15' + + self.cli_set(self._base_path + [interface, 'ssid', ssid]) + self.cli_set(self._base_path + [interface, 'type', 'access-point']) + self.cli_set(self._base_path + [interface, 'channel', channel]) + self.cli_set(self._base_path + [interface, 'mode', 'ax']) + self.cli_set(self._base_path + [interface, 'security', 'wpa', 'mode', 'wpa3']) + self.cli_set(self._base_path + [interface, 'security', 'wpa', 'passphrase', sae_pw]) + self.cli_set(self._base_path + [interface, 'security', 'wpa', 'cipher', 'CCMP']) + self.cli_set(self._base_path + [interface, 'security', 'wpa', 'cipher', 'GCMP']) + self.cli_set(self._base_path + [interface, 'enable-bf-protection']) + self.cli_set(self._base_path + [interface, 'mgmt-frame-protection', 'required']) + self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'bss-color', bss_color]) + self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'channel-set-width', channel_set_width]) + self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'center-channel-freq', 'freq-1', center_channel_freq_1]) + self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'antenna-pattern-fixed']) + self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'beamform', 'multi-user-beamformer']) + self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'beamform', 'single-user-beamformer']) + + self.cli_commit() + + # + # Validate Config + # + tmp = get_config_value(interface, 'interface') + self.assertEqual(interface, tmp) + + # ssid + tmp = get_config_value(interface, 'ssid') + self.assertEqual(ssid, tmp) + + # mode of operation resulting from [interface, 'mode', 'ax'] + tmp = get_config_value(interface, 'hw_mode') + self.assertEqual('a', tmp) + tmp = get_config_value(interface, 'ieee80211h') + self.assertEqual('1', tmp) + tmp = get_config_value(interface, 'ieee80211ax') + self.assertEqual('1', tmp) + + # channel and channel width + tmp = get_config_value(interface, 'channel') + self.assertEqual(channel, tmp) + tmp = get_config_value(interface, 'op_class') + self.assertEqual(channel_set_width, tmp) + tmp = get_config_value(interface, 'he_oper_centr_freq_seg0_idx') + self.assertEqual(center_channel_freq_1, tmp) + + # BSS coloring + tmp = get_config_value(interface, 'he_bss_color') + self.assertEqual(bss_color, tmp) + + # sae_password + tmp = get_config_value(interface, 'sae_password') + self.assertEqual(sae_pw, tmp) + + # WPA3 and dependencies + tmp = get_config_value(interface, 'wpa') + self.assertEqual('2', tmp) + tmp = get_config_value(interface, 'rsn_pairwise') + self.assertEqual('CCMP GCMP', tmp) + tmp = get_config_value(interface, 'wpa_key_mgmt') + self.assertEqual('SAE', tmp) + + # antenna pattern + tmp = get_config_value(interface, 'he_6ghz_rx_ant_pat') + self.assertEqual('1', tmp) + + # beamforming + tmp = get_config_value(interface, 'he_mu_beamformer') + self.assertEqual('1', tmp) + tmp = get_config_value(interface, 'he_su_beamformee') + self.assertEqual('0', tmp) + tmp = get_config_value(interface, 'he_mu_beamformer') + self.assertEqual('1', tmp) + + # Check for running process + self.assertTrue(process_named_running('hostapd')) + + def test_wireless_hostapd_wpa_config(self): + # Only set the hostapd (access-point) options + interface = self._interfaces[1] # wlan1 + ssid = 'VyOS-SMOKETEST' + channel = '1' + wpa_key = 'VyOSVyOSVyOS' + mode = 'n' + + self.cli_set(self._base_path + [interface, 'type', 'access-point']) + self.cli_set(self._base_path + [interface, 'mode', mode]) + + # SSID and country-code are already configured in self.setUpClass() + # Therefore, we must delete those here to check if commit will fail without it. + self.cli_delete(wifi_cc_path) + self.cli_delete(self._base_path + [interface, 'ssid']) + + # Country-Code must be set + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(wifi_cc_path + [country]) + + # SSID must be set + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'ssid', ssid]) + + # Channel must be set (defaults to channel 0) + self.cli_set(self._base_path + [interface, 'channel', channel]) + + self.cli_set(self._base_path + [interface, 'security', 'wpa', 'mode', 'wpa2']) + self.cli_set(self._base_path + [interface, 'security', 'wpa', 'passphrase', wpa_key]) + + self.cli_commit() + + # + # Validate Config + # + tmp = get_config_value(interface, 'interface') + self.assertEqual(interface, tmp) + + tmp = get_config_value(interface, 'hw_mode') + # rewrite special mode + if mode == 'n': mode = 'g' + self.assertEqual(mode, tmp) + + # WPA key + tmp = get_config_value(interface, 'wpa') + self.assertEqual('2', tmp) + tmp = get_config_value(interface, 'wpa_passphrase') + self.assertEqual(wpa_key, tmp) + + # SSID + tmp = get_config_value(interface, 'ssid') + self.assertEqual(ssid, tmp) + + # channel + tmp = get_config_value(interface, 'channel') + self.assertEqual(channel, tmp) + + # Country code + tmp = get_config_value(interface, 'country_code') + self.assertEqual(country.upper(), tmp) + + # Check for running process + self.assertTrue(process_named_running('hostapd')) + + def test_wireless_access_point_bridge(self): + interface = self._interfaces[1] # wlan1 + ssid = 'VyOS-Test' + bridge = 'br42477' + + # We need a bridge where we can hook our access-point interface to + bridge_path = ['interfaces', 'bridge', bridge] + self.cli_set(bridge_path + ['member', 'interface', interface]) + + self.cli_set(self._base_path + [interface, 'ssid', ssid]) + self.cli_set(self._base_path + [interface, 'type', 'access-point']) + self.cli_set(self._base_path + [interface, 'channel', '1']) + + self.cli_commit() + + # Check for running process + self.assertTrue(process_named_running('hostapd')) + + bridge_members = [] + for tmp in glob(f'/sys/class/net/{bridge}/lower_*'): + bridge_members.append(os.path.basename(tmp).replace('lower_', '')) + + self.assertIn(interface, bridge_members) + + # Now generate a VLAN on the bridge + self.cli_set(bridge_path + ['enable-vlan']) + self.cli_set(bridge_path + ['vif', '20', 'address', '10.0.0.1/24']) + + self.cli_commit() + + tmp = get_config_value(interface, 'bridge') + self.assertEqual(tmp, bridge) + tmp = get_config_value(interface, 'wds_sta') + self.assertEqual(tmp, '1') + + self.cli_delete(bridge_path) + + def test_wireless_security_station_address(self): + interface = self._interfaces[1] # wlan1 + ssid = 'VyOS-ACL' + + hostapd_accept_station_conf = f'/run/hostapd/{interface}_station_accept.conf' + hostapd_deny_station_conf = f'/run/hostapd/{interface}_station_deny.conf' + + accept_mac = ['00:00:00:00:ac:01', '00:00:00:00:ac:02', '00:00:00:00:ac:03', '00:00:00:00:ac:04'] + deny_mac = ['00:00:00:00:de:01', '00:00:00:00:de:02', '00:00:00:00:de:03', '00:00:00:00:de:04'] + + self.cli_set(self._base_path + [interface, 'ssid', ssid]) + self.cli_set(self._base_path + [interface, 'type', 'access-point']) + self.cli_set(self._base_path + [interface, 'security', 'station-address', 'mode', 'accept']) + + for mac in accept_mac: + self.cli_set(self._base_path + [interface, 'security', 'station-address', 'accept', 'mac', mac]) + for mac in deny_mac: + self.cli_set(self._base_path + [interface, 'security', 'station-address', 'deny', 'mac', mac]) + + self.cli_commit() + + self.assertTrue(interface_exists(interface)) + self.assertTrue(os.path.isfile(f'/run/hostapd/{interface}_station_accept.conf')) + self.assertTrue(os.path.isfile(f'/run/hostapd/{interface}_station_deny.conf')) + + self.assertTrue(process_named_running('hostapd')) + + # in accept mode all addresses are allowed unless specified in the deny list + tmp = get_config_value(interface, 'macaddr_acl') + self.assertEqual(tmp, '0') + + accept_list = read_file(hostapd_accept_station_conf) + for mac in accept_mac: + self.assertIn(mac, accept_list) + + deny_list = read_file(hostapd_deny_station_conf) + for mac in deny_mac: + self.assertIn(mac, deny_list) + + # Switch mode accept -> deny + self.cli_set(self._base_path + [interface, 'security', 'station-address', 'mode', 'deny']) + self.cli_commit() + + self.assertTrue(interface_exists(interface)) + self.assertTrue(os.path.isfile(f'/run/hostapd/{interface}_station_accept.conf')) + self.assertTrue(os.path.isfile(f'/run/hostapd/{interface}_station_deny.conf')) + + # In deny mode all addresses are denied unless specified in the allow list + tmp = get_config_value(interface, 'macaddr_acl') + self.assertEqual(tmp, '1') + + # Check for running process + self.assertTrue(process_named_running('hostapd')) + +if __name__ == '__main__': + check_kmod('mac80211_hwsim') + # loading the module created two WIFI Interfaces in the background (wlan0 and wlan1) + # remove them to have a clean test start + for interface in ['wlan0', 'wlan1']: + if interface_exists(interface): + call(f'sudo iw dev {interface} del') + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py b/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py new file mode 100644 index 0000000..34f77b9 --- /dev/null +++ b/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py @@ -0,0 +1,502 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +PROCESS_NAME = 'haproxy' +HAPROXY_CONF = '/run/haproxy/haproxy.cfg' +base_path = ['load-balancing', 'reverse-proxy'] +proxy_interface = 'eth1' + +valid_ca_cert = """ +MIIDnTCCAoWgAwIBAgIUewSDtLiZbhg1YEslMnqRl1shoPcwDQYJKoZIhvcNAQEL +BQAwVzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcM +CVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzEQMA4GA1UEAwwHdnlvcy5pbzAeFw0y +NDA0MDEwNTQ3MzJaFw0yOTAzMzEwNTQ3MzJaMFcxCzAJBgNVBAYTAkdCMRMwEQYD +VQQIDApTb21lLVN0YXRlMRIwEAYDVQQHDAlTb21lLUNpdHkxDTALBgNVBAoMBFZ5 +T1MxEDAOBgNVBAMMB3Z5b3MuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC/D6W27rfpdPIf16JHs8fx/7VehyCk8m03dPAQqv6wQiHF5xhXaFZER1+c +nf7oExp9zi/4HJ/KRbcc1loVArXtV0zwAUftBmUeezGVfxhCHKhP89GnV4NB97jj +klHFSxjEoT/0YvJQ1IV/3Cos1T5O8x14WIi31l7WQGYAyWxUXiP8QxGVmF3odEJo +O3e7Ew9HFkamvuL6Z6c4uAVMM7uYXme7q0OM49Wu7C9hj39ZKbjG5FFKZTj+zDKg +SbOiQaFk3blOky/e3ifNjZelGtussYPOMBkUirLvrSGGy7s3lm8Yp5PH5+UkVQB2 +rZyxRdZTC9kh+dShR1s/qcPnDw7lAgMBAAGjYTBfMA8GA1UdEwEB/wQFMAMBAf8w +DgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAd +BgNVHQ4EFgQU/HE2UPn8JQB/9EL52GquPxZqr5MwDQYJKoZIhvcNAQELBQADggEB +AIkMmqyoMqidTa3lvUPJNl4H+Ef/yPQkTkrsOd3WL8DQysyUdMLdQozr3K1bH5XB +wRxoXX211nu4WhN18LsFJRCuHBSxmaNkBGFyl+JNvhPUSI6j0somNMCS75KJ0ZDx +2HZsXmmJFF902VQxCR7vCIrFDrKDYq1e7GQbFS8t46FlpqivQMQWNPt18Bthj/1Y +lO2GKRWFCX8VlOW7FtDQ6B3oC1oAGHBBGogAx7/0gh9DnYBKT14V/kuWW3RNABZJ +ewHO1C6icQdnjtaREDyTP4oyL+uyAfXrFfbpti2hc00f8oYPQZYxj1yxl4UAdNij +mS6YqH/WRioGMe3tBVeSdoo= +""" + +valid_ca_private_key = """ +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC/D6W27rfpdPIf +16JHs8fx/7VehyCk8m03dPAQqv6wQiHF5xhXaFZER1+cnf7oExp9zi/4HJ/KRbcc +1loVArXtV0zwAUftBmUeezGVfxhCHKhP89GnV4NB97jjklHFSxjEoT/0YvJQ1IV/ +3Cos1T5O8x14WIi31l7WQGYAyWxUXiP8QxGVmF3odEJoO3e7Ew9HFkamvuL6Z6c4 +uAVMM7uYXme7q0OM49Wu7C9hj39ZKbjG5FFKZTj+zDKgSbOiQaFk3blOky/e3ifN +jZelGtussYPOMBkUirLvrSGGy7s3lm8Yp5PH5+UkVQB2rZyxRdZTC9kh+dShR1s/ +qcPnDw7lAgMBAAECggEAGm+j0kf9koPn7Jf9kEZD6CwlgEraLXiNvBqmDOhcDS9Z +VPTA3XdGWHQ3uofx+VKLW9TntkDfqzEyQP83v6h8W7a0opDKzvUPkMQi/Dh1ttAY +SdfGrozhUINiRbq9LbtSVgKpwrreJGkDf8mK3GE1Gd9xuHEnmahDvwlyE7HLF3Eh +2xJDSAPx3OxcjR5hW7vbojhVCyCfuYTlZB86f0Sb8SqxZMt/y2zKmbzoTqpUBWbg +lBnE7GJoNR07DWjxvEP8r6kQMh670I01SUR42CSK8X8asHhhZHUcggsNno+BBc6K +sy4HzDIYIay6oy0atcVzKsGrlNCveeAiSEcw7x2yAQKBgQDsXz2FbhXYV5Vbt4wU +5EWOa7if/+FG+TcVezOF3xlNBgykjXHQaYTYHrJq0qsEFrNT3ZGm9ezY4LdF3BTt +5z/+i8QlCCw/nr3N7JZx6U5+OJl1j3NLFoFx3+DXo31pgJJEQCHHwdCkF5IuOcZ/ +b3nXkRZ80BVv7XD6F9bMHEwLYQKBgQDO7THcRDbsE6/+7VsTDf0P/JENba3DBBu1 +gjb1ItL5FHJwMgnkUadRZRo0QKye848ugribed39qSoJfNaBJrAT5T8S/9q+lXft +vXUckcBO1CKNaP9gqF5fPIdNHf64GbmCiiHjOTE3rwJjkxJPpzLXyvgBO4aLeesK +ThBdW+iWBQKBgD3crz08knsMcQqP/xl4pLuhdbBqR4tLrh7xH4rp2LVP3/8xBZiG +BT6Kyicq+5cWWdiZJIWN127rYQvnjZK18wmriqomeW4tHX/Ha5hkdyaRqZga8xGz +0iz7at0E7M2v2JgEMNMW5oQLpzZx6IFxq3G/hyMjUnj4q5jIpG7G+SABAoGBAKgT +8Ika+4WcpDssrup2VVTT8Tp4GUkroBo6D8vkInvhiObrLi+/x2mM9tD0q4JdEbNU +yQC454EwFA4q0c2MED/I2QfkvNhLbmO0nVi8ZvlgxEQawjzP5f/zmW8haxI9Cvsm +mkoH3Zt+UzFwd9ItXFX97p6JrErEmA8Bw7chfXXFAoGACWR/c+s7hnX6gzyah3N1 +Db0xAaS6M9fzogcg2OM1i/6OCOcp4Sh1fmPG7tN45CCnFkhgVoRkSSA5MJAe2I/r +xFm72VX7567T+4qIFua2iDxIBA/Z4zmj+RYfhHGPYZjdSjprKJxY6QOv5aoluBvE +mlLy1Hmcry+ukWZtWezZfGY= +""" + +valid_cert = """ +MIIDsTCCApmgAwIBAgIUDKOfYIwwtjww0vAMvJnXnGLhL+0wDQYJKoZIhvcNAQEL +BQAwVzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcM +CVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzEQMA4GA1UEAwwHdnlvcy5pbzAeFw0y +NDA0MDEwNTQ5NTdaFw0yNTA0MDEwNTQ5NTdaMFcxCzAJBgNVBAYTAkdCMRMwEQYD +VQQIDApTb21lLVN0YXRlMRIwEAYDVQQHDAlTb21lLUNpdHkxDTALBgNVBAoMBFZ5 +T1MxEDAOBgNVBAMMB3Z5b3MuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCHtW25Umt6rqm2gfzqAZg1/VsqefZwAqIUAm2T3VwHQZ/2tNdr8ROWASii +W5PToC7N8StMwFl2YoIof+MXGMO00toTTJePZOJKjF9U9hL3kuYuY1+yng4fl+E0 +96xVobb2KY4lMZ2rVwmpB7jkNO2LWxbJ6vHKcwMOhlx/8NEKIoVmkBT1Zkgy5dgn +PgTtJcdVIU75XhQWqBmAUsMmACuZfqSYJbAv3hHz5V+Ejt0dI6mlGM7TXsCC9tKM +64paIKZooFm78IsxJ26jHpZ8eh+SDBz0VBydBFWXm8VhOJ8NlZ1opAh3AWxFZDGt +49uOsy82VmUcHPyoZ8DKYkBFHfSpAgMBAAGjdTBzMAwGA1UdEwEB/wQCMAAwDgYD +VR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB0GA1UdDgQWBBTeTcgM +pRxAMjVBirjzo2QUu5H5fzAfBgNVHSMEGDAWgBT8cTZQ+fwlAH/0QvnYaq4/Fmqv +kzANBgkqhkiG9w0BAQsFAAOCAQEAi4dBcH7TIYwWRW6bWRubMA7ztonV4EYb15Zf +9yNafMWAEEBOii/DFo+j/ky9oInl7ZHw7gTIyXfLEarX/bM6fHOgiyj4zp3u6RnH +5qlBypu/YCnyPjE/GvV05m2rrXnxZ4rCtcoO4u/HyGbV+jGnCmjShKICKyu1FdMd +eeZRrLKPO/yghadGH34WVQnrbaorwlbi+NjB6fxmZQx5HE/SyK/9sb6WCpLMGHoy +MpdQo3lV1ewtL3ElIWDq6mO030Mo5pwpjIU+8yHHNBVzg6mlGVgQPAp0gbUei9aP +CJ8SLmMEi3NDk0E/sPgVC17e6bf2bx2nRuXROZekG2dd90Iu8g== +""" + +valid_cert_private_key = """ +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCHtW25Umt6rqm2 +gfzqAZg1/VsqefZwAqIUAm2T3VwHQZ/2tNdr8ROWASiiW5PToC7N8StMwFl2YoIo +f+MXGMO00toTTJePZOJKjF9U9hL3kuYuY1+yng4fl+E096xVobb2KY4lMZ2rVwmp +B7jkNO2LWxbJ6vHKcwMOhlx/8NEKIoVmkBT1Zkgy5dgnPgTtJcdVIU75XhQWqBmA +UsMmACuZfqSYJbAv3hHz5V+Ejt0dI6mlGM7TXsCC9tKM64paIKZooFm78IsxJ26j +HpZ8eh+SDBz0VBydBFWXm8VhOJ8NlZ1opAh3AWxFZDGt49uOsy82VmUcHPyoZ8DK +YkBFHfSpAgMBAAECggEABofhw0W/ACEMcAjmpNTFkFCUXPGQXWDVD7EzuIZSNdOv +yOm4Rbys6H6/B7wwO6KVagoBf1Cw5Xh1YtFPuoZxsZ+liMD6eLc+SB/j/RTYAhPO +0bvsyK3gSF8w4nGKWLce9M74ZRwThkG6qGijmlDdPyP3r2kn8GoTQzVOWYZbavk/ +H3uE6PsZSWjOY+Mnm3vEmeItPYKGZ5+IP+YiTqZ4NCggBwH7csnR3/kbwY5Ns7jl +3Av+EAdIeUwDNeMfLTzN7GphJR7gL6YQIhGKxE+W0GHXL2FubnnrFx8G75HFh1ay +GkJXEqY5Lbd+7VPS0KcQdwhMSSoJsY5GUORUqrU80QKBgQC/0wJSu+Gfe7dONIby +mnGRppSRIQVRjCjbVIN+Y2h1Kp3aK0qDpV7KFLCiUUtz9rWHR/NB4cDaIW543T55 +/jXUMD2j3EqtbtlsVQfDLQV7DyDrMmBAs4REHmyZmWTzHjCDUO79ahdOlZs34Alz +wfpX3L3WVYGIAJKZtsUZ8FbrGQKBgQC1HFgVZ1PqP9/pW50RMh06BbQrhWPGiWgH +Rn5bFthLkp3uqr9bReBq9tu3sqJuAhFudH68wup+Z+fTcHAcNg2Rs+Q+IKnULdB/ +UQHYoPjeWOvHAuOmgn9iD9OD7GCIv8fZmLit09vAsOWq+NKNBKCknGM70CDrvAlQ +lOAUa34YEQKBgQC5i8GThWiYe3Kzktt1jy6LVDYgq3AZkRl0Diui9UT1EGPfxEAv +VqZ5kcnJOBlj8h9k25PRBi0k0XGqN1dXaS1oMcFt3ofdenuU7iqz/7htcBTHa9Lu +wrYNreAeMuISyADlBEQnm5cvzEZ3pZ1++wLMOhjmWY8Rnnwvczrz/CYXAQKBgH+t +vcNJFvWblkUzWuWWiNgw0TWlUhPTJs2KOuYIku+kK0bohQLZnj6KTZeRjcU0HAnc +gsScPShkJCEBsWeSC7reMVhDOrbknYpEF6MayJgn5ABm3wqyEQ+WzKzCZcPCQCf8 +7KVPKCsOCrufsv/LdVzXC3ZNYggOhhqS+e4rYbehAoGBAIsq252o3vgrunzS5FZx +IONA2FvYrxVbDn5aF8WfNSdKFy3CAlt0P+Fm8gYbrKylIfMXpL8Oqc9RJou5onZP +ZXLrtgVJR9W020qTurO2f91qfU8646n11hR9ObBB1IYbagOU0Pw1Nrq/FRp/u2tx +7i7xFz2WEiQeSCPaKYOiqM3t +""" + + +class TestLoadBalancingReverseProxy(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(['interfaces', 'ethernet', proxy_interface, 'address']) + self.cli_delete(base_path) + self.cli_delete(['pki']) + self.cli_commit() + + # Process must be terminated after deleting the config + self.assertFalse(process_named_running(PROCESS_NAME)) + + def base_config(self): + self.cli_set(base_path + ['service', 'https_front', 'mode', 'http']) + self.cli_set(base_path + ['service', 'https_front', 'port', '4433']) + self.cli_set(base_path + ['service', 'https_front', 'backend', 'bk-01']) + + self.cli_set(base_path + ['backend', 'bk-01', 'mode', 'http']) + self.cli_set(base_path + ['backend', 'bk-01', 'server', 'bk-01', 'address', '192.0.2.11']) + self.cli_set(base_path + ['backend', 'bk-01', 'server', 'bk-01', 'port', '9090']) + self.cli_set(base_path + ['backend', 'bk-01', 'server', 'bk-01', 'send-proxy']) + + self.cli_set(base_path + ['global-parameters', 'max-connections', '1000']) + + def configure_pki(self): + + # Valid CA + self.cli_set(['pki', 'ca', 'smoketest', 'certificate', valid_ca_cert.replace('\n','')]) + self.cli_set(['pki', 'ca', 'smoketest', 'private', 'key', valid_ca_private_key.replace('\n','')]) + + # Valid cert + self.cli_set(['pki', 'certificate', 'smoketest', 'certificate', valid_cert.replace('\n','')]) + self.cli_set(['pki', 'certificate', 'smoketest', 'private', 'key', valid_cert_private_key.replace('\n','')]) + + def test_01_lb_reverse_proxy_domain(self): + domains_bk_first = ['n1.example.com', 'n2.example.com', 'n3.example.com'] + domain_bk_second = 'n5.example.com' + frontend = 'https_front' + front_port = '4433' + bk_server_first = '192.0.2.11' + bk_server_second = '192.0.2.12' + bk_first_name = 'bk-01' + bk_second_name = 'bk-02' + bk_server_port = '9090' + mode = 'http' + rule_ten = '10' + rule_twenty = '20' + rule_thirty = '30' + send_proxy = 'send-proxy' + max_connections = '1000' + + back_base = base_path + ['backend'] + + self.cli_set(base_path + ['service', frontend, 'mode', mode]) + self.cli_set(base_path + ['service', frontend, 'port', front_port]) + for domain in domains_bk_first: + self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'domain-name', domain]) + self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'set', 'backend', bk_first_name]) + self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'domain-name', domain_bk_second]) + self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'set', 'backend', bk_second_name]) + self.cli_set(base_path + ['service', frontend, 'rule', rule_thirty, 'url-path', 'end', '/test']) + self.cli_set(base_path + ['service', frontend, 'rule', rule_thirty, 'set', 'backend', bk_second_name]) + + self.cli_set(back_base + [bk_first_name, 'mode', mode]) + self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'address', bk_server_first]) + self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'port', bk_server_port]) + self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, send_proxy]) + + self.cli_set(back_base + [bk_second_name, 'mode', mode]) + self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'address', bk_server_second]) + self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'port', bk_server_port]) + self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'backup']) + + self.cli_set(base_path + ['global-parameters', 'max-connections', max_connections]) + + # commit changes + self.cli_commit() + + config = read_file(HAPROXY_CONF) + + # Global + self.assertIn(f'maxconn {max_connections}', config) + + # Frontend + self.assertIn(f'frontend {frontend}', config) + self.assertIn(f'bind [::]:{front_port} v4v6', config) + self.assertIn(f'mode {mode}', config) + for domain in domains_bk_first: + self.assertIn(f'acl {rule_ten} hdr(host) -i {domain}', config) + self.assertIn(f'use_backend {bk_first_name} if {rule_ten}', config) + self.assertIn(f'acl {rule_twenty} hdr(host) -i {domain_bk_second}', config) + self.assertIn(f'use_backend {bk_second_name} if {rule_twenty}', config) + self.assertIn(f'acl {rule_thirty} path -i -m end /test', config) + self.assertIn(f'use_backend {bk_second_name} if {rule_thirty}', config) + + # Backend + self.assertIn(f'backend {bk_first_name}', config) + self.assertIn(f'balance roundrobin', config) + self.assertIn(f'option forwardfor', config) + self.assertIn('http-request add-header X-Forwarded-Proto https if { ssl_fc }', config) + self.assertIn(f'mode {mode}', config) + self.assertIn(f'server {bk_first_name} {bk_server_first}:{bk_server_port} send-proxy', config) + + self.assertIn(f'backend {bk_second_name}', config) + self.assertIn(f'mode {mode}', config) + self.assertIn(f'server {bk_second_name} {bk_server_second}:{bk_server_port}', config) + self.assertIn(f'server {bk_second_name} {bk_server_second}:{bk_server_port} backup', config) + + def test_02_lb_reverse_proxy_cert_not_exists(self): + self.base_config() + self.cli_set(base_path + ['service', 'https_front', 'ssl', 'certificate', 'cert']) + + with self.assertRaises(ConfigSessionError) as e: + self.cli_commit() + # self.assertIn('\nCertificates does not exist in PKI\n', str(e.exception)) + + self.cli_delete(base_path) + self.configure_pki() + + self.base_config() + self.cli_set(base_path + ['service', 'https_front', 'ssl', 'certificate', 'cert']) + + with self.assertRaises(ConfigSessionError) as e: + self.cli_commit() + # self.assertIn('\nCertificate "cert" does not exist\n', str(e.exception)) + + self.cli_delete(base_path + ['service', 'https_front', 'ssl', 'certificate', 'cert']) + self.cli_set(base_path + ['service', 'https_front', 'ssl', 'certificate', 'smoketest']) + self.cli_commit() + + def test_03_lb_reverse_proxy_ca_not_exists(self): + self.base_config() + self.cli_set(base_path + ['backend', 'bk-01', 'ssl', 'ca-certificate', 'ca-test']) + + with self.assertRaises(ConfigSessionError) as e: + self.cli_commit() + # self.assertIn('\nCA certificates does not exist in PKI\n', str(e.exception)) + + self.cli_delete(base_path) + self.configure_pki() + + self.base_config() + self.cli_set(base_path + ['backend', 'bk-01', 'ssl', 'ca-certificate', 'ca-test']) + + with self.assertRaises(ConfigSessionError) as e: + self.cli_commit() + # self.assertIn('\nCA certificate "ca-test" does not exist\n', str(e.exception)) + + self.cli_delete(base_path + ['backend', 'bk-01', 'ssl', 'ca-certificate', 'ca-test']) + self.cli_set(base_path + ['backend', 'bk-01', 'ssl', 'ca-certificate', 'smoketest']) + self.cli_commit() + + def test_04_lb_reverse_proxy_backend_ssl_no_verify(self): + # Setup base + self.configure_pki() + self.base_config() + + # Set no-verify option + self.cli_set(base_path + ['backend', 'bk-01', 'ssl', 'no-verify']) + self.cli_commit() + + # Test no-verify option + config = read_file(HAPROXY_CONF) + self.assertIn('server bk-01 192.0.2.11:9090 send-proxy ssl verify none', config) + + # Test setting ca-certificate alongside no-verify option fails, to test config validation + self.cli_set(base_path + ['backend', 'bk-01', 'ssl', 'ca-certificate', 'smoketest']) + with self.assertRaises(ConfigSessionError) as e: + self.cli_commit() + + def test_05_lb_reverse_proxy_backend_http_check(self): + # Setup base + self.base_config() + + # Set http-check + self.cli_set(base_path + ['backend', 'bk-01', 'http-check', 'method', 'get']) + self.cli_commit() + + # Test http-check + config = read_file(HAPROXY_CONF) + self.assertIn('option httpchk', config) + self.assertIn('http-check send meth GET', config) + + # Set http-check with uri and status + self.cli_set(base_path + ['backend', 'bk-01', 'http-check', 'uri', '/health']) + self.cli_set(base_path + ['backend', 'bk-01', 'http-check', 'expect', 'status', '200']) + self.cli_commit() + + # Test http-check with uri and status + config = read_file(HAPROXY_CONF) + self.assertIn('option httpchk', config) + self.assertIn('http-check send meth GET uri /health', config) + self.assertIn('http-check expect status 200', config) + + # Set http-check with string + self.cli_delete(base_path + ['backend', 'bk-01', 'http-check', 'expect', 'status', '200']) + self.cli_set(base_path + ['backend', 'bk-01', 'http-check', 'expect', 'string', 'success']) + self.cli_commit() + + # Test http-check with string + config = read_file(HAPROXY_CONF) + self.assertIn('option httpchk', config) + self.assertIn('http-check send meth GET uri /health', config) + self.assertIn('http-check expect string success', config) + + # Test configuring both http-check & health-check fails validation script + self.cli_set(base_path + ['backend', 'bk-01', 'health-check', 'ldap']) + with self.assertRaises(ConfigSessionError) as e: + self.cli_commit() + + def test_06_lb_reverse_proxy_tcp_mode(self): + frontend = 'tcp_8443' + mode = 'tcp' + front_port = '8433' + tcp_request_delay = "5000" + rule_thirty = '30' + domain_bk = 'n6.example.com' + ssl_opt = "req-ssl-sni" + bk_name = 'bk-03' + bk_server = '192.0.2.11' + bk_server_port = '9090' + + back_base = base_path + ['backend'] + + self.cli_set(base_path + ['service', frontend, 'mode', mode]) + self.cli_set(base_path + ['service', frontend, 'port', front_port]) + self.cli_set(base_path + ['service', frontend, 'tcp-request', 'inspect-delay', tcp_request_delay]) + + self.cli_set(base_path + ['service', frontend, 'rule', rule_thirty, 'domain-name', domain_bk]) + self.cli_set(base_path + ['service', frontend, 'rule', rule_thirty, 'ssl', ssl_opt]) + self.cli_set(base_path + ['service', frontend, 'rule', rule_thirty, 'set', 'backend', bk_name]) + + self.cli_set(back_base + [bk_name, 'mode', mode]) + self.cli_set(back_base + [bk_name, 'server', bk_name, 'address', bk_server]) + self.cli_set(back_base + [bk_name, 'server', bk_name, 'port', bk_server_port]) + + # commit changes + self.cli_commit() + + config = read_file(HAPROXY_CONF) + + # Frontend + self.assertIn(f'frontend {frontend}', config) + self.assertIn(f'bind [::]:{front_port} v4v6', config) + self.assertIn(f'mode {mode}', config) + + self.assertIn(f'tcp-request inspect-delay {tcp_request_delay}', config) + self.assertIn(f"tcp-request content accept if {{ req_ssl_hello_type 1 }}", config) + self.assertIn(f'acl {rule_thirty} req_ssl_sni -i {domain_bk}', config) + self.assertIn(f'use_backend {bk_name} if {rule_thirty}', config) + + # Backend + self.assertIn(f'backend {bk_name}', config) + self.assertIn(f'balance roundrobin', config) + self.assertIn(f'mode {mode}', config) + self.assertIn(f'server {bk_name} {bk_server}:{bk_server_port}', config) + + def test_07_lb_reverse_proxy_http_response_headers(self): + # Setup base + self.configure_pki() + self.base_config() + + # Set example headers in both frontend and backend + self.cli_set(base_path + ['service', 'https_front', 'http-response-headers', 'Cache-Control', 'value', 'max-age=604800']) + self.cli_set(base_path + ['backend', 'bk-01', 'http-response-headers', 'Proxy-Backend-ID', 'value', 'bk-01']) + self.cli_commit() + + # Test headers are present in generated configuration file + config = read_file(HAPROXY_CONF) + self.assertIn('http-response set-header Cache-Control \'max-age=604800\'', config) + self.assertIn('http-response set-header Proxy-Backend-ID \'bk-01\'', config) + + # Test setting alongside modes other than http is blocked by validation conditions + self.cli_set(base_path + ['service', 'https_front', 'mode', 'tcp']) + with self.assertRaises(ConfigSessionError) as e: + self.cli_commit() + + def test_08_lb_reverse_proxy_tcp_health_checks(self): + # Setup PKI + self.configure_pki() + + # Define variables + frontend = 'fe_ldaps' + mode = 'tcp' + health_check = 'ldap' + front_port = '636' + bk_name = 'bk_ldap' + bk_servers = ['192.0.2.11', '192.0.2.12'] + bk_server_port = '389' + + # Configure frontend + self.cli_set(base_path + ['service', frontend, 'mode', mode]) + self.cli_set(base_path + ['service', frontend, 'port', front_port]) + self.cli_set(base_path + ['service', frontend, 'ssl', 'certificate', 'smoketest']) + + # Configure backend + self.cli_set(base_path + ['backend', bk_name, 'mode', mode]) + self.cli_set(base_path + ['backend', bk_name, 'health-check', health_check]) + for index, bk_server in enumerate(bk_servers): + self.cli_set(base_path + ['backend', bk_name, 'server', f'srv-{index}', 'address', bk_server]) + self.cli_set(base_path + ['backend', bk_name, 'server', f'srv-{index}', 'port', bk_server_port]) + + # Commit & read config + self.cli_commit() + config = read_file(HAPROXY_CONF) + + # Validate Frontend + self.assertIn(f'frontend {frontend}', config) + self.assertIn(f'bind [::]:{front_port} v4v6 ssl crt /run/haproxy/smoketest.pem', config) + self.assertIn(f'mode {mode}', config) + self.assertIn(f'backend {bk_name}', config) + + # Validate Backend + self.assertIn(f'backend {bk_name}', config) + self.assertIn(f'option {health_check}-check', config) + self.assertIn(f'mode {mode}', config) + for index, bk_server in enumerate(bk_servers): + self.assertIn(f'server srv-{index} {bk_server}:{bk_server_port}', config) + + # Validate SMTP option renders correctly + self.cli_set(base_path + ['backend', bk_name, 'health-check', 'smtp']) + self.cli_commit() + config = read_file(HAPROXY_CONF) + self.assertIn(f'option smtpchk', config) + + def test_09_lb_reverse_proxy_logging(self): + # Setup base + self.base_config() + self.cli_commit() + + # Ensure default logging configuration is present + config = read_file(HAPROXY_CONF) + + # Test global-parameters logging options + self.cli_set(base_path + ['global-parameters', 'logging', 'facility', 'local1', 'level', 'err']) + self.cli_set(base_path + ['global-parameters', 'logging', 'facility', 'local2', 'level', 'warning']) + self.cli_commit() + + # Test global logging parameters are generated in configuration file + config = read_file(HAPROXY_CONF) + self.assertIn('log /dev/log local1 err', config) + self.assertIn('log /dev/log local2 warning', config) + + # Test backend logging options + backend_path = base_path + ['backend', 'bk-01'] + self.cli_set(backend_path + ['logging', 'facility', 'local3', 'level', 'debug']) + self.cli_set(backend_path + ['logging', 'facility', 'local4', 'level', 'info']) + self.cli_commit() + + # Test backend logging parameters are generated in configuration file + config = read_file(HAPROXY_CONF) + self.assertIn('log /dev/log local3 debug', config) + self.assertIn('log /dev/log local4 info', config) + + # Test service logging options + service_path = base_path + ['service', 'https_front'] + self.cli_set(service_path + ['logging', 'facility', 'local5', 'level', 'notice']) + self.cli_set(service_path + ['logging', 'facility', 'local6', 'level', 'crit']) + self.cli_commit() + + # Test service logging parameters are generated in configuration file + config = read_file(HAPROXY_CONF) + self.assertIn('log /dev/log local5 notice', config) + self.assertIn('log /dev/log local6 crit', config) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_load-balancing_wan.py b/smoketest/scripts/cli/test_load-balancing_wan.py new file mode 100644 index 0000000..92b4000 --- /dev/null +++ b/smoketest/scripts/cli/test_load-balancing_wan.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest +import time + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.utils.process import call +from vyos.utils.process import cmd + +base_path = ['load-balancing'] + +def create_netns(name): + return call(f'sudo ip netns add {name}') + +def create_veth_pair(local='veth0', peer='ceth0'): + return call(f'sudo ip link add {local} type veth peer name {peer}') + +def move_interface_to_netns(iface, netns_name): + return call(f'sudo ip link set {iface} netns {netns_name}') + +def rename_interface(iface, new_name): + return call(f'sudo ip link set {iface} name {new_name}') + +def cmd_in_netns(netns, cmd): + return call(f'sudo ip netns exec {netns} {cmd}') + +def delete_netns(name): + return call(f'sudo ip netns del {name}') + +class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestLoadBalancingWan, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_table_routes(self): + ns1 = 'ns201' + ns2 = 'ns202' + ns3 = 'ns203' + iface1 = 'eth201' + iface2 = 'eth202' + iface3 = 'eth203' + container_iface1 = 'ceth0' + container_iface2 = 'ceth1' + container_iface3 = 'ceth2' + + # Create network namespeces + create_netns(ns1) + create_netns(ns2) + create_netns(ns3) + create_veth_pair(iface1, container_iface1) + create_veth_pair(iface2, container_iface2) + create_veth_pair(iface3, container_iface3) + + move_interface_to_netns(container_iface1, ns1) + move_interface_to_netns(container_iface2, ns2) + move_interface_to_netns(container_iface3, ns3) + call(f'sudo ip address add 203.0.113.10/24 dev {iface1}') + call(f'sudo ip address add 192.0.2.10/24 dev {iface2}') + call(f'sudo ip address add 198.51.100.10/24 dev {iface3}') + call(f'sudo ip link set dev {iface1} up') + call(f'sudo ip link set dev {iface2} up') + call(f'sudo ip link set dev {iface3} up') + cmd_in_netns(ns1, f'ip link set {container_iface1} name eth0') + cmd_in_netns(ns2, f'ip link set {container_iface2} name eth0') + cmd_in_netns(ns3, f'ip link set {container_iface3} name eth0') + cmd_in_netns(ns1, 'ip address add 203.0.113.1/24 dev eth0') + cmd_in_netns(ns2, 'ip address add 192.0.2.1/24 dev eth0') + cmd_in_netns(ns3, 'ip address add 198.51.100.1/24 dev eth0') + cmd_in_netns(ns1, 'ip link set dev eth0 up') + cmd_in_netns(ns2, 'ip link set dev eth0 up') + cmd_in_netns(ns3, 'ip link set dev eth0 up') + + # Set load-balancing configuration + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'failure-count', '2']) + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'nexthop', '203.0.113.1']) + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'success-count', '1']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'failure-count', '2']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'nexthop', '192.0.2.1']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'success-count', '1']) + + self.cli_set(base_path + ['wan', 'rule', '10', 'inbound-interface', iface3]) + self.cli_set(base_path + ['wan', 'rule', '10', 'source', 'address', '198.51.100.0/24']) + + + # commit changes + self.cli_commit() + + time.sleep(5) + # Check default routes in tables 201, 202 + # Expected values + original = 'default via 203.0.113.1 dev eth201' + tmp = cmd('sudo ip route show table 201') + self.assertEqual(tmp, original) + + original = 'default via 192.0.2.1 dev eth202' + tmp = cmd('sudo ip route show table 202') + self.assertEqual(tmp, original) + + # Delete veth interfaces and netns + for iface in [iface1, iface2, iface3]: + call(f'sudo ip link del dev {iface}') + + delete_netns(ns1) + delete_netns(ns2) + delete_netns(ns3) + + def test_check_chains(self): + + ns1 = 'nsA' + ns2 = 'nsB' + ns3 = 'nsC' + iface1 = 'veth1' + iface2 = 'veth2' + iface3 = 'veth3' + container_iface1 = 'ceth0' + container_iface2 = 'ceth1' + container_iface3 = 'ceth2' + mangle_isp1 = """table ip mangle { + chain ISP_veth1 { + counter ct mark set 0xc9 + counter meta mark set 0xc9 + counter accept + } +}""" + mangle_isp2 = """table ip mangle { + chain ISP_veth2 { + counter ct mark set 0xca + counter meta mark set 0xca + counter accept + } +}""" + mangle_prerouting = """table ip mangle { + chain PREROUTING { + type filter hook prerouting priority mangle; policy accept; + counter jump WANLOADBALANCE_PRE + } +}""" + mangle_wanloadbalance_pre = """table ip mangle { + chain WANLOADBALANCE_PRE { + iifname "veth3" ip saddr 198.51.100.0/24 ct state new meta random & 2147483647 < 1073741824 counter jump ISP_veth1 + iifname "veth3" ip saddr 198.51.100.0/24 ct state new counter jump ISP_veth2 + iifname "veth3" ip saddr 198.51.100.0/24 counter meta mark set ct mark + } +}""" + nat_wanloadbalance = """table ip nat { + chain WANLOADBALANCE { + ct mark 0xc9 counter snat to 203.0.113.10 + ct mark 0xca counter snat to 192.0.2.10 + } +}""" + nat_vyos_pre_snat_hook = """table ip nat { + chain VYOS_PRE_SNAT_HOOK { + type nat hook postrouting priority srcnat - 1; policy accept; + counter jump WANLOADBALANCE + } +}""" + + # Create network namespeces + create_netns(ns1) + create_netns(ns2) + create_netns(ns3) + create_veth_pair(iface1, container_iface1) + create_veth_pair(iface2, container_iface2) + create_veth_pair(iface3, container_iface3) + move_interface_to_netns(container_iface1, ns1) + move_interface_to_netns(container_iface2, ns2) + move_interface_to_netns(container_iface3, ns3) + call(f'sudo ip address add 203.0.113.10/24 dev {iface1}') + call(f'sudo ip address add 192.0.2.10/24 dev {iface2}') + call(f'sudo ip address add 198.51.100.10/24 dev {iface3}') + + for iface in [iface1, iface2, iface3]: + call(f'sudo ip link set dev {iface} up') + + cmd_in_netns(ns1, f'ip link set {container_iface1} name eth0') + cmd_in_netns(ns2, f'ip link set {container_iface2} name eth0') + cmd_in_netns(ns3, f'ip link set {container_iface3} name eth0') + cmd_in_netns(ns1, 'ip address add 203.0.113.1/24 dev eth0') + cmd_in_netns(ns2, 'ip address add 192.0.2.1/24 dev eth0') + cmd_in_netns(ns3, 'ip address add 198.51.100.1/24 dev eth0') + cmd_in_netns(ns1, 'ip link set dev eth0 up') + cmd_in_netns(ns2, 'ip link set dev eth0 up') + cmd_in_netns(ns3, 'ip link set dev eth0 up') + + # Set load-balancing configuration + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'failure-count', '2']) + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'nexthop', '203.0.113.1']) + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'success-count', '1']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'failure-count', '2']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'nexthop', '192.0.2.1']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'success-count', '1']) + self.cli_set(base_path + ['wan', 'rule', '10', 'inbound-interface', iface3]) + self.cli_set(base_path + ['wan', 'rule', '10', 'source', 'address', '198.51.100.0/24']) + self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface1]) + self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface2]) + + # commit changes + self.cli_commit() + + time.sleep(5) + + # Check mangle chains + tmp = cmd(f'sudo nft -s list chain mangle ISP_{iface1}') + self.assertEqual(tmp, mangle_isp1) + + tmp = cmd(f'sudo nft -s list chain mangle ISP_{iface2}') + self.assertEqual(tmp, mangle_isp2) + + tmp = cmd(f'sudo nft -s list chain mangle PREROUTING') + self.assertEqual(tmp, mangle_prerouting) + + tmp = cmd(f'sudo nft -s list chain mangle WANLOADBALANCE_PRE') + self.assertEqual(tmp, mangle_wanloadbalance_pre) + + # Check nat chains + tmp = cmd(f'sudo nft -s list chain nat WANLOADBALANCE') + self.assertEqual(tmp, nat_wanloadbalance) + + tmp = cmd(f'sudo nft -s list chain nat VYOS_PRE_SNAT_HOOK') + self.assertEqual(tmp, nat_vyos_pre_snat_hook) + + # Delete veth interfaces and netns + for iface in [iface1, iface2, iface3]: + call(f'sudo ip link del dev {iface}') + + delete_netns(ns1) + delete_netns(ns2) + delete_netns(ns3) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py new file mode 100644 index 0000000..5161e47 --- /dev/null +++ b/smoketest/scripts/cli/test_nat.py @@ -0,0 +1,308 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError + +base_path = ['nat'] +src_path = base_path + ['source'] +dst_path = base_path + ['destination'] +static_path = base_path + ['static'] + +nftables_nat_config = '/run/nftables_nat.conf' +nftables_static_nat_conf = '/run/nftables_static-nat-rules.nft' + +class TestNAT(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestNAT, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + self.assertFalse(os.path.exists(nftables_nat_config)) + self.assertFalse(os.path.exists(nftables_static_nat_conf)) + + def wait_for_domain_resolver(self, table, set_name, element, max_wait=10): + # Resolver no longer blocks commit, need to wait for daemon to populate set + count = 0 + while count < max_wait: + code = run(f'sudo nft get element {table} {set_name} {{ {element} }}') + if code == 0: + return True + count += 1 + sleep(1) + return False + + def test_snat(self): + rules = ['100', '110', '120', '130', '200', '210', '220', '230'] + outbound_iface_100 = 'eth0' + outbound_iface_200 = 'eth1' + + nftables_search = ['jump VYOS_PRE_SNAT_HOOK'] + + for rule in rules: + network = f'192.168.{rule}.0/24' + # depending of rule order we check either for source address for NAT + # or configured destination address for NAT + if int(rule) < 200: + self.cli_set(src_path + ['rule', rule, 'source', 'address', network]) + self.cli_set(src_path + ['rule', rule, 'outbound-interface', 'name', outbound_iface_100]) + self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade']) + nftables_search.append([f'saddr {network}', f'oifname "{outbound_iface_100}"', 'masquerade']) + else: + self.cli_set(src_path + ['rule', rule, 'destination', 'address', network]) + self.cli_set(src_path + ['rule', rule, 'outbound-interface', 'name', outbound_iface_200]) + self.cli_set(src_path + ['rule', rule, 'exclude']) + nftables_search.append([f'daddr {network}', f'oifname "{outbound_iface_200}"', 'return']) + + self.cli_commit() + + self.verify_nftables(nftables_search, 'ip vyos_nat') + + def test_snat_groups(self): + address_group = 'smoketest_addr' + address_group_member = '192.0.2.1' + interface_group = 'smoketest_ifaces' + interface_group_member = 'bond.99' + + self.cli_set(['firewall', 'group', 'address-group', address_group, 'address', address_group_member]) + self.cli_set(['firewall', 'group', 'interface-group', interface_group, 'interface', interface_group_member]) + + self.cli_set(src_path + ['rule', '100', 'source', 'group', 'address-group', address_group]) + self.cli_set(src_path + ['rule', '100', 'outbound-interface', 'group', interface_group]) + self.cli_set(src_path + ['rule', '100', 'translation', 'address', 'masquerade']) + + self.cli_set(src_path + ['rule', '110', 'source', 'group', 'address-group', address_group]) + self.cli_set(src_path + ['rule', '110', 'translation', 'address', '203.0.113.1']) + + self.cli_set(src_path + ['rule', '120', 'source', 'group', 'address-group', address_group]) + self.cli_set(src_path + ['rule', '120', 'translation', 'address', '203.0.113.111/32']) + + self.cli_commit() + + nftables_search = [ + [f'set A_{address_group}'], + [f'elements = {{ {address_group_member} }}'], + [f'ip saddr @A_{address_group}', f'oifname @I_{interface_group}', 'masquerade'], + [f'ip saddr @A_{address_group}', 'snat to 203.0.113.1'], + [f'ip saddr @A_{address_group}', 'snat prefix to 203.0.113.111/32'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_nat') + + self.cli_delete(['firewall']) + + def test_dnat(self): + rules = ['100', '110', '120', '130', '200', '210', '220', '230'] + inbound_iface_100 = 'eth0' + inbound_iface_200 = 'eth1' + inbound_proto_100 = 'udp' + inbound_proto_200 = 'tcp' + + nftables_search = ['jump VYOS_PRE_DNAT_HOOK'] + + for rule in rules: + port = f'10{rule}' + self.cli_set(dst_path + ['rule', rule, 'source', 'port', port]) + self.cli_set(dst_path + ['rule', rule, 'translation', 'address', '192.0.2.1']) + self.cli_set(dst_path + ['rule', rule, 'translation', 'port', port]) + rule_search = [f'dnat to 192.0.2.1:{port}'] + if int(rule) < 200: + self.cli_set(dst_path + ['rule', rule, 'protocol', inbound_proto_100]) + self.cli_set(dst_path + ['rule', rule, 'inbound-interface', 'name', inbound_iface_100]) + rule_search.append(f'{inbound_proto_100} sport {port}') + rule_search.append(f'iifname "{inbound_iface_100}"') + else: + self.cli_set(dst_path + ['rule', rule, 'protocol', inbound_proto_200]) + self.cli_set(dst_path + ['rule', rule, 'inbound-interface', 'name', inbound_iface_200]) + rule_search.append(f'iifname "{inbound_iface_200}"') + + nftables_search.append(rule_search) + + self.cli_commit() + + self.verify_nftables(nftables_search, 'ip vyos_nat') + + def test_snat_required_translation_address(self): + # T2813: Ensure translation address is specified + rule = '5' + self.cli_set(src_path + ['rule', rule, 'source', 'address', '192.0.2.0/24']) + + # check validate() - translation address not specified + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade']) + self.cli_commit() + + def test_dnat_negated_addresses(self): + # T3186: negated addresses are not accepted by nftables + rule = '1000' + self.cli_set(dst_path + ['rule', rule, 'destination', 'address', '!192.0.2.1']) + self.cli_set(dst_path + ['rule', rule, 'destination', 'port', '53']) + self.cli_set(dst_path + ['rule', rule, 'inbound-interface', 'name', 'eth0']) + self.cli_set(dst_path + ['rule', rule, 'protocol', 'tcp_udp']) + self.cli_set(dst_path + ['rule', rule, 'source', 'address', '!192.0.2.1']) + self.cli_set(dst_path + ['rule', rule, 'translation', 'address', '192.0.2.1']) + self.cli_set(dst_path + ['rule', rule, 'translation', 'port', '53']) + self.cli_commit() + + def test_nat_no_rules(self): + # T3206: deleting all rules but keep the direction 'destination' or + # 'source' resulteds in KeyError: 'rule'. + # + # Test that both 'nat destination' and 'nat source' nodes can exist + # without any rule + self.cli_set(src_path) + self.cli_set(dst_path) + self.cli_set(static_path) + self.cli_commit() + + def test_dnat_without_translation_address(self): + self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'name', 'eth1']) + self.cli_set(dst_path + ['rule', '1', 'destination', 'port', '443']) + self.cli_set(dst_path + ['rule', '1', 'protocol', 'tcp']) + self.cli_set(dst_path + ['rule', '1', 'packet-type', 'host']) + self.cli_set(dst_path + ['rule', '1', 'translation', 'port', '443']) + + self.cli_commit() + + nftables_search = [ + ['iifname "eth1"', 'tcp dport 443', 'pkttype host', 'dnat to :443'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_nat') + + def test_static_nat(self): + dst_addr_1 = '10.0.1.1' + translate_addr_1 = '192.168.1.1' + dst_addr_2 = '203.0.113.0/24' + translate_addr_2 = '192.0.2.0/24' + ifname = 'eth0' + + self.cli_set(static_path + ['rule', '10', 'destination', 'address', dst_addr_1]) + self.cli_set(static_path + ['rule', '10', 'inbound-interface', ifname]) + self.cli_set(static_path + ['rule', '10', 'translation', 'address', translate_addr_1]) + + self.cli_set(static_path + ['rule', '20', 'destination', 'address', dst_addr_2]) + self.cli_set(static_path + ['rule', '20', 'inbound-interface', ifname]) + self.cli_set(static_path + ['rule', '20', 'translation', 'address', translate_addr_2]) + + self.cli_commit() + + nftables_search = [ + [f'iifname "{ifname}"', f'ip daddr {dst_addr_1}', f'dnat to {translate_addr_1}'], + [f'oifname "{ifname}"', f'ip saddr {translate_addr_1}', f'snat to {dst_addr_1}'], + [f'iifname "{ifname}"', f'dnat ip prefix to ip daddr map {{ {dst_addr_2} : {translate_addr_2} }}'], + [f'oifname "{ifname}"', f'snat ip prefix to ip saddr map {{ {translate_addr_2} : {dst_addr_2} }}'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_static_nat') + + def test_dnat_redirect(self): + dst_addr_1 = '10.0.1.1' + dest_port = '5122' + protocol = 'tcp' + redirected_port = '22' + ifname = 'eth0' + + self.cli_set(dst_path + ['rule', '10', 'destination', 'address', dst_addr_1]) + self.cli_set(dst_path + ['rule', '10', 'destination', 'port', dest_port]) + self.cli_set(dst_path + ['rule', '10', 'protocol', protocol]) + self.cli_set(dst_path + ['rule', '10', 'inbound-interface', 'name', ifname]) + self.cli_set(dst_path + ['rule', '10', 'translation', 'redirect', 'port', redirected_port]) + + self.cli_set(dst_path + ['rule', '20', 'destination', 'address', dst_addr_1]) + self.cli_set(dst_path + ['rule', '20', 'destination', 'port', dest_port]) + self.cli_set(dst_path + ['rule', '20', 'protocol', protocol]) + self.cli_set(dst_path + ['rule', '20', 'inbound-interface', 'name', ifname]) + self.cli_set(dst_path + ['rule', '20', 'translation', 'redirect']) + + self.cli_commit() + + nftables_search = [ + [f'iifname "{ifname}"', f'ip daddr {dst_addr_1}', f'{protocol} dport {dest_port}', f'redirect to :{redirected_port}'], + [f'iifname "{ifname}"', f'ip daddr {dst_addr_1}', f'{protocol} dport {dest_port}', f'redirect'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_nat') + + def test_nat_balance(self): + ifname = 'eth0' + member_1 = '198.51.100.1' + weight_1 = '10' + member_2 = '198.51.100.2' + weight_2 = '90' + member_3 = '192.0.2.1' + weight_3 = '35' + member_4 = '192.0.2.2' + weight_4 = '65' + dst_port = '443' + + self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'name', ifname]) + self.cli_set(dst_path + ['rule', '1', 'protocol', 'tcp']) + self.cli_set(dst_path + ['rule', '1', 'destination', 'port', dst_port]) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'source-address']) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'source-port']) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'destination-address']) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'destination-port']) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'backend', member_1, 'weight', weight_1]) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'backend', member_2, 'weight', weight_2]) + + self.cli_set(src_path + ['rule', '1', 'outbound-interface', 'name', ifname]) + self.cli_set(src_path + ['rule', '1', 'load-balance', 'hash', 'random']) + self.cli_set(src_path + ['rule', '1', 'load-balance', 'backend', member_3, 'weight', weight_3]) + self.cli_set(src_path + ['rule', '1', 'load-balance', 'backend', member_4, 'weight', weight_4]) + + self.cli_commit() + + nftables_search = [ + [f'iifname "{ifname}"', f'tcp dport {dst_port}', f'dnat to jhash ip saddr . tcp sport . ip daddr . tcp dport mod 100 map', f'0-9 : {member_1}, 10-99 : {member_2}'], + [f'oifname "{ifname}"', f'snat to numgen random mod 100 map', f'0-34 : {member_3}, 35-99 : {member_4}'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_nat') + + def test_snat_net_port_map(self): + self.cli_set(src_path + ['rule', '10', 'protocol', 'tcp_udp']) + self.cli_set(src_path + ['rule', '10', 'source', 'address', '100.64.0.0/25']) + self.cli_set(src_path + ['rule', '10', 'translation', 'address', '203.0.113.0/25']) + self.cli_set(src_path + ['rule', '10', 'translation', 'port', '1025-3072']) + + self.cli_set(src_path + ['rule', '20', 'protocol', 'tcp_udp']) + self.cli_set(src_path + ['rule', '20', 'source', 'address', '100.64.0.128/25']) + self.cli_set(src_path + ['rule', '20', 'translation', 'address', '203.0.113.128/25']) + self.cli_set(src_path + ['rule', '20', 'translation', 'port', '1025-3072']) + + self.cli_commit() + + nftables_search = [ + ['meta l4proto { tcp, udp }', 'snat ip prefix to ip saddr map { 100.64.0.0/25 : 203.0.113.0/25 . 1025-3072 }', 'comment "SRC-NAT-10"'], + ['meta l4proto { tcp, udp }', 'snat ip prefix to ip saddr map { 100.64.0.128/25 : 203.0.113.128/25 . 1025-3072 }', 'comment "SRC-NAT-20"'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_nat') + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_nat64.py b/smoketest/scripts/cli/test_nat64.py new file mode 100644 index 0000000..5c907f6 --- /dev/null +++ b/smoketest/scripts/cli/test_nat64.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json +import os +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +base_path = ['nat64'] +src_path = base_path + ['source'] + +jool_nat64_config = '/run/jool/instance-100.json' + +class TestNAT64(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestNAT64, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + self.assertFalse(os.path.exists(jool_nat64_config)) + + def test_snat64(self): + rule = '100' + translation_rule = '10' + prefix_v6 = '64:ff9b::/96' + pool = '192.0.2.10' + pool_port = '1-65535' + + self.cli_set(src_path + ['rule', rule, 'source', 'prefix', prefix_v6]) + self.cli_set( + src_path + + ['rule', rule, 'translation', 'pool', translation_rule, 'address', pool] + ) + self.cli_set( + src_path + + ['rule', rule, 'translation', 'pool', translation_rule, 'port', pool_port] + ) + self.cli_commit() + + # Load the JSON file + with open(f'/run/jool/instance-{rule}.json', 'r') as json_file: + config_data = json.load(json_file) + + # Assertions based on the content of the JSON file + self.assertEqual(config_data['instance'], f'instance-{rule}') + self.assertEqual(config_data['framework'], 'netfilter') + self.assertEqual(config_data['global']['pool6'], prefix_v6) + self.assertTrue(config_data['global']['manually-enabled']) + + # Check the pool4 entries + pool4_entries = config_data.get('pool4', []) + self.assertIsInstance(pool4_entries, list) + self.assertGreater(len(pool4_entries), 0) + + for entry in pool4_entries: + self.assertIn('protocol', entry) + self.assertIn('prefix', entry) + self.assertIn('port range', entry) + + protocol = entry['protocol'] + prefix = entry['prefix'] + port_range = entry['port range'] + + if protocol == 'ICMP': + self.assertEqual(prefix, pool) + self.assertEqual(port_range, pool_port) + elif protocol == 'UDP': + self.assertEqual(prefix, pool) + self.assertEqual(port_range, pool_port) + elif protocol == 'TCP': + self.assertEqual(prefix, pool) + self.assertEqual(port_range, pool_port) + else: + self.fail(f'Unexpected protocol: {protocol}') + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py new file mode 100644 index 0000000..52ad8e3 --- /dev/null +++ b/smoketest/scripts/cli/test_nat66.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError + +base_path = ['nat66'] +src_path = base_path + ['source'] +dst_path = base_path + ['destination'] + +class TestNAT66(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestNAT66, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_source_nat66(self): + source_prefix = 'fc00::/64' + translation_prefix = 'fc01::/64' + self.cli_set(src_path + ['rule', '1', 'outbound-interface', 'name', 'eth1']) + self.cli_set(src_path + ['rule', '1', 'source', 'prefix', source_prefix]) + self.cli_set(src_path + ['rule', '1', 'translation', 'address', translation_prefix]) + + self.cli_set(src_path + ['rule', '2', 'outbound-interface', 'name', 'eth1']) + self.cli_set(src_path + ['rule', '2', 'source', 'prefix', source_prefix]) + self.cli_set(src_path + ['rule', '2', 'translation', 'address', 'masquerade']) + + self.cli_set(src_path + ['rule', '3', 'outbound-interface', 'name', 'eth1']) + self.cli_set(src_path + ['rule', '3', 'source', 'prefix', source_prefix]) + self.cli_set(src_path + ['rule', '3', 'exclude']) + + self.cli_commit() + + nftables_search = [ + ['oifname "eth1"', f'ip6 saddr {source_prefix}', f'snat prefix to {translation_prefix}'], + ['oifname "eth1"', f'ip6 saddr {source_prefix}', 'masquerade'], + ['oifname "eth1"', f'ip6 saddr {source_prefix}', 'return'] + ] + + self.verify_nftables(nftables_search, 'ip6 vyos_nat') + + def test_source_nat66_address(self): + source_prefix = 'fc00::/64' + translation_address = 'fc00::1' + self.cli_set(src_path + ['rule', '1', 'outbound-interface', 'name', 'eth1']) + self.cli_set(src_path + ['rule', '1', 'source', 'prefix', source_prefix]) + self.cli_set(src_path + ['rule', '1', 'translation', 'address', translation_address]) + + # check validate() - outbound-interface must be defined + self.cli_commit() + + nftables_search = [ + ['oifname "eth1"', f'ip6 saddr {source_prefix}', f'snat to {translation_address}'] + ] + + self.verify_nftables(nftables_search, 'ip6 vyos_nat') + + def test_destination_nat66(self): + destination_address = 'fc00::1' + translation_address = 'fc01::1' + source_address = 'fc02::1' + self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'name', 'eth1']) + self.cli_set(dst_path + ['rule', '1', 'destination', 'address', destination_address]) + self.cli_set(dst_path + ['rule', '1', 'translation', 'address', translation_address]) + + self.cli_set(dst_path + ['rule', '2', 'inbound-interface', 'name', 'eth1']) + self.cli_set(dst_path + ['rule', '2', 'destination', 'address', destination_address]) + self.cli_set(dst_path + ['rule', '2', 'source', 'address', source_address]) + self.cli_set(dst_path + ['rule', '2', 'exclude']) + + # check validate() - outbound-interface must be defined + self.cli_commit() + + nftables_search = [ + ['iifname "eth1"', 'ip6 daddr fc00::1', 'dnat to fc01::1'], + ['iifname "eth1"', 'ip6 saddr fc02::1', 'ip6 daddr fc00::1', 'return'] + ] + + self.verify_nftables(nftables_search, 'ip6 vyos_nat') + + def test_destination_nat66_protocol(self): + translation_address = '2001:db8:1111::1' + source_prefix = '2001:db8:2222::/64' + dport = '4545' + sport = '8080' + tport = '5555' + proto = 'tcp' + self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'name', 'eth1']) + self.cli_set(dst_path + ['rule', '1', 'destination', 'port', dport]) + self.cli_set(dst_path + ['rule', '1', 'source', 'address', source_prefix]) + self.cli_set(dst_path + ['rule', '1', 'source', 'port', sport]) + self.cli_set(dst_path + ['rule', '1', 'protocol', proto]) + self.cli_set(dst_path + ['rule', '1', 'translation', 'address', translation_address]) + self.cli_set(dst_path + ['rule', '1', 'translation', 'port', tport]) + + # check validate() - outbound-interface must be defined + self.cli_commit() + + nftables_search = [ + ['iifname "eth1"', 'tcp dport 4545', 'ip6 saddr 2001:db8:2222::/64', 'tcp sport 8080', 'dnat to [2001:db8:1111::1]:5555'] + ] + + self.verify_nftables(nftables_search, 'ip6 vyos_nat') + + def test_destination_nat66_prefix(self): + destination_prefix = 'fc00::/64' + translation_prefix = 'fc01::/64' + self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'name', 'eth1']) + self.cli_set(dst_path + ['rule', '1', 'destination', 'address', destination_prefix]) + self.cli_set(dst_path + ['rule', '1', 'translation', 'address', translation_prefix]) + + # check validate() - outbound-interface must be defined + self.cli_commit() + + nftables_search = [ + ['iifname "eth1"', f'ip6 daddr {destination_prefix}', f'dnat prefix to {translation_prefix}'] + ] + + self.verify_nftables(nftables_search, 'ip6 vyos_nat') + + def test_destination_nat66_network_group(self): + address_group = 'smoketest_addr' + address_group_member = 'fc00::1' + network_group = 'smoketest_net' + network_group_member = 'fc00::/64' + translation_prefix = 'fc01::/64' + + self.cli_set(['firewall', 'group', 'ipv6-address-group', address_group, 'address', address_group_member]) + self.cli_set(['firewall', 'group', 'ipv6-network-group', network_group, 'network', network_group_member]) + + self.cli_set(dst_path + ['rule', '1', 'destination', 'group', 'address-group', address_group]) + self.cli_set(dst_path + ['rule', '1', 'translation', 'address', translation_prefix]) + + self.cli_set(dst_path + ['rule', '2', 'destination', 'group', 'network-group', network_group]) + self.cli_set(dst_path + ['rule', '2', 'translation', 'address', translation_prefix]) + + self.cli_commit() + + nftables_search = [ + [f'set A6_{address_group}'], + [f'elements = {{ {address_group_member} }}'], + [f'set N6_{network_group}'], + [f'elements = {{ {network_group_member} }}'], + ['ip6 daddr', f'@A6_{address_group}', 'dnat prefix to fc01::/64'], + ['ip6 daddr', f'@N6_{network_group}', 'dnat prefix to fc01::/64'] + ] + + self.verify_nftables(nftables_search, 'ip6 vyos_nat') + + + def test_destination_nat66_without_translation_address(self): + self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'name', 'eth1']) + self.cli_set(dst_path + ['rule', '1', 'destination', 'port', '443']) + self.cli_set(dst_path + ['rule', '1', 'protocol', 'tcp']) + self.cli_set(dst_path + ['rule', '1', 'translation', 'port', '443']) + + self.cli_commit() + + nftables_search = [ + ['iifname "eth1"', 'tcp dport 443', 'dnat to :443'] + ] + + self.verify_nftables(nftables_search, 'ip6 vyos_nat') + + def test_source_nat66_required_translation_prefix(self): + # T2813: Ensure translation address is specified + rule = '5' + source_prefix = 'fc00::/64' + self.cli_set(src_path + ['rule', rule, 'source', 'prefix', source_prefix]) + + # check validate() - outbound-interface must be defined + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(src_path + ['rule', rule, 'outbound-interface', 'name', 'eth0']) + + # check validate() - translation address not specified + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade']) + self.cli_commit() + + def test_source_nat66_protocol(self): + translation_address = '2001:db8:1111::1' + source_prefix = '2001:db8:2222::/64' + dport = '9999' + sport = '8080' + tport = '80' + proto = 'tcp' + self.cli_set(src_path + ['rule', '1', 'outbound-interface', 'name', 'eth1']) + self.cli_set(src_path + ['rule', '1', 'destination', 'port', dport]) + self.cli_set(src_path + ['rule', '1', 'source', 'prefix', source_prefix]) + self.cli_set(src_path + ['rule', '1', 'source', 'port', sport]) + self.cli_set(src_path + ['rule', '1', 'protocol', proto]) + self.cli_set(src_path + ['rule', '1', 'translation', 'address', translation_address]) + self.cli_set(src_path + ['rule', '1', 'translation', 'port', tport]) + + # check validate() - outbound-interface must be defined + self.cli_commit() + + nftables_search = [ + ['oifname "eth1"', 'ip6 saddr 2001:db8:2222::/64', 'tcp dport 9999', 'tcp sport 8080', 'snat to [2001:db8:1111::1]:80'] + ] + + self.verify_nftables(nftables_search, 'ip6 vyos_nat') + + def test_nat66_no_rules(self): + # T3206: deleting all rules but keep the direction 'destination' or + # 'source' resulteds in KeyError: 'rule'. + # + # Test that both 'nat destination' and 'nat source' nodes can exist + # without any rule + self.cli_set(src_path) + self.cli_set(dst_path) + self.cli_commit() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_netns.py b/smoketest/scripts/cli/test_netns.py new file mode 100644 index 0000000..2ac603a --- /dev/null +++ b/smoketest/scripts/cli/test_netns.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.utils.process import cmd +from vyos.utils.network import is_netns_interface +from vyos.utils.network import get_netns_all + +base_path = ['netns'] +interfaces = ['dum10', 'dum12', 'dum50'] + +class NetNSTest(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + self.cli_delete(base_path) + # commit changes + self.cli_commit() + + # There should be no network namespace remaining + tmp = cmd('ip netns ls') + self.assertFalse(tmp) + + super(NetNSTest, self).tearDown() + + def test_netns_create(self): + namespaces = ['mgmt', 'front', 'back'] + for netns in namespaces: + self.cli_set(base_path + ['name', netns]) + + # commit changes + self.cli_commit() + + # Verify NETNS configuration + for netns in namespaces: + self.assertIn(netns, get_netns_all()) + + def test_netns_interface(self): + netns = 'foo' + self.cli_set(base_path + ['name', netns]) + + # Set + for iface in interfaces: + self.cli_set(['interfaces', 'dummy', iface, 'netns', netns]) + + # commit changes + self.cli_commit() + + for interface in interfaces: + self.assertTrue(is_netns_interface(interface, netns)) + + # Delete + for interface in interfaces: + self.cli_delete(['interfaces', 'dummy', interface]) + + # commit changes + self.cli_commit() + + netns_iface_list = cmd(f'sudo ip netns exec {netns} ip link show') + + for interface in interfaces: + self.assertFalse(is_netns_interface(interface, netns)) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_op-mode_show.py b/smoketest/scripts/cli/test_op-mode_show.py new file mode 100644 index 0000000..62f8e88 --- /dev/null +++ b/smoketest/scripts/cli/test_op-mode_show.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.utils.process import cmd +from vyos.version import get_version + +base_path = ['show'] + +class TestOPModeShow(VyOSUnitTestSHIM.TestCase): + def test_op_mode_show_version(self): + # Retrieve output of "show version" OP-mode command + tmp = self.op_mode(base_path + ['version']) + # Validate + version = get_version() + self.assertIn(f'Version: VyOS {version}', tmp) + + def test_op_mode_show_version_kernel(self): + # Retrieve output of "show version" OP-mode command + tmp = self.op_mode(base_path + ['version', 'kernel']) + self.assertEqual(cmd('uname -r'), tmp) + + def test_op_mode_show_vrf(self): + # Retrieve output of "show version" OP-mode command + tmp = self.op_mode(base_path + ['vrf']) + # Validate + self.assertIn('VRF is not configured', tmp) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_pki.py b/smoketest/scripts/cli/test_pki.py new file mode 100644 index 0000000..02beafb --- /dev/null +++ b/smoketest/scripts/cli/test_pki.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError + +from vyos.utils.file import read_file + +base_path = ['pki'] + +valid_ca_cert = """ +MIIDgTCCAmmgAwIBAgIUeM0mATGs+sKF7ViBM6DEf9fQ19swDQYJKoZIhvcNAQEL +BQAwVzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcM +CVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzEQMA4GA1UEAwwHVnlPUyBDQTAeFw0y +MTA2MjgxMzE2NDZaFw0yNjA2MjcxMzE2NDZaMFcxCzAJBgNVBAYTAkdCMRMwEQYD +VQQIDApTb21lLVN0YXRlMRIwEAYDVQQHDAlTb21lLUNpdHkxDTALBgNVBAoMBFZ5 +T1MxEDAOBgNVBAMMB1Z5T1MgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDK98WwZIqgC6teHPSsyKLLRtboy55aisJN0D3iHJ8WGKkDmIrdCR2LI4J5 +C82ErfPOzl4Ck4vTmqh8wnuK/dhUxxzNdFJBMPHAe/E+UawYrubtJj5g8iHYowZJ +T5HQKnZbcqlPvl6EizA+etO48WGljKhpimj9/LVTp81+BtFNP4tJ/vOl+iqyJ0+P +xiqQNDJgAF18meQRKaT9CcXycsciG9snMlB1tdOR7KDbi8lJ86lOi5ukPJaiMgWE +u4UlyFVyHJ/68NvtwRhYerMoQquqDs21OXkOd8spZL6qEsxMeK8InedA7abPaxgx +ORpHguPQV4Ib5HBH9Chdb9zBMheZAgMBAAGjRTBDMA8GA1UdEwEB/wQFMAMBAf8w +IAYDVR0lAQH/BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQsFAAOCAQEAbwJZifMEDbrKPQfGLp7ZA1muM728o4EYmmE7 +9eWwH22wGMSZI7T2xr5zRlFLs+Jha917yQK4b5xBMjQRAJlHKjzNLJ+3XaGlnWja +TBJ2SC5YktrmXRAIS7PxTRk/r1bHs/D00+sEWewbFYr8Js4a1Cv4TksTNyjHx8pv +phA+KIx/4qdojTslz+oH/cakUz0M9fh2B2xsO4bab5vX+LGLCK7jjeAL4Zyjf1hD +yx+Ri79L5N8h4Q69fER4cIkW7KVKUOyjEg3N4ST56urdycmyq9bXFz5pRxuZLInA +6RRToJrL8i0aPLJ6SyMujfREfjqOxdW5vyNF5/RkY+5Nz8JMgQ== +""" + +valid_ca_private_key = """ +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK98WwZIqgC6te +HPSsyKLLRtboy55aisJN0D3iHJ8WGKkDmIrdCR2LI4J5C82ErfPOzl4Ck4vTmqh8 +wnuK/dhUxxzNdFJBMPHAe/E+UawYrubtJj5g8iHYowZJT5HQKnZbcqlPvl6EizA+ +etO48WGljKhpimj9/LVTp81+BtFNP4tJ/vOl+iqyJ0+PxiqQNDJgAF18meQRKaT9 +CcXycsciG9snMlB1tdOR7KDbi8lJ86lOi5ukPJaiMgWEu4UlyFVyHJ/68NvtwRhY +erMoQquqDs21OXkOd8spZL6qEsxMeK8InedA7abPaxgxORpHguPQV4Ib5HBH9Chd +b9zBMheZAgMBAAECggEAa/CK5L0DcAvkrd9OS9lDokFhJ1qqM1KZ9NHrJyW7gP/K +Wow0RUqEuKtAxuj8+jOcdn4PRuV6tiUIt5iiJQ/MjYF6ktTqrZq+5nPDnzXGBTZ2 +vuXYxKvgThqczD4RuJfsa8O1wR/nmit/k6q0kCVmnakJI1+laHWNZRjXUs+DXcWb +rUN5D4/5kyjvFilH1c8arfrO2O4DcwfX1zNbxicgYrGmjE5m6WCZKWdcgpBcIQSh +ZfNATfXIEZ16WmDIFZnuOEUtFAzweR2ataLQNoyaTUeEe6g+ZDtUQIGKR/f0+Z4T +/JMJfPX/vRn0l3nRJWWC7Okpa2xb0hVdBmS/op+TNQKBgQDvNGAkS4uUx8xw724k +zCKQJRnzR80AQ6b2FoqRbAevWm+i0ntsCMyvCItAQS8Bw+9fgITvsmd9SdYPncMQ +Z1oQYPk5yso/SPUyuNPXtygDxUP1xS1yja5MObqyrq2O2EzcxiVxEHGlZMLTNxNA +1tE8nF4c0nQpV/EfLtkQFnnUSwKBgQDZOA2hiLaiDlPj03S4UXDu6aUD2o07782C +UKl6A331ZhH/8zGEiUvBKg8IG/2FyCHQDC0C6rbfoarAhrRGbDHKkDTKNmThTj+I +YBkLt/5OATvqkEw8eL0nB+PY5JKH04/jE0F/YM/StUsgxvMCVhtp0u/d2Hq4V9sk +xah6oFbtKwKBgGEvs3wroWtyffLIpMSYl9Ze7Js2aekYk4ZahDQvYzPwl3jc8b5k +GN1oqEMT+MhL1j7EFb7ZikiSLkGsBGvuwd3zuG6toNxzhQP1qkRzqvNVO5ZoZV2s +iMt5jQw6AlQON7RfYSj92F6tgKaWMuFeJibtFSO6se12SIY134U0zIzfAoGAQWF7 +yNkrj4+cdICbKzdoNKEiyAwqYpYFV2oL+OvAJ/L3DAEZMHla0eNk7t3t6yyX8NUZ +Xz1imeFBUf25mVDLk9rf6NWCe8ZfnR6/qyVQaA47CJkyOSlmVa8sR4ZVDIkDUCfl +mP98zkE/QbhgQJ3GVo3lIPMdzQq0rVbJJU/Jmk0CgYEAtHRNaoKBsxKfb7N7ewla +MzwcULIORODjWM8MUXM+R50F/2uYMiTvpz6eIUVfXoFyQoioYI8kcDZ8NamiQIS7 +uZsHfKpgMDJkV3kOoZQusoDhasGQ0SOnxbz/y0XmNUtAePipH0jPY1SYUvWbvm2y +a4aWVhBFly9hi2ZeHiVxVhk= +""" + +valid_cert = """ +MIIB9zCCAZygAwIBAgIUQ5G1nyASL/YsKGyLNGhRPPQyo4kwCgYIKoZIzj0EAwIw +XjELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv +bWUtQ2l0eTENMAsGA1UECgwEVnlPUzEXMBUGA1UEAwwOVnlPUyBUZXN0IENlcnQw +HhcNMjEwNjI4MTMyNjIyWhcNMjIwNjI4MTMyNjIyWjBeMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKU29tZS1TdGF0ZTESMBAGA1UEBwwJU29tZS1DaXR5MQ0wCwYDVQQK +DARWeU9TMRcwFQYDVQQDDA5WeU9TIFRlc3QgQ2VydDBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABBsebIt+8rr2UysTpL8NnYUtmt47e3sC3H9IO8iI/N4uFrmGVgTL +E2G+RDGzZgG/r7LviJSTuE9HX7wHLcIr0SmjODA2MAwGA1UdEwEB/wQCMAAwFgYD +VR0lAQH/BAwwCgYIKwYBBQUHAwEwDgYDVR0PAQH/BAQDAgeAMAoGCCqGSM49BAMC +A0kAMEYCIQD5xK5kdC3TJ7SZrBGvzIM7E7Cil/KZJUyQDR9eFNNZVQIhALg8DTfr +wAawf8L+Ncjn/l2gd5cB0nGij0D7uYnm3zf/ +""" + +valid_dh_params = """ +MIIBCAKCAQEAnNldZCrJk5MxhFoUlvvaYmUO+TmtL0uL62H2RIHJ+O0R+8vzdGPh +6zDAzo46EJK735haUgu8+A1RTsXDOXcwBqDlVe0hYj9KaPHz1HpfNKntpoPCJAYJ +wiH8dd5zVMH+iBwEKlrfteV9vWHn0HUxgLJFSLp5o6y0qpKPREJu6k0XguGScrPa +Iw6RUwsoDy3unHfk+YeC0o040R18F75V1mXWTjQlEgM7ZO2JZkLGkhW30jB0vSHr +krFqOvtPUiyG7r3+j18IUYLTN0s+5FOCfCjvSVKibNlB1vUz5y/9Ve8roctpkRM/ +5R5FA0mtbl7U/yMSX4FRIQ/A9BlHiu4bowIBAg== +""" +valid_public_ec_key = """ +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAoInInwjlu/3+wDqvRa/Eyg3EMvB +pPyq2v4jqEtEh2n4lOCi7ZgNjr+1sQSvrn8mccpALYl3/RKOougC5oQzCg== +""" + +valid_private_rsa_key = """ +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDoAVyJPpcLBFs +2NdS1qMOSj7mwKBKVZiBN3nqbLiOvEHbVe22UMNvUFU3sGs2Ta2zXwhPF3d6vPPs +GlYTkO3XAffMSNXhjCsvWHiIOR4JrWf598Bpt+txBsxsa12kM3/HM7RDf3zdN2gT +twzrcWzu+zOTXlqJ2OSq/BRRZO9IMbQLQ1/h42GJHEr4THnY4zDqUjmMmIuiBXn4 +xoE4KFLH1+xPTVleeKvPPeJ1wsshoUjlXYOgcsrXasDUt5gtkkXsVQwR9Lvbh+Rc +BhT+tJmrX9Cwq4YAd3tLSNJARS9HanRZ8uV0RTyZsImdw1Fr5ySpG2oEp/Z5mbL6 +QYqDmQ+DAgMBAAECggEAGu7qMQf0TEJo98J3CtmwQ2Rnep+ksfdM8uVvbJ4hXs1+ +h7Mx8jr2XVoDEZLBgA17z8lSvIjvkz92mdgaZ8E5bbPAqSiSAeapf3A/0AmFIDH2 +scyxehyvVrVn6blygAvzGLr+o5hm2ZIqSySVq8jHBbQiKrT/5CCvgvcH2Rj7dMXd +T5lL73tCRJZsgvFNlxyj4Omj9Lh7SjL+tIwEQaLFbvANXrZ/BPyw4OlK8daBNg9b +5GvJSDitAVMgDEEApGYu1iNwMM4UJSQAC27eJdr+qJO6DDqktWOyWcyXrxJ9mDVK +FNbb9QNQZDj7bFfm6rCuSdH9yYe3vly+SNJqtyCiwQKBgQDvemt/57KiwQffmoKR +65NAZsQvmA4PtELYOV8NPeYH1BZN/EPmCc74iELJdQPFDYy903aRJEPGt7jfqprd +PexLwt73P/XiUjPrsbqgJqfF/EMiczxAktyW3xBt2lIWU1MUUmO1ps+ZZEg8Ks4e +K/3+FWqbwZ8drDBUT9BthUA0oQKBgQDRHxU6bu938PGweFJcIG6U21nsYaWiwCiT +LXA5vWZ+UEqz81BUye6tIcCDgeku3HvC/0ycvrBM9F4AZCjnnEvrAJHKl6e4j+C4 +IpghGQvRvQ9ihDs9JIHnaoUC1i8dE3ISbbp1r7CN+J/HnAC2OeECMJuffXdnkVWa +xRdxU+9towKBgCwFVeNyJO00DI126o+GPVA2U9Pn4JXUbgEvMqDNgw5nVx5Iw/Zy +USBwc85yexnq7rcqOv5dKzRJK2u6AbOvoVMf5DqRAFL1B2RJDGRKFscXIwQfKLE6 +DeCR6oQ3AKXn9TqkFn4axsiMnZapy6/SKGNfbnRpOCWNNGkbLtYjC3VhAoGAN0kO +ZapaaM0sOEk3DOAOHBB5j4KpNYOztmU23Cz0YcR8W2KiBCh2jxLzQFEiAp+LoJu5 +9156YX3hNB1GqySo9XHrGTJKxwJSmJucuHNUqphe7t6igqGaLkH89CkHv5oaeEDG +IMLX3FC0fSMDFSnsEJYlLl8PKDRF+2rLrcxQ6h0CgYAZllNu8a7tE6cM6QsCILQn +NjuLuZRX8/KYWRqBJxatwZXCcMe2jti1HKTVVVCyYffOFa1QcAjCPknAmAz80l3e +g6a75NnEXo0J6YLAOOxd8fD2/HidhbceCmTF+3msidIzCsBidBkgn6V5TXx2IyMS +xGsJxVHfSKeooUQn6q76sg== +""" + +valid_update_cert = """ +MIICJTCCAcugAwIBAgIUZJqjNmPfVQwePjNFBtB6WI31ThMwCgYIKoZIzj0EAwIw +VzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv +bWUtQ2l0eTENMAsGA1UECgwEVnlPUzEQMA4GA1UEAwwHdnlvcy5pbzAeFw0yMjA1 +MzExNTE3NDlaFw0yMzA1MzExNTE3NDlaMFcxCzAJBgNVBAYTAkdCMRMwEQYDVQQI +DApTb21lLVN0YXRlMRIwEAYDVQQHDAlTb21lLUNpdHkxDTALBgNVBAoMBFZ5T1Mx +EDAOBgNVBAMMB3Z5b3MuaW8wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQMe0h/ +3CdD8mEgy+klk55QfJ8R3ZycefxCn4abWjzTXz/TuCIxqb4wpRT8DZtIn4NRimFT +mODYdEDOYxFtZm37o3UwczAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDAT +BgNVHSUEDDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQUqH7KSZpzArpMFuxLXqI8e1QD +fBkwHwYDVR0jBBgwFoAUqH7KSZpzArpMFuxLXqI8e1QDfBkwCgYIKoZIzj0EAwID +SAAwRQIhAKofUgRtcUljmbubPF6sqHtn/3TRvuafl8VfPbk3s2bJAiBp3Q1AnU/O +i7t5FGhCgnv5m8DW2F3LZPCJdW4ELQ3d9A== +""" + +valid_update_private_key = """ +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvyODf22w/p7Zgfz9 +dyLIT09LqLOrUN6zbAecfukiiiyhRANCAAQMe0h/3CdD8mEgy+klk55QfJ8R3Zyc +efxCn4abWjzTXz/TuCIxqb4wpRT8DZtIn4NRimFTmODYdEDOYxFtZm37 +""" + +class TestPKI(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestPKI, cls).setUpClass() + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + cls.cli_delete(cls, ['service', 'https']) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_valid_pki(self): + # Valid CA + self.cli_set(base_path + ['ca', 'smoketest', 'certificate', valid_ca_cert.replace('\n','')]) + self.cli_set(base_path + ['ca', 'smoketest', 'private', 'key', valid_ca_private_key.replace('\n','')]) + + # Valid cert + self.cli_set(base_path + ['certificate', 'smoketest', 'certificate', valid_cert.replace('\n','')]) + + # Valid DH + self.cli_set(base_path + ['dh', 'smoketest', 'parameters', valid_dh_params.replace('\n','')]) + + # Valid public key + self.cli_set(base_path + ['key-pair', 'smoketest', 'public', 'key', valid_public_ec_key.replace('\n','')]) + + # Valid private key + self.cli_set(base_path + ['key-pair', 'smoketest1', 'private', 'key', valid_private_rsa_key.replace('\n','')]) + self.cli_commit() + + def test_invalid_ca_valid_certificate(self): + self.cli_set(base_path + ['ca', 'invalid-ca', 'certificate', valid_cert.replace('\n','')]) + + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_certificate_in_use(self): + cert_name = 'smoketest' + + self.cli_set(base_path + ['certificate', cert_name, 'certificate', valid_ca_cert.replace('\n','')]) + self.cli_set(base_path + ['certificate', cert_name, 'private', 'key', valid_ca_private_key.replace('\n','')]) + self.cli_commit() + + self.cli_set(['service', 'https', 'certificates', 'certificate', cert_name]) + self.cli_commit() + + self.cli_delete(base_path + ['certificate', cert_name]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete(['service', 'https', 'certificates', 'certificate']) + + def test_certificate_https_update(self): + cert_name = 'smoke-test_foo' + cert_path = f'/run/nginx/certs/{cert_name}_cert.pem' + self.cli_set(base_path + ['certificate', cert_name, 'certificate', valid_ca_cert.replace('\n','')]) + self.cli_set(base_path + ['certificate', cert_name, 'private', 'key', valid_ca_private_key.replace('\n','')]) + self.cli_commit() + + self.cli_set(['service', 'https', 'certificates', 'certificate', cert_name]) + self.cli_commit() + + cert_data = None + + cert_data = read_file(cert_path) + + self.cli_set(base_path + ['certificate', cert_name, 'certificate', valid_update_cert.replace('\n','')]) + self.cli_set(base_path + ['certificate', cert_name, 'private', 'key', valid_update_private_key.replace('\n','')]) + self.cli_commit() + + self.assertNotEqual(cert_data, read_file(cert_path)) + + self.cli_delete(['service', 'https', 'certificates', 'certificate']) + + def test_certificate_eapol_update(self): + cert_name = 'eapol' + interface = 'eth1' + self.cli_set(base_path + ['certificate', cert_name, 'certificate', valid_ca_cert.replace('\n','')]) + self.cli_set(base_path + ['certificate', cert_name, 'private', 'key', valid_ca_private_key.replace('\n','')]) + self.cli_commit() + + self.cli_set(['interfaces', 'ethernet', interface, 'eapol', 'certificate', cert_name]) + self.cli_commit() + + cert_data = None + + with open(f'/run/wpa_supplicant/{interface}_cert.pem') as f: + cert_data = f.read() + + self.cli_set(base_path + ['certificate', cert_name, 'certificate', valid_update_cert.replace('\n','')]) + self.cli_set(base_path + ['certificate', cert_name, 'private', 'key', valid_update_private_key.replace('\n','')]) + self.cli_commit() + + with open(f'/run/wpa_supplicant/{interface}_cert.pem') as f: + self.assertNotEqual(cert_data, f.read()) + + self.cli_delete(['interfaces', 'ethernet', interface, 'eapol']) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py new file mode 100644 index 0000000..a0c6ab0 --- /dev/null +++ b/smoketest/scripts/cli/test_policy.py @@ -0,0 +1,1994 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import cmd + +base_path = ['policy'] + +class TestPolicy(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_access_list(self): + acls = { + '50' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'source' : { 'any' : '' }, + }, + '10' : { + 'action' : 'deny', + 'source' : { 'host' : '1.2.3.4' }, + }, + }, + }, + '150' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'source' : { 'any' : '' }, + 'destination' : { 'host' : '2.2.2.2' }, + }, + '10' : { + 'action' : 'deny', + 'source' : { 'any' : '' }, + 'destination' : { 'any' : '' }, + }, + }, + }, + '2000' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'destination' : { 'any' : '' }, + 'source' : { 'network' : '10.0.0.0', 'inverse-mask' : '0.255.255.255' }, + }, + '10' : { + 'action' : 'permit', + 'destination' : { 'any' : '' }, + 'source' : { 'network' : '172.16.0.0', 'inverse-mask' : '0.15.255.255' }, + }, + '15' : { + 'action' : 'permit', + 'destination' : { 'any' : '' }, + 'source' : { 'network' : '192.168.0.0', 'inverse-mask' : '0.0.255.255' }, + }, + '20' : { + 'action' : 'permit', + 'destination' : { 'network' : '172.16.0.0', 'inverse-mask' : '0.15.255.255' }, + 'source' : { 'network' : '10.0.0.0', 'inverse-mask' : '0.255.255.255' }, + }, + '25' : { + 'action' : 'deny', + 'destination' : { 'network' : '192.168.0.0', 'inverse-mask' : '0.0.255.255' }, + 'source' : { 'network' : '172.16.0.0', 'inverse-mask' : '0.15.255.255' }, + }, + '30' : { + 'action' : 'deny', + 'destination' : { 'any' : '' }, + 'source' : { 'any' : '' }, + }, + }, + }, + } + + for acl, acl_config in acls.items(): + path = base_path + ['access-list', acl] + self.cli_set(path + ['description', f'VyOS-ACL-{acl}']) + if 'rule' not in acl_config: + continue + + for rule, rule_config in acl_config['rule'].items(): + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + for direction in ['source', 'destination']: + if direction in rule_config: + if 'any' in rule_config[direction]: + self.cli_set(path + ['rule', rule, direction, 'any']) + if 'host' in rule_config[direction]: + self.cli_set(path + ['rule', rule, direction, 'host', rule_config[direction]['host']]) + if 'network' in rule_config[direction]: + self.cli_set(path + ['rule', rule, direction, 'network', rule_config[direction]['network']]) + self.cli_set(path + ['rule', rule, direction, 'inverse-mask', rule_config[direction]['inverse-mask']]) + + self.cli_commit() + + config = self.getFRRconfig('access-list', end='') + for acl, acl_config in acls.items(): + for rule, rule_config in acl_config['rule'].items(): + tmp = f'access-list {acl} seq {rule}' + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + if {'source', 'destination'} <= set(rule_config): + tmp += ' ip' + + for direction in ['source', 'destination']: + if direction in rule_config: + if 'any' in rule_config[direction]: + tmp += ' any' + if 'host' in rule_config[direction]: + # XXX: Some weird side rule from the old vyatta days + # possible to clean this up after the vyos-1x migration + if int(acl) in range(100, 200) or int(acl) in range(2000, 2700): + tmp += ' host' + + tmp += ' ' + rule_config[direction]['host'] + if 'network' in rule_config[direction]: + tmp += ' ' + rule_config[direction]['network'] + ' ' + rule_config[direction]['inverse-mask'] + + self.assertIn(tmp, config) + + def test_access_list6(self): + acls = { + '50' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'source' : { 'any' : '' }, + }, + '10' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:10::/48', 'exact-match' : '' }, + }, + '15' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:20::/48' }, + }, + }, + }, + '100' : { + 'rule' : { + '5' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:10::/64', 'exact-match' : '' }, + }, + '10' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:20::/64', }, + }, + '15' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:30::/64', 'exact-match' : '' }, + }, + '20' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:40::/64', 'exact-match' : '' }, + }, + '25' : { + 'action' : 'deny', + 'source' : { 'any' : '' }, + }, + }, + }, + } + + for acl, acl_config in acls.items(): + path = base_path + ['access-list6', acl] + self.cli_set(path + ['description', f'VyOS-ACL-{acl}']) + if 'rule' not in acl_config: + continue + + for rule, rule_config in acl_config['rule'].items(): + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + for direction in ['source', 'destination']: + if direction in rule_config: + if 'any' in rule_config[direction]: + self.cli_set(path + ['rule', rule, direction, 'any']) + if 'network' in rule_config[direction]: + self.cli_set(path + ['rule', rule, direction, 'network', rule_config[direction]['network']]) + if 'exact-match' in rule_config[direction]: + self.cli_set(path + ['rule', rule, direction, 'exact-match']) + + self.cli_commit() + + config = self.getFRRconfig('ipv6 access-list', end='') + for acl, acl_config in acls.items(): + for rule, rule_config in acl_config['rule'].items(): + tmp = f'ipv6 access-list {acl} seq {rule}' + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + if {'source', 'destination'} <= set(rule_config): + tmp += ' ip' + + for direction in ['source', 'destination']: + if direction in rule_config: + if 'any' in rule_config[direction]: + tmp += ' any' + if 'network' in rule_config[direction]: + tmp += ' ' + rule_config[direction]['network'] + if 'exact-match' in rule_config[direction]: + tmp += ' exact-match' + + self.assertIn(tmp, config) + + + def test_as_path_list(self): + test_data = { + 'VyOS' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'regex' : '^44501 64502$', + }, + '10' : { + 'action' : 'permit', + 'regex' : '44501|44502|44503', + }, + '15' : { + 'action' : 'permit', + 'regex' : '^44501_([0-9]+_)+', + }, + }, + }, + 'Customers' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'regex' : '_10_', + }, + '10' : { + 'action' : 'permit', + 'regex' : '_20_', + }, + '15' : { + 'action' : 'permit', + 'regex' : '_30_', + }, + '20' : { + 'action' : 'deny', + 'regex' : '_40_', + }, + }, + }, + 'bogons' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'regex' : '_0_', + }, + '10' : { + 'action' : 'permit', + 'regex' : '_23456_', + }, + '15' : { + 'action' : 'permit', + 'regex' : '_6449[6-9]_|_65[0-4][0-9][0-9]_|_655[0-4][0-9]_|_6555[0-1]_', + }, + '20' : { + 'action' : 'permit', + 'regex' : '_6555[2-9]_|_655[6-9][0-9]_|_65[6-9][0-9][0-9]_|_6[6-9][0-9][0-9][0-]_|_[7-9][0-9][0-9][0-9][0-9]_|_1[0-2][0-9][0-9][0-9][0-9]_|_130[0-9][0-9][0-9]_|_1310[0-6][0-9]_|_13107[01]_', + }, + }, + }, + } + + for as_path, as_path_config in test_data.items(): + path = base_path + ['as-path-list', as_path] + self.cli_set(path + ['description', f'VyOS-ASPATH-{as_path}']) + if 'rule' not in as_path_config: + continue + + for rule, rule_config in as_path_config['rule'].items(): + if 'action' in rule_config: + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + if 'regex' in rule_config: + self.cli_set(path + ['rule', rule, 'regex', rule_config['regex']]) + + self.cli_commit() + + config = self.getFRRconfig('bgp as-path access-list', end='') + for as_path, as_path_config in test_data.items(): + if 'rule' not in as_path_config: + continue + + for rule, rule_config in as_path_config['rule'].items(): + tmp = f'bgp as-path access-list {as_path} seq {rule}' + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['regex'] + + self.assertIn(tmp, config) + + def test_community_list(self): + test_data = { + '100' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'regex' : '.*', + }, + }, + }, + '200' : { + 'rule' : { + '5' : { + 'action' : 'deny', + 'regex' : '^1:201$', + }, + '10' : { + 'action' : 'deny', + 'regex' : '1:101$', + }, + '15' : { + 'action' : 'deny', + 'regex' : '^1:100$', + }, + }, + }, + } + + for comm_list, comm_list_config in test_data.items(): + path = base_path + ['community-list', comm_list] + self.cli_set(path + ['description', f'VyOS-COMM-{comm_list}']) + if 'rule' not in comm_list_config: + continue + + for rule, rule_config in comm_list_config['rule'].items(): + if 'action' in rule_config: + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + if 'regex' in rule_config: + self.cli_set(path + ['rule', rule, 'regex', rule_config['regex']]) + + self.cli_commit() + + config = self.getFRRconfig('bgp community-list', end='') + for comm_list, comm_list_config in test_data.items(): + if 'rule' not in comm_list_config: + continue + + for rule, rule_config in comm_list_config['rule'].items(): + tmp = f'bgp community-list {comm_list} seq {rule}' + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['regex'] + + self.assertIn(tmp, config) + + def test_extended_community_list(self): + test_data = { + 'foo' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'regex' : '.*', + }, + }, + }, + '200' : { + 'rule' : { + '5' : { + 'action' : 'deny', + 'regex' : '^1:201$', + }, + '10' : { + 'action' : 'deny', + 'regex' : '1:101$', + }, + '15' : { + 'action' : 'deny', + 'regex' : '^1:100$', + }, + }, + }, + } + + for comm_list, comm_list_config in test_data.items(): + path = base_path + ['extcommunity-list', comm_list] + self.cli_set(path + ['description', f'VyOS-EXTCOMM-{comm_list}']) + if 'rule' not in comm_list_config: + continue + + for rule, rule_config in comm_list_config['rule'].items(): + if 'action' in rule_config: + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + if 'regex' in rule_config: + self.cli_set(path + ['rule', rule, 'regex', rule_config['regex']]) + + self.cli_commit() + + config = self.getFRRconfig('bgp extcommunity-list', end='') + for comm_list, comm_list_config in test_data.items(): + if 'rule' not in comm_list_config: + continue + + for rule, rule_config in comm_list_config['rule'].items(): + # if the community is not a number but a name, the expanded + # keyword is used + expanded = '' + if not comm_list.isnumeric(): + expanded = ' expanded' + tmp = f'bgp extcommunity-list{expanded} {comm_list} seq {rule}' + + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['regex'] + + self.assertIn(tmp, config) + + + def test_large_community_list(self): + test_data = { + 'foo' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'regex' : '667:123:100', + }, + }, + }, + 'bar' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'regex' : '65000:120:10', + }, + '10' : { + 'action' : 'permit', + 'regex' : '65000:120:20', + }, + '15' : { + 'action' : 'permit', + 'regex' : '65000:120:30', + }, + }, + }, + } + + for comm_list, comm_list_config in test_data.items(): + path = base_path + ['large-community-list', comm_list] + self.cli_set(path + ['description', f'VyOS-LARGECOMM-{comm_list}']) + if 'rule' not in comm_list_config: + continue + + for rule, rule_config in comm_list_config['rule'].items(): + if 'action' in rule_config: + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + if 'regex' in rule_config: + self.cli_set(path + ['rule', rule, 'regex', rule_config['regex']]) + + self.cli_commit() + + config = self.getFRRconfig('bgp large-community-list', end='') + for comm_list, comm_list_config in test_data.items(): + if 'rule' not in comm_list_config: + continue + + for rule, rule_config in comm_list_config['rule'].items(): + tmp = f'bgp large-community-list expanded {comm_list} seq {rule}' + + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['regex'] + + self.assertIn(tmp, config) + + + def test_prefix_list(self): + test_data = { + 'foo' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'prefix' : '10.0.0.0/8', + 'ge' : '16', + 'le' : '24', + }, + '10' : { + 'action' : 'deny', + 'prefix' : '172.16.0.0/12', + 'ge' : '16', + }, + '15' : { + 'action' : 'permit', + 'prefix' : '192.168.0.0/16', + }, + }, + }, + 'bar' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'prefix' : '10.0.10.0/24', + 'ge' : '25', + 'le' : '26', + }, + '10' : { + 'action' : 'deny', + 'prefix' : '10.0.20.0/24', + 'le' : '25', + }, + '15' : { + 'action' : 'permit', + 'prefix' : '10.0.25.0/24', + }, + }, + }, + } + + for prefix_list, prefix_list_config in test_data.items(): + path = base_path + ['prefix-list', prefix_list] + self.cli_set(path + ['description', f'VyOS-PFX-LIST-{prefix_list}']) + if 'rule' not in prefix_list_config: + continue + + for rule, rule_config in prefix_list_config['rule'].items(): + if 'action' in rule_config: + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + if 'prefix' in rule_config: + self.cli_set(path + ['rule', rule, 'prefix', rule_config['prefix']]) + if 'ge' in rule_config: + self.cli_set(path + ['rule', rule, 'ge', rule_config['ge']]) + if 'le' in rule_config: + self.cli_set(path + ['rule', rule, 'le', rule_config['le']]) + + self.cli_commit() + + config = self.getFRRconfig('ip prefix-list', end='') + for prefix_list, prefix_list_config in test_data.items(): + if 'rule' not in prefix_list_config: + continue + + for rule, rule_config in prefix_list_config['rule'].items(): + tmp = f'ip prefix-list {prefix_list} seq {rule}' + + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['prefix'] + + if 'ge' in rule_config: + tmp += ' ge ' + rule_config['ge'] + if 'le' in rule_config: + tmp += ' le ' + rule_config['le'] + + self.assertIn(tmp, config) + + + def test_prefix_list6(self): + test_data = { + 'foo' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'prefix' : '2001:db8::/32', + 'ge' : '40', + 'le' : '48', + }, + '10' : { + 'action' : 'deny', + 'prefix' : '2001:db8::/32', + 'ge' : '48', + }, + '15' : { + 'action' : 'permit', + 'prefix' : '2001:db8:1000::/64', + }, + }, + }, + 'bar' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'prefix' : '2001:db8:100::/40', + 'ge' : '48', + }, + '10' : { + 'action' : 'permit', + 'prefix' : '2001:db8:200::/40', + 'ge' : '48', + }, + '15' : { + 'action' : 'deny', + 'prefix' : '2001:db8:300::/40', + 'le' : '64', + }, + }, + }, + } + + for prefix_list, prefix_list_config in test_data.items(): + path = base_path + ['prefix-list6', prefix_list] + self.cli_set(path + ['description', f'VyOS-PFX-LIST-{prefix_list}']) + if 'rule' not in prefix_list_config: + continue + + for rule, rule_config in prefix_list_config['rule'].items(): + if 'action' in rule_config: + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + if 'prefix' in rule_config: + self.cli_set(path + ['rule', rule, 'prefix', rule_config['prefix']]) + if 'ge' in rule_config: + self.cli_set(path + ['rule', rule, 'ge', rule_config['ge']]) + if 'le' in rule_config: + self.cli_set(path + ['rule', rule, 'le', rule_config['le']]) + + self.cli_commit() + + config = self.getFRRconfig('ipv6 prefix-list', end='') + for prefix_list, prefix_list_config in test_data.items(): + if 'rule' not in prefix_list_config: + continue + + for rule, rule_config in prefix_list_config['rule'].items(): + tmp = f'ipv6 prefix-list {prefix_list} seq {rule}' + + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['prefix'] + + if 'ge' in rule_config: + tmp += ' ge ' + rule_config['ge'] + if 'le' in rule_config: + tmp += ' le ' + rule_config['le'] + + self.assertIn(tmp, config) + + def test_prefix_list_duplicates(self): + # FRR does not allow to specify the same profix list rule multiple times + # + # vyos(config)# ip prefix-list foo seq 10 permit 192.0.2.0/24 + # vyos(config)# ip prefix-list foo seq 20 permit 192.0.2.0/24 + # % Configuration failed. + # Error type: validation + # Error description: duplicated prefix list value: 192.0.2.0/24 + + # There is also a VyOS verify() function to test this + + prefix = '100.64.0.0/10' + prefix_list = 'duplicates' + test_range = range(20, 25) + path = base_path + ['prefix-list', prefix_list] + + for rule in test_range: + self.cli_set(path + ['rule', str(rule), 'action', 'permit']) + self.cli_set(path + ['rule', str(rule), 'prefix', prefix]) + + # Duplicate prefixes + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + for rule in test_range: + self.cli_set(path + ['rule', str(rule), 'le', str(rule)]) + + self.cli_commit() + + config = self.getFRRconfig('ip prefix-list', end='') + for rule in test_range: + tmp = f'ip prefix-list {prefix_list} seq {rule} permit {prefix} le {rule}' + self.assertIn(tmp, config) + def test_route_map_community_set(self): + test_data = { + "community-configuration": { + "rule": { + "10": { + "action": "permit", + "set": { + "community": { + "replace": [ + "65000:10", + "65001:11" + ] + }, + "extcommunity": { + "bandwidth": "200", + "rt": [ + "65000:10", + "192.168.0.1:11" + ], + "soo": [ + "192.168.0.1:11", + "65000:10" + ] + }, + "large-community": { + "replace": [ + "65000:65000:10", + "65000:65000:11" + ] + } + } + }, + "20": { + "action": "permit", + "set": { + "community": { + "add": [ + "65000:10", + "65001:11" + ] + }, + "extcommunity": { + "bandwidth": "200", + "bandwidth-non-transitive": {} + }, + "large-community": { + "add": [ + "65000:65000:10", + "65000:65000:11" + ] + } + } + }, + "30": { + "action": "permit", + "set": { + "community": { + "none": {} + }, + "extcommunity": { + "none": {} + }, + "large-community": { + "none": {} + } + } + } + } + } + } + for route_map, route_map_config in test_data.items(): + path = base_path + ['route-map', route_map] + self.cli_set(path + ['description', f'VyOS ROUTE-MAP {route_map}']) + if 'rule' not in route_map_config: + continue + + for rule, rule_config in route_map_config['rule'].items(): + if 'action' in rule_config: + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + if 'set' in rule_config: + + #Add community in configuration + if 'community' in rule_config['set']: + if 'none' in rule_config['set']['community']: + self.cli_set(path + ['rule', rule, 'set', 'community', 'none']) + else: + community_path = path + ['rule', rule, 'set', 'community'] + if 'add' in rule_config['set']['community']: + for community_unit in rule_config['set']['community']['add']: + self.cli_set(community_path + ['add', community_unit]) + if 'replace' in rule_config['set']['community']: + for community_unit in rule_config['set']['community']['replace']: + self.cli_set(community_path + ['replace', community_unit]) + + #Add large-community in configuration + if 'large-community' in rule_config['set']: + if 'none' in rule_config['set']['large-community']: + self.cli_set(path + ['rule', rule, 'set', 'large-community', 'none']) + else: + community_path = path + ['rule', rule, 'set', 'large-community'] + if 'add' in rule_config['set']['large-community']: + for community_unit in rule_config['set']['large-community']['add']: + self.cli_set(community_path + ['add', community_unit]) + if 'replace' in rule_config['set']['large-community']: + for community_unit in rule_config['set']['large-community']['replace']: + self.cli_set(community_path + ['replace', community_unit]) + + #Add extcommunity in configuration + if 'extcommunity' in rule_config['set']: + if 'none' in rule_config['set']['extcommunity']: + self.cli_set(path + ['rule', rule, 'set', 'extcommunity', 'none']) + else: + if 'bandwidth' in rule_config['set']['extcommunity']: + self.cli_set(path + ['rule', rule, 'set', 'extcommunity', 'bandwidth', rule_config['set']['extcommunity']['bandwidth']]) + if 'bandwidth-non-transitive' in rule_config['set']['extcommunity']: + self.cli_set(path + ['rule', rule, 'set','extcommunity', 'bandwidth-non-transitive']) + if 'rt' in rule_config['set']['extcommunity']: + for community_unit in rule_config['set']['extcommunity']['rt']: + self.cli_set(path + ['rule', rule, 'set', 'extcommunity','rt',community_unit]) + if 'soo' in rule_config['set']['extcommunity']: + for community_unit in rule_config['set']['extcommunity']['soo']: + self.cli_set(path + ['rule', rule, 'set', 'extcommunity','soo',community_unit]) + self.cli_commit() + + for route_map, route_map_config in test_data.items(): + if 'rule' not in route_map_config: + continue + for rule, rule_config in route_map_config['rule'].items(): + name = f'route-map {route_map} {rule_config["action"]} {rule}' + config = self.getFRRconfig(name) + self.assertIn(name, config) + + if 'set' in rule_config: + #Check community + if 'community' in rule_config['set']: + if 'none' in rule_config['set']['community']: + tmp = f'set community none' + self.assertIn(tmp, config) + if 'replace' in rule_config['set']['community']: + values = ' '.join(rule_config['set']['community']['replace']) + tmp = f'set community {values}' + self.assertIn(tmp, config) + if 'add' in rule_config['set']['community']: + values = ' '.join(rule_config['set']['community']['add']) + tmp = f'set community {values} additive' + self.assertIn(tmp, config) + #Check large-community + if 'large-community' in rule_config['set']: + if 'none' in rule_config['set']['large-community']: + tmp = f'set large-community none' + self.assertIn(tmp, config) + if 'replace' in rule_config['set']['large-community']: + values = ' '.join(rule_config['set']['large-community']['replace']) + tmp = f'set large-community {values}' + self.assertIn(tmp, config) + if 'add' in rule_config['set']['large-community']: + values = ' '.join(rule_config['set']['large-community']['add']) + tmp = f'set large-community {values} additive' + self.assertIn(tmp, config) + #Check extcommunity + if 'extcommunity' in rule_config['set']: + if 'none' in rule_config['set']['extcommunity']: + tmp = 'set extcommunity none' + self.assertIn(tmp, config) + if 'bandwidth' in rule_config['set']['extcommunity']: + values = rule_config['set']['extcommunity']['bandwidth'] + tmp = f'set extcommunity bandwidth {values}' + if 'bandwidth-non-transitive' in rule_config['set']['extcommunity']: + tmp = tmp + ' non-transitive' + self.assertIn(tmp, config) + if 'rt' in rule_config['set']['extcommunity']: + values = ' '.join(rule_config['set']['extcommunity']['rt']) + tmp = f'set extcommunity rt {values}' + self.assertIn(tmp, config) + if 'soo' in rule_config['set']['extcommunity']: + values = ' '.join(rule_config['set']['extcommunity']['soo']) + tmp = f'set extcommunity soo {values}' + self.assertIn(tmp, config) + + def test_route_map(self): + access_list = '50' + as_path_list = '100' + test_interface = 'eth0' + community_list = 'BGP-comm-0815' + + # ext community name only allows alphanumeric characters and no hyphen :/ + # maybe change this if possible in vyos-1x rewrite + extcommunity_list = 'BGPextcomm123' + + large_community_list = 'bgp-large-community-123456' + prefix_list = 'foo-pfx-list' + ipv6_nexthop_address = 'fe80::1' + local_pref = '300' + metric = '50' + peer = '2.3.4.5' + peerv6 = '2001:db8::1' + tag = '6542' + goto = '25' + + ipv4_nexthop_address= '192.0.2.2' + ipv4_prefix_len= '18' + ipv6_prefix_len= '122' + ipv4_nexthop_type= 'blackhole' + ipv6_nexthop_type= 'blackhole' + + test_data = { + 'foo-map-bar' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'continue' : '20', + }, + '10' : { + 'action' : 'permit', + 'call' : 'complicated-configuration', + }, + }, + }, + 'a-matching-rule-0815': { + 'rule' : { + '5' : { + 'action' : 'deny', + 'match' : { + 'as-path' : as_path_list, + 'rpki-invalid': '', + 'tag': tag, + }, + }, + '10' : { + 'action' : 'permit', + 'match' : { + 'community' : community_list, + 'interface' : test_interface, + 'rpki-not-found': '', + }, + }, + '15' : { + 'action' : 'permit', + 'match' : { + 'extcommunity' : extcommunity_list, + 'rpki-valid': '', + }, + 'on-match' : { + 'next' : '', + }, + }, + '20' : { + 'action' : 'permit', + 'match' : { + 'ip-address-acl': access_list, + 'ip-nexthop-acl': access_list, + 'ip-route-source-acl': access_list, + 'ipv6-address-acl': access_list, + 'origin-incomplete' : '', + }, + 'on-match' : { + 'goto' : goto, + }, + }, + '25' : { + 'action' : 'permit', + 'match' : { + 'ip-address-pfx': prefix_list, + 'ip-nexthop-pfx': prefix_list, + 'ip-route-source-pfx': prefix_list, + 'ipv6-address-pfx': prefix_list, + 'origin-igp': '', + }, + }, + '30' : { + 'action' : 'permit', + 'match' : { + 'ipv6-nexthop-address' : ipv6_nexthop_address, + 'ipv6-nexthop-access-list' : access_list, + 'ipv6-nexthop-prefix-list' : prefix_list, + 'ipv6-nexthop-type' : ipv6_nexthop_type, + 'ipv6-address-pfx-len' : ipv6_prefix_len, + 'large-community' : large_community_list, + 'local-pref' : local_pref, + 'metric': metric, + 'origin-egp': '', + 'peer' : peer, + }, + }, + + '31' : { + 'action' : 'permit', + 'match' : { + 'peer' : peerv6, + }, + }, + + '40' : { + 'action' : 'permit', + 'match' : { + 'ip-nexthop-addr' : ipv4_nexthop_address, + 'ip-address-pfx-len' : ipv4_prefix_len, + }, + }, + '42' : { + 'action' : 'deny', + 'match' : { + 'ip-nexthop-plen' : ipv4_prefix_len, + }, + }, + '44' : { + 'action' : 'permit', + 'match' : { + 'ip-nexthop-type' : ipv4_nexthop_type, + }, + }, + }, + }, + 'complicated-configuration' : { + 'rule' : { + '10' : { + 'action' : 'deny', + 'set' : { + 'aggregator-as' : '1234567890', + 'aggregator-ip' : '10.255.255.0', + 'as-path-exclude' : '1234', + 'as-path-prepend' : '1234567890 987654321', + 'as-path-prepend-last-as' : '5', + 'atomic-aggregate' : '', + 'distance' : '110', + 'ipv6-next-hop-global' : '2001::1', + 'ipv6-next-hop-local' : 'fe80::1', + 'ip-next-hop' : '192.168.1.1', + 'local-preference' : '500', + 'metric' : '150', + 'metric-type' : 'type-1', + 'origin' : 'incomplete', + 'l3vpn' : '', + 'originator-id' : '172.16.10.1', + 'src' : '100.0.0.1', + 'tag' : '65530', + 'weight' : '2', + }, + }, + }, + }, + 'bandwidth-configuration' : { + 'rule' : { + '10' : { + 'action' : 'deny', + 'set' : { + 'as-path-prepend' : '100 100', + 'distance' : '200', + 'extcommunity-bw' : 'num-multipaths', + }, + }, + }, + }, + 'evpn-configuration' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'match' : { + 'evpn-default-route' : '', + 'evpn-rd' : '100:300', + 'evpn-route-type' : 'prefix', + 'evpn-vni' : '1234', + }, + }, + '20' : { + 'action' : 'permit', + 'set' : { + 'as-path-exclude' : 'all', + 'evpn-gateway-ipv4' : '192.0.2.99', + 'evpn-gateway-ipv6' : '2001:db8:f00::1', + }, + }, + }, + }, + 'match-protocol' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'match' : { + 'protocol' : 'static', + }, + }, + '20' : { + 'action' : 'permit', + 'match' : { + 'protocol' : 'bgp', + }, + }, + }, + }, + 'relative-metric' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'match' : { + 'ip-nexthop-addr' : ipv4_nexthop_address, + }, + 'set' : { + 'metric' : '+10', + }, + }, + '20' : { + 'action' : 'permit', + 'match' : { + 'ip-nexthop-addr' : ipv4_nexthop_address, + }, + 'set' : { + 'metric' : '-20', + }, + }, + '30': { + 'action': 'permit', + 'match': { + 'ip-nexthop-addr': ipv4_nexthop_address, + }, + 'set': { + 'metric': 'rtt', + }, + }, + '40': { + 'action': 'permit', + 'match': { + 'ip-nexthop-addr': ipv4_nexthop_address, + }, + 'set': { + 'metric': '+rtt', + }, + }, + '50': { + 'action': 'permit', + 'match': { + 'ip-nexthop-addr': ipv4_nexthop_address, + }, + 'set': { + 'metric': '-rtt', + }, + }, + }, + }, + } + + self.cli_set(['policy', 'access-list', access_list, 'rule', '10', 'action', 'permit']) + self.cli_set(['policy', 'access-list', access_list, 'rule', '10', 'source', 'host', '1.1.1.1']) + self.cli_set(['policy', 'access-list6', access_list, 'rule', '10', 'action', 'permit']) + self.cli_set(['policy', 'access-list6', access_list, 'rule', '10', 'source', 'network', '2001:db8::/32']) + + self.cli_set(['policy', 'as-path-list', as_path_list, 'rule', '10', 'action', 'permit']) + self.cli_set(['policy', 'as-path-list', as_path_list, 'rule', '10', 'regex', '64501 64502']) + self.cli_set(['policy', 'community-list', community_list, 'rule', '10', 'action', 'deny']) + self.cli_set(['policy', 'community-list', community_list, 'rule', '10', 'regex', '65432']) + self.cli_set(['policy', 'extcommunity-list', extcommunity_list, 'rule', '10', 'action', 'deny']) + self.cli_set(['policy', 'extcommunity-list', extcommunity_list, 'rule', '10', 'regex', '65000']) + self.cli_set(['policy', 'large-community-list', large_community_list, 'rule', '10', 'action', 'permit']) + self.cli_set(['policy', 'large-community-list', large_community_list, 'rule', '10', 'regex', '100:200:300']) + + self.cli_set(['policy', 'prefix-list', prefix_list, 'rule', '10', 'action', 'permit']) + self.cli_set(['policy', 'prefix-list', prefix_list, 'rule', '10', 'prefix', '192.0.2.0/24']) + self.cli_set(['policy', 'prefix-list6', prefix_list, 'rule', '10', 'action', 'permit']) + self.cli_set(['policy', 'prefix-list6', prefix_list, 'rule', '10', 'prefix', '2001:db8::/32']) + + for route_map, route_map_config in test_data.items(): + path = base_path + ['route-map', route_map] + self.cli_set(path + ['description', f'VyOS ROUTE-MAP {route_map}']) + if 'rule' not in route_map_config: + continue + + for rule, rule_config in route_map_config['rule'].items(): + if 'action' in rule_config: + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + + if 'call' in rule_config: + self.cli_set(path + ['rule', rule, 'call', rule_config['call']]) + + if 'continue' in rule_config: + self.cli_set(path + ['rule', rule, 'continue', rule_config['continue']]) + + if 'match' in rule_config: + if 'as-path' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'as-path', rule_config['match']['as-path']]) + if 'community' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'community', 'community-list', rule_config['match']['community']]) + self.cli_set(path + ['rule', rule, 'match', 'community', 'exact-match']) + if 'evpn-default-route' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'evpn', 'default-route']) + if 'evpn-rd' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'evpn', 'rd', rule_config['match']['evpn-rd']]) + if 'evpn-route-type' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'evpn', 'route-type', rule_config['match']['evpn-route-type']]) + if 'evpn-vni' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'evpn', 'vni', rule_config['match']['evpn-vni']]) + if 'extcommunity' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'extcommunity', rule_config['match']['extcommunity']]) + if 'interface' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'interface', rule_config['match']['interface']]) + if 'ip-address-acl' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ip', 'address', 'access-list', rule_config['match']['ip-address-acl']]) + if 'ip-address-pfx' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ip', 'address', 'prefix-list', rule_config['match']['ip-address-pfx']]) + if 'ip-address-pfx-len' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ip', 'address', 'prefix-len', rule_config['match']['ip-address-pfx-len']]) + if 'ip-nexthop-acl' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'access-list', rule_config['match']['ip-nexthop-acl']]) + if 'ip-nexthop-pfx' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'prefix-list', rule_config['match']['ip-nexthop-pfx']]) + if 'ip-nexthop-addr' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'address', rule_config['match']['ip-nexthop-addr']]) + if 'ip-nexthop-plen' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'prefix-len', rule_config['match']['ip-nexthop-plen']]) + if 'ip-nexthop-type' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'type', rule_config['match']['ip-nexthop-type']]) + if 'ip-route-source-acl' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ip', 'route-source', 'access-list', rule_config['match']['ip-route-source-acl']]) + if 'ip-route-source-pfx' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ip', 'route-source', 'prefix-list', rule_config['match']['ip-route-source-pfx']]) + if 'ipv6-address-acl' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'address', 'access-list', rule_config['match']['ipv6-address-acl']]) + if 'ipv6-address-pfx' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'address', 'prefix-list', rule_config['match']['ipv6-address-pfx']]) + if 'ipv6-address-pfx-len' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'address', 'prefix-len', rule_config['match']['ipv6-address-pfx-len']]) + if 'ipv6-nexthop-address' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', 'address', rule_config['match']['ipv6-nexthop-address']]) + if 'ipv6-nexthop-access-list' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', 'access-list', rule_config['match']['ipv6-nexthop-access-list']]) + if 'ipv6-nexthop-prefix-list' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', 'prefix-list', rule_config['match']['ipv6-nexthop-prefix-list']]) + if 'ipv6-nexthop-type' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', 'type', rule_config['match']['ipv6-nexthop-type']]) + if 'large-community' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'large-community', 'large-community-list', rule_config['match']['large-community']]) + if 'local-pref' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'local-preference', rule_config['match']['local-pref']]) + if 'metric' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'metric', rule_config['match']['metric']]) + if 'origin-igp' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'origin', 'igp']) + if 'origin-egp' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'origin', 'egp']) + if 'origin-incomplete' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'origin', 'incomplete']) + if 'peer' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'peer', rule_config['match']['peer']]) + if 'rpki-invalid' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'rpki', 'invalid']) + if 'rpki-not-found' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'rpki', 'notfound']) + if 'rpki-valid' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'rpki', 'valid']) + if 'protocol' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'protocol', rule_config['match']['protocol']]) + if 'tag' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'tag', rule_config['match']['tag']]) + + if 'on-match' in rule_config: + if 'goto' in rule_config['on-match']: + self.cli_set(path + ['rule', rule, 'on-match', 'goto', rule_config['on-match']['goto']]) + if 'next' in rule_config['on-match']: + self.cli_set(path + ['rule', rule, 'on-match', 'next']) + + if 'set' in rule_config: + if 'aggregator-as' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'aggregator', 'as', rule_config['set']['aggregator-as']]) + if 'aggregator-ip' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'aggregator', 'ip', rule_config['set']['aggregator-ip']]) + if 'as-path-exclude' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'as-path', 'exclude', rule_config['set']['as-path-exclude']]) + if 'as-path-prepend' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'as-path', 'prepend', rule_config['set']['as-path-prepend']]) + if 'atomic-aggregate' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'atomic-aggregate']) + if 'distance' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'distance', rule_config['set']['distance']]) + if 'ipv6-next-hop-global' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'ipv6-next-hop', 'global', rule_config['set']['ipv6-next-hop-global']]) + if 'ipv6-next-hop-local' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'ipv6-next-hop', 'local', rule_config['set']['ipv6-next-hop-local']]) + if 'ip-next-hop' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'ip-next-hop', rule_config['set']['ip-next-hop']]) + if 'l3vpn' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'l3vpn-nexthop', 'encapsulation', 'gre']) + if 'local-preference' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'local-preference', rule_config['set']['local-preference']]) + if 'metric' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'metric', rule_config['set']['metric']]) + if 'metric-type' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'metric-type', rule_config['set']['metric-type']]) + if 'origin' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'origin', rule_config['set']['origin']]) + if 'originator-id' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'originator-id', rule_config['set']['originator-id']]) + if 'src' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'src', rule_config['set']['src']]) + if 'tag' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'tag', rule_config['set']['tag']]) + if 'weight' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'weight', rule_config['set']['weight']]) + if 'evpn-gateway-ipv4' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'evpn', 'gateway', 'ipv4', rule_config['set']['evpn-gateway-ipv4']]) + if 'evpn-gateway-ipv6' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'evpn', 'gateway', 'ipv6', rule_config['set']['evpn-gateway-ipv6']]) + + self.cli_commit() + + for route_map, route_map_config in test_data.items(): + if 'rule' not in route_map_config: + continue + for rule, rule_config in route_map_config['rule'].items(): + name = f'route-map {route_map} {rule_config["action"]} {rule}' + config = self.getFRRconfig(name) + self.assertIn(name, config) + + if 'call' in rule_config: + tmp = 'call ' + rule_config['call'] + self.assertIn(tmp, config) + + if 'continue' in rule_config: + tmp = 'on-match goto ' + rule_config['continue'] + self.assertIn(tmp, config) + + if 'match' in rule_config: + if 'as-path' in rule_config['match']: + tmp = 'match as-path ' + rule_config['match']['as-path'] + self.assertIn(tmp, config) + if 'community' in rule_config['match']: + tmp = f'match community {rule_config["match"]["community"]} exact-match' + self.assertIn(tmp, config) + if 'evpn-default-route' in rule_config['match']: + tmp = f'match evpn default-route' + self.assertIn(tmp, config) + if 'evpn-rd' in rule_config['match']: + tmp = f'match evpn rd {rule_config["match"]["evpn-rd"]}' + self.assertIn(tmp, config) + if 'evpn-route-type' in rule_config['match']: + tmp = f'match evpn route-type {rule_config["match"]["evpn-route-type"]}' + self.assertIn(tmp, config) + if 'evpn-vni' in rule_config['match']: + tmp = f'match evpn vni {rule_config["match"]["evpn-vni"]}' + self.assertIn(tmp, config) + if 'extcommunity' in rule_config['match']: + tmp = f'match extcommunity {rule_config["match"]["extcommunity"]}' + self.assertIn(tmp, config) + if 'interface' in rule_config['match']: + tmp = f'match interface {rule_config["match"]["interface"]}' + self.assertIn(tmp, config) + if 'ip-address-acl' in rule_config['match']: + tmp = f'match ip address {rule_config["match"]["ip-address-acl"]}' + self.assertIn(tmp, config) + if 'ip-address-pfx' in rule_config['match']: + tmp = f'match ip address prefix-list {rule_config["match"]["ip-address-pfx"]}' + self.assertIn(tmp, config) + if 'ip-address-pfx-len' in rule_config['match']: + tmp = f'match ip address prefix-len {rule_config["match"]["ip-address-pfx-len"]}' + self.assertIn(tmp, config) + if 'ip-nexthop-acl' in rule_config['match']: + tmp = f'match ip next-hop {rule_config["match"]["ip-nexthop-acl"]}' + self.assertIn(tmp, config) + if 'ip-nexthop-pfx' in rule_config['match']: + tmp = f'match ip next-hop prefix-list {rule_config["match"]["ip-nexthop-pfx"]}' + self.assertIn(tmp, config) + if 'ip-nexthop-addr' in rule_config['match']: + tmp = f'match ip next-hop address {rule_config["match"]["ip-nexthop-addr"]}' + self.assertIn(tmp, config) + if 'ip-nexthop-plen' in rule_config['match']: + tmp = f'match ip next-hop prefix-len {rule_config["match"]["ip-nexthop-plen"]}' + self.assertIn(tmp, config) + if 'ip-nexthop-type' in rule_config['match']: + tmp = f'match ip next-hop type {rule_config["match"]["ip-nexthop-type"]}' + self.assertIn(tmp, config) + if 'ip-route-source-acl' in rule_config['match']: + tmp = f'match ip route-source {rule_config["match"]["ip-route-source-acl"]}' + self.assertIn(tmp, config) + if 'ip-route-source-pfx' in rule_config['match']: + tmp = f'match ip route-source prefix-list {rule_config["match"]["ip-route-source-pfx"]}' + self.assertIn(tmp, config) + if 'ipv6-address-acl' in rule_config['match']: + tmp = f'match ipv6 address {rule_config["match"]["ipv6-address-acl"]}' + self.assertIn(tmp, config) + if 'ipv6-address-pfx' in rule_config['match']: + tmp = f'match ipv6 address prefix-list {rule_config["match"]["ipv6-address-pfx"]}' + self.assertIn(tmp, config) + if 'ipv6-address-pfx-len' in rule_config['match']: + tmp = f'match ipv6 address prefix-len {rule_config["match"]["ipv6-address-pfx-len"]}' + self.assertIn(tmp, config) + if 'ipv6-nexthop-address' in rule_config['match']: + tmp = f'match ipv6 next-hop address {rule_config["match"]["ipv6-nexthop-address"]}' + self.assertIn(tmp, config) + if 'ipv6-nexthop-access-list' in rule_config['match']: + tmp = f'match ipv6 next-hop {rule_config["match"]["ipv6-nexthop-access-list"]}' + self.assertIn(tmp, config) + if 'ipv6-nexthop-prefix-list' in rule_config['match']: + tmp = f'match ipv6 next-hop prefix-list {rule_config["match"]["ipv6-nexthop-prefix-list"]}' + self.assertIn(tmp, config) + if 'ipv6-nexthop-type' in rule_config['match']: + tmp = f'match ipv6 next-hop type {rule_config["match"]["ipv6-nexthop-type"]}' + self.assertIn(tmp, config) + if 'large-community' in rule_config['match']: + tmp = f'match large-community {rule_config["match"]["large-community"]}' + self.assertIn(tmp, config) + if 'local-pref' in rule_config['match']: + tmp = f'match local-preference {rule_config["match"]["local-pref"]}' + self.assertIn(tmp, config) + if 'metric' in rule_config['match']: + tmp = f'match metric {rule_config["match"]["metric"]}' + self.assertIn(tmp, config) + if 'origin-igp' in rule_config['match']: + tmp = f'match origin igp' + self.assertIn(tmp, config) + if 'origin-egp' in rule_config['match']: + tmp = f'match origin egp' + self.assertIn(tmp, config) + if 'origin-incomplete' in rule_config['match']: + tmp = f'match origin incomplete' + self.assertIn(tmp, config) + if 'peer' in rule_config['match']: + tmp = f'match peer {rule_config["match"]["peer"]}' + self.assertIn(tmp, config) + if 'protocol' in rule_config['match']: + tmp = f'match source-protocol {rule_config["match"]["protocol"]}' + self.assertIn(tmp, config) + if 'rpki-invalid' in rule_config['match']: + tmp = f'match rpki invalid' + self.assertIn(tmp, config) + if 'rpki-not-found' in rule_config['match']: + tmp = f'match rpki notfound' + self.assertIn(tmp, config) + if 'rpki-valid' in rule_config['match']: + tmp = f'match rpki valid' + self.assertIn(tmp, config) + if 'tag' in rule_config['match']: + tmp = f'match tag {rule_config["match"]["tag"]}' + self.assertIn(tmp, config) + + if 'on-match' in rule_config: + if 'goto' in rule_config['on-match']: + tmp = f'on-match goto {rule_config["on-match"]["goto"]}' + self.assertIn(tmp, config) + if 'next' in rule_config['on-match']: + tmp = f'on-match next' + self.assertIn(tmp, config) + + if 'set' in rule_config: + tmp = ' set ' + if 'aggregator-as' in rule_config['set']: + tmp += 'aggregator as ' + rule_config['set']['aggregator-as'] + elif 'aggregator-ip' in rule_config['set']: + tmp += ' ' + rule_config['set']['aggregator-ip'] + elif 'as-path-exclude' in rule_config['set']: + tmp += 'as-path exclude ' + rule_config['set']['as-path-exclude'] + elif 'as-path-prepend' in rule_config['set']: + tmp += 'as-path prepend ' + rule_config['set']['as-path-prepend'] + elif 'as-path-prepend-last-as' in rule_config['set']: + tmp += 'as-path prepend last-as' + rule_config['set']['as-path-prepend-last-as'] + elif 'atomic-aggregate' in rule_config['set']: + tmp += 'atomic-aggregate' + elif 'distance' in rule_config['set']: + tmp += 'distance ' + rule_config['set']['distance'] + elif 'ip-next-hop' in rule_config['set']: + tmp += 'ip next-hop ' + rule_config['set']['ip-next-hop'] + elif 'ipv6-next-hop-global' in rule_config['set']: + tmp += 'ipv6 next-hop global ' + rule_config['set']['ipv6-next-hop-global'] + elif 'ipv6-next-hop-local' in rule_config['set']: + tmp += 'ipv6 next-hop local ' + rule_config['set']['ipv6-next-hop-local'] + elif 'l3vpn' in rule_config['set']: + tmp += 'l3vpn next-hop encapsulation gre' + elif 'local-preference' in rule_config['set']: + tmp += 'local-preference ' + rule_config['set']['local-preference'] + elif 'metric' in rule_config['set']: + tmp += 'metric ' + rule_config['set']['metric'] + elif 'metric-type' in rule_config['set']: + tmp += 'metric-type ' + rule_config['set']['metric-type'] + elif 'origin' in rule_config['set']: + tmp += 'origin ' + rule_config['set']['origin'] + elif 'originator-id' in rule_config['set']: + tmp += 'originator-id ' + rule_config['set']['originator-id'] + elif 'src' in rule_config['set']: + tmp += 'src ' + rule_config['set']['src'] + elif 'tag' in rule_config['set']: + tmp += 'tag ' + rule_config['set']['tag'] + elif 'weight' in rule_config['set']: + tmp += 'weight ' + rule_config['set']['weight'] + elif 'vpn-gateway-ipv4' in rule_config['set']: + tmp += 'evpn gateway ipv4 ' + rule_config['set']['vpn-gateway-ipv4'] + elif 'vpn-gateway-ipv6' in rule_config['set']: + tmp += 'evpn gateway ipv6 ' + rule_config['set']['vpn-gateway-ipv6'] + + self.assertIn(tmp, config) + + + # Test set table for some sources + def test_table_id(self): + path = base_path + ['local-route'] + + sources = ['203.0.113.1', '203.0.113.2'] + rule = '50' + table = '23' + for src in sources: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', 'address', src]) + + self.cli_commit() + + original = """ + 50: from 203.0.113.1 lookup 23 + 50: from 203.0.113.2 lookup 23 + """ + tmp = cmd('ip rule show prio 50') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for fwmark + def test_fwmark_table_id(self): + path = base_path + ['local-route'] + + fwmk = '24' + rule = '101' + table = '154' + + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + original = """ + 101: from all fwmark 0x18 lookup 154 + """ + tmp = cmd('ip rule show prio 101') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for destination + def test_destination_table_id(self): + path = base_path + ['local-route'] + + dst = '203.0.113.1' + rule = '102' + table = '154' + + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'destination', 'address', dst]) + + self.cli_commit() + + original = """ + 102: from all to 203.0.113.1 lookup 154 + """ + tmp = cmd('ip rule show prio 102') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for destination and protocol + def test_protocol_destination_table_id(self): + path = base_path + ['local-route'] + + dst = '203.0.113.12' + rule = '85' + table = '104' + proto = 'tcp' + + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'destination', 'address', dst]) + self.cli_set(path + ['rule', rule, 'protocol', proto]) + + self.cli_commit() + + original = """ + 85: from all to 203.0.113.12 ipproto tcp lookup 104 + """ + tmp = cmd('ip rule show prio 85') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for destination, source, protocol, fwmark and port + def test_protocol_port_address_fwmark_table_id(self): + path = base_path + ['local-route'] + + dst = '203.0.113.5' + src_list = ['203.0.113.1', '203.0.113.2'] + rule = '23' + fwmark = '123456' + table = '123' + new_table = '111' + proto = 'udp' + new_proto = 'tcp' + src_port = '5555' + dst_port = '8888' + + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'destination', 'address', dst]) + self.cli_set(path + ['rule', rule, 'source', 'port', src_port]) + self.cli_set(path + ['rule', rule, 'protocol', proto]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmark]) + self.cli_set(path + ['rule', rule, 'destination', 'port', dst_port]) + for src in src_list: + self.cli_set(path + ['rule', rule, 'source', 'address', src]) + + self.cli_commit() + + original = """ + 23: from 203.0.113.1 to 203.0.113.5 fwmark 0x1e240 ipproto udp sport 5555 dport 8888 lookup 123 + 23: from 203.0.113.2 to 203.0.113.5 fwmark 0x1e240 ipproto udp sport 5555 dport 8888 lookup 123 + """ + tmp = cmd(f'ip rule show prio {rule}') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Change table and protocol, delete fwmark and source port + self.cli_delete(path + ['rule', rule, 'fwmark']) + self.cli_delete(path + ['rule', rule, 'source', 'port']) + self.cli_set(path + ['rule', rule, 'set', 'table', new_table]) + self.cli_set(path + ['rule', rule, 'protocol', new_proto]) + + self.cli_commit() + + original = """ + 23: from 203.0.113.1 to 203.0.113.5 ipproto tcp dport 8888 lookup 111 + 23: from 203.0.113.2 to 203.0.113.5 ipproto tcp dport 8888 lookup 111 + """ + tmp = cmd(f'ip rule show prio {rule}') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for sources with fwmark + def test_fwmark_sources_table_id(self): + path = base_path + ['local-route'] + + sources = ['203.0.113.11', '203.0.113.12'] + fwmk = '23' + rule = '100' + table = '150' + for src in sources: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', 'address', src]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + original = """ + 100: from 203.0.113.11 fwmark 0x17 lookup 150 + 100: from 203.0.113.12 fwmark 0x17 lookup 150 + """ + tmp = cmd('ip rule show prio 100') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for sources with iif + def test_iif_sources_table_id(self): + path = base_path + ['local-route'] + + sources = ['203.0.113.11', '203.0.113.12'] + iif = 'lo' + rule = '100' + table = '150' + + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'inbound-interface', iif]) + for src in sources: + self.cli_set(path + ['rule', rule, 'source', 'address', src]) + + self.cli_commit() + + # Check generated configuration + # Expected values + original = """ + 100: from 203.0.113.11 iif lo lookup 150 + 100: from 203.0.113.12 iif lo lookup 150 + """ + tmp = cmd('ip rule show prio 100') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for sources and destinations with fwmark + def test_fwmark_sources_destination_table_id(self): + path = base_path + ['local-route'] + + sources = ['203.0.113.11', '203.0.113.12'] + destinations = ['203.0.113.13', '203.0.113.15'] + fwmk = '23' + rule = '103' + table = '150' + for src in sources: + for dst in destinations: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', 'address', src]) + self.cli_set(path + ['rule', rule, 'destination', 'address', dst]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + original = """ + 103: from 203.0.113.11 to 203.0.113.13 fwmark 0x17 lookup 150 + 103: from 203.0.113.11 to 203.0.113.15 fwmark 0x17 lookup 150 + 103: from 203.0.113.12 to 203.0.113.13 fwmark 0x17 lookup 150 + 103: from 203.0.113.12 to 203.0.113.15 fwmark 0x17 lookup 150 + """ + tmp = cmd('ip rule show prio 103') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table ipv6 for some sources ipv6 + def test_ipv6_table_id(self): + path = base_path + ['local-route6'] + + sources = ['2001:db8:123::/48', '2001:db8:126::/48'] + rule = '50' + table = '23' + for src in sources: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', 'address', src]) + + self.cli_commit() + + original = """ + 50: from 2001:db8:123::/48 lookup 23 + 50: from 2001:db8:126::/48 lookup 23 + """ + tmp = cmd('ip -6 rule show prio 50') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for fwmark ipv6 + def test_fwmark_ipv6_table_id(self): + path = base_path + ['local-route6'] + + fwmk = '24' + rule = '100' + table = '154' + + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + original = """ + 100: from all fwmark 0x18 lookup 154 + """ + tmp = cmd('ip -6 rule show prio 100') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for destination ipv6 + def test_destination_ipv6_table_id(self): + path = base_path + ['local-route6'] + + dst = '2001:db8:1337::/126' + rule = '101' + table = '154' + + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'destination', 'address', dst]) + + self.cli_commit() + + original = """ + 101: from all to 2001:db8:1337::/126 lookup 154 + """ + tmp = cmd('ip -6 rule show prio 101') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for sources with fwmark ipv6 + def test_fwmark_sources_ipv6_table_id(self): + path = base_path + ['local-route6'] + + sources = ['2001:db8:1338::/126', '2001:db8:1339::/126'] + fwmk = '23' + rule = '102' + table = '150' + for src in sources: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', 'address', src]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + original = """ + 102: from 2001:db8:1338::/126 fwmark 0x17 lookup 150 + 102: from 2001:db8:1339::/126 fwmark 0x17 lookup 150 + """ + tmp = cmd('ip -6 rule show prio 102') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for sources with iif ipv6 + def test_iif_sources_ipv6_table_id(self): + path = base_path + ['local-route6'] + + sources = ['2001:db8:1338::/126', '2001:db8:1339::/126'] + iif = 'lo' + rule = '102' + table = '150' + for src in sources: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', 'address', src]) + self.cli_set(path + ['rule', rule, 'inbound-interface', iif]) + + self.cli_commit() + + # Check generated configuration + # Expected values + original = """ + 102: from 2001:db8:1338::/126 iif lo lookup 150 + 102: from 2001:db8:1339::/126 iif lo lookup 150 + """ + tmp = cmd('ip -6 rule show prio 102') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for sources and destinations with fwmark ipv6 + def test_fwmark_sources_destination_ipv6_table_id(self): + path = base_path + ['local-route6'] + + sources = ['2001:db8:1338::/126', '2001:db8:1339::/56'] + destinations = ['2001:db8:13::/48', '2001:db8:16::/48'] + fwmk = '23' + rule = '103' + table = '150' + for src in sources: + for dst in destinations: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', 'address', src]) + self.cli_set(path + ['rule', rule, 'destination', 'address', dst]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + original = """ + 103: from 2001:db8:1338::/126 to 2001:db8:13::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1338::/126 to 2001:db8:16::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1339::/56 to 2001:db8:13::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1339::/56 to 2001:db8:16::/48 fwmark 0x17 lookup 150 + """ + tmp = cmd('ip -6 rule show prio 103') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test delete table for sources and destination with fwmark ipv4/ipv6 + def test_delete_ipv4_ipv6_table_id(self): + path = base_path + ['local-route'] + path_v6 = base_path + ['local-route6'] + + sources = ['203.0.113.0/24', '203.0.114.5'] + destinations = ['203.0.112.0/24', '203.0.116.5'] + sources_v6 = ['2001:db8:1338::/126', '2001:db8:1339::/56'] + destinations_v6 = ['2001:db8:13::/48', '2001:db8:16::/48'] + fwmk = '23' + rule = '103' + table = '150' + for src in sources: + for dst in destinations: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', 'address', src]) + self.cli_set(path + ['rule', rule, 'destination', 'address', dst]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + for src in sources_v6: + for dst in destinations_v6: + self.cli_set(path_v6 + ['rule', rule, 'set', 'table', table]) + self.cli_set(path_v6 + ['rule', rule, 'source', 'address', src]) + self.cli_set(path_v6 + ['rule', rule, 'destination', 'address', dst]) + self.cli_set(path_v6 + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + original = """ + 103: from 203.0.113.0/24 to 203.0.116.5 fwmark 0x17 lookup 150 + 103: from 203.0.114.5 to 203.0.112.0/24 fwmark 0x17 lookup 150 + 103: from 203.0.114.5 to 203.0.116.5 fwmark 0x17 lookup 150 + 103: from 203.0.113.0/24 to 203.0.112.0/24 fwmark 0x17 lookup 150 + """ + original_v6 = """ + 103: from 2001:db8:1338::/126 to 2001:db8:16::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1339::/56 to 2001:db8:13::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1339::/56 to 2001:db8:16::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1338::/126 to 2001:db8:13::/48 fwmark 0x17 lookup 150 + """ + tmp = cmd('ip rule show prio 103') + tmp_v6 = cmd('ip -6 rule show prio 103') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + self.assertEqual(sort_ip(tmp_v6), sort_ip(original_v6)) + + self.cli_delete(path) + self.cli_delete(path_v6) + self.cli_commit() + + tmp = cmd('ip rule show prio 103') + tmp_v6 = cmd('ip -6 rule show prio 103') + + self.assertEqual(sort_ip(tmp), []) + self.assertEqual(sort_ip(tmp_v6), []) + + # Test multiple commits ipv4 + def test_multiple_commit_ipv4_table_id(self): + path = base_path + ['local-route'] + + sources = ['192.0.2.1', '192.0.2.2'] + destination = '203.0.113.25' + rule = '105' + table = '151' + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + for src in sources: + self.cli_set(path + ['rule', rule, 'source', 'address', src]) + + self.cli_commit() + + original_first = """ + 105: from 192.0.2.1 lookup 151 + 105: from 192.0.2.2 lookup 151 + """ + tmp = cmd('ip rule show prio 105') + + self.assertEqual(sort_ip(tmp), sort_ip(original_first)) + + # Create second commit with added destination + self.cli_set(path + ['rule', rule, 'destination', 'address', destination]) + self.cli_commit() + + original_second = """ + 105: from 192.0.2.1 to 203.0.113.25 lookup 151 + 105: from 192.0.2.2 to 203.0.113.25 lookup 151 + """ + tmp = cmd('ip rule show prio 105') + + self.assertEqual(sort_ip(tmp), sort_ip(original_second)) + + def test_frr_individual_remove_T6283_T6250(self): + path = base_path + ['route-map'] + route_maps = ['RMAP-1', 'RMAP_2'] + seq = '10' + base_local_preference = 300 + base_table = 50 + + # T6250 + local_preference = base_local_preference + table = base_table + for route_map in route_maps: + self.cli_set(path + [route_map, 'rule', seq, 'action', 'permit']) + self.cli_set(path + [route_map, 'rule', seq, 'set', 'table', str(table)]) + self.cli_set(path + [route_map, 'rule', seq, 'set', 'local-preference', str(local_preference)]) + local_preference += 20 + table += 5 + + self.cli_commit() + + local_preference = base_local_preference + table = base_table + for route_map in route_maps: + config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='') + self.assertIn(f' set local-preference {local_preference}', config) + self.assertIn(f' set table {table}', config) + local_preference += 20 + table += 5 + + for route_map in route_maps: + self.cli_delete(path + [route_map, 'rule', '10', 'set', 'table']) + # we explicitly commit multiple times to be as vandal as possible to the system + self.cli_commit() + + local_preference = base_local_preference + for route_map in route_maps: + config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='') + self.assertIn(f' set local-preference {local_preference}', config) + local_preference += 20 + + # T6283 + seq = '20' + prepend = '100 100 100' + for route_map in route_maps: + self.cli_set(path + [route_map, 'rule', seq, 'action', 'permit']) + self.cli_set(path + [route_map, 'rule', seq, 'set', 'as-path', 'prepend', prepend]) + + self.cli_commit() + + for route_map in route_maps: + config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='') + self.assertIn(f' set as-path prepend {prepend}', config) + + for route_map in route_maps: + self.cli_delete(path + [route_map, 'rule', seq, 'set']) + # we explicitly commit multiple times to be as vandal as possible to the system + self.cli_commit() + + for route_map in route_maps: + config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='') + self.assertNotIn(f' set', config) + +def sort_ip(output): + o = '\n'.join([' '.join(line.strip().split()) for line in output.strip().splitlines()]) + o = o.splitlines() + o.sort() + return o + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py new file mode 100644 index 0000000..797ab97 --- /dev/null +++ b/smoketest/scripts/cli/test_policy_route.py @@ -0,0 +1,321 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.utils.process import cmd + +mark = '100' +conn_mark = '555' +conn_mark_set = '111' +table_mark_offset = 0x7fffffff +table_id = '101' +vrf = 'PBRVRF' +vrf_table_id = '102' +interface = 'eth0' +interface_wc = 'ppp*' +interface_ip = '172.16.10.1/24' + +class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestPolicyRoute, cls).setUpClass() + # Clear out current configuration to allow running this test on a live system + cls.cli_delete(cls, ['policy', 'route']) + cls.cli_delete(cls, ['policy', 'route6']) + + cls.cli_set(cls, ['interfaces', 'ethernet', interface, 'address', interface_ip]) + cls.cli_set(cls, ['protocols', 'static', 'table', table_id, 'route', '0.0.0.0/0', 'interface', interface]) + + cls.cli_set(cls, ['vrf', 'name', vrf, 'table', vrf_table_id]) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'ethernet', interface, 'address', interface_ip]) + cls.cli_delete(cls, ['protocols', 'static', 'table', table_id]) + cls.cli_delete(cls, ['vrf', 'name', vrf]) + + super(TestPolicyRoute, cls).tearDownClass() + + def tearDown(self): + self.cli_delete(['policy', 'route']) + self.cli_delete(['policy', 'route6']) + self.cli_commit() + + # Verify nftables cleanup + nftables_search = [ + ['set N_smoketest_network'], + ['set N_smoketest_network1'], + ['chain VYOS_PBR_smoketest'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_mangle', inverse=True) + + # Verify ip rule cleanup + ip_rule_search = [ + ['fwmark ' + hex(table_mark_offset - int(table_id)), 'lookup ' + table_id] + ] + + self.verify_rules(ip_rule_search, inverse=True) + + def verify_rules(self, rules_search, inverse=False): + rule_output = cmd('ip rule show') + + for search in rules_search: + matched = False + for line in rule_output.split("\n"): + if all(item in line for item in search): + matched = True + break + self.assertTrue(not matched if inverse else matched, msg=search) + + def test_pbr_group(self): + self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24']) + self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'network', '172.16.101.0/24']) + self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'include', 'smoketest_network']) + + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'group', 'network-group', 'smoketest_network1']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'mark', mark]) + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface]) + + self.cli_commit() + + nftables_search = [ + [f'iifname "{interface}"','jump VYOS_PBR_UD_smoketest'], + ['ip daddr @N_smoketest_network1', 'ip saddr @N_smoketest_network'], + ] + + self.verify_nftables(nftables_search, 'ip vyos_mangle') + + self.cli_delete(['firewall']) + + def test_pbr_mark(self): + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'address', '172.16.20.10']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'mark', mark]) + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface]) + + self.cli_commit() + + mark_hex = "{0:#010x}".format(int(mark)) + + nftables_search = [ + [f'iifname "{interface}"','jump VYOS_PBR_UD_smoketest'], + ['ip daddr 172.16.10.10', 'ip saddr 172.16.20.10', 'meta mark set ' + mark_hex], + ] + + self.verify_nftables(nftables_search, 'ip vyos_mangle') + + def test_pbr_mark_connection(self): + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'address', '172.16.20.10']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'connection-mark', conn_mark]) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'connection-mark', conn_mark_set]) + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface]) + + self.cli_commit() + + mark_hex = "{0:#010x}".format(int(conn_mark)) + mark_hex_set = "{0:#010x}".format(int(conn_mark_set)) + + nftables_search = [ + [f'iifname "{interface}"','jump VYOS_PBR_UD_smoketest'], + ['ip daddr 172.16.10.10', 'ip saddr 172.16.20.10', 'ct mark ' + mark_hex, 'ct mark set ' + mark_hex_set], + ] + + self.verify_nftables(nftables_search, 'ip vyos_mangle') + + def test_pbr_table(self): + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'protocol', 'tcp']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'port', '8888']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'tcp', 'flags', 'syn']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'tcp', 'flags', 'not', 'ack']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'table', table_id]) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'protocol', 'tcp_udp']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'destination', 'port', '8888']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'set', 'table', table_id]) + + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface]) + self.cli_set(['policy', 'route6', 'smoketest6', 'interface', interface]) + + self.cli_commit() + + mark_hex = "{0:#010x}".format(table_mark_offset - int(table_id)) + + # IPv4 + + nftables_search = [ + [f'iifname "{interface}"', 'jump VYOS_PBR_UD_smoketest'], + ['tcp flags syn / syn,ack', 'tcp dport 8888', 'meta mark set ' + mark_hex] + ] + + self.verify_nftables(nftables_search, 'ip vyos_mangle') + + # IPv6 + + nftables6_search = [ + [f'iifname "{interface}"', 'jump VYOS_PBR6_UD_smoketest'], + ['meta l4proto { tcp, udp }', 'th dport 8888', 'meta mark set ' + mark_hex] + ] + + self.verify_nftables(nftables6_search, 'ip6 vyos_mangle') + + # IP rule fwmark -> table + + ip_rule_search = [ + ['fwmark ' + hex(table_mark_offset - int(table_id)), 'lookup ' + table_id] + ] + + self.verify_rules(ip_rule_search) + + + def test_pbr_vrf(self): + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'protocol', 'tcp']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'port', '8888']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'tcp', 'flags', 'syn']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'tcp', 'flags', 'not', 'ack']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'vrf', vrf]) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'protocol', 'tcp_udp']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'destination', 'port', '8888']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'set', 'vrf', vrf]) + + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface]) + self.cli_set(['policy', 'route6', 'smoketest6', 'interface', interface]) + + self.cli_commit() + + mark_hex = "{0:#010x}".format(table_mark_offset - int(vrf_table_id)) + + # IPv4 + + nftables_search = [ + [f'iifname "{interface}"', 'jump VYOS_PBR_UD_smoketest'], + ['tcp flags syn / syn,ack', 'tcp dport 8888', 'meta mark set ' + mark_hex] + ] + + self.verify_nftables(nftables_search, 'ip vyos_mangle') + + # IPv6 + + nftables6_search = [ + [f'iifname "{interface}"', 'jump VYOS_PBR6_UD_smoketest'], + ['meta l4proto { tcp, udp }', 'th dport 8888', 'meta mark set ' + mark_hex] + ] + + self.verify_nftables(nftables6_search, 'ip6 vyos_mangle') + + # IP rule fwmark -> table + + ip_rule_search = [ + ['fwmark ' + hex(table_mark_offset - int(vrf_table_id)), 'lookup ' + vrf] + ] + + self.verify_rules(ip_rule_search) + + + def test_pbr_matching_criteria(self): + self.cli_set(['policy', 'route', 'smoketest', 'default-log']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'protocol', 'udp']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'action', 'drop']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'mark', '2020']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'protocol', 'tcp']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'tcp', 'flags', 'syn']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'tcp', 'flags', 'not', 'ack']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'mark', '2-3000']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'set', 'table', table_id]) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'source', 'address', '198.51.100.0/24']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'protocol', 'tcp']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'destination', 'port', '22']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'state', 'new']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'ttl', 'gt', '2']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'mark', '!456']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'set', 'table', table_id]) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'protocol', 'icmp']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'icmp', 'type-name', 'echo-request']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'packet-length', '128']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'packet-length', '1024-2048']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'packet-type', 'other']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'log']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'set', 'table', table_id]) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '5', 'dscp', '41']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '5', 'dscp', '57-59']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '5', 'mark', '!456-500']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '5', 'set', 'table', table_id]) + + self.cli_set(['policy', 'route6', 'smoketest6', 'default-log']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'protocol', 'udp']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'action', 'drop']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '2', 'protocol', 'tcp']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '2', 'tcp', 'flags', 'syn']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '2', 'tcp', 'flags', 'not', 'ack']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '2', 'set', 'table', table_id]) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'source', 'address', '2001:db8::0/64']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'protocol', 'tcp']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'destination', 'port', '22']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'state', 'new']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'hop-limit', 'gt', '2']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'set', 'table', table_id]) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'protocol', 'icmpv6']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'icmpv6', 'type', 'echo-request']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'packet-length-exclude', '128']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'packet-length-exclude', '1024-2048']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'packet-type', 'multicast']) + + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'log']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'set', 'table', table_id]) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'dscp-exclude', '61']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'dscp-exclude', '14-19']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'set', 'table', table_id]) + + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface]) + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface_wc]) + self.cli_set(['policy', 'route6', 'smoketest6', 'interface', interface_wc]) + + self.cli_commit() + + mark_hex = "{0:#010x}".format(table_mark_offset - int(table_id)) + + # IPv4 + nftables_search = [ + ['iifname { "' + interface + '", "' + interface_wc + '" }', 'jump VYOS_PBR_UD_smoketest'], + ['meta l4proto udp', 'meta mark 0x000007e4', 'drop'], + ['tcp flags syn / syn,ack', 'meta mark 0x00000002-0x00000bb8', 'meta mark set ' + mark_hex], + ['ct state new', 'tcp dport 22', 'ip saddr 198.51.100.0/24', 'ip ttl > 2', 'meta mark != 0x000001c8', 'meta mark set ' + mark_hex], + ['log prefix "[ipv4-route-smoketest-4-A]"', 'icmp type echo-request', 'ip length { 128, 1024-2048 }', 'meta pkttype other', 'meta mark set ' + mark_hex], + ['ip dscp { 0x29, 0x39-0x3b }', 'meta mark != 0x000001c8-0x000001f4', 'meta mark set ' + mark_hex], + ['log prefix "[ipv4-smoketest-default]"'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_mangle') + + # IPv6 + nftables6_search = [ + [f'iifname "{interface_wc}"', 'jump VYOS_PBR6_UD_smoketest'], + ['meta l4proto udp', 'drop'], + ['tcp flags syn / syn,ack', 'meta mark set ' + mark_hex], + ['ct state new', 'tcp dport 22', 'ip6 saddr 2001:db8::/64', 'ip6 hoplimit > 2', 'meta mark set ' + mark_hex], + ['log prefix "[ipv6-route6-smoketest6-4-A]"', 'icmpv6 type echo-request', 'ip6 length != { 128, 1024-2048 }', 'meta pkttype multicast', 'meta mark set ' + mark_hex], + ['ip6 dscp != { 0x0e-0x13, 0x3d }', 'meta mark set ' + mark_hex], + ['log prefix "[ipv6-smoketest6-default]"'] + ] + + self.verify_nftables(nftables6_search, 'ip6 vyos_mangle') + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_bfd.py b/smoketest/scripts/cli/test_protocols_bfd.py new file mode 100644 index 0000000..716d0a8 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_bfd.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'bfdd' +base_path = ['protocols', 'bfd'] + +dum_if = 'dum1001' +vrf_name = 'red' +peers = { + '192.0.2.10' : { + 'intv_rx' : '500', + 'intv_tx' : '600', + 'multihop' : '', + 'source_addr': '192.0.2.254', + 'profile' : 'foo-bar-baz', + 'minimum_ttl': '20', + }, + '192.0.2.20' : { + 'echo_mode' : '', + 'intv_echo' : '100', + 'intv_mult' : '100', + 'intv_rx' : '222', + 'intv_tx' : '333', + 'passive' : '', + 'shutdown' : '', + 'profile' : 'foo', + 'source_intf': dum_if, + }, + '2001:db8::1000:1' : { + 'source_addr': '2001:db8::1', + 'vrf' : vrf_name, + }, + '2001:db8::2000:1' : { + 'source_addr': '2001:db8::1', + 'multihop' : '', + 'profile' : 'baz_foo', + }, +} + +profiles = { + 'foo' : { + 'echo_mode' : '', + 'intv_echo' : '100', + 'intv_mult' : '101', + 'intv_rx' : '222', + 'intv_tx' : '333', + 'shutdown' : '', + 'minimum_ttl': '40', + }, + 'foo-bar-baz' : { + 'intv_mult' : '4', + 'intv_rx' : '400', + 'intv_tx' : '400', + }, + 'baz_foo' : { + 'intv_mult' : '102', + 'intv_rx' : '444', + 'passive' : '', + }, +} + +class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestProtocolsBFD, cls).setUpClass() + + # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same + cls.daemon_pid = process_named_running(PROCESS_NAME) + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + # check process health and continuity + self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + + def test_bfd_peer(self): + self.cli_set(['vrf', 'name', vrf_name, 'table', '1000']) + + for peer, peer_config in peers.items(): + if 'echo_mode' in peer_config: + self.cli_set(base_path + ['peer', peer, 'echo-mode']) + if 'intv_echo' in peer_config: + self.cli_set(base_path + ['peer', peer, 'interval', 'echo-interval', peer_config["intv_echo"]]) + if 'intv_mult' in peer_config: + self.cli_set(base_path + ['peer', peer, 'interval', 'multiplier', peer_config["intv_mult"]]) + if 'intv_rx' in peer_config: + self.cli_set(base_path + ['peer', peer, 'interval', 'receive', peer_config["intv_rx"]]) + if 'intv_tx' in peer_config: + self.cli_set(base_path + ['peer', peer, 'interval', 'transmit', peer_config["intv_tx"]]) + if 'minimum_ttl' in peer_config: + self.cli_set(base_path + ['peer', peer, 'minimum-ttl', peer_config["minimum_ttl"]]) + if 'multihop' in peer_config: + self.cli_set(base_path + ['peer', peer, 'multihop']) + if 'passive' in peer_config: + self.cli_set(base_path + ['peer', peer, 'passive']) + if 'shutdown' in peer_config: + self.cli_set(base_path + ['peer', peer, 'shutdown']) + if 'source_addr' in peer_config: + self.cli_set(base_path + ['peer', peer, 'source', 'address', peer_config["source_addr"]]) + if 'source_intf' in peer_config: + self.cli_set(base_path + ['peer', peer, 'source', 'interface', peer_config["source_intf"]]) + if 'vrf' in peer_config: + self.cli_set(base_path + ['peer', peer, 'vrf', peer_config["vrf"]]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig('bfd', daemon=PROCESS_NAME) + for peer, peer_config in peers.items(): + tmp = f'peer {peer}' + if 'multihop' in peer_config: + tmp += f' multihop' + if 'source_addr' in peer_config: + tmp += f' local-address {peer_config["source_addr"]}' + if 'source_intf' in peer_config: + tmp += f' interface {peer_config["source_intf"]}' + if 'vrf' in peer_config: + tmp += f' vrf {peer_config["vrf"]}' + + self.assertIn(tmp, frrconfig) + peerconfig = self.getFRRconfig(f' peer {peer}', end='', daemon=PROCESS_NAME) + + if 'echo_mode' in peer_config: + self.assertIn(f'echo-mode', peerconfig) + if 'intv_echo' in peer_config: + self.assertIn(f'echo receive-interval {peer_config["intv_echo"]}', peerconfig) + self.assertIn(f'echo transmit-interval {peer_config["intv_echo"]}', peerconfig) + if 'intv_mult' in peer_config: + self.assertIn(f'detect-multiplier {peer_config["intv_mult"]}', peerconfig) + if 'intv_rx' in peer_config: + self.assertIn(f'receive-interval {peer_config["intv_rx"]}', peerconfig) + if 'intv_tx' in peer_config: + self.assertIn(f'transmit-interval {peer_config["intv_tx"]}', peerconfig) + if 'minimum_ttl' in peer_config: + self.assertIn(f'minimum-ttl {peer_config["minimum_ttl"]}', peerconfig) + if 'passive' in peer_config: + self.assertIn(f'passive-mode', peerconfig) + if 'shutdown' in peer_config: + self.assertIn(f'shutdown', peerconfig) + else: + self.assertNotIn(f'shutdown', peerconfig) + + self.cli_delete(['vrf', 'name', vrf_name]) + + def test_bfd_profile(self): + for profile, profile_config in profiles.items(): + if 'echo_mode' in profile_config: + self.cli_set(base_path + ['profile', profile, 'echo-mode']) + if 'intv_echo' in profile_config: + self.cli_set(base_path + ['profile', profile, 'interval', 'echo-interval', profile_config["intv_echo"]]) + if 'intv_mult' in profile_config: + self.cli_set(base_path + ['profile', profile, 'interval', 'multiplier', profile_config["intv_mult"]]) + if 'intv_rx' in profile_config: + self.cli_set(base_path + ['profile', profile, 'interval', 'receive', profile_config["intv_rx"]]) + if 'intv_tx' in profile_config: + self.cli_set(base_path + ['profile', profile, 'interval', 'transmit', profile_config["intv_tx"]]) + if 'minimum_ttl' in profile_config: + self.cli_set(base_path + ['profile', profile, 'minimum-ttl', profile_config["minimum_ttl"]]) + if 'passive' in profile_config: + self.cli_set(base_path + ['profile', profile, 'passive']) + if 'shutdown' in profile_config: + self.cli_set(base_path + ['profile', profile, 'shutdown']) + + for peer, peer_config in peers.items(): + if 'profile' in peer_config: + self.cli_set(base_path + ['peer', peer, 'profile', peer_config["profile"] + 'wrong']) + if 'source_addr' in peer_config: + self.cli_set(base_path + ['peer', peer, 'source', 'address', peer_config["source_addr"]]) + if 'source_intf' in peer_config: + self.cli_set(base_path + ['peer', peer, 'source', 'interface', peer_config["source_intf"]]) + + # BFD profile does not exist! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for peer, peer_config in peers.items(): + if 'profile' in peer_config: + self.cli_set(base_path + ['peer', peer, 'profile', peer_config["profile"]]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + for profile, profile_config in profiles.items(): + config = self.getFRRconfig(f' profile {profile}', endsection='^ !') + if 'echo_mode' in profile_config: + self.assertIn(f' echo-mode', config) + if 'intv_echo' in profile_config: + self.assertIn(f' echo receive-interval {profile_config["intv_echo"]}', config) + self.assertIn(f' echo transmit-interval {profile_config["intv_echo"]}', config) + if 'intv_mult' in profile_config: + self.assertIn(f' detect-multiplier {profile_config["intv_mult"]}', config) + if 'intv_rx' in profile_config: + self.assertIn(f' receive-interval {profile_config["intv_rx"]}', config) + if 'intv_tx' in profile_config: + self.assertIn(f' transmit-interval {profile_config["intv_tx"]}', config) + if 'minimum_ttl' in profile_config: + self.assertIn(f' minimum-ttl {profile_config["minimum_ttl"]}', config) + if 'passive' in profile_config: + self.assertIn(f' passive-mode', config) + if 'shutdown' in profile_config: + self.assertIn(f' shutdown', config) + else: + self.assertNotIn(f'shutdown', config) + + for peer, peer_config in peers.items(): + peerconfig = self.getFRRconfig(f' peer {peer}', end='', daemon=PROCESS_NAME) + if 'profile' in peer_config: + self.assertIn(f' profile {peer_config["profile"]}', peerconfig) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py new file mode 100644 index 0000000..ea2f561 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -0,0 +1,1411 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from time import sleep + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.ifconfig import Section +from vyos.configsession import ConfigSessionError +from vyos.template import is_ipv6 +from vyos.utils.process import process_named_running +from vyos.utils.process import cmd + +PROCESS_NAME = 'bgpd' +ASN = '64512' +base_path = ['protocols', 'bgp'] + +route_map_in = 'foo-map-in' +route_map_out = 'foo-map-out' +prefix_list_in = 'pfx-foo-in' +prefix_list_out = 'pfx-foo-out' +prefix_list_in6 = 'pfx-foo-in6' +prefix_list_out6 = 'pfx-foo-out6' +bfd_profile = 'foo-bar-baz' + +import_afi = 'ipv4-unicast' +import_vrf = 'red' +import_rd = ASN + ':100' +import_vrf_base = ['vrf', 'name'] +neighbor_config = { + '192.0.2.1' : { + 'bfd' : '', + 'cap_dynamic' : '', + 'cap_ext_next' : '', + 'cap_ext_sver' : '', + 'remote_as' : '100', + 'adv_interv' : '400', + 'passive' : '', + 'password' : 'VyOS-Secure123', + 'shutdown' : '', + 'cap_over' : '', + 'ttl_security' : '5', + 'system_as' : '300', + 'route_map_in' : route_map_in, + 'route_map_out' : route_map_out, + 'no_send_comm_ext' : '', + 'addpath_all' : '', + 'p_attr_discard' : ['10', '20', '30', '40', '50'], + }, + '192.0.2.2' : { + 'bfd_profile' : bfd_profile, + 'remote_as' : '200', + 'shutdown' : '', + 'no_cap_nego' : '', + 'port' : '667', + 'cap_strict' : '', + 'advertise_map' : route_map_in, + 'non_exist_map' : route_map_out, + 'pfx_list_in' : prefix_list_in, + 'pfx_list_out' : prefix_list_out, + 'no_send_comm_std' : '', + 'local_role' : 'rs-client', + 'p_attr_taw' : '200', + }, + '192.0.2.3' : { + 'advertise_map' : route_map_in, + 'description' : 'foo bar baz', + 'remote_as' : '200', + 'passive' : '', + 'multi_hop' : '5', + 'update_src' : 'lo', + 'peer_group' : 'foo', + 'graceful_rst' : '', + }, + '2001:db8::1' : { + 'advertise_map' : route_map_in, + 'exist_map' : route_map_out, + 'cap_dynamic' : '', + 'cap_ext_next' : '', + 'cap_ext_sver' : '', + 'remote_as' : '123', + 'adv_interv' : '400', + 'passive' : '', + 'password' : 'VyOS-Secure123', + 'shutdown' : '', + 'cap_over' : '', + 'ttl_security' : '5', + 'system_as' : '300', + 'solo' : '', + 'route_map_in' : route_map_in, + 'route_map_out' : route_map_out, + 'no_send_comm_std' : '', + 'addpath_per_as' : '', + 'peer_group' : 'foo-bar', + 'local_role' : 'customer', + 'local_role_strict': '', + }, + '2001:db8::2' : { + 'remote_as' : '456', + 'shutdown' : '', + 'no_cap_nego' : '', + 'port' : '667', + 'cap_strict' : '', + 'pfx_list_in' : prefix_list_in6, + 'pfx_list_out' : prefix_list_out6, + 'no_send_comm_ext' : '', + 'peer_group' : 'foo-bar_baz', + 'graceful_rst_hlp' : '', + 'disable_conn_chk' : '', + }, +} + +peer_group_config = { + 'foo' : { + 'advertise_map' : route_map_in, + 'exist_map' : route_map_out, + 'bfd' : '', + 'remote_as' : '100', + 'passive' : '', + 'password' : 'VyOS-Secure123', + 'shutdown' : '', + 'cap_over' : '', + 'ttl_security' : '5', + 'disable_conn_chk' : '', + 'p_attr_discard' : ['100', '150', '200'], + }, + 'bar' : { + 'remote_as' : '111', + 'graceful_rst_no' : '', + 'port' : '667', + 'p_attr_taw' : '126', + }, + 'foo-bar' : { + 'advertise_map' : route_map_in, + 'description' : 'foo peer bar group', + 'remote_as' : '200', + 'shutdown' : '', + 'no_cap_nego' : '', + 'system_as' : '300', + 'pfx_list_in' : prefix_list_in, + 'pfx_list_out' : prefix_list_out, + 'no_send_comm_ext' : '', + }, + 'foo-bar_baz' : { + 'advertise_map' : route_map_in, + 'non_exist_map' : route_map_out, + 'bfd_profile' : bfd_profile, + 'cap_dynamic' : '', + 'cap_ext_next' : '', + 'remote_as' : '200', + 'passive' : '', + 'multi_hop' : '5', + 'update_src' : 'lo', + 'route_map_in' : route_map_in, + 'route_map_out' : route_map_out, + 'local_role' : 'peer', + 'local_role_strict': '', + }, +} +class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestProtocolsBGP, cls).setUpClass() + + # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same + cls.daemon_pid = process_named_running(PROCESS_NAME) + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + cls.cli_delete(cls, ['policy', 'route-map']) + cls.cli_delete(cls, ['policy', 'prefix-list']) + cls.cli_delete(cls, ['policy', 'prefix-list6']) + cls.cli_delete(cls, ['vrf']) + + cls.cli_set(cls, ['policy', 'route-map', route_map_in, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'route-map', route_map_out, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_in, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_in, 'rule', '10', 'prefix', '192.0.2.0/25']) + cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_out, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_out, 'rule', '10', 'prefix', '192.0.2.128/25']) + + cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_in6, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_in6, 'rule', '10', 'prefix', '2001:db8:1000::/64']) + cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_out6, 'rule', '10', 'action', 'deny']) + cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_out6, 'rule', '10', 'prefix', '2001:db8:2000::/64']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['policy', 'route-map']) + cls.cli_delete(cls, ['policy', 'prefix-list']) + cls.cli_delete(cls, ['policy', 'prefix-list6']) + + def setUp(self): + self.cli_set(base_path + ['system-as', ASN]) + + def tearDown(self): + # cleanup any possible VRF mess + self.cli_delete(['vrf']) + # always destrox the entire bgpd configuration to make the processes + # life as hard as possible + self.cli_delete(base_path) + self.cli_commit() + + # check process health and continuity + self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + + def create_bgp_instances_for_import_test(self): + table = '1000' + self.cli_set(import_vrf_base + [import_vrf, 'table', table]) + self.cli_set(import_vrf_base + [import_vrf, 'protocols', 'bgp', 'system-as', ASN]) + + def verify_frr_config(self, peer, peer_config, frrconfig): + # recurring patterns to verify for both a simple neighbor and a peer-group + if 'bfd' in peer_config: + self.assertIn(f' neighbor {peer} bfd', frrconfig) + if 'bfd_profile' in peer_config: + self.assertIn(f' neighbor {peer} bfd profile {peer_config["bfd_profile"]}', frrconfig) + self.assertIn(f' neighbor {peer} bfd check-control-plane-failure', frrconfig) + if 'cap_dynamic' in peer_config: + self.assertIn(f' neighbor {peer} capability dynamic', frrconfig) + if 'cap_ext_next' in peer_config: + self.assertIn(f' neighbor {peer} capability extended-nexthop', frrconfig) + if 'cap_ext_sver' in peer_config: + self.assertIn(f' neighbor {peer} capability software-version', frrconfig) + if 'description' in peer_config: + self.assertIn(f' neighbor {peer} description {peer_config["description"]}', frrconfig) + if 'no_cap_nego' in peer_config: + self.assertIn(f' neighbor {peer} dont-capability-negotiate', frrconfig) + if 'multi_hop' in peer_config: + self.assertIn(f' neighbor {peer} ebgp-multihop {peer_config["multi_hop"]}', frrconfig) + if 'local_as' in peer_config: + self.assertIn(f' neighbor {peer} local-as {peer_config["local_as"]} no-prepend replace-as', frrconfig) + if 'local_role' in peer_config: + tmp = f' neighbor {peer} local-role {peer_config["local_role"]}' + if 'local_role_strict' in peer_config: + tmp += ' strict' + self.assertIn(tmp, frrconfig) + if 'cap_over' in peer_config: + self.assertIn(f' neighbor {peer} override-capability', frrconfig) + if 'passive' in peer_config: + self.assertIn(f' neighbor {peer} passive', frrconfig) + if 'password' in peer_config: + self.assertIn(f' neighbor {peer} password {peer_config["password"]}', frrconfig) + if 'port' in peer_config: + self.assertIn(f' neighbor {peer} port {peer_config["port"]}', frrconfig) + if 'remote_as' in peer_config: + self.assertIn(f' neighbor {peer} remote-as {peer_config["remote_as"]}', frrconfig) + if 'solo' in peer_config: + self.assertIn(f' neighbor {peer} solo', frrconfig) + if 'shutdown' in peer_config: + self.assertIn(f' neighbor {peer} shutdown', frrconfig) + if 'ttl_security' in peer_config: + self.assertIn(f' neighbor {peer} ttl-security hops {peer_config["ttl_security"]}', frrconfig) + if 'update_src' in peer_config: + self.assertIn(f' neighbor {peer} update-source {peer_config["update_src"]}', frrconfig) + if 'route_map_in' in peer_config: + self.assertIn(f' neighbor {peer} route-map {peer_config["route_map_in"]} in', frrconfig) + if 'route_map_out' in peer_config: + self.assertIn(f' neighbor {peer} route-map {peer_config["route_map_out"]} out', frrconfig) + if 'pfx_list_in' in peer_config: + self.assertIn(f' neighbor {peer} prefix-list {peer_config["pfx_list_in"]} in', frrconfig) + if 'pfx_list_out' in peer_config: + self.assertIn(f' neighbor {peer} prefix-list {peer_config["pfx_list_out"]} out', frrconfig) + if 'no_send_comm_std' in peer_config: + self.assertIn(f' no neighbor {peer} send-community', frrconfig) + if 'no_send_comm_ext' in peer_config: + self.assertIn(f' no neighbor {peer} send-community extended', frrconfig) + if 'addpath_all' in peer_config: + self.assertIn(f' neighbor {peer} addpath-tx-all-paths', frrconfig) + if 'p_attr_discard' in peer_config: + tmp = ' '.join(peer_config["p_attr_discard"]) + self.assertIn(f' neighbor {peer} path-attribute discard {tmp}', frrconfig) + if 'p_attr_taw' in peer_config: + self.assertIn(f' neighbor {peer} path-attribute treat-as-withdraw {peer_config["p_attr_taw"]}', frrconfig) + if 'addpath_per_as' in peer_config: + self.assertIn(f' neighbor {peer} addpath-tx-bestpath-per-AS', frrconfig) + if 'advertise_map' in peer_config: + base = f' neighbor {peer} advertise-map {peer_config["advertise_map"]}' + if 'exist_map' in peer_config: + base = f'{base} exist-map {peer_config["exist_map"]}' + if 'non_exist_map' in peer_config: + base = f'{base} non-exist-map {peer_config["non_exist_map"]}' + self.assertIn(base, frrconfig) + if 'graceful_rst' in peer_config: + self.assertIn(f' neighbor {peer} graceful-restart', frrconfig) + if 'graceful_rst_no' in peer_config: + self.assertIn(f' neighbor {peer} graceful-restart-disable', frrconfig) + if 'graceful_rst_hlp' in peer_config: + self.assertIn(f' neighbor {peer} graceful-restart-helper', frrconfig) + if 'disable_conn_chk' in peer_config: + self.assertIn(f' neighbor {peer} disable-connected-check', frrconfig) + + def test_bgp_01_simple(self): + router_id = '127.0.0.1' + local_pref = '500' + stalepath_time = '60' + max_path_v4 = '2' + max_path_v4ibgp = '4' + max_path_v6 = '8' + max_path_v6ibgp = '16' + cond_adv_timer = '30' + min_hold_time = '2' + tcp_keepalive_idle = '66' + tcp_keepalive_interval = '77' + tcp_keepalive_probes = '22' + + self.cli_set(base_path + ['parameters', 'allow-martian-nexthop']) + self.cli_set(base_path + ['parameters', 'disable-ebgp-connected-route-check']) + self.cli_set(base_path + ['parameters', 'no-hard-administrative-reset']) + self.cli_set(base_path + ['parameters', 'log-neighbor-changes']) + self.cli_set(base_path + ['parameters', 'labeled-unicast', 'explicit-null']) + self.cli_set(base_path + ['parameters', 'router-id', router_id]) + + # System AS number MUST be defined - as this is set in setUp() we remove + # this once for testing of the proper error + self.cli_delete(base_path + ['system-as']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['system-as', ASN]) + + # Default local preference (higher = more preferred, default value is 100) + self.cli_set(base_path + ['parameters', 'default', 'local-pref', local_pref]) + self.cli_set(base_path + ['parameters', 'graceful-restart', 'stalepath-time', stalepath_time]) + self.cli_set(base_path + ['parameters', 'graceful-shutdown']) + self.cli_set(base_path + ['parameters', 'ebgp-requires-policy']) + + self.cli_set(base_path + ['parameters', 'bestpath', 'as-path', 'multipath-relax']) + self.cli_set(base_path + ['parameters', 'bestpath', 'bandwidth', 'default-weight-for-missing']) + self.cli_set(base_path + ['parameters', 'bestpath', 'compare-routerid']) + self.cli_set(base_path + ['parameters', 'bestpath', 'peer-type', 'multipath-relax']) + + self.cli_set(base_path + ['parameters', 'conditional-advertisement', 'timer', cond_adv_timer]) + self.cli_set(base_path + ['parameters', 'fast-convergence']) + self.cli_set(base_path + ['parameters', 'minimum-holdtime', min_hold_time]) + self.cli_set(base_path + ['parameters', 'no-suppress-duplicates']) + self.cli_set(base_path + ['parameters', 'reject-as-sets']) + self.cli_set(base_path + ['parameters', 'route-reflector-allow-outbound-policy']) + self.cli_set(base_path + ['parameters', 'shutdown']) + self.cli_set(base_path + ['parameters', 'suppress-fib-pending']) + self.cli_set(base_path + ['parameters', 'tcp-keepalive', 'idle', tcp_keepalive_idle]) + self.cli_set(base_path + ['parameters', 'tcp-keepalive', 'interval', tcp_keepalive_interval]) + self.cli_set(base_path + ['parameters', 'tcp-keepalive', 'probes', tcp_keepalive_probes]) + + # AFI maximum path support + self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ebgp', max_path_v4]) + self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ibgp', max_path_v4ibgp]) + self.cli_set(base_path + ['address-family', 'ipv4-labeled-unicast', 'maximum-paths', 'ebgp', max_path_v4]) + self.cli_set(base_path + ['address-family', 'ipv4-labeled-unicast', 'maximum-paths', 'ibgp', max_path_v4ibgp]) + self.cli_set(base_path + ['address-family', 'ipv6-unicast', 'maximum-paths', 'ebgp', max_path_v6]) + self.cli_set(base_path + ['address-family', 'ipv6-unicast', 'maximum-paths', 'ibgp', max_path_v6ibgp]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' bgp router-id {router_id}', frrconfig) + self.assertIn(f' bgp allow-martian-nexthop', frrconfig) + self.assertIn(f' bgp disable-ebgp-connected-route-check', frrconfig) + self.assertIn(f' bgp log-neighbor-changes', frrconfig) + self.assertIn(f' bgp default local-preference {local_pref}', frrconfig) + self.assertIn(f' bgp conditional-advertisement timer {cond_adv_timer}', frrconfig) + self.assertIn(f' bgp fast-convergence', frrconfig) + self.assertIn(f' bgp graceful-restart stalepath-time {stalepath_time}', frrconfig) + self.assertIn(f' bgp graceful-shutdown', frrconfig) + self.assertIn(f' no bgp hard-administrative-reset', frrconfig) + self.assertIn(f' bgp labeled-unicast explicit-null', frrconfig) + self.assertIn(f' bgp bestpath as-path multipath-relax', frrconfig) + self.assertIn(f' bgp bestpath bandwidth default-weight-for-missing', frrconfig) + self.assertIn(f' bgp bestpath compare-routerid', frrconfig) + self.assertIn(f' bgp bestpath peer-type multipath-relax', frrconfig) + self.assertIn(f' bgp minimum-holdtime {min_hold_time}', frrconfig) + self.assertIn(f' bgp reject-as-sets', frrconfig) + self.assertIn(f' bgp route-reflector allow-outbound-policy', frrconfig) + self.assertIn(f' bgp shutdown', frrconfig) + self.assertIn(f' bgp suppress-fib-pending', frrconfig) + self.assertIn(f' bgp tcp-keepalive {tcp_keepalive_idle} {tcp_keepalive_interval} {tcp_keepalive_probes}', frrconfig) + self.assertNotIn(f'bgp ebgp-requires-policy', frrconfig) + self.assertIn(f' no bgp suppress-duplicates', frrconfig) + + afiv4_config = self.getFRRconfig(' address-family ipv4 unicast') + self.assertIn(f' maximum-paths {max_path_v4}', afiv4_config) + self.assertIn(f' maximum-paths ibgp {max_path_v4ibgp}', afiv4_config) + + afiv4_config = self.getFRRconfig(' address-family ipv4 labeled-unicast') + self.assertIn(f' maximum-paths {max_path_v4}', afiv4_config) + self.assertIn(f' maximum-paths ibgp {max_path_v4ibgp}', afiv4_config) + + afiv6_config = self.getFRRconfig(' address-family ipv6 unicast') + self.assertIn(f' maximum-paths {max_path_v6}', afiv6_config) + self.assertIn(f' maximum-paths ibgp {max_path_v6ibgp}', afiv6_config) + + def test_bgp_02_neighbors(self): + # Test out individual neighbor configuration items, not all of them are + # also available to a peer-group! + self.cli_set(base_path + ['parameters', 'deterministic-med']) + + for peer, peer_config in neighbor_config.items(): + afi = 'ipv4-unicast' + if is_ipv6(peer): + afi = 'ipv6-unicast' + + if 'adv_interv' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'advertisement-interval', peer_config["adv_interv"]]) + if 'bfd' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'bfd']) + if 'bfd_profile' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'bfd', 'profile', peer_config["bfd_profile"]]) + self.cli_set(base_path + ['neighbor', peer, 'bfd', 'check-control-plane-failure']) + if 'cap_dynamic' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'capability', 'dynamic']) + if 'cap_ext_next' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'capability', 'extended-nexthop']) + if 'cap_ext_sver' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'capability', 'software-version']) + if 'description' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'description', peer_config["description"]]) + if 'no_cap_nego' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'disable-capability-negotiation']) + if 'multi_hop' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'ebgp-multihop', peer_config["multi_hop"]]) + if 'local_as' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'local-as', peer_config["local_as"], 'no-prepend', 'replace-as']) + if 'local_role' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'local-role', peer_config["local_role"]]) + if 'local_role_strict' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'local-role', peer_config["local_role"], 'strict']) + if 'cap_over' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'override-capability']) + if 'passive' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'passive']) + if 'password' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'password', peer_config["password"]]) + if 'port' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'port', peer_config["port"]]) + if 'remote_as' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'remote-as', peer_config["remote_as"]]) + if 'cap_strict' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'strict-capability-match']) + if 'shutdown' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'shutdown']) + if 'solo' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'solo']) + if 'ttl_security' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'ttl-security', 'hops', peer_config["ttl_security"]]) + if 'update_src' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'update-source', peer_config["update_src"]]) + if 'p_attr_discard' in peer_config: + for attribute in peer_config['p_attr_discard']: + self.cli_set(base_path + ['neighbor', peer, 'path-attribute', 'discard', attribute]) + if 'p_attr_taw' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'path-attribute', 'treat-as-withdraw', peer_config["p_attr_taw"]]) + if 'route_map_in' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'route-map', 'import', peer_config["route_map_in"]]) + if 'route_map_out' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'route-map', 'export', peer_config["route_map_out"]]) + if 'pfx_list_in' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'prefix-list', 'import', peer_config["pfx_list_in"]]) + if 'pfx_list_out' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'prefix-list', 'export', peer_config["pfx_list_out"]]) + if 'no_send_comm_std' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'disable-send-community', 'standard']) + if 'no_send_comm_ext' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'disable-send-community', 'extended']) + if 'addpath_all' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'addpath-tx-all']) + if 'addpath_per_as' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'addpath-tx-per-as']) + if 'graceful_rst' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'graceful-restart', 'enable']) + if 'graceful_rst_no' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'graceful-restart', 'disable']) + if 'graceful_rst_hlp' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'graceful-restart', 'restart-helper']) + if 'disable_conn_chk' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'disable-connected-check']) + + # Conditional advertisement + if 'advertise_map' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'conditionally-advertise', 'advertise-map', peer_config["advertise_map"]]) + # Either exist-map or non-exist-map needs to be specified + if 'exist_map' not in peer_config and 'non_exist_map' not in peer_config: + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'conditionally-advertise', 'exist-map', route_map_in]) + + if 'exist_map' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'conditionally-advertise', 'exist-map', peer_config["exist_map"]]) + if 'non_exist_map' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'conditionally-advertise', 'non-exist-map', peer_config["non_exist_map"]]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + + for peer, peer_config in neighbor_config.items(): + if 'adv_interv' in peer_config: + self.assertIn(f' neighbor {peer} advertisement-interval {peer_config["adv_interv"]}', frrconfig) + if 'cap_strict' in peer_config: + self.assertIn(f' neighbor {peer} strict-capability-match', frrconfig) + + self.verify_frr_config(peer, peer_config, frrconfig) + + def test_bgp_03_peer_groups(self): + # Test out individual peer-group configuration items + for peer_group, config in peer_group_config.items(): + if 'bfd' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'bfd']) + if 'bfd_profile' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'bfd', 'profile', config["bfd_profile"]]) + self.cli_set(base_path + ['peer-group', peer_group, 'bfd', 'check-control-plane-failure']) + if 'cap_dynamic' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'capability', 'dynamic']) + if 'cap_ext_next' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'capability', 'extended-nexthop']) + if 'cap_ext_sver' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'capability', 'software-version']) + if 'description' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'description', config["description"]]) + if 'no_cap_nego' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'disable-capability-negotiation']) + if 'multi_hop' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'ebgp-multihop', config["multi_hop"]]) + if 'local_as' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'local-as', config["local_as"], 'no-prepend', 'replace-as']) + if 'local_role' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'local-role', config["local_role"]]) + if 'local_role_strict' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'local-role', config["local_role"], 'strict']) + if 'cap_over' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'override-capability']) + if 'passive' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'passive']) + if 'password' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'password', config["password"]]) + if 'port' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'port', config["port"]]) + if 'remote_as' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'remote-as', config["remote_as"]]) + if 'shutdown' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'shutdown']) + if 'ttl_security' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'ttl-security', 'hops', config["ttl_security"]]) + if 'update_src' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'update-source', config["update_src"]]) + if 'route_map_in' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'route-map', 'import', config["route_map_in"]]) + if 'route_map_out' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'route-map', 'export', config["route_map_out"]]) + if 'pfx_list_in' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'prefix-list', 'import', config["pfx_list_in"]]) + if 'pfx_list_out' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'prefix-list', 'export', config["pfx_list_out"]]) + if 'no_send_comm_std' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'disable-send-community', 'standard']) + if 'no_send_comm_ext' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'disable-send-community', 'extended']) + if 'addpath_all' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'addpath-tx-all']) + if 'addpath_per_as' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'addpath-tx-per-as']) + if 'graceful_rst' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'graceful-restart', 'enable']) + if 'graceful_rst_no' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'graceful-restart', 'disable']) + if 'graceful_rst_hlp' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'graceful-restart', 'restart-helper']) + if 'disable_conn_chk' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'disable-connected-check']) + if 'p_attr_discard' in config: + for attribute in config['p_attr_discard']: + self.cli_set(base_path + ['peer-group', peer_group, 'path-attribute', 'discard', attribute]) + if 'p_attr_taw' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'path-attribute', 'treat-as-withdraw', config["p_attr_taw"]]) + + # Conditional advertisement + if 'advertise_map' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'conditionally-advertise', 'advertise-map', config["advertise_map"]]) + # Either exist-map or non-exist-map needs to be specified + if 'exist_map' not in config and 'non_exist_map' not in config: + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'conditionally-advertise', 'exist-map', route_map_in]) + + if 'exist_map' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'conditionally-advertise', 'exist-map', config["exist_map"]]) + if 'non_exist_map' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'conditionally-advertise', 'non-exist-map', config["non_exist_map"]]) + + for peer, peer_config in neighbor_config.items(): + if 'peer_group' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'peer-group', peer_config['peer_group']]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + + for peer, peer_config in peer_group_config.items(): + self.assertIn(f' neighbor {peer_group} peer-group', frrconfig) + self.verify_frr_config(peer, peer_config, frrconfig) + + for peer, peer_config in neighbor_config.items(): + if 'peer_group' in peer_config: + self.assertIn(f' neighbor {peer} peer-group {peer_config["peer_group"]}', frrconfig) + + def test_bgp_04_afi_ipv4(self): + networks = { + '10.0.0.0/8' : { + 'as_set' : '', + 'summary_only' : '', + 'route_map' : route_map_in, + }, + '100.64.0.0/10' : { + 'as_set' : '', + }, + '192.168.0.0/16' : { + 'summary_only' : '', + }, + } + + # We want to redistribute ... + redistributes = ['connected', 'isis', 'kernel', 'ospf', 'rip', 'static'] + for redistribute in redistributes: + self.cli_set(base_path + ['address-family', 'ipv4-unicast', + 'redistribute', redistribute]) + + for network, network_config in networks.items(): + self.cli_set(base_path + ['address-family', 'ipv4-unicast', + 'network', network]) + if 'as_set' in network_config: + self.cli_set(base_path + ['address-family', 'ipv4-unicast', + 'aggregate-address', network, 'as-set']) + if 'summary_only' in network_config: + self.cli_set(base_path + ['address-family', 'ipv4-unicast', + 'aggregate-address', network, 'summary-only']) + if 'route_map' in network_config: + self.cli_set(base_path + ['address-family', 'ipv4-unicast', + 'aggregate-address', network, 'route-map', network_config['route_map']]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' address-family ipv4 unicast', frrconfig) + + for redistribute in redistributes: + self.assertIn(f' redistribute {redistribute}', frrconfig) + + for network, network_config in networks.items(): + self.assertIn(f' network {network}', frrconfig) + command = f'aggregate-address {network}' + if 'as_set' in network_config: + command = f'{command} as-set' + if 'summary_only' in network_config: + command = f'{command} summary-only' + if 'route_map' in network_config: + command = f'{command} route-map {network_config["route_map"]}' + self.assertIn(command, frrconfig) + + def test_bgp_05_afi_ipv6(self): + networks = { + '2001:db8:100::/48' : { + }, + '2001:db8:200::/48' : { + }, + '2001:db8:300::/48' : { + 'summary_only' : '', + }, + } + + # We want to redistribute ... + redistributes = ['connected', 'kernel', 'ospfv3', 'ripng', 'static'] + for redistribute in redistributes: + self.cli_set(base_path + ['address-family', 'ipv6-unicast', + 'redistribute', redistribute]) + + for network, network_config in networks.items(): + self.cli_set(base_path + ['address-family', 'ipv6-unicast', + 'network', network]) + if 'summary_only' in network_config: + self.cli_set(base_path + ['address-family', 'ipv6-unicast', + 'aggregate-address', network, 'summary-only']) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' address-family ipv6 unicast', frrconfig) + # T2100: By default ebgp-requires-policy is disabled to keep VyOS + # 1.3 and 1.2 backwards compatibility + self.assertIn(f' no bgp ebgp-requires-policy', frrconfig) + + for redistribute in redistributes: + # FRR calls this OSPF6 + if redistribute == 'ospfv3': + redistribute = 'ospf6' + self.assertIn(f' redistribute {redistribute}', frrconfig) + + for network, network_config in networks.items(): + self.assertIn(f' network {network}', frrconfig) + if 'as_set' in network_config: + self.assertIn(f' aggregate-address {network} summary-only', frrconfig) + + def test_bgp_06_listen_range(self): + # Implemented via T1875 + limit = '64' + listen_ranges = ['192.0.2.0/25', '192.0.2.128/25'] + peer_group = 'listenfoobar' + + self.cli_set(base_path + ['listen', 'limit', limit]) + + for prefix in listen_ranges: + self.cli_set(base_path + ['listen', 'range', prefix]) + # check validate() - peer-group must be defined for range/prefix + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['listen', 'range', prefix, 'peer-group', peer_group]) + + # check validate() - peer-group does yet not exist! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['peer-group', peer_group, 'remote-as', ASN]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' neighbor {peer_group} peer-group', frrconfig) + self.assertIn(f' neighbor {peer_group} remote-as {ASN}', frrconfig) + self.assertIn(f' bgp listen limit {limit}', frrconfig) + for prefix in listen_ranges: + self.assertIn(f' bgp listen range {prefix} peer-group {peer_group}', frrconfig) + + def test_bgp_07_l2vpn_evpn(self): + vnis = ['10010', '10020', '10030'] + soo = '1.2.3.4:10000' + evi_limit = '1000' + route_targets = ['1.1.1.1:100', '1.1.1.1:200', '1.1.1.1:300'] + + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'advertise-all-vni']) + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'advertise-default-gw']) + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'advertise-svi-ip']) + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'flooding', 'disable']) + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'default-originate', 'ipv4']) + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'default-originate', 'ipv6']) + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'disable-ead-evi-rx']) + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'disable-ead-evi-tx']) + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'mac-vrf', 'soo', soo]) + for vni in vnis: + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'vni', vni, 'advertise-default-gw']) + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'vni', vni, 'advertise-svi-ip']) + + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'ead-es-frag', 'evi-limit', evi_limit]) + for route_target in route_targets: + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'ead-es-route-target', 'export', route_target]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' address-family l2vpn evpn', frrconfig) + self.assertIn(f' advertise-all-vni', frrconfig) + self.assertIn(f' advertise-default-gw', frrconfig) + self.assertIn(f' advertise-svi-ip', frrconfig) + self.assertIn(f' default-originate ipv4', frrconfig) + self.assertIn(f' default-originate ipv6', frrconfig) + self.assertIn(f' disable-ead-evi-rx', frrconfig) + self.assertIn(f' disable-ead-evi-tx', frrconfig) + self.assertIn(f' flooding disable', frrconfig) + self.assertIn(f' mac-vrf soo {soo}', frrconfig) + for vni in vnis: + vniconfig = self.getFRRconfig(f' vni {vni}') + self.assertIn(f'vni {vni}', vniconfig) + self.assertIn(f' advertise-default-gw', vniconfig) + self.assertIn(f' advertise-svi-ip', vniconfig) + self.assertIn(f' ead-es-frag evi-limit {evi_limit}', frrconfig) + for route_target in route_targets: + self.assertIn(f' ead-es-route-target export {route_target}', frrconfig) + + + def test_bgp_09_distance_and_flowspec(self): + distance_external = '25' + distance_internal = '30' + distance_local = '35' + distance_v4_prefix = '169.254.0.0/32' + distance_v6_prefix = '2001::/128' + distance_prefix_value = '110' + distance_families = ['ipv4-unicast', 'ipv6-unicast','ipv4-multicast', 'ipv6-multicast'] + verify_families = ['ipv4 unicast', 'ipv6 unicast','ipv4 multicast', 'ipv6 multicast'] + flowspec_families = ['address-family ipv4 flowspec', 'address-family ipv6 flowspec'] + flowspec_int = 'lo' + + # Per family distance support + for family in distance_families: + self.cli_set(base_path + ['address-family', family, 'distance', 'external', distance_external]) + self.cli_set(base_path + ['address-family', family, 'distance', 'internal', distance_internal]) + self.cli_set(base_path + ['address-family', family, 'distance', 'local', distance_local]) + if 'ipv4' in family: + self.cli_set(base_path + ['address-family', family, 'distance', + 'prefix', distance_v4_prefix, 'distance', distance_prefix_value]) + if 'ipv6' in family: + self.cli_set(base_path + ['address-family', family, 'distance', + 'prefix', distance_v6_prefix, 'distance', distance_prefix_value]) + + # IPv4 flowspec interface check + self.cli_set(base_path + ['address-family', 'ipv4-flowspec', 'local-install', 'interface', flowspec_int]) + + # IPv6 flowspec interface check + self.cli_set(base_path + ['address-family', 'ipv6-flowspec', 'local-install', 'interface', flowspec_int]) + + # Commit changes + self.cli_commit() + + # Verify FRR distances configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + for family in verify_families: + self.assertIn(f'address-family {family}', frrconfig) + self.assertIn(f'distance bgp {distance_external} {distance_internal} {distance_local}', frrconfig) + if 'ipv4' in family: + self.assertIn(f'distance {distance_prefix_value} {distance_v4_prefix}', frrconfig) + if 'ipv6' in family: + self.assertIn(f'distance {distance_prefix_value} {distance_v6_prefix}', frrconfig) + + # Verify FRR flowspec configuration + for family in flowspec_families: + self.assertIn(f'{family}', frrconfig) + self.assertIn(f'local-install {flowspec_int}', frrconfig) + + def test_bgp_10_vrf_simple(self): + router_id = '127.0.0.3' + vrfs = ['red', 'green', 'blue'] + + # It is safe to assume that when the basic VRF test works, all + # other BGP related features work, as we entirely inherit the CLI + # templates and Jinja2 FRR template. + table = '1000' + + # testing only one AFI is sufficient as it's generic code + for vrf in vrfs: + vrf_base = ['vrf', 'name', vrf] + self.cli_set(vrf_base + ['table', table]) + self.cli_set(vrf_base + ['protocols', 'bgp', 'system-as', ASN]) + self.cli_set(vrf_base + ['protocols', 'bgp', 'parameters', 'router-id', router_id]) + table = str(int(table) + 1000) + + # import VRF routes do main RIB + self.cli_set(base_path + ['address-family', 'ipv6-unicast', 'import', 'vrf', vrf]) + + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' address-family ipv6 unicast', frrconfig) + + for vrf in vrfs: + self.assertIn(f' import vrf {vrf}', frrconfig) + + # Verify FRR bgpd configuration + frr_vrf_config = self.getFRRconfig(f'router bgp {ASN} vrf {vrf}') + self.assertIn(f'router bgp {ASN} vrf {vrf}', frr_vrf_config) + self.assertIn(f' bgp router-id {router_id}', frr_vrf_config) + + def test_bgp_11_confederation(self): + router_id = '127.10.10.2' + confed_id = str(int(ASN) + 1) + confed_asns = '10 20 30 40' + + self.cli_set(base_path + ['parameters', 'router-id', router_id]) + self.cli_set(base_path + ['parameters', 'confederation', 'identifier', confed_id]) + for asn in confed_asns.split(): + self.cli_set(base_path + ['parameters', 'confederation', 'peers', asn]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' bgp router-id {router_id}', frrconfig) + self.assertIn(f' bgp confederation identifier {confed_id}', frrconfig) + self.assertIn(f' bgp confederation peers {confed_asns}', frrconfig) + + def test_bgp_12_v6_link_local(self): + remote_asn = str(int(ASN) + 10) + interface = 'eth0' + + self.cli_set(base_path + ['neighbor', interface, 'address-family', 'ipv6-unicast']) + self.cli_set(base_path + ['neighbor', interface, 'interface', 'v6only', 'remote-as', remote_asn]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' neighbor {interface} interface v6only remote-as {remote_asn}', frrconfig) + self.assertIn(f' address-family ipv6 unicast', frrconfig) + self.assertIn(f' neighbor {interface} activate', frrconfig) + self.assertIn(f' exit-address-family', frrconfig) + + def test_bgp_13_vpn(self): + remote_asn = str(int(ASN) + 150) + neighbor = '192.0.2.55' + vrf_name = 'red' + label = 'auto' + rd = f'{neighbor}:{ASN}' + rt_export = f'{neighbor}:1002 1.2.3.4:567' + rt_import = f'{neighbor}:1003 500:100' + + # testing only one AFI is sufficient as it's generic code + for afi in ['ipv4-unicast', 'ipv6-unicast']: + self.cli_set(base_path + ['address-family', afi, 'export', 'vpn']) + self.cli_set(base_path + ['address-family', afi, 'import', 'vpn']) + self.cli_set(base_path + ['address-family', afi, 'label', 'vpn', 'export', label]) + self.cli_set(base_path + ['address-family', afi, 'label', 'vpn', 'allocation-mode', 'per-nexthop']) + self.cli_set(base_path + ['address-family', afi, 'rd', 'vpn', 'export', rd]) + self.cli_set(base_path + ['address-family', afi, 'route-map', 'vpn', 'export', route_map_out]) + self.cli_set(base_path + ['address-family', afi, 'route-map', 'vpn', 'import', route_map_in]) + self.cli_set(base_path + ['address-family', afi, 'route-target', 'vpn', 'export', rt_export]) + self.cli_set(base_path + ['address-family', afi, 'route-target', 'vpn', 'import', rt_import]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + + for afi in ['ipv4', 'ipv6']: + afi_config = self.getFRRconfig(f' address-family {afi} unicast', endsection='exit-address-family', daemon='bgpd') + self.assertIn(f'address-family {afi} unicast', afi_config) + self.assertIn(f' export vpn', afi_config) + self.assertIn(f' import vpn', afi_config) + self.assertIn(f' label vpn export {label}', afi_config) + self.assertIn(f' label vpn export allocation-mode per-nexthop', afi_config) + self.assertIn(f' rd vpn export {rd}', afi_config) + self.assertIn(f' route-map vpn export {route_map_out}', afi_config) + self.assertIn(f' route-map vpn import {route_map_in}', afi_config) + self.assertIn(f' rt vpn export {rt_export}', afi_config) + self.assertIn(f' rt vpn import {rt_import}', afi_config) + self.assertIn(f' exit-address-family', afi_config) + + def test_bgp_14_remote_as_peer_group_override(self): + # Peer-group member cannot override remote-as of peer-group + remote_asn = str(int(ASN) + 150) + neighbor = '192.0.2.1' + peer_group = 'bar' + interface = 'eth0' + + self.cli_set(base_path + ['neighbor', neighbor, 'remote-as', remote_asn]) + self.cli_set(base_path + ['neighbor', neighbor, 'peer-group', peer_group]) + self.cli_set(base_path + ['peer-group', peer_group, 'remote-as', remote_asn]) + + # Peer-group member cannot override remote-as of peer-group + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['neighbor', neighbor, 'remote-as']) + + # re-test with interface based peer-group + self.cli_set(base_path + ['neighbor', interface, 'interface', 'peer-group', peer_group]) + self.cli_set(base_path + ['neighbor', interface, 'interface', 'remote-as', 'external']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['neighbor', interface, 'interface', 'remote-as']) + + # re-test with interface based v6only peer-group + self.cli_set(base_path + ['neighbor', interface, 'interface', 'v6only', 'peer-group', peer_group]) + self.cli_set(base_path + ['neighbor', interface, 'interface', 'v6only', 'remote-as', 'external']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['neighbor', interface, 'interface', 'v6only', 'remote-as']) + + self.cli_commit() + + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' neighbor {neighbor} peer-group {peer_group}', frrconfig) + self.assertIn(f' neighbor {peer_group} peer-group', frrconfig) + self.assertIn(f' neighbor {peer_group} remote-as {remote_asn}', frrconfig) + + def test_bgp_15_local_as_ebgp(self): + # https://vyos.dev/T4560 + # local-as allowed only for ebgp peers + + neighbor = '192.0.2.99' + remote_asn = '500' + local_asn = '400' + + self.cli_set(base_path + ['neighbor', neighbor, 'remote-as', ASN]) + self.cli_set(base_path + ['neighbor', neighbor, 'local-as', local_asn]) + + # check validate() - local-as allowed only for ebgp peers + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['neighbor', neighbor, 'remote-as', remote_asn]) + + self.cli_commit() + + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' neighbor {neighbor} remote-as {remote_asn}', frrconfig) + self.assertIn(f' neighbor {neighbor} local-as {local_asn}', frrconfig) + + def test_bgp_16_import_rd_rt_compatibility(self): + # Verify if import vrf and rd vpn export + # exist in the same address family + self.create_bgp_instances_for_import_test() + self.cli_set( + base_path + ['address-family', import_afi, 'import', 'vrf', + import_vrf]) + self.cli_set( + base_path + ['address-family', import_afi, 'rd', 'vpn', 'export', + import_rd]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_17_import_rd_rt_compatibility(self): + # Verify if vrf that is in import vrf list contains rd vpn export + self.create_bgp_instances_for_import_test() + self.cli_set( + base_path + ['address-family', import_afi, 'import', 'vrf', + import_vrf]) + self.cli_commit() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}') + + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f'address-family ipv4 unicast', frrconfig) + self.assertIn(f' import vrf {import_vrf}', frrconfig) + self.assertIn(f'router bgp {ASN} vrf {import_vrf}', frrconfig_vrf) + + self.cli_set( + import_vrf_base + [import_vrf] + base_path + ['address-family', + import_afi, 'rd', + 'vpn', 'export', + import_rd]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_18_deleting_import_vrf(self): + # Verify deleting vrf that is in import vrf list + self.create_bgp_instances_for_import_test() + self.cli_set( + base_path + ['address-family', import_afi, 'import', 'vrf', + import_vrf]) + self.cli_commit() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f'address-family ipv4 unicast', frrconfig) + self.assertIn(f' import vrf {import_vrf}', frrconfig) + self.assertIn(f'router bgp {ASN} vrf {import_vrf}', frrconfig_vrf) + self.cli_delete(import_vrf_base + [import_vrf]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_19_deleting_default_vrf(self): + # Verify deleting existent vrf default if other vrfs were created + self.create_bgp_instances_for_import_test() + self.cli_commit() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f'router bgp {ASN} vrf {import_vrf}', frrconfig_vrf) + self.cli_delete(base_path) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_20_import_rd_rt_compatibility(self): + # Verify if vrf that has rd vpn export is in import vrf of other vrfs + self.create_bgp_instances_for_import_test() + self.cli_set( + import_vrf_base + [import_vrf] + base_path + ['address-family', + import_afi, 'rd', + 'vpn', 'export', + import_rd]) + self.cli_commit() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f'router bgp {ASN} vrf {import_vrf}', frrconfig_vrf) + self.assertIn(f'address-family ipv4 unicast', frrconfig_vrf) + self.assertIn(f' rd vpn export {import_rd}', frrconfig_vrf) + + self.cli_set( + base_path + ['address-family', import_afi, 'import', 'vrf', + import_vrf]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_21_import_unspecified_vrf(self): + # Verify if vrf that is in import is unspecified + self.create_bgp_instances_for_import_test() + self.cli_set( + base_path + ['address-family', import_afi, 'import', 'vrf', + 'test']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_22_interface_mpls_forwarding(self): + interfaces = Section.interfaces('ethernet', vlan=False) + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'mpls', 'forwarding']) + + self.cli_commit() + + for interface in interfaces: + frrconfig = self.getFRRconfig(f'interface {interface}') + self.assertIn(f'interface {interface}', frrconfig) + self.assertIn(f' mpls bgp forwarding', frrconfig) + + def test_bgp_23_vrf_interface_mpls_forwarding(self): + self.create_bgp_instances_for_import_test() + interfaces = Section.interfaces('ethernet', vlan=False) + for interface in interfaces: + self.cli_set(['interfaces', 'ethernet', interface, 'vrf', import_vrf]) + self.cli_set(import_vrf_base + [import_vrf] + base_path + ['interface', interface, 'mpls', 'forwarding']) + + self.cli_commit() + + for interface in interfaces: + frrconfig = self.getFRRconfig(f'interface {interface}') + self.assertIn(f'interface {interface}', frrconfig) + self.assertIn(f' mpls bgp forwarding', frrconfig) + self.cli_delete(['interfaces', 'ethernet', interface, 'vrf']) + + def test_bgp_24_srv6_sid(self): + locator_name = 'VyOS_foo' + sid = 'auto' + nexthop_ipv4 = '192.0.0.1' + nexthop_ipv6 = '2001:db8:100:200::2' + + self.cli_set(base_path + ['srv6', 'locator', locator_name]) + self.cli_set(base_path + ['sid', 'vpn', 'per-vrf', 'export', sid]) + self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'sid', 'vpn', 'export', sid]) + # verify() - SID per VRF and SID per address-family are mutually exclusive! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['address-family', 'ipv4-unicast', 'sid']) + self.cli_commit() + + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' segment-routing srv6', frrconfig) + self.assertIn(f' locator {locator_name}', frrconfig) + self.assertIn(f' sid vpn per-vrf export {sid}', frrconfig) + + # Now test AFI SID + self.cli_delete(base_path + ['sid']) + self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'sid', 'vpn', 'export', sid]) + self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'nexthop', 'vpn', 'export', nexthop_ipv4]) + self.cli_set(base_path + ['address-family', 'ipv6-unicast', 'sid', 'vpn', 'export', sid]) + self.cli_set(base_path + ['address-family', 'ipv6-unicast', 'nexthop', 'vpn', 'export', nexthop_ipv6]) + + self.cli_commit() + + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' segment-routing srv6', frrconfig) + self.assertIn(f' locator {locator_name}', frrconfig) + + afiv4_config = self.getFRRconfig(' address-family ipv4 unicast') + self.assertIn(f' sid vpn export {sid}', afiv4_config) + self.assertIn(f' nexthop vpn export {nexthop_ipv4}', afiv4_config) + afiv6_config = self.getFRRconfig(' address-family ipv6 unicast') + self.assertIn(f' sid vpn export {sid}', afiv6_config) + self.assertIn(f' nexthop vpn export {nexthop_ipv6}', afiv4_config) + + def test_bgp_25_ipv4_labeled_unicast_peer_group(self): + pg_ipv4 = 'foo4' + ipv4_max_prefix = '20' + ipv4_prefix = '192.0.2.0/24' + + self.cli_set(base_path + ['listen', 'range', ipv4_prefix, 'peer-group', pg_ipv4]) + self.cli_set(base_path + ['parameters', 'labeled-unicast', 'ipv4-explicit-null']) + self.cli_set(base_path + ['peer-group', pg_ipv4, 'address-family', 'ipv4-labeled-unicast', 'maximum-prefix', ipv4_max_prefix]) + self.cli_set(base_path + ['peer-group', pg_ipv4, 'remote-as', 'external']) + + self.cli_commit() + + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' neighbor {pg_ipv4} peer-group', frrconfig) + self.assertIn(f' neighbor {pg_ipv4} remote-as external', frrconfig) + self.assertIn(f' bgp listen range {ipv4_prefix} peer-group {pg_ipv4}', frrconfig) + self.assertIn(f' bgp labeled-unicast ipv4-explicit-null', frrconfig) + + afiv4_config = self.getFRRconfig(' address-family ipv4 labeled-unicast') + self.assertIn(f' neighbor {pg_ipv4} activate', afiv4_config) + self.assertIn(f' neighbor {pg_ipv4} maximum-prefix {ipv4_max_prefix}', afiv4_config) + + def test_bgp_26_ipv6_labeled_unicast_peer_group(self): + pg_ipv6 = 'foo6' + ipv6_max_prefix = '200' + ipv6_prefix = '2001:db8:1000::/64' + + self.cli_set(base_path + ['listen', 'range', ipv6_prefix, 'peer-group', pg_ipv6]) + self.cli_set(base_path + ['parameters', 'labeled-unicast', 'ipv6-explicit-null']) + + self.cli_set(base_path + ['peer-group', pg_ipv6, 'address-family', 'ipv6-labeled-unicast', 'maximum-prefix', ipv6_max_prefix]) + self.cli_set(base_path + ['peer-group', pg_ipv6, 'remote-as', 'external']) + + self.cli_commit() + + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' neighbor {pg_ipv6} peer-group', frrconfig) + self.assertIn(f' neighbor {pg_ipv6} remote-as external', frrconfig) + self.assertIn(f' bgp listen range {ipv6_prefix} peer-group {pg_ipv6}', frrconfig) + self.assertIn(f' bgp labeled-unicast ipv6-explicit-null', frrconfig) + + afiv6_config = self.getFRRconfig(' address-family ipv6 labeled-unicast') + self.assertIn(f' neighbor {pg_ipv6} activate', afiv6_config) + self.assertIn(f' neighbor {pg_ipv6} maximum-prefix {ipv6_max_prefix}', afiv6_config) + + def test_bgp_27_route_reflector_client(self): + self.cli_set(base_path + ['peer-group', 'peer1', 'address-family', 'l2vpn-evpn', 'route-reflector-client']) + with self.assertRaises(ConfigSessionError) as e: + self.cli_commit() + + self.cli_set(base_path + ['peer-group', 'peer1', 'remote-as', 'internal']) + self.cli_commit() + + conf = self.getFRRconfig(' address-family l2vpn evpn') + + self.assertIn('neighbor peer1 route-reflector-client', conf) + + def test_bgp_28_peer_group_member_all_internal_or_external(self): + def _common_config_check(conf, include_ras=True): + if include_ras: + self.assertIn(f'neighbor {int_neighbors[0]} remote-as {ASN}', conf) + self.assertIn(f'neighbor {int_neighbors[1]} remote-as {ASN}', conf) + self.assertIn(f'neighbor {ext_neighbors[0]} remote-as {int(ASN) + 1}',conf) + + self.assertIn(f'neighbor {int_neighbors[0]} peer-group {int_pg_name}', conf) + self.assertIn(f'neighbor {int_neighbors[1]} peer-group {int_pg_name}', conf) + self.assertIn(f'neighbor {ext_neighbors[0]} peer-group {ext_pg_name}', conf) + + int_neighbors = ['192.0.2.2', '192.0.2.3'] + ext_neighbors = ['192.122.2.2', '192.122.2.3'] + int_pg_name, ext_pg_name = 'SMOKETESTINT', 'SMOKETESTEXT' + + self.cli_set(base_path + ['neighbor', int_neighbors[0], 'peer-group', int_pg_name]) + self.cli_set(base_path + ['neighbor', int_neighbors[0], 'remote-as', ASN]) + self.cli_set(base_path + ['peer-group', int_pg_name, 'address-family', 'ipv4-unicast']) + self.cli_set(base_path + ['neighbor', ext_neighbors[0], 'peer-group', ext_pg_name]) + self.cli_set(base_path + ['neighbor', ext_neighbors[0], 'remote-as', f'{int(ASN) + 1}']) + self.cli_set(base_path + ['peer-group', ext_pg_name, 'address-family', 'ipv4-unicast']) + self.cli_commit() + + # test add external remote-as to internal group + self.cli_set(base_path + ['neighbor', int_neighbors[1], 'peer-group', int_pg_name]) + self.cli_set(base_path + ['neighbor', int_neighbors[1], 'remote-as', f'{int(ASN) + 1}']) + + with self.assertRaises(ConfigSessionError) as e: + self.cli_commit() + # self.assertIn('\nPeer-group members must be all internal or all external\n', str(e.exception)) + + # test add internal remote-as to internal group + self.cli_set(base_path + ['neighbor', int_neighbors[1], 'remote-as', ASN]) + self.cli_commit() + + conf = self.getFRRconfig(f'router bgp {ASN}') + _common_config_check(conf) + + # test add internal remote-as to external group + self.cli_set(base_path + ['neighbor', ext_neighbors[1], 'peer-group', ext_pg_name]) + self.cli_set(base_path + ['neighbor', ext_neighbors[1], 'remote-as', ASN]) + + with self.assertRaises(ConfigSessionError) as e: + self.cli_commit() + # self.assertIn('\nPeer-group members must be all internal or all external\n', str(e.exception)) + + # test add external remote-as to external group + self.cli_set(base_path + ['neighbor', ext_neighbors[1], 'remote-as', f'{int(ASN) + 2}']) + self.cli_commit() + + conf = self.getFRRconfig(f'router bgp {ASN}') + _common_config_check(conf) + self.assertIn(f'neighbor {ext_neighbors[1]} remote-as {int(ASN) + 2}', conf) + self.assertIn(f'neighbor {ext_neighbors[1]} peer-group {ext_pg_name}', conf) + + # test named remote-as + self.cli_set(base_path + ['neighbor', int_neighbors[0], 'remote-as', 'internal']) + self.cli_set(base_path + ['neighbor', int_neighbors[1], 'remote-as', 'internal']) + self.cli_set(base_path + ['neighbor', ext_neighbors[0], 'remote-as', 'external']) + self.cli_set(base_path + ['neighbor', ext_neighbors[1], 'remote-as', 'external']) + self.cli_commit() + + conf = self.getFRRconfig(f'router bgp {ASN}') + _common_config_check(conf, include_ras=False) + + self.assertIn(f'neighbor {int_neighbors[0]} remote-as internal', conf) + self.assertIn(f'neighbor {int_neighbors[1]} remote-as internal', conf) + self.assertIn(f'neighbor {ext_neighbors[0]} remote-as external', conf) + self.assertIn(f'neighbor {ext_neighbors[1]} remote-as external', conf) + self.assertIn(f'neighbor {ext_neighbors[1]} peer-group {ext_pg_name}', conf) + + def test_bgp_29_peer_group_remote_as_equal_local_as(self): + self.cli_set(base_path + ['system-as', ASN]) + self.cli_set(base_path + ['peer-group', 'OVERLAY', 'local-as', f'{int(ASN) + 1}']) + self.cli_set(base_path + ['peer-group', 'OVERLAY', 'remote-as', f'{int(ASN) + 1}']) + self.cli_set(base_path + ['peer-group', 'OVERLAY', 'address-family', 'l2vpn-evpn']) + + self.cli_set(base_path + ['peer-group', 'UNDERLAY', 'address-family', 'ipv4-unicast']) + + self.cli_set(base_path + ['neighbor', '10.177.70.62', 'peer-group', 'UNDERLAY']) + self.cli_set(base_path + ['neighbor', '10.177.70.62', 'remote-as', 'external']) + + self.cli_set(base_path + ['neighbor', '10.177.75.1', 'peer-group', 'OVERLAY']) + self.cli_set(base_path + ['neighbor', '10.177.75.2', 'peer-group', 'OVERLAY']) + + self.cli_commit() + + conf = self.getFRRconfig(f'router bgp {ASN}') + + self.assertIn(f'neighbor OVERLAY remote-as {int(ASN) + 1}', conf) + self.assertIn(f'neighbor OVERLAY local-as {int(ASN) + 1}', conf) + + def test_bgp_99_bmp(self): + target_name = 'instance-bmp' + target_address = '127.0.0.1' + target_port = '5000' + min_retry = '1024' + max_retry = '2048' + monitor_ipv4 = 'pre-policy' + monitor_ipv6 = 'pre-policy' + mirror_buffer = '32000000' + bmp_path = base_path + ['bmp'] + target_path = bmp_path + ['target', target_name] + + # by default the 'bmp' module not loaded for the bgpd expect Error + self.cli_set(bmp_path) + if not process_named_running('bgpd', 'bmp'): + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # add required 'bmp' module to bgpd and restart bgpd + self.cli_delete(bmp_path) + self.cli_set(['system', 'frr', 'bmp']) + self.cli_commit() + + # restart bgpd to apply "-M bmp" and update PID + cmd(f'sudo kill -9 {self.daemon_pid}') + # let the bgpd process recover + sleep(10) + # update daemon PID - this was a planned daemon restart + self.daemon_pid = process_named_running(PROCESS_NAME) + + # set bmp config but not set address + self.cli_set(target_path + ['port', target_port]) + # address is not set, expect Error + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # config other bmp options + self.cli_set(target_path + ['address', target_address]) + self.cli_set(bmp_path + ['mirror-buffer-limit', mirror_buffer]) + self.cli_set(target_path + ['port', target_port]) + self.cli_set(target_path + ['min-retry', min_retry]) + self.cli_set(target_path + ['max-retry', max_retry]) + self.cli_set(target_path + ['mirror']) + self.cli_set(target_path + ['monitor', 'ipv4-unicast', monitor_ipv4]) + self.cli_set(target_path + ['monitor', 'ipv6-unicast', monitor_ipv6]) + self.cli_commit() + + # Verify bgpd bmp configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'bmp mirror buffer-limit {mirror_buffer}', frrconfig) + self.assertIn(f'bmp targets {target_name}', frrconfig) + self.assertIn(f'bmp mirror', frrconfig) + self.assertIn(f'bmp monitor ipv4 unicast {monitor_ipv4}', frrconfig) + self.assertIn(f'bmp monitor ipv6 unicast {monitor_ipv6}', frrconfig) + self.assertIn(f'bmp connect {target_address} port {target_port} min-retry {min_retry} max-retry {max_retry}', frrconfig) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_igmp-proxy.py b/smoketest/scripts/cli/test_protocols_igmp-proxy.py new file mode 100644 index 0000000..df10442 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_igmp-proxy.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.file import read_file +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'igmpproxy' +IGMP_PROXY_CONF = '/etc/igmpproxy.conf' +base_path = ['protocols', 'igmp-proxy'] +upstream_if = 'eth1' +downstream_if = 'eth2' + +class TestProtocolsIGMPProxy(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + # call base-classes classmethod + super(TestProtocolsIGMPProxy, cls).setUpClass() + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + cls.cli_set(cls, ['interfaces', 'ethernet', upstream_if, 'address', '172.16.1.1/24']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'ethernet', upstream_if, 'address']) + + # call base-classes classmethod + super(TestProtocolsIGMPProxy, cls).tearDownClass() + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(base_path) + self.cli_commit() + + # Check for no longer running process + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_igmpproxy(self): + threshold = '20' + altnet = '192.0.2.0/24' + whitelist = '10.0.0.0/8' + + self.cli_set(base_path + ['disable-quickleave']) + self.cli_set(base_path + ['interface', upstream_if, 'threshold', threshold]) + self.cli_set(base_path + ['interface', upstream_if, 'alt-subnet', altnet]) + self.cli_set(base_path + ['interface', upstream_if, 'whitelist', whitelist]) + + # Must define an upstream and at least 1 downstream interface! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['interface', upstream_if, 'role', 'upstream']) + + # Interface does not exist + self.cli_set(base_path + ['interface', 'eth20', 'role', 'upstream']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', 'eth20']) + + # Only 1 upstream interface allowed + self.cli_set(base_path + ['interface', downstream_if, 'role', 'upstream']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['interface', downstream_if, 'role', 'downstream']) + + # commit changes + self.cli_commit() + + # Check generated configuration + config = read_file(IGMP_PROXY_CONF) + self.assertIn(f'phyint {upstream_if} upstream ratelimit 0 threshold {threshold}', config) + self.assertIn(f'altnet {altnet}', config) + self.assertIn(f'whitelist {whitelist}', config) + self.assertIn(f'phyint {downstream_if} downstream ratelimit 0 threshold 1', config) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py new file mode 100644 index 0000000..769f3dd --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -0,0 +1,416 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'isisd' +base_path = ['protocols', 'isis'] + +domain = 'VyOS' +net = '49.0001.1921.6800.1002.00' + +class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + cls._interfaces = Section.interfaces('ethernet') + # call base-classes classmethod + super(TestProtocolsISIS, cls).setUpClass() + # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same + cls.daemon_pid = process_named_running(PROCESS_NAME) + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + cls.cli_delete(cls, ['vrf']) + + def tearDown(self): + # cleanup any possible VRF mess + self.cli_delete(['vrf']) + # always destrox the entire isisd configuration to make the processes + # life as hard as possible + self.cli_delete(base_path) + self.cli_commit() + + # check process health and continuity + self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + + def isis_base_config(self): + self.cli_set(base_path + ['net', net]) + for interface in self._interfaces: + self.cli_set(base_path + ['interface', interface]) + + def test_isis_01_redistribute(self): + prefix_list = 'EXPORT-ISIS' + route_map = 'EXPORT-ISIS' + rule = '10' + metric_style = 'transition' + + self.cli_set(['policy', 'prefix-list', prefix_list, 'rule', rule, 'action', 'permit']) + self.cli_set(['policy', 'prefix-list', prefix_list, 'rule', rule, 'prefix', '203.0.113.0/24']) + self.cli_set(['policy', 'route-map', route_map, 'rule', rule, 'action', 'permit']) + self.cli_set(['policy', 'route-map', route_map, 'rule', rule, 'match', 'ip', 'address', 'prefix-list', prefix_list]) + + self.cli_set(base_path) + + # verify() - net id and interface are mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.isis_base_config() + + self.cli_set(base_path + ['redistribute', 'ipv4', 'connected']) + # verify() - Redistribute level-1 or level-2 should be specified + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['redistribute', 'ipv4', 'connected', 'level-2', 'route-map', route_map]) + self.cli_set(base_path + ['metric-style', metric_style]) + self.cli_set(base_path + ['log-adjacency-changes']) + + # Commit all changes + self.cli_commit() + + # Verify all changes + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' metric-style {metric_style}', tmp) + self.assertIn(f' log-adjacency-changes', tmp) + self.assertIn(f' redistribute ipv4 connected level-2 route-map {route_map}', tmp) + + for interface in self._interfaces: + tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') + self.assertIn(f' ip router isis {domain}', tmp) + self.assertIn(f' ipv6 router isis {domain}', tmp) + + self.cli_delete(['policy', 'route-map', route_map]) + self.cli_delete(['policy', 'prefix-list', prefix_list]) + + def test_isis_02_vrfs(self): + vrfs = ['red', 'green', 'blue'] + # It is safe to assume that when the basic VRF test works, all other + # IS-IS related features work, as we entirely inherit the CLI templates + # and Jinja2 FRR template. + table = '1000' + vrf = 'red' + vrf_base = ['vrf', 'name', vrf] + vrf_iface = 'eth1' + self.cli_set(vrf_base + ['table', table]) + self.cli_set(vrf_base + ['protocols', 'isis', 'net', net]) + self.cli_set(vrf_base + ['protocols', 'isis', 'interface', vrf_iface]) + self.cli_set(vrf_base + ['protocols', 'isis', 'advertise-high-metrics']) + self.cli_set(vrf_base + ['protocols', 'isis', 'advertise-passive-only']) + self.cli_set(['interfaces', 'ethernet', vrf_iface, 'vrf', vrf]) + + # Also set a default VRF IS-IS config + self.cli_set(base_path + ['net', net]) + self.cli_set(base_path + ['interface', 'eth0']) + self.cli_commit() + + # Verify FRR isisd configuration + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + self.assertIn(f'router isis {domain}', tmp) + self.assertIn(f' net {net}', tmp) + + tmp = self.getFRRconfig(f'router isis {domain} vrf {vrf}', daemon='isisd') + self.assertIn(f'router isis {domain} vrf {vrf}', tmp) + self.assertIn(f' net {net}', tmp) + self.assertIn(f' advertise-high-metrics', tmp) + self.assertIn(f' advertise-passive-only', tmp) + + self.cli_delete(['vrf', 'name', vrf]) + self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) + + def test_isis_04_default_information(self): + metric = '50' + route_map = 'default-foo-' + + self.isis_base_config() + for afi in ['ipv4', 'ipv6']: + for level in ['level-1', 'level-2']: + self.cli_set(base_path + ['default-information', 'originate', afi, level, 'always']) + self.cli_set(base_path + ['default-information', 'originate', afi, level, 'metric', metric]) + self.cli_set(base_path + ['default-information', 'originate', afi, level, 'route-map', route_map + level + afi]) + + # Commit all changes + self.cli_commit() + + # Verify all changes + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + self.assertIn(f' net {net}', tmp) + + for afi in ['ipv4', 'ipv6']: + for level in ['level-1', 'level-2']: + route_map_name = route_map + level + afi + self.assertIn(f' default-information originate {afi} {level} always route-map {route_map_name} metric {metric}', tmp) + + + def test_isis_05_password(self): + password = 'foo' + + self.isis_base_config() + for interface in self._interfaces: + self.cli_set(base_path + ['interface', interface, 'password', 'plaintext-password', f'{password}-{interface}']) + + self.cli_set(base_path + ['area-password', 'plaintext-password', password]) + self.cli_set(base_path + ['area-password', 'md5', password]) + self.cli_set(base_path + ['domain-password', 'plaintext-password', password]) + self.cli_set(base_path + ['domain-password', 'md5', password]) + + # verify() - can not use both md5 and plaintext-password for area-password + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['area-password', 'md5', password]) + + # verify() - can not use both md5 and plaintext-password for domain-password + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['domain-password', 'md5', password]) + + # Commit all changes + self.cli_commit() + + # Verify all changes + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' domain-password clear {password}', tmp) + self.assertIn(f' area-password clear {password}', tmp) + + for interface in self._interfaces: + tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') + self.assertIn(f' isis password clear {password}-{interface}', tmp) + + def test_isis_06_spf_delay_bfd(self): + network = 'point-to-point' + holddown = '10' + init_delay = '50' + long_delay = '200' + short_delay = '100' + time_to_learn = '75' + bfd_profile = 'isis-bfd' + + self.cli_set(base_path + ['net', net]) + for interface in self._interfaces: + self.cli_set(base_path + ['interface', interface, 'network', network]) + self.cli_set(base_path + ['interface', interface, 'bfd', 'profile', bfd_profile]) + + self.cli_set(base_path + ['spf-delay-ietf', 'holddown', holddown]) + # verify() - All types of spf-delay must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['spf-delay-ietf', 'init-delay', init_delay]) + # verify() - All types of spf-delay must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['spf-delay-ietf', 'long-delay', long_delay]) + # verify() - All types of spf-delay must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['spf-delay-ietf', 'short-delay', short_delay]) + # verify() - All types of spf-delay must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['spf-delay-ietf', 'time-to-learn', time_to_learn]) + + # Commit all changes + self.cli_commit() + + # Verify all changes + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' spf-delay-ietf init-delay {init_delay} short-delay {short_delay} long-delay {long_delay} holddown {holddown} time-to-learn {time_to_learn}', tmp) + + for interface in self._interfaces: + tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') + self.assertIn(f' ip router isis {domain}', tmp) + self.assertIn(f' ipv6 router isis {domain}', tmp) + self.assertIn(f' isis network {network}', tmp) + self.assertIn(f' isis bfd', tmp) + self.assertIn(f' isis bfd profile {bfd_profile}', tmp) + + def test_isis_07_segment_routing_configuration(self): + global_block_low = "300" + global_block_high = "399" + local_block_low = "400" + local_block_high = "499" + interface = 'lo' + maximum_stack_size = '5' + prefix_one = '192.168.0.1/32' + prefix_two = '192.168.0.2/32' + prefix_three = '192.168.0.3/32' + prefix_four = '192.168.0.4/32' + prefix_one_value = '1' + prefix_two_value = '2' + prefix_three_value = '60000' + prefix_four_value = '65000' + + self.cli_set(base_path + ['net', net]) + self.cli_set(base_path + ['interface', interface]) + self.cli_set(base_path + ['segment-routing', 'maximum-label-depth', maximum_stack_size]) + self.cli_set(base_path + ['segment-routing', 'global-block', 'low-label-value', global_block_low]) + self.cli_set(base_path + ['segment-routing', 'global-block', 'high-label-value', global_block_high]) + self.cli_set(base_path + ['segment-routing', 'local-block', 'low-label-value', local_block_low]) + self.cli_set(base_path + ['segment-routing', 'local-block', 'high-label-value', local_block_high]) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_one, 'index', 'value', prefix_one_value]) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_one, 'index', 'explicit-null']) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_two, 'index', 'value', prefix_two_value]) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_two, 'index', 'no-php-flag']) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_three, 'absolute', 'value', prefix_three_value]) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_three, 'absolute', 'explicit-null']) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_four, 'absolute', 'value', prefix_four_value]) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_four, 'absolute', 'no-php-flag']) + + # Commit all changes + self.cli_commit() + + # Verify all changes + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' segment-routing on', tmp) + self.assertIn(f' segment-routing global-block {global_block_low} {global_block_high} local-block {local_block_low} {local_block_high}', tmp) + self.assertIn(f' segment-routing node-msd {maximum_stack_size}', tmp) + self.assertIn(f' segment-routing prefix {prefix_one} index {prefix_one_value} explicit-null', tmp) + self.assertIn(f' segment-routing prefix {prefix_two} index {prefix_two_value} no-php-flag', tmp) + self.assertIn(f' segment-routing prefix {prefix_three} absolute {prefix_three_value} explicit-null', tmp) + self.assertIn(f' segment-routing prefix {prefix_four} absolute {prefix_four_value} no-php-flag', tmp) + + def test_isis_08_ldp_sync(self): + holddown = "500" + interface = 'lo' + + self.cli_set(base_path + ['net', net]) + self.cli_set(base_path + ['interface', interface]) + self.cli_set(base_path + ['ldp-sync', 'holddown', holddown]) + + # Commit main ISIS changes + self.cli_commit() + + # Verify main ISIS changes + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' mpls ldp-sync', tmp) + self.assertIn(f' mpls ldp-sync holddown {holddown}', tmp) + + for interface in self._interfaces: + self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'holddown', holddown]) + + # Commit interface changes for holddown + self.cli_commit() + + for interface in self._interfaces: + # Verify interface changes for holddown + tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') + self.assertIn(f'interface {interface}', tmp) + self.assertIn(f' ip router isis {domain}', tmp) + self.assertIn(f' ipv6 router isis {domain}', tmp) + self.assertIn(f' isis mpls ldp-sync holddown {holddown}', tmp) + + for interface in self._interfaces: + self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'disable']) + + # Commit interface changes for disable + self.cli_commit() + + for interface in self._interfaces: + # Verify interface changes for disable + tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') + self.assertIn(f'interface {interface}', tmp) + self.assertIn(f' ip router isis {domain}', tmp) + self.assertIn(f' ipv6 router isis {domain}', tmp) + self.assertIn(f' no isis mpls ldp-sync', tmp) + + def test_isis_09_lfa(self): + prefix_list = 'lfa-prefix-list-test-1' + prefix_list_address = '192.168.255.255/32' + interface = 'lo' + + self.cli_set(base_path + ['net', net]) + self.cli_set(base_path + ['interface', interface]) + self.cli_set(['policy', 'prefix-list', prefix_list, 'rule', '1', 'action', 'permit']) + self.cli_set(['policy', 'prefix-list', prefix_list, 'rule', '1', 'prefix', prefix_list_address]) + + # Commit main ISIS changes + self.cli_commit() + + # Add remote portion of LFA with prefix list with validation + for level in ['level-1', 'level-2']: + self.cli_set(base_path + ['fast-reroute', 'lfa', 'remote', 'prefix-list', prefix_list, level]) + self.cli_commit() + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' fast-reroute remote-lfa prefix-list {prefix_list} {level}', tmp) + self.cli_delete(base_path + ['fast-reroute']) + self.cli_commit() + + # Add local portion of LFA load-sharing portion with validation + for level in ['level-1', 'level-2']: + self.cli_set(base_path + ['fast-reroute', 'lfa', 'local', 'load-sharing', 'disable', level]) + self.cli_commit() + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' fast-reroute load-sharing disable {level}', tmp) + self.cli_delete(base_path + ['fast-reroute']) + self.cli_commit() + + # Add local portion of LFA priority-limit portion with validation + for priority in ['critical', 'high', 'medium']: + for level in ['level-1', 'level-2']: + self.cli_set(base_path + ['fast-reroute', 'lfa', 'local', 'priority-limit', priority, level]) + self.cli_commit() + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' fast-reroute priority-limit {priority} {level}', tmp) + self.cli_delete(base_path + ['fast-reroute']) + self.cli_commit() + + # Add local portion of LFA tiebreaker portion with validation + index = '100' + for tiebreaker in ['downstream','lowest-backup-metric','node-protecting']: + for level in ['level-1', 'level-2']: + self.cli_set(base_path + ['fast-reroute', 'lfa', 'local', 'tiebreaker', tiebreaker, 'index', index, level]) + self.cli_commit() + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' fast-reroute lfa tiebreaker {tiebreaker} index {index} {level}', tmp) + self.cli_delete(base_path + ['fast-reroute']) + self.cli_commit() + + # Clean up and remove prefix list + self.cli_delete(['policy', 'prefix-list', prefix_list]) + self.cli_commit() + + def test_isis_10_topology(self): + topologies = ['ipv4-multicast', 'ipv4-mgmt', 'ipv6-unicast', 'ipv6-multicast', 'ipv6-mgmt'] + interface = 'lo' + + # Set a basic IS-IS config + self.cli_set(base_path + ['net', net]) + self.cli_set(base_path + ['interface', interface]) + for topology in topologies: + self.cli_set(base_path + ['topology', topology]) + self.cli_commit() + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' topology {topology}', tmp) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_mpls.py b/smoketest/scripts/cli/test_protocols_mpls.py new file mode 100644 index 0000000..0c1599f --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_mpls.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'ldpd' +base_path = ['protocols', 'mpls', 'ldp'] + +peers = { + '192.0.2.10' : { + 'intv_rx' : '500', + 'intv_tx' : '600', + 'multihop' : '', + 'source_addr': '192.0.2.254', + }, + '192.0.2.20' : { + 'echo_mode' : '', + 'intv_echo' : '100', + 'intv_mult' : '100', + 'intv_rx' : '222', + 'intv_tx' : '333', + 'passive' : '', + 'shutdown' : '', + }, + '2001:db8::a' : { + 'source_addr': '2001:db8::1', + }, + '2001:db8::b' : { + 'source_addr': '2001:db8::1', + 'multihop' : '', + }, +} + +profiles = { + 'foo' : { + 'echo_mode' : '', + 'intv_echo' : '100', + 'intv_mult' : '101', + 'intv_rx' : '222', + 'intv_tx' : '333', + 'shutdown' : '', + }, + 'bar' : { + 'intv_mult' : '102', + 'intv_rx' : '444', + 'passive' : '', + }, +} + +class TestProtocolsMPLS(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestProtocolsMPLS, cls).setUpClass() + + # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same + cls.daemon_pid = process_named_running(PROCESS_NAME) + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + # check process health and continuity + self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + + def test_mpls_basic(self): + router_id = '1.2.3.4' + transport_ipv4_addr = '5.6.7.8' + interfaces = Section.interfaces('ethernet') + + self.cli_set(base_path + ['router-id', router_id]) + + # At least one LDP interface must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for interface in interfaces: + self.cli_set(base_path + ['interface', interface]) + + # LDP transport address missing + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['discovery', 'transport-ipv4-address', transport_ipv4_addr]) + + # Commit changes + self.cli_commit() + + # Validate configuration + frrconfig = self.getFRRconfig('mpls ldp', daemon=PROCESS_NAME) + self.assertIn(f'mpls ldp', frrconfig) + self.assertIn(f' router-id {router_id}', frrconfig) + + # Validate AFI IPv4 + afiv4_config = self.getFRRconfig(' address-family ipv4', daemon=PROCESS_NAME) + self.assertIn(f' discovery transport-address {transport_ipv4_addr}', afiv4_config) + for interface in interfaces: + self.assertIn(f' interface {interface}', afiv4_config) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_nhrp.py b/smoketest/scripts/cli/test_protocols_nhrp.py new file mode 100644 index 0000000..43ae4ab --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_nhrp.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.firewall import find_nftables_rule +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +tunnel_path = ['interfaces', 'tunnel'] +nhrp_path = ['protocols', 'nhrp'] +vpn_path = ['vpn', 'ipsec'] + +class TestProtocolsNHRP(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestProtocolsNHRP, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, nhrp_path) + cls.cli_delete(cls, tunnel_path) + + def tearDown(self): + self.cli_delete(nhrp_path) + self.cli_delete(tunnel_path) + self.cli_commit() + + def test_config(self): + tunnel_if = "tun100" + tunnel_source = "192.0.2.1" + tunnel_encapsulation = "gre" + esp_group = "ESP-HUB" + ike_group = "IKE-HUB" + nhrp_secret = "vyos123" + nhrp_profile = "NHRPVPN" + ipsec_secret = "secret" + + # Tunnel + self.cli_set(tunnel_path + [tunnel_if, "address", "172.16.253.134/29"]) + self.cli_set(tunnel_path + [tunnel_if, "encapsulation", tunnel_encapsulation]) + self.cli_set(tunnel_path + [tunnel_if, "source-address", tunnel_source]) + self.cli_set(tunnel_path + [tunnel_if, "enable-multicast"]) + self.cli_set(tunnel_path + [tunnel_if, "parameters", "ip", "key", "1"]) + + # NHRP + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "cisco-authentication", nhrp_secret]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "holding-time", "300"]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "multicast", "dynamic"]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "redirect"]) + self.cli_set(nhrp_path + ["tunnel", tunnel_if, "shortcut"]) + + # IKE/ESP Groups + self.cli_set(vpn_path + ["esp-group", esp_group, "lifetime", "1800"]) + self.cli_set(vpn_path + ["esp-group", esp_group, "mode", "transport"]) + self.cli_set(vpn_path + ["esp-group", esp_group, "pfs", "dh-group2"]) + self.cli_set(vpn_path + ["esp-group", esp_group, "proposal", "1", "encryption", "aes256"]) + self.cli_set(vpn_path + ["esp-group", esp_group, "proposal", "1", "hash", "sha1"]) + self.cli_set(vpn_path + ["esp-group", esp_group, "proposal", "2", "encryption", "3des"]) + self.cli_set(vpn_path + ["esp-group", esp_group, "proposal", "2", "hash", "md5"]) + + self.cli_set(vpn_path + ["ike-group", ike_group, "key-exchange", "ikev1"]) + self.cli_set(vpn_path + ["ike-group", ike_group, "lifetime", "3600"]) + self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "1", "dh-group", "2"]) + self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "1", "encryption", "aes256"]) + self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "1", "hash", "sha1"]) + self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "2", "dh-group", "2"]) + self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "2", "encryption", "aes128"]) + self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "2", "hash", "sha1"]) + + # Profile - Not doing full DMVPN checks here, just want to verify the profile name in the output + self.cli_set(vpn_path + ["interface", "eth0"]) + self.cli_set(vpn_path + ["profile", nhrp_profile, "authentication", "mode", "pre-shared-secret"]) + self.cli_set(vpn_path + ["profile", nhrp_profile, "authentication", "pre-shared-secret", ipsec_secret]) + self.cli_set(vpn_path + ["profile", nhrp_profile, "bind", "tunnel", tunnel_if]) + self.cli_set(vpn_path + ["profile", nhrp_profile, "esp-group", esp_group]) + self.cli_set(vpn_path + ["profile", nhrp_profile, "ike-group", ike_group]) + + self.cli_commit() + + opennhrp_lines = [ + f'interface {tunnel_if} #hub {nhrp_profile}', + f'cisco-authentication {nhrp_secret}', + f'holding-time 300', + f'shortcut', + f'multicast dynamic', + f'redirect' + ] + + tmp_opennhrp_conf = read_file('/run/opennhrp/opennhrp.conf') + + for line in opennhrp_lines: + self.assertIn(line, tmp_opennhrp_conf) + + firewall_matches = [ + f'ip protocol {tunnel_encapsulation}', + f'ip saddr {tunnel_source}', + f'ip daddr 224.0.0.0/4', + f'comment "VYOS_NHRP_{tunnel_if}"' + ] + + self.assertTrue(find_nftables_rule('ip vyos_nhrp_filter', 'VYOS_NHRP_OUTPUT', firewall_matches) is not None) + self.assertTrue(process_named_running('opennhrp')) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_openfabric.py b/smoketest/scripts/cli/test_protocols_openfabric.py new file mode 100644 index 0000000..e37aed4 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_openfabric.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'fabricd' +base_path = ['protocols', 'openfabric'] + +domain = 'VyOS' +net = '49.0001.1111.1111.1111.00' +dummy_if = 'dum1234' +address_families = ['ipv4', 'ipv6'] + +path = base_path + ['domain', domain] + +class TestProtocolsOpenFabric(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + # call base-classes classmethod + super(TestProtocolsOpenFabric, cls).setUpClass() + # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same + cls.daemon_pid = process_named_running(PROCESS_NAME) + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + # check process health and continuity + self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + + def openfabric_base_config(self): + self.cli_set(['interfaces', 'dummy', dummy_if]) + self.cli_set(base_path + ['net', net]) + for family in address_families: + self.cli_set(path + ['interface', dummy_if, 'address-family', family]) + + def test_openfabric_01_router_params(self): + fabric_tier = '5' + lsp_gen_interval = '20' + + self.cli_set(base_path) + + # verify() - net id and domain name are mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.openfabric_base_config() + + self.cli_set(path + ['log-adjacency-changes']) + self.cli_set(path + ['set-overload-bit']) + self.cli_set(path + ['fabric-tier', fabric_tier]) + self.cli_set(path + ['lsp-gen-interval', lsp_gen_interval]) + + # Commit all changes + self.cli_commit() + + # Verify all changes + tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' log-adjacency-changes', tmp) + self.assertIn(f' set-overload-bit', tmp) + self.assertIn(f' fabric-tier {fabric_tier}', tmp) + self.assertIn(f' lsp-gen-interval {lsp_gen_interval}', tmp) + + tmp = self.getFRRconfig(f'interface {dummy_if}', daemon='fabricd') + self.assertIn(f' ip router openfabric {domain}', tmp) + self.assertIn(f' ipv6 router openfabric {domain}', tmp) + + def test_openfabric_02_loopback_interface(self): + interface = 'lo' + hello_interval = '100' + metric = '24478' + + self.openfabric_base_config() + self.cli_set(path + ['interface', interface, 'address-family', 'ipv4']) + + self.cli_set(path + ['interface', interface, 'hello-interval', hello_interval]) + self.cli_set(path + ['interface', interface, 'metric', metric]) + + # Commit all changes + self.cli_commit() + + # Verify FRR openfabric configuration + tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd') + self.assertIn(f'router openfabric {domain}', tmp) + self.assertIn(f' net {net}', tmp) + + # Verify interface configuration + tmp = self.getFRRconfig(f'interface {interface}', daemon='fabricd') + self.assertIn(f' ip router openfabric {domain}', tmp) + # for lo interface 'openfabric passive' is implied + self.assertIn(f' openfabric passive', tmp) + self.assertIn(f' openfabric metric {metric}', tmp) + + def test_openfabric_03_password(self): + password = 'foo' + + self.openfabric_base_config() + + self.cli_set(path + ['interface', dummy_if, 'password', 'plaintext-password', f'{password}-{dummy_if}']) + self.cli_set(path + ['interface', dummy_if, 'password', 'md5', f'{password}-{dummy_if}']) + + # verify() - can not use both md5 and plaintext-password for password for the interface + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['interface', dummy_if, 'password', 'md5']) + + self.cli_set(path + ['domain-password', 'plaintext-password', password]) + self.cli_set(path + ['domain-password', 'md5', password]) + + # verify() - can not use both md5 and plaintext-password for domain-password + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['domain-password', 'md5']) + + # Commit all changes + self.cli_commit() + + # Verify all changes + tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' domain-password clear {password}', tmp) + + tmp = self.getFRRconfig(f'interface {dummy_if}', daemon='fabricd') + self.assertIn(f' openfabric password clear {password}-{dummy_if}', tmp) + + def test_openfabric_multiple_domains(self): + domain_2 = 'VyOS_2' + interface = 'dum5678' + new_path = base_path + ['domain', domain_2] + + self.openfabric_base_config() + + # set same interface for 2 OpenFabric domains + self.cli_set(['interfaces', 'dummy', interface]) + self.cli_set(new_path + ['interface', interface, 'address-family', 'ipv4']) + self.cli_set(path + ['interface', interface, 'address-family', 'ipv4']) + + # verify() - same interface can be used only for one OpenFabric instance + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['interface', interface]) + + # Commit all changes + self.cli_commit() + + # Verify FRR openfabric configuration + tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd') + self.assertIn(f'router openfabric {domain}', tmp) + self.assertIn(f' net {net}', tmp) + + tmp = self.getFRRconfig(f'router openfabric {domain_2}', daemon='fabricd') + self.assertIn(f'router openfabric {domain_2}', tmp) + self.assertIn(f' net {net}', tmp) + + # Verify interface configuration + tmp = self.getFRRconfig(f'interface {dummy_if}', daemon='fabricd') + self.assertIn(f' ip router openfabric {domain}', tmp) + self.assertIn(f' ipv6 router openfabric {domain}', tmp) + + tmp = self.getFRRconfig(f'interface {interface}', daemon='fabricd') + self.assertIn(f' ip router openfabric {domain_2}', tmp) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py new file mode 100644 index 0000000..905eaf2 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -0,0 +1,565 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'ospfd' +base_path = ['protocols', 'ospf'] + +route_map = 'foo-bar-baz10' +dummy_if = 'dum3562' + +class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestProtocolsOSPF, cls).setUpClass() + + # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same + cls.daemon_pid = process_named_running(PROCESS_NAME) + + cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit']) + cls.cli_set(cls, ['interfaces', 'dummy', dummy_if]) + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['policy', 'route-map', route_map]) + cls.cli_delete(cls, ['interfaces', 'dummy', dummy_if]) + super(TestProtocolsOSPF, cls).tearDownClass() + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + # check process health and continuity + self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + + def test_ospf_01_defaults(self): + # commit changes + self.cli_set(base_path) + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + + def test_ospf_02_simple(self): + router_id = '127.0.0.1' + abr_type = 'ibm' + bandwidth = '1000' + metric = '123' + + self.cli_set(base_path + ['auto-cost', 'reference-bandwidth', bandwidth]) + self.cli_set(base_path + ['parameters', 'router-id', router_id]) + self.cli_set(base_path + ['parameters', 'abr-type', abr_type]) + self.cli_set(base_path + ['parameters', 'opaque-lsa']) + self.cli_set(base_path + ['parameters', 'rfc1583-compatibility']) + self.cli_set(base_path + ['log-adjacency-changes', 'detail']) + self.cli_set(base_path + ['default-metric', metric]) + self.cli_set(base_path + ['passive-interface', 'default']) + self.cli_set(base_path + ['area', '10', 'area-type', 'stub']) + self.cli_set(base_path + ['area', '10', 'network', '10.0.0.0/16']) + self.cli_set(base_path + ['area', '10', 'range', '10.0.1.0/24']) + self.cli_set(base_path + ['area', '10', 'range', '10.0.2.0/24', 'not-advertise']) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' compatible rfc1583', frrconfig) + self.assertIn(f' auto-cost reference-bandwidth {bandwidth}', frrconfig) + self.assertIn(f' ospf router-id {router_id}', frrconfig) + self.assertIn(f' ospf abr-type {abr_type}', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + self.assertIn(f' capability opaque', frrconfig) + self.assertIn(f' default-metric {metric}', frrconfig) + self.assertIn(f' passive-interface default', frrconfig) + self.assertIn(f' area 10 stub', frrconfig) + self.assertIn(f' network 10.0.0.0/16 area 10', frrconfig) + self.assertIn(f' area 10 range 10.0.1.0/24', frrconfig) + self.assertNotIn(f' area 10 range 10.0.1.0/24 not-advertise', frrconfig) + self.assertIn(f' area 10 range 10.0.2.0/24 not-advertise', frrconfig) + + + def test_ospf_03_access_list(self): + acl = '100' + seq = '10' + protocols = ['bgp', 'connected', 'isis', 'kernel', 'rip', 'static'] + + self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'action', 'permit']) + self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'source', 'any']) + self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'destination', 'any']) + for ptotocol in protocols: + self.cli_set(base_path + ['access-list', acl, 'export', ptotocol]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + for ptotocol in protocols: + self.assertIn(f' distribute-list {acl} out {ptotocol}', frrconfig) # defaults + self.cli_delete(['policy', 'access-list', acl]) + + + def test_ospf_04_default_originate(self): + seq = '100' + metric = '50' + metric_type = '1' + + self.cli_set(base_path + ['default-information', 'originate', 'metric', metric]) + self.cli_set(base_path + ['default-information', 'originate', 'metric-type', metric_type]) + self.cli_set(base_path + ['default-information', 'originate', 'route-map', route_map]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + self.assertIn(f' default-information originate metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) + + # Now set 'always' + self.cli_set(base_path + ['default-information', 'originate', 'always']) + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) + + + def test_ospf_05_options(self): + global_distance = '128' + intra_area = '100' + inter_area = '110' + external = '120' + on_startup = '30' + on_shutdown = '60' + refresh = '50' + aggregation_timer = '100' + summary_nets = { + '10.0.1.0/24' : {}, + '10.0.2.0/24' : {'tag' : '50'}, + '10.0.3.0/24' : {'no_advertise' : {}}, + } + + self.cli_set(base_path + ['distance', 'global', global_distance]) + self.cli_set(base_path + ['distance', 'ospf', 'external', external]) + self.cli_set(base_path + ['distance', 'ospf', 'intra-area', intra_area]) + + self.cli_set(base_path + ['max-metric', 'router-lsa', 'on-startup', on_startup]) + self.cli_set(base_path + ['max-metric', 'router-lsa', 'on-shutdown', on_shutdown]) + + self.cli_set(base_path + ['mpls-te', 'enable']) + self.cli_set(base_path + ['refresh', 'timers', refresh]) + + self.cli_set(base_path + ['aggregation', 'timer', aggregation_timer]) + + for summary, summary_options in summary_nets.items(): + self.cli_set(base_path + ['summary-address', summary]) + if 'tag' in summary_options: + self.cli_set(base_path + ['summary-address', summary, 'tag', summary_options['tag']]) + if 'no_advertise' in summary_options: + self.cli_set(base_path + ['summary-address', summary, 'no-advertise']) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' mpls-te on', frrconfig) + self.assertIn(f' mpls-te router-address 0.0.0.0', frrconfig) # default + self.assertIn(f' distance {global_distance}', frrconfig) + self.assertIn(f' distance ospf intra-area {intra_area} external {external}', frrconfig) + self.assertIn(f' max-metric router-lsa on-startup {on_startup}', frrconfig) + self.assertIn(f' max-metric router-lsa on-shutdown {on_shutdown}', frrconfig) + self.assertIn(f' refresh timer {refresh}', frrconfig) + + self.assertIn(f' aggregation timer {aggregation_timer}', frrconfig) + for summary, summary_options in summary_nets.items(): + self.assertIn(f' summary-address {summary}', frrconfig) + if 'tag' in summary_options: + tag = summary_options['tag'] + self.assertIn(f' summary-address {summary} tag {tag}', frrconfig) + if 'no_advertise' in summary_options: + self.assertIn(f' summary-address {summary} no-advertise', frrconfig) + + # enable inter-area + self.cli_set(base_path + ['distance', 'ospf', 'inter-area', inter_area]) + self.cli_commit() + + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f' distance ospf intra-area {intra_area} inter-area {inter_area} external {external}', frrconfig) + + + def test_ospf_06_neighbor(self): + priority = '10' + poll_interval = '20' + neighbors = ['1.1.1.1', '2.2.2.2', '3.3.3.3'] + for neighbor in neighbors: + self.cli_set(base_path + ['neighbor', neighbor, 'priority', priority]) + self.cli_set(base_path + ['neighbor', neighbor, 'poll-interval', poll_interval]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + for neighbor in neighbors: + self.assertIn(f' neighbor {neighbor} priority {priority} poll-interval {poll_interval}', frrconfig) # default + + def test_ospf_07_redistribute(self): + metric = '15' + metric_type = '1' + redistribute = ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static'] + + for protocol in redistribute: + self.cli_set(base_path + ['redistribute', protocol, 'metric', metric]) + self.cli_set(base_path + ['redistribute', protocol, 'route-map', route_map]) + self.cli_set(base_path + ['redistribute', protocol, 'metric-type', metric_type]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + for protocol in redistribute: + self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) + + def test_ospf_08_virtual_link(self): + networks = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'] + area = '10' + shortcut = 'enable' + virtual_link = '192.0.2.1' + hello = '6' + retransmit = '5' + transmit = '5' + dead = '40' + + self.cli_set(base_path + ['area', area, 'shortcut', shortcut]) + self.cli_set(base_path + ['area', area, 'virtual-link', virtual_link, 'hello-interval', hello]) + self.cli_set(base_path + ['area', area, 'virtual-link', virtual_link, 'retransmit-interval', retransmit]) + self.cli_set(base_path + ['area', area, 'virtual-link', virtual_link, 'transmit-delay', transmit]) + self.cli_set(base_path + ['area', area, 'virtual-link', virtual_link, 'dead-interval', dead]) + for network in networks: + self.cli_set(base_path + ['area', area, 'network', network]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' area {area} shortcut {shortcut}', frrconfig) + self.assertIn(f' area {area} virtual-link {virtual_link} hello-interval {hello} retransmit-interval {retransmit} transmit-delay {transmit} dead-interval {dead}', frrconfig) + for network in networks: + self.assertIn(f' network {network} area {area}', frrconfig) + + + def test_ospf_09_interface_configuration(self): + interfaces = Section.interfaces('ethernet') + password = 'vyos1234' + bandwidth = '10000' + cost = '150' + network = 'point-to-point' + priority = '200' + bfd_profile = 'vyos-test' + + self.cli_set(base_path + ['passive-interface', 'default']) + for interface in interfaces: + base_interface = base_path + ['interface', interface] + self.cli_set(base_interface + ['authentication', 'plaintext-password', password]) + self.cli_set(base_interface + ['bandwidth', bandwidth]) + self.cli_set(base_interface + ['bfd', 'profile', bfd_profile]) + self.cli_set(base_interface + ['cost', cost]) + self.cli_set(base_interface + ['mtu-ignore']) + self.cli_set(base_interface + ['network', network]) + self.cli_set(base_interface + ['priority', priority]) + self.cli_set(base_interface + ['passive', 'disable']) + + # commit changes + self.cli_commit() + + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' passive-interface default', frrconfig) + + for interface in interfaces: + # Can not use daemon for getFRRconfig() as bandwidth parameter belongs to zebra process + config = self.getFRRconfig(f'interface {interface}') + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ip ospf authentication-key {password}', config) + self.assertIn(f' ip ospf bfd', config) + self.assertIn(f' ip ospf bfd profile {bfd_profile}', config) + self.assertIn(f' ip ospf cost {cost}', config) + self.assertIn(f' ip ospf mtu-ignore', config) + self.assertIn(f' ip ospf network {network}', config) + self.assertIn(f' ip ospf priority {priority}', config) + self.assertIn(f' no ip ospf passive', config) + self.assertIn(f' bandwidth {bandwidth}', config) + + # T5467: Remove interface from OSPF process and VRF + self.cli_delete(base_path + ['interface']) + self.cli_commit() + + for interface in interfaces: + # T5467: It must also be removed from FRR config + frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + self.assertNotIn(f'interface {interface}', frrconfig) + # There should be no OSPF related command at all under the interface + self.assertNotIn(f' ip ospf', frrconfig) + + def test_ospf_11_interface_area(self): + area = '0' + interfaces = Section.interfaces('ethernet') + + self.cli_set(base_path + ['area', area, 'network', '10.0.0.0/8']) + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'area', area]) + + # we can not have bot area network and interface area set + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['area', area, 'network']) + + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + + for interface in interfaces: + config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ip ospf area {area}', config) + + def test_ospf_12_vrfs(self): + # It is safe to assume that when the basic VRF test works, all + # other OSPF related features work, as we entirely inherit the CLI + # templates and Jinja2 FRR template. + table = '1000' + vrf = 'blue' + vrf_base = ['vrf', 'name', vrf] + vrf_iface = 'eth1' + area = '1' + + self.cli_set(vrf_base + ['table', table]) + self.cli_set(vrf_base + ['protocols', 'ospf', 'interface', vrf_iface, 'area', area]) + self.cli_set(['interfaces', 'ethernet', vrf_iface, 'vrf', vrf]) + + # Also set a default VRF OSPF config + self.cli_set(base_path) + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + + frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}', daemon=PROCESS_NAME) + self.assertIn(f'router ospf vrf {vrf}', frrconfig) + self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {vrf_iface}', frrconfig) + self.assertIn(f' ip ospf area {area}', frrconfig) + + # T5467: Remove interface from OSPF process and VRF + self.cli_delete(vrf_base + ['protocols', 'ospf', 'interface']) + self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) + self.cli_commit() + + # T5467: It must also be removed from FRR config + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME) + self.assertNotIn(f'interface {vrf_iface}', frrconfig) + # There should be no OSPF related command at all under the interface + self.assertNotIn(f' ip ospf', frrconfig) + + # cleanup + self.cli_delete(['vrf', 'name', vrf]) + self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) + + def test_ospf_13_export_list(self): + # Verify explort-list works on ospf-area + acl = '100' + seq = '10' + area = '0.0.0.10' + network = '10.0.0.0/8' + + self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'action', 'permit']) + self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'source', 'any']) + self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'destination', 'any']) + self.cli_set(base_path + ['area', area, 'network', network]) + self.cli_set(base_path + ['area', area, 'export-list', acl]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # default + self.assertIn(f' network {network} area {area}', frrconfig) + self.assertIn(f' area {area} export-list {acl}', frrconfig) + + + def test_ospf_14_segment_routing_configuration(self): + global_block_low = "300" + global_block_high = "399" + local_block_low = "400" + local_block_high = "499" + maximum_stack_size = '5' + prefix_one = '192.168.0.1/32' + prefix_two = '192.168.0.2/32' + prefix_one_value = '1' + prefix_two_value = '2' + + self.cli_set(base_path + ['interface', dummy_if]) + self.cli_set(base_path + ['segment-routing', 'maximum-label-depth', maximum_stack_size]) + self.cli_set(base_path + ['segment-routing', 'global-block', 'low-label-value', global_block_low]) + self.cli_set(base_path + ['segment-routing', 'global-block', 'high-label-value', global_block_high]) + self.cli_set(base_path + ['segment-routing', 'local-block', 'low-label-value', local_block_low]) + self.cli_set(base_path + ['segment-routing', 'local-block', 'high-label-value', local_block_high]) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_one, 'index', 'value', prefix_one_value]) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_one, 'index', 'explicit-null']) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_two, 'index', 'value', prefix_two_value]) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_two, 'index', 'no-php-flag']) + + # Commit all changes + self.cli_commit() + + # Verify all changes + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f' segment-routing on', frrconfig) + self.assertIn(f' segment-routing global-block {global_block_low} {global_block_high} local-block {local_block_low} {local_block_high}', frrconfig) + self.assertIn(f' segment-routing node-msd {maximum_stack_size}', frrconfig) + self.assertIn(f' segment-routing prefix {prefix_one} index {prefix_one_value} explicit-null', frrconfig) + self.assertIn(f' segment-routing prefix {prefix_two} index {prefix_two_value} no-php-flag', frrconfig) + + def test_ospf_15_ldp_sync(self): + holddown = "500" + interfaces = Section.interfaces('ethernet') + + self.cli_set(base_path + ['interface', dummy_if]) + self.cli_set(base_path + ['ldp-sync', 'holddown', holddown]) + + # Commit main OSPF changes + self.cli_commit() + + # Verify main OSPF changes + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) + self.assertIn(f' mpls ldp-sync holddown {holddown}', frrconfig) + + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'holddown', holddown]) + + # Commit interface changes for holddown + self.cli_commit() + + for interface in interfaces: + # Verify interface changes for holddown + config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ip ospf dead-interval 40', config) + self.assertIn(f' ip ospf mpls ldp-sync', config) + self.assertIn(f' ip ospf mpls ldp-sync holddown {holddown}', config) + + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'disable']) + + # Commit interface changes for disable + self.cli_commit() + + for interface in interfaces: + # Verify interface changes for disable + config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ip ospf dead-interval 40', config) + self.assertNotIn(f' ip ospf mpls ldp-sync', config) + + def test_ospf_16_graceful_restart(self): + period = '300' + supported_grace_time = '400' + router_ids = ['192.0.2.1', '192.0.2.2'] + + self.cli_set(base_path + ['capability', 'opaque']) + self.cli_set(base_path + ['graceful-restart', 'grace-period', period]) + self.cli_set(base_path + ['graceful-restart', 'helper', 'planned-only']) + self.cli_set(base_path + ['graceful-restart', 'helper', 'no-strict-lsa-checking']) + self.cli_set(base_path + ['graceful-restart', 'helper', 'supported-grace-time', supported_grace_time]) + for router_id in router_ids: + self.cli_set(base_path + ['graceful-restart', 'helper', 'enable', 'router-id', router_id]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' capability opaque', frrconfig) + self.assertIn(f' graceful-restart grace-period {period}', frrconfig) + self.assertIn(f' graceful-restart helper planned-only', frrconfig) + self.assertIn(f' no graceful-restart helper strict-lsa-checking', frrconfig) + self.assertIn(f' graceful-restart helper supported-grace-time {supported_grace_time}', frrconfig) + for router_id in router_ids: + self.assertIn(f' graceful-restart helper enable {router_id}', frrconfig) + + def test_ospf_17_duplicate_area_network(self): + area0 = '0' + area1 = '1' + network = '10.0.0.0/8' + + self.cli_set(base_path + ['area', area0, 'network', network]) + + # we can not have the same network defined on two areas + self.cli_set(base_path + ['area', area1, 'network', network]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['area', area0]) + + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' network {network} area {area1}', frrconfig) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py new file mode 100644 index 0000000..989e155 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_ospfv3.py @@ -0,0 +1,339 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'ospf6d' +base_path = ['protocols', 'ospfv3'] + +route_map = 'foo-bar-baz-0815' + +router_id = '192.0.2.1' +default_area = '0' + +class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestProtocolsOSPFv3, cls).setUpClass() + + # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same + cls.daemon_pid = process_named_running(PROCESS_NAME) + + cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit']) + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['policy', 'route-map', route_map]) + super(TestProtocolsOSPFv3, cls).tearDownClass() + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + # check process health and continuity + self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + + def test_ospfv3_01_basic(self): + seq = '10' + prefix = '2001:db8::/32' + acl_name = 'foo-acl-100' + + self.cli_set(['policy', 'access-list6', acl_name, 'rule', seq, 'action', 'permit']) + self.cli_set(['policy', 'access-list6', acl_name, 'rule', seq, 'source', 'any']) + + self.cli_set(base_path + ['parameters', 'router-id', router_id]) + self.cli_set(base_path + ['area', default_area, 'range', prefix, 'advertise']) + self.cli_set(base_path + ['area', default_area, 'export-list', acl_name]) + self.cli_set(base_path + ['area', default_area, 'import-list', acl_name]) + + interfaces = Section.interfaces('ethernet') + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'area', default_area]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + self.assertIn(f'router ospf6', frrconfig) + self.assertIn(f' area {default_area} range {prefix}', frrconfig) + self.assertIn(f' ospf6 router-id {router_id}', frrconfig) + self.assertIn(f' area {default_area} import-list {acl_name}', frrconfig) + self.assertIn(f' area {default_area} export-list {acl_name}', frrconfig) + + for interface in interfaces: + if_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'ipv6 ospf6 area {default_area}', if_config) + + self.cli_delete(['policy', 'access-list6', acl_name]) + + + def test_ospfv3_02_distance(self): + dist_global = '200' + dist_external = '110' + dist_inter_area = '120' + dist_intra_area = '130' + + self.cli_set(base_path + ['distance', 'global', dist_global]) + self.cli_set(base_path + ['distance', 'ospfv3', 'external', dist_external]) + self.cli_set(base_path + ['distance', 'ospfv3', 'inter-area', dist_inter_area]) + self.cli_set(base_path + ['distance', 'ospfv3', 'intra-area', dist_intra_area]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + self.assertIn(f'router ospf6', frrconfig) + self.assertIn(f' distance {dist_global}', frrconfig) + self.assertIn(f' distance ospf6 intra-area {dist_intra_area} inter-area {dist_inter_area} external {dist_external}', frrconfig) + + + def test_ospfv3_03_redistribute(self): + metric = '15' + metric_type = '1' + route_map = 'foo-bar' + route_map_seq = '10' + redistribute = ['babel', 'bgp', 'connected', 'isis', 'kernel', 'ripng', 'static'] + + self.cli_set(['policy', 'route-map', route_map, 'rule', route_map_seq, 'action', 'permit']) + + for protocol in redistribute: + self.cli_set(base_path + ['redistribute', protocol, 'metric', metric]) + self.cli_set(base_path + ['redistribute', protocol, 'route-map', route_map]) + self.cli_set(base_path + ['redistribute', protocol, 'metric-type', metric_type]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + self.assertIn(f'router ospf6', frrconfig) + for protocol in redistribute: + self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) + + + def test_ospfv3_04_interfaces(self): + bfd_profile = 'vyos-ipv6' + + self.cli_set(base_path + ['parameters', 'router-id', router_id]) + self.cli_set(base_path + ['area', default_area]) + + cost = '100' + priority = '10' + interfaces = Section.interfaces('ethernet') + for interface in interfaces: + if_base = base_path + ['interface', interface] + self.cli_set(if_base + ['bfd', 'profile', bfd_profile]) + self.cli_set(if_base + ['cost', cost]) + self.cli_set(if_base + ['instance-id', '0']) + self.cli_set(if_base + ['mtu-ignore']) + self.cli_set(if_base + ['network', 'point-to-point']) + self.cli_set(if_base + ['passive']) + self.cli_set(if_base + ['priority', priority]) + cost = str(int(cost) + 10) + priority = str(int(priority) + 5) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + self.assertIn(f'router ospf6', frrconfig) + + cost = '100' + priority = '10' + for interface in interfaces: + if_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', if_config) + self.assertIn(f' ipv6 ospf6 bfd', if_config) + self.assertIn(f' ipv6 ospf6 bfd profile {bfd_profile}', if_config) + self.assertIn(f' ipv6 ospf6 cost {cost}', if_config) + self.assertIn(f' ipv6 ospf6 mtu-ignore', if_config) + self.assertIn(f' ipv6 ospf6 network point-to-point', if_config) + self.assertIn(f' ipv6 ospf6 passive', if_config) + self.assertIn(f' ipv6 ospf6 priority {priority}', if_config) + cost = str(int(cost) + 10) + priority = str(int(priority) + 5) + + # Cleanup interfaces + self.cli_delete(base_path + ['interface']) + self.cli_commit() + + for interface in interfaces: + if_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + # There should be no OSPF6 configuration at all after interface removal + self.assertNotIn(f' ipv6 ospf6', if_config) + + + def test_ospfv3_05_area_stub(self): + area_stub = '23' + area_stub_nosum = '26' + + self.cli_set(base_path + ['area', area_stub, 'area-type', 'stub']) + self.cli_set(base_path + ['area', area_stub_nosum, 'area-type', 'stub', 'no-summary']) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + self.assertIn(f'router ospf6', frrconfig) + self.assertIn(f' area {area_stub} stub', frrconfig) + self.assertIn(f' area {area_stub_nosum} stub no-summary', frrconfig) + + + def test_ospfv3_06_area_nssa(self): + area_nssa = '1.1.1.1' + area_nssa_nosum = '2.2.2.2' + area_nssa_default = '3.3.3.3' + + self.cli_set(base_path + ['area', area_nssa, 'area-type', 'nssa']) + self.cli_set(base_path + ['area', area_nssa, 'area-type', 'stub']) + # can only set one area-type per OSPFv3 area + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['area', area_nssa, 'area-type', 'stub']) + + self.cli_set(base_path + ['area', area_nssa_nosum, 'area-type', 'nssa', 'no-summary']) + self.cli_set(base_path + ['area', area_nssa_nosum, 'area-type', 'nssa', 'default-information-originate']) + self.cli_set(base_path + ['area', area_nssa_default, 'area-type', 'nssa', 'default-information-originate']) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + self.assertIn(f'router ospf6', frrconfig) + self.assertIn(f' area {area_nssa} nssa', frrconfig) + self.assertIn(f' area {area_nssa_nosum} nssa default-information-originate no-summary', frrconfig) + self.assertIn(f' area {area_nssa_default} nssa default-information-originate', frrconfig) + + + def test_ospfv3_07_default_originate(self): + seq = '100' + metric = '50' + metric_type = '1' + + self.cli_set(base_path + ['default-information', 'originate', 'metric', metric]) + self.cli_set(base_path + ['default-information', 'originate', 'metric-type', metric_type]) + self.cli_set(base_path + ['default-information', 'originate', 'route-map', route_map]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + self.assertIn(f'router ospf6', frrconfig) + self.assertIn(f' default-information originate metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) + + # Now set 'always' + self.cli_set(base_path + ['default-information', 'originate', 'always']) + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) + + + def test_ospfv3_08_vrfs(self): + # It is safe to assume that when the basic VRF test works, all + # other OSPF related features work, as we entirely inherit the CLI + # templates and Jinja2 FRR template. + table = '1000' + vrf = 'blue' + vrf_base = ['vrf', 'name', vrf] + vrf_iface = 'eth1' + router_id = '1.2.3.4' + router_id_vrf = '1.2.3.5' + + self.cli_set(vrf_base + ['table', table]) + self.cli_set(vrf_base + ['protocols', 'ospfv3', 'interface', vrf_iface, 'bfd']) + self.cli_set(vrf_base + ['protocols', 'ospfv3', 'parameters', 'router-id', router_id_vrf]) + + self.cli_set(['interfaces', 'ethernet', vrf_iface, 'vrf', vrf]) + + # Also set a default VRF OSPF config + self.cli_set(base_path + ['parameters', 'router-id', router_id]) + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + self.assertIn(f'router ospf6', frrconfig) + self.assertIn(f' ospf6 router-id {router_id}', frrconfig) + + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {vrf_iface}', frrconfig) + self.assertIn(f' ipv6 ospf6 bfd', frrconfig) + + frrconfig = self.getFRRconfig(f'router ospf6 vrf {vrf}', daemon=PROCESS_NAME) + self.assertIn(f'router ospf6 vrf {vrf}', frrconfig) + self.assertIn(f' ospf6 router-id {router_id_vrf}', frrconfig) + + # T5467: Remove interface from OSPF process and VRF + self.cli_delete(vrf_base + ['protocols', 'ospfv3', 'interface']) + self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) + self.cli_commit() + + # T5467: It must also be removed from FRR config + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME) + self.assertNotIn(f'interface {vrf_iface}', frrconfig) + # There should be no OSPF related command at all under the interface + self.assertNotIn(f' ipv6 ospf6', frrconfig) + + # cleanup + self.cli_delete(['vrf', 'name', vrf]) + self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) + + + def test_ospfv3_09_graceful_restart(self): + period = '300' + supported_grace_time = '400' + router_ids = ['192.0.2.1', '192.0.2.2'] + + self.cli_set(base_path + ['graceful-restart', 'grace-period', period]) + self.cli_set(base_path + ['graceful-restart', 'helper', 'planned-only']) + self.cli_set(base_path + ['graceful-restart', 'helper', 'lsa-check-disable']) + self.cli_set(base_path + ['graceful-restart', 'helper', 'supported-grace-time', supported_grace_time]) + for router_id in router_ids: + self.cli_set(base_path + ['graceful-restart', 'helper', 'enable', 'router-id', router_id]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + self.assertIn(f'router ospf6', frrconfig) + self.assertIn(f' graceful-restart grace-period {period}', frrconfig) + self.assertIn(f' graceful-restart helper planned-only', frrconfig) + self.assertIn(f' graceful-restart helper lsa-check-disable', frrconfig) + self.assertIn(f' graceful-restart helper supported-grace-time {supported_grace_time}', frrconfig) + for router_id in router_ids: + self.assertIn(f' graceful-restart helper enable {router_id}', frrconfig) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_pim.py b/smoketest/scripts/cli/test_protocols_pim.py new file mode 100644 index 0000000..ccfced1 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_pim.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'pimd' +base_path = ['protocols', 'pim'] + +class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # pimd process must be running + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(base_path) + self.cli_commit() + + # pimd process must be stopped by now + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_pim_basic(self): + rp = '127.0.0.1' + group = '224.0.0.0/4' + hello = '100' + dr_priority = '64' + + self.cli_set(base_path + ['rp', 'address', rp, 'group', group]) + + interfaces = Section.interfaces('ethernet') + for interface in interfaces: + self.cli_set(base_path + ['interface', interface , 'bfd']) + self.cli_set(base_path + ['interface', interface , 'dr-priority', dr_priority]) + self.cli_set(base_path + ['interface', interface , 'hello', hello]) + self.cli_set(base_path + ['interface', interface , 'no-bsm']) + self.cli_set(base_path + ['interface', interface , 'no-unicast-bsm']) + self.cli_set(base_path + ['interface', interface , 'passive']) + + # commit changes + self.cli_commit() + + # Verify FRR pimd configuration + frrconfig = self.getFRRconfig(daemon=PROCESS_NAME) + self.assertIn(f'ip pim rp {rp} {group}', frrconfig) + + for interface in interfaces: + frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', frrconfig) + self.assertIn(f' ip pim', frrconfig) + self.assertIn(f' ip pim bfd', frrconfig) + self.assertIn(f' ip pim drpriority {dr_priority}', frrconfig) + self.assertIn(f' ip pim hello {hello}', frrconfig) + self.assertIn(f' no ip pim bsm', frrconfig) + self.assertIn(f' no ip pim unicast-bsm', frrconfig) + self.assertIn(f' ip pim passive', frrconfig) + + self.cli_commit() + + def test_02_pim_advanced(self): + rp = '127.0.0.2' + group = '224.0.0.0/4' + join_prune_interval = '123' + rp_keep_alive_timer = '190' + keep_alive_timer = '180' + packets = '10' + prefix_list = 'pim-test' + register_suppress_time = '300' + + self.cli_set(base_path + ['rp', 'address', rp, 'group', group]) + self.cli_set(base_path + ['rp', 'keep-alive-timer', rp_keep_alive_timer]) + + self.cli_set(base_path + ['ecmp', 'rebalance']) + self.cli_set(base_path + ['join-prune-interval', join_prune_interval]) + self.cli_set(base_path + ['keep-alive-timer', keep_alive_timer]) + self.cli_set(base_path + ['packets', packets]) + self.cli_set(base_path + ['register-accept-list', 'prefix-list', prefix_list]) + self.cli_set(base_path + ['register-suppress-time', register_suppress_time]) + self.cli_set(base_path + ['no-v6-secondary']) + self.cli_set(base_path + ['spt-switchover', 'infinity-and-beyond', 'prefix-list', prefix_list]) + self.cli_set(base_path + ['ssm', 'prefix-list', prefix_list]) + + # check validate() - PIM require defined interfaces! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + interfaces = Section.interfaces('ethernet') + for interface in interfaces: + self.cli_set(base_path + ['interface', interface]) + + # commit changes + self.cli_commit() + + # Verify FRR pimd configuration + frrconfig = self.getFRRconfig(daemon=PROCESS_NAME) + self.assertIn(f'ip pim rp {rp} {group}', frrconfig) + self.assertIn(f'ip pim rp keep-alive-timer {rp_keep_alive_timer}', frrconfig) + self.assertIn(f'ip pim ecmp rebalance', frrconfig) + self.assertIn(f'ip pim join-prune-interval {join_prune_interval}', frrconfig) + self.assertIn(f'ip pim keep-alive-timer {keep_alive_timer}', frrconfig) + self.assertIn(f'ip pim packets {packets}', frrconfig) + self.assertIn(f'ip pim register-accept-list {prefix_list}', frrconfig) + self.assertIn(f'ip pim register-suppress-time {register_suppress_time}', frrconfig) + self.assertIn(f'no ip pim send-v6-secondary', frrconfig) + self.assertIn(f'ip pim spt-switchover infinity-and-beyond prefix-list {prefix_list}', frrconfig) + self.assertIn(f'ip pim ssm prefix-list {prefix_list}', frrconfig) + + def test_03_pim_igmp_proxy(self): + igmp_proxy = ['protocols', 'igmp-proxy'] + rp = '127.0.0.1' + group = '224.0.0.0/4' + + self.cli_set(base_path) + self.cli_set(igmp_proxy) + + # check validate() - can not set both IGMP proxy and PIM + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete(igmp_proxy) + + self.cli_set(base_path + ['rp', 'address', rp, 'group', group]) + interfaces = Section.interfaces('ethernet') + for interface in interfaces: + self.cli_set(base_path + ['interface', interface , 'bfd']) + + # commit changes + self.cli_commit() + + def test_04_igmp(self): + watermark_warning = '2000' + query_interval = '1000' + query_max_response_time = '200' + version = '2' + + igmp_join = { + '224.1.1.1' : { 'source' : ['1.1.1.1', '2.2.2.2', '3.3.3.3'] }, + '224.1.2.2' : { 'source' : [] }, + '224.1.3.3' : {}, + } + + self.cli_set(base_path + ['igmp', 'watermark-warning', watermark_warning]) + interfaces = Section.interfaces('ethernet') + for interface in interfaces: + self.cli_set(base_path + ['interface', interface , 'igmp', 'version', version]) + self.cli_set(base_path + ['interface', interface , 'igmp', 'query-interval', query_interval]) + self.cli_set(base_path + ['interface', interface , 'igmp', 'query-max-response-time', query_max_response_time]) + + for join, join_config in igmp_join.items(): + self.cli_set(base_path + ['interface', interface , 'igmp', 'join', join]) + if 'source' in join_config: + for source in join_config['source']: + self.cli_set(base_path + ['interface', interface , 'igmp', 'join', join, 'source-address', source]) + + self.cli_commit() + + frrconfig = self.getFRRconfig(daemon=PROCESS_NAME) + self.assertIn(f'ip igmp watermark-warn {watermark_warning}', frrconfig) + + for interface in interfaces: + frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', frrconfig) + self.assertIn(f' ip igmp', frrconfig) + self.assertIn(f' ip igmp version {version}', frrconfig) + self.assertIn(f' ip igmp query-interval {query_interval}', frrconfig) + self.assertIn(f' ip igmp query-max-response-time {query_max_response_time}', frrconfig) + + for join, join_config in igmp_join.items(): + if 'source' in join_config: + for source in join_config['source']: + self.assertIn(f' ip igmp join {join} {source}', frrconfig) + else: + self.assertIn(f' ip igmp join {join}', frrconfig) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_pim6.py b/smoketest/scripts/cli/test_protocols_pim6.py new file mode 100644 index 0000000..ba24edc --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_pim6.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'pim6d' +base_path = ['protocols', 'pim6'] + +class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + # call base-classes classmethod + super(TestProtocolsPIMv6, cls).setUpClass() + # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same + cls.daemon_pid = process_named_running(PROCESS_NAME) + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + # check process health and continuity + self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + + def test_pim6_01_mld_simple(self): + # commit changes + interfaces = Section.interfaces('ethernet') + for interface in interfaces: + self.cli_set(base_path + ['interface', interface]) + + self.cli_commit() + + # Verify FRR pim6d configuration + for interface in interfaces: + config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ipv6 mld', config) + self.assertNotIn(f' ipv6 mld version 1', config) + + # Change to MLD version 1 + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'mld', 'version', '1']) + + self.cli_commit() + + # Verify FRR pim6d configuration + for interface in interfaces: + config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ipv6 mld', config) + self.assertIn(f' ipv6 mld version 1', config) + + def test_pim6_02_mld_join(self): + interfaces = Section.interfaces('ethernet') + # Use an invalid multicast group address + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'mld', 'join', 'fd00::1234']) + + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface']) + + # Use a valid multicast group address + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'mld', 'join', 'ff18::1234']) + + self.cli_commit() + + # Verify FRR pim6d configuration + for interface in interfaces: + config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ipv6 mld join ff18::1234', config) + + # Join a source-specific multicast group + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'mld', 'join', 'ff38::5678', 'source', '2001:db8::5678']) + + self.cli_commit() + + # Verify FRR pim6d configuration + for interface in interfaces: + config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ipv6 mld join ff38::5678 2001:db8::5678', config) + + def test_pim6_03_basic(self): + interfaces = Section.interfaces('ethernet') + join_prune_interval = '123' + keep_alive_timer = '77' + packets = '5' + register_suppress_time = '99' + dr_priority = '100' + hello = '50' + + self.cli_set(base_path + ['join-prune-interval', join_prune_interval]) + self.cli_set(base_path + ['keep-alive-timer', keep_alive_timer]) + self.cli_set(base_path + ['packets', packets]) + self.cli_set(base_path + ['register-suppress-time', register_suppress_time]) + + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'dr-priority', dr_priority]) + self.cli_set(base_path + ['interface', interface, 'hello', hello]) + self.cli_set(base_path + ['interface', interface, 'no-bsm']) + self.cli_set(base_path + ['interface', interface, 'no-unicast-bsm']) + self.cli_set(base_path + ['interface', interface, 'passive']) + + self.cli_commit() + + # Verify FRR pim6d configuration + config = self.getFRRconfig(daemon=PROCESS_NAME) + self.assertIn(f'ipv6 pim join-prune-interval {join_prune_interval}', config) + self.assertIn(f'ipv6 pim keep-alive-timer {keep_alive_timer}', config) + self.assertIn(f'ipv6 pim packets {packets}', config) + self.assertIn(f'ipv6 pim register-suppress-time {register_suppress_time}', config) + + for interface in interfaces: + config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f' ipv6 pim drpriority {dr_priority}', config) + self.assertIn(f' ipv6 pim hello {hello}', config) + self.assertIn(f' no ipv6 pim bsm', config) + self.assertIn(f' no ipv6 pim unicast-bsm', config) + self.assertIn(f' ipv6 pim passive', config) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_rip.py b/smoketest/scripts/cli/test_protocols_rip.py new file mode 100644 index 0000000..bfc327f --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_rip.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.ifconfig import Section +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'ripd' +acl_in = '198' +acl_out = '199' +prefix_list_in = 'foo-prefix' +prefix_list_out = 'bar-prefix' +route_map = 'FooBar123' + +base_path = ['protocols', 'rip'] + +class TestProtocolsRIP(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestProtocolsRIP, cls).setUpClass() + # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same + cls.daemon_pid = process_named_running(PROCESS_NAME) + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + cls.cli_set(cls, ['policy', 'access-list', acl_in, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'access-list', acl_in, 'rule', '10', 'source', 'any']) + cls.cli_set(cls, ['policy', 'access-list', acl_in, 'rule', '10', 'destination', 'any']) + cls.cli_set(cls, ['policy', 'access-list', acl_out, 'rule', '20', 'action', 'deny']) + cls.cli_set(cls, ['policy', 'access-list', acl_out, 'rule', '20', 'source', 'any']) + cls.cli_set(cls, ['policy', 'access-list', acl_out, 'rule', '20', 'destination', 'any']) + cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_in, 'rule', '100', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_in, 'rule', '100', 'prefix', '192.0.2.0/24']) + cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_out, 'rule', '200', 'action', 'deny']) + cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_out, 'rule', '200', 'prefix', '192.0.2.0/24']) + cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['policy', 'access-list', acl_in]) + cls.cli_delete(cls, ['policy', 'access-list', acl_out]) + cls.cli_delete(cls, ['policy', 'prefix-list', prefix_list_in]) + cls.cli_delete(cls, ['policy', 'prefix-list', prefix_list_out]) + cls.cli_delete(cls, ['policy', 'route-map', route_map]) + + super(TestProtocolsRIP, cls).tearDownClass() + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + # check process health and continuity + self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + + def test_rip_01_parameters(self): + distance = '40' + network_distance = '66' + metric = '8' + interfaces = Section.interfaces('ethernet') + neighbors = ['1.2.3.4', '1.2.3.5', '1.2.3.6'] + networks = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'] + redistribute = ['bgp', 'connected', 'isis', 'kernel', 'ospf', 'static'] + timer_garbage = '888' + timer_timeout = '1000' + timer_update = '90' + + self.cli_set(base_path + ['default-distance', distance]) + self.cli_set(base_path + ['default-information', 'originate']) + self.cli_set(base_path + ['default-metric', metric]) + self.cli_set(base_path + ['distribute-list', 'access-list', 'in', acl_in]) + self.cli_set(base_path + ['distribute-list', 'access-list', 'out', acl_out]) + self.cli_set(base_path + ['distribute-list', 'prefix-list', 'in', prefix_list_in]) + self.cli_set(base_path + ['distribute-list', 'prefix-list', 'out', prefix_list_out]) + self.cli_set(base_path + ['passive-interface', 'default']) + self.cli_set(base_path + ['timers', 'garbage-collection', timer_garbage]) + self.cli_set(base_path + ['timers', 'timeout', timer_timeout]) + self.cli_set(base_path + ['timers', 'update', timer_update]) + for interface in interfaces: + self.cli_set(base_path + ['interface', interface]) + self.cli_set(base_path + ['distribute-list', 'interface', interface, 'access-list', 'in', acl_in]) + self.cli_set(base_path + ['distribute-list', 'interface', interface, 'access-list', 'out', acl_out]) + self.cli_set(base_path + ['distribute-list', 'interface', interface, 'prefix-list', 'in', prefix_list_in]) + self.cli_set(base_path + ['distribute-list', 'interface', interface, 'prefix-list', 'out', prefix_list_out]) + for neighbor in neighbors: + self.cli_set(base_path + ['neighbor', neighbor]) + for network in networks: + self.cli_set(base_path + ['network', network]) + self.cli_set(base_path + ['network-distance', network, 'distance', network_distance]) + self.cli_set(base_path + ['route', network]) + for proto in redistribute: + self.cli_set(base_path + ['redistribute', proto, 'metric', metric]) + self.cli_set(base_path + ['redistribute', proto, 'route-map', route_map]) + + + # commit changes + self.cli_commit() + + # Verify FRR ripd configuration + frrconfig = self.getFRRconfig('router rip') + self.assertIn(f'router rip', frrconfig) + self.assertIn(f' distance {distance}', frrconfig) + self.assertIn(f' default-information originate', frrconfig) + self.assertIn(f' default-metric {metric}', frrconfig) + self.assertIn(f' distribute-list {acl_in} in', frrconfig) + self.assertIn(f' distribute-list {acl_out} out', frrconfig) + self.assertIn(f' distribute-list prefix {prefix_list_in} in', frrconfig) + self.assertIn(f' distribute-list prefix {prefix_list_out} out', frrconfig) + self.assertIn(f' passive-interface default', frrconfig) + self.assertIn(f' timers basic {timer_update} {timer_timeout} {timer_garbage}', frrconfig) + for interface in interfaces: + self.assertIn(f' network {interface}', frrconfig) + self.assertIn(f' distribute-list {acl_in} in {interface}', frrconfig) + self.assertIn(f' distribute-list {acl_out} out {interface}', frrconfig) + self.assertIn(f' distribute-list prefix {prefix_list_in} in {interface}', frrconfig) + self.assertIn(f' distribute-list prefix {prefix_list_out} out {interface}', frrconfig) + for neighbor in neighbors: + self.assertIn(f' neighbor {neighbor}', frrconfig) + for network in networks: + self.assertIn(f' network {network}', frrconfig) + self.assertIn(f' distance {network_distance} {network}', frrconfig) + self.assertIn(f' route {network}', frrconfig) + for proto in redistribute: + self.assertIn(f' redistribute {proto} metric {metric} route-map {route_map}', frrconfig) + + def test_rip_02_zebra_route_map(self): + # Implemented because of T3328 + self.cli_set(base_path + ['route-map', route_map]) + # commit changes + self.cli_commit() + + # Verify FRR configuration + zebra_route_map = f'ip protocol rip route-map {route_map}' + frrconfig = self.getFRRconfig(zebra_route_map) + self.assertIn(zebra_route_map, frrconfig) + + # Remove the route-map again + self.cli_delete(base_path + ['route-map']) + # commit changes + self.cli_commit() + + # Verify FRR configuration + frrconfig = self.getFRRconfig(zebra_route_map) + self.assertNotIn(zebra_route_map, frrconfig) + + def test_rip_03_version(self): + rx_version = '1' + tx_version = '2' + interface = 'eth0' + + self.cli_set(base_path + ['version', tx_version]) + self.cli_set(base_path + ['interface', interface, 'send', 'version', tx_version]) + self.cli_set(base_path + ['interface', interface, 'receive', 'version', rx_version]) + + # commit changes + self.cli_commit() + + # Verify FRR configuration + frrconfig = self.getFRRconfig('router rip') + self.assertIn(f'version {tx_version}', frrconfig) + + frrconfig = self.getFRRconfig(f'interface {interface}') + self.assertIn(f' ip rip receive version {rx_version}', frrconfig) + self.assertIn(f' ip rip send version {tx_version}', frrconfig) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_ripng.py b/smoketest/scripts/cli/test_protocols_ripng.py new file mode 100644 index 0000000..0cfb065 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_ripng.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.ifconfig import Section +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'ripngd' +acl_in = '198' +acl_out = '199' +prefix_list_in = 'foo-prefix' +prefix_list_out = 'bar-prefix' +route_map = 'FooBar123' + +base_path = ['protocols', 'ripng'] + +class TestProtocolsRIPng(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + # call base-classes classmethod + super(TestProtocolsRIPng, cls).setUpClass() + # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same + cls.daemon_pid = process_named_running(PROCESS_NAME) + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + cls.cli_set(cls, ['policy', 'access-list6', acl_in, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'access-list6', acl_in, 'rule', '10', 'source', 'any']) + cls.cli_set(cls, ['policy', 'access-list6', acl_out, 'rule', '20', 'action', 'deny']) + cls.cli_set(cls, ['policy', 'access-list6', acl_out, 'rule', '20', 'source', 'any']) + cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_in, 'rule', '100', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_in, 'rule', '100', 'prefix', '2001:db8::/32']) + cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_out, 'rule', '200', 'action', 'deny']) + cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_out, 'rule', '200', 'prefix', '2001:db8::/32']) + cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) + + @classmethod + def tearDownClass(cls): + # call base-classes classmethod + super(TestProtocolsRIPng, cls).tearDownClass() + + cls.cli_delete(cls, ['policy', 'access-list6', acl_in]) + cls.cli_delete(cls, ['policy', 'access-list6', acl_out]) + cls.cli_delete(cls, ['policy', 'prefix-list6', prefix_list_in]) + cls.cli_delete(cls, ['policy', 'prefix-list6', prefix_list_out]) + cls.cli_delete(cls, ['policy', 'route-map', route_map]) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + # check process health and continuity + self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + + def test_ripng_01_parameters(self): + metric = '8' + interfaces = Section.interfaces('ethernet') + aggregates = ['2001:db8:1000::/48', '2001:db8:2000::/48', '2001:db8:3000::/48'] + networks = ['2001:db8:1000::/64', '2001:db8:1001::/64', '2001:db8:2000::/64', '2001:db8:2001::/64'] + redistribute = ['bgp', 'connected', 'kernel', 'ospfv3', 'static'] + timer_garbage = '888' + timer_timeout = '1000' + timer_update = '90' + + self.cli_set(base_path + ['default-information', 'originate']) + self.cli_set(base_path + ['default-metric', metric]) + self.cli_set(base_path + ['distribute-list', 'access-list', 'in', acl_in]) + self.cli_set(base_path + ['distribute-list', 'access-list', 'out', acl_out]) + self.cli_set(base_path + ['distribute-list', 'prefix-list', 'in', prefix_list_in]) + self.cli_set(base_path + ['distribute-list', 'prefix-list', 'out', prefix_list_out]) + self.cli_set(base_path + ['passive-interface', 'default']) + self.cli_set(base_path + ['timers', 'garbage-collection', timer_garbage]) + self.cli_set(base_path + ['timers', 'timeout', timer_timeout]) + self.cli_set(base_path + ['timers', 'update', timer_update]) + for aggregate in aggregates: + self.cli_set(base_path + ['aggregate-address', aggregate]) + + for interface in interfaces: + self.cli_set(base_path + ['interface', interface]) + self.cli_set(base_path + ['distribute-list', 'interface', interface, 'access-list', 'in', acl_in]) + self.cli_set(base_path + ['distribute-list', 'interface', interface, 'access-list', 'out', acl_out]) + self.cli_set(base_path + ['distribute-list', 'interface', interface, 'prefix-list', 'in', prefix_list_in]) + self.cli_set(base_path + ['distribute-list', 'interface', interface, 'prefix-list', 'out', prefix_list_out]) + for network in networks: + self.cli_set(base_path + ['network', network]) + self.cli_set(base_path + ['route', network]) + for proto in redistribute: + self.cli_set(base_path + ['redistribute', proto, 'metric', metric]) + self.cli_set(base_path + ['redistribute', proto, 'route-map', route_map]) + + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ripng') + self.assertIn(f'router ripng', frrconfig) + self.assertIn(f' default-information originate', frrconfig) + self.assertIn(f' default-metric {metric}', frrconfig) + self.assertIn(f' ipv6 distribute-list {acl_in} in', frrconfig) + self.assertIn(f' ipv6 distribute-list {acl_out} out', frrconfig) + self.assertIn(f' ipv6 distribute-list prefix {prefix_list_in} in', frrconfig) + self.assertIn(f' ipv6 distribute-list prefix {prefix_list_out} out', frrconfig) + self.assertIn(f' passive-interface default', frrconfig) + self.assertIn(f' timers basic {timer_update} {timer_timeout} {timer_garbage}', frrconfig) + for aggregate in aggregates: + self.assertIn(f' aggregate-address {aggregate}', frrconfig) + for interface in interfaces: + self.assertIn(f' network {interface}', frrconfig) + self.assertIn(f' ipv6 distribute-list {acl_in} in {interface}', frrconfig) + self.assertIn(f' ipv6 distribute-list {acl_out} out {interface}', frrconfig) + self.assertIn(f' ipv6 distribute-list prefix {prefix_list_in} in {interface}', frrconfig) + self.assertIn(f' ipv6 distribute-list prefix {prefix_list_out} out {interface}', frrconfig) + for network in networks: + self.assertIn(f' network {network}', frrconfig) + self.assertIn(f' route {network}', frrconfig) + for proto in redistribute: + if proto == 'ospfv3': + proto = 'ospf6' + self.assertIn(f' redistribute {proto} metric {metric} route-map {route_map}', frrconfig) + + def test_ripng_02_zebra_route_map(self): + # Implemented because of T3328 + self.cli_set(base_path + ['route-map', route_map]) + # commit changes + self.cli_commit() + + # Verify FRR configuration + zebra_route_map = f'ipv6 protocol ripng route-map {route_map}' + frrconfig = self.getFRRconfig(zebra_route_map) + self.assertIn(zebra_route_map, frrconfig) + + # Remove the route-map again + self.cli_delete(base_path + ['route-map']) + # commit changes + self.cli_commit() + + # Verify FRR configuration + frrconfig = self.getFRRconfig(zebra_route_map) + self.assertNotIn(zebra_route_map, frrconfig) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_rpki.py b/smoketest/scripts/cli/test_protocols_rpki.py new file mode 100644 index 0000000..29f03a2 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_rpki.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.file import read_file +from vyos.utils.process import process_named_running + +base_path = ['protocols', 'rpki'] +PROCESS_NAME = 'bgpd' + +rpki_key_name = 'rpki-smoketest' +rpki_key_type = 'ssh-rsa' + +rpki_ssh_key = """ +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAweDyflDFR4qyEwETbJkZ2ZZc+sJNiDTvYpwGsWIkju49lJSxHe1x +Kf8FhwfyMu40Snt1yDlRmmmz4CsbLgbuZGMPvXG11e34+C0pSVUvpF6aqRTeLl1pDRK7Rn +jgm3su+I8SRLQR4qbLG6VXWOFuVpwiqbExLaU0hFYTPNP+dArNpsWEEKsohk6pTXdhg3Vz +Wp3vCMjl2JTshDa3lD7p2xISSAReEY0fnfEAmQzH4Z6DIwwGdFuMWoQIg+oFBM9ARrO2/F +IjRsz6AecR/WeU72JEw4aJic1/cAJQA6PiQBHwkuo3Wll1tbpxeRZoB2NQG22ETyJLvhfT +aooNLT9HpQAAA8joU5dM6FOXTAAAAAdzc2gtcnNhAAABAQDB4PJ+UMVHirITARNsmRnZll +z6wk2INO9inAaxYiSO7j2UlLEd7XEp/wWHB/Iy7jRKe3XIOVGaabPgKxsuBu5kYw+9cbXV +7fj4LSlJVS+kXpqpFN4uXWkNErtGeOCbey74jxJEtBHipssbpVdY4W5WnCKpsTEtpTSEVh +M80/50Cs2mxYQQqyiGTqlNd2GDdXNane8IyOXYlOyENreUPunbEhJIBF4RjR+d8QCZDMfh +noMjDAZ0W4xahAiD6gUEz0BGs7b8UiNGzPoB5xH9Z5TvYkTDhomJzX9wAlADo+JAEfCS6j +daWXW1unF5FmgHY1AbbYRPIku+F9Nqig0tP0elAAAAAwEAAQAAAQACkDlUjzfUhtJs6uY5 +WNrdJB5NmHUS+HQzzxFNlhkapK6+wKqI1UNaRUtq6iF7J+gcFf7MK2nXS098BsXguWm8fQ +zPuemoDvHsQhiaJhyvpSqRUrvPTB/f8t/0AhQiKiJIWgfpTaIw53inAGwjujNNxNm2eafH +TThhCYxOkRT7rsT6bnSio6yeqPy5QHg7IKFztp5FXDUyiOS3aX3SvzQcDUkMXALdvzX50t +1XIk+X48Rgkq72dL4VpV2oMNDu3hM6FqBUplf9Mv3s51FNSma/cibCQoVufrIfoqYjkNTj +IpYFUcq4zZ0/KvgXgzSsy9VN/4TtbalrOuu7X/SHJbvhAAAAgGPFsXgONYQvXxCnK1dIue +ozgaZg1I/n522E2ZCOXBW4dYJVyNpppwRreDzuFzTDEe061MpNHfScjVBJCCulivFYWscL +6oaGsryDbFxO3QmB4I98UBqrds2yan9/JGc6EYe299yvaHy7Y64+NC0+fN8H2RAZ61T4w1 +0JrCaJRyvzAAAAgQDvBfuV1U7o9k/fbU+U7W2UYnWblpOZAMfi1XQP6IJJeyWs90PdTdXh ++l0eIQrCawIiRJytNfxMmbD4huwTf77fWiyCcPznmALQ7ex/yJ+W5Z0V4dPGF3h7o1uiS2 +36JhQ7mfcliCkhp/1PIklBIMPcCp0zl+s9wMv2hX7w1Pah9QAAAIEAz6YgU9Xute+J+dBw +oWxEQ+igR6KE55Um7O9AvSrqnCm9r7lSFsXC2ErYOxoDSJ3yIBEV0b4XAGn6tbbVIs3jS8 +BnLHxclAHQecOx1PGn7PKbnPW0oJRq/X9QCIEelKYvlykpayn7uZooTXqcDaPZxfPpmPdy +e8chVJvdygi7kPEAAAAMY3BvQExSMS53dWUzAQIDBAUGBw== +""" + +rpki_ssh_pub = """ +AAAAB3NzaC1yc2EAAAADAQABAAABAQDB4PJ+UMVHirITARNsmRnZllz6wk2INO9inAaxYi +SO7j2UlLEd7XEp/wWHB/Iy7jRKe3XIOVGaabPgKxsuBu5kYw+9cbXV7fj4LSlJVS+kXpqp +FN4uXWkNErtGeOCbey74jxJEtBHipssbpVdY4W5WnCKpsTEtpTSEVhM80/50Cs2mxYQQqy +iGTqlNd2GDdXNane8IyOXYlOyENreUPunbEhJIBF4RjR+d8QCZDMfhnoMjDAZ0W4xahAiD +6gUEz0BGs7b8UiNGzPoB5xH9Z5TvYkTDhomJzX9wAlADo+JAEfCS6jdaWXW1unF5FmgHY1 +AbbYRPIku+F9Nqig0tP0el +""" + +rpki_ssh_key_replacement = """ +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAtLPMwiGR3o6puPDbus9Yqoah9/7rv7i6ykykPmcEZ6ERnA0N6bl7 +LkQxnCuX270ukTTZOhROvQnvQYIZohCMz27Q16z7r+I755QXL0x8x4Gqhg/hQUY7UtX6ts +db8+pO7G1PL4r9zT6/KJAF/wv86DezJ3I6TMaA7MCikXfQWJisBvhgAXF1+7V9CWaroGgV +/hHzQJu1yd4cfsYoHyeDaZ+lwFw4egNItIy63fIGDxrnXaonJ1ODGQh7zWlpl/cwQR/KyJ +P8vvOZ9olQ6syZV+DAcAo4Fe59wW2Zj4bl8bdGcdiDn0grkafxwTcg9ynr9kwQ8b66oXY4 +hwB4vlPFPwAAA8jkGyX45Bsl+AAAAAdzc2gtcnNhAAABAQC0s8zCIZHejqm48Nu6z1iqhq +H3/uu/uLrKTKQ+ZwRnoRGcDQ3puXsuRDGcK5fbvS6RNNk6FE69Ce9BghmiEIzPbtDXrPuv +4jvnlBcvTHzHgaqGD+FBRjtS1fq2x1vz6k7sbU8viv3NPr8okAX/C/zoN7MncjpMxoDswK +KRd9BYmKwG+GABcXX7tX0JZqugaBX+EfNAm7XJ3hx+xigfJ4Npn6XAXDh6A0i0jLrd8gYP +GuddqicnU4MZCHvNaWmX9zBBH8rIk/y+85n2iVDqzJlX4MBwCjgV7n3BbZmPhuXxt0Zx2I +OfSCuRp/HBNyD3Kev2TBDxvrqhdjiHAHi+U8U/AAAAAwEAAQAAAQA99gkX5/rknXaE+9Hc +VIzKrC+NodOkgetKwszuuNRB1HD9WVyT8A3U5307V5dSuaPmFoEF8UCugWGQzNONRq+B0T +W7Po1u2dxAo/7vMQL4RfX60icjAroExWqakfFtycIWP8UPQFGWtxVFC12C/tFRrwe3Vuu2 +t7otdEBKMRM3zU0Hj88/5FIk/MDhththDCKTMe4+iwNKo30dyqSCckpTd2k5de9JYz8Aom +87jtQcyDdynaELSo9CsA8KRPlozZ4VSWTVLH+Cv2TZWPL7hy79YvvIfuF/Sd6PGkNwG1Vj +TAbq2Wx4uq+HmpNiz7W0LnbZtQJ7dzLA3FZlvQMC8fVBAAAAgQDWvImVZCyVWpoG+LnKY3 +joegjKRYKdgKRPCqGoIHiYsqCRxqSRW3jsuQCCvk4YO3/ZmqORiGktK+5r8R1QEtwg5qbi +N7GZD34m7USNuqG2G/4puEly8syMmR6VRRvEURFQrpv2wniXNSefvsDc+WDqTfXGUxr+FT +478wkzjwc/fAAAAIEA9uP0Ym3OC3cZ5FOvmu51lxo5lqPlUeE78axg2I4u/9Il8nOvSVuq +B9X5wAUyGAGcUjT3EZmRAtL2sQxc5T0Vw3bnxCjzukEbFM+DRtYy1hXSOoGTTwKoMWBpho +R3X5uRLUQL/22C4rd7tSJpjqnZXIH0B5z2fFh4vzu8/SrgCrUAAACBALtep4BcGJfjfhfF +ODzQe7Rk7tsaX8pfNv6bQu0sR5C9pDURFRf0fRC0oqgeTuzq/vHPyNLsUUgTCpKWiLFmvU +G9pelLT3XPPgzA+g0gycM0unuX8kkP3T5VQAM/7u0+h1CaJ8A6cCkzvDJxYdfio3WR60OP +ulHg7HCcyomFLaSjAAAADGNwb0BMUjEud3VlMwECAwQFBg== +""" + +rpki_ssh_pub_replacement = """ +AAAAB3NzaC1yc2EAAAADAQABAAABAQC0s8zCIZHejqm48Nu6z1iqhqH3/uu/uLrKTKQ+Zw +RnoRGcDQ3puXsuRDGcK5fbvS6RNNk6FE69Ce9BghmiEIzPbtDXrPuv4jvnlBcvTHzHgaqG +D+FBRjtS1fq2x1vz6k7sbU8viv3NPr8okAX/C/zoN7MncjpMxoDswKKRd9BYmKwG+GABcX +X7tX0JZqugaBX+EfNAm7XJ3hx+xigfJ4Npn6XAXDh6A0i0jLrd8gYPGuddqicnU4MZCHvN +aWmX9zBBH8rIk/y+85n2iVDqzJlX4MBwCjgV7n3BbZmPhuXxt0Zx2IOfSCuRp/HBNyD3Ke +v2TBDxvrqhdjiHAHi+U8U/ +""" + +class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + # call base-classes classmethod + super(TestProtocolsRPKI, cls).setUpClass() + # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same + cls.daemon_pid = process_named_running(PROCESS_NAME) + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + # check process health and continuity + self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + + def test_rpki(self): + expire_interval = '3600' + polling_period = '600' + retry_interval = '300' + cache = { + '192.0.2.1' : { + 'port' : '8080', + 'preference' : '10' + }, + '2001:db8::1' : { + 'port' : '1234', + 'preference' : '30' + }, + 'rpki.vyos.net' : { + 'port' : '5678', + 'preference' : '40' + }, + } + + self.cli_set(base_path + ['expire-interval', expire_interval]) + self.cli_set(base_path + ['polling-period', polling_period]) + self.cli_set(base_path + ['retry-interval', retry_interval]) + + for peer, peer_config in cache.items(): + self.cli_set(base_path + ['cache', peer, 'port', peer_config['port']]) + self.cli_set(base_path + ['cache', peer, 'preference', peer_config['preference']]) + + # commit changes + self.cli_commit() + + # Verify FRR configuration + frrconfig = self.getFRRconfig('rpki') + self.assertIn(f'rpki expire_interval {expire_interval}', frrconfig) + self.assertIn(f'rpki polling_period {polling_period}', frrconfig) + self.assertIn(f'rpki retry_interval {retry_interval}', frrconfig) + + for peer, peer_config in cache.items(): + port = peer_config['port'] + preference = peer_config['preference'] + self.assertIn(f'rpki cache {peer} {port} preference {preference}', frrconfig) + + def test_rpki_ssh(self): + polling = '7200' + cache = { + '192.0.2.3' : { + 'port' : '1234', + 'username' : 'foo', + 'preference' : '10' + }, + '192.0.2.4' : { + 'port' : '5678', + 'username' : 'bar', + 'preference' : '20' + }, + } + + self.cli_set(['pki', 'openssh', rpki_key_name, 'private', 'key', rpki_ssh_key.replace('\n','')]) + self.cli_set(['pki', 'openssh', rpki_key_name, 'public', 'key', rpki_ssh_pub.replace('\n','')]) + self.cli_set(['pki', 'openssh', rpki_key_name, 'public', 'type', rpki_key_type]) + + for cache_name, cache_config in cache.items(): + self.cli_set(base_path + ['cache', cache_name, 'port', cache_config['port']]) + self.cli_set(base_path + ['cache', cache_name, 'preference', cache_config['preference']]) + self.cli_set(base_path + ['cache', cache_name, 'ssh', 'username', cache_config['username']]) + self.cli_set(base_path + ['cache', cache_name, 'ssh', 'key', rpki_key_name]) + + # commit changes + self.cli_commit() + + # Verify FRR configuration + frrconfig = self.getFRRconfig('rpki') + for cache_name, cache_config in cache.items(): + port = cache_config['port'] + preference = cache_config['preference'] + username = cache_config['username'] + self.assertIn(f'rpki cache {cache_name} {port} {username} /run/frr/id_rpki_{cache_name} /run/frr/id_rpki_{cache_name}.pub preference {preference}', frrconfig) + + # Verify content of SSH keys + tmp = read_file(f'/run/frr/id_rpki_{cache_name}') + self.assertIn(rpki_ssh_key.replace('\n',''), tmp) + tmp = read_file(f'/run/frr/id_rpki_{cache_name}.pub') + self.assertIn(rpki_ssh_pub.replace('\n',''), tmp) + + # Change OpenSSH key and verify it was properly written to filesystem + self.cli_set(['pki', 'openssh', rpki_key_name, 'private', 'key', rpki_ssh_key_replacement.replace('\n','')]) + self.cli_set(['pki', 'openssh', rpki_key_name, 'public', 'key', rpki_ssh_pub_replacement.replace('\n','')]) + # commit changes + self.cli_commit() + + for cache_name, cache_config in cache.items(): + port = cache_config['port'] + preference = cache_config['preference'] + username = cache_config['username'] + self.assertIn(f'rpki cache {cache_name} {port} {username} /run/frr/id_rpki_{cache_name} /run/frr/id_rpki_{cache_name}.pub preference {preference}', frrconfig) + + # Verify content of SSH keys + tmp = read_file(f'/run/frr/id_rpki_{cache_name}') + self.assertIn(rpki_ssh_key_replacement.replace('\n',''), tmp) + tmp = read_file(f'/run/frr/id_rpki_{cache_name}.pub') + self.assertIn(rpki_ssh_pub_replacement.replace('\n',''), tmp) + + self.cli_delete(['pki', 'openssh']) + + def test_rpki_verify_preference(self): + cache = { + '192.0.2.1' : { + 'port' : '8080', + 'preference' : '1' + }, + '192.0.2.2' : { + 'port' : '9090', + 'preference' : '1' + }, + } + + for peer, peer_config in cache.items(): + self.cli_set(base_path + ['cache', peer, 'port', peer_config['port']]) + self.cli_set(base_path + ['cache', peer, 'preference', peer_config['preference']]) + + # check validate() - preferences must be unique + with self.assertRaises(ConfigSessionError): + self.cli_commit() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_segment-routing.py b/smoketest/scripts/cli/test_protocols_segment-routing.py new file mode 100644 index 0000000..daa7f08 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_segment-routing.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import process_named_running +from vyos.utils.system import sysctl_read + +base_path = ['protocols', 'segment-routing'] +PROCESS_NAME = 'zebra' + +class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + # call base-classes classmethod + super(TestProtocolsSegmentRouting, cls).setUpClass() + # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same + cls.daemon_pid = process_named_running(PROCESS_NAME) + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + # check process health and continuity + self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + + def test_srv6(self): + interfaces = Section.interfaces('ethernet', vlan=False) + locators = { + 'foo' : { 'prefix' : '2001:a::/64' }, + 'foo' : { 'prefix' : '2001:b::/64', 'usid' : {} }, + } + + for locator, locator_config in locators.items(): + self.cli_set(base_path + ['srv6', 'locator', locator, 'prefix', locator_config['prefix']]) + if 'usid' in locator_config: + self.cli_set(base_path + ['srv6', 'locator', locator, 'behavior-usid']) + + # verify() - SRv6 should be enabled on at least one interface! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'srv6']) + + self.cli_commit() + + for interface in interfaces: + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '0') # default + + frrconfig = self.getFRRconfig(f'segment-routing', daemon='zebra') + self.assertIn(f'segment-routing', frrconfig) + self.assertIn(f' srv6', frrconfig) + self.assertIn(f' locators', frrconfig) + for locator, locator_config in locators.items(): + self.assertIn(f' locator {locator}', frrconfig) + self.assertIn(f' prefix {locator_config["prefix"]} block-len 40 node-len 24 func-bits 16', frrconfig) + + def test_srv6_sysctl(self): + interfaces = Section.interfaces('ethernet', vlan=False) + + # HMAC accept + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'srv6']) + self.cli_set(base_path + ['interface', interface, 'srv6', 'hmac', 'ignore']) + self.cli_commit() + + for interface in interfaces: + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '-1') # ignore + + # HMAC drop + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'srv6']) + self.cli_set(base_path + ['interface', interface, 'srv6', 'hmac', 'drop']) + self.cli_commit() + + for interface in interfaces: + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '1') # drop + + # Disable SRv6 on first interface + first_if = interfaces[-1] + self.cli_delete(base_path + ['interface', first_if]) + self.cli_commit() + + self.assertEqual(sysctl_read(f'net.ipv6.conf.{first_if}.seg6_enabled'), '0') + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py new file mode 100644 index 0000000..f676e2a --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_static.py @@ -0,0 +1,482 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.template import is_ipv6 +from vyos.utils.network import get_interface_config +from vyos.utils.network import get_vrf_tableid + +base_path = ['protocols', 'static'] +vrf_path = ['protocols', 'vrf'] + +routes = { + '10.0.0.0/8' : { + 'next_hop' : { + '192.0.2.100' : { 'distance' : '100' }, + '192.0.2.110' : { 'distance' : '110', 'interface' : 'eth0' }, + '192.0.2.120' : { 'distance' : '120', 'disable' : '' }, + '192.0.2.130' : { 'bfd' : '' }, + '192.0.2.140' : { 'bfd_source' : '192.0.2.10' }, + }, + 'interface' : { + 'eth0' : { 'distance' : '130' }, + 'eth1' : { 'distance' : '140' }, + }, + 'blackhole' : { 'distance' : '250', 'tag' : '500' }, + }, + '172.16.0.0/12' : { + 'interface' : { + 'eth0' : { 'distance' : '50', 'vrf' : 'black' }, + 'eth1' : { 'distance' : '60', 'vrf' : 'black' }, + }, + 'blackhole' : { 'distance' : '90' }, + }, + '192.0.2.0/24' : { + 'interface' : { + 'eth0' : { 'distance' : '50', 'vrf' : 'black' }, + 'eth1' : { 'disable' : '' }, + }, + 'blackhole' : { 'distance' : '90' }, + }, + '100.64.0.0/16' : { + 'blackhole' : {}, + }, + '100.65.0.0/16' : { + 'reject' : { 'distance' : '10', 'tag' : '200' }, + }, + '100.66.0.0/16' : { + 'blackhole' : {}, + 'reject' : { 'distance' : '10', 'tag' : '200' }, + }, + '2001:db8:100::/40' : { + 'next_hop' : { + '2001:db8::1' : { 'distance' : '10' }, + '2001:db8::2' : { 'distance' : '20', 'interface' : 'eth0' }, + '2001:db8::3' : { 'distance' : '30', 'disable' : '' }, + '2001:db8::4' : { 'bfd' : '' }, + '2001:db8::5' : { 'bfd_source' : '2001:db8::ffff' }, + }, + 'interface' : { + 'eth0' : { 'distance' : '40', 'vrf' : 'black' }, + 'eth1' : { 'distance' : '50', 'disable' : '' }, + }, + 'blackhole' : { 'distance' : '250', 'tag' : '500' }, + }, + '2001:db8:200::/40' : { + 'interface' : { + 'eth0' : { 'distance' : '40' }, + 'eth1' : { 'distance' : '50', 'disable' : '' }, + }, + 'blackhole' : { 'distance' : '250', 'tag' : '500' }, + }, + '2001:db8:300::/40' : { + 'reject' : { 'distance' : '250', 'tag' : '500' }, + }, + '2001:db8:400::/40' : { + 'next_hop' : { + '2001:db8::400' : { 'segments' : '2001:db8:aaaa::400/2002::400/2003::400/2004::400' }, + }, + }, + '2001:db8:500::/40' : { + 'next_hop' : { + '2001:db8::500' : { 'segments' : '2001:db8:aaaa::500/2002::500/2003::500/2004::500' }, + }, + }, + '2001:db8:600::/40' : { + 'interface' : { + 'eth0' : { 'segments' : '2001:db8:aaaa::600/2002::600' }, + }, + }, + '2001:db8:700::/40' : { + 'interface' : { + 'eth1' : { 'segments' : '2001:db8:aaaa::700' }, + }, + }, + '2001:db8::/32' : { + 'blackhole' : { 'distance' : '200', 'tag' : '600' } + }, +} + +tables = ['80', '81', '82'] + +class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestProtocolsStatic, cls).setUpClass() + cls.cli_delete(cls, ['vrf']) + cls.cli_set(cls, ['vrf', 'name', 'black', 'table', '43210']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['vrf']) + super(TestProtocolsStatic, cls).tearDownClass() + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + v4route = self.getFRRconfig('ip route', end='') + self.assertFalse(v4route) + v6route = self.getFRRconfig('ipv6 route', end='') + self.assertFalse(v6route) + + def test_01_static(self): + bfd_profile = 'vyos-test' + for route, route_config in routes.items(): + route_type = 'route' + if is_ipv6(route): + route_type = 'route6' + base = base_path + [route_type, route] + if 'next_hop' in route_config: + for next_hop, next_hop_config in route_config['next_hop'].items(): + self.cli_set(base + ['next-hop', next_hop]) + if 'disable' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'disable']) + if 'distance' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'distance', next_hop_config['distance']]) + if 'interface' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'interface', next_hop_config['interface']]) + if 'vrf' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'vrf', next_hop_config['vrf']]) + if 'bfd' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'bfd', 'profile', bfd_profile ]) + if 'bfd_source' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'bfd', 'multi-hop', 'source', next_hop_config['bfd_source'], 'profile', bfd_profile]) + if 'segments' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'segments', next_hop_config['segments']]) + + if 'interface' in route_config: + for interface, interface_config in route_config['interface'].items(): + self.cli_set(base + ['interface', interface]) + if 'disable' in interface_config: + self.cli_set(base + ['interface', interface, 'disable']) + if 'distance' in interface_config: + self.cli_set(base + ['interface', interface, 'distance', interface_config['distance']]) + if 'vrf' in interface_config: + self.cli_set(base + ['interface', interface, 'vrf', interface_config['vrf']]) + if 'segments' in interface_config: + self.cli_set(base + ['interface', interface, 'segments', interface_config['segments']]) + + if 'blackhole' in route_config: + self.cli_set(base + ['blackhole']) + if 'distance' in route_config['blackhole']: + self.cli_set(base + ['blackhole', 'distance', route_config['blackhole']['distance']]) + if 'tag' in route_config['blackhole']: + self.cli_set(base + ['blackhole', 'tag', route_config['blackhole']['tag']]) + + if 'reject' in route_config: + self.cli_set(base + ['reject']) + if 'distance' in route_config['reject']: + self.cli_set(base + ['reject', 'distance', route_config['reject']['distance']]) + if 'tag' in route_config['reject']: + self.cli_set(base + ['reject', 'tag', route_config['reject']['tag']]) + + if {'blackhole', 'reject'} <= set(route_config): + # Can not use blackhole and reject at the same time + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base + ['blackhole']) + self.cli_delete(base + ['reject']) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig('ip route', end='') + + # Verify routes + for route, route_config in routes.items(): + ip_ipv6 = 'ip' + if is_ipv6(route): + ip_ipv6 = 'ipv6' + + if 'next_hop' in route_config: + for next_hop, next_hop_config in route_config['next_hop'].items(): + tmp = f'{ip_ipv6} route {route} {next_hop}' + if 'interface' in next_hop_config: + tmp += ' ' + next_hop_config['interface'] + if 'distance' in next_hop_config: + tmp += ' ' + next_hop_config['distance'] + if 'vrf' in next_hop_config: + tmp += ' nexthop-vrf ' + next_hop_config['vrf'] + if 'bfd' in next_hop_config: + tmp += ' bfd profile ' + bfd_profile + if 'bfd_source' in next_hop_config: + tmp += ' bfd multi-hop source ' + next_hop_config['bfd_source'] + ' profile ' + bfd_profile + if 'segments' in next_hop_config: + tmp += ' segments ' + next_hop_config['segments'] + + if 'disable' in next_hop_config: + self.assertNotIn(tmp, frrconfig) + else: + self.assertIn(tmp, frrconfig) + + if 'interface' in route_config: + for interface, interface_config in route_config['interface'].items(): + tmp = f'{ip_ipv6} route {route} {interface}' + if 'interface' in interface_config: + tmp += ' ' + interface_config['interface'] + if 'distance' in interface_config: + tmp += ' ' + interface_config['distance'] + if 'vrf' in interface_config: + tmp += ' nexthop-vrf ' + interface_config['vrf'] + if 'segments' in interface_config: + tmp += ' segments ' + interface_config['segments'] + + if 'disable' in interface_config: + self.assertNotIn(tmp, frrconfig) + else: + self.assertIn(tmp, frrconfig) + + if {'blackhole', 'reject'} <= set(route_config): + # Can not use blackhole and reject at the same time + # Config error validated above - skip this route + continue + + if 'blackhole' in route_config: + tmp = f'{ip_ipv6} route {route} blackhole' + if 'tag' in route_config['blackhole']: + tmp += ' tag ' + route_config['blackhole']['tag'] + if 'distance' in route_config['blackhole']: + tmp += ' ' + route_config['blackhole']['distance'] + + self.assertIn(tmp, frrconfig) + + if 'reject' in route_config: + tmp = f'{ip_ipv6} route {route} reject' + if 'tag' in route_config['reject']: + tmp += ' tag ' + route_config['reject']['tag'] + if 'distance' in route_config['reject']: + tmp += ' ' + route_config['reject']['distance'] + + self.assertIn(tmp, frrconfig) + + def test_02_static_table(self): + for table in tables: + for route, route_config in routes.items(): + route_type = 'route' + if is_ipv6(route): + route_type = 'route6' + base = base_path + ['table', table, route_type, route] + + if 'next_hop' in route_config: + for next_hop, next_hop_config in route_config['next_hop'].items(): + self.cli_set(base + ['next-hop', next_hop]) + if 'disable' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'disable']) + if 'distance' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'distance', next_hop_config['distance']]) + if 'interface' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'interface', next_hop_config['interface']]) + if 'vrf' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'vrf', next_hop_config['vrf']]) + + + if 'interface' in route_config: + for interface, interface_config in route_config['interface'].items(): + self.cli_set(base + ['interface', interface]) + if 'disable' in interface_config: + self.cli_set(base + ['interface', interface, 'disable']) + if 'distance' in interface_config: + self.cli_set(base + ['interface', interface, 'distance', interface_config['distance']]) + if 'vrf' in interface_config: + self.cli_set(base + ['interface', interface, 'vrf', interface_config['vrf']]) + + if 'blackhole' in route_config: + self.cli_set(base + ['blackhole']) + if 'distance' in route_config['blackhole']: + self.cli_set(base + ['blackhole', 'distance', route_config['blackhole']['distance']]) + if 'tag' in route_config['blackhole']: + self.cli_set(base + ['blackhole', 'tag', route_config['blackhole']['tag']]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig('ip route', end='') + + for table in tables: + # Verify routes + for route, route_config in routes.items(): + ip_ipv6 = 'ip' + if is_ipv6(route): + ip_ipv6 = 'ipv6' + + if 'next_hop' in route_config: + for next_hop, next_hop_config in route_config['next_hop'].items(): + tmp = f'{ip_ipv6} route {route} {next_hop}' + if 'interface' in next_hop_config: + tmp += ' ' + next_hop_config['interface'] + if 'distance' in next_hop_config: + tmp += ' ' + next_hop_config['distance'] + if 'vrf' in next_hop_config: + tmp += ' nexthop-vrf ' + next_hop_config['vrf'] + + tmp += ' table ' + table + if 'disable' in next_hop_config: + self.assertNotIn(tmp, frrconfig) + else: + self.assertIn(tmp, frrconfig) + + if 'interface' in route_config: + for interface, interface_config in route_config['interface'].items(): + tmp = f'{ip_ipv6} route {route} {interface}' + if 'interface' in interface_config: + tmp += ' ' + interface_config['interface'] + if 'distance' in interface_config: + tmp += ' ' + interface_config['distance'] + if 'vrf' in interface_config: + tmp += ' nexthop-vrf ' + interface_config['vrf'] + + tmp += ' table ' + table + if 'disable' in interface_config: + self.assertNotIn(tmp, frrconfig) + else: + self.assertIn(tmp, frrconfig) + + if 'blackhole' in route_config: + tmp = f'{ip_ipv6} route {route} blackhole' + if 'tag' in route_config['blackhole']: + tmp += ' tag ' + route_config['blackhole']['tag'] + if 'distance' in route_config['blackhole']: + tmp += ' ' + route_config['blackhole']['distance'] + + tmp += ' table ' + table + self.assertIn(tmp, frrconfig) + + + def test_03_static_vrf(self): + # Create VRF instances and apply the static routes from above to FRR. + # Re-read the configured routes and match them if they are programmed + # properly. This also includes VRF leaking + vrfs = { + 'red' : { 'table' : '1000' }, + 'green' : { 'table' : '2000' }, + 'blue' : { 'table' : '3000' }, + } + + for vrf, vrf_config in vrfs.items(): + vrf_base_path = ['vrf', 'name', vrf] + self.cli_set(vrf_base_path + ['table', vrf_config['table']]) + + for route, route_config in routes.items(): + route_type = 'route' + if is_ipv6(route): + route_type = 'route6' + route_base_path = vrf_base_path + ['protocols', 'static', route_type, route] + + if 'next_hop' in route_config: + for next_hop, next_hop_config in route_config['next_hop'].items(): + self.cli_set(route_base_path + ['next-hop', next_hop]) + if 'disable' in next_hop_config: + self.cli_set(route_base_path + ['next-hop', next_hop, 'disable']) + if 'distance' in next_hop_config: + self.cli_set(route_base_path + ['next-hop', next_hop, 'distance', next_hop_config['distance']]) + if 'interface' in next_hop_config: + self.cli_set(route_base_path + ['next-hop', next_hop, 'interface', next_hop_config['interface']]) + if 'vrf' in next_hop_config: + self.cli_set(route_base_path + ['next-hop', next_hop, 'vrf', next_hop_config['vrf']]) + if 'segments' in next_hop_config: + self.cli_set(route_base_path + ['next-hop', next_hop, 'segments', next_hop_config['segments']]) + + if 'interface' in route_config: + for interface, interface_config in route_config['interface'].items(): + self.cli_set(route_base_path + ['interface', interface]) + if 'disable' in interface_config: + self.cli_set(route_base_path + ['interface', interface, 'disable']) + if 'distance' in interface_config: + self.cli_set(route_base_path + ['interface', interface, 'distance', interface_config['distance']]) + if 'vrf' in interface_config: + self.cli_set(route_base_path + ['interface', interface, 'vrf', interface_config['vrf']]) + if 'segments' in interface_config: + self.cli_set(route_base_path + ['interface', interface, 'segments', interface_config['segments']]) + + if 'blackhole' in route_config: + self.cli_set(route_base_path + ['blackhole']) + if 'distance' in route_config['blackhole']: + self.cli_set(route_base_path + ['blackhole', 'distance', route_config['blackhole']['distance']]) + if 'tag' in route_config['blackhole']: + self.cli_set(route_base_path + ['blackhole', 'tag', route_config['blackhole']['tag']]) + + # commit changes + self.cli_commit() + + for vrf, vrf_config in vrfs.items(): + tmp = get_interface_config(vrf) + + # Compare VRF table ID + self.assertEqual(get_vrf_tableid(vrf), int(vrf_config['table'])) + self.assertEqual(tmp['linkinfo']['info_kind'], 'vrf') + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'vrf {vrf}') + self.assertIn(f'vrf {vrf}', frrconfig) + + # Verify routes + for route, route_config in routes.items(): + ip_ipv6 = 'ip' + if is_ipv6(route): + ip_ipv6 = 'ipv6' + + if 'next_hop' in route_config: + for next_hop, next_hop_config in route_config['next_hop'].items(): + tmp = f'{ip_ipv6} route {route} {next_hop}' + if 'interface' in next_hop_config: + tmp += ' ' + next_hop_config['interface'] + if 'distance' in next_hop_config: + tmp += ' ' + next_hop_config['distance'] + if 'vrf' in next_hop_config: + tmp += ' nexthop-vrf ' + next_hop_config['vrf'] + if 'segments' in next_hop_config: + tmp += ' segments ' + next_hop_config['segments'] + + if 'disable' in next_hop_config: + self.assertNotIn(tmp, frrconfig) + else: + self.assertIn(tmp, frrconfig) + + if 'interface' in route_config: + for interface, interface_config in route_config['interface'].items(): + tmp = f'{ip_ipv6} route {route} {interface}' + if 'interface' in interface_config: + tmp += ' ' + interface_config['interface'] + if 'distance' in interface_config: + tmp += ' ' + interface_config['distance'] + if 'vrf' in interface_config: + tmp += ' nexthop-vrf ' + interface_config['vrf'] + if 'segments' in interface_config: + tmp += ' segments ' + interface_config['segments'] + + if 'disable' in interface_config: + self.assertNotIn(tmp, frrconfig) + else: + self.assertIn(tmp, frrconfig) + + if 'blackhole' in route_config: + tmp = f'{ip_ipv6} route {route} blackhole' + if 'tag' in route_config['blackhole']: + tmp += ' tag ' + route_config['blackhole']['tag'] + if 'distance' in route_config['blackhole']: + tmp += ' ' + route_config['blackhole']['distance'] + + self.assertIn(tmp, frrconfig) + +if __name__ == '__main__': + unittest.main(verbosity=2, failfast=True) diff --git a/smoketest/scripts/cli/test_protocols_static_arp.py b/smoketest/scripts/cli/test_protocols_static_arp.py new file mode 100644 index 0000000..7f80472 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_static_arp.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.utils.process import cmd + +base_path = ['protocols', 'static', 'arp'] +interface = 'eth0' +address = '192.0.2.1/24' + +class TestARP(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestARP, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + # we need a L2 interface with a L3 address to properly configure ARP entries + cls.cli_set(cls, ['interfaces', 'ethernet', interface, 'address', address]) + + @classmethod + def tearDownClass(cls): + # cleanuop L2 interface + cls.cli_delete(cls, ['interfaces', 'ethernet', interface, 'address', address]) + cls.cli_commit(cls) + + super(TestARP, cls).tearDownClass() + + def tearDown(self): + # delete test config + self.cli_delete(base_path) + self.cli_commit() + + def test_static_arp(self): + test_data = { + '192.0.2.10' : { 'mac' : '00:01:02:03:04:0a' }, + '192.0.2.11' : { 'mac' : '00:01:02:03:04:0b' }, + '192.0.2.12' : { 'mac' : '00:01:02:03:04:0c' }, + '192.0.2.13' : { 'mac' : '00:01:02:03:04:0d' }, + '192.0.2.14' : { 'mac' : '00:01:02:03:04:0e' }, + '192.0.2.15' : { 'mac' : '00:01:02:03:04:0f' }, + } + + for host, host_config in test_data.items(): + self.cli_set(base_path + ['interface', interface, 'address', host, 'mac', host_config['mac']]) + + self.cli_commit() + + arp_table = json.loads(cmd('ip -j -4 neigh show')) + for host, host_config in test_data.items(): + # As we search within a list of hosts we need to mark if it was + # found or not. This ensures all hosts from test_data are processed + found = False + for entry in arp_table: + # Other ARP entry - not related to this testcase + if entry['dst'] not in list(test_data): + continue + + if entry['dst'] == host: + self.assertEqual(entry['lladdr'], host_config['mac']) + self.assertEqual(entry['dev'], interface) + found = True + + if found == False: + print(entry) + self.assertTrue(found) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_static_multicast.py b/smoketest/scripts/cli/test_protocols_static_multicast.py new file mode 100644 index 0000000..9fdda23 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_static_multicast.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + + +base_path = ['protocols', 'static', 'multicast'] + + +class TestProtocolsStaticMulticast(VyOSUnitTestSHIM.TestCase): + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + mroute = self.getFRRconfig('ip mroute', end='') + self.assertFalse(mroute) + + def test_01_static_multicast(self): + + self.cli_set(base_path + ['route', '224.202.0.0/24', 'next-hop', '224.203.0.1']) + self.cli_set(base_path + ['interface-route', '224.203.0.0/24', 'next-hop-interface', 'eth0']) + + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig('ip mroute', end='') + + self.assertIn('ip mroute 224.202.0.0/24 224.203.0.1', frrconfig) + self.assertIn('ip mroute 224.203.0.0/24 eth0', frrconfig) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_qos.py b/smoketest/scripts/cli/test_qos.py new file mode 100644 index 0000000..b98c0e9 --- /dev/null +++ b/smoketest/scripts/cli/test_qos.py @@ -0,0 +1,859 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from json import loads +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import cmd + +base_path = ['qos'] + +def get_tc_qdisc_json(interface) -> dict: + tmp = cmd(f'tc -detail -json qdisc show dev {interface}') + tmp = loads(tmp) + return next(iter(tmp)) + +def get_tc_filter_json(interface, direction) -> list: + if direction not in ['ingress', 'egress']: + raise ValueError() + tmp = cmd(f'tc -detail -json filter show dev {interface} {direction}') + tmp = loads(tmp) + return tmp + +def get_tc_filter_details(interface, direction) -> list: + # json doesn't contain all params, such as mtu + if direction not in ['ingress', 'egress']: + raise ValueError() + tmp = cmd(f'tc -details filter show dev {interface} {direction}') + return tmp + +class TestQoS(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestQoS, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + # We only test on physical interfaces and not VLAN (sub-)interfaces + cls._interfaces = [] + if 'TEST_ETH' in os.environ: + tmp = os.environ['TEST_ETH'].split() + cls._interfaces = tmp + else: + for tmp in Section.interfaces('ethernet', vlan=False): + cls._interfaces.append(tmp) + + def tearDown(self): + # delete testing SSH config + self.cli_delete(base_path) + self.cli_commit() + + def test_01_cake(self): + bandwidth = 1000000 + rtt = 200 + + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', 'cake', policy_name, 'bandwidth', str(bandwidth)]) + self.cli_set(base_path + ['policy', 'cake', policy_name, 'rtt', str(rtt)]) + self.cli_set(base_path + ['policy', 'cake', policy_name, 'flow-isolation', 'dual-src-host']) + + bandwidth += 1000000 + rtt += 20 + + # commit changes + self.cli_commit() + + bandwidth = 1000000 + rtt = 200 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + + self.assertEqual('cake', tmp['kind']) + # TC store rates as a 32-bit unsigned integer in bps (Bytes per second) + self.assertEqual(int(bandwidth *125), tmp['options']['bandwidth']) + # RTT internally is in us + self.assertEqual(int(rtt *1000), tmp['options']['rtt']) + self.assertEqual('dual-srchost', tmp['options']['flowmode']) + self.assertFalse(tmp['options']['ingress']) + self.assertFalse(tmp['options']['nat']) + self.assertTrue(tmp['options']['raw']) + + bandwidth += 1000000 + rtt += 20 + + def test_02_drop_tail(self): + queue_limit = 50 + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', 'drop-tail', policy_name, 'queue-limit', str(queue_limit)]) + + queue_limit += 10 + + # commit changes + self.cli_commit() + + queue_limit = 50 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + + self.assertEqual('pfifo', tmp['kind']) + self.assertEqual(queue_limit, tmp['options']['limit']) + + queue_limit += 10 + + def test_03_fair_queue(self): + hash_interval = 10 + queue_limit = 5 + policy_type = 'fair-queue' + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'hash-interval', str(hash_interval)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'queue-limit', str(queue_limit)]) + + hash_interval += 1 + queue_limit += 1 + + # commit changes + self.cli_commit() + + hash_interval = 10 + queue_limit = 5 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + + self.assertEqual('sfq', tmp['kind']) + self.assertEqual(hash_interval, tmp['options']['perturb']) + self.assertEqual(queue_limit, tmp['options']['limit']) + + hash_interval += 1 + queue_limit += 1 + + def test_04_fq_codel(self): + policy_type = 'fq-codel' + codel_quantum = 1500 + flows = 512 + interval = 100 + queue_limit = 2048 + target = 5 + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'codel-quantum', str(codel_quantum)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'flows', str(flows)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'interval', str(interval)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'queue-limit', str(queue_limit)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'target', str(target)]) + + codel_quantum += 10 + flows += 2 + interval += 10 + queue_limit += 512 + target += 1 + + # commit changes + self.cli_commit() + + codel_quantum = 1500 + flows = 512 + interval = 100 + queue_limit = 2048 + target = 5 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + + self.assertEqual('fq_codel', tmp['kind']) + self.assertEqual(codel_quantum, tmp['options']['quantum']) + self.assertEqual(flows, tmp['options']['flows']) + self.assertEqual(queue_limit, tmp['options']['limit']) + + # due to internal rounding we need to substract 1 from interval and target after converting to milliseconds + # configuration of: + # tc qdisc add dev eth0 root fq_codel quantum 1500 flows 512 interval 100ms limit 2048 target 5ms noecn + # results in: tc -j qdisc show dev eth0 + # [{"kind":"fq_codel","handle":"8046:","root":true,"refcnt":3,"options":{"limit":2048,"flows":512, + # "quantum":1500,"target":4999,"interval":99999,"memory_limit":33554432,"drop_batch":64}}] + self.assertAlmostEqual(tmp['options']['interval'], interval *1000, delta=1) + self.assertAlmostEqual(tmp['options']['target'], target *1000 -1, delta=1) + + codel_quantum += 10 + flows += 2 + interval += 10 + queue_limit += 512 + target += 1 + + def test_05_limiter(self): + qos_config = { + '1' : { + 'bandwidth' : '3000000', + 'exceed' : 'pipe', + 'burst' : '100Kb', + 'mtu' : '1600', + 'not-exceed' : 'continue', + 'priority': '15', + 'match4' : { + 'ssh' : { 'dport' : '22', }, + }, + }, + '2' : { + 'bandwidth' : '1000000', + 'match6' : { + 'ssh' : { 'dport' : '22', }, + }, + }, + } + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'egress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # set default bandwidth parameter for all remaining connections + self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'bandwidth', '500000']) + self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'burst', '200kb']) + self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'exceed', 'drop']) + self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'mtu', '3000']) + self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'not-exceed', 'ok']) + + for qos_class, qos_class_config in qos_config.items(): + qos_class_base = base_path + ['policy', 'limiter', policy_name, 'class', qos_class] + + if 'match4' in qos_class_config: + for match, match_config in qos_class_config['match4'].items(): + if 'dport' in match_config: + self.cli_set(qos_class_base + ['match', match, 'ip', 'destination', 'port', match_config['dport']]) + + if 'match6' in qos_class_config: + for match, match_config in qos_class_config['match6'].items(): + if 'dport' in match_config: + self.cli_set(qos_class_base + ['match', match, 'ipv6', 'destination', 'port', match_config['dport']]) + + if 'bandwidth' in qos_class_config: + self.cli_set(qos_class_base + ['bandwidth', qos_class_config['bandwidth']]) + + if 'exceed' in qos_class_config: + self.cli_set(qos_class_base + ['exceed', qos_class_config['exceed']]) + + if 'not-exceed' in qos_class_config: + self.cli_set(qos_class_base + ['not-exceed', qos_class_config['not-exceed']]) + + if 'burst' in qos_class_config: + self.cli_set(qos_class_base + ['burst', qos_class_config['burst']]) + + if 'mtu' in qos_class_config: + self.cli_set(qos_class_base + ['mtu', qos_class_config['mtu']]) + + if 'priority' in qos_class_config: + self.cli_set(qos_class_base + ['priority', qos_class_config['priority']]) + + + # commit changes + self.cli_commit() + + for interface in self._interfaces: + for filter in get_tc_filter_json(interface, 'ingress'): + # bail out early if filter has no attached action + if 'options' not in filter or 'actions' not in filter['options']: + continue + + for qos_class, qos_class_config in qos_config.items(): + # Every flowid starts with ffff and we encopde the class number after the colon + if 'flowid' not in filter['options'] or filter['options']['flowid'] != f'ffff:{qos_class}': + continue + + ip_hdr_offset = 20 + if 'match6' in qos_class_config: + ip_hdr_offset = 40 + + self.assertEqual(ip_hdr_offset, filter['options']['match']['off']) + if 'dport' in match_config: + dport = int(match_config['dport']) + self.assertEqual(f'{dport:x}', filter['options']['match']['value']) + + tc_details = get_tc_filter_details(interface, 'ingress') + self.assertTrue('filter parent ffff: protocol all pref 20 u32 chain 0' in tc_details) + self.assertTrue('rate 1Gbit burst 15125b mtu 2Kb action drop overhead 0b linklayer ethernet' in tc_details) + self.assertTrue('filter parent ffff: protocol all pref 15 u32 chain 0' in tc_details) + self.assertTrue('rate 3Gbit burst 102000b mtu 1600b action pipe/continue overhead 0b linklayer ethernet' in tc_details) + self.assertTrue('rate 500Mbit burst 204687b mtu 3000b action drop overhead 0b linklayer ethernet' in tc_details) + self.assertTrue('filter parent ffff: protocol all pref 255 basic chain 0' in tc_details) + + def test_06_network_emulator(self): + policy_type = 'network-emulator' + + bandwidth = 1000000 + corruption = 1 + delay = 2 + duplicate = 3 + loss = 4 + queue_limit = 5 + reordering = 6 + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + + self.cli_set(base_path + ['policy', policy_type, policy_name, 'bandwidth', str(bandwidth)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'corruption', str(corruption)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'delay', str(delay)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'duplicate', str(duplicate)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'loss', str(loss)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'queue-limit', str(queue_limit)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'reordering', str(reordering)]) + + bandwidth += 1000000 + corruption += 1 + delay += 1 + duplicate +=1 + loss += 1 + queue_limit += 1 + reordering += 1 + + # commit changes + self.cli_commit() + + bandwidth = 1000000 + corruption = 1 + delay = 2 + duplicate = 3 + loss = 4 + queue_limit = 5 + reordering = 6 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + self.assertEqual('netem', tmp['kind']) + + self.assertEqual(int(bandwidth *125), tmp['options']['rate']['rate']) + # values are in % + self.assertEqual(corruption/100, tmp['options']['corrupt']['corrupt']) + self.assertEqual(duplicate/100, tmp['options']['duplicate']['duplicate']) + self.assertEqual(loss/100, tmp['options']['loss-random']['loss']) + self.assertEqual(reordering/100, tmp['options']['reorder']['reorder']) + self.assertEqual(delay/1000, tmp['options']['delay']['delay']) + + self.assertEqual(queue_limit, tmp['options']['limit']) + + bandwidth += 1000000 + corruption += 1 + delay += 1 + duplicate += 1 + loss += 1 + queue_limit += 1 + reordering += 1 + + def test_07_priority_queue(self): + priorities = ['1', '2', '3', '4', '5'] + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', 'priority-queue', policy_name, 'default', 'queue-limit', '10']) + + for priority in priorities: + prio_base = base_path + ['policy', 'priority-queue', policy_name, 'class', priority] + self.cli_set(prio_base + ['match', f'prio-{priority}', 'ip', 'destination', 'port', str(1000 + int(priority))]) + + # commit changes + self.cli_commit() + + def test_08_random_detect(self): + bandwidth = 5000 + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', 'random-detect', policy_name, 'bandwidth', str(bandwidth)]) + + bandwidth += 1000 + + # commit changes + self.cli_commit() + + bandwidth = 5000 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + self.assertTrue('gred' in tmp.get('kind')) + self.assertEqual(8, len(tmp.get('options', {}).get('vqs'))) + self.assertEqual(8, tmp.get('options', {}).get('dp_cnt')) + self.assertEqual(0, tmp.get('options', {}).get('dp_default')) + self.assertTrue(tmp.get('options', {}).get('grio')) + + def test_09_rate_control(self): + bandwidth = 5000 + burst = 20 + latency = 5 + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', 'rate-control', policy_name, 'bandwidth', str(bandwidth)]) + self.cli_set(base_path + ['policy', 'rate-control', policy_name, 'burst', str(burst)]) + self.cli_set(base_path + ['policy', 'rate-control', policy_name, 'latency', str(latency)]) + + bandwidth += 1000 + burst += 5 + latency += 1 + # commit changes + self.cli_commit() + + bandwidth = 5000 + burst = 20 + latency = 5 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + + self.assertEqual('tbf', tmp['kind']) + self.assertEqual(0, tmp['options']['mpu']) + # TC store rates as a 32-bit unsigned integer in bps (Bytes per second) + self.assertEqual(int(bandwidth * 125), tmp['options']['rate']) + + bandwidth += 1000 + burst += 5 + latency += 1 + + def test_10_round_robin(self): + qos_config = { + '1' : { + 'match4' : { + 'ssh' : { 'dport' : '22', }, + }, + }, + '2' : { + 'match6' : { + 'ssh' : { 'dport' : '22', }, + }, + }, + } + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + + for qos_class, qos_class_config in qos_config.items(): + qos_class_base = base_path + ['policy', 'round-robin', policy_name, 'class', qos_class] + + if 'match4' in qos_class_config: + for match, match_config in qos_class_config['match4'].items(): + if 'dport' in match_config: + self.cli_set(qos_class_base + ['match', match, 'ip', 'destination', 'port', match_config['dport']]) + + if 'match6' in qos_class_config: + for match, match_config in qos_class_config['match6'].items(): + if 'dport' in match_config: + self.cli_set(qos_class_base + ['match', match, 'ipv6', 'destination', 'port', match_config['dport']]) + + + # commit changes + self.cli_commit() + + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + self.assertEqual('drr', tmp['kind']) + + for filter in get_tc_filter_json(interface, 'ingress'): + # bail out early if filter has no attached action + if 'options' not in filter or 'actions' not in filter['options']: + continue + + for qos_class, qos_class_config in qos_config.items(): + # Every flowid starts with ffff and we encopde the class number after the colon + if 'flowid' not in filter['options'] or filter['options']['flowid'] != f'ffff:{qos_class}': + continue + + ip_hdr_offset = 20 + if 'match6' in qos_class_config: + ip_hdr_offset = 40 + + self.assertEqual(ip_hdr_offset, filter['options']['match']['off']) + if 'dport' in match_config: + dport = int(match_config['dport']) + self.assertEqual(f'{dport:x}', filter['options']['match']['value']) + + def test_11_shaper(self): + bandwidth = 250 + default_bandwidth = 20 + default_ceil = 30 + class_bandwidth = 50 + class_ceil = 80 + dst_address = '192.0.2.8/32' + + for interface in self._interfaces: + shaper_name = f'qos-shaper-{interface}' + + self.cli_set(base_path + ['interface', interface, 'egress', shaper_name]) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'bandwidth', f'{bandwidth}mbit']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'bandwidth', f'{default_bandwidth}mbit']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'ceiling', f'{default_ceil}mbit']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'queue-type', 'fair-queue']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'bandwidth', f'{class_bandwidth}mbit']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'ceiling', f'{class_ceil}mbit']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'match', '10', 'ip', 'destination', 'address', dst_address]) + + bandwidth += 1 + default_bandwidth += 1 + default_ceil += 1 + class_bandwidth += 1 + class_ceil += 1 + + # commit changes + self.cli_commit() + + bandwidth = 250 + default_bandwidth = 20 + default_ceil = 30 + class_bandwidth = 50 + class_ceil = 80 + + for interface in self._interfaces: + config_entries = ( + f'root rate {bandwidth}Mbit ceil {bandwidth}Mbit', + f'prio 0 rate {class_bandwidth}Mbit ceil {class_ceil}Mbit', + f'prio 7 rate {default_bandwidth}Mbit ceil {default_ceil}Mbit' + ) + + output = cmd(f'tc class show dev {interface}') + + for config_entry in config_entries: + self.assertIn(config_entry, output) + + bandwidth += 1 + default_bandwidth += 1 + default_ceil += 1 + class_bandwidth += 1 + class_ceil += 1 + + def test_12_shaper_with_red_queue(self): + bandwidth = 100 + default_bandwidth = 100 + default_burst = 100 + interface = self._interfaces[0] + class_bandwidth = 50 + dst_address = '192.0.2.8/32' + + shaper_name = f'qos-shaper-{interface}' + self.cli_set(base_path + ['interface', interface, 'egress', shaper_name]) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'bandwidth', f'{bandwidth}mbit']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'bandwidth', f'{default_bandwidth}%']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'burst', f'{default_burst}']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'queue-type', 'random-detect']) + + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '2', 'bandwidth', f'{class_bandwidth}mbit']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '2', 'match', '10', 'ip', 'destination', 'address', dst_address]) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '2', 'queue-type', 'random-detect']) + + # commit changes + self.cli_commit() + + # check root htb config + output = cmd(f'tc class show dev {interface}') + + config_entries = ( + f'prio 0 rate {class_bandwidth}Mbit ceil 50Mbit burst 15Kb', # specified class + f'prio 7 rate {default_bandwidth}Mbit ceil 100Mbit burst {default_burst}b', # default class + ) + for config_entry in config_entries: + self.assertIn(config_entry, output) + + output = cmd(f'tc -d qdisc show dev {interface}') + config_entries = ( + 'qdisc red', # use random detect + 'limit 72Kb min 9Kb max 18Kb ewma 3 probability 0.1', # default config for random detect + ) + for config_entry in config_entries: + self.assertIn(config_entry, output) + + # test random detect queue params + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'queue-limit', '1024']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'average-packet', '1024']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'maximum-threshold', '32']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'minimum-threshold', '16']) + + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '2', 'queue-limit', '1024']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '2', 'average-packet', '512']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '2', 'maximum-threshold', '32']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '2', 'minimum-threshold', '16']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '2', 'mark-probability', '20']) + + self.cli_commit() + + output = cmd(f'tc -d qdisc show dev {interface}') + config_entries = ( + 'qdisc red', # use random detect + 'limit 1Mb min 16Kb max 32Kb ewma 3 probability 0.1', # default config for random detect + 'limit 512Kb min 8Kb max 16Kb ewma 3 probability 0.05', # class config for random detect + ) + for config_entry in config_entries: + self.assertIn(config_entry, output) + + def test_13_shaper_delete_only_rule(self): + default_bandwidth = 100 + default_burst = 100 + interface = self._interfaces[0] + class_bandwidth = 50 + class_ceiling = 5 + src_address = '10.1.1.0/24' + + shaper_name = f'qos-shaper-{interface}' + self.cli_set(base_path + ['interface', interface, 'egress', shaper_name]) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'bandwidth', f'10mbit']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'bandwidth', f'{default_bandwidth}mbit']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'burst', f'{default_burst}']) + + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '30', 'bandwidth', f'{class_bandwidth}mbit']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '30', 'ceiling', f'{class_ceiling}mbit']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '30', 'match', 'ADDRESS30', 'ip', 'source', 'address', src_address]) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '30', 'match', 'ADDRESS30', 'description', 'smoketest']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '30', 'priority', '5']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '30', 'queue-type', 'fair-queue']) + + # commit changes + self.cli_commit() + # check root htb config + output = cmd(f'tc class show dev {interface}') + + config_entries = ( + f'prio 5 rate {class_bandwidth}Mbit ceil {class_ceiling}Mbit burst 15Kb', # specified class + f'prio 7 rate {default_bandwidth}Mbit ceil 100Mbit burst {default_burst}b', # default class + ) + for config_entry in config_entries: + self.assertIn(config_entry, output) + + self.assertTrue('' != cmd(f'tc filter show dev {interface}')) + # self.cli_delete(base_path + ['policy', 'shaper', shaper_name, 'class', '30', 'match', 'ADDRESS30']) + self.cli_delete(base_path + ['policy', 'shaper', shaper_name, 'class', '30', 'match', 'ADDRESS30', 'ip', 'source', 'address', src_address]) + self.cli_commit() + self.assertEqual('', cmd(f'tc filter show dev {interface}')) + + def test_14_policy_limiter_marked_traffic(self): + policy_name = 'smoke_test' + base_policy_path = ['qos', 'policy', 'limiter', policy_name] + + self.cli_set(['qos', 'interface', self._interfaces[0], 'ingress', policy_name]) + self.cli_set(base_policy_path + ['class', '100', 'bandwidth', '20gbit']) + self.cli_set(base_policy_path + ['class', '100', 'burst', '3760k']) + self.cli_set(base_policy_path + ['class', '100', 'match', 'INTERNAL', 'mark', '100']) + self.cli_set(base_policy_path + ['class', '100', 'priority', '20']) + self.cli_set(base_policy_path + ['default', 'bandwidth', '1gbit']) + self.cli_set(base_policy_path + ['default', 'burst', '125000000b']) + self.cli_commit() + + tc_filters = cmd(f'tc filter show dev {self._interfaces[0]} ingress') + # class 100 + self.assertIn('filter parent ffff: protocol all pref 20 fw chain 0', tc_filters) + self.assertIn('action order 1: police 0x1 rate 20Gbit burst 3847500b mtu 2Kb action drop overhead 0b', tc_filters) + # default + self.assertIn('filter parent ffff: protocol all pref 255 basic chain 0', tc_filters) + self.assertIn('action order 1: police 0x2 rate 1Gbit burst 125000000b mtu 2Kb action drop overhead 0b', tc_filters) + + def test_15_traffic_match_group(self): + interface = self._interfaces[0] + self.cli_set(['qos', 'interface', interface, 'egress', 'VyOS-HTB']) + base_policy_path = ['qos', 'policy', 'shaper', 'VyOS-HTB'] + + #old syntax + self.cli_set(base_policy_path + ['bandwidth', '100mbit']) + self.cli_set(base_policy_path + ['class', '10', 'bandwidth', '40%']) + self.cli_set(base_policy_path + ['class', '10', 'match', 'AF11', 'ip', 'dscp', 'AF11']) + self.cli_set(base_policy_path + ['class', '10', 'match', 'AF41', 'ip', 'dscp', 'AF41']) + self.cli_set(base_policy_path + ['class', '10', 'match', 'AF43', 'ip', 'dscp', 'AF43']) + self.cli_set(base_policy_path + ['class', '10', 'match', 'CS4', 'ip', 'dscp', 'CS4']) + self.cli_set(base_policy_path + ['class', '10', 'priority', '1']) + self.cli_set(base_policy_path + ['class', '10', 'queue-type', 'fair-queue']) + self.cli_set(base_policy_path + ['class', '20', 'bandwidth', '30%']) + self.cli_set(base_policy_path + ['class', '20', 'match', 'EF', 'ip', 'dscp', 'EF']) + self.cli_set(base_policy_path + ['class', '20', 'match', 'CS5', 'ip', 'dscp', 'CS5']) + self.cli_set(base_policy_path + ['class', '20', 'priority', '2']) + self.cli_set(base_policy_path + ['class', '20', 'queue-type', 'fair-queue']) + self.cli_set(base_policy_path + ['default', 'bandwidth', '20%']) + self.cli_set(base_policy_path + ['default', 'queue-type', 'fair-queue']) + self.cli_commit() + + tc_filters_old = cmd(f'tc -details filter show dev {interface}') + self.assertIn('match 00280000/00ff0000', tc_filters_old) + self.assertIn('match 00880000/00ff0000', tc_filters_old) + self.assertIn('match 00980000/00ff0000', tc_filters_old) + self.assertIn('match 00800000/00ff0000', tc_filters_old) + self.assertIn('match 00a00000/00ff0000', tc_filters_old) + self.assertIn('match 00b80000/00ff0000', tc_filters_old) + # delete config by old syntax + self.cli_delete(base_policy_path) + self.cli_delete(['qos', 'interface', interface, 'egress', 'VyOS-HTB']) + self.cli_commit() + self.assertEqual('', cmd(f'tc -s filter show dev {interface}')) + + self.cli_set(['qos', 'interface', interface, 'egress', 'VyOS-HTB']) + # prepare traffic match group + self.cli_set(['qos', 'traffic-match-group', 'VOICE', 'description', 'voice shaper']) + self.cli_set(['qos', 'traffic-match-group', 'VOICE', 'match', 'EF', 'ip', 'dscp', 'EF']) + self.cli_set(['qos', 'traffic-match-group', 'VOICE', 'match', 'CS5', 'ip', 'dscp', 'CS5']) + + self.cli_set(['qos', 'traffic-match-group', 'REAL_TIME_COMMON', 'description', 'real time common filters']) + self.cli_set(['qos', 'traffic-match-group', 'REAL_TIME_COMMON', 'match', 'AF43', 'ip', 'dscp', 'AF43']) + self.cli_set(['qos', 'traffic-match-group', 'REAL_TIME_COMMON', 'match', 'CS4', 'ip', 'dscp', 'CS4']) + + self.cli_set(['qos', 'traffic-match-group', 'REAL_TIME', 'description', 'real time shaper']) + self.cli_set(['qos', 'traffic-match-group', 'REAL_TIME', 'match', 'AF41', 'ip', 'dscp', 'AF41']) + self.cli_set(['qos', 'traffic-match-group', 'REAL_TIME', 'match-group', 'REAL_TIME_COMMON']) + + # new syntax + self.cli_set(base_policy_path + ['bandwidth', '100mbit']) + self.cli_set(base_policy_path + ['class', '10', 'bandwidth', '40%']) + self.cli_set(base_policy_path + ['class', '10', 'match', 'AF11', 'ip', 'dscp', 'AF11']) + self.cli_set(base_policy_path + ['class', '10', 'match-group', 'REAL_TIME']) + self.cli_set(base_policy_path + ['class', '10', 'priority', '1']) + self.cli_set(base_policy_path + ['class', '10', 'queue-type', 'fair-queue']) + self.cli_set(base_policy_path + ['class', '20', 'bandwidth', '30%']) + self.cli_set(base_policy_path + ['class', '20', 'match-group', 'VOICE']) + self.cli_set(base_policy_path + ['class', '20', 'priority', '2']) + self.cli_set(base_policy_path + ['class', '20', 'queue-type', 'fair-queue']) + self.cli_set(base_policy_path + ['default', 'bandwidth', '20%']) + self.cli_set(base_policy_path + ['default', 'queue-type', 'fair-queue']) + self.cli_commit() + + self.assertEqual(tc_filters_old, cmd(f'tc -details filter show dev {interface}')) + + def test_16_wrong_traffic_match_group(self): + interface = self._interfaces[0] + self.cli_set(['qos', 'interface', interface]) + + # Can not use both IPv6 and IPv4 in one match + self.cli_set(['qos', 'traffic-match-group', '1', 'match', 'one', 'ip', 'dscp', 'EF']) + self.cli_set(['qos', 'traffic-match-group', '1', 'match', 'one', 'ipv6', 'dscp', 'EF']) + with self.assertRaises(ConfigSessionError) as e: + self.cli_commit() + + # check contain itself, should commit success + self.cli_delete(['qos', 'traffic-match-group', '1', 'match', 'one', 'ipv6']) + self.cli_set(['qos', 'traffic-match-group', '1', 'match-group', '1']) + self.cli_commit() + + # check cycle dependency, should commit success + self.cli_set(['qos', 'traffic-match-group', '1', 'match-group', '3']) + self.cli_set(['qos', 'traffic-match-group', '2', 'match', 'one', 'ip', 'dscp', 'CS4']) + self.cli_set(['qos', 'traffic-match-group', '2', 'match-group', '1']) + + self.cli_set(['qos', 'traffic-match-group', '3', 'match', 'one', 'ipv6', 'dscp', 'CS4']) + self.cli_set(['qos', 'traffic-match-group', '3', 'match-group', '2']) + self.cli_commit() + + # inherit from non exist group, should commit success with warning + self.cli_set(['qos', 'traffic-match-group', '3', 'match-group', 'unexpected']) + self.cli_commit() + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_broadcast-relay.py b/smoketest/scripts/cli/test_service_broadcast-relay.py new file mode 100644 index 0000000..8790186 --- /dev/null +++ b/smoketest/scripts/cli/test_service_broadcast-relay.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from psutil import process_iter +from vyos.configsession import ConfigSessionError + +base_path = ['service', 'broadcast-relay'] + +class TestServiceBroadcastRelay(VyOSUnitTestSHIM.TestCase): + _address1 = '192.0.2.1/24' + _address2 = '192.0.2.1/24' + + def setUp(self): + self.cli_set(['interfaces', 'dummy', 'dum1001', 'address', self._address1]) + self.cli_set(['interfaces', 'dummy', 'dum1002', 'address', self._address2]) + + def tearDown(self): + self.cli_delete(['interfaces', 'dummy', 'dum1001']) + self.cli_delete(['interfaces', 'dummy', 'dum1002']) + self.cli_delete(base_path) + self.cli_commit() + + def test_broadcast_relay_service(self): + ids = range(1, 5) + for id in ids: + base = base_path + ['id', str(id)] + self.cli_set(base + ['description', 'vyos']) + self.cli_set(base + ['port', str(10000 + id)]) + + # check validate() - two interfaces must be present + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base + ['interface', 'dum1001']) + self.cli_set(base + ['interface', 'dum1002']) + self.cli_set(base + ['address', self._address1.split('/')[0]]) + + self.cli_commit() + + for id in ids: + # check if process is running + running = False + for p in process_iter(): + if "udp-broadcast-relay" in p.name(): + if p.cmdline()[3] == str(id): + running = True + break + self.assertTrue(running) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_dhcp-relay.py b/smoketest/scripts/cli/test_service_dhcp-relay.py new file mode 100644 index 0000000..59c4b59 --- /dev/null +++ b/smoketest/scripts/cli/test_service_dhcp-relay.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +PROCESS_NAME = 'dhcrelay' +RELAY_CONF = '/run/dhcp-relay/dhcrelay.conf' +base_path = ['service', 'dhcp-relay'] + +class TestServiceDHCPRelay(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_relay_default(self): + max_size = '800' + hop_count = '20' + agents_packets = 'append' + servers = ['192.0.2.1', '192.0.2.2'] + + self.cli_set(base_path + ['interface', 'lo']) + # check validate() - DHCP relay does not support the loopback interface + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', 'lo']) + + # activate DHCP relay on all ethernet interfaces + for tmp in Section.interfaces("ethernet"): + self.cli_set(base_path + ['interface', tmp]) + + # check validate() - No DHCP relay server(s) configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for server in servers: + self.cli_set(base_path + ['server', server]) + + self.cli_set(base_path + ['relay-options', 'max-size', max_size]) + self.cli_set(base_path + ['relay-options', 'hop-count', hop_count]) + self.cli_set(base_path + ['relay-options', 'relay-agents-packets', agents_packets]) + + # commit changes + self.cli_commit() + + # Check configured port + config = read_file(RELAY_CONF) + + # Test configured relay interfaces + for tmp in Section.interfaces("ethernet"): + self.assertIn(f'-i {tmp}', config) + + # Test relay servers + for server in servers: + self.assertIn(f' {server}', config) + + # Test max-size + self.assertIn(f'-A {max_size}', config) + # Hop count + self.assertIn(f'-c {hop_count}', config) + # relay-agents-packets + self.assertIn(f'-a -m {agents_packets}', config) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_relay_interfaces(self): + max_size = '800' + hop_count = '20' + agents_packets = 'append' + servers = ['192.0.2.1', '192.0.2.2'] + listen_iface = 'eth0' + up_iface = 'eth1' + + self.cli_set(base_path + ['interface', up_iface]) + self.cli_set(base_path + ['listen-interface', listen_iface]) + # check validate() - backward interface plus listen_interface + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface']) + + self.cli_set(base_path + ['upstream-interface', up_iface]) + + for server in servers: + self.cli_set(base_path + ['server', server]) + + # commit changes + self.cli_commit() + + # Check configured port + config = read_file(RELAY_CONF) + + # Test configured relay interfaces + self.assertIn(f'-id {listen_iface}', config) + self.assertIn(f'-iu {up_iface}', config) + + # Test relay servers + for server in servers: + self.assertIn(f' {server}', config) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + +if __name__ == '__main__': + unittest.main(verbosity=2) + diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py new file mode 100644 index 0000000..46c4e25 --- /dev/null +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -0,0 +1,830 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from json import loads + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.template import inc_ip +from vyos.template import dec_ip + +PROCESS_NAME = 'kea-dhcp4' +CTRL_PROCESS_NAME = 'kea-ctrl-agent' +KEA4_CONF = '/run/kea/kea-dhcp4.conf' +KEA4_CTRL = '/run/kea/dhcp4-ctrl-socket' +base_path = ['service', 'dhcp-server'] +interface = 'dum8765' +subnet = '192.0.2.0/25' +router = inc_ip(subnet, 1) +dns_1 = inc_ip(subnet, 2) +dns_2 = inc_ip(subnet, 3) +domain_name = 'vyos.net' + +class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestServiceDHCPServer, cls).setUpClass() + # Clear out current configuration to allow running this test on a live system + cls.cli_delete(cls, base_path) + + cidr_mask = subnet.split('/')[-1] + cls.cli_set(cls, ['interfaces', 'dummy', interface, 'address', f'{router}/{cidr_mask}']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'dummy', interface]) + super(TestServiceDHCPServer, cls).tearDownClass() + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def walk_path(self, obj, path): + current = obj + + for i, key in enumerate(path): + if isinstance(key, str): + self.assertTrue(isinstance(current, dict), msg=f'Failed path: {path}') + self.assertTrue(key in current, msg=f'Failed path: {path}') + elif isinstance(key, int): + self.assertTrue(isinstance(current, list), msg=f'Failed path: {path}') + self.assertTrue(0 <= key < len(current), msg=f'Failed path: {path}') + else: + assert False, "Invalid type" + + current = current[key] + + return current + + def verify_config_object(self, obj, path, value): + base_obj = self.walk_path(obj, path) + self.assertTrue(isinstance(base_obj, list)) + self.assertTrue(any(True for v in base_obj if v == value)) + + def verify_config_value(self, obj, path, key, value): + base_obj = self.walk_path(obj, path) + if isinstance(base_obj, list): + self.assertTrue(any(True for v in base_obj if key in v and v[key] == value)) + elif isinstance(base_obj, dict): + self.assertTrue(key in base_obj) + self.assertEqual(base_obj[key], value) + + def test_dhcp_single_pool_range(self): + shared_net_name = 'SMOKE-1' + + range_0_start = inc_ip(subnet, 10) + range_0_stop = inc_ip(subnet, 20) + range_1_start = inc_ip(subnet, 40) + range_1_stop = inc_ip(subnet, 50) + + self.cli_set(base_path + ['listen-interface', interface]) + + pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] + self.cli_set(pool + ['subnet-id', '1']) + self.cli_set(pool + ['ignore-client-id']) + # we use the first subnet IP address as default gateway + self.cli_set(pool + ['option', 'default-router', router]) + self.cli_set(pool + ['option', 'name-server', dns_1]) + self.cli_set(pool + ['option', 'name-server', dns_2]) + self.cli_set(pool + ['option', 'domain-name', domain_name]) + + # check validate() - No DHCP address range or active static-mapping set + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) + self.cli_set(pool + ['range', '1', 'start', range_1_start]) + self.cli_set(pool + ['range', '1', 'stop', range_1_stop]) + + # commit changes + self.cli_commit() + + config = read_file(KEA4_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp4', 'interfaces-config'], 'interfaces', [interface]) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'id', 1) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'match-client-id', False) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400) + + # Verify options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'domain-name', 'data': domain_name}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': router}) + + # Verify pools + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start} - {range_0_stop}'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_1_start} - {range_1_stop}'}) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_dhcp_single_pool_options(self): + shared_net_name = 'SMOKE-0815' + + range_0_start = inc_ip(subnet, 10) + range_0_stop = inc_ip(subnet, 20) + smtp_server = '1.2.3.4' + time_server = '4.3.2.1' + tftp_server = 'tftp.vyos.io' + search_domains = ['foo.vyos.net', 'bar.vyos.net'] + bootfile_name = 'vyos' + bootfile_server = '192.0.2.1' + wpad = 'http://wpad.vyos.io/foo/bar' + server_identifier = bootfile_server + ipv6_only_preferred = '300' + + pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] + self.cli_set(pool + ['subnet-id', '1']) + # we use the first subnet IP address as default gateway + self.cli_set(pool + ['option', 'default-router', router]) + self.cli_set(pool + ['option', 'name-server', dns_1]) + self.cli_set(pool + ['option', 'name-server', dns_2]) + self.cli_set(pool + ['option', 'domain-name', domain_name]) + self.cli_set(pool + ['option', 'ip-forwarding']) + self.cli_set(pool + ['option', 'smtp-server', smtp_server]) + self.cli_set(pool + ['option', 'pop-server', smtp_server]) + self.cli_set(pool + ['option', 'time-server', time_server]) + self.cli_set(pool + ['option', 'tftp-server-name', tftp_server]) + for search in search_domains: + self.cli_set(pool + ['option', 'domain-search', search]) + self.cli_set(pool + ['option', 'bootfile-name', bootfile_name]) + self.cli_set(pool + ['option', 'bootfile-server', bootfile_server]) + self.cli_set(pool + ['option', 'wpad-url', wpad]) + self.cli_set(pool + ['option', 'server-identifier', server_identifier]) + + self.cli_set(pool + ['option', 'static-route', '10.0.0.0/24', 'next-hop', '192.0.2.1']) + self.cli_set(pool + ['option', 'ipv6-only-preferred', ipv6_only_preferred]) + self.cli_set(pool + ['option', 'time-zone', 'Europe/London']) + + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) + + # commit changes + self.cli_commit() + + config = read_file(KEA4_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'boot-file-name', bootfile_name) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'next-server', bootfile_server) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400) + + # Verify options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'domain-name', 'data': domain_name}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'domain-search', 'data': ', '.join(search_domains)}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'pop-server', 'data': smtp_server}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'smtp-server', 'data': smtp_server}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'time-servers', 'data': time_server}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': router}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'dhcp-server-identifier', 'data': server_identifier}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'tftp-server-name', 'data': tftp_server}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'wpad-url', 'data': wpad}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'rfc3442-static-route', 'data': '24,10,0,0,192,0,2,1, 0,192,0,2,1'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'windows-static-route', 'data': '24,10,0,0,192,0,2,1'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'v6-only-preferred', 'data': ipv6_only_preferred}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'ip-forwarding', 'data': "true"}) + + # Time zone + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'pcode', 'data': 'GMT0BST,M3.5.0/1,M10.5.0'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'tcode', 'data': 'Europe/London'}) + + # Verify pools + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start} - {range_0_stop}'}) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_dhcp_single_pool_options_scoped(self): + shared_net_name = 'SMOKE-2' + + range_0_start = inc_ip(subnet, 10) + range_0_stop = inc_ip(subnet, 20) + + range_router = inc_ip(subnet, 5) + range_dns_1 = inc_ip(subnet, 6) + range_dns_2 = inc_ip(subnet, 7) + + shared_network = base_path + ['shared-network-name', shared_net_name] + pool = shared_network + ['subnet', subnet] + + self.cli_set(pool + ['subnet-id', '1']) + + # we use the first subnet IP address as default gateway + self.cli_set(shared_network + ['option', 'default-router', router]) + self.cli_set(shared_network + ['option', 'name-server', dns_1]) + self.cli_set(shared_network + ['option', 'name-server', dns_2]) + self.cli_set(shared_network + ['option', 'domain-name', domain_name]) + + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) + self.cli_set(pool + ['range', '0', 'option', 'default-router', range_router]) + self.cli_set(pool + ['range', '0', 'option', 'name-server', range_dns_1]) + self.cli_set(pool + ['range', '0', 'option', 'name-server', range_dns_2]) + + # commit changes + self.cli_commit() + + config = read_file(KEA4_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400) + + # Verify shared-network options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'option-data'], + {'name': 'domain-name', 'data': domain_name}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'option-data'], + {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'option-data'], + {'name': 'routers', 'data': router}) + + # Verify range options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools', 0, 'option-data'], + {'name': 'domain-name-servers', 'data': f'{range_dns_1}, {range_dns_2}'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools', 0, 'option-data'], + {'name': 'routers', 'data': range_router}) + + # Verify pool + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], 'pool', f'{range_0_start} - {range_0_stop}') + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_dhcp_single_pool_static_mapping(self): + shared_net_name = 'SMOKE-2' + domain_name = 'private' + + pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] + self.cli_set(pool + ['subnet-id', '1']) + # we use the first subnet IP address as default gateway + self.cli_set(pool + ['option', 'default-router', router]) + self.cli_set(pool + ['option', 'name-server', dns_1]) + self.cli_set(pool + ['option', 'name-server', dns_2]) + self.cli_set(pool + ['option', 'domain-name', domain_name]) + + # check validate() - No DHCP address range or active static-mapping set + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + client_base = 10 + for client in ['client1', 'client2', 'client3']: + mac = '00:50:00:00:00:{}'.format(client_base) + self.cli_set(pool + ['static-mapping', client, 'mac', mac]) + self.cli_set(pool + ['static-mapping', client, 'ip-address', inc_ip(subnet, client_base)]) + client_base += 1 + + # cannot have both mac-address and duid set + with self.assertRaises(ConfigSessionError): + self.cli_set(pool + ['static-mapping', 'client1', 'duid', '00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:11']) + self.cli_commit() + self.cli_delete(pool + ['static-mapping', 'client1', 'duid']) + + # cannot have mappings with duplicate IP addresses + self.cli_set(pool + ['static-mapping', 'dupe1', 'mac', '00:50:00:00:fe:ff']) + self.cli_set(pool + ['static-mapping', 'dupe1', 'ip-address', inc_ip(subnet, 10)]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + # Should allow disabled duplicate + self.cli_set(pool + ['static-mapping', 'dupe1', 'disable']) + self.cli_commit() + self.cli_delete(pool + ['static-mapping', 'dupe1']) + + # cannot have mappings with duplicate MAC addresses + self.cli_set(pool + ['static-mapping', 'dupe2', 'mac', '00:50:00:00:00:10']) + self.cli_set(pool + ['static-mapping', 'dupe2', 'ip-address', inc_ip(subnet, 120)]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(pool + ['static-mapping', 'dupe2']) + + + # cannot have mappings with duplicate MAC addresses + self.cli_set(pool + ['static-mapping', 'dupe3', 'duid', '00:01:02:03:04:05:06:07:aa:aa:aa:aa:aa:01']) + self.cli_set(pool + ['static-mapping', 'dupe3', 'ip-address', inc_ip(subnet, 121)]) + self.cli_set(pool + ['static-mapping', 'dupe4', 'duid', '00:01:02:03:04:05:06:07:aa:aa:aa:aa:aa:01']) + self.cli_set(pool + ['static-mapping', 'dupe4', 'ip-address', inc_ip(subnet, 121)]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(pool + ['static-mapping', 'dupe3']) + self.cli_delete(pool + ['static-mapping', 'dupe4']) + + # commit changes + self.cli_commit() + + config = read_file(KEA4_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'id', 1) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400) + + # Verify options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'domain-name', 'data': domain_name}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': router}) + + client_base = 10 + for client in ['client1', 'client2', 'client3']: + mac = '00:50:00:00:00:{}'.format(client_base) + ip = inc_ip(subnet, client_base) + + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'reservations'], + {'hostname': client, 'hw-address': mac, 'ip-address': ip}) + + client_base += 1 + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_dhcp_multiple_pools(self): + lease_time = '14400' + + for network in ['0', '1', '2', '3']: + shared_net_name = f'VyOS-SMOKETEST-{network}' + subnet = f'192.0.{network}.0/24' + router = inc_ip(subnet, 1) + dns_1 = inc_ip(subnet, 2) + + range_0_start = inc_ip(subnet, 10) + range_0_stop = inc_ip(subnet, 20) + range_1_start = inc_ip(subnet, 30) + range_1_stop = inc_ip(subnet, 40) + + pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] + self.cli_set(pool + ['subnet-id', str(int(network) + 1)]) + # we use the first subnet IP address as default gateway + self.cli_set(pool + ['option', 'default-router', router]) + self.cli_set(pool + ['option', 'name-server', dns_1]) + self.cli_set(pool + ['option', 'domain-name', domain_name]) + self.cli_set(pool + ['lease', lease_time]) + + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) + self.cli_set(pool + ['range', '1', 'start', range_1_start]) + self.cli_set(pool + ['range', '1', 'stop', range_1_stop]) + + client_base = 60 + for client in ['client1', 'client2', 'client3', 'client4']: + mac = '02:50:00:00:00:{}'.format(client_base) + self.cli_set(pool + ['static-mapping', client, 'mac', mac]) + self.cli_set(pool + ['static-mapping', client, 'ip-address', inc_ip(subnet, client_base)]) + client_base += 1 + + # commit changes + self.cli_commit() + + config = read_file(KEA4_CONF) + obj = loads(config) + + for network in ['0', '1', '2', '3']: + shared_net_name = f'VyOS-SMOKETEST-{network}' + subnet = f'192.0.{network}.0/24' + router = inc_ip(subnet, 1) + dns_1 = inc_ip(subnet, 2) + + range_0_start = inc_ip(subnet, 10) + range_0_stop = inc_ip(subnet, 20) + range_1_start = inc_ip(subnet, 30) + range_1_stop = inc_ip(subnet, 40) + + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'subnet', subnet) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'id', int(network) + 1) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'valid-lifetime', int(lease_time)) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'max-valid-lifetime', int(lease_time)) + + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'option-data'], + {'name': 'domain-name', 'data': domain_name}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'option-data'], + {'name': 'domain-name-servers', 'data': dns_1}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': router}) + + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start} - {range_0_stop}'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'pools'], + {'pool': f'{range_1_start} - {range_1_stop}'}) + + client_base = 60 + for client in ['client1', 'client2', 'client3', 'client4']: + mac = '02:50:00:00:00:{}'.format(client_base) + ip = inc_ip(subnet, client_base) + + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'reservations'], + {'hostname': client, 'hw-address': mac, 'ip-address': ip}) + + client_base += 1 + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_dhcp_exclude_not_in_range(self): + # T3180: verify else path when slicing DHCP ranges and exclude address + # is not part of the DHCP range + range_0_start = inc_ip(subnet, 10) + range_0_stop = inc_ip(subnet, 20) + + pool = base_path + ['shared-network-name', 'EXCLUDE-TEST', 'subnet', subnet] + self.cli_set(pool + ['subnet-id', '1']) + self.cli_set(pool + ['option', 'default-router', router]) + self.cli_set(pool + ['exclude', router]) + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) + + # commit changes + self.cli_commit() + + config = read_file(KEA4_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', 'EXCLUDE-TEST') + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) + + # Verify options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': router}) + + # Verify pools + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start} - {range_0_stop}'}) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_dhcp_exclude_in_range(self): + # T3180: verify else path when slicing DHCP ranges and exclude address + # is not part of the DHCP range + range_0_start = inc_ip(subnet, 10) + range_0_stop = inc_ip(subnet, 100) + + # the DHCP exclude addresse is blanked out of the range which is done + # by slicing one range into two ranges + exclude_addr = inc_ip(range_0_start, 20) + range_0_stop_excl = dec_ip(exclude_addr, 1) + range_0_start_excl = inc_ip(exclude_addr, 1) + + pool = base_path + ['shared-network-name', 'EXCLUDE-TEST-2', 'subnet', subnet] + self.cli_set(pool + ['subnet-id', '1']) + self.cli_set(pool + ['option', 'default-router', router]) + self.cli_set(pool + ['exclude', exclude_addr]) + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) + + # commit changes + self.cli_commit() + + config = read_file(KEA4_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', 'EXCLUDE-TEST-2') + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) + + # Verify options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': router}) + + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start} - {range_0_stop_excl}'}) + + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start_excl} - {range_0_stop}'}) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_dhcp_relay_server(self): + # Listen on specific address and return DHCP leases from a non + # directly connected pool + self.cli_set(base_path + ['listen-address', router]) + + relay_subnet = '10.0.0.0/16' + relay_router = inc_ip(relay_subnet, 1) + + range_0_start = '10.0.1.0' + range_0_stop = '10.0.250.255' + + pool = base_path + ['shared-network-name', 'RELAY', 'subnet', relay_subnet] + self.cli_set(pool + ['subnet-id', '1']) + self.cli_set(pool + ['option', 'default-router', relay_router]) + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) + + # commit changes + self.cli_commit() + + config = read_file(KEA4_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp4', 'interfaces-config'], 'interfaces', [f'{interface}/{router}']) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', 'RELAY') + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', relay_subnet) + + # Verify options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': relay_router}) + + # Verify pools + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start} - {range_0_stop}'}) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_dhcp_high_availability(self): + shared_net_name = 'FAILOVER' + failover_name = 'VyOS-Failover' + + range_0_start = inc_ip(subnet, 10) + range_0_stop = inc_ip(subnet, 20) + + pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] + self.cli_set(pool + ['subnet-id', '1']) + # we use the first subnet IP address as default gateway + self.cli_set(pool + ['option', 'default-router', router]) + + # check validate() - No DHCP address range or active static-mapping set + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) + + # failover + failover_local = router + failover_remote = inc_ip(router, 1) + + self.cli_set(base_path + ['high-availability', 'source-address', failover_local]) + self.cli_set(base_path + ['high-availability', 'name', failover_name]) + self.cli_set(base_path + ['high-availability', 'remote', failover_remote]) + self.cli_set(base_path + ['high-availability', 'status', 'primary']) + ## No mode defined -> its active-active mode by default + + # commit changes + self.cli_commit() + + config = read_file(KEA4_CONF) + obj = loads(config) + + # Verify failover + self.verify_config_value(obj, ['Dhcp4', 'control-socket'], 'socket-name', KEA4_CTRL) + + self.verify_config_object( + obj, + ['Dhcp4', 'hooks-libraries', 0, 'parameters', 'high-availability', 0, 'peers'], + {'name': os.uname()[1], 'url': f'http://{failover_local}:647/', 'role': 'primary', 'auto-failover': True}) + + self.verify_config_object( + obj, + ['Dhcp4', 'hooks-libraries', 0, 'parameters', 'high-availability', 0, 'peers'], + {'name': failover_name, 'url': f'http://{failover_remote}:647/', 'role': 'secondary', 'auto-failover': True}) + + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) + + # Verify options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': router}) + + # Verify pools + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start} - {range_0_stop}'}) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + self.assertTrue(process_named_running(CTRL_PROCESS_NAME)) + + def test_dhcp_high_availability_standby(self): + shared_net_name = 'FAILOVER' + failover_name = 'VyOS-Failover' + + range_0_start = inc_ip(subnet, 10) + range_0_stop = inc_ip(subnet, 20) + + pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] + self.cli_set(pool + ['subnet-id', '1']) + # we use the first subnet IP address as default gateway + self.cli_set(pool + ['option', 'default-router', router]) + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) + + # failover + failover_local = router + failover_remote = inc_ip(router, 1) + + self.cli_set(base_path + ['high-availability', 'source-address', failover_local]) + self.cli_set(base_path + ['high-availability', 'name', failover_name]) + self.cli_set(base_path + ['high-availability', 'remote', failover_remote]) + self.cli_set(base_path + ['high-availability', 'status', 'secondary']) + self.cli_set(base_path + ['high-availability', 'mode', 'active-passive']) + + # commit changes + self.cli_commit() + + config = read_file(KEA4_CONF) + obj = loads(config) + + # Verify failover + self.verify_config_value(obj, ['Dhcp4', 'control-socket'], 'socket-name', KEA4_CTRL) + + self.verify_config_object( + obj, + ['Dhcp4', 'hooks-libraries', 0, 'parameters', 'high-availability', 0, 'peers'], + {'name': os.uname()[1], 'url': f'http://{failover_local}:647/', 'role': 'standby', 'auto-failover': True}) + + self.verify_config_object( + obj, + ['Dhcp4', 'hooks-libraries', 0, 'parameters', 'high-availability', 0, 'peers'], + {'name': failover_name, 'url': f'http://{failover_remote}:647/', 'role': 'primary', 'auto-failover': True}) + + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) + + # Verify options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': router}) + + # Verify pools + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start} - {range_0_stop}'}) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + self.assertTrue(process_named_running(CTRL_PROCESS_NAME)) + + def test_dhcp_on_interface_with_vrf(self): + self.cli_set(['interfaces', 'ethernet', 'eth1', 'address', '10.1.1.1/30']) + self.cli_set(['interfaces', 'ethernet', 'eth1', 'vrf', 'SMOKE-DHCP']) + self.cli_set(['protocols', 'static', 'route', '10.1.10.0/24', 'interface', 'eth1', 'vrf', 'SMOKE-DHCP']) + self.cli_set(['vrf', 'name', 'SMOKE-DHCP', 'protocols', 'static', 'route', '10.1.10.0/24', 'next-hop', '10.1.1.2']) + self.cli_set(['vrf', 'name', 'SMOKE-DHCP', 'table', '1000']) + self.cli_set(base_path + ['shared-network-name', 'SMOKE-DHCP-NETWORK', 'subnet', '10.1.10.0/24', 'subnet-id', '1']) + self.cli_set(base_path + ['shared-network-name', 'SMOKE-DHCP-NETWORK', 'subnet', '10.1.10.0/24', 'option', 'default-router', '10.1.10.1']) + self.cli_set(base_path + ['shared-network-name', 'SMOKE-DHCP-NETWORK', 'subnet', '10.1.10.0/24', 'option', 'name-server', '1.1.1.1']) + self.cli_set(base_path + ['shared-network-name', 'SMOKE-DHCP-NETWORK', 'subnet', '10.1.10.0/24', 'range', '1', 'start', '10.1.10.10']) + self.cli_set(base_path + ['shared-network-name', 'SMOKE-DHCP-NETWORK', 'subnet', '10.1.10.0/24', 'range', '1', 'stop', '10.1.10.20']) + self.cli_set(base_path + ['listen-address', '10.1.1.1']) + self.cli_commit() + + config = read_file(KEA4_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp4', 'interfaces-config'], 'interfaces', ['eth1/10.1.1.1']) + + self.cli_delete(['interfaces', 'ethernet', 'eth1', 'vrf', 'SMOKE-DHCP']) + self.cli_delete(['protocols', 'static', 'route', '10.1.10.0/24', 'interface', 'eth1', 'vrf']) + self.cli_delete(['vrf', 'name', 'SMOKE-DHCP']) + self.cli_commit() + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_dhcpv6-relay.py b/smoketest/scripts/cli/test_service_dhcpv6-relay.py new file mode 100644 index 0000000..e634a01 --- /dev/null +++ b/smoketest/scripts/cli/test_service_dhcpv6-relay.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +PROCESS_NAME = 'dhcrelay' +RELAY_CONF = '/run/dhcp-relay/dhcrelay6.conf' +base_path = ['service', 'dhcpv6-relay'] + +upstream_if = 'eth0' +upstream_if_addr = '2001:db8::1/64' +listen_addr = '2001:db8:ffff::1/64' +interfaces = [] + +class TestServiceDHCPv6Relay(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestServiceDHCPv6Relay, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + for tmp in Section.interfaces('ethernet', vlan=False): + interfaces.append(tmp) + listen = listen_addr + if tmp == upstream_if: + listen = upstream_if_addr + cls.cli_set(cls, ['interfaces', 'ethernet', tmp, 'address', listen]) + + @classmethod + def tearDownClass(cls): + for tmp in interfaces: + listen = listen_addr + if tmp == upstream_if: + listen = upstream_if_addr + cls.cli_delete(cls, ['interfaces', 'ethernet', tmp, 'address', listen]) + + super(TestServiceDHCPv6Relay, cls).tearDownClass() + + def test_relay_default(self): + dhcpv6_server = '2001:db8::ffff' + hop_count = '20' + + self.cli_set(base_path + ['use-interface-id-option']) + self.cli_set(base_path + ['max-hop-count', hop_count]) + + # check validate() - Must set at least one listen and upstream + # interface addresses. + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['upstream-interface', upstream_if, 'address', dhcpv6_server]) + + # check validate() - Must set at least one listen and upstream + # interface addresses. + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # add listener on all ethernet interfaces except the upstream interface + for tmp in interfaces: + if tmp == upstream_if: + continue + self.cli_set(base_path + ['listen-interface', tmp, 'address', listen_addr.split('/')[0]]) + + # commit changes + self.cli_commit() + + # Check configured port + config = read_file(RELAY_CONF) + + # Test configured upstream interfaces + self.assertIn(f'-u {dhcpv6_server}%{upstream_if}', config) + + # Check listener on all ethernet interfaces + for tmp in interfaces: + if tmp == upstream_if: + continue + addr = listen_addr.split('/')[0] + self.assertIn(f'-l {addr}%{tmp}', config) + + # Check hop count + self.assertIn(f'-c {hop_count}', config) + # Check Interface ID option + self.assertIn('-I', config) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py new file mode 100644 index 0000000..6ecf6c1 --- /dev/null +++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from json import loads + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.template import inc_ip +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +PROCESS_NAME = 'kea-dhcp6' +KEA6_CONF = '/run/kea/kea-dhcp6.conf' +base_path = ['service', 'dhcpv6-server'] + +subnet = '2001:db8:f00::/64' +dns_1 = '2001:db8::1' +dns_2 = '2001:db8::2' +domain = 'vyos.net' +nis_servers = ['2001:db8:ffff::1', '2001:db8:ffff::2'] +interface = 'eth0' +interface_addr = inc_ip(subnet, 1) + '/64' + +class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestServiceDHCPv6Server, cls).setUpClass() + # Clear out current configuration to allow running this test on a live system + cls.cli_delete(cls, base_path) + + cls.cli_set(cls, ['interfaces', 'ethernet', interface, 'address', interface_addr]) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'ethernet', interface, 'address', interface_addr]) + cls.cli_commit(cls) + + super(TestServiceDHCPv6Server, cls).tearDownClass() + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def walk_path(self, obj, path): + current = obj + + for i, key in enumerate(path): + if isinstance(key, str): + self.assertTrue(isinstance(current, dict), msg=f'Failed path: {path}') + self.assertTrue(key in current, msg=f'Failed path: {path}') + elif isinstance(key, int): + self.assertTrue(isinstance(current, list), msg=f'Failed path: {path}') + self.assertTrue(0 <= key < len(current), msg=f'Failed path: {path}') + else: + assert False, "Invalid type" + + current = current[key] + + return current + + def verify_config_object(self, obj, path, value): + base_obj = self.walk_path(obj, path) + self.assertTrue(isinstance(base_obj, list)) + self.assertTrue(any(True for v in base_obj if v == value)) + + def verify_config_value(self, obj, path, key, value): + base_obj = self.walk_path(obj, path) + if isinstance(base_obj, list): + self.assertTrue(any(True for v in base_obj if key in v and v[key] == value)) + elif isinstance(base_obj, dict): + self.assertTrue(key in base_obj) + self.assertEqual(base_obj[key], value) + + def test_single_pool(self): + shared_net_name = 'SMOKE-1' + search_domains = ['foo.vyos.net', 'bar.vyos.net'] + lease_time = '1200' + max_lease_time = '72000' + min_lease_time = '600' + preference = '10' + sip_server = 'sip.vyos.net' + sntp_server = inc_ip(subnet, 100) + range_start = inc_ip(subnet, 256) # ::100 + range_stop = inc_ip(subnet, 65535) # ::ffff + + pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] + + self.cli_set(base_path + ['preference', preference]) + self.cli_set(pool + ['interface', interface]) + self.cli_set(pool + ['subnet-id', '1']) + # we use the first subnet IP address as default gateway + self.cli_set(pool + ['lease-time', 'default', lease_time]) + self.cli_set(pool + ['lease-time', 'maximum', max_lease_time]) + self.cli_set(pool + ['lease-time', 'minimum', min_lease_time]) + self.cli_set(pool + ['option', 'name-server', dns_1]) + self.cli_set(pool + ['option', 'name-server', dns_2]) + self.cli_set(pool + ['option', 'name-server', dns_2]) + self.cli_set(pool + ['option', 'nis-domain', domain]) + self.cli_set(pool + ['option', 'nisplus-domain', domain]) + self.cli_set(pool + ['option', 'sip-server', sip_server]) + self.cli_set(pool + ['option', 'sntp-server', sntp_server]) + self.cli_set(pool + ['range', '1', 'start', range_start]) + self.cli_set(pool + ['range', '1', 'stop', range_stop]) + + for server in nis_servers: + self.cli_set(pool + ['option', 'nis-server', server]) + self.cli_set(pool + ['option', 'nisplus-server', server]) + + for search in search_domains: + self.cli_set(pool + ['option', 'domain-search', search]) + + client_base = 1 + for client in ['client1', 'client2', 'client3']: + duid = f'00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:{client_base:02}' + self.cli_set(pool + ['static-mapping', client, 'duid', duid]) + self.cli_set(pool + ['static-mapping', client, 'ipv6-address', inc_ip(subnet, client_base)]) + self.cli_set(pool + ['static-mapping', client, 'ipv6-prefix', inc_ip(subnet, client_base << 64) + '/64']) + client_base += 1 + + # cannot have both mac-address and duid set + with self.assertRaises(ConfigSessionError): + self.cli_set(pool + ['static-mapping', 'client1', 'mac', '00:50:00:00:00:11']) + self.cli_commit() + self.cli_delete(pool + ['static-mapping', 'client1', 'mac']) + + # commit changes + self.cli_commit() + + config = read_file(KEA6_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp6', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'subnet', subnet) + self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'interface', interface) + self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'id', 1) + self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'valid-lifetime', int(lease_time)) + self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'min-valid-lifetime', int(min_lease_time)) + self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'max-valid-lifetime', int(max_lease_time)) + + # Verify options + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'dns-servers', 'data': f'{dns_1}, {dns_2}'}) + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'domain-search', 'data': ", ".join(search_domains)}) + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'nis-domain-name', 'data': domain}) + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'nis-servers', 'data': ", ".join(nis_servers)}) + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'nisp-domain-name', 'data': domain}) + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'nisp-servers', 'data': ", ".join(nis_servers)}) + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'sntp-servers', 'data': sntp_server}) + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'sip-server-dns', 'data': sip_server}) + + # Verify pools + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'pools'], + {'pool': f'{range_start} - {range_stop}'}) + + client_base = 1 + for client in ['client1', 'client2', 'client3']: + duid = f'00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:{client_base:02}' + ip = inc_ip(subnet, client_base) + prefix = inc_ip(subnet, client_base << 64) + '/64' + + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'reservations'], + {'hostname': client, 'duid': duid, 'ip-addresses': [ip], 'prefixes': [prefix]}) + + client_base += 1 + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + + def test_prefix_delegation(self): + shared_net_name = 'SMOKE-2' + range_start = inc_ip(subnet, 256) # ::100 + range_stop = inc_ip(subnet, 65535) # ::ffff + delegate_start = '2001:db8:ee::' + delegate_len = '64' + prefix_len = '56' + exclude_len = '66' + + pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] + self.cli_set(pool + ['subnet-id', '1']) + self.cli_set(pool + ['range', '1', 'start', range_start]) + self.cli_set(pool + ['range', '1', 'stop', range_stop]) + self.cli_set(pool + ['prefix-delegation', 'prefix', delegate_start, 'delegated-length', delegate_len]) + self.cli_set(pool + ['prefix-delegation', 'prefix', delegate_start, 'prefix-length', prefix_len]) + self.cli_set(pool + ['prefix-delegation', 'prefix', delegate_start, 'excluded-prefix', delegate_start]) + self.cli_set(pool + ['prefix-delegation', 'prefix', delegate_start, 'excluded-prefix-length', exclude_len]) + + # commit changes + self.cli_commit() + + config = read_file(KEA6_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp6', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'subnet', subnet) + + # Verify pools + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'pools'], + {'pool': f'{range_start} - {range_stop}'}) + + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'pd-pools'], + { + 'prefix': delegate_start, + 'prefix-len': int(prefix_len), + 'delegated-len': int(delegate_len), + 'excluded-prefix': delegate_start, + 'excluded-prefix-len': int(exclude_len) + }) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_global_nameserver(self): + shared_net_name = 'SMOKE-3' + ns_global_1 = '2001:db8::1111' + ns_global_2 = '2001:db8::2222' + + self.cli_set(base_path + ['global-parameters', 'name-server', ns_global_1]) + self.cli_set(base_path + ['global-parameters', 'name-server', ns_global_2]) + self.cli_set(base_path + ['shared-network-name', shared_net_name, 'subnet', subnet, 'subnet-id', '1']) + + # commit changes + self.cli_commit() + + config = read_file(KEA6_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp6', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'subnet', subnet) + self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'id', 1) + + self.verify_config_object( + obj, + ['Dhcp6', 'option-data'], + {'name': 'dns-servers', "code": 23, "space": "dhcp6", "csv-format": True, 'data': f'{ns_global_1}, {ns_global_2}'}) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py new file mode 100644 index 0000000..c39d446 --- /dev/null +++ b/smoketest/scripts/cli/test_service_dns_dynamic.py @@ -0,0 +1,352 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest +import tempfile + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import cmd +from vyos.utils.process import process_running + +DDCLIENT_SYSTEMD_UNIT = '/run/systemd/system/ddclient.service.d/override.conf' +DDCLIENT_CONF = '/run/ddclient/ddclient.conf' +DDCLIENT_PID = '/run/ddclient/ddclient.pid' +DDCLIENT_PNAME = 'ddclient' + +base_path = ['service', 'dns', 'dynamic'] +name_path = base_path + ['name'] +server = 'ddns.vyos.io' +hostname = 'test.ddns.vyos.io' +zone = 'vyos.io' +username = 'vyos_user' +password = 'paSS_@4ord' +ttl = '300' +interface = 'eth0' + +class TestServiceDDNS(VyOSUnitTestSHIM.TestCase): + def setUp(self): + # Always start with a clean CLI instance + self.cli_delete(base_path) + + def tearDown(self): + # Check for running process + self.assertTrue(process_running(DDCLIENT_PID)) + + # Delete DDNS configuration + self.cli_delete(base_path) + self.cli_commit() + + # PID file must no londer exist after process exited + self.assertFalse(os.path.exists(DDCLIENT_PID)) + + # IPv4 standard DDNS service configuration + def test_01_dyndns_service_standard(self): + services = {'cloudflare': {'protocol': 'cloudflare'}, + 'freedns': {'protocol': 'freedns', 'username': username}, + 'zoneedit': {'protocol': 'zoneedit1', 'username': username}} + + for svc, details in services.items(): + self.cli_set(name_path + [svc, 'address', 'interface', interface]) + self.cli_set(name_path + [svc, 'host-name', hostname]) + self.cli_set(name_path + [svc, 'password', password]) + for opt, value in details.items(): + self.cli_set(name_path + [svc, opt, value]) + + # 'zone' option is supported by 'cloudfare' and 'zoneedit1', but not 'freedns' + self.cli_set(name_path + [svc, 'zone', zone]) + if details['protocol'] in ['cloudflare', 'zoneedit1']: + pass + else: + # exception is raised for unsupported ones + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(name_path + [svc, 'zone']) + + # 'ttl' option is supported by 'cloudfare', but not 'freedns' and 'zoneedit' + self.cli_set(name_path + [svc, 'ttl', ttl]) + if details['protocol'] == 'cloudflare': + pass + else: + # exception is raised for unsupported ones + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(name_path + [svc, 'ttl']) + + # commit changes + self.cli_commit() + + # Check the generating config parameters + ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}') + # default value 300 seconds + self.assertIn(f'daemon=300', ddclient_conf) + self.assertIn(f'usev4=ifv4', ddclient_conf) + self.assertIn(f'ifv4={interface}', ddclient_conf) + self.assertIn(f'password=\'{password}\'', ddclient_conf) + + for opt in details.keys(): + if opt == 'username': + login = details[opt] + self.assertIn(f'login={login}', ddclient_conf) + else: + tmp = details[opt] + self.assertIn(f'{opt}={tmp}', ddclient_conf) + + # IPv6 only DDNS service configuration + def test_02_dyndns_service_ipv6(self): + interval = '60' + svc_path = name_path + ['dynv6'] + proto = 'dyndns2' + ip_version = 'ipv6' + wait_time = '600' + expiry_time_good = '3600' + expiry_time_bad = '360' + + self.cli_set(base_path + ['interval', interval]) + self.cli_set(svc_path + ['address', 'interface', interface]) + self.cli_set(svc_path + ['ip-version', ip_version]) + self.cli_set(svc_path + ['protocol', proto]) + self.cli_set(svc_path + ['server', server]) + self.cli_set(svc_path + ['username', username]) + self.cli_set(svc_path + ['password', password]) + self.cli_set(svc_path + ['host-name', hostname]) + self.cli_set(svc_path + ['wait-time', wait_time]) + + # expiry-time must be greater than wait-time, exception is raised otherwise + with self.assertRaises(ConfigSessionError): + self.cli_set(svc_path + ['expiry-time', expiry_time_bad]) + self.cli_commit() + self.cli_set(svc_path + ['expiry-time', expiry_time_good]) + + # commit changes + self.cli_commit() + + # Check the generating config parameters + ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}') + self.assertIn(f'daemon={interval}', ddclient_conf) + self.assertIn(f'usev6=ifv6', ddclient_conf) + self.assertIn(f'ifv6={interface}', ddclient_conf) + self.assertIn(f'protocol={proto}', ddclient_conf) + self.assertIn(f'server={server}', ddclient_conf) + self.assertIn(f'login={username}', ddclient_conf) + self.assertIn(f'password=\'{password}\'', ddclient_conf) + self.assertIn(f'min-interval={wait_time}', ddclient_conf) + self.assertIn(f'max-interval={expiry_time_good}', ddclient_conf) + + # IPv4+IPv6 dual DDNS service configuration + def test_03_dyndns_service_dual_stack(self): + services = {'cloudflare': {'protocol': 'cloudflare', 'zone': zone}, + 'freedns': {'protocol': 'freedns', 'username': username}, + 'google': {'protocol': 'googledomains', 'username': username}} + ip_version = 'both' + + for name, details in services.items(): + self.cli_set(name_path + [name, 'address', 'interface', interface]) + self.cli_set(name_path + [name, 'host-name', hostname]) + self.cli_set(name_path + [name, 'password', password]) + for opt, value in details.items(): + self.cli_set(name_path + [name, opt, value]) + + # Dual stack is supported by 'cloudfare' and 'freedns' but not 'googledomains' + # exception is raised for unsupported ones + self.cli_set(name_path + [name, 'ip-version', ip_version]) + if details['protocol'] not in ['cloudflare', 'freedns']: + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(name_path + [name, 'ip-version']) + + # commit changes + self.cli_commit() + + # Check the generating config parameters + ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}') + if details['protocol'] not in ['cloudflare', 'freedns']: + self.assertIn(f'usev4=ifv4', ddclient_conf) + self.assertIn(f'ifv4={interface}', ddclient_conf) + else: + self.assertIn(f'usev4=ifv4', ddclient_conf) + self.assertIn(f'usev6=ifv6', ddclient_conf) + self.assertIn(f'ifv4={interface}', ddclient_conf) + self.assertIn(f'ifv6={interface}', ddclient_conf) + self.assertIn(f'password=\'{password}\'', ddclient_conf) + + for opt in details.keys(): + if opt == 'username': + login = details[opt] + self.assertIn(f'login={login}', ddclient_conf) + else: + tmp = details[opt] + self.assertIn(f'{opt}={tmp}', ddclient_conf) + + def test_04_dyndns_rfc2136(self): + # Check if DDNS service can be configured and runs + svc_path = name_path + ['vyos'] + proto = 'nsupdate' + + with tempfile.NamedTemporaryFile(prefix='/config/auth/') as key_file: + key_file.write(b'S3cretKey') + + self.cli_set(svc_path + ['address', 'interface', interface]) + self.cli_set(svc_path + ['protocol', proto]) + self.cli_set(svc_path + ['server', server]) + self.cli_set(svc_path + ['zone', zone]) + self.cli_set(svc_path + ['key', key_file.name]) + self.cli_set(svc_path + ['ttl', ttl]) + self.cli_set(svc_path + ['host-name', hostname]) + + # commit changes + self.cli_commit() + + # Check some generating config parameters + ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}') + self.assertIn(f'use=if', ddclient_conf) + self.assertIn(f'if={interface}', ddclient_conf) + self.assertIn(f'protocol={proto}', ddclient_conf) + self.assertIn(f'server={server}', ddclient_conf) + self.assertIn(f'zone={zone}', ddclient_conf) + self.assertIn(f'password=\'{key_file.name}\'', ddclient_conf) + self.assertIn(f'ttl={ttl}', ddclient_conf) + + def test_05_dyndns_hostname(self): + # Check if DDNS service can be configured and runs + svc_path = name_path + ['namecheap'] + proto = 'namecheap' + hostnames = ['@', 'www', hostname, f'@.{hostname}'] + + for name in hostnames: + self.cli_set(svc_path + ['address', 'interface', interface]) + self.cli_set(svc_path + ['protocol', proto]) + self.cli_set(svc_path + ['server', server]) + self.cli_set(svc_path + ['username', username]) + self.cli_set(svc_path + ['password', password]) + self.cli_set(svc_path + ['host-name', name]) + + # commit changes + self.cli_commit() + + # Check the generating config parameters + ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}') + self.assertIn(f'protocol={proto}', ddclient_conf) + self.assertIn(f'server={server}', ddclient_conf) + self.assertIn(f'login={username}', ddclient_conf) + self.assertIn(f'password=\'{password}\'', ddclient_conf) + self.assertIn(f'{name}', ddclient_conf) + + def test_06_dyndns_web_options(self): + # Check if DDNS service can be configured and runs + svc_path = name_path + ['cloudflare'] + proto = 'cloudflare' + web_url = 'https://ifconfig.me/ip' + web_skip = 'Current IP Address:' + + self.cli_set(svc_path + ['protocol', proto]) + self.cli_set(svc_path + ['zone', zone]) + self.cli_set(svc_path + ['password', password]) + self.cli_set(svc_path + ['host-name', hostname]) + + # not specifying either 'interface' or 'web' will raise an exception + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(svc_path + ['address', 'web']) + + # specifying both 'interface' and 'web' will raise an exception as well + with self.assertRaises(ConfigSessionError): + self.cli_set(svc_path + ['address', 'interface', interface]) + self.cli_commit() + self.cli_delete(svc_path + ['address', 'interface']) + self.cli_commit() + + # web option 'skip' is useless without the option 'url' + with self.assertRaises(ConfigSessionError): + self.cli_set(svc_path + ['address', 'web', 'skip', web_skip]) + self.cli_commit() + self.cli_set(svc_path + ['address', 'web', 'url', web_url]) + self.cli_commit() + + # Check the generating config parameters + ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}') + self.assertIn(f'usev4=webv4', ddclient_conf) + self.assertIn(f'webv4={web_url}', ddclient_conf) + self.assertIn(f'webv4-skip=\'{web_skip}\'', ddclient_conf) + self.assertIn(f'protocol={proto}', ddclient_conf) + self.assertIn(f'zone={zone}', ddclient_conf) + self.assertIn(f'password=\'{password}\'', ddclient_conf) + self.assertIn(f'{hostname}', ddclient_conf) + + def test_07_dyndns_dynamic_interface(self): + # Check if DDNS service can be configured and runs + svc_path = name_path + ['namecheap'] + proto = 'namecheap' + dyn_interface = 'pppoe587' + + self.cli_set(svc_path + ['address', 'interface', dyn_interface]) + self.cli_set(svc_path + ['protocol', proto]) + self.cli_set(svc_path + ['server', server]) + self.cli_set(svc_path + ['username', username]) + self.cli_set(svc_path + ['password', password]) + self.cli_set(svc_path + ['host-name', hostname]) + + # Dynamic interface will raise a warning but still go through + # XXX: We should have idiomatic class "ConfigSessionWarning" wrapping + # "Warning" similar to "ConfigSessionError". + # with self.assertWarns(Warning): + # self.cli_commit() + self.cli_commit() + + # Check the generating config parameters + ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}') + self.assertIn(f'ifv4={dyn_interface}', ddclient_conf) + self.assertIn(f'protocol={proto}', ddclient_conf) + self.assertIn(f'server={server}', ddclient_conf) + self.assertIn(f'login={username}', ddclient_conf) + self.assertIn(f'password=\'{password}\'', ddclient_conf) + self.assertIn(f'{hostname}', ddclient_conf) + + def test_08_dyndns_vrf(self): + # Table number randomized, but should be within range 100-65535 + vrf_table = '58710' + vrf_name = f'vyos-test-{vrf_table}' + svc_path = name_path + ['cloudflare'] + proto = 'cloudflare' + + self.cli_set(['vrf', 'name', vrf_name, 'table', vrf_table]) + self.cli_set(base_path + ['vrf', vrf_name]) + + self.cli_set(svc_path + ['address', 'interface', interface]) + self.cli_set(svc_path + ['protocol', proto]) + self.cli_set(svc_path + ['host-name', hostname]) + self.cli_set(svc_path + ['zone', zone]) + self.cli_set(svc_path + ['password', password]) + + # commit changes + self.cli_commit() + + # Check for process in VRF + systemd_override = cmd(f'cat {DDCLIENT_SYSTEMD_UNIT}') + self.assertIn(f'ExecStart=ip vrf exec {vrf_name} /usr/bin/ddclient -file {DDCLIENT_CONF}', + systemd_override) + + # Check for process in VRF + proc = cmd(f'ip vrf pids {vrf_name}') + self.assertIn(DDCLIENT_PNAME, proc) + + # Cleanup VRF + self.cli_delete(['vrf', 'name', vrf_name]) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py new file mode 100644 index 0000000..9a3f493 --- /dev/null +++ b/smoketest/scripts/cli/test_service_dns_forwarding.py @@ -0,0 +1,344 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.template import bracketize_ipv6 +from vyos.utils.file import read_file +from vyos.utils.process import process_named_running + +PDNS_REC_RUN_DIR = '/run/pdns-recursor' +CONFIG_FILE = f'{PDNS_REC_RUN_DIR}/recursor.conf' +PDNS_REC_LUA_CONF_FILE = f'{PDNS_REC_RUN_DIR}/recursor.conf.lua' +FORWARD_FILE = f'{PDNS_REC_RUN_DIR}/recursor.forward-zones.conf' +HOSTSD_FILE = f'{PDNS_REC_RUN_DIR}/recursor.vyos-hostsd.conf.lua' +PROCESS_NAME= 'pdns_recursor' + +base_path = ['service', 'dns', 'forwarding'] + +allow_from = ['192.0.2.0/24', '2001:db8::/32'] +listen_adress = ['127.0.0.1', '::1'] + +def get_config_value(key, file=CONFIG_FILE): + tmp = read_file(file) + tmp = re.findall(r'\n{}=+(.*)'.format(key), tmp) + return tmp[0] + +class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestServicePowerDNS, cls).setUpClass() + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + # Delete DNS forwarding configuration + self.cli_delete(base_path) + self.cli_commit() + + # Check for running process + self.assertFalse(process_named_running(PROCESS_NAME)) + + def setUp(self): + # forward to base class + super().setUp() + for network in allow_from: + self.cli_set(base_path + ['allow-from', network]) + for address in listen_adress: + self.cli_set(base_path + ['listen-address', address]) + + def test_basic_forwarding(self): + # Check basic DNS forwarding settings + cache_size = '20' + negative_ttl = '120' + + # remove code from setUp() as in this test-case we validate the proper + # handling of assertions when specific CLI nodes are missing + self.cli_delete(base_path) + + self.cli_set(base_path + ['cache-size', cache_size]) + self.cli_set(base_path + ['negative-ttl', negative_ttl]) + + # check validate() - allow from must be defined + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for network in allow_from: + self.cli_set(base_path + ['allow-from', network]) + + # check validate() - listen-address must be defined + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for address in listen_adress: + self.cli_set(base_path + ['listen-address', address]) + + # configure DNSSEC + self.cli_set(base_path + ['dnssec', 'validate']) + + # Do not use local /etc/hosts file in name resolution + self.cli_set(base_path + ['ignore-hosts-file']) + + # commit changes + self.cli_commit() + + # Check configured cache-size + tmp = get_config_value('max-cache-entries') + self.assertEqual(tmp, cache_size) + + # Networks allowed to query this server + tmp = get_config_value('allow-from') + self.assertEqual(tmp, ','.join(allow_from)) + + # Addresses to listen for DNS queries + tmp = get_config_value('local-address') + self.assertEqual(tmp, ','.join(listen_adress)) + + # Maximum amount of time negative entries are cached + tmp = get_config_value('max-negative-ttl') + self.assertEqual(tmp, negative_ttl) + + # Do not use local /etc/hosts file in name resolution + tmp = get_config_value('export-etc-hosts') + self.assertEqual(tmp, 'no') + + # RFC1918 addresses are looked up by default + tmp = get_config_value('serve-rfc1918') + self.assertEqual(tmp, 'yes') + + # verify default port configuration + tmp = get_config_value('local-port') + self.assertEqual(tmp, '53') + + def test_dnssec(self): + # DNSSEC option testing + options = ['off', 'process-no-validate', 'process', 'log-fail', 'validate'] + for option in options: + self.cli_set(base_path + ['dnssec', option]) + + # commit changes + self.cli_commit() + + tmp = get_config_value('dnssec') + self.assertEqual(tmp, option) + + def test_external_nameserver(self): + # Externe Domain Name Servers (DNS) addresses + nameservers = {'192.0.2.1': {}, '192.0.2.2': {'port': '53'}, '2001:db8::1': {'port': '853'}} + for h,p in nameservers.items(): + if 'port' in p: + self.cli_set(base_path + ['name-server', h, 'port', p['port']]) + else: + self.cli_set(base_path + ['name-server', h]) + + # commit changes + self.cli_commit() + + tmp = get_config_value(r'\+.', file=FORWARD_FILE) + canonical_entries = [(lambda h, p: f"{bracketize_ipv6(h)}:{p['port'] if 'port' in p else 53}")(h, p) + for (h, p) in nameservers.items()] + self.assertEqual(tmp, ', '.join(canonical_entries)) + + # Do not use local /etc/hosts file in name resolution + # default: yes + tmp = get_config_value('export-etc-hosts') + self.assertEqual(tmp, 'yes') + + def test_domain_forwarding(self): + domains = ['vyos.io', 'vyos.net', 'vyos.com'] + nameservers = {'192.0.2.1': {}, '192.0.2.2': {'port': '53'}, '2001:db8::1': {'port': '853'}} + for domain in domains: + for h,p in nameservers.items(): + if 'port' in p: + self.cli_set(base_path + ['domain', domain, 'name-server', h, 'port', p['port']]) + else: + self.cli_set(base_path + ['domain', domain, 'name-server', h]) + + # Test 'recursion-desired' flag for only one domain + if domain == domains[0]: + self.cli_set(base_path + ['domain', domain, 'recursion-desired']) + + # Test 'negative trust anchor' flag for the second domain only + if domain == domains[1]: + self.cli_set(base_path + ['domain', domain, 'addnta']) + + # commit changes + self.cli_commit() + + # Test configured name-servers + hosts_conf = read_file(HOSTSD_FILE) + for domain in domains: + # Test 'recursion-desired' flag for the first domain only + if domain == domains[0]: key =f'\+{domain}' + else: key =f'{domain}' + tmp = get_config_value(key, file=FORWARD_FILE) + canonical_entries = [(lambda h, p: f"{bracketize_ipv6(h)}:{p['port'] if 'port' in p else 53}")(h, p) + for (h, p) in nameservers.items()] + self.assertEqual(tmp, ', '.join(canonical_entries)) + + # Test 'negative trust anchor' flag for the second domain only + if domain == domains[1]: + self.assertIn(f'addNTA("{domain}", "static")', hosts_conf) + + def test_no_rfc1918_forwarding(self): + self.cli_set(base_path + ['no-serve-rfc1918']) + + # commit changes + self.cli_commit() + + # verify configuration + tmp = get_config_value('serve-rfc1918') + self.assertEqual(tmp, 'no') + + def test_dns64(self): + dns_prefix = '64:ff9b::/96' + # Check dns64-prefix - must be prefix /96 + self.cli_set(base_path + ['dns64-prefix', '2001:db8:aabb::/64']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['dns64-prefix', dns_prefix]) + + # commit changes + self.cli_commit() + + # verify dns64-prefix configuration + tmp = get_config_value('dns64-prefix') + self.assertEqual(tmp, dns_prefix) + + def test_exclude_throttle_adress(self): + exclude_throttle_adress_examples = [ + '192.168.128.255', + '10.0.0.0/25', + '2001:db8:85a3:8d3:1319:8a2e:370:7348', + '64:ff9b::/96' + ] + for exclude_throttle_adress in exclude_throttle_adress_examples: + self.cli_set(base_path + ['exclude-throttle-address', exclude_throttle_adress]) + + # commit changes + self.cli_commit() + + # verify dont-throttle-netmasks configuration + tmp = get_config_value('dont-throttle-netmasks') + self.assertEqual(tmp, ','.join(exclude_throttle_adress_examples)) + + def test_serve_stale_extension(self): + server_stale = '20' + self.cli_set(base_path + ['serve-stale-extension', server_stale]) + # commit changes + self.cli_commit() + # verify configuration + tmp = get_config_value('serve-stale-extensions') + self.assertEqual(tmp, server_stale) + + def test_listening_port(self): + # We can listen on a different port compared to '53' but only one at a time + for port in ['10053', '10054']: + self.cli_set(base_path + ['port', port]) + # commit changes + self.cli_commit() + # verify local-port configuration + tmp = get_config_value('local-port') + self.assertEqual(tmp, port) + + def test_ecs_add_for(self): + options = ['0.0.0.0/0', '!10.0.0.0/8', 'fc00::/7', '!fe80::/10'] + for param in options: + self.cli_set(base_path + ['options', 'ecs-add-for', param]) + + # commit changes + self.cli_commit() + # verify ecs_add_for configuration + tmp = get_config_value('ecs-add-for') + self.assertEqual(tmp, ','.join(options)) + + def test_ecs_ipv4_bits(self): + option_value = '24' + self.cli_set(base_path + ['options', 'ecs-ipv4-bits', option_value]) + # commit changes + self.cli_commit() + # verify ecs_ipv4_bits configuration + tmp = get_config_value('ecs-ipv4-bits') + self.assertEqual(tmp, option_value) + + def test_edns_subnet_allow_list(self): + options = ['192.0.2.1/32', 'example.com', 'fe80::/10'] + for param in options: + self.cli_set(base_path + ['options', 'edns-subnet-allow-list', param]) + + # commit changes + self.cli_commit() + + # verify edns_subnet_allow_list configuration + tmp = get_config_value('edns-subnet-allow-list') + self.assertEqual(tmp, ','.join(options)) + + def test_multiple_ns_records(self): + test_zone = 'example.com' + self.cli_set(base_path + ['authoritative-domain', test_zone, 'records', 'ns', 'test', 'target', f'ns1.{test_zone}']) + self.cli_set(base_path + ['authoritative-domain', test_zone, 'records', 'ns', 'test', 'target', f'ns2.{test_zone}']) + self.cli_commit() + zone_config = read_file(f'{PDNS_REC_RUN_DIR}/zone.{test_zone}.conf') + self.assertRegex(zone_config, fr'test\s+\d+\s+NS\s+ns1\.{test_zone}\.') + self.assertRegex(zone_config, fr'test\s+\d+\s+NS\s+ns2\.{test_zone}\.') + + def test_zone_cache_url(self): + self.cli_set(base_path + ['zone-cache', 'smoketest', 'source', 'url', 'https://www.internic.net/domain/root.zone']) + self.cli_commit() + + lua_config = read_file(PDNS_REC_LUA_CONF_FILE) + self.assertIn('zoneToCache("smoketest", "url", "https://www.internic.net/domain/root.zone", { dnssec = "validate", zonemd = "validate", maxReceivedMBytes = 0, retryOnErrorPeriod = 60, refreshPeriod = 86400, timeout = 20 })', lua_config) + + def test_zone_cache_axfr(self): + + self.cli_set(base_path + ['zone-cache', 'smoketest', 'source', 'axfr', '127.0.0.1']) + self.cli_commit() + + lua_config = read_file(PDNS_REC_LUA_CONF_FILE) + self.assertIn('zoneToCache("smoketest", "axfr", "127.0.0.1", { dnssec = "validate", zonemd = "validate", maxReceivedMBytes = 0, retryOnErrorPeriod = 60, refreshPeriod = 86400, timeout = 20 })', lua_config) + + def test_zone_cache_options(self): + self.cli_set(base_path + ['zone-cache', 'smoketest', 'source', 'url', 'https://www.internic.net/domain/root.zone']) + self.cli_set(base_path + ['zone-cache', 'smoketest', 'options', 'dnssec', 'ignore']) + self.cli_set(base_path + ['zone-cache', 'smoketest', 'options', 'max-zone-size', '100']) + self.cli_set(base_path + ['zone-cache', 'smoketest', 'options', 'refresh', 'interval', '10']) + self.cli_set(base_path + ['zone-cache', 'smoketest', 'options', 'retry-interval', '90']) + self.cli_set(base_path + ['zone-cache', 'smoketest', 'options', 'timeout', '50']) + self.cli_set(base_path + ['zone-cache', 'smoketest', 'options', 'zonemd', 'require']) + self.cli_commit() + + lua_config = read_file(PDNS_REC_LUA_CONF_FILE) + self.assertIn('zoneToCache("smoketest", "url", "https://www.internic.net/domain/root.zone", { dnssec = "ignore", maxReceivedMBytes = 100, refreshPeriod = 10, retryOnErrorPeriod = 90, timeout = 50, zonemd = "require" })', lua_config) + + def test_zone_cache_wrong_source(self): + self.cli_set(base_path + ['zone-cache', 'smoketest', 'source', 'url', 'https://www.internic.net/domain/root.zone']) + self.cli_set(base_path + ['zone-cache', 'smoketest', 'source', 'axfr', '127.0.0.1']) + + with self.assertRaises(ConfigSessionError): + self.cli_commit() + # correct config to correct finish the test + self.cli_delete(base_path + ['zone-cache', 'smoketest', 'source', 'axfr']) + self.cli_commit() + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py new file mode 100644 index 0000000..8a6386e --- /dev/null +++ b/smoketest/scripts/cli/test_service_https.py @@ -0,0 +1,502 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest +import json + +from requests import request +from urllib3.exceptions import InsecureRequestWarning +from time import sleep + +from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import ignore_warning +from vyos.utils.file import read_file +from vyos.utils.file import write_file +from vyos.utils.process import call +from vyos.utils.process import process_named_running +from vyos.xml_ref import default_value + +from vyos.configsession import ConfigSessionError + +base_path = ['service', 'https'] +pki_base = ['pki'] + +cert_data = """ +MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIw +WTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv +bWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIx +MDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNV +BAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlP +UzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3 +QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu ++JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3Lftz +ngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93 ++dm/LDnp7C0= +""" + +key_data = """ +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx +2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7 +u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww +""" + +dh_1024 = """ +MIGHAoGBAM3nvMkHGi/xmRs8cYg4pcl5sAanxel9EM+1XobVhUViXw8JvlmSEVOj +n2aXUifc4SEs3WDzVPRC8O8qQWjvErpTq/HOgt3aqBCabMgvflmt706XP0KiqnpW +EyvNiI27J3wBUzEXLIS110MxPAX5Tcug974PecFcOxn1RWrbWcx/AgEC +""" + +dh_2048 = """ +MIIBCAKCAQEA1mld/V7WnxxRinkOlhx/BoZkRELtIUQFYxyARBqYk4C5G3YnZNNu +zjaGyPnfIKHu8SIUH85OecM+5/co9nYlcUJuph2tbR6qNgPw7LOKIhf27u7WhvJk +iVsJhwZiWmvvMV4jTParNEI2svoooMyhHXzeweYsg6YtgLVmwiwKj3XP3gRH2i3B +Mq8CDS7X6xaKvjfeMPZBFqOM5nb6HhsbaAUyiZxrfipLvXxtnbzd/eJUQVfVdxM3 +pn0i+QrO2tuNAzX7GoPc9pefrbb5xJmGS50G0uqsR59+7LhYmyZSBASA0lxTEW9t +kv/0LPvaYTY57WL7hBeqqHy/WPZHPzDI3wIBAg== +""" +# to test load config via HTTP URL +nginx_tmp_site = '/etc/nginx/sites-enabled/smoketest' +nginx_conf_smoketest = """ +server { + listen 8000; + server_name localhost; + + root /tmp; + + index index.html; + + location / { + try_files $uri $uri/ =404; + autoindex on; + } +} +""" + +PROCESS_NAME = 'nginx' + +class TestHTTPSService(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestHTTPSService, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + cls.cli_delete(cls, pki_base) + + @classmethod + def tearDownClass(cls): + super(TestHTTPSService, cls).tearDownClass() + call(f'sudo rm -f {nginx_tmp_site}') + + def tearDown(self): + self.cli_delete(base_path) + self.cli_delete(pki_base) + self.cli_commit() + + # Check for stopped process + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_certificate(self): + cert_name = 'test_https' + dh_name = 'dh-test' + + self.cli_set(base_path + ['certificates', 'certificate', cert_name]) + # verify() - certificates do not exist (yet) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(pki_base + ['certificate', cert_name, 'certificate', cert_data.replace('\n','')]) + self.cli_set(pki_base + ['certificate', cert_name, 'private', 'key', key_data.replace('\n','')]) + + self.cli_set(base_path + ['certificates', 'dh-params', dh_name]) + # verify() - dh-params do not exist (yet) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(pki_base + ['dh', dh_name, 'parameters', dh_1024.replace('\n','')]) + # verify() - dh-param minimum length is 2048 bit + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(pki_base + ['dh', dh_name, 'parameters', dh_2048.replace('\n','')]) + + self.cli_commit() + self.assertTrue(process_named_running(PROCESS_NAME)) + self.debug = False + + def test_api_missing_keys(self): + self.cli_set(base_path + ['api']) + self.assertRaises(ConfigSessionError, self.cli_commit) + + def test_api_incomplete_key(self): + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01']) + self.assertRaises(ConfigSessionError, self.cli_commit) + + @ignore_warning(InsecureRequestWarning) + def test_api_auth(self): + address = '127.0.0.1' + port = default_value(base_path + ['port']) + + key = 'MySuperSecretVyOS' + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + + self.cli_set(base_path + ['listen-address', address]) + + self.cli_commit() + + nginx_config = read_file('/etc/nginx/sites-enabled/default') + self.assertIn(f'listen {address}:{port} ssl;', nginx_config) + self.assertIn(f'ssl_protocols TLSv1.2 TLSv1.3;', nginx_config) # default + + url = f'https://{address}/retrieve' + payload = {'data': '{"op": "showConfig", "path": []}', 'key': f'{key}'} + headers = {} + r = request('POST', url, verify=False, headers=headers, data=payload) + # Must get HTTP code 200 on success + self.assertEqual(r.status_code, 200) + + payload_invalid = {'data': '{"op": "showConfig", "path": []}', 'key': 'invalid'} + r = request('POST', url, verify=False, headers=headers, data=payload_invalid) + # Must get HTTP code 401 on invalid key (Unauthorized) + self.assertEqual(r.status_code, 401) + + payload_no_key = {'data': '{"op": "showConfig", "path": []}'} + r = request('POST', url, verify=False, headers=headers, data=payload_no_key) + # Must get HTTP code 401 on missing key (Unauthorized) + self.assertEqual(r.status_code, 401) + + # Check path config + payload = {'data': '{"op": "showConfig", "path": ["system", "login"]}', 'key': f'{key}'} + r = request('POST', url, verify=False, headers=headers, data=payload) + response = r.json() + vyos_user_exists = 'vyos' in response.get('data', {}).get('user', {}) + self.assertTrue(vyos_user_exists, "The 'vyos' user does not exist in the response.") + + # GraphQL auth test: a missing key will return status code 400, as + # 'key' is a non-nullable field in the schema; an incorrect key is + # caught by the resolver, and returns success 'False', so one must + # check the return value. + + self.cli_set(base_path + ['api', 'graphql']) + self.cli_commit() + + graphql_url = f'https://{address}/graphql' + + query_valid_key = f""" + {{ + SystemStatus (data: {{key: "{key}"}}) {{ + success + errors + data {{ + result + }} + }} + }} + """ + + r = request('POST', graphql_url, verify=False, headers=headers, json={'query': query_valid_key}) + success = r.json()['data']['SystemStatus']['success'] + self.assertTrue(success) + + query_invalid_key = """ + { + SystemStatus (data: {key: "invalid"}) { + success + errors + data { + result + } + } + } + """ + + r = request('POST', graphql_url, verify=False, headers=headers, json={'query': query_invalid_key}) + success = r.json()['data']['SystemStatus']['success'] + self.assertFalse(success) + + query_no_key = """ + { + SystemStatus (data: {}) { + success + errors + data { + result + } + } + } + """ + + r = request('POST', graphql_url, verify=False, headers=headers, json={'query': query_no_key}) + success = r.json()['data']['SystemStatus']['success'] + self.assertFalse(success) + + # GraphQL token authentication test: request token; pass in header + # of query. + + self.cli_set(base_path + ['api', 'graphql', 'authentication', 'type', 'token']) + self.cli_commit() + + mutation = """ + mutation { + AuthToken (data: {username: "vyos", password: "vyos"}) { + success + errors + data { + result + } + } + } + """ + r = request('POST', graphql_url, verify=False, headers=headers, json={'query': mutation}) + + token = r.json()['data']['AuthToken']['data']['result']['token'] + + headers = {'Authorization': f'Bearer {token}'} + + query = """ + { + ShowVersion (data: {}) { + success + errors + op_mode_error { + name + message + vyos_code + } + data { + result + } + } + } + """ + + r = request('POST', graphql_url, verify=False, headers=headers, json={'query': query}) + success = r.json()['data']['ShowVersion']['success'] + self.assertTrue(success) + + @ignore_warning(InsecureRequestWarning) + def test_api_add_delete(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/retrieve' + payload = {'data': '{"op": "showConfig", "path": []}', 'key': f'{key}'} + headers = {} + + self.cli_set(base_path) + self.cli_commit() + + r = request('POST', url, verify=False, headers=headers, data=payload) + # api not configured; expect 503 + self.assertEqual(r.status_code, 503) + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + sleep(2) + + r = request('POST', url, verify=False, headers=headers, data=payload) + # api configured; expect 200 + self.assertEqual(r.status_code, 200) + + self.cli_delete(base_path + ['api']) + self.cli_commit() + + r = request('POST', url, verify=False, headers=headers, data=payload) + # api deleted; expect 503 + self.assertEqual(r.status_code, 503) + + @ignore_warning(InsecureRequestWarning) + def test_api_show(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/show' + headers = {} + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + payload = { + 'data': '{"op": "show", "path": ["system", "image"]}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + @ignore_warning(InsecureRequestWarning) + def test_api_generate(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/generate' + headers = {} + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + payload = { + 'data': '{"op": "generate", "path": ["macsec", "mka", "cak", "gcm-aes-256"]}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + @ignore_warning(InsecureRequestWarning) + def test_api_configure(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/configure' + headers = {} + conf_interface = 'dum0' + conf_address = '192.0.2.44/32' + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + payload_path = [ + "interfaces", + "dummy", + f"{conf_interface}", + "address", + f"{conf_address}", + ] + + payload = {'data': json.dumps({"op": "set", "path": payload_path}), 'key': key} + + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + @ignore_warning(InsecureRequestWarning) + def test_api_config_file(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/config-file' + headers = {} + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + payload = { + 'data': '{"op": "save"}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + @ignore_warning(InsecureRequestWarning) + def test_api_reset(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/reset' + headers = {} + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + payload = { + 'data': '{"op": "reset", "path": ["ip", "arp", "table"]}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + @ignore_warning(InsecureRequestWarning) + def test_api_image(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/image' + headers = {} + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + payload = { + 'data': '{"op": "add"}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 400) + self.assertIn('Missing required field "url"', r.json().get('error')) + + payload = { + 'data': '{"op": "delete"}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 400) + self.assertIn('Missing required field "name"', r.json().get('error')) + + payload = { + 'data': '{"op": "set_default"}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 400) + self.assertIn('Missing required field "name"', r.json().get('error')) + + payload = { + 'data': '{"op": "show"}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + @ignore_warning(InsecureRequestWarning) + def test_api_config_file_load_http(self): + # Test load config from HTTP URL + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/config-file' + url_config = f'https://{address}/configure' + headers = {} + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + # load config via HTTP requires nginx config + call(f'sudo touch {nginx_tmp_site}') + call(f'sudo chmod 666 {nginx_tmp_site}') + write_file(nginx_tmp_site, nginx_conf_smoketest) + call('sudo systemctl reload nginx') + + # save config + payload = { + 'data': '{"op": "save", "file": "/tmp/tmp-config.boot"}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + # change config + payload = { + 'data': '{"op": "set", "path": ["interfaces", "dummy", "dum1", "address", "192.0.2.31/32"]}', + 'key': f'{key}', + } + r = request('POST', url_config, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + # load config from URL + payload = { + 'data': '{"op": "load", "file": "http://localhost:8000/tmp-config.boot"}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + # cleanup tmp nginx conf + call(f'sudo rm -f {nginx_tmp_site}') + call('sudo systemctl reload nginx') + +if __name__ == '__main__': + unittest.main(verbosity=5) diff --git a/smoketest/scripts/cli/test_service_ids_ddos-protection.py b/smoketest/scripts/cli/test_service_ids_ddos-protection.py new file mode 100644 index 0000000..91b056e --- /dev/null +++ b/smoketest/scripts/cli/test_service_ids_ddos-protection.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +PROCESS_NAME = 'fastnetmon' +FASTNETMON_CONF = '/run/fastnetmon/fastnetmon.conf' +NETWORKS_CONF = '/run/fastnetmon/networks_list' +EXCLUDED_NETWORKS_CONF = '/run/fastnetmon/excluded_networks_list' +base_path = ['service', 'ids', 'ddos-protection'] + +class TestServiceIDS(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestServiceIDS, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + # delete test config + self.cli_delete(base_path) + self.cli_commit() + + self.assertFalse(os.path.exists(FASTNETMON_CONF)) + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_fastnetmon(self): + networks = ['10.0.0.0/24', '10.5.5.0/24', '2001:db8:10::/64', '2001:db8:20::/64'] + excluded_networks = ['10.0.0.1/32', '2001:db8:10::1/128'] + interfaces = ['eth0', 'eth1'] + fps = '3500' + mbps = '300' + pps = '60000' + + self.cli_set(base_path + ['mode', 'mirror']) + # Required network! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for tmp in networks: + self.cli_set(base_path + ['network', tmp]) + + # optional excluded-network! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for tmp in excluded_networks: + self.cli_set(base_path + ['excluded-network', tmp]) + + # Required interface(s)! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for tmp in interfaces: + self.cli_set(base_path + ['listen-interface', tmp]) + + self.cli_set(base_path + ['direction', 'in']) + self.cli_set(base_path + ['threshold', 'general', 'fps', fps]) + self.cli_set(base_path + ['threshold', 'general', 'pps', pps]) + self.cli_set(base_path + ['threshold', 'general', 'mbps', mbps]) + + # commit changes + self.cli_commit() + + # Check configured port + config = read_file(FASTNETMON_CONF) + self.assertIn(f'mirror_afpacket = on', config) + self.assertIn(f'process_incoming_traffic = on', config) + self.assertIn(f'process_outgoing_traffic = off', config) + self.assertIn(f'ban_for_flows = on', config) + self.assertIn(f'threshold_flows = {fps}', config) + self.assertIn(f'ban_for_bandwidth = on', config) + self.assertIn(f'threshold_mbps = {mbps}', config) + self.assertIn(f'ban_for_pps = on', config) + self.assertIn(f'threshold_pps = {pps}', config) + # default + self.assertIn(f'enable_ban = on', config) + self.assertIn(f'enable_ban_ipv6 = on', config) + self.assertIn(f'ban_time = 1900', config) + + tmp = ','.join(interfaces) + self.assertIn(f'interfaces = {tmp}', config) + + + network_config = read_file(NETWORKS_CONF) + for tmp in networks: + self.assertIn(f'{tmp}', network_config) + + excluded_network_config = read_file(EXCLUDED_NETWORKS_CONF) + for tmp in excluded_networks: + self.assertIn(f'{tmp}', excluded_network_config) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_ipoe-server.py b/smoketest/scripts/cli/test_service_ipoe-server.py new file mode 100644 index 0000000..be03179 --- /dev/null +++ b/smoketest/scripts/cli/test_service_ipoe-server.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import unittest + +from collections import OrderedDict +from base_accel_ppp_test import BasicAccelPPPTest +from vyos.configsession import ConfigSessionError +from vyos.utils.process import cmd +from vyos.template import range_to_regex +from configparser import ConfigParser +from configparser import RawConfigParser + +ac_name = "ACN" +interface = "eth0" + + +class MultiOrderedDict(OrderedDict): + # Accel-ppp has duplicate keys in config file (gw-ip-address) + # This class is used to define dictionary which can contain multiple values + # in one key. + def __setitem__(self, key, value): + if isinstance(value, list) and key in self: + self[key].extend(value) + else: + super(OrderedDict, self).__setitem__(key, value) + + +class TestServiceIPoEServer(BasicAccelPPPTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ["service", "ipoe-server"] + cls._config_file = "/run/accel-pppd/ipoe.conf" + cls._chap_secrets = "/run/accel-pppd/ipoe.chap-secrets" + cls._protocol_section = "ipoe" + + # call base-classes classmethod + super(TestServiceIPoEServer, cls).setUpClass() + + def verify(self, conf): + super().verify(conf) + + # Validate configuration values + accel_modules = list(conf["modules"].keys()) + self.assertIn("log_syslog", accel_modules) + self.assertIn("ipoe", accel_modules) + self.assertIn("shaper", accel_modules) + self.assertIn("ipv6pool", accel_modules) + self.assertIn("ipv6_nd", accel_modules) + self.assertIn("ipv6_dhcp", accel_modules) + self.assertIn("ippool", accel_modules) + + def initial_gateway_config(self): + self._gateway = "192.0.2.1/24" + super().initial_gateway_config() + + def initial_auth_config(self): + self.set(["authentication", "mode", "noauth"]) + + def basic_protocol_specific_config(self): + self.set(["interface", interface, "client-subnet", "192.168.0.0/24"]) + + def test_accel_local_authentication(self): + mac_address = "08:00:27:2f:d8:06" + self.set(["authentication", "interface", interface, "mac", mac_address]) + self.set(["authentication", "mode", "local"]) + + # No IPoE interface configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # Test configuration of local authentication for PPPoE server + self.basic_config() + # Rewrite authentication from basic_config + self.set(["authentication", "interface", interface, "mac", mac_address]) + self.set(["authentication", "mode", "local"]) + # commit changes + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False) + conf.read(self._config_file) + + # check proper path to chap-secrets file + self.assertEqual(conf["chap-secrets"]["chap-secrets"], self._chap_secrets) + + accel_modules = list(conf["modules"].keys()) + self.assertIn("chap-secrets", accel_modules) + + # basic verification + self.verify(conf) + + # check local users + tmp = cmd(f"sudo cat {self._chap_secrets}") + regex = f"{interface}\s+\*\s+{mac_address}\s+\*" + tmp = re.findall(regex, tmp) + self.assertTrue(tmp) + + def test_accel_ipv4_pool(self): + self.basic_config(is_gateway=False, is_client_pool=False) + + gateway = ["172.16.0.1/25", "192.0.2.1/24"] + subnet = "172.16.0.0/24" + first_pool = "POOL1" + second_pool = "POOL2" + range = "192.0.2.10-192.0.2.20" + range_config = "192.0.2.10-20" + + for gw in gateway: + self.set(["gateway-address", gw]) + + self.set(["client-ip-pool", first_pool, "range", subnet]) + self.set(["client-ip-pool", first_pool, "next-pool", second_pool]) + self.set(["client-ip-pool", second_pool, "range", range]) + self.set(["default-pool", first_pool]) + # commit changes + + self.cli_commit() + + # Validate configuration values + conf = RawConfigParser( + allow_no_value=True, + delimiters="=", + strict=False, + dict_type=MultiOrderedDict, + ) + conf.read(self._config_file) + + self.assertIn( + f"{first_pool},next={second_pool}", conf["ip-pool"][f"{subnet},name"] + ) + self.assertIn(second_pool, conf["ip-pool"][f"{range_config},name"]) + + gw_pool_config_list = conf.get("ip-pool", "gw-ip-address") + gw_ipoe_config_list = conf.get(self._protocol_section, "gw-ip-address") + for gw in gateway: + self.assertIn(gw.split("/")[0], gw_pool_config_list) + self.assertIn(gw, gw_ipoe_config_list) + + self.assertIn(first_pool, conf[self._protocol_section]["ip-pool"]) + + def test_accel_next_pool(self): + self.basic_config(is_gateway=False, is_client_pool=False) + + first_pool = "VyOS-pool1" + first_subnet = "192.0.2.0/25" + first_gateway = "192.0.2.1/24" + second_pool = "Vyos-pool2" + second_subnet = "203.0.113.0/25" + second_gateway = "203.0.113.1/24" + third_pool = "Vyos-pool3" + third_subnet = "198.51.100.0/24" + third_gateway = "198.51.100.1/24" + + self.set(["gateway-address", f"{first_gateway}"]) + self.set(["gateway-address", f"{second_gateway}"]) + self.set(["gateway-address", f"{third_gateway}"]) + + self.set(["client-ip-pool", first_pool, "range", first_subnet]) + self.set(["client-ip-pool", first_pool, "next-pool", second_pool]) + self.set(["client-ip-pool", second_pool, "range", second_subnet]) + self.set(["client-ip-pool", second_pool, "next-pool", third_pool]) + self.set(["client-ip-pool", third_pool, "range", third_subnet]) + + # commit changes + self.cli_commit() + + config = self.getConfig("ip-pool") + # T5099 required specific order + pool_config = f"""gw-ip-address={first_gateway.split('/')[0]} +gw-ip-address={second_gateway.split('/')[0]} +gw-ip-address={third_gateway.split('/')[0]} +{third_subnet},name={third_pool} +{second_subnet},name={second_pool},next={third_pool} +{first_subnet},name={first_pool},next={second_pool}""" + self.assertIn(pool_config, config) + + def test_accel_ipv6_pool(self): + # Test configuration of IPv6 client pools + self.basic_config(is_gateway=False, is_client_pool=False) + + pool_name = 'ipv6_test_pool' + prefix_1 = '2001:db8:fffe::/56' + prefix_mask = '64' + prefix_2 = '2001:db8:ffff::/56' + client_prefix_1 = f'{prefix_1},{prefix_mask}' + client_prefix_2 = f'{prefix_2},{prefix_mask}' + self.set(['client-ipv6-pool', pool_name, 'prefix', prefix_1, 'mask', + prefix_mask]) + self.set(['client-ipv6-pool', pool_name, 'prefix', prefix_2, 'mask', + prefix_mask]) + + delegate_1_prefix = '2001:db8:fff1::/56' + delegate_2_prefix = '2001:db8:fff2::/56' + delegate_mask = '64' + self.set(['client-ipv6-pool', pool_name, 'delegate', delegate_1_prefix, + 'delegation-prefix', delegate_mask]) + self.set(['client-ipv6-pool', pool_name, 'delegate', delegate_2_prefix, + 'delegation-prefix', delegate_mask]) + + # commit changes + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True, delimiters='=', strict=False) + conf.read(self._config_file) + + for tmp in ['ipv6pool', 'ipv6_nd', 'ipv6_dhcp']: + self.assertEqual(conf['modules'][tmp], None) + + config = self.getConfig("ipv6-pool") + pool_config = f"""{client_prefix_1},name={pool_name} +{client_prefix_2},name={pool_name} +delegate={delegate_1_prefix},{delegate_mask},name={pool_name} +delegate={delegate_2_prefix},{delegate_mask},name={pool_name}""" + self.assertIn(pool_config, config) + + def test_ipoe_server_vlan(self): + vlans = ['100', '200', '300-310'] + + # Test configuration of local authentication for PPPoE server + self.basic_config() + # cannot use "client-subnet" option with "vlan" option + # have to delete it + self.delete(['interface', interface, 'client-subnet']) + self.cli_commit() + + self.set(['interface', interface, 'vlan-mon']) + + # cannot use option "vlan-mon" if no "vlan" set + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + for vlan in vlans: + self.set(['interface', interface, 'vlan', vlan]) + + # commit changes + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True, delimiters='=', strict=False) + conf.read(self._config_file) + tmp = range_to_regex(vlans) + self.assertIn(f're:^{interface}\.{tmp}$', conf['ipoe']['interface']) + + tmp = ','.join(vlans) + self.assertIn(f'{interface},{tmp}', conf['ipoe']['vlan-mon']) + + @unittest.skip("PPP is not a part of IPoE") + def test_accel_ppp_options(self): + pass + + @unittest.skip("WINS server is not used in IPoE") + def test_accel_wins_server(self): + pass + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_lldp.py b/smoketest/scripts/cli/test_service_lldp.py new file mode 100644 index 0000000..9d72ef7 --- /dev/null +++ b/smoketest/scripts/cli/test_service_lldp.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.version import get_version_data + +PROCESS_NAME = 'lldpd' +LLDPD_CONF = '/etc/lldpd.d/01-vyos.conf' +base_path = ['service', 'lldp'] +mgmt_if = 'dum83513' +mgmt_addr = ['1.2.3.4', '1.2.3.5'] + +class TestServiceLLDP(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + # call base-classes classmethod + super(TestServiceLLDP, cls).setUpClass() + + # create a test interfaces + for addr in mgmt_addr: + cls.cli_set(cls, ['interfaces', 'dummy', mgmt_if, 'address', addr + '/32']) + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'dummy', mgmt_if]) + super(TestServiceLLDP, cls).tearDownClass() + + def tearDown(self): + # service must be running after it was configured + self.assertTrue(process_named_running(PROCESS_NAME)) + + # delete/stop LLDP service + self.cli_delete(base_path) + self.cli_commit() + + # service is no longer allowed to run after it was removed + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_lldp_basic(self): + self.cli_set(base_path) + self.cli_commit() + + config = read_file(LLDPD_CONF) + version_data = get_version_data() + version = version_data['version'] + self.assertIn(f'configure system platform VyOS', config) + self.assertIn(f'configure system description "VyOS {version}"', config) + + def test_02_lldp_mgmt_address(self): + for addr in mgmt_addr: + self.cli_set(base_path + ['management-address', addr]) + self.cli_commit() + + config = read_file(LLDPD_CONF) + self.assertIn(f'configure system ip management pattern {",".join(mgmt_addr)}', config) + + def test_03_lldp_interfaces(self): + for interface in Section.interfaces('ethernet'): + if not '.' in interface: + self.cli_set(base_path + ['interface', interface]) + + # commit changes + self.cli_commit() + + # verify configuration + config = read_file(LLDPD_CONF) + + interface_list = [] + for interface in Section.interfaces('ethernet'): + if not '.' in interface: + interface_list.append(interface) + tmp = ','.join(interface_list) + self.assertIn(f'configure system interface pattern "{tmp}"', config) + + def test_04_lldp_all_interfaces(self): + self.cli_set(base_path + ['interface', 'all']) + # commit changes + self.cli_commit() + + # verify configuration + config = read_file(LLDPD_CONF) + self.assertIn(f'configure system interface pattern "*"', config) + + def test_05_lldp_location(self): + interface = 'eth0' + elin = '1234567890' + self.cli_set(base_path + ['interface', interface, 'location', 'elin', elin]) + + # commit changes + self.cli_commit() + + # verify configuration + config = read_file(LLDPD_CONF) + + self.assertIn(f'configure ports {interface} med location elin "{elin}"', config) + self.assertIn(f'configure system interface pattern "{interface}"', config) + + def test_06_lldp_snmp(self): + self.cli_set(base_path + ['snmp']) + + # verify - can not start lldp snmp without snmp beeing configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(['service', 'snmp']) + self.cli_commit() + + # SNMP required process to be started with -x option + tmp = read_file('/etc/default/lldpd') + self.assertIn('-x', tmp) + + self.cli_delete(['service', 'snmp']) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_mdns_repeater.py b/smoketest/scripts/cli/test_service_mdns_repeater.py new file mode 100644 index 0000000..f2fb3b5 --- /dev/null +++ b/smoketest/scripts/cli/test_service_mdns_repeater.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from configparser import ConfigParser +from vyos.configsession import ConfigSessionError +from vyos.utils.process import process_named_running + +base_path = ['service', 'mdns', 'repeater'] +intf_base = ['interfaces', 'dummy'] +config_file = '/run/avahi-daemon/avahi-daemon.conf' + + +class TestServiceMDNSrepeater(VyOSUnitTestSHIM.TestCase): + def setUp(self): + # Start with a clean CLI instance + self.cli_delete(base_path) + + # Service required a configured IP address on the interface + self.cli_set(intf_base + ['dum10', 'address', '192.0.2.1/30']) + self.cli_set(intf_base + ['dum10', 'ipv6', 'address', 'no-default-link-local']) + self.cli_set(intf_base + ['dum20', 'address', '192.0.2.5/30']) + self.cli_set(intf_base + ['dum20', 'address', '2001:db8:0:2::5/64']) + self.cli_set(intf_base + ['dum30', 'address', '192.0.2.9/30']) + self.cli_set(intf_base + ['dum30', 'address', '2001:db8:0:2::9/64']) + self.cli_set(intf_base + ['dum40', 'address', '2001:db8:0:2::11/64']) + self.cli_commit() + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running('avahi-daemon')) + + self.cli_delete(base_path) + self.cli_delete(intf_base + ['dum10']) + self.cli_delete(intf_base + ['dum20']) + self.cli_delete(intf_base + ['dum30']) + self.cli_delete(intf_base + ['dum40']) + self.cli_commit() + + # Check that there is no longer a running process + self.assertFalse(process_named_running('avahi-daemon')) + + def test_service_dual_stack(self): + # mDNS browsing domains in addition to the default one (local) + domains = ['dom1.home.arpa', 'dom2.home.arpa'] + + # mDNS services to be repeated + services = ['_ipp._tcp', '_smb._tcp', '_ssh._tcp'] + + self.cli_set(base_path + ['ip-version', 'both']) + self.cli_set(base_path + ['interface', 'dum20']) + self.cli_set(base_path + ['interface', 'dum30']) + + for domain in domains: + self.cli_set(base_path + ['browse-domain', domain]) + + for service in services: + self.cli_set(base_path + ['allow-service', service]) + + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(delimiters='=') + conf.read(config_file) + + self.assertEqual(conf['server']['use-ipv4'], 'yes') + self.assertEqual(conf['server']['use-ipv6'], 'yes') + self.assertEqual(conf['server']['allow-interfaces'], 'dum20, dum30') + self.assertEqual(conf['server']['browse-domains'], ', '.join(domains)) + self.assertEqual(conf['reflector']['enable-reflector'], 'yes') + self.assertEqual(conf['reflector']['reflect-filters'], ', '.join(services)) + + def test_service_ipv4(self): + # partcipating interfaces should have IPv4 addresses + self.cli_set(base_path + ['ip-version', 'ipv4']) + self.cli_set(base_path + ['interface', 'dum10']) + self.cli_set(base_path + ['interface', 'dum40']) + + # exception is raised if partcipating interfaces do not have IPv4 address + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', 'dum40']) + self.cli_set(base_path + ['interface', 'dum20']) + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(delimiters='=') + conf.read(config_file) + + self.assertEqual(conf['server']['use-ipv4'], 'yes') + self.assertEqual(conf['server']['use-ipv6'], 'no') + self.assertEqual(conf['server']['allow-interfaces'], 'dum10, dum20') + self.assertEqual(conf['reflector']['enable-reflector'], 'yes') + + def test_service_ipv6(self): + # partcipating interfaces should have IPv6 addresses + self.cli_set(base_path + ['ip-version', 'ipv6']) + self.cli_set(base_path + ['interface', 'dum10']) + self.cli_set(base_path + ['interface', 'dum30']) + + # exception is raised if partcipating interfaces do not have IPv4 address + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', 'dum10']) + self.cli_set(base_path + ['interface', 'dum40']) + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(delimiters='=') + conf.read(config_file) + + self.assertEqual(conf['server']['use-ipv4'], 'no') + self.assertEqual(conf['server']['use-ipv6'], 'yes') + self.assertEqual(conf['server']['allow-interfaces'], 'dum30, dum40') + self.assertEqual(conf['reflector']['enable-reflector'], 'yes') + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_monitoring_telegraf.py b/smoketest/scripts/cli/test_service_monitoring_telegraf.py new file mode 100644 index 0000000..886b886 --- /dev/null +++ b/smoketest/scripts/cli/test_service_monitoring_telegraf.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError + +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +PROCESS_NAME = 'telegraf' +TELEGRAF_CONF = '/run/telegraf/telegraf.conf' +base_path = ['service', 'monitoring', 'telegraf'] +org = 'log@in.local' +token = 'GuRJc12tIzfjnYdKRAIYbxdWd2aTpOT9PVYNddzDnFV4HkAcD7u7-kndTFXjGuXzJN6TTxmrvPODB4mnFcseDV==' +port = '8888' +url = 'https://foo.local' +bucket = 'main' +inputs = ['cpu', 'disk', 'mem', 'net', 'system', 'kernel', 'interrupts', 'syslog'] + +class TestMonitoringTelegraf(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(base_path) + self.cli_commit() + + # Check for not longer running process + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_basic_config(self): + self.cli_set(base_path + ['influxdb', 'authentication', 'organization', org]) + self.cli_set(base_path + ['influxdb', 'authentication', 'token', token]) + self.cli_set(base_path + ['influxdb', 'port', port]) + self.cli_set(base_path + ['influxdb', 'url', url]) + + # commit changes + self.cli_commit() + + config = read_file(TELEGRAF_CONF) + + # Check telegraf config + self.assertIn(f'organization = "{org}"', config) + self.assertIn(f' token = "$INFLUX_TOKEN"', config) + self.assertIn(f'urls = ["{url}:{port}"]', config) + self.assertIn(f'bucket = "{bucket}"', config) + self.assertIn(f'[[inputs.exec]]', config) + + for input in inputs: + self.assertIn(input, config) + + def test_02_loki(self): + label = 'r123' + loki_url = 'http://localhost' + port = '3100' + loki_username = 'VyOS' + loki_password = 'PassW0Rd_VyOS' + + self.cli_set(base_path + ['loki', 'url', loki_url]) + self.cli_set(base_path + ['loki', 'port', port]) + self.cli_set(base_path + ['loki', 'metric-name-label', label]) + + self.cli_set(base_path + ['loki', 'authentication', 'username', loki_username]) + # password not set + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['loki', 'authentication', 'password', loki_password]) + + # commit changes + self.cli_commit() + + config = read_file(TELEGRAF_CONF) + self.assertIn(f'[[outputs.loki]]', config) + self.assertIn(f'domain = "{loki_url}:{port}"', config) + self.assertIn(f'metric_name_label = "{label}"', config) + self.assertIn(f'username = "{loki_username}"', config) + self.assertIn(f'password = "{loki_password}"', config) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_monitoring_zabbix-agent.py b/smoketest/scripts/cli/test_service_monitoring_zabbix-agent.py new file mode 100644 index 0000000..a60dae0 --- /dev/null +++ b/smoketest/scripts/cli/test_service_monitoring_zabbix-agent.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + + +PROCESS_NAME = 'zabbix_agent2' +ZABBIX_AGENT_CONF = '/run/zabbix/zabbix-agent2.conf' +base_path = ['service', 'monitoring', 'zabbix-agent'] + + +class TestZabbixAgent(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(base_path) + self.cli_commit() + + # Process must be terminated after deleting the config + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_zabbix_agent(self): + directory = '/tmp' + buffer_send = '8' + buffer_size = '120' + log_level = {'warning': '3'} + log_size = '1' + servers = ['192.0.2.1', '2001:db8::1'] + servers_active = {'192.0.2.5': {'port': '10051'}, '2001:db8::123': {'port': '10052'}} + port = '10050' + timeout = '5' + listen_ip = '0.0.0.0' + hostname = 'r-vyos' + + self.cli_set(base_path + ['directory', directory]) + self.cli_set(base_path + ['limits', 'buffer-flush-interval', buffer_send]) + self.cli_set(base_path + ['limits', 'buffer-size', buffer_size]) + self.cli_set(base_path + ['log', 'debug-level', next(iter(log_level))]) + self.cli_set(base_path + ['log', 'size', log_size]) + for server in servers: + self.cli_set(base_path + ['server', server]) + for server_active, server_config in servers_active.items(): + self.cli_set(base_path + ['server-active', server_active, 'port', server_config['port']]) + self.cli_set(base_path + ['timeout', timeout]) + self.cli_set(base_path + ['host-name', hostname]) + + # commit changes + self.cli_commit() + + config = read_file(ZABBIX_AGENT_CONF) + + self.assertIn(f'LogFileSize={log_size}', config) + self.assertIn(f'DebugLevel={log_level.get("warning")}', config) + + self.assertIn(f'Server={",".join(sorted(servers))}', config) + tmp = 'ServerActive=192.0.2.5:10051,[2001:db8::123]:10052' + self.assertIn(tmp, config) + + self.assertIn(f'ListenPort={port}', config) + self.assertIn(f'ListenIP={listen_ip}', config) + self.assertIn(f'BufferSend={buffer_send}', config) + self.assertIn(f'BufferSize={buffer_size}', config) + self.assertIn(f'Include={directory}/*.conf', config) + self.assertIn(f'Timeout={timeout}', config) + self.assertIn(f'Hostname={hostname}', config) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_ndp-proxy.py b/smoketest/scripts/cli/test_service_ndp-proxy.py new file mode 100644 index 0000000..dfdb3f6 --- /dev/null +++ b/smoketest/scripts/cli/test_service_ndp-proxy.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.ifconfig import Section +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'ndppd' +NDPPD_CONF = '/run/ndppd/ndppd.conf' +base_path = ['service', 'ndp-proxy'] + +def getConfigSection(string=None, end=' {', endsection='^}'): + tmp = f'cat {NDPPD_CONF} | sed -n "/^{string}{end}/,/{endsection}/p"' + out = cmd(tmp) + return out + +class TestServiceNDPProxy(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestServiceNDPProxy, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + # delete testing SSH config + self.cli_delete(base_path) + self.cli_commit() + + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_basic(self): + interfaces = Section.interfaces('ethernet') + for interface in interfaces: + self.cli_set(base_path + ['interface', interface]) + self.cli_set(base_path + ['interface', interface, 'enable-router-bit']) + + self.cli_commit() + + for interface in interfaces: + config = getConfigSection(f'proxy {interface}') + self.assertIn(f'proxy {interface}', config) + self.assertIn(f'router yes', config) + self.assertIn(f'timeout 500', config) # default value + self.assertIn(f'ttl 30000', config) # default value + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_ntp.py b/smoketest/scripts/cli/test_service_ntp.py new file mode 100644 index 0000000..07af4f5 --- /dev/null +++ b/smoketest/scripts/cli/test_service_ntp.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.xml_ref import default_value + +PROCESS_NAME = 'chronyd' +NTP_CONF = '/run/chrony/chrony.conf' +base_path = ['service', 'ntp'] + +class TestSystemNTP(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestSystemNTP, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(base_path) + self.cli_commit() + + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_base_options(self): + # Test basic NTP support with multiple servers and their options + servers = ['192.0.2.1', '192.0.2.2'] + options = ['nts', 'noselect', 'prefer'] + pools = ['pool.vyos.io'] + + for server in servers: + for option in options: + self.cli_set(base_path + ['server', server, option]) + + # Test NTP pool + for pool in pools: + self.cli_set(base_path + ['server', pool, 'pool']) + + # commit changes + self.cli_commit() + + # Check generated configuration + # this file must be read with higher permissions + config = cmd(f'sudo cat {NTP_CONF}') + self.assertIn('driftfile /run/chrony/drift', config) + self.assertIn('dumpdir /run/chrony', config) + self.assertIn('ntsdumpdir /run/chrony', config) + self.assertIn('clientloglimit 1048576', config) + self.assertIn('rtcsync', config) + self.assertIn('makestep 1.0 3', config) + self.assertIn('leapsectz right/UTC', config) + + for server in servers: + self.assertIn(f'server {server} iburst ' + ' '.join(options), config) + + for pool in pools: + self.assertIn(f'pool {pool} iburst', config) + + def test_clients(self): + # Test the allowed-networks statement + listen_address = ['127.0.0.1', '::1'] + for listen in listen_address: + self.cli_set(base_path + ['listen-address', listen]) + + networks = ['192.0.2.0/24', '2001:db8:1000::/64', '100.64.0.0', '2001:db8::ffff'] + for network in networks: + self.cli_set(base_path + ['allow-client', 'address', network]) + + # Verify "NTP server not configured" verify() statement + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + servers = ['192.0.2.1', '192.0.2.2'] + for server in servers: + self.cli_set(base_path + ['server', server]) + + self.cli_commit() + + # Check generated client address configuration + # this file must be read with higher permissions + config = cmd(f'sudo cat {NTP_CONF}') + for network in networks: + self.assertIn(f'allow {network}', config) + + # Check listen address + for listen in listen_address: + self.assertIn(f'bindaddress {listen}', config) + + def test_interface(self): + interfaces = ['eth0'] + for interface in interfaces: + self.cli_set(base_path + ['interface', interface]) + + servers = ['time1.vyos.net', 'time2.vyos.net'] + for server in servers: + self.cli_set(base_path + ['server', server]) + + self.cli_commit() + + # Check generated client address configuration + # this file must be read with higher permissions + config = cmd(f'sudo cat {NTP_CONF}') + for interface in interfaces: + self.assertIn(f'binddevice {interface}', config) + + def test_vrf(self): + vrf_name = 'vyos-mgmt' + + self.cli_set(['vrf', 'name', vrf_name, 'table', '12345']) + self.cli_set(base_path + ['vrf', vrf_name]) + + servers = ['time1.vyos.net', 'time2.vyos.net'] + for server in servers: + self.cli_set(base_path + ['server', server]) + + self.cli_commit() + + # Check for process in VRF + tmp = cmd(f'ip vrf pids {vrf_name}') + self.assertIn(PROCESS_NAME, tmp) + + self.cli_delete(['vrf', 'name', vrf_name]) + + def test_leap_seconds(self): + servers = ['time1.vyos.net', 'time2.vyos.net'] + for server in servers: + self.cli_set(base_path + ['server', server]) + + self.cli_commit() + + # Check generated client address configuration + # this file must be read with higher permissions + config = cmd(f'sudo cat {NTP_CONF}') + self.assertIn('leapsectz right/UTC', config) # CLI default + + for mode in ['ignore', 'system', 'smear']: + self.cli_set(base_path + ['leap-second', mode]) + self.cli_commit() + config = cmd(f'sudo cat {NTP_CONF}') + if mode != 'smear': + self.assertIn(f'leapsecmode {mode}', config) + else: + self.assertIn(f'leapsecmode slew', config) + self.assertIn(f'maxslewrate 1000', config) + self.assertIn(f'smoothtime 400 0.001024 leaponly', config) + + def test_interleave_option(self): + # "interleave" option differs from some others in that the + # name is not a 1:1 mapping from VyOS config + servers = ['192.0.2.1', '192.0.2.2'] + options = ['prefer'] + + for server in servers: + for option in options: + self.cli_set(base_path + ['server', server, option]) + self.cli_set(base_path + ['server', server, 'interleave']) + + # commit changes + self.cli_commit() + + # Check generated configuration + # this file must be read with higher permissions + config = cmd(f'sudo cat {NTP_CONF}') + self.assertIn('driftfile /run/chrony/drift', config) + self.assertIn('dumpdir /run/chrony', config) + self.assertIn('ntsdumpdir /run/chrony', config) + self.assertIn('clientloglimit 1048576', config) + self.assertIn('rtcsync', config) + self.assertIn('makestep 1.0 3', config) + self.assertIn('leapsectz right/UTC', config) + + for server in servers: + self.assertIn(f'server {server} iburst ' + ' '.join(options) + ' xleave', config) + + def test_offload_timestamp_default(self): + # Test offloading of NIC timestamp + servers = ['192.0.2.1', '192.0.2.2'] + ptp_port = '8319' + + for server in servers: + self.cli_set(base_path + ['server', server, 'ptp']) + + self.cli_set(base_path + ['ptp', 'port', ptp_port]) + self.cli_set(base_path + ['ptp', 'timestamp', 'interface', 'all']) + + # commit changes + self.cli_commit() + + # Check generated configuration + # this file must be read with higher permissions + config = cmd(f'sudo cat {NTP_CONF}') + self.assertIn('driftfile /run/chrony/drift', config) + self.assertIn('dumpdir /run/chrony', config) + self.assertIn('ntsdumpdir /run/chrony', config) + self.assertIn('clientloglimit 1048576', config) + self.assertIn('rtcsync', config) + self.assertIn('makestep 1.0 3', config) + self.assertIn('leapsectz right/UTC', config) + + for server in servers: + self.assertIn(f'server {server} iburst port {ptp_port}', config) + + self.assertIn('hwtimestamp *', config) + + def test_ptp_transport(self): + # Test offloading of NIC timestamp + servers = ['192.0.2.1', '192.0.2.2'] + options = ['prefer'] + + default_ptp_port = default_value(base_path + ['ptp', 'port']) + + for server in servers: + for option in options: + self.cli_set(base_path + ['server', server, option]) + self.cli_set(base_path + ['server', server, 'ptp']) + + # commit changes (expected to fail) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # add the required top-level option and commit + self.cli_set(base_path + ['ptp']) + self.cli_commit() + + # Check generated configuration + # this file must be read with higher permissions + config = cmd(f'sudo cat {NTP_CONF}') + self.assertIn('driftfile /run/chrony/drift', config) + self.assertIn('dumpdir /run/chrony', config) + self.assertIn('ntsdumpdir /run/chrony', config) + self.assertIn('clientloglimit 1048576', config) + self.assertIn('rtcsync', config) + self.assertIn('makestep 1.0 3', config) + self.assertIn('leapsectz right/UTC', config) + + for server in servers: + self.assertIn(f'server {server} iburst ' + ' '.join(options) + f' port {default_ptp_port}', config) + + self.assertIn(f'ptpport {default_ptp_port}', config) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py new file mode 100644 index 0000000..8cd87e0 --- /dev/null +++ b/smoketest/scripts/cli/test_service_pppoe-server.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_accel_ppp_test import BasicAccelPPPTest + +from configparser import ConfigParser +from vyos.utils.file import read_file +from vyos.template import range_to_regex +from vyos.configsession import ConfigSessionError + +local_if = ['interfaces', 'dummy', 'dum667'] +ac_name = 'ACN' +interface = 'eth0' + +class TestServicePPPoEServer(BasicAccelPPPTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['service', 'pppoe-server'] + cls._config_file = '/run/accel-pppd/pppoe.conf' + cls._chap_secrets = '/run/accel-pppd/pppoe.chap-secrets' + cls._protocol_section = 'pppoe' + # call base-classes classmethod + super(TestServicePPPoEServer, cls).setUpClass() + + def tearDown(self): + self.cli_delete(local_if) + super().tearDown() + + def verify(self, conf): + mtu = '1492' + + # validate some common values in the configuration + for tmp in ['log_syslog', 'pppoe', 'ippool', + 'auth_mschap_v2', 'auth_mschap_v1', 'auth_chap_md5', + 'auth_pap', 'shaper']: + # Settings without values provide None + self.assertEqual(conf['modules'][tmp], None) + + # check Access Concentrator setting + self.assertTrue(conf['pppoe']['ac-name'] == ac_name) + self.assertTrue(conf['pppoe'].getboolean('verbose')) + self.assertTrue(conf['pppoe']['interface'], interface) + + # check ppp + self.assertTrue(conf['ppp'].getboolean('verbose')) + self.assertTrue(conf['ppp'].getboolean('check-ip')) + self.assertEqual(conf['ppp']['mtu'], mtu) + + super().verify(conf) + + def basic_protocol_specific_config(self): + self.cli_set(local_if + ['address', '192.0.2.1/32']) + self.set(['access-concentrator', ac_name]) + self.set(['interface', interface]) + + def test_pppoe_limits(self): + self.basic_config() + self.set(['limits', 'connection-limit', '20/min']) + self.cli_commit() + conf = ConfigParser(allow_no_value=True, delimiters='=') + conf.read(self._config_file) + self.assertEqual(conf['connlimit']['limit'], '20/min') + + def test_pppoe_server_authentication_protocols(self): + # Test configuration of local authentication for PPPoE server + self.basic_config() + + # explicitly test mschap-v2 - no special reason + self.set( ['authentication', 'protocols', 'mschap-v2']) + + # commit changes + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True) + conf.read(self._config_file) + + self.assertEqual(conf['modules']['auth_mschap_v2'], None) + + def test_pppoe_server_shaper(self): + fwmark = '223' + limiter = 'tbf' + self.basic_config() + + self.set(['shaper', 'fwmark', fwmark]) + # commit changes + + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True, delimiters='=') + conf.read(self._config_file) + + # basic verification + self.verify(conf) + + self.assertEqual(conf['shaper']['fwmark'], fwmark) + self.assertEqual(conf['shaper']['down-limiter'], limiter) + + def test_accel_radius_authentication(self): + radius_called_sid = 'ifname:mac' + + self.set(['authentication', 'radius', 'called-sid-format', radius_called_sid]) + + # run common tests + super().test_accel_radius_authentication() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True, delimiters='=') + conf.read(self._config_file) + + # Validate configuration + self.assertEqual(conf['pppoe']['called-sid'], radius_called_sid) + + def test_pppoe_server_vlan(self): + + vlans = ['100', '200', '300-310'] + + # Test configuration of local authentication for PPPoE server + self.basic_config() + + self.set(['interface', interface, 'vlan-mon']) + + # cannot use option "vlan-mon" if no "vlan" set + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + for vlan in vlans: + self.set(['interface', interface, 'vlan', vlan]) + + # commit changes + self.cli_commit() + + # Validate configuration values + config = read_file(self._config_file) + for vlan in vlans: + tmp = range_to_regex(vlan) + self.assertIn(f'interface=re:^{interface}\.{tmp}$', config) + + tmp = ','.join(vlans) + self.assertIn(f'vlan-mon={interface},{tmp}', config) + + def test_pppoe_server_pado_delay(self): + delay_without_sessions = '10' + delays = {'20': '200', '30': '300'} + + self.basic_config() + + self.set(['pado-delay', delay_without_sessions]) + self.cli_commit() + + conf = ConfigParser(allow_no_value=True, delimiters='=') + conf.read(self._config_file) + self.assertEqual(conf['pppoe']['pado-delay'], delay_without_sessions) + + for delay, sessions in delays.items(): + self.set(['pado-delay', delay, 'sessions', sessions]) + self.cli_commit() + + conf = ConfigParser(allow_no_value=True, delimiters='=') + conf.read(self._config_file) + + self.assertEqual(conf['pppoe']['pado-delay'], '10,20:200,30:300') + + self.set(['pado-delay', 'disable', 'sessions', '400']) + self.cli_commit() + + conf = ConfigParser(allow_no_value=True, delimiters='=') + conf.read(self._config_file) + self.assertEqual(conf['pppoe']['pado-delay'], '10,20:200,30:300,-1:400') + + def test_pppoe_server_any_login(self): + # Test configuration of local authentication for PPPoE server + self.basic_config() + + self.set(['authentication', 'any-login']) + self.cli_commit() + + # Validate configuration values + config = read_file(self._config_file) + self.assertIn('any-login=1', config) + + def test_pppoe_server_accept_service(self): + services = ['user1-service', 'user2-service'] + self.basic_config() + + for service in services: + self.set(['service-name', service]) + self.set(['accept-any-service']) + self.set(['accept-blank-service']) + self.cli_commit() + + # Validate configuration values + config = read_file(self._config_file) + self.assertIn(f'service-name={",".join(services)}', config) + self.assertIn('accept-any-service=1', config) + self.assertIn('accept-blank-service=1', config) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py new file mode 100644 index 0000000..6dbb6ad --- /dev/null +++ b/smoketest/scripts/cli/test_service_router-advert.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import unittest + +from vyos.configsession import ConfigSessionError +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.utils.file import read_file +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'radvd' +RADVD_CONF = '/run/radvd/radvd.conf' + +interface = 'eth1' +base_path = ['service', 'router-advert', 'interface', interface] +address_base = ['interfaces', 'ethernet', interface, 'address'] +prefix = '::/64' + +def get_config_value(key): + tmp = read_file(RADVD_CONF) + tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) + return tmp[0].split()[0].replace(';','') + +class TestServiceRADVD(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestServiceRADVD, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, ['service', 'router-advert']) + + cls.cli_set(cls, address_base + ['2001:db8::1/64']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, address_base) + super(TestServiceRADVD, cls).tearDownClass() + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(base_path) + self.cli_commit() + + # Check for no longer running process + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_common(self): + self.cli_set(base_path + ['prefix', prefix, 'no-on-link-flag']) + self.cli_set(base_path + ['prefix', prefix, 'no-autonomous-flag']) + self.cli_set(base_path + ['prefix', prefix, 'valid-lifetime', 'infinity']) + self.cli_set(base_path + ['other-config-flag']) + + # commit changes + self.cli_commit() + + # verify values + tmp = get_config_value('interface') + self.assertEqual(tmp, interface) + + tmp = get_config_value('prefix') + self.assertEqual(tmp, prefix) + + tmp = get_config_value('AdvOtherConfigFlag') + self.assertEqual(tmp, 'on') + + # this is a default value + tmp = get_config_value('AdvRetransTimer') + self.assertEqual(tmp, '0') + + # this is a default value + tmp = get_config_value('AdvCurHopLimit') + self.assertEqual(tmp, '64') + + # this is a default value + tmp = get_config_value('AdvDefaultPreference') + self.assertEqual(tmp, 'medium') + + tmp = get_config_value('AdvAutonomous') + self.assertEqual(tmp, 'off') + + # this is a default value + tmp = get_config_value('AdvValidLifetime') + self.assertEqual(tmp, 'infinity') + + # this is a default value + tmp = get_config_value('AdvPreferredLifetime') + self.assertEqual(tmp, '14400') + + tmp = get_config_value('AdvOnLink') + self.assertEqual(tmp, 'off') + + tmp = get_config_value('DeprecatePrefix') + self.assertEqual(tmp, 'off') + + tmp = get_config_value('DecrementLifetimes') + self.assertEqual(tmp, 'off') + + def test_dns(self): + nameserver = ['2001:db8::1', '2001:db8::2'] + dnssl = ['vyos.net', 'vyos.io'] + ns_lifetime = '599' + + self.cli_set(base_path + ['prefix', prefix, 'valid-lifetime', 'infinity']) + self.cli_set(base_path + ['other-config-flag']) + + for ns in nameserver: + self.cli_set(base_path + ['name-server', ns]) + for sl in dnssl: + self.cli_set(base_path + ['dnssl', sl]) + + self.cli_set(base_path + ['name-server-lifetime', ns_lifetime]) + # The value, if not 0, must be at least interval max (defaults to 600). + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + ns_lifetime = '600' + self.cli_set(base_path + ['name-server-lifetime', ns_lifetime]) + + # commit changes + self.cli_commit() + + config = read_file(RADVD_CONF) + + tmp = 'RDNSS ' + ' '.join(nameserver) + ' {' + self.assertIn(tmp, config) + + tmp = f'AdvRDNSSLifetime {ns_lifetime};' + self.assertIn(tmp, config) + + tmp = 'DNSSL ' + ' '.join(dnssl) + ' {' + self.assertIn(tmp, config) + + def test_deprecate_prefix(self): + self.cli_set(base_path + ['prefix', prefix, 'valid-lifetime', 'infinity']) + self.cli_set(base_path + ['prefix', prefix, 'deprecate-prefix']) + self.cli_set(base_path + ['prefix', prefix, 'decrement-lifetime']) + + # commit changes + self.cli_commit() + + tmp = get_config_value('DeprecatePrefix') + self.assertEqual(tmp, 'on') + + tmp = get_config_value('DecrementLifetimes') + self.assertEqual(tmp, 'on') + + def test_route(self): + route = '2001:db8:1000::/64' + + self.cli_set(base_path + ['prefix', prefix]) + self.cli_set(base_path + ['route', route]) + + # commit changes + self.cli_commit() + + config = read_file(RADVD_CONF) + + tmp = f'route {route}' + ' {' + self.assertIn(tmp, config) + + self.assertIn('AdvRouteLifetime 1800;', config) + self.assertIn('AdvRoutePreference medium;', config) + self.assertIn('RemoveRoute on;', config) + + def test_rasrcaddress(self): + ra_src = ['fe80::1', 'fe80::2'] + + self.cli_set(base_path + ['prefix', prefix]) + for src in ra_src: + self.cli_set(base_path + ['source-address', src]) + + # commit changes + self.cli_commit() + + config = read_file(RADVD_CONF) + self.assertIn('AdvRASrcAddress {', config) + for src in ra_src: + self.assertIn(f' {src};', config) + + def test_nat64prefix(self): + nat64prefix = '64:ff9b::/96' + nat64prefix_invalid = '64:ff9b::/44' + + self.cli_set(base_path + ['nat64prefix', nat64prefix]) + + # and another invalid prefix + # Invalid NAT64 prefix length for "2001:db8::/34", can only be one of: + # /32, /40, /48, /56, /64, /96 + self.cli_set(base_path + ['nat64prefix', nat64prefix_invalid]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['nat64prefix', nat64prefix_invalid]) + + # NAT64 valid-lifetime must not be smaller then "interval max" + self.cli_set(base_path + ['nat64prefix', nat64prefix, 'valid-lifetime', '500']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['nat64prefix', nat64prefix, 'valid-lifetime']) + + # commit changes + self.cli_commit() + + config = read_file(RADVD_CONF) + + tmp = f'nat64prefix {nat64prefix}' + ' {' + self.assertIn(tmp, config) + self.assertIn('AdvValidLifetime 65528;', config) # default + + def test_advsendadvert_advintervalopt(self): + ra_src = ['fe80::1', 'fe80::2'] + + self.cli_set(base_path + ['prefix', prefix]) + self.cli_set(base_path + ['no-send-advert']) + # commit changes + self.cli_commit() + + # Verify generated configuration + config = read_file(RADVD_CONF) + tmp = get_config_value('AdvSendAdvert') + self.assertEqual(tmp, 'off') + + tmp = get_config_value('AdvIntervalOpt') + self.assertEqual(tmp, 'on') + + self.cli_set(base_path + ['no-send-interval']) + # commit changes + self.cli_commit() + + # Verify generated configuration + config = read_file(RADVD_CONF) + tmp = get_config_value('AdvSendAdvert') + self.assertEqual(tmp, 'off') + + tmp = get_config_value('AdvIntervalOpt') + self.assertEqual(tmp, 'off') + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_salt-minion.py b/smoketest/scripts/cli/test_service_salt-minion.py new file mode 100644 index 0000000..48a588b --- /dev/null +++ b/smoketest/scripts/cli/test_service_salt-minion.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from socket import gethostname +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.utils.process import cmd + +PROCESS_NAME = 'salt-minion' +SALT_CONF = '/etc/salt/minion' +base_path = ['service', 'salt-minion'] + +interface = 'dum4456' + +class TestServiceSALT(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestServiceSALT, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + cls.cli_set(cls, ['interfaces', 'dummy', interface, 'address', '100.64.0.1/16']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'dummy', interface]) + super(TestServiceSALT, cls).tearDownClass() + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + # delete testing SALT config + self.cli_delete(base_path) + self.cli_commit() + + # For an unknown reason on QEMU systems (e.g. where smoketests are executed + # from the CI) salt-minion process is not killed by systemd. Apparently + # no issue on VMWare. + if cmd('systemd-detect-virt') != 'kvm': + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_default(self): + servers = ['192.0.2.1', '192.0.2.2'] + + for server in servers: + self.cli_set(base_path + ['master', server]) + + self.cli_commit() + + # commiconf = read_file() Check configured port + conf = read_file(SALT_CONF) + self.assertIn(f' - {server}', conf) + + # defaults + hostname = gethostname() + self.assertIn(f'hash_type: sha256', conf) + self.assertIn(f'id: {hostname}', conf) + self.assertIn(f'mine_interval: 60', conf) + + def test_options(self): + server = '192.0.2.3' + hash = 'sha1' + id = 'foo' + interval = '120' + + self.cli_set(base_path + ['master', server]) + self.cli_set(base_path + ['hash', hash]) + self.cli_set(base_path + ['id', id]) + self.cli_set(base_path + ['interval', interval]) + self.cli_set(base_path + ['source-interface', interface]) + + self.cli_commit() + + # commiconf = read_file() Check configured port + conf = read_file(SALT_CONF) + self.assertIn(f'- {server}', conf) + + # defaults + self.assertIn(f'hash_type: {hash}', conf) + self.assertIn(f'id: {id}', conf) + self.assertIn(f'mine_interval: {interval}', conf) + self.assertIn(f'source_interface_name: {interface}', conf) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_snmp.py b/smoketest/scripts/cli/test_service_snmp.py new file mode 100644 index 0000000..7d5eaa4 --- /dev/null +++ b/smoketest/scripts/cli/test_service_snmp.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.template import is_ipv4 +from vyos.template import address_from_cidr +from vyos.utils.process import call +from vyos.utils.process import DEVNULL +from vyos.utils.file import read_file +from vyos.utils.process import process_named_running +from vyos.version import get_version_data + +PROCESS_NAME = 'snmpd' +SNMPD_CONF = '/etc/snmp/snmpd.conf' + +base_path = ['service', 'snmp'] + +snmpv3_group = 'default_group' +snmpv3_view = 'default_view' +snmpv3_view_oid = '1' +snmpv3_user = 'vyos' +snmpv3_auth_pw = 'vyos12345678' +snmpv3_priv_pw = 'vyos87654321' +snmpv3_engine_id = '000000000000000000000002' + +def get_config_value(key): + tmp = read_file(SNMPD_CONF) + tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) + return tmp[0] + +class TestSNMPService(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestSNMPService, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + # delete testing SNMP config + self.cli_delete(base_path) + self.cli_commit() + + # Check for running process + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_snmp_basic(self): + dummy_if = 'dum7312' + dummy_addr = '100.64.0.1/32' + contact = 'maintainers@vyos.io' + location = 'QEMU' + + self.cli_set(['interfaces', 'dummy', dummy_if, 'address', dummy_addr]) + + # Check if SNMP can be configured and service runs + clients = ['192.0.2.1', '2001:db8::1'] + networks = ['192.0.2.128/25', '2001:db8:babe::/48'] + listen = ['127.0.0.1', '::1', address_from_cidr(dummy_addr)] + port = '5000' + + for auth in ['ro', 'rw']: + community = 'VyOS' + auth + self.cli_set(base_path + ['community', community, 'authorization', auth]) + for client in clients: + self.cli_set(base_path + ['community', community, 'client', client]) + for network in networks: + self.cli_set(base_path + ['community', community, 'network', network]) + + for addr in listen: + self.cli_set(base_path + ['listen-address', addr, 'port', port]) + + self.cli_set(base_path + ['contact', contact]) + self.cli_set(base_path + ['location', location]) + + self.cli_commit() + + # verify listen address, it will be returned as + # ['unix:/run/snmpd.socket,udp:127.0.0.1:161,udp6:[::1]:161'] + # thus we need to transfor this into a proper list + config = get_config_value('agentaddress') + expected = 'unix:/run/snmpd.socket' + self.assertIn(expected, config) + for addr in listen: + if is_ipv4(addr): + expected = f'udp:{addr}:{port}' + else: + expected = f'udp6:[{addr}]:{port}' + self.assertIn(expected, config) + + config = get_config_value('sysDescr') + version_data = get_version_data() + self.assertEqual('VyOS ' + version_data['version'], config) + + config = get_config_value('SysContact') + self.assertEqual(contact, config) + + config = get_config_value('SysLocation') + self.assertEqual(location, config) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + self.cli_delete(['interfaces', 'dummy', dummy_if]) + + ## Check communities and default view RESTRICTED + for auth in ['ro', 'rw']: + community = 'VyOS' + auth + for addr in clients: + if is_ipv4(addr): + entry = auth + 'community ' + community + ' ' + addr + ' -V' + else: + entry = auth + 'community6 ' + community + ' ' + addr + ' -V' + config = get_config_value(entry) + expected = 'RESTRICTED' + self.assertIn(expected, config) + for addr in networks: + if is_ipv4(addr): + entry = auth + 'community ' + community + ' ' + addr + ' -V' + else: + entry = auth + 'community6 ' + community + ' ' + addr + ' -V' + config = get_config_value(entry) + expected = 'RESTRICTED' + self.assertIn(expected, config) + # And finally check global entry for RESTRICTED view + config = get_config_value('view RESTRICTED included .1') + self.assertIn('80', config) + + def test_snmpv3_sha(self): + # Check if SNMPv3 can be configured with SHA authentication + # and service runs + self.cli_set(base_path + ['v3', 'engineid', snmpv3_engine_id]) + self.cli_set(base_path + ['v3', 'group', 'default', 'mode', 'ro']) + # check validate() - a view must be created before this can be committed + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['v3', 'view', 'default', 'oid', '1']) + self.cli_set(base_path + ['v3', 'group', 'default', 'view', 'default']) + + # create user + self.cli_set(base_path + ['v3', 'user', snmpv3_user, 'auth', 'plaintext-password', snmpv3_auth_pw]) + self.cli_set(base_path + ['v3', 'user', snmpv3_user, 'auth', 'type', 'sha']) + self.cli_set(base_path + ['v3', 'user', snmpv3_user, 'privacy', 'plaintext-password', snmpv3_priv_pw]) + self.cli_set(base_path + ['v3', 'user', snmpv3_user, 'privacy', 'type', 'aes']) + self.cli_set(base_path + ['v3', 'user', snmpv3_user, 'group', 'default']) + + self.cli_commit() + + # commit will alter the CLI values - check if they have been updated: + hashed_password = '4e52fe55fd011c9c51ae2c65f4b78ca93dcafdfe' + tmp = self._session.show_config(base_path + ['v3', 'user', snmpv3_user, 'auth', 'encrypted-password']).split()[1] + self.assertEqual(tmp, hashed_password) + + hashed_password = '54705c8de9e81fdf61ad7ac044fa8fe611ddff6b' + tmp = self._session.show_config(base_path + ['v3', 'user', snmpv3_user, 'privacy', 'encrypted-password']).split()[1] + self.assertEqual(tmp, hashed_password) + + # TODO: read in config file and check values + + # Try SNMPv3 connection + tmp = call(f'snmpwalk -v 3 -u {snmpv3_user} -a SHA -A {snmpv3_auth_pw} -x AES -X {snmpv3_priv_pw} -l authPriv 127.0.0.1', stdout=DEVNULL) + self.assertEqual(tmp, 0) + + def test_snmpv3_md5(self): + # Check if SNMPv3 can be configured with MD5 authentication + # and service runs + self.cli_set(base_path + ['v3', 'engineid', snmpv3_engine_id]) + + # create user + self.cli_set(base_path + ['v3', 'user', snmpv3_user, 'auth', 'plaintext-password', snmpv3_auth_pw]) + self.cli_set(base_path + ['v3', 'user', snmpv3_user, 'auth', 'type', 'md5']) + self.cli_set(base_path + ['v3', 'user', snmpv3_user, 'privacy', 'plaintext-password', snmpv3_priv_pw]) + self.cli_set(base_path + ['v3', 'user', snmpv3_user, 'privacy', 'type', 'des']) + + # check validate() - user requires a group to be created + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['v3', 'user', 'vyos', 'group', snmpv3_group]) + + self.cli_set(base_path + ['v3', 'group', snmpv3_group, 'mode', 'ro']) + # check validate() - a view must be created before this can be comitted + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['v3', 'view', snmpv3_view, 'oid', snmpv3_view_oid]) + self.cli_set(base_path + ['v3', 'group', snmpv3_group, 'view', snmpv3_view]) + + self.cli_commit() + + # commit will alter the CLI values - check if they have been updated: + hashed_password = '4c67690d45d3dfcd33d0d7e308e370ad' + tmp = self._session.show_config(base_path + ['v3', 'user', 'vyos', 'auth', 'encrypted-password']).split()[1] + self.assertEqual(tmp, hashed_password) + + hashed_password = 'e11c83f2c510540a3c4de84ee66de440' + tmp = self._session.show_config(base_path + ['v3', 'user', 'vyos', 'privacy', 'encrypted-password']).split()[1] + self.assertEqual(tmp, hashed_password) + + tmp = read_file(SNMPD_CONF) + # views + self.assertIn(f'view {snmpv3_view} included .{snmpv3_view_oid}', tmp) + # group + self.assertIn(f'group {snmpv3_group} usm {snmpv3_user}', tmp) + # access + self.assertIn(f'access {snmpv3_group} "" usm auth exact {snmpv3_view} none none', tmp) + + # Try SNMPv3 connection + tmp = call(f'snmpwalk -v 3 -u {snmpv3_user} -a MD5 -A {snmpv3_auth_pw} -x DES -X {snmpv3_priv_pw} -l authPriv 127.0.0.1', stdout=DEVNULL) + self.assertEqual(tmp, 0) + + def test_snmpv3_view_exclude(self): + snmpv3_view_oid_exclude = ['1.3.6.1.2.1.4.21', '1.3.6.1.2.1.4.24'] + + self.cli_set(base_path + ['v3', 'group', snmpv3_group, 'view', snmpv3_view]) + self.cli_set(base_path + ['v3', 'view', snmpv3_view, 'oid', snmpv3_view_oid]) + + for excluded in snmpv3_view_oid_exclude: + self.cli_set(base_path + ['v3', 'view', snmpv3_view, 'oid', snmpv3_view_oid, 'exclude', excluded]) + + self.cli_commit() + + tmp = read_file(SNMPD_CONF) + # views + self.assertIn(f'view {snmpv3_view} included .{snmpv3_view_oid}', tmp) + for excluded in snmpv3_view_oid_exclude: + self.assertIn(f'view {snmpv3_view} excluded .{excluded}', tmp) + + def test_snmp_script_extensions(self): + extensions = { + 'default': 'snmp_smoketest_extension_script.sh', + 'external': '/run/external_snmp_smoketest_extension_script.sh' + } + + for key, val in extensions.items(): + self.cli_set(base_path + ['script-extensions', 'extension-name', key, 'script', val]) + self.cli_commit() + + self.assertEqual(get_config_value('extend default'), f'/config/user-data/{extensions["default"]}') + self.assertEqual(get_config_value('extend external'), extensions["external"]) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py new file mode 100644 index 0000000..d8e325e --- /dev/null +++ b/smoketest/scripts/cli/test_service_ssh.py @@ -0,0 +1,325 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import paramiko +import re +import unittest + +from pwd import getpwall + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import cmd +from vyos.utils.process import is_systemd_service_running +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.xml_ref import default_value + +PROCESS_NAME = 'sshd' +SSHD_CONF = '/run/sshd/sshd_config' +base_path = ['service', 'ssh'] + +key_rsa = '/etc/ssh/ssh_host_rsa_key' +key_dsa = '/etc/ssh/ssh_host_dsa_key' +key_ed25519 = '/etc/ssh/ssh_host_ed25519_key' + +def get_config_value(key): + tmp = read_file(SSHD_CONF) + tmp = re.findall(f'\n?{key}\s+(.*)', tmp) + return tmp + +class TestServiceSSH(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestServiceSSH, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + cls.cli_delete(cls, ['vrf']) + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + # delete testing SSH config + self.cli_delete(base_path) + self.cli_delete(['vrf']) + self.cli_commit() + + self.assertTrue(os.path.isfile(key_rsa)) + self.assertTrue(os.path.isfile(key_dsa)) + self.assertTrue(os.path.isfile(key_ed25519)) + + # Established SSH connections remains running after service is stopped. + # We can not use process_named_running here - we rather need to check + # that the systemd service is no longer running + self.assertFalse(is_systemd_service_running(PROCESS_NAME)) + + def test_ssh_default(self): + # Check if SSH service runs with default settings - used for checking + # behavior of <defaultValue> in XML definition + self.cli_set(base_path) + + # commit changes + self.cli_commit() + + # Check configured port agains CLI default value + port = get_config_value('Port') + cli_default = default_value(base_path + ['port']) + self.assertEqual(port, cli_default) + + def test_ssh_single_listen_address(self): + # Check if SSH service can be configured and runs + self.cli_set(base_path + ['port', '1234']) + self.cli_set(base_path + ['disable-host-validation']) + self.cli_set(base_path + ['disable-password-authentication']) + self.cli_set(base_path + ['loglevel', 'verbose']) + self.cli_set(base_path + ['client-keepalive-interval', '100']) + self.cli_set(base_path + ['listen-address', '127.0.0.1']) + + # commit changes + self.cli_commit() + + # Check configured port + port = get_config_value('Port')[0] + self.assertTrue("1234" in port) + + # Check DNS usage + dns = get_config_value('UseDNS')[0] + self.assertTrue("no" in dns) + + # Check PasswordAuthentication + pwd = get_config_value('PasswordAuthentication')[0] + self.assertTrue("no" in pwd) + + # Check loglevel + loglevel = get_config_value('LogLevel')[0] + self.assertTrue("VERBOSE" in loglevel) + + # Check listen address + address = get_config_value('ListenAddress')[0] + self.assertTrue("127.0.0.1" in address) + + # Check keepalive + keepalive = get_config_value('ClientAliveInterval')[0] + self.assertTrue("100" in keepalive) + + def test_ssh_multiple_listen_addresses(self): + # Check if SSH service can be configured and runs with multiple + # listen ports and listen-addresses + ports = ['22', '2222', '2223', '2224'] + for port in ports: + self.cli_set(base_path + ['port', port]) + + addresses = ['127.0.0.1', '::1'] + for address in addresses: + self.cli_set(base_path + ['listen-address', address]) + + # commit changes + self.cli_commit() + + # Check configured port + tmp = get_config_value('Port') + for port in ports: + self.assertIn(port, tmp) + + # Check listen address + tmp = get_config_value('ListenAddress') + for address in addresses: + self.assertIn(address, tmp) + + def test_ssh_vrf_single(self): + vrf = 'mgmt' + # Check if SSH service can be bound to given VRF + self.cli_set(base_path + ['vrf', vrf]) + + # VRF does yet not exist - an error must be thrown + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(['vrf', 'name', vrf, 'table', '1338']) + + # commit changes + self.cli_commit() + + # Check for process in VRF + tmp = cmd(f'ip vrf pids {vrf}') + self.assertIn(PROCESS_NAME, tmp) + + def test_ssh_vrf_multi(self): + # Check if SSH service can be bound to multiple VRFs + vrfs = ['red', 'blue', 'green'] + for vrf in vrfs: + self.cli_set(base_path + ['vrf', vrf]) + + # VRF does yet not exist - an error must be thrown + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + table = 12345 + for vrf in vrfs: + self.cli_set(['vrf', 'name', vrf, 'table', str(table)]) + table += 1 + + # commit changes + self.cli_commit() + + # Check for process in VRF + for vrf in vrfs: + tmp = cmd(f'ip vrf pids {vrf}') + self.assertIn(PROCESS_NAME, tmp) + + def test_ssh_login(self): + # Perform SSH login and command execution with a predefined user. The + # result (output of uname -a) must match the output if the command is + # run natively. + # + # We also try to login as an invalid user - this is not allowed to work. + + test_user = 'ssh_test' + test_pass = 'v2i57DZs8idUwMN3VC92' + test_command = 'uname -a' + + self.cli_set(base_path) + self.cli_set(['system', 'login', 'user', test_user, 'authentication', 'plaintext-password', test_pass]) + + # commit changes + self.cli_commit() + + # Login with proper credentials + output, error = self.ssh_send_cmd(test_command, test_user, test_pass) + # verify login + self.assertFalse(error) + self.assertEqual(output, cmd(test_command)) + + # Login with invalid credentials + with self.assertRaises(paramiko.ssh_exception.AuthenticationException): + output, error = self.ssh_send_cmd(test_command, 'invalid_user', 'invalid_password') + + self.cli_delete(['system', 'login', 'user', test_user]) + self.cli_commit() + + # After deletion the test user is not allowed to remain in /etc/passwd + usernames = [x[0] for x in getpwall()] + self.assertNotIn(test_user, usernames) + + def test_ssh_dynamic_protection(self): + # check sshguard service + + SSHGUARD_CONFIG = '/etc/sshguard/sshguard.conf' + SSHGUARD_WHITELIST = '/etc/sshguard/whitelist' + SSHGUARD_PROCESS = 'sshguard' + block_time = '123' + detect_time = '1804' + port = '22' + threshold = '10' + allow_list = ['192.0.2.0/24', '2001:db8::/48'] + + self.cli_set(base_path + ['dynamic-protection', 'block-time', block_time]) + self.cli_set(base_path + ['dynamic-protection', 'detect-time', detect_time]) + self.cli_set(base_path + ['dynamic-protection', 'threshold', threshold]) + for allow in allow_list: + self.cli_set(base_path + ['dynamic-protection', 'allow-from', allow]) + + # commit changes + self.cli_commit() + + # Check configured port + tmp = get_config_value('Port') + self.assertIn(port, tmp) + + # Check sshgurad service + self.assertTrue(process_named_running(SSHGUARD_PROCESS)) + + sshguard_lines = [ + f'THRESHOLD={threshold}', + f'BLOCK_TIME={block_time}', + f'DETECTION_TIME={detect_time}' + ] + + tmp_sshguard_conf = read_file(SSHGUARD_CONFIG) + for line in sshguard_lines: + self.assertIn(line, tmp_sshguard_conf) + + tmp_whitelist_conf = read_file(SSHGUARD_WHITELIST) + for allow in allow_list: + self.assertIn(allow, tmp_whitelist_conf) + + # Delete service ssh dynamic-protection + # but not service ssh itself + self.cli_delete(base_path + ['dynamic-protection']) + self.cli_commit() + + self.assertFalse(process_named_running(SSHGUARD_PROCESS)) + + + # Network Device Collaborative Protection Profile + def test_ssh_ndcpp(self): + ciphers = ['aes128-cbc', 'aes128-ctr', 'aes256-cbc', 'aes256-ctr'] + host_key_algs = ['sk-ssh-ed25519@openssh.com', 'ssh-rsa', 'ssh-ed25519'] + kexes = ['diffie-hellman-group14-sha1', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521'] + macs = ['hmac-sha1', 'hmac-sha2-256', 'hmac-sha2-512'] + rekey_time = '60' + rekey_data = '1024' + + for cipher in ciphers: + self.cli_set(base_path + ['ciphers', cipher]) + for host_key in host_key_algs: + self.cli_set(base_path + ['hostkey-algorithm', host_key]) + for kex in kexes: + self.cli_set(base_path + ['key-exchange', kex]) + for mac in macs: + self.cli_set(base_path + ['mac', mac]) + # Optional rekey parameters + self.cli_set(base_path + ['rekey', 'data', rekey_data]) + self.cli_set(base_path + ['rekey', 'time', rekey_time]) + + # commit changes + self.cli_commit() + + ssh_lines = ['Ciphers aes128-cbc,aes128-ctr,aes256-cbc,aes256-ctr', + 'HostKeyAlgorithms sk-ssh-ed25519@openssh.com,ssh-rsa,ssh-ed25519', + 'MACs hmac-sha1,hmac-sha2-256,hmac-sha2-512', + 'KexAlgorithms diffie-hellman-group14-sha1,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521', + 'RekeyLimit 1024M 60M' + ] + tmp_sshd_conf = read_file(SSHD_CONF) + + for line in ssh_lines: + self.assertIn(line, tmp_sshd_conf) + + def test_ssh_pubkey_accepted_algorithm(self): + algs = ['ssh-ed25519', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', + 'ecdsa-sha2-nistp521', 'ssh-dss', 'ssh-rsa', 'rsa-sha2-256', + 'rsa-sha2-512' + ] + + expected = 'PubkeyAcceptedAlgorithms ' + for alg in algs: + self.cli_set(base_path + ['pubkey-accepted-algorithm', alg]) + expected = f'{expected}{alg},' + expected = expected[:-1] + + self.cli_commit() + tmp_sshd_conf = read_file(SSHD_CONF) + self.assertIn(expected, tmp_sshd_conf) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_stunnel.py b/smoketest/scripts/cli/test_service_stunnel.py new file mode 100644 index 0000000..3aeffd0 --- /dev/null +++ b/smoketest/scripts/cli/test_service_stunnel.py @@ -0,0 +1,624 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + + +PROCESS_NAME = 'stunnel' +STUNNEL_CONF = '/run/stunnel/stunnel.conf' +base_path = ['service', 'stunnel'] + +ca_certificate = """ +MIIDnTCCAoWgAwIBAgIUcSMo/zT/GUAyH3uM3Hr3qjCDmMUwDQYJKoZIhvcNAQELBQAwVzELMAkGA1U +EBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVn +lPUzEQMA4GA1UEAwwHdnlvcy5pbzAeFw0yNDA2MDQwNjU1MDFaFw0yOTA2MDMwNjU1MDFaMFcxCzAJB +gNVBAYTAkdCMRMwEQYDVQQIDApTb21lLVN0YXRlMRIwEAYDVQQHDAlTb21lLUNpdHkxDTALBgNVBAoM +BFZ5T1MxEDAOBgNVBAMMB3Z5b3MuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzN7B +Zw0OBBgeGL7KCKdDIUfBEhh08+3V8Nm7K23mU/pYd3bR5WXt9VWkW5YWUw1hr1N3qEQ2AZX8TrIDj37 +zzy1jyDCvJHGWnKTOOAboNIInP+PvUQrSH8SDAw/+/KjKKgM069NFhGq9TTHg4BAYC0GsZL+JE3Ptee +cIVmekf5Dw+vnD0Mlwx5Ouaf/9OwRcGhfwEkIORQLXDuMayOI/JdFbaDVlA6Z/d8GLp3Xlc8/l5XFtg +fvMNQSB9B69Cs4qwU/yey8tPWeDBiW6Cx2XOnKqiNBaCY1BzvSH+hmHcos1DOLHgEZ3d2zaNn2mrhmB +Ry7/5Ww7O5PoF00OB9WHFAgMBAAGjYTBfMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB +0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAdBgNVHQ4EFgQU4zgMpOMOweRZUbeNewJnh5xZL +XwwDQYJKoZIhvcNAQELBQADggEBAAEK+jXvCKuC/n8qu9XFcLYfO3kUKPlXD30V61KRZilHyYGYu0MY +sSNeX8+K7CpeAo06HHocrrDfCKltoLFuix7qblr2DEub+v3V21pllMfThkz9FsXWFGfmOyI7sXNXUg9 +cVQHzj2SvMj+IfnJoCIuYnigmlKVTuxV31iYv2RpML/PBw29xI0G/AsmXZK4wOQ0eA9gU+ggURE98hG +8f4DRpGVnlyP1d+P2Va0bsl3Yek9QfrotnmE1EzwZzPZyCL9rv8oDjfJ98O3YqoNSRNvD+Glke2ZlTj +WFw+uCj0GTki5+V40E9X9Rwcje+s/5zWDBfu0akufcI1nsu++rZz/s= +""" + +ca_private_key = """ +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCzN7BZw0OBBgeGL7KCKdDIUfBEhh0 +8+3V8Nm7K23mU/pYd3bR5WXt9VWkW5YWUw1hr1N3qEQ2AZX8TrIDj37zzy1jyDCvJHGWnKTOOAboNII +nP+PvUQrSH8SDAw/+/KjKKgM069NFhGq9TTHg4BAYC0GsZL+JE3PteecIVmekf5Dw+vnD0Mlwx5Ouaf +/9OwRcGhfwEkIORQLXDuMayOI/JdFbaDVlA6Z/d8GLp3Xlc8/l5XFtgfvMNQSB9B69Cs4qwU/yey8tP +WeDBiW6Cx2XOnKqiNBaCY1BzvSH+hmHcos1DOLHgEZ3d2zaNn2mrhmBRy7/5Ww7O5PoF00OB9WHFAgM +BAAECggEAHFC/pacCutdrh+lwUD1wFb5QclsoMnLeYJYvEhD0GDTTHfvh4ExhhO9iL7Jq1RK6HStgNn +OkSPWASuj14kr+zRwDPRbsMhWw/+S0FwsxzJIoA/poO2SgplvUG3C8LwVpP9XS1y5ICIoRSl1qHxuPo +ZExYqTcoJmzg31ES2pqWVXPx14DdpE6yvSL2XwFS4mb291OkydnvKSBcK0MwgEWLQHouzMjihJ1MCXx +7NXsOxFX76OpmywMW7EtTLEngxL9b61hCYwWeNxmx9pN8qRzmvayKl40VLyqAlVcElZ7aEK0+O/Qpsu +QhsFRjA4HcXUqlHbvh92OqX+QmBU2RIZ27wKBgQDnJ8E8cJOlJ9ZvFBfw8az49IX9E72oxb2yaXm0Eo +OQ2Jz88+b2jzWqf3wdGvigNO25DbdYYodR7iJJo4OYPuyAnkJMWdPQ91ADo7ABwJiBqtUHC+Gvq5Rmm +I2T3T4+Vqu5Sa8lVfHWfv7Pnb++I5/7bH+VuGspyf+0NcpPh9UfIwKBgQDGet1wh0+2378HnnQNb10w +wxDiMC2hP+3RGPB6bKHLJ60LE+Ed2KFY+j8Q1+jQk9eMe6A75bwB/q6rMO1evpauCHTJoA863HxXtuL +P9ozVpDk9k4TbiSOsD0s8TXL3XG1ANshk4VfuLboKj4MEwiuxfGt6QGpsgLfHcmlkFIM99wKBgQDeea +C97wvrVOBJoGk6eSAlrBKZdTqBCXB+Go4MBhWifxj5TDXq8AKSyohF6wOIDekOxmjEJHBhJnTRsxKgo +U82qxrcKUh4Qs878Xsg9KDTi/vkAEeCr/zwkbsRqUqS7Q/yET0FDibobuoIIKe+9MKxVcel7g0V91in +tW22BeHVSQKBgQCKctwSiaCWTP8A/ouvb3ZO9FLLpJW/vEtUpxPgIfS+NH/lkUlfu2PZID5rrmAtVmN +uEDJWdcsujQwkSC3cABA1d5qXpnnZMkHeIamXLUFSKYrwI/3x8XibpdNyTgga+jMPLuecTwA6GVWD1l +WrNRKrbMG/9j0GUMdhbbKMaC6gQwKBgQCC9EUZBqCXS167OZNPQN4oKx6nJ9uTKUVyPigr12cMpPL6t +JwZAVVwSnyg+cVznNrMhAnG547c0NnoOe+nd9zczLJOuQHHLSMZUH08c2ZWtwpwbHDWI55hfZL4te8e +dEcxanXNYAfSMMtOoA+LmcCtfvqld/EucAN4mKTPGmWPQg== +""" + +srv_certificate = """ +MIIDsTCCApmgAwIBAgIUIvMZU3zc3iYl6JzbDLSvr8NOK5swDQYJKoZIhvcNAQELBQAwVzELMAkGA1U +EBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVn +lPUzEQMA4GA1UEAwwHdnlvcy5pbzAeFw0yNDA2MDQwNjU1NDVaFw0yNTA2MDQwNjU1NDVaMFcxCzAJB +gNVBAYTAkdCMRMwEQYDVQQIDApTb21lLVN0YXRlMRIwEAYDVQQHDAlTb21lLUNpdHkxDTALBgNVBAoM +BFZ5T1MxEDAOBgNVBAMMB3Z5b3MuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtani +zx0h1fEH0pMRBB7V7nUAnSOAiCRUNpeTz6RoUqH0y/UaxM+kqitUm+MSAWxEJAXW4ZlxNzU+tC6DOwP +d+7/rZsT6fKeCbMIs8Es9VaXd2sZzb7DajEygeyIy1b3JGXIiNJ9KcOxzhmu5VHe+6qLCO3FDt4iFIr +HXJxwQKm8qL6zgn7f9kboQYBHKOhY8x+ghkhLYAwMlvIHGwjF+I/p65J1LOBhAsmOLcX0/CygKXz5qe +wyG16zNft6OWPIOBTs56NnNlW6EdqomxBM5SWr888qEjUy0ruUpAH4Ug8SloL+AeDW+TqUUcfoOiTi9 +3ZJ/t9YRj0+wQw4vakpUTAgMBAAGjdTBzMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1 +UdJQQMMAoGCCsGAQUFBwMBMB0GA1UdDgQWBBTCubAbczcJE76YabOv+2oVV1zNSzAfBgNVHSMEGDAWg +BTjOAyk4w7B5FlRt417AmeHnFktfDANBgkqhkiG9w0BAQsFAAOCAQEAjW9ovWDgEoUi8DWwNVtudKiT +6ylJTSMqY7C+qJlRHpnZ64TNZFXI0BldYZr0QXGsZ257m9m9BiUcZr6UR0hywy4SiyxuteufniKIp9E +vqv0aJhdTXO+l5msaGWu7YvWYqXW0m3rA9oiNYyBcNSFzlwiyvztYUmFFPrvhFHVSt+DuxZSltdf78G +exS4YRMCTI+cuCfBt65Vkss4bNJH7kyWVc5aSQ/vKitMxB10gzsUa7psgS6LsBWxnehd3HKBPaHiWG9 +ssHKhHJWfjifgz0K1Y0/vi33USPJ1cBhWWx/dolXWmSmpfqXpD3Q84YjVWIRnFpQzwbT650v/H+fwB1 +zw== +""" + +srv_private_key = """ +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCtanizx0h1fEH0pMRBB7V7nUAnSOA +iCRUNpeTz6RoUqH0y/UaxM+kqitUm+MSAWxEJAXW4ZlxNzU+tC6DOwPd+7/rZsT6fKeCbMIs8Es9VaX +d2sZzb7DajEygeyIy1b3JGXIiNJ9KcOxzhmu5VHe+6qLCO3FDt4iFIrHXJxwQKm8qL6zgn7f9kboQYB +HKOhY8x+ghkhLYAwMlvIHGwjF+I/p65J1LOBhAsmOLcX0/CygKXz5qewyG16zNft6OWPIOBTs56NnNl +W6EdqomxBM5SWr888qEjUy0ruUpAH4Ug8SloL+AeDW+TqUUcfoOiTi93ZJ/t9YRj0+wQw4vakpUTAgM +BAAECggEAEa54SyBSb4QxYg/bYM636Y2G3oU229GK6il+4YMOy99tZeG0L6+IInR7DO5ddBbqSD2esq +QL3PTw9EcUvi/9AYjXeL3H5vOeo+7Rq4OMIfx5wp+Ty6AB5s5hD1kfG7AWzzzHwYNiHS2Gdtb/XldfO +5bP6xO5/rSenynSbWCTir8yakfoDenT12CXWzU+T10MKhoTXb/Uao+bMjziKEviK6OWq0vsLlDqyOAE +Va685s7T0vHTfSs+yK9pqVypHXbkH1nJCoi9P4pcJ4Sslc3meStv3bqg8T62Ufv8QkQLTfJyKZlR1aV +9ZjWT84YoH1XRnnkAZ+BMC267sHeBJbu6EQKBgQDbIUjQh/iPlkK77tFa//gSMD5ouJtuwtdS1MJ44p +C81A140vjpkSCdU8zWRifi+akR1k6fXCp6VFUFvTCXkGlpbD4TNjCCRJjS4SoQ89jEnePQ2iS59jkn3 +V3OPNitOzk0jEm/x3R5wNdPlSX6+pLiUZAtMgcmCMv205VOkeqx8QKBgQDKmB3FtEfkKRrGkOJgaEkY +iXp9b0Uy8peBoTcdqMMnXSlm9+CfIdhSwbQDiAhEcUeCE/S6TDqaMS+ekKFfs6kDlaJMStGsy81lwr5 +W/oZOldajDCu1CDInc+czkep10lsHdQwr71zXntiK3Fwq8Mr3ROBSpaH+DWIjILiQIOMzQwKBgQC7Mt +UUqIQUjkZWbG/XcMLJLwOxzLukRLlUXsQAJ3WEixczN/BDAKM/JB7ikq5yfdwMi+tAwqjbNn4n1/bSF +CGpWToyiWGpd9aimI6qStbNKSE9A47KeulbAAaqMFreqrB1Dr/WIRuFA9QsfXsjzLp8szcbFRj8ShmM +tDZiF8/K0QKBgQCYbb0wzESu8RJZRhddC/m7QWzsxXReMdI2UTLj2N8EVf7ZnzTc5h0Znu4vHgGCZWy +0/QjLxqDs9Ibsmcsg807+CG51UnHRvgFLSCvnzlcE943nXTfhXEpIDtdsoKO0hFHDGZjP0aeb/8LTL5 +sVH9jGFIdnB4ILYMxuu6bBokzvewKBgBWbjPppjrM46bZ0rwEYCcG0F/k6TKkw4pjyrDR4B0XsrqTjK +0yz0ga7FHe10saeS2cXMqygdkjhWLZ6Zhrp0LAEzhEvdiBYeRH37J9Bvwo2YIHakox4hJCSXNnELs/A +GhUb5YIISNnZnZZeUD/Z0IJXJryjk9eUbhDCgEZRVzeT +""" + + +def get_config_value(key): + tmp = read_file(STUNNEL_CONF) + tmp = re.findall(f'\n?{key}\s+(.*)', tmp) + return tmp + + +def read_config(): + config = {'global': {}, + 'services': {} + } + service_pattern = re.compile(r'\[(.+?)\]') + key_value_pattern = re.compile(r'(\S+)\s*=\s*(.+)') + service = None + + for line in read_file(STUNNEL_CONF).split('\n'): + line.strip() + if not line or line.startswith(';'): + continue + if service_pattern.match(line): + service = line.strip('[]') + config['services'][service] = {} + key_value_match = key_value_pattern.match(line) + if key_value_match: + key, value = key_value_match.group(1), key_value_match.group(2) + if service: + apply_value(config['services'][service], key, value) + else: + apply_value(config['global'], key, value) + + return config + + +def apply_value(service_config, key, value): + if service_config.get(key) is None: + service_config[key] = value + else: + if not isinstance(service_config[key], list): + service_config[key] = [ + service_config[key]] + else: + service_config[key].append(value) + + +class TestServiceStunnel(VyOSUnitTestSHIM.TestCase): + maxDiff = None + @classmethod + def setUpClass(cls): + super(TestServiceStunnel, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + cls.is_valid_conf = True + + def tearDown(self): + # Check for running process + if self.is_valid_conf: + self.assertTrue(process_named_running(PROCESS_NAME)) + self.is_valid_conf = True + # delete testing Stunnel config + self.cli_delete(base_path) + self.cli_delete(['pki']) + self.cli_commit() + + # Check for stopped process + self.assertFalse(process_named_running(PROCESS_NAME)) + + def set_pki(self): + self.cli_set(['pki', 'ca', 'ca-1', 'certificate', ca_certificate.replace('\n','')]) + self.cli_set(['pki', 'ca', 'ca-1', 'private', 'key', ca_private_key.replace('\n','')]) + self.cli_set(['pki', 'certificate', 'srv-1', 'certificate', srv_certificate.replace('\n','')]) + self.cli_set(['pki', 'certificate', 'srv-1', 'private', 'key', srv_private_key.replace('\n','')]) + self.cli_commit() + + def test_01_stunnel_simple_client(self): + service = 'app1' + self.cli_set(base_path + ['client', service, 'connect', 'port', '9001']) + + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['client', service, 'listen', 'port', '8001']) + + self.cli_commit() + config = read_config() + + self.assertEqual('/run/stunnel/stunnel.pid', config['global']['pid']) + self.assertEqual('notice', config['global']['debug']) + self.assertListEqual([service], list(config['services'])) + self.assertEqual('8001', config['services'][service]['accept']) + self.assertEqual('9001', config['services'][service]['connect']) + self.assertEqual('yes', config['services'][service]['client']) + + def test_02_stunnel_simple_server(self): + service = 'ser1' + self.set_pki() + self.cli_set(base_path + ['server', service, 'connect', 'port', '8080']) + self.cli_set(base_path + ['server', service, 'ssl', 'certificate', 'srv-1']) + + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['server', service, 'listen', 'port', '9001']) + + self.cli_commit() + config = read_config() + + self.assertEqual('/run/stunnel/stunnel.pid', config['global']['pid']) + self.assertEqual('notice', config['global']['debug']) + self.assertListEqual([service], list(config['services'])) + self.assertEqual('9001', config['services'][service]['accept']) + self.assertEqual('8080', config['services'][service]['connect']) + self.assertIsNone(config['services'][service].get('client')) + self.assertEqual('/run/stunnel/server-ser1-srv-1.pem', config['services'][service]['cert']) + self.assertEqual('/run/stunnel/server-ser1-srv-1.pem.key', config['services'][service]['key']) + + def test_03_multy_services(self): + self.set_pki() + clients = ['app1', 'app2', 'app3'] + servers = ['serv1', 'serv2', 'serv3'] + port = 80 + for service in clients: + port += 1 + self.cli_set(base_path + ['client', service, 'listen', 'port', f'{port}']) + port += 1 + self.cli_set(base_path + ['client', service, 'connect', 'port', f'{port}']) + if service == 'app2': + self.cli_set(base_path + ['client', service, 'connect', 'address', f'192.168.0.10']) + self.cli_set(base_path + ['client', service, 'listen', 'address', '127.0.0.1']) + self.cli_set(base_path + ['client', service, 'protocol', 'connect']) + self.cli_set(base_path + ['client', service, 'options', 'authentication', 'basic']) + self.cli_set(base_path + ['client', service, 'options', 'domain', 'basic.com']) + self.cli_set(base_path + ['client', service, 'options', 'host', 'address', '127.0.0.1']) + self.cli_set(base_path + ['client', service, 'options', 'host', 'port', '5000']) + self.cli_set(base_path + ['client', service, 'options', 'password', 'test_pass']) + self.cli_set(base_path + ['client', service, 'options', 'username', 'test']) + if service == 'app3': + self.cli_set(base_path + ['client', service, 'ssl', 'ca-certificate', 'ca-1']) + self.cli_set(base_path + ['client', service, 'ssl', 'certificate', 'srv-1']) + + for service in servers: + port += 1 + self.cli_set(base_path + ['server', service, 'listen', 'port', f'{port}']) + port += 1 + self.cli_set(base_path + ['server', service, 'connect', 'port', f'{port}']) + self.cli_set(base_path + ['server', service, 'ssl', 'certificate', 'srv-1']) + if service == 'serv2': + self.cli_set(base_path + ['server', service, 'ssl', 'ca-certificate', 'ca-1']) + self.cli_set(base_path + ['server', service, 'connect', 'address', f'google.com']) + self.cli_set(base_path + ['server', service, 'listen', 'address', f'127.0.0.1']) + if service == 'serv3': + self.cli_set(base_path + ['server', service, 'connect', 'address', f'10.18.105.10']) + self.cli_set(base_path + ['server', service, 'protocol', 'imap']) + + self.cli_commit() + config = read_config() + + self.assertEqual('/run/stunnel/stunnel.pid', config['global']['pid']) + self.assertListEqual(clients + servers, list(config['services'])) + self.assertDictEqual(config['services'], { + 'app1': { + 'client': 'yes', + 'accept': '81', + 'connect': '82' + }, + 'app2': { + 'client': 'yes', + 'accept': '127.0.0.1:83', + 'connect': '192.168.0.10:84', + 'protocol': 'connect', + 'protocolAuthentication': 'basic', + 'protocolDomain': 'basic.com', + 'protocolHost': '127.0.0.1:5000', + 'protocolPassword': 'test_pass', + 'protocolUsername': 'test' + }, + 'app3': { + 'client': 'yes', + 'accept': '85', + 'connect': '86', + 'CApath': '/run/stunnel/ca', + 'CAfile': 'client-app3-ca.pem', + 'cert': '/run/stunnel/client-app3-srv-1.pem', + 'key': '/run/stunnel/client-app3-srv-1.pem.key' + }, + 'serv1': { + 'accept': '87', + 'connect': '88', + 'cert': '/run/stunnel/server-serv1-srv-1.pem', + 'key': '/run/stunnel/server-serv1-srv-1.pem.key' + }, + 'serv2': { + 'accept': '127.0.0.1:89', + 'connect': 'google.com:90', + 'CApath': '/run/stunnel/ca', + 'CAfile': 'server-serv2-ca.pem', + 'cert': '/run/stunnel/server-serv2-srv-1.pem', + 'key': '/run/stunnel/server-serv2-srv-1.pem.key' + }, + 'serv3': { + 'accept': '91', + 'connect': '10.18.105.10:92', + 'protocol': 'imap', + 'cert': '/run/stunnel/server-serv3-srv-1.pem', + 'key': '/run/stunnel/server-serv3-srv-1.pem.key' + } + }) + + def test_04_cert_problems(self): + service = 'app1' + self.cli_set(base_path + ['client', service, 'connect', 'port', '9001']) + self.cli_set(base_path + ['client', service, 'listen', 'port', '8001']) + self.cli_set(base_path + ['client', service, 'ssl', 'ca-certificate', 'ca-2']) + + # ca not exist in pki + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete(base_path + ['client', service, 'ssl', 'ca-certificate', 'ca-2']) + self.cli_set(base_path + ['client', service, 'ssl', 'certificate', 'srv-2']) + + # cert not exist in pki + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path) + + self.cli_set(base_path + ['server', service, 'connect', 'port', '8080']) + self.cli_set(base_path + ['server', service, 'listen', 'port', '9001']) + + # Create server without any cert + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['server', service, 'ssl', 'ca-certificate', 'ca-2']) + # ca not exist in pki + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete(base_path + ['server', service, 'ssl', 'ca-certificate', 'ca-2']) + self.cli_set(base_path + ['server', service, 'ssl', 'certificate', 'srv-2']) + # cert not exist in pki + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.is_valid_conf = False + + def test_05_psk_auth(self): + modes = ['client', 'server'] + psk_id_1 = 'psk_id_1' + psk_secret_1 = '1234567890ABCDEF1234567890ABCDEF' + psk_id_2 = 'psk_id_2' + psk_secret_2 = '1234567890ABCDEF1234567890ABCDEA' + expected_config = { + 'global': {'pid': '/run/stunnel/stunnel.pid', + 'debug': 'notice'}, + 'services': {}} + port = 8000 + for mode in modes: + service = f'{mode}-one' + psk_secrets = f'/run/stunnel/psk/{mode}_{service}.txt' + expected_config['services'][service] = { + 'PSKsecrets': psk_secrets, + } + port += 1 + expected_config['services'][service]['accept'] = f'{port}' + self.cli_set(base_path + [mode, service, 'listen', 'port', f'{port}']) + port += 1 + expected_config['services'][service]['connect'] = f'{port}' + self.cli_set(base_path + [mode, service, 'connect', 'port', f'{port}']) + self.cli_set(base_path + [mode, service, 'psk', 'smoketest1', 'id', psk_id_1]) + with self.assertRaises(ConfigSessionError): + self.cli_set(base_path + [mode, service, 'psk', 'smoketest1', 'secret', '123']) + with self.assertRaises(ConfigSessionError): + self.cli_set(base_path + [mode, service, 'psk', 'smoketest1', 'secret', '1234567890ABCDEF1234567890ABCDEZ']) + self.cli_set(base_path + [mode, service, 'psk', 'smoketest1', 'secret', psk_secret_1]) + self.cli_set(base_path + [mode, service, 'psk', 'smoketest2', 'id', psk_id_2]) + self.cli_set(base_path + [mode, service, 'psk', 'smoketest2', 'secret', psk_secret_2]) + if mode != 'server': + expected_config['services'][service]['client'] = 'yes' + + self.cli_commit() + config = read_config() + + self.assertDictEqual(expected_config, config) + + self.assertListEqual([f'{psk_id_1}:{psk_secret_1}', + f'{psk_id_2}:{psk_secret_2}'], + [line for line in read_file(psk_secrets).split('\n')]) + + def test_06_socks_proxy(self): + server_port = '9001' + client_port = '9000' + srv_name = 'srv-one' + cli_name = 'cli-one' + expected_config = { + 'global': {'pid': '/run/stunnel/stunnel.pid', + 'debug': 'notice'}, + 'services': { + 'cli-one': { + 'PSKsecrets': f'/run/stunnel/psk/client_{cli_name}.txt', + 'client': 'yes', + 'accept': client_port, + 'connect': server_port + }, + 'srv-one': { + 'PSKsecrets': f'/run/stunnel/psk/server_{srv_name}.txt', + 'accept': server_port, + 'protocol': 'socks' + } + }} + + self.cli_set(base_path + ['server', srv_name, 'listen', 'port', server_port]) + self.cli_set(base_path + ['server', srv_name, 'connect', 'port', '9005']) + self.cli_set(base_path + ['server', srv_name, 'protocol', 'socks']) + self.cli_set(base_path + ['server', srv_name, 'psk', 'sock_proxy', 'id', cli_name]) + self.cli_set(base_path + ['server', srv_name, 'psk', 'sock_proxy', 'secret', '1234567890ABCDEF1234567890ABCDEF']) + + self.cli_set(base_path + ['client', cli_name, 'listen', 'port', client_port]) + self.cli_set(base_path + ['client', cli_name, 'connect', 'port', server_port]) + self.cli_set(base_path + ['client', cli_name, 'psk', 'sock_proxy', 'id', cli_name]) + self.cli_set(base_path + ['client', cli_name, 'psk', 'sock_proxy', 'secret', '1234567890ABCDEF1234567890ABCDEF']) + + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete(base_path + ['server', srv_name, 'connect']) + self.cli_commit() + config = read_config() + + self.assertDictEqual(expected_config, config) + + def test_07_available_port(self): + expected_config = { + 'global': {'pid': '/run/stunnel/stunnel.pid', + 'debug': 'notice'}, + 'services': { + 'app1': { + 'client': 'yes', + 'accept': '8001', + 'connect': '9001' + }, + 'srv1': { + 'PSKsecrets': f'/run/stunnel/psk/server_srv1.txt', + 'accept': '127.0.0.1:8002', + 'connect': '9001' + } + }} + self.cli_set(base_path + ['client', 'app1', 'connect', 'port', '9001']) + self.cli_set(base_path + ['client', 'app1', 'listen', 'port', '8001']) + + self.cli_set(base_path + ['server', 'srv1', 'connect', 'port', '9001']) + self.cli_set(base_path + ['server', 'srv1', 'listen', 'address', + '127.0.0.1']) + self.cli_set(base_path + ['server', 'srv1', 'listen', 'port', '8001']) + self.cli_set(base_path + ['server', 'srv1', 'psk', 'smoketest1', + 'id', 'foo']) + self.cli_set(base_path + ['server', 'srv1', 'psk', 'smoketest1', + 'secret', '1234567890ABCDEF1234567890ABCDEF']) + + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['server', 'srv1', 'listen', 'port', '8002']) + self.cli_commit() + + config = read_config() + self.assertDictEqual(expected_config, config) + + def test_08_two_endpoints(self): + expected_config = { + 'global': {'pid': '/run/stunnel/stunnel.pid', + 'debug': 'notice'}, + 'services': { + 'app1': { + 'client': 'yes', + 'accept': '8001', + 'connect': '9001' + } + }} + + self.cli_set(base_path + ['client', 'app1', 'listen', 'port', '8001']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['client', 'app1', 'connect', 'port', '9001']) + self.cli_commit() + + config = read_config() + self.assertDictEqual(expected_config, config) + + def test_09_pki_still_used(self): + service = 'ser1' + self.set_pki() + self.cli_set(base_path + ['server', service, 'connect', 'port', '8080']) + self.cli_set(base_path + ['server', service, 'listen', 'port', '9001']) + self.cli_set(base_path + ['server', service, 'ssl', 'certificate', 'srv-1']) + self.cli_commit() + + self.cli_delete(['pki']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete(base_path) + self.cli_commit() + + self.is_valid_conf = False + + def test_99_protocols(self): + self.set_pki() + service = 'one' + proto_address = 'google.com' + proto_port = '80' + modes = ['client', 'server'] + protocols = ['cifs', 'connect', 'imap', 'nntp', 'pgsql', 'pop3', + 'proxy', 'smtp', 'socks'] + options = ['authentication', 'domain', 'host', 'password', 'username'] + + for protocol in protocols: + for mode in modes: + expected_config = { + 'global': {'pid': '/run/stunnel/stunnel.pid', + 'debug': 'notice'}, + 'services': {'one': { + 'accept': '8001', + 'protocol': protocol, + }}} + if not(mode == 'server' and protocol == 'socks'): + self.cli_set(base_path + [mode, service, 'connect', 'port', '9001']) + expected_config['services']['one']['connect'] = '9001' + self.cli_set(base_path + [mode, service, 'listen', 'port', '8001']) + + if mode == 'server': + expected_config['services'][service]['cert'] = '/run/stunnel/server-one-srv-1.pem' + expected_config['services'][service]['key'] = '/run/stunnel/server-one-srv-1.pem.key' + self.cli_set(base_path + [mode, service, 'ssl', + 'certificate', 'srv-1']) + else: + expected_config['services'][service]['client'] = 'yes' + + # protocols connect and nntp is only supported in client mode. + if mode == 'server' and protocol in ['connect', 'nntp']: + with self.assertRaises(ConfigSessionError): + self.cli_set(base_path + [mode, service, 'protocol', protocol]) + # self.cli_commit() + else: + self.cli_set(base_path + [mode, service, 'protocol', protocol]) + self.cli_commit() + config = read_config() + + self.assertDictEqual(expected_config, config) + + expected_config['services'][service]['protocolDomain'] = 'valdomain' + expected_config['services'][service]['protocolPassword'] = 'valpassword' + expected_config['services'][service]['protocolUsername'] = 'valusername' + + for option in options: + if option == 'authentication': + expected_config['services'][service]['protocolAuthentication'] = \ + 'basic' if protocol == 'connect' else 'plain' + continue + + if option == 'host' and mode != 'server': + expected_config['services'][service]['protocolHost'] = \ + f'{proto_address}:{proto_port}' + self.cli_set(base_path + [mode, service, 'options', + option, 'address', f'{proto_address}']) + self.cli_set(base_path + [mode, service, 'options', + option, 'port', f'{proto_port}']) + continue + if mode == 'server': + with self.assertRaises(ConfigSessionError): + self.cli_set( + base_path + [mode, service, 'options', option, f'val{option}']) + else: + self.cli_set( + base_path + [mode, service, 'options', option, f'val{option}']) + # Additional option is only supported in the 'connect' and 'smtp' protocols. + if mode != 'server': + if protocol not in ['connect', 'smtp']: + with self.assertRaises(ConfigSessionError): + self.cli_commit() + else: + if protocol == 'smtp': + # Protocol smtp does not support options domain and host + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete( + base_path + [mode, service, 'options', 'domain']) + self.cli_delete( + base_path + [mode, service, 'options', 'host']) + del expected_config['services'][service]['protocolDomain'] + del expected_config['services'][service]['protocolHost'] + + self.cli_commit() + config = read_config() + + self.assertDictEqual(expected_config, config) + + self.cli_delete(base_path) + self.cli_commit() + + self.is_valid_conf = False + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_tftp-server.py b/smoketest/scripts/cli/test_service_tftp-server.py new file mode 100644 index 0000000..d607949 --- /dev/null +++ b/smoketest/scripts/cli/test_service_tftp-server.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from psutil import process_iter +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import cmd +from vyos.utils.file import read_file +from vyos.utils.process import process_named_running +from vyos.template import is_ipv6 + +PROCESS_NAME = 'in.tftpd' +base_path = ['service', 'tftp-server'] +dummy_if_path = ['interfaces', 'dummy', 'dum69'] +address_ipv4 = '192.0.2.1' +address_ipv6 = '2001:db8::1' +vrf = 'mgmt' + +class TestServiceTFTPD(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestServiceTFTPD, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + cls.cli_set(cls, dummy_if_path + ['address', address_ipv4 + '/32']) + cls.cli_set(cls, dummy_if_path + ['address', address_ipv6 + '/128']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, dummy_if_path) + super(TestServiceTFTPD, cls).tearDownClass() + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(base_path) + self.cli_commit() + + # Check for no longer running process + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_tftpd_single(self): + directory = '/tmp' + port = '69' # default port + + self.cli_set(base_path + ['allow-upload']) + self.cli_set(base_path + ['directory', directory]) + self.cli_set(base_path + ['listen-address', address_ipv4]) + + # commit changes + self.cli_commit() + + config = read_file('/etc/default/tftpd0') + # verify listen IP address + self.assertIn(f'{address_ipv4}:{port} -4', config) + # verify directory + self.assertIn(directory, config) + # verify upload + self.assertIn('--create --umask 000', config) + + def test_02_tftpd_multi(self): + directory = '/tmp' + address = [address_ipv4, address_ipv6] + port = '70' + + self.cli_set(base_path + ['directory', directory]) + for addr in address: + self.cli_set(base_path + ['listen-address', addr]) + self.cli_set(base_path + ['port', port]) + + # commit changes + self.cli_commit() + + for idx in range(0, len(address)): + config = read_file(f'/etc/default/tftpd{idx}') + addr = address[idx] + + # verify listen IP address + if is_ipv6(addr): + addr = f'[{addr}]' + self.assertIn(f'{addr}:{port} -6', config) + else: + self.assertIn(f'{addr}:{port} -4', config) + + # verify directory + self.assertIn(directory, config) + + # Check for running processes - one process is spawned per listen + # IP address, wheter it's IPv4 or IPv6 + count = 0 + for p in process_iter(): + if PROCESS_NAME in p.name(): + count += 1 + self.assertEqual(count, len(address)) + + def test_03_tftpd_vrf(self): + directory = '/tmp' + port = '69' # default port + + self.cli_set(base_path + ['allow-upload']) + self.cli_set(base_path + ['directory', directory]) + self.cli_set(base_path + ['listen-address', address_ipv4, 'vrf', vrf]) + + # VRF does yet not exist - an error must be thrown + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(['vrf', 'name', vrf, 'table', '1338']) + self.cli_set(dummy_if_path + ['vrf', vrf]) + + # commit changes + self.cli_commit() + + config = read_file('/etc/default/tftpd0') + # verify listen IP address + self.assertIn(f'{address_ipv4}:{port} -4', config) + # verify directory + self.assertIn(directory, config) + # verify upload + self.assertIn('--create --umask 000', config) + + # Check for process in VRF + tmp = cmd(f'ip vrf pids {vrf}') + self.assertIn(PROCESS_NAME, tmp) + + # delete VRF + self.cli_delete(dummy_if_path + ['vrf']) + self.cli_delete(['vrf', 'name', vrf]) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_webproxy.py b/smoketest/scripts/cli/test_service_webproxy.py new file mode 100644 index 0000000..2b3f6d2 --- /dev/null +++ b/smoketest/scripts/cli/test_service_webproxy.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +PROCESS_NAME = 'squid' +PROXY_CONF = '/etc/squid/squid.conf' +base_path = ['service', 'webproxy'] +listen_if = 'dum3632' +listen_ip = '192.0.2.1' + +class TestServiceWebProxy(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + # call base-classes classmethod + super(TestServiceWebProxy, cls).setUpClass() + # create a test interfaces + cls.cli_set(cls, ['interfaces', 'dummy', listen_if, 'address', listen_ip + '/32']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'dummy', listen_if]) + super(TestServiceWebProxy, cls).tearDownClass() + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_01_basic_proxy(self): + default_cache = '100' + self.cli_set(base_path + ['listen-address', listen_ip]) + + # commit changes + self.cli_commit() + + config = read_file(PROXY_CONF) + self.assertIn(f'http_port {listen_ip}:3128 intercept', config) + self.assertIn(f'cache_dir ufs /var/spool/squid {default_cache} 16 256', config) + self.assertIn(f'access_log /var/log/squid/access.log squid', config) + + # ACL verification + self.assertIn(f'acl net src all', config) + self.assertIn(f'acl SSL_ports port 443', config) + + safe_ports = ['80', '21', '443', '873', '70', '210', '1025-65535', '280', + '488', '591', '777'] + for port in safe_ports: + self.assertIn(f'acl Safe_ports port {port}', config) + self.assertIn(f'acl CONNECT method CONNECT', config) + + self.assertIn(f'http_access allow manager localhost', config) + self.assertIn(f'http_access deny manager', config) + self.assertIn(f'http_access deny !Safe_ports', config) + self.assertIn(f'http_access deny CONNECT !SSL_ports', config) + self.assertIn(f'http_access allow localhost', config) + self.assertIn(f'http_access allow net', config) + self.assertIn(f'http_access deny all', config) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_02_advanced_proxy(self): + domain = '.vyos.io' + cache_size = '900' + port = '8080' + min_obj_size = '128' + max_obj_size = '8192' + block_mine = ['application/pdf', 'application/x-sh'] + body_max_size = '4096' + safe_port = '88' + ssl_safe_port = '8443' + + self.cli_set(base_path + ['listen-address', listen_ip]) + self.cli_set(base_path + ['append-domain', domain]) + self.cli_set(base_path + ['default-port', port]) + self.cli_set(base_path + ['cache-size', cache_size]) + self.cli_set(base_path + ['disable-access-log']) + + self.cli_set(base_path + ['minimum-object-size', min_obj_size]) + self.cli_set(base_path + ['maximum-object-size', max_obj_size]) + + self.cli_set(base_path + ['outgoing-address', listen_ip]) + + for mime in block_mine: + self.cli_set(base_path + ['reply-block-mime', mime]) + + self.cli_set(base_path + ['reply-body-max-size', body_max_size]) + + self.cli_set(base_path + ['safe-ports', safe_port]) + self.cli_set(base_path + ['ssl-safe-ports', ssl_safe_port]) + + # commit changes + self.cli_commit() + + config = read_file(PROXY_CONF) + self.assertIn(f'http_port {listen_ip}:{port} intercept', config) + self.assertIn(f'append_domain {domain}', config) + self.assertIn(f'cache_dir ufs /var/spool/squid {cache_size} 16 256', config) + self.assertIn(f'access_log none', config) + self.assertIn(f'minimum_object_size {min_obj_size} KB', config) + self.assertIn(f'maximum_object_size {max_obj_size} KB', config) + self.assertIn(f'tcp_outgoing_address {listen_ip}', config) + + for mime in block_mine: + self.assertIn(f'acl BLOCK_MIME rep_mime_type {mime}', config) + self.assertIn(f'http_reply_access deny BLOCK_MIME', config) + + self.assertIn(f'reply_body_max_size {body_max_size} KB', config) + + self.assertIn(f'acl Safe_ports port {safe_port}', config) + self.assertIn(f'acl SSL_ports port {ssl_safe_port}', config) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_03_ldap_proxy_auth(self): + auth_children = '20' + cred_ttl = '120' + realm = 'VyOS Webproxy' + ldap_base_dn = 'DC=vyos,DC=net' + ldap_server = 'ldap.vyos.net' + ldap_bind_dn = f'CN=proxyuser,CN=Users,{ldap_base_dn}' + ldap_password = 'VyOS12345' + ldap_attr = 'cn' + ldap_filter = '(cn=%s)' + + self.cli_set(base_path + ['listen-address', listen_ip, 'disable-transparent']) + self.cli_set(base_path + ['authentication', 'children', auth_children]) + self.cli_set(base_path + ['authentication', 'credentials-ttl', cred_ttl]) + + self.cli_set(base_path + ['authentication', 'realm', realm]) + self.cli_set(base_path + ['authentication', 'method', 'ldap']) + # check validate() - LDAP authentication is enabled, but server not set + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['authentication', 'ldap', 'server', ldap_server]) + + # check validate() - LDAP password can not be set when bind-dn is not define + self.cli_set(base_path + ['authentication', 'ldap', 'password', ldap_password]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['authentication', 'ldap', 'bind-dn', ldap_bind_dn]) + + # check validate() - LDAP base-dn must be set + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['authentication', 'ldap', 'base-dn', ldap_base_dn]) + + self.cli_set(base_path + ['authentication', 'ldap', 'username-attribute', ldap_attr]) + self.cli_set(base_path + ['authentication', 'ldap', 'filter-expression', ldap_filter]) + self.cli_set(base_path + ['authentication', 'ldap', 'use-ssl']) + + # commit changes + self.cli_commit() + + config = read_file(PROXY_CONF) + self.assertIn(f'http_port {listen_ip}:3128', config) # disable-transparent + + # Now verify LDAP settings + self.assertIn(f'auth_param basic children {auth_children}', config) + self.assertIn(f'auth_param basic credentialsttl {cred_ttl} minute', config) + self.assertIn(f'auth_param basic realm "{realm}"', config) + self.assertIn(f'auth_param basic program /usr/lib/squid/basic_ldap_auth -v 3 -b "{ldap_base_dn}" -D "{ldap_bind_dn}" -w "{ldap_password}" -f "{ldap_filter}" -u "{ldap_attr}" -p 389 -ZZ -R -h "{ldap_server}"', config) + self.assertIn(f'acl auth proxy_auth REQUIRED', config) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_04_cache_peer(self): + self.cli_set(base_path + ['listen-address', listen_ip]) + + cache_peers = { + 'foo' : '192.0.2.1', + 'bar' : '192.0.2.2', + 'baz' : '192.0.2.3', + } + for peer in cache_peers: + self.cli_set(base_path + ['cache-peer', peer, 'address', cache_peers[peer]]) + if peer == 'baz': + self.cli_set(base_path + ['cache-peer', peer, 'type', 'sibling']) + + # commit changes + self.cli_commit() + + config = read_file(PROXY_CONF) + self.assertIn('never_direct allow all', config) + + for peer in cache_peers: + address = cache_peers[peer] + if peer == 'baz': + self.assertIn(f'cache_peer {address} sibling 3128 0 no-query default', config) + else: + self.assertIn(f'cache_peer {address} parent 3128 0 no-query default', config) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_05_basic_squidguard(self): + # Create very basic local SquidGuard blacklist and verify its contents + sg_db_dir = '/opt/vyatta/etc/config/url-filtering/squidguard/db' + + default_cache = '100' + local_block = ['192.0.0.1', '10.0.0.1', 'block.vyos.net'] + local_block_url = ['foo.com/bar.html', 'bar.com/foo.htm'] + local_block_pattern = ['porn', 'cisco', 'juniper'] + local_ok = ['10.0.0.0', 'vyos.net'] + local_ok_url = ['vyos.net', 'vyos.io'] + + self.cli_set(base_path + ['listen-address', listen_ip]) + self.cli_set(base_path + ['url-filtering', 'squidguard', 'log', 'all']) + + for block in local_block: + self.cli_set(base_path + ['url-filtering', 'squidguard', 'local-block', block]) + for ok in local_ok: + self.cli_set(base_path + ['url-filtering', 'squidguard', 'local-ok', ok]) + for url in local_block_url: + self.cli_set(base_path + ['url-filtering', 'squidguard', 'local-block-url', url]) + for url in local_ok_url: + self.cli_set(base_path + ['url-filtering', 'squidguard', 'local-ok-url', url]) + for pattern in local_block_pattern: + self.cli_set(base_path + ['url-filtering', 'squidguard', 'local-block-keyword', pattern]) + + # commit changes + self.cli_commit() + + # Check regular Squid config + config = read_file(PROXY_CONF) + self.assertIn(f'http_port {listen_ip}:3128 intercept', config) + + self.assertIn(f'url_rewrite_program /usr/bin/squidGuard -c /etc/squidguard/squidGuard.conf', config) + self.assertIn(f'url_rewrite_children 8', config) + + # Check SquidGuard config + sg_config = read_file('/etc/squidguard/squidGuard.conf') + self.assertIn(f'log blacklist.log', sg_config) + + # The following are rewrite strings to force safe/strict search for + # several popular search engines. + self.assertIn(r's@(.*\.google\..*/(custom|search|images|groups|news)?.*q=.*)@\1\&safe=active@i', sg_config) + self.assertIn(r's@(.*\..*/yandsearch?.*text=.*)@\1\&fyandex=1@i', sg_config) + self.assertIn(r's@(.*\.yahoo\..*/search.*p=.*)@\1\&vm=r@i', sg_config) + self.assertIn(r's@(.*\.live\..*/.*q=.*)@\1\&adlt=strict@i', sg_config) + self.assertIn(r's@(.*\.msn\..*/.*q=.*)@\1\&adlt=strict@i', sg_config) + self.assertIn(r's@(.*\.bing\..*/search.*q=.*)@\1\&adlt=strict@i', sg_config) + + # URL lists + self.assertIn(r'dest local-ok-default {', sg_config) + self.assertIn(f'domainlist local-ok-default/domains', sg_config) + self.assertIn(r'dest local-ok-url-default {', sg_config) + self.assertIn(f'urllist local-ok-url-default/urls', sg_config) + + # Redirect - default value + self.assertIn(f'redirect 302:http://block.vyos.net', sg_config) + + # local-block database + tmp = cmd(f'sudo cat {sg_db_dir}/local-block-default/domains') + for block in local_block: + self.assertIn(f'{block}', tmp) + + tmp = cmd(f'sudo cat {sg_db_dir}/local-block-url-default/urls') + for url in local_block_url: + self.assertIn(f'{url}', tmp) + + tmp = cmd(f'sudo cat {sg_db_dir}/local-block-keyword-default/expressions') + for pattern in local_block_pattern: + self.assertIn(f'{pattern}', tmp) + + # local-ok database + tmp = cmd(f'sudo cat {sg_db_dir}/local-ok-default/domains') + for ok in local_ok: + self.assertIn(f'{ok}', tmp) + + tmp = cmd(f'sudo cat {sg_db_dir}/local-ok-url-default/urls') + for url in local_ok_url: + self.assertIn(f'{url}', tmp) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_acceleration_qat.py b/smoketest/scripts/cli/test_system_acceleration_qat.py new file mode 100644 index 0000000..9e60bb2 --- /dev/null +++ b/smoketest/scripts/cli/test_system_acceleration_qat.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError + +base_path = ['system', 'acceleration', 'qat'] + +class TestIntelQAT(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_simple_unsupported(self): + # Check if configuration script is in place and that the config script + # throws an error as QAT device is not present in Qemu. This *must* be + # extended with QAT autodetection once run on a QAT enabled device + + # configure some system display + self.cli_set(base_path) + + # An error must be thrown if QAT device could not be found + with self.assertRaises(ConfigSessionError): + self.cli_commit() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py new file mode 100644 index 0000000..72deb75 --- /dev/null +++ b/smoketest/scripts/cli/test_system_conntrack.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.firewall import find_nftables_rule +from vyos.utils.file import read_file, read_json + +base_path = ['system', 'conntrack'] + +def get_sysctl(parameter): + tmp = parameter.replace(r'.', r'/') + return read_file(f'/proc/sys/{tmp}') + +def get_logger_config(): + return read_json('/run/vyos-conntrack-logger.conf') + +class TestSystemConntrack(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestSystemConntrack, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_conntrack_options(self): + conntrack_config = { + 'net.netfilter.nf_conntrack_expect_max' : { + 'cli' : ['expect-table-size'], + 'test_value' : '8192', + 'default_value' : '2048', + }, + 'net.nf_conntrack_max' :{ + 'cli' : ['table-size'], + 'test_value' : '500000', + 'default_value' : '262144', + }, + 'net.ipv4.tcp_max_syn_backlog' :{ + 'cli' : ['tcp', 'half-open-connections'], + 'test_value' : '2048', + 'default_value' : '512', + }, + 'net.netfilter.nf_conntrack_tcp_loose' :{ + 'cli' : ['tcp', 'loose'], + 'test_value' : 'disable', + 'default_value' : '1', + }, + 'net.netfilter.nf_conntrack_tcp_max_retrans' :{ + 'cli' : ['tcp', 'max-retrans'], + 'test_value' : '128', + 'default_value' : '3', + }, + } + + for parameter, parameter_config in conntrack_config.items(): + self.cli_set(base_path + parameter_config['cli'] + [parameter_config['test_value']]) + + # commit changes + self.cli_commit() + + # validate configuration + for parameter, parameter_config in conntrack_config.items(): + tmp = parameter_config['test_value'] + # net.netfilter.nf_conntrack_tcp_loose has a fancy "disable" value, + # make this work + if tmp == 'disable': + tmp = '0' + self.assertEqual(get_sysctl(f'{parameter}'), tmp) + + # delete all configuration options and revert back to defaults + self.cli_delete(base_path) + self.cli_commit() + + # validate configuration + for parameter, parameter_config in conntrack_config.items(): + self.assertEqual(get_sysctl(f'{parameter}'), parameter_config['default_value']) + + + def test_conntrack_module_enable(self): + # conntrack helper modules are disabled by default + modules = { + 'ftp': { + 'driver': ['nf_nat_ftp', 'nf_conntrack_ftp'], + 'nftables': ['ct helper set "ftp_tcp"'] + }, + 'h323': { + 'driver': ['nf_nat_h323', 'nf_conntrack_h323'], + 'nftables': ['ct helper set "ras_udp"', + 'ct helper set "q931_tcp"'] + }, + 'nfs': { + 'nftables': ['ct helper set "rpc_tcp"', + 'ct helper set "rpc_udp"'] + }, + 'pptp': { + 'driver': ['nf_nat_pptp', 'nf_conntrack_pptp'], + 'nftables': ['ct helper set "pptp_tcp"'] + }, + 'rtsp': { + 'driver': ['nf_nat_rtsp', 'nf_conntrack_rtsp'], + 'nftables': ['ct helper set "rtsp_tcp"'] + }, + 'sip': { + 'driver': ['nf_nat_sip', 'nf_conntrack_sip'], + 'nftables': ['ct helper set "sip_tcp"', + 'ct helper set "sip_udp"'] + }, + 'sqlnet': { + 'nftables': ['ct helper set "tns_tcp"'] + }, + 'tftp': { + 'driver': ['nf_nat_tftp', 'nf_conntrack_tftp'], + 'nftables': ['ct helper set "tftp_udp"'] + }, + } + + # load modules + for module in modules: + self.cli_set(base_path + ['modules', module]) + + # commit changes + self.cli_commit() + + # verify modules are loaded on the system + for module, module_options in modules.items(): + if 'driver' in module_options: + for driver in module_options['driver']: + self.assertTrue(os.path.isdir(f'/sys/module/{driver}')) + if 'nftables' in module_options: + for rule in module_options['nftables']: + self.assertTrue(find_nftables_rule('ip vyos_conntrack', 'VYOS_CT_HELPER', [rule]) != None) + + # unload modules + for module in modules: + self.cli_delete(base_path + ['modules', module]) + + # commit changes + self.cli_commit() + + # verify modules are not loaded on the system + for module, module_options in modules.items(): + if 'driver' in module_options: + for driver in module_options['driver']: + self.assertFalse(os.path.isdir(f'/sys/module/{driver}')) + if 'nftables' in module_options: + for rule in module_options['nftables']: + self.assertTrue(find_nftables_rule('ip vyos_conntrack', 'VYOS_CT_HELPER', [rule]) == None) + + def test_conntrack_hash_size(self): + hash_size = '65536' + hash_size_default = '32768' + + self.cli_set(base_path + ['hash-size', hash_size]) + + # commit changes + self.cli_commit() + + # verify new configuration - only effective after reboot, but + # a valid config file is sufficient + tmp = read_file('/etc/modprobe.d/vyatta_nf_conntrack.conf') + self.assertIn(hash_size, tmp) + + # Test default value by deleting the configuration + self.cli_delete(base_path + ['hash-size']) + + # commit changes + self.cli_commit() + + # verify new configuration - only effective after reboot, but + # a valid config file is sufficient + tmp = read_file('/etc/modprobe.d/vyatta_nf_conntrack.conf') + self.assertIn(hash_size_default, tmp) + + def test_conntrack_ignore(self): + address_group = 'conntracktest' + address_group_member = '192.168.0.1' + ipv6_address_group = 'conntracktest6' + ipv6_address_group_member = 'dead:beef::1' + + self.cli_set(['firewall', 'group', 'address-group', address_group, 'address', address_group_member]) + self.cli_set(['firewall', 'group', 'ipv6-address-group', ipv6_address_group, 'address', ipv6_address_group_member]) + + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'source', 'address', '192.0.2.1']) + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'destination', 'address', '192.0.2.2']) + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'destination', 'port', '22']) + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'protocol', 'tcp']) + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'tcp', 'flags', 'syn']) + + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'source', 'address', '192.0.2.1']) + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'destination', 'group', 'address-group', address_group]) + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'protocol', 'all']) + + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'source', 'address', 'fe80::1']) + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'address', 'fe80::2']) + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'port', '22']) + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'protocol', 'tcp']) + + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '12', 'source', 'address', 'fe80::1']) + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '12', 'destination', 'group', 'address-group', ipv6_address_group]) + + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '13', 'source', 'address', 'fe80::1']) + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '13', 'destination', 'address', '!fe80::3']) + + self.cli_commit() + + nftables_search = [ + ['ip saddr 192.0.2.1', 'ip daddr 192.0.2.2', 'tcp dport 22', 'tcp flags & syn == syn', 'notrack'], + ['ip saddr 192.0.2.1', 'ip daddr @A_conntracktest', 'notrack'] + ] + + nftables6_search = [ + ['ip6 saddr fe80::1', 'ip6 daddr fe80::2', 'tcp dport 22', 'notrack'], + ['ip6 saddr fe80::1', 'ip6 daddr @A6_conntracktest6', 'notrack'], + ['ip6 saddr fe80::1', 'ip6 daddr != fe80::3', 'notrack'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_conntrack') + self.verify_nftables(nftables6_search, 'ip6 vyos_conntrack') + + self.cli_delete(['firewall']) + + def test_conntrack_timeout_custom(self): + + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'source', 'address', '192.0.2.1']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'destination', 'address', '192.0.2.2']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'destination', 'port', '22']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'protocol', 'tcp', 'syn-sent', '77']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'protocol', 'tcp', 'close', '88']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'protocol', 'tcp', 'established', '99']) + + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '2', 'inbound-interface', 'eth1']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '2', 'source', 'address', '198.51.100.1']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '2', 'protocol', 'udp', 'unreplied', '55']) + + self.cli_set(base_path + ['timeout', 'custom', 'ipv6', 'rule', '1', 'source', 'address', '2001:db8::1']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv6', 'rule', '1', 'inbound-interface', 'eth2']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv6', 'rule', '1', 'protocol', 'tcp', 'time-wait', '22']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv6', 'rule', '1', 'protocol', 'tcp', 'last-ack', '33']) + + self.cli_commit() + + nftables_search = [ + ['ct timeout ct-timeout-1 {'], + ['protocol tcp'], + ['policy = { syn_sent : 1m17s, established : 1m39s, close : 1m28s }'], + ['ct timeout ct-timeout-2 {'], + ['protocol udp'], + ['policy = { unreplied : 55s }'], + ['chain VYOS_CT_TIMEOUT {'], + ['ip saddr 192.0.2.1', 'ip daddr 192.0.2.2', 'tcp dport 22', 'ct timeout set "ct-timeout-1"'], + ['iifname "eth1"', 'meta l4proto udp', 'ip saddr 198.51.100.1', 'ct timeout set "ct-timeout-2"'] + ] + + nftables6_search = [ + ['ct timeout ct-timeout-1 {'], + ['protocol tcp'], + ['policy = { last_ack : 33s, time_wait : 22s }'], + ['chain VYOS_CT_TIMEOUT {'], + ['iifname "eth2"', 'meta l4proto tcp', 'ip6 saddr 2001:db8::1', 'ct timeout set "ct-timeout-1"'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_conntrack') + self.verify_nftables(nftables6_search, 'ip6 vyos_conntrack') + + self.cli_delete(['firewall']) + + def test_conntrack_log(self): + expected_config = { + 'event': { + 'destroy': {}, + 'new': {}, + 'update': {}, + }, + 'queue_size': '10000' + } + self.cli_set(base_path + ['log', 'event', 'destroy']) + self.cli_set(base_path + ['log', 'event', 'new']) + self.cli_set(base_path + ['log', 'event', 'update']) + self.cli_set(base_path + ['log', 'queue-size', '10000']) + self.cli_commit() + self.assertEqual(expected_config, get_logger_config()) + self.assertEqual('0', get_sysctl('net.netfilter.nf_conntrack_timestamp')) + + for event in ['destroy', 'new', 'update']: + for proto in ['icmp', 'other', 'tcp', 'udp']: + self.cli_set(base_path + ['log', 'event', event, proto]) + expected_config['event'][event][proto] = {} + self.cli_set(base_path + ['log', 'timestamp']) + expected_config['timestamp'] = {} + self.cli_commit() + + self.assertEqual(expected_config, get_logger_config()) + self.assertEqual('1', get_sysctl('net.netfilter.nf_conntrack_timestamp')) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_flow-accounting.py b/smoketest/scripts/cli/test_system_flow-accounting.py new file mode 100644 index 0000000..5151342 --- /dev/null +++ b/smoketest/scripts/cli/test_system_flow-accounting.py @@ -0,0 +1,295 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.template import bracketize_ipv6 +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +PROCESS_NAME = 'uacctd' +base_path = ['system', 'flow-accounting'] + +uacctd_conf = '/run/pmacct/uacctd.conf' + +class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestSystemFlowAccounting, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + # after service removal process must no longer run + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(base_path) + self.cli_commit() + + # after service removal process must no longer run + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_basic(self): + buffer_size = '5' # MiB + syslog = 'all' + + self.cli_set(base_path + ['buffer-size', buffer_size]) + self.cli_set(base_path + ['syslog-facility', syslog]) + + # You need to configure at least one interface for flow-accounting + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for interface in Section.interfaces('ethernet'): + self.cli_set(base_path + ['interface', interface]) + + # commit changes + self.cli_commit() + + # verify configuration + nftables_output = cmd('sudo nft list chain raw VYOS_PREROUTING_HOOK').splitlines() + for interface in Section.interfaces('ethernet'): + rule_found = False + ifname_search = f'iifname "{interface}"' + + for nftables_line in nftables_output: + if 'FLOW_ACCOUNTING_RULE' in nftables_line and ifname_search in nftables_line: + self.assertIn('group 2', nftables_line) + self.assertIn('snaplen 128', nftables_line) + self.assertIn('queue-threshold 100', nftables_line) + rule_found = True + break + + self.assertTrue(rule_found) + + uacctd = read_file(uacctd_conf) + # circular queue size - buffer_size + tmp = int(buffer_size) *1024 *1024 + self.assertIn(f'plugin_pipe_size: {tmp}', uacctd) + # transfer buffer size - recommended value from pmacct developers 1/1000 of pipe size + tmp = int(buffer_size) *1024 *1024 + # do an integer division + tmp //= 1000 + self.assertIn(f'plugin_buffer_size: {tmp}', uacctd) + + # when 'disable-imt' is not configured on the CLI it must be present + self.assertIn(f'imt_path: /tmp/uacctd.pipe', uacctd) + self.assertIn(f'imt_mem_pools_number: 169', uacctd) + self.assertIn(f'syslog: {syslog}', uacctd) + self.assertIn(f'plugins: memory', uacctd) + + def test_sflow(self): + sampling_rate = '4000' + source_address = '192.0.2.1' + dummy_if = 'dum3841' + agent_address = '192.0.2.2' + + sflow_server = { + '1.2.3.4' : { }, + '5.6.7.8' : { 'port' : '6000' }, + } + + self.cli_set(['interfaces', 'dummy', dummy_if, 'address', agent_address + '/32']) + self.cli_set(['interfaces', 'dummy', dummy_if, 'address', source_address + '/32']) + self.cli_set(base_path + ['disable-imt']) + + # You need to configure at least one interface for flow-accounting + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for interface in Section.interfaces('ethernet'): + self.cli_set(base_path + ['interface', interface]) + + + # You need to configure at least one sFlow or NetFlow protocol, or not + # set "disable-imt" for flow-accounting + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['sflow', 'agent-address', agent_address]) + self.cli_set(base_path + ['sflow', 'sampling-rate', sampling_rate]) + self.cli_set(base_path + ['sflow', 'source-address', source_address]) + for server, server_config in sflow_server.items(): + self.cli_set(base_path + ['sflow', 'server', server]) + if 'port' in server_config: + self.cli_set(base_path + ['sflow', 'server', server, 'port', server_config['port']]) + + # commit changes + self.cli_commit() + + uacctd = read_file(uacctd_conf) + + # when 'disable-imt' is not configured on the CLI it must be present + self.assertNotIn(f'imt_path: /tmp/uacctd.pipe', uacctd) + self.assertNotIn(f'imt_mem_pools_number: 169', uacctd) + self.assertNotIn(f'plugins: memory', uacctd) + + for server, server_config in sflow_server.items(): + plugin_name = server.replace('.', '-') + if 'port' in server_config: + self.assertIn(f'sfprobe_receiver[sf_{plugin_name}]: {server}', uacctd) + else: + self.assertIn(f'sfprobe_receiver[sf_{plugin_name}]: {server}:6343', uacctd) + + self.assertIn(f'sfprobe_agentip[sf_{plugin_name}]: {agent_address}', uacctd) + self.assertIn(f'sampling_rate[sf_{plugin_name}]: {sampling_rate}', uacctd) + self.assertIn(f'sfprobe_source_ip[sf_{plugin_name}]: {source_address}', uacctd) + + self.cli_delete(['interfaces', 'dummy', dummy_if]) + + def test_sflow_ipv6(self): + sampling_rate = '100' + sflow_server = { + '2001:db8::1' : { }, + '2001:db8::2' : { 'port' : '6000' }, + } + + self.cli_set(base_path + ['disable-imt']) + + # You need to configure at least one interface for flow-accounting + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for interface in Section.interfaces('ethernet'): + self.cli_set(base_path + ['interface', interface]) + + + # You need to configure at least one sFlow or NetFlow protocol, or not + # set "disable-imt" for flow-accounting + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['sflow', 'sampling-rate', sampling_rate]) + for server, server_config in sflow_server.items(): + self.cli_set(base_path + ['sflow', 'server', server]) + if 'port' in server_config: + self.cli_set(base_path + ['sflow', 'server', server, 'port', server_config['port']]) + + # commit changes + self.cli_commit() + + uacctd = read_file(uacctd_conf) + + # when 'disable-imt' is not configured on the CLI it must be present + self.assertNotIn(f'imt_path: /tmp/uacctd.pipe', uacctd) + self.assertNotIn(f'imt_mem_pools_number: 169', uacctd) + self.assertNotIn(f'plugins: memory', uacctd) + + for server, server_config in sflow_server.items(): + tmp_srv = server + tmp_srv = tmp_srv.replace(':', '-') + + if 'port' in server_config: + self.assertIn(f'sfprobe_receiver[sf_{tmp_srv}]: {bracketize_ipv6(server)}', uacctd) + else: + self.assertIn(f'sfprobe_receiver[sf_{tmp_srv}]: {bracketize_ipv6(server)}:6343', uacctd) + self.assertIn(f'sampling_rate[sf_{tmp_srv}]: {sampling_rate}', uacctd) + + def test_netflow(self): + engine_id = '33' + max_flows = '667' + sampling_rate = '100' + source_address = '192.0.2.1' + dummy_if = 'dum3842' + agent_address = '192.0.2.10' + version = '10' + tmo_expiry = '120' + tmo_flow = '1200' + tmo_icmp = '60' + tmo_max = '50000' + tmo_tcp_fin = '100' + tmo_tcp_generic = '120' + tmo_tcp_rst = '99' + tmo_udp = '10' + + netflow_server = { + '11.22.33.44' : { }, + '55.66.77.88' : { 'port' : '6000' }, + '2001:db8::1' : { }, + } + + self.cli_set(['interfaces', 'dummy', dummy_if, 'address', agent_address + '/32']) + self.cli_set(['interfaces', 'dummy', dummy_if, 'address', source_address + '/32']) + + for interface in Section.interfaces('ethernet'): + self.cli_set(base_path + ['interface', interface]) + + self.cli_set(base_path + ['netflow', 'engine-id', engine_id]) + self.cli_set(base_path + ['netflow', 'max-flows', max_flows]) + self.cli_set(base_path + ['netflow', 'sampling-rate', sampling_rate]) + self.cli_set(base_path + ['netflow', 'source-address', source_address]) + self.cli_set(base_path + ['netflow', 'version', version]) + + # timeouts + self.cli_set(base_path + ['netflow', 'timeout', 'expiry-interval', tmo_expiry]) + self.cli_set(base_path + ['netflow', 'timeout', 'flow-generic', tmo_flow]) + self.cli_set(base_path + ['netflow', 'timeout', 'icmp', tmo_icmp]) + self.cli_set(base_path + ['netflow', 'timeout', 'max-active-life', tmo_max]) + self.cli_set(base_path + ['netflow', 'timeout', 'tcp-fin', tmo_tcp_fin]) + self.cli_set(base_path + ['netflow', 'timeout', 'tcp-generic', tmo_tcp_generic]) + self.cli_set(base_path + ['netflow', 'timeout', 'tcp-rst', tmo_tcp_rst]) + self.cli_set(base_path + ['netflow', 'timeout', 'udp', tmo_udp]) + + # You need to configure at least one netflow server + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + for server, server_config in netflow_server.items(): + self.cli_set(base_path + ['netflow', 'server', server]) + if 'port' in server_config: + self.cli_set(base_path + ['netflow', 'server', server, 'port', server_config['port']]) + + # commit changes + self.cli_commit() + + uacctd = read_file(uacctd_conf) + + tmp = [] + for server, server_config in netflow_server.items(): + tmp_srv = server + tmp_srv = tmp_srv.replace('.', '-') + tmp_srv = tmp_srv.replace(':', '-') + tmp.append(f'nfprobe[nf_{tmp_srv}]') + tmp.append('memory') + self.assertIn('plugins: ' + ','.join(tmp), uacctd) + + for server, server_config in netflow_server.items(): + tmp_srv = server + tmp_srv = tmp_srv.replace('.', '-') + tmp_srv = tmp_srv.replace(':', '-') + + self.assertIn(f'nfprobe_engine[nf_{tmp_srv}]: {engine_id}', uacctd) + self.assertIn(f'nfprobe_maxflows[nf_{tmp_srv}]: {max_flows}', uacctd) + self.assertIn(f'sampling_rate[nf_{tmp_srv}]: {sampling_rate}', uacctd) + self.assertIn(f'nfprobe_source_ip[nf_{tmp_srv}]: {source_address}', uacctd) + self.assertIn(f'nfprobe_version[nf_{tmp_srv}]: {version}', uacctd) + + if 'port' in server_config: + self.assertIn(f'nfprobe_receiver[nf_{tmp_srv}]: {bracketize_ipv6(server)}', uacctd) + else: + self.assertIn(f'nfprobe_receiver[nf_{tmp_srv}]: {bracketize_ipv6(server)}:2055', uacctd) + + self.assertIn(f'nfprobe_timeouts[nf_{tmp_srv}]: expint={tmo_expiry}:general={tmo_flow}:icmp={tmo_icmp}:maxlife={tmo_max}:tcp.fin={tmo_tcp_fin}:tcp={tmo_tcp_generic}:tcp.rst={tmo_tcp_rst}:udp={tmo_udp}', uacctd) + + + self.cli_delete(['interfaces', 'dummy', dummy_if]) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_frr.py b/smoketest/scripts/cli/test_system_frr.py new file mode 100644 index 0000000..a2ce58b --- /dev/null +++ b/smoketest/scripts/cli/test_system_frr.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.utils.file import read_file + +config_file = '/etc/frr/daemons' +base_path = ['system', 'frr'] + +def daemons_config_parse(daemons_config): + # create regex for parsing daemons options + regex_daemon_config = re.compile( + r'^(?P<daemon_name>\w+)_options="(?P<daemon_options>.*)"$', re.M) + # create empty dict for config + daemons_config_dict = {} + # fill dictionary with actual config + for daemon in regex_daemon_config.finditer(daemons_config): + daemon_name = daemon.group('daemon_name') + daemon_options = daemon.group('daemon_options') + daemons_config_dict[daemon_name] = daemon_options.lstrip() + + # return daemons config + return (daemons_config_dict) + + +class TestSystemFRR(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestSystemFRR, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_frr_snmp_multipledaemons(self): + # test SNMP integration for multiple daemons + test_daemon_names = ['ospfd', 'bgpd'] + for test_daemon_name in test_daemon_names: + self.cli_set(base_path + ['snmp', test_daemon_name]) + self.cli_commit() + + # read the config file and check content + daemons_config = read_file(config_file) + daemons_config_dict = daemons_config_parse(daemons_config) + # prepare regex for matching SNMP integration + regex_snmp = re.compile(r'^.* -M snmp.*$') + for (daemon_name, daemon_options) in daemons_config_dict.items(): + snmp_enabled = regex_snmp.match(daemon_options) + if daemon_name in test_daemon_names: + self.assertTrue(snmp_enabled) + else: + self.assertFalse(snmp_enabled) + + def test_frr_snmp_add_remove(self): + # test enabling and disabling of SNMP integration + test_daemon_names = ['ospfd', 'bgpd'] + for test_daemon_name in test_daemon_names: + self.cli_set(base_path + ['snmp', test_daemon_name]) + self.cli_commit() + + self.cli_delete(base_path) + self.cli_commit() + + # read the config file and check content + daemons_config = read_file(config_file) + daemons_config_dict = daemons_config_parse(daemons_config) + # prepare regex for matching SNMP integration + regex_snmp = re.compile(r'^.* -M snmp.*$') + for test_daemon_name in test_daemon_names: + snmp_enabled = regex_snmp.match( + daemons_config_dict[test_daemon_name]) + self.assertFalse(snmp_enabled) + + def test_frr_snmp_empty(self): + # test empty config section + self.cli_set(base_path + ['snmp']) + self.cli_commit() + + # read the config file and check content + daemons_config = read_file(config_file) + daemons_config_dict = daemons_config_parse(daemons_config) + # prepare regex for matching SNMP integration + regex_snmp = re.compile(r'^.* -M snmp.*$') + for daemon_options in daemons_config_dict.values(): + snmp_enabled = regex_snmp.match(daemon_options) + self.assertFalse(snmp_enabled) + + def test_frr_bmp(self): + # test BMP + self.cli_set(base_path + ['bmp']) + self.cli_commit() + + # read the config file and check content + daemons_config = read_file(config_file) + daemons_config_dict = daemons_config_parse(daemons_config) + # prepare regex + regex_bmp = re.compile(r'^.* -M bmp.*$') + bmp_enabled = regex_bmp.match(daemons_config_dict['bgpd']) + self.assertTrue(bmp_enabled) + + def test_frr_irdp(self): + # test IRDP + self.cli_set(base_path + ['irdp']) + self.cli_commit() + + # read the config file and check content + daemons_config = read_file(config_file) + daemons_config_dict = daemons_config_parse(daemons_config) + # prepare regex + regex_irdp = re.compile(r'^.* -M irdp.*$') + irdp_enabled = regex_irdp.match(daemons_config_dict['zebra']) + self.assertTrue(irdp_enabled) + + def test_frr_bmp_and_snmp(self): + # test empty config section + self.cli_set(base_path + ['bmp']) + self.cli_set(base_path + ['snmp', 'bgpd']) + self.cli_commit() + + # read the config file and check content + daemons_config = read_file(config_file) + daemons_config_dict = daemons_config_parse(daemons_config) + # prepare regex + regex_snmp = re.compile(r'^.* -M bmp.*$') + regex_snmp = re.compile(r'^.* -M snmp.*$') + bmp_enabled = regex_snmp.match(daemons_config_dict['bgpd']) + snmp_enabled = regex_snmp.match(daemons_config_dict['bgpd']) + self.assertTrue(bmp_enabled) + self.assertTrue(snmp_enabled) + + def test_frr_file_descriptors(self): + file_descriptors = '4096' + + self.cli_set(base_path + ['descriptors', file_descriptors]) + self.cli_commit() + + # read the config file and check content + daemons_config = read_file(config_file) + self.assertIn(f'MAX_FDS={file_descriptors}', daemons_config) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_ip.py b/smoketest/scripts/cli/test_system_ip.py new file mode 100644 index 0000000..5b00902 --- /dev/null +++ b/smoketest/scripts/cli/test_system_ip.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.file import read_file + +base_path = ['system', 'ip'] + +class TestSystemIP(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_system_ip_forwarding(self): + # Test if IPv4 forwarding can be disabled globally, default is '1' + # which means forwarding enabled + all_forwarding = '/proc/sys/net/ipv4/conf/all/forwarding' + self.assertEqual(read_file(all_forwarding), '1') + + self.cli_set(base_path + ['disable-forwarding']) + self.cli_commit() + + self.assertEqual(read_file(all_forwarding), '0') + + def test_system_ip_multipath(self): + # Test IPv4 multipathing options, options default to off -> '0' + use_neigh = '/proc/sys/net/ipv4/fib_multipath_use_neigh' + hash_policy = '/proc/sys/net/ipv4/fib_multipath_hash_policy' + + self.assertEqual(read_file(use_neigh), '0') + self.assertEqual(read_file(hash_policy), '0') + + self.cli_set(base_path + ['multipath', 'ignore-unreachable-nexthops']) + self.cli_set(base_path + ['multipath', 'layer4-hashing']) + self.cli_commit() + + self.assertEqual(read_file(use_neigh), '1') + self.assertEqual(read_file(hash_policy), '1') + + def test_system_ip_arp_table_size(self): + # Maximum number of entries to keep in the ARP cache, the + # default is 8k + + gc_thresh3 = '/proc/sys/net/ipv4/neigh/default/gc_thresh3' + gc_thresh2 = '/proc/sys/net/ipv4/neigh/default/gc_thresh2' + gc_thresh1 = '/proc/sys/net/ipv4/neigh/default/gc_thresh1' + self.assertEqual(read_file(gc_thresh3), '8192') + self.assertEqual(read_file(gc_thresh2), '4096') + self.assertEqual(read_file(gc_thresh1), '1024') + + for size in [1024, 2048, 4096, 8192, 16384, 32768]: + self.cli_set(base_path + ['arp', 'table-size', str(size)]) + self.cli_commit() + + self.assertEqual(read_file(gc_thresh3), str(size)) + self.assertEqual(read_file(gc_thresh2), str(size // 2)) + self.assertEqual(read_file(gc_thresh1), str(size // 8)) + + def test_system_ip_protocol_route_map(self): + protocols = ['any', 'babel', 'bgp', 'connected', 'eigrp', 'isis', + 'kernel', 'ospf', 'rip', 'static', 'table'] + + for protocol in protocols: + self.cli_set(['policy', 'route-map', f'route-map-{protocol}', 'rule', '10', 'action', 'permit']) + self.cli_set(base_path + ['protocol', protocol, 'route-map', f'route-map-{protocol}']) + + self.cli_commit() + + # Verify route-map properly applied to FRR + frrconfig = self.getFRRconfig('ip protocol', end='', daemon='zebra') + for protocol in protocols: + self.assertIn(f'ip protocol {protocol} route-map route-map-{protocol}', frrconfig) + + # Delete route-maps + self.cli_delete(['policy', 'route-map']) + self.cli_delete(base_path + ['protocol']) + + self.cli_commit() + + # Verify route-map properly applied to FRR + frrconfig = self.getFRRconfig('ip protocol', end='', daemon='zebra') + self.assertNotIn(f'ip protocol', frrconfig) + + def test_system_ip_protocol_non_existing_route_map(self): + non_existing = 'non-existing' + self.cli_set(base_path + ['protocol', 'static', 'route-map', non_existing]) + + # VRF does yet not exist - an error must be thrown + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(['policy', 'route-map', non_existing, 'rule', '10', 'action', 'deny']) + + # Commit again + self.cli_commit() + + def test_system_ip_nht(self): + self.cli_set(base_path + ['nht', 'no-resolve-via-default']) + self.cli_commit() + # Verify CLI config applied to FRR + frrconfig = self.getFRRconfig('', end='', daemon='zebra') + self.assertIn(f'no ip nht resolve-via-default', frrconfig) + + self.cli_delete(base_path + ['nht', 'no-resolve-via-default']) + self.cli_commit() + # Verify CLI config removed to FRR + frrconfig = self.getFRRconfig('', end='', daemon='zebra') + self.assertNotIn(f'no ip nht resolve-via-default', frrconfig) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py new file mode 100644 index 0000000..0c77c1d --- /dev/null +++ b/smoketest/scripts/cli/test_system_ipv6.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.file import read_file + +base_path = ['system', 'ipv6'] + +file_forwarding = '/proc/sys/net/ipv6/conf/all/forwarding' +file_disable = '/proc/sys/net/ipv6/conf/all/disable_ipv6' +file_dad = '/proc/sys/net/ipv6/conf/all/accept_dad' +file_multipath = '/proc/sys/net/ipv6/fib_multipath_hash_policy' + +class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_system_ipv6_forwarding(self): + # Test if IPv6 forwarding can be disabled globally, default is '1' + # which means forwearding enabled + self.assertEqual(read_file(file_forwarding), '1') + + self.cli_set(base_path + ['disable-forwarding']) + self.cli_commit() + + self.assertEqual(read_file(file_forwarding), '0') + + def test_system_ipv6_strict_dad(self): + # This defaults to 1 + self.assertEqual(read_file(file_dad), '1') + + # Do not assign any IPv6 address on interfaces, this requires a reboot + # which can not be tested, but we can read the config file :) + self.cli_set(base_path + ['strict-dad']) + self.cli_commit() + + # Verify configuration file + self.assertEqual(read_file(file_dad), '2') + + def test_system_ipv6_multipath(self): + # This defaults to 0 + self.assertEqual(read_file(file_multipath), '0') + + # Do not assign any IPv6 address on interfaces, this requires a reboot + # which can not be tested, but we can read the config file :) + self.cli_set(base_path + ['multipath', 'layer4-hashing']) + self.cli_commit() + + # Verify configuration file + self.assertEqual(read_file(file_multipath), '1') + + def test_system_ipv6_neighbor_table_size(self): + # Maximum number of entries to keep in the ARP cache, the + # default is 8192 + + gc_thresh3 = '/proc/sys/net/ipv6/neigh/default/gc_thresh3' + gc_thresh2 = '/proc/sys/net/ipv6/neigh/default/gc_thresh2' + gc_thresh1 = '/proc/sys/net/ipv6/neigh/default/gc_thresh1' + self.assertEqual(read_file(gc_thresh3), '8192') + self.assertEqual(read_file(gc_thresh2), '4096') + self.assertEqual(read_file(gc_thresh1), '1024') + + for size in [1024, 2048, 4096, 8192, 16384, 32768]: + self.cli_set(base_path + ['neighbor', 'table-size', str(size)]) + self.cli_commit() + + self.assertEqual(read_file(gc_thresh3), str(size)) + self.assertEqual(read_file(gc_thresh2), str(size // 2)) + self.assertEqual(read_file(gc_thresh1), str(size // 8)) + + def test_system_ipv6_protocol_route_map(self): + protocols = ['any', 'babel', 'bgp', 'connected', 'isis', + 'kernel', 'ospfv3', 'ripng', 'static', 'table'] + + for protocol in protocols: + route_map = 'route-map-' + protocol.replace('ospfv3', 'ospf6') + + self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) + self.cli_set(base_path + ['protocol', protocol, 'route-map', route_map]) + + self.cli_commit() + + # Verify route-map properly applied to FRR + frrconfig = self.getFRRconfig('ipv6 protocol', end='', daemon='zebra') + for protocol in protocols: + # VyOS and FRR use a different name for OSPFv3 (IPv6) + if protocol == 'ospfv3': + protocol = 'ospf6' + self.assertIn(f'ipv6 protocol {protocol} route-map route-map-{protocol}', frrconfig) + + # Delete route-maps + self.cli_delete(['policy', 'route-map']) + self.cli_delete(base_path + ['protocol']) + + self.cli_commit() + + # Verify route-map properly applied to FRR + frrconfig = self.getFRRconfig('ipv6 protocol', end='', daemon='zebra') + self.assertNotIn(f'ipv6 protocol', frrconfig) + + def test_system_ipv6_protocol_non_existing_route_map(self): + non_existing = 'non-existing6' + self.cli_set(base_path + ['protocol', 'static', 'route-map', non_existing]) + + # VRF does yet not exist - an error must be thrown + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(['policy', 'route-map', non_existing, 'rule', '10', 'action', 'deny']) + + # Commit again + self.cli_commit() + + def test_system_ipv6_nht(self): + self.cli_set(base_path + ['nht', 'no-resolve-via-default']) + self.cli_commit() + # Verify CLI config applied to FRR + frrconfig = self.getFRRconfig('', end='', daemon='zebra') + self.assertIn(f'no ipv6 nht resolve-via-default', frrconfig) + + self.cli_delete(base_path + ['nht', 'no-resolve-via-default']) + self.cli_commit() + # Verify CLI config removed to FRR + frrconfig = self.getFRRconfig('', end='', daemon='zebra') + self.assertNotIn(f'no ipv6 nht resolve-via-default', frrconfig) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_lcd.py b/smoketest/scripts/cli/test_system_lcd.py new file mode 100644 index 0000000..fc440ca --- /dev/null +++ b/smoketest/scripts/cli/test_system_lcd.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 Francois Mertz fireboxled@gmail.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from configparser import ConfigParser + +from vyos.utils.process import process_named_running + +config_file = '/run/LCDd/LCDd.conf' +base_path = ['system', 'lcd'] + +class TestSystemLCD(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_system_display(self): + # configure some system display + self.cli_set(base_path + ['device', 'ttyS1']) + self.cli_set(base_path + ['model', 'cfa-533']) + + # commit changes + self.cli_commit() + + # load up ini-styled LCDd.conf + conf = ConfigParser() + conf.read(config_file) + + self.assertEqual(conf['CFontzPacket']['Model'], '533') + self.assertEqual(conf['CFontzPacket']['Device'], '/dev/ttyS1') + + # Check for running process + self.assertTrue(process_named_running('LCDd')) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_login.py b/smoketest/scripts/cli/test_system_login.py new file mode 100644 index 0000000..28abba0 --- /dev/null +++ b/smoketest/scripts/cli/test_system_login.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from gzip import GzipFile +from subprocess import Popen, PIPE +from pwd import getpwall + +from vyos.configsession import ConfigSessionError +from vyos.utils.auth import get_current_user +from vyos.utils.process import cmd +from vyos.utils.file import read_file +from vyos.template import inc_ip + +base_path = ['system', 'login'] +users = ['vyos1', 'vyos-roxx123', 'VyOS-123_super.Nice'] + +ssh_pubkey = """ +AAAAB3NzaC1yc2EAAAADAQABAAABgQD0NuhUOEtMIKnUVFIHoFatqX/c4mjerXyF +TlXYfVt6Ls2NZZsUSwHbnhK4BKDrPvVZMW/LycjQPzWW6TGtk6UbZP1WqdviQ9hP +jsEeKJSTKciMSvQpjBWyEQQPXSKYQC7ryQQilZDqnJgzqwzejKEe+nhhOdBvjuZc +uukxjT69E0UmWAwLxzvfiurwiQaC7tG+PwqvtfHOPL3i6yRO2C5ORpFarx8PeGDS +IfIXJCr3LoUbLHeuE7T2KaOKQcX0UsWJ4CoCapRLpTVYPDB32BYfgq7cW1Sal1re +EGH2PzuXBklinTBgCHA87lHjpwDIAqdmvMj7SXIW9LxazLtP+e37sexE7xEs0cpN +l68txdDbY2P2Kbz5mqGFfCvBYKv9V2clM5vyWNy/Xp5TsCis89nn83KJmgFS7sMx +pHJz8umqkxy3hfw0K7BRFtjWd63sbOP8Q/SDV7LPaIfIxenA9zv2rY7y+AIqTmSr +TTSb0X1zPGxPIRFy5GoGtO9Mm5h4OZk= +""" + +class TestSystemLogin(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestSystemLogin, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration which will break this test + cls.cli_delete(cls, base_path + ['radius']) + cls.cli_delete(cls, base_path + ['tacacs']) + + def tearDown(self): + # Delete individual users from configuration + for user in users: + self.cli_delete(base_path + ['user', user]) + + self.cli_delete(base_path + ['radius']) + self.cli_delete(base_path + ['tacacs']) + + self.cli_commit() + + # After deletion, a user is not allowed to remain in /etc/passwd + usernames = [x[0] for x in getpwall()] + for user in users: + self.assertNotIn(user, usernames) + + def test_add_linux_system_user(self): + # We are not allowed to re-use a username already taken by the Linux + # base system + system_user = 'backup' + self.cli_set(base_path + ['user', system_user, 'authentication', 'plaintext-password', system_user]) + + # check validate() - can not add username which exists on the Debian + # base system (UID < 1000) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete(base_path + ['user', system_user]) + + def test_system_login_user(self): + # Check if user can be created and we can SSH to localhost + self.cli_set(['service', 'ssh', 'port', '22']) + + for user in users: + name = "VyOS Roxx " + user + home_dir = "/tmp/" + user + + self.cli_set(base_path + ['user', user, 'authentication', 'plaintext-password', user]) + self.cli_set(base_path + ['user', user, 'full-name', 'VyOS Roxx']) + self.cli_set(base_path + ['user', user, 'home-directory', home_dir]) + + self.cli_commit() + + for user in users: + tmp = ['su','-', user] + proc = Popen(tmp, stdin=PIPE, stdout=PIPE, stderr=PIPE) + tmp = "{}\nuname -a".format(user) + proc.stdin.write(tmp.encode()) + proc.stdin.flush() + (stdout, stderr) = proc.communicate() + + # stdout is something like this: + # b'Linux LR1.wue3 5.10.61-amd64-vyos #1 SMP Fri Aug 27 08:55:46 UTC 2021 x86_64 GNU/Linux\n' + self.assertTrue(len(stdout) > 40) + + locked_user = users[0] + # disable the first user in list + self.cli_set(base_path + ['user', locked_user, 'disable']) + self.cli_commit() + # check if account is locked + tmp = cmd(f'sudo passwd -S {locked_user}') + self.assertIn(f'{locked_user} L ', tmp) + + # unlock account + self.cli_delete(base_path + ['user', locked_user, 'disable']) + self.cli_commit() + # check if account is unlocked + tmp = cmd(f'sudo passwd -S {locked_user}') + self.assertIn(f'{locked_user} P ', tmp) + + + def test_system_login_otp(self): + otp_user = 'otp-test_user' + otp_password = 'SuperTestPassword' + otp_key = '76A3ZS6HFHBTOK2H4NDHTIVFPQ' + + self.cli_set(base_path + ['user', otp_user, 'authentication', 'plaintext-password', otp_password]) + self.cli_set(base_path + ['user', otp_user, 'authentication', 'otp', 'key', otp_key]) + + self.cli_commit() + + # Check if OTP key was written properly + tmp = cmd(f'sudo head -1 /home/{otp_user}/.google_authenticator') + self.assertIn(otp_key, tmp) + + self.cli_delete(base_path + ['user', otp_user]) + + def test_system_user_ssh_key(self): + ssh_user = 'ssh-test_user' + public_keys = 'vyos_test@domain-foo.com' + type = 'ssh-rsa' + + self.cli_set(base_path + ['user', ssh_user, 'authentication', 'public-keys', public_keys, 'key', ssh_pubkey.replace('\n','')]) + + # check validate() - missing type for public-key + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['user', ssh_user, 'authentication', 'public-keys', public_keys, 'type', type]) + + self.cli_commit() + + # Check that SSH key was written properly + tmp = cmd(f'sudo cat /home/{ssh_user}/.ssh/authorized_keys') + key = f'{type} ' + ssh_pubkey.replace('\n','') + self.assertIn(key, tmp) + + self.cli_delete(base_path + ['user', ssh_user]) + + def test_radius_kernel_features(self): + # T2886: RADIUS requires some Kernel options to be present + kernel_config = GzipFile('/proc/config.gz').read().decode('UTF-8') + + # T2886 - RADIUS authentication - check for statically compiled options + options = ['CONFIG_AUDIT', 'CONFIG_AUDITSYSCALL', 'CONFIG_AUDIT_ARCH'] + + for option in options: + self.assertIn(f'{option}=y', kernel_config) + + def test_system_login_radius_ipv4(self): + # Verify generated RADIUS configuration files + + radius_key = 'VyOSsecretVyOS' + radius_server = '172.16.100.10' + radius_source = '127.0.0.1' + radius_port = '2000' + radius_timeout = '1' + + self.cli_set(base_path + ['radius', 'server', radius_server, 'key', radius_key]) + self.cli_set(base_path + ['radius', 'server', radius_server, 'port', radius_port]) + self.cli_set(base_path + ['radius', 'server', radius_server, 'timeout', radius_timeout]) + self.cli_set(base_path + ['radius', 'source-address', radius_source]) + self.cli_set(base_path + ['radius', 'source-address', inc_ip(radius_source, 1)]) + + # check validate() - Only one IPv4 source-address supported + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['radius', 'source-address', inc_ip(radius_source, 1)]) + + self.cli_commit() + + # this file must be read with higher permissions + pam_radius_auth_conf = cmd('sudo cat /etc/pam_radius_auth.conf') + tmp = re.findall(r'\n?{}:{}\s+{}\s+{}\s+{}'.format(radius_server, + radius_port, radius_key, radius_timeout, + radius_source), pam_radius_auth_conf) + self.assertTrue(tmp) + + # required, static options + self.assertIn('priv-lvl 15', pam_radius_auth_conf) + self.assertIn('mapped_priv_user radius_priv_user', pam_radius_auth_conf) + + # PAM + pam_common_account = read_file('/etc/pam.d/common-account') + self.assertIn('pam_radius_auth.so', pam_common_account) + + pam_common_auth = read_file('/etc/pam.d/common-auth') + self.assertIn('pam_radius_auth.so', pam_common_auth) + + pam_common_session = read_file('/etc/pam.d/common-session') + self.assertIn('pam_radius_auth.so', pam_common_session) + + pam_common_session_noninteractive = read_file('/etc/pam.d/common-session-noninteractive') + self.assertIn('pam_radius_auth.so', pam_common_session_noninteractive) + + # NSS + nsswitch_conf = read_file('/etc/nsswitch.conf') + tmp = re.findall(r'passwd:\s+mapuid\s+files\s+mapname', nsswitch_conf) + self.assertTrue(tmp) + + tmp = re.findall(r'group:\s+mapname\s+files', nsswitch_conf) + self.assertTrue(tmp) + + def test_system_login_radius_ipv6(self): + # Verify generated RADIUS configuration files + + radius_key = 'VyOS-VyOS' + radius_server = '2001:db8::1' + radius_source = '::1' + radius_port = '4000' + radius_timeout = '4' + + self.cli_set(base_path + ['radius', 'server', radius_server, 'key', radius_key]) + self.cli_set(base_path + ['radius', 'server', radius_server, 'port', radius_port]) + self.cli_set(base_path + ['radius', 'server', radius_server, 'timeout', radius_timeout]) + self.cli_set(base_path + ['radius', 'source-address', radius_source]) + self.cli_set(base_path + ['radius', 'source-address', inc_ip(radius_source, 1)]) + + # check validate() - Only one IPv4 source-address supported + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['radius', 'source-address', inc_ip(radius_source, 1)]) + + self.cli_commit() + + # this file must be read with higher permissions + pam_radius_auth_conf = cmd('sudo cat /etc/pam_radius_auth.conf') + tmp = re.findall(r'\n?\[{}\]:{}\s+{}\s+{}\s+\[{}\]'.format(radius_server, + radius_port, radius_key, radius_timeout, + radius_source), pam_radius_auth_conf) + self.assertTrue(tmp) + + # required, static options + self.assertIn('priv-lvl 15', pam_radius_auth_conf) + self.assertIn('mapped_priv_user radius_priv_user', pam_radius_auth_conf) + + # PAM + pam_common_account = read_file('/etc/pam.d/common-account') + self.assertIn('pam_radius_auth.so', pam_common_account) + + pam_common_auth = read_file('/etc/pam.d/common-auth') + self.assertIn('pam_radius_auth.so', pam_common_auth) + + pam_common_session = read_file('/etc/pam.d/common-session') + self.assertIn('pam_radius_auth.so', pam_common_session) + + pam_common_session_noninteractive = read_file('/etc/pam.d/common-session-noninteractive') + self.assertIn('pam_radius_auth.so', pam_common_session_noninteractive) + + # NSS + nsswitch_conf = read_file('/etc/nsswitch.conf') + tmp = re.findall(r'passwd:\s+mapuid\s+files\s+mapname', nsswitch_conf) + self.assertTrue(tmp) + + tmp = re.findall(r'group:\s+mapname\s+files', nsswitch_conf) + self.assertTrue(tmp) + + def test_system_login_max_login_session(self): + max_logins = '2' + timeout = '600' + + self.cli_set(base_path + ['max-login-session', max_logins]) + + # 'max-login-session' must be only with 'timeout' option + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['timeout', timeout]) + + self.cli_commit() + + security_limits = read_file('/etc/security/limits.d/10-vyos.conf') + self.assertIn(f'* - maxsyslogins {max_logins}', security_limits) + + self.cli_delete(base_path + ['timeout']) + self.cli_delete(base_path + ['max-login-session']) + + def test_system_login_tacacs(self): + tacacs_secret = 'tac_plus_key' + tacacs_servers = ['100.64.0.11', '100.64.0.12'] + + # Enable TACACS + for server in tacacs_servers: + self.cli_set(base_path + ['tacacs', 'server', server, 'key', tacacs_secret]) + + self.cli_commit() + + # NSS + nsswitch_conf = read_file('/etc/nsswitch.conf') + tmp = re.findall(r'passwd:\s+tacplus\s+files', nsswitch_conf) + self.assertTrue(tmp) + + tmp = re.findall(r'group:\s+tacplus\s+files', nsswitch_conf) + self.assertTrue(tmp) + + # PAM TACACS configuration + pam_tacacs_conf = read_file('/etc/tacplus_servers') + # NSS TACACS configuration + nss_tacacs_conf = read_file('/etc/tacplus_nss.conf') + # Users have individual home directories + self.assertIn('user_homedir=1', pam_tacacs_conf) + + # specify services + self.assertIn('service=shell', pam_tacacs_conf) + self.assertIn('protocol=ssh', pam_tacacs_conf) + + for server in tacacs_servers: + self.assertIn(f'secret={tacacs_secret}', pam_tacacs_conf) + self.assertIn(f'server={server}', pam_tacacs_conf) + + self.assertIn(f'secret={tacacs_secret}', nss_tacacs_conf) + self.assertIn(f'server={server}', nss_tacacs_conf) + + def test_delete_current_user(self): + current_user = get_current_user() + + # We are not allowed to delete the current user + self.cli_delete(base_path + ['user', current_user]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_discard() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_logs.py b/smoketest/scripts/cli/test_system_logs.py new file mode 100644 index 0000000..17cce5c --- /dev/null +++ b/smoketest/scripts/cli/test_system_logs.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import unittest +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.utils.file import read_file + +# path to logrotate configs +logrotate_atop_file = '/etc/logrotate.d/vyos-atop' +logrotate_rsyslog_file = '/etc/logrotate.d/vyos-rsyslog' +# default values +default_atop_maxsize = '10M' +default_atop_rotate = '10' +default_rsyslog_size = '1M' +default_rsyslog_rotate = '10' + +base_path = ['system', 'logs'] + + +def logrotate_config_parse(file_path): + # read the file + logrotate_config = read_file(file_path) + # create regex for parsing options + regex_options = re.compile( + r'(^\s+(?P<option_name_script>postrotate|prerotate|firstaction|lastaction|preremove)\n(?P<option_value_script>((?!endscript).)*)\n\s+endscript\n)|(^\s+(?P<option_name>[\S]+)([ \t]+(?P<option_value>\S+))*$)', + re.M | re.S) + # create empty dict for config + logrotate_config_dict = {} + # fill dictionary with actual config + for option in regex_options.finditer(logrotate_config): + option_name = option.group('option_name') + option_value = option.group('option_value') + option_name_script = option.group('option_name_script') + option_value_script = option.group('option_value_script') + if option_name: + logrotate_config_dict[option_name] = option_value + if option_name_script: + logrotate_config_dict[option_name_script] = option_value_script + + # return config dictionary + return (logrotate_config_dict) + + +class TestSystemLogs(VyOSUnitTestSHIM.TestCase): + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_logs_defaults(self): + # test with empty section for default values + self.cli_set(base_path) + self.cli_commit() + + # read the config file and check content + logrotate_config_atop = logrotate_config_parse(logrotate_atop_file) + logrotate_config_rsyslog = logrotate_config_parse( + logrotate_rsyslog_file) + self.assertEqual(logrotate_config_atop['maxsize'], default_atop_maxsize) + self.assertEqual(logrotate_config_atop['rotate'], default_atop_rotate) + self.assertEqual(logrotate_config_rsyslog['size'], default_rsyslog_size) + self.assertEqual(logrotate_config_rsyslog['rotate'], + default_rsyslog_rotate) + + def test_logs_atop_maxsize(self): + # test for maxsize option + self.cli_set(base_path + ['logrotate', 'atop', 'max-size', '50']) + self.cli_commit() + + # read the config file and check content + logrotate_config = logrotate_config_parse(logrotate_atop_file) + self.assertEqual(logrotate_config['maxsize'], '50M') + + def test_logs_atop_rotate(self): + # test for rotate option + self.cli_set(base_path + ['logrotate', 'atop', 'rotate', '50']) + self.cli_commit() + + # read the config file and check content + logrotate_config = logrotate_config_parse(logrotate_atop_file) + self.assertEqual(logrotate_config['rotate'], '50') + + def test_logs_rsyslog_size(self): + # test for size option + self.cli_set(base_path + ['logrotate', 'messages', 'max-size', '50']) + self.cli_commit() + + # read the config file and check content + logrotate_config = logrotate_config_parse(logrotate_rsyslog_file) + self.assertEqual(logrotate_config['size'], '50M') + + def test_logs_rsyslog_rotate(self): + # test for rotate option + self.cli_set(base_path + ['logrotate', 'messages', 'rotate', '50']) + self.cli_commit() + + # read the config file and check content + logrotate_config = logrotate_config_parse(logrotate_rsyslog_file) + self.assertEqual(logrotate_config['rotate'], '50') + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_option.py b/smoketest/scripts/cli/test_system_option.py new file mode 100644 index 0000000..ffb1d76 --- /dev/null +++ b/smoketest/scripts/cli/test_system_option.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.utils.file import read_file +from vyos.utils.process import is_systemd_service_active +from vyos.utils.system import sysctl_read + +base_path = ['system', 'option'] + +class TestSystemOption(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_ctrl_alt_delete(self): + self.cli_set(base_path + ['ctrl-alt-delete', 'reboot']) + self.cli_commit() + + tmp = os.readlink('/lib/systemd/system/ctrl-alt-del.target') + self.assertEqual(tmp, '/lib/systemd/system/reboot.target') + + self.cli_set(base_path + ['ctrl-alt-delete', 'poweroff']) + self.cli_commit() + + tmp = os.readlink('/lib/systemd/system/ctrl-alt-del.target') + self.assertEqual(tmp, '/lib/systemd/system/poweroff.target') + + self.cli_delete(base_path + ['ctrl-alt-delete', 'poweroff']) + self.cli_commit() + self.assertFalse(os.path.exists('/lib/systemd/system/ctrl-alt-del.target')) + + def test_reboot_on_panic(self): + panic_file = '/proc/sys/kernel/panic' + + tmp = read_file(panic_file) + self.assertEqual(tmp, '0') + + self.cli_set(base_path + ['reboot-on-panic']) + self.cli_commit() + + tmp = read_file(panic_file) + self.assertEqual(tmp, '60') + + def test_performance(self): + tuned_service = 'tuned.service' + + self.assertFalse(is_systemd_service_active(tuned_service)) + + # T3204 sysctl options must not be overwritten by tuned + gc_thresh1 = '131072' + gc_thresh2 = '262000' + gc_thresh3 = '524000' + + self.cli_set(['system', 'sysctl', 'parameter', 'net.ipv4.neigh.default.gc_thresh1', 'value', gc_thresh1]) + self.cli_set(['system', 'sysctl', 'parameter', 'net.ipv4.neigh.default.gc_thresh2', 'value', gc_thresh2]) + self.cli_set(['system', 'sysctl', 'parameter', 'net.ipv4.neigh.default.gc_thresh3', 'value', gc_thresh3]) + + self.cli_set(base_path + ['performance', 'throughput']) + self.cli_commit() + + self.assertTrue(is_systemd_service_active(tuned_service)) + + self.assertEqual(sysctl_read('net.ipv4.neigh.default.gc_thresh1'), gc_thresh1) + self.assertEqual(sysctl_read('net.ipv4.neigh.default.gc_thresh2'), gc_thresh2) + self.assertEqual(sysctl_read('net.ipv4.neigh.default.gc_thresh3'), gc_thresh3) + + def test_ssh_client_options(self): + loopback = 'lo' + ssh_client_opt_file = '/etc/ssh/ssh_config.d/91-vyos-ssh-client-options.conf' + + self.cli_set(['system', 'option', 'ssh-client', 'source-interface', loopback]) + self.cli_commit() + + tmp = read_file(ssh_client_opt_file) + self.assertEqual(tmp, f'BindInterface {loopback}') + + self.cli_delete(['system', 'option']) + self.cli_commit() + self.assertFalse(os.path.exists(ssh_client_opt_file)) + + +if __name__ == '__main__': + unittest.main(verbosity=2, failfast=True) diff --git a/smoketest/scripts/cli/test_system_resolvconf.py b/smoketest/scripts/cli/test_system_resolvconf.py new file mode 100644 index 0000000..d8726a3 --- /dev/null +++ b/smoketest/scripts/cli/test_system_resolvconf.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.utils.file import read_file + +RESOLV_CONF = '/etc/resolv.conf' + +name_servers = ['192.0.2.10', '2001:db8:1::100'] +domain_name = 'vyos.net' +domain_search = ['vyos.net', 'vyos.io'] + +base_path_nameserver = ['system', 'name-server'] +base_path_domainname = ['system', 'domain-name'] +base_path_domainsearch = ['system', 'domain-search'] + +def get_name_servers(): + resolv_conf = read_file(RESOLV_CONF) + return re.findall(r'\n?nameserver\s+(.*)', resolv_conf) + +def get_domain_name(): + resolv_conf = read_file(RESOLV_CONF) + res = re.findall(r'\n?domain\s+(.*)', resolv_conf) + return res[0] if res else None + +def get_domain_searches(): + resolv_conf = read_file(RESOLV_CONF) + res = re.findall(r'\n?search\s+(.*)', resolv_conf) + return res[0].split() if res else [] + +class TestSystemResolvConf(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestSystemResolvConf, cls).setUpClass() + # Clear out current configuration to allow running this test on a live system + cls.cli_delete(cls, base_path_nameserver) + cls.cli_delete(cls, base_path_domainname) + cls.cli_delete(cls, base_path_domainsearch) + + def tearDown(self): + # Delete test entries servers + self.cli_delete(base_path_nameserver) + self.cli_delete(base_path_domainname) + self.cli_delete(base_path_domainsearch) + self.cli_commit() + + def test_nameserver(self): + # Check if server is added to resolv.conf + for s in name_servers: + self.cli_set(base_path_nameserver + [s]) + self.cli_commit() + + for s in get_name_servers(): + self.assertTrue(s in name_servers) + + # Test if a deleted server disappears from resolv.conf + for s in name_servers: + self.cli_delete(base_path_nameserver + [s]) + self.cli_commit() + + for s in get_name_servers(): + self.assertTrue(s not in name_servers) + + def test_domainname(self): + # Check if domain-name is added to resolv.conf + self.cli_set(base_path_domainname + [domain_name]) + self.cli_commit() + + self.assertEqual(get_domain_name(), domain_name) + + # Test if domain-name disappears from resolv.conf + self.cli_delete(base_path_domainname + [domain_name]) + self.cli_commit() + + self.assertTrue(get_domain_name() is None) + + def test_domainsearch(self): + # Check if domain-search is added to resolv.conf + for s in domain_search: + self.cli_set(base_path_domainsearch + [s]) + self.cli_commit() + + for s in get_domain_searches(): + self.assertTrue(s in domain_search) + + # Test if domain-search disappears from resolv.conf + for s in domain_search: + self.cli_delete(base_path_domainsearch + [s]) + self.cli_commit() + + for s in get_domain_searches(): + self.assertTrue(s not in domain_search) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_sflow.py b/smoketest/scripts/cli/test_system_sflow.py new file mode 100644 index 0000000..74c0654 --- /dev/null +++ b/smoketest/scripts/cli/test_system_sflow.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +PROCESS_NAME = 'hsflowd' +base_path = ['system', 'sflow'] +vrf = 'mgmt' + +hsflowd_conf = '/run/sflow/hsflowd.conf' + +class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestSystemFlowAccounting, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + # after service removal process must no longer run + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(base_path) + self.cli_delete(['vrf', 'name', vrf]) + self.cli_commit() + + # after service removal process must no longer run + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_sflow(self): + agent_address = '192.0.2.5' + agent_interface = 'eth0' + polling = '24' + sampling_rate = '128' + server = '192.0.2.254' + local_server = '127.0.0.1' + port = '8192' + default_port = '6343' + mon_limit = '50' + + self.cli_set( + ['interfaces', 'dummy', 'dum0', 'address', f'{agent_address}/24']) + self.cli_set(base_path + ['agent-address', agent_address]) + self.cli_set(base_path + ['agent-interface', agent_interface]) + + # You need to configure at least one interface for sflow + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for interface in Section.interfaces('ethernet'): + self.cli_set(base_path + ['interface', interface]) + + self.cli_set(base_path + ['polling', polling]) + self.cli_set(base_path + ['sampling-rate', sampling_rate]) + self.cli_set(base_path + ['server', server, 'port', port]) + self.cli_set(base_path + ['server', local_server]) + self.cli_set(base_path + ['drop-monitor-limit', mon_limit]) + + # commit changes + self.cli_commit() + + # verify configuration + hsflowd = read_file(hsflowd_conf) + + self.assertIn(f'polling={polling}', hsflowd) + self.assertIn(f'sampling={sampling_rate}', hsflowd) + self.assertIn(f'agentIP={agent_address}', hsflowd) + self.assertIn(f'agent={agent_interface}', hsflowd) + self.assertIn(f'collector {{ ip = {server} udpport = {port} }}', hsflowd) + self.assertIn(f'collector {{ ip = {local_server} udpport = {default_port} }}', hsflowd) + self.assertIn(f'dropmon {{ limit={mon_limit} start=on sw=on hw=off }}', hsflowd) + self.assertIn('dbus { }', hsflowd) + + for interface in Section.interfaces('ethernet'): + self.assertIn(f'pcap {{ dev={interface} }}', hsflowd) + + def test_vrf(self): + interface = 'eth0' + server = '192.0.2.1' + + # Check if sFlow service can be bound to given VRF + self.cli_set(['vrf', 'name', vrf, 'table', '10100']) + self.cli_set(base_path + ['interface', interface]) + self.cli_set(base_path + ['server', server]) + self.cli_set(base_path + ['vrf', vrf]) + + # commit changes + self.cli_commit() + + # verify configuration + hsflowd = read_file(hsflowd_conf) + self.assertIn(f'collector {{ ip = {server} udpport = 6343 }}', hsflowd) # default port + self.assertIn(f'pcap {{ dev=eth0 }}', hsflowd) + + # Check for process in VRF + tmp = cmd(f'ip vrf pids {vrf}') + self.assertIn(PROCESS_NAME, tmp) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_syslog.py b/smoketest/scripts/cli/test_system_syslog.py new file mode 100644 index 0000000..c802cee --- /dev/null +++ b/smoketest/scripts/cli/test_system_syslog.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.utils.file import read_file +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'rsyslogd' +RSYSLOG_CONF = '/etc/rsyslog.d/00-vyos.conf' + +base_path = ['system', 'syslog'] + +def get_config_value(key): + tmp = read_file(RSYSLOG_CONF) + tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) + return tmp[0] + +class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestRSYSLOGService, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + # delete testing SYSLOG config + self.cli_delete(base_path) + self.cli_commit() + + # Check for running process + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_syslog_basic(self): + host1 = '127.0.0.10' + host2 = '127.0.0.20' + + self.cli_set(base_path + ['host', host1, 'port', '999']) + self.cli_set(base_path + ['host', host1, 'facility', 'all', 'level', 'all']) + self.cli_set(base_path + ['host', host2, 'facility', 'kern', 'level', 'err']) + self.cli_set(base_path + ['console', 'facility', 'all', 'level', 'warning']) + + self.cli_commit() + # verify log level and facilities in config file + # *.warning /dev/console + # *.* @198.51.100.1:999 + # kern.err @192.0.2.1:514 + config = [ + get_config_value('\*.\*'), + get_config_value('kern.err'), + get_config_value('\*.warning'), + ] + expected = [f'@{host1}:999', f'@{host2}:514', '/dev/console'] + + for i in range(0, 3): + self.assertIn(expected[i], config[i]) + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_syslog_global(self): + self.cli_set(['system', 'host-name', 'vyos']) + self.cli_set(['system', 'domain-name', 'example.local']) + self.cli_set(base_path + ['global', 'marker', 'interval', '600']) + self.cli_set(base_path + ['global', 'preserve-fqdn']) + self.cli_set(base_path + ['global', 'facility', 'kern', 'level', 'err']) + + self.cli_commit() + + config = cmd(f'sudo cat {RSYSLOG_CONF}') + expected = [ + '$MarkMessagePeriod 600', + '$PreserveFQDN on', + 'kern.err', + '$LocalHostName vyos.example.local', + ] + + for e in expected: + self.assertIn(e, config) + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py new file mode 100644 index 0000000..3b8687b --- /dev/null +++ b/smoketest/scripts/cli/test_vpn_ipsec.py @@ -0,0 +1,1359 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Interface +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +ethernet_path = ['interfaces', 'ethernet'] +tunnel_path = ['interfaces', 'tunnel'] +vti_path = ['interfaces', 'vti'] +nhrp_path = ['protocols', 'nhrp'] +base_path = ['vpn', 'ipsec'] + +charon_file = '/etc/strongswan.d/charon.conf' +dhcp_interfaces_file = '/tmp/ipsec_dhcp_interfaces' +swanctl_file = '/etc/swanctl/swanctl.conf' + +peer_ip = '203.0.113.45' +connection_name = 'main-branch' +local_id = 'left' +remote_id = 'right' +interface = 'eth1' +vif = '100' +esp_group = 'MyESPGroup' +ike_group = 'MyIKEGroup' +secret = 'MYSECRETKEY' +PROCESS_NAME = 'charon-systemd' +regex_uuid4 = '[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}' + +ca_name = 'MyVyOS-CA' +ca_pem = """ +MIICMDCCAdegAwIBAgIUBCzIjYvD7SPbx5oU18IYg7NVxQ0wCgYIKoZIzj0EAwIw +ZzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv +bWUtQ2l0eTENMAsGA1UECgwEVnlPUzEgMB4GA1UEAwwXSVBTZWMgU21va2V0ZXN0 +IFJvb3QgQ0EwHhcNMjMwOTI0MTIwMzQxWhcNMzMwOTIxMTIwMzQxWjBnMQswCQYD +VQQGEwJHQjETMBEGA1UECAwKU29tZS1TdGF0ZTESMBAGA1UEBwwJU29tZS1DaXR5 +MQ0wCwYDVQQKDARWeU9TMSAwHgYDVQQDDBdJUFNlYyBTbW9rZXRlc3QgUm9vdCBD +QTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEh8/yU572B3zmFxrGgHk+H7grYt +EHUJodY3gXNWMHz0gySrbGhsGtECDfP/G+T4Suk7cuVzB1wnLocSafD8TcqjYTBf +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsG +AQUFBwMCBggrBgEFBQcDATAdBgNVHQ4EFgQUTYoQJNlk7X87/gRegHnCnPef39Aw +CgYIKoZIzj0EAwIDRwAwRAIgX1spXjrUc10r3g/Zm4O31LU5O08J2vVqFo94zHE5 +0VgCIG4JK9Zg5O/yn4mYksZux7efiHRUzL2y2TXQ9IqrqM8W +""" + +int_ca_name = 'MyVyOS-IntCA' +int_ca_pem = """ +MIICYDCCAgWgAwIBAgIUcFx2BVYErHI+SneyPYHijxXt1cgwCgYIKoZIzj0EAwIw +ZzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv +bWUtQ2l0eTENMAsGA1UECgwEVnlPUzEgMB4GA1UEAwwXSVBTZWMgU21va2V0ZXN0 +IFJvb3QgQ0EwHhcNMjMwOTI0MTIwNTE5WhcNMzMwOTIwMTIwNTE5WjBvMQswCQYD +VQQGEwJHQjETMBEGA1UECAwKU29tZS1TdGF0ZTESMBAGA1UEBwwJU29tZS1DaXR5 +MQ0wCwYDVQQKDARWeU9TMSgwJgYDVQQDDB9JUFNlYyBTbW9rZXRlc3QgSW50ZXJt +ZWRpYXRlIENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIHw2G5dq3c715AcA +tzR++dYu1fLRFmHzRGTZOT7hLrh2Fg4hnKFPLOeUA5Qi50xCvjJ9JnonTyy2RfRH +axYizKOBhjCBgzASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAd +BgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwHQYDVR0OBBYEFC9KrFYtA+hO +l7vdMbWxTMAyLB7BMB8GA1UdIwQYMBaAFE2KECTZZO1/O/4EXoB5wpz3n9/QMAoG +CCqGSM49BAMCA0kAMEYCIQCnqWbElgOL9dGO3iLxasFNq/hM7vM/DzaiHi4BowxW +0gIhAMohefNj+QgLfPhvyODHIPE9LMyfp7lJEaCC2K8PCSFD +""" + +peer_name = 'peer1' +peer_cert = """ +MIICSTCCAfCgAwIBAgIUPxYleUgCo/glVVePze3QmAFgi6MwCgYIKoZIzj0EAwIw +bzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv +bWUtQ2l0eTENMAsGA1UECgwEVnlPUzEoMCYGA1UEAwwfSVBTZWMgU21va2V0ZXN0 +IEludGVybWVkaWF0ZSBDQTAeFw0yMzA5MjQxMjA2NDJaFw0yODA5MjIxMjA2NDJa +MGQxCzAJBgNVBAYTAkdCMRMwEQYDVQQIDApTb21lLVN0YXRlMRIwEAYDVQQHDAlT +b21lLUNpdHkxDTALBgNVBAoMBFZ5T1MxHTAbBgNVBAMMFElQU2VjIFNtb2tldGVz +dCBQZWVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZJtuTDu84uy++GMwRNLl +10JAXZxXQSDl+CdTWwjbQZURcdY+ia7BoaoYX/0VKPel3Se64rIUQQLQoY/9MJb9 +UKN1MHMwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI +KwYBBQUHAwEwHQYDVR0OBBYEFNJCdnkm3cAmf04UwOKL7IqMJ6OXMB8GA1UdIwQY +MBaAFC9KrFYtA+hOl7vdMbWxTMAyLB7BMAoGCCqGSM49BAMCA0cAMEQCIGVnDRUy +UJ0U/deDvrBo1+AakZndkNAMN/XNo5a5GzhEAiBCY7E/3b0BIO8FiIbVB3iDcaxg +g7ET2RgWxvhEoN3ZRw== +""" + +peer_key = """ +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgVDEZDK7q/T+tiJUV +WLKS3ZYDfZ4lZv0C1gJpYq0gWP2hRANCAARkm25MO7zi7L74YzBE0uXXQkBdnFdB +IOX4J1NbCNtBlRFx1j6JrsGhqhhf/RUo96XdJ7rishRBAtChj/0wlv1Q +""" + +swanctl_dir = '/etc/swanctl' +CERT_PATH = f'{swanctl_dir}/x509/' +CA_PATH = f'{swanctl_dir}/x509ca/' + +class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): + skip_process_check = False + + @classmethod + def setUpClass(cls): + super(TestVPNIPsec, cls).setUpClass() + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + cls.cli_delete(cls, ['pki']) + + cls.cli_set(cls, base_path + ['interface', f'{interface}.{vif}']) + + @classmethod + def tearDownClass(cls): + super(TestVPNIPsec, cls).tearDownClass() + cls.cli_delete(cls, base_path + ['interface', f'{interface}.{vif}']) + + def setUp(self): + # Set IKE/ESP Groups + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes128']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha1']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'dh-group', '2']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'encryption', 'aes128']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'hash', 'sha1']) + + def tearDown(self): + # Check for running process + if not self.skip_process_check: + self.assertTrue(process_named_running(PROCESS_NAME)) + else: + self.skip_process_check = False # Reset + + self.cli_delete(base_path) + self.cli_delete(tunnel_path) + self.cli_delete(vti_path) + self.cli_commit() + + # Check for no longer running process + self.assertFalse(process_named_running(PROCESS_NAME)) + + def setupPKI(self): + self.cli_set(['pki', 'ca', ca_name, 'certificate', ca_pem.replace('\n','')]) + self.cli_set(['pki', 'ca', int_ca_name, 'certificate', int_ca_pem.replace('\n','')]) + self.cli_set(['pki', 'certificate', peer_name, 'certificate', peer_cert.replace('\n','')]) + self.cli_set(['pki', 'certificate', peer_name, 'private', 'key', peer_key.replace('\n','')]) + + def tearDownPKI(self): + self.cli_delete(['pki']) + + def test_dhcp_fail_handling(self): + # Skip process check - connection is not created for this test + self.skip_process_check = True + + # Interface for dhcp-interface + self.cli_set(ethernet_path + [interface, 'vif', vif, 'address', 'dhcp']) # Use VLAN to avoid getting IP from qemu dhcp server + + # vpn ipsec auth psk <tag> id <x.x.x.x> + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret]) + + # Site to site + peer_base_path = base_path + ['site-to-site', 'peer', connection_name] + self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret']) + self.cli_set(peer_base_path + ['ike-group', ike_group]) + self.cli_set(peer_base_path + ['default-esp-group', esp_group]) + self.cli_set(peer_base_path + ['dhcp-interface', f'{interface}.{vif}']) + self.cli_set(peer_base_path + ['tunnel', '1', 'protocol', 'gre']) + + self.cli_commit() + + self.assertTrue(os.path.exists(dhcp_interfaces_file)) + + dhcp_interfaces = read_file(dhcp_interfaces_file) + self.assertIn(f'{interface}.{vif}', dhcp_interfaces) # Ensure dhcp interface was added for dhclient hook + + self.cli_delete(ethernet_path + [interface, 'vif', vif, 'address']) + + def test_site_to_site(self): + self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) + + local_address = '192.0.2.10' + priority = '20' + life_bytes = '100000' + life_packets = '2000000' + + # vpn ipsec auth psk <tag> id <x.x.x.x> + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_address]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret]) + + # Site to site + peer_base_path = base_path + ['site-to-site', 'peer', connection_name] + + self.cli_set(base_path + ['esp-group', esp_group, 'life-bytes', life_bytes]) + self.cli_set(base_path + ['esp-group', esp_group, 'life-packets', life_packets]) + + self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret']) + self.cli_set(peer_base_path + ['ike-group', ike_group]) + self.cli_set(peer_base_path + ['default-esp-group', esp_group]) + self.cli_set(peer_base_path + ['local-address', local_address]) + self.cli_set(peer_base_path + ['remote-address', peer_ip]) + self.cli_set(peer_base_path + ['tunnel', '1', 'protocol', 'tcp']) + self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.10.0/24']) + self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.11.0/24']) + self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'port', '443']) + self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'prefix', '172.17.10.0/24']) + self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'prefix', '172.17.11.0/24']) + self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'port', '443']) + + self.cli_set(peer_base_path + ['tunnel', '2', 'local', 'prefix', '10.1.0.0/16']) + self.cli_set(peer_base_path + ['tunnel', '2', 'remote', 'prefix', '10.2.0.0/16']) + self.cli_set(peer_base_path + ['tunnel', '2', 'priority', priority]) + + self.cli_commit() + + # Verify strongSwan configuration + swanctl_conf = read_file(swanctl_file) + swanctl_conf_lines = [ + f'version = 2', + f'auth = psk', + f'life_bytes = {life_bytes}', + f'life_packets = {life_packets}', + f'rekey_time = 28800s', # default value + f'proposals = aes128-sha1-modp1024', + f'esp_proposals = aes128-sha1-modp1024', + f'life_time = 3600s', # default value + f'local_addrs = {local_address} # dhcp:no', + f'remote_addrs = {peer_ip}', + f'mode = tunnel', + f'{connection_name}-tunnel-1', + f'local_ts = 172.16.10.0/24[tcp/443],172.16.11.0/24[tcp/443]', + f'remote_ts = 172.17.10.0/24[tcp/443],172.17.11.0/24[tcp/443]', + f'mode = tunnel', + f'{connection_name}-tunnel-2', + f'local_ts = 10.1.0.0/16', + f'remote_ts = 10.2.0.0/16', + f'priority = {priority}', + f'mode = tunnel', + f'replay_window = 32', + ] + for line in swanctl_conf_lines: + self.assertIn(line, swanctl_conf) + + # if dpd is not specified it should not be enabled (see T6599) + swanctl_unexpected_lines = [ + f'dpd_timeout' + f'dpd_delay' + ] + + for unexpected_line in swanctl_unexpected_lines: + self.assertNotIn(unexpected_line, swanctl_conf) + + swanctl_secrets_lines = [ + f'id-{regex_uuid4} = "{local_id}"', + f'id-{regex_uuid4} = "{remote_id}"', + f'id-{regex_uuid4} = "{local_address}"', + f'id-{regex_uuid4} = "{peer_ip}"', + f'secret = "{secret}"' + ] + for line in swanctl_secrets_lines: + self.assertRegex(swanctl_conf, fr'{line}') + + + def test_site_to_site_vti(self): + local_address = '192.0.2.10' + vti = 'vti10' + # IKE + self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) + self.cli_set(base_path + ['ike-group', ike_group, 'disable-mobike']) + # ESP + self.cli_set(base_path + ['esp-group', esp_group, 'compression']) + # VTI interface + self.cli_set(vti_path + [vti, 'address', '10.1.1.1/24']) + + # vpn ipsec auth psk <tag> id <x.x.x.x> + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret]) + + # Site to site + peer_base_path = base_path + ['site-to-site', 'peer', connection_name] + self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret']) + self.cli_set(peer_base_path + ['connection-type', 'none']) + self.cli_set(peer_base_path + ['force-udp-encapsulation']) + self.cli_set(peer_base_path + ['ike-group', ike_group]) + self.cli_set(peer_base_path + ['default-esp-group', esp_group]) + self.cli_set(peer_base_path + ['local-address', local_address]) + self.cli_set(peer_base_path + ['remote-address', peer_ip]) + self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.10.0/24']) + self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.11.0/24']) + self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'prefix', '172.17.10.0/24']) + self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'prefix', '172.17.11.0/24']) + self.cli_set(peer_base_path + ['vti', 'bind', vti]) + self.cli_set(peer_base_path + ['vti', 'esp-group', esp_group]) + + self.cli_commit() + + swanctl_conf = read_file(swanctl_file) + if_id = vti.lstrip('vti') + # The key defaults to 0 and will match any policies which similarly do + # not have a lookup key configuration - thus we shift the key by one + # to also support a vti0 interface + if_id = str(int(if_id) +1) + swanctl_conf_lines = [ + f'version = 2', + f'auth = psk', + f'proposals = aes128-sha1-modp1024', + f'esp_proposals = aes128-sha1-modp1024', + f'local_addrs = {local_address} # dhcp:no', + f'mobike = no', + f'remote_addrs = {peer_ip}', + f'mode = tunnel', + f'local_ts = 172.16.10.0/24,172.16.11.0/24', + f'remote_ts = 172.17.10.0/24,172.17.11.0/24', + f'ipcomp = yes', + f'start_action = none', + f'replay_window = 32', + f'if_id_in = {if_id}', # will be 11 for vti10 - shifted by one + f'if_id_out = {if_id}', + f'updown = "/etc/ipsec.d/vti-up-down {vti}"' + ] + for line in swanctl_conf_lines: + self.assertIn(line, swanctl_conf) + + swanctl_secrets_lines = [ + f'id-{regex_uuid4} = "{local_id}"', + f'id-{regex_uuid4} = "{remote_id}"', + f'secret = "{secret}"' + ] + for line in swanctl_secrets_lines: + self.assertRegex(swanctl_conf, fr'{line}') + + # Site-to-site interfaces should start out as 'down' + self.assertEqual(Interface(vti).get_admin_state(), 'down') + + # Disable PKI + self.tearDownPKI() + + + def test_dmvpn(self): + tunnel_if = 'tun100' + nhrp_secret = 'secret' + ike_lifetime = '3600' + esp_lifetime = '1800' + + # Tunnel + self.cli_set(tunnel_path + [tunnel_if, 'address', '172.16.253.134/29']) + self.cli_set(tunnel_path + [tunnel_if, 'encapsulation', 'gre']) + self.cli_set(tunnel_path + [tunnel_if, 'source-address', '192.0.2.1']) + self.cli_set(tunnel_path + [tunnel_if, 'enable-multicast']) + self.cli_set(tunnel_path + [tunnel_if, 'parameters', 'ip', 'key', '1']) + + # NHRP + self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'cisco-authentication', nhrp_secret]) + self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'holding-time', '300']) + self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'multicast', 'dynamic']) + self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'redirect']) + self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'shortcut']) + + # IKE/ESP Groups + self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', esp_lifetime]) + self.cli_set(base_path + ['esp-group', esp_group, 'mode', 'transport']) + self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'dh-group2']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'hash', 'sha1']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'encryption', '3des']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'hash', 'md5']) + + self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev1']) + self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime]) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '2']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha1']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'prf', 'prfsha1']) + + # Profile + self.cli_set(base_path + ['profile', 'NHRPVPN', 'authentication', 'mode', 'pre-shared-secret']) + self.cli_set(base_path + ['profile', 'NHRPVPN', 'authentication', 'pre-shared-secret', nhrp_secret]) + self.cli_set(base_path + ['profile', 'NHRPVPN', 'bind', 'tunnel', tunnel_if]) + self.cli_set(base_path + ['profile', 'NHRPVPN', 'esp-group', esp_group]) + self.cli_set(base_path + ['profile', 'NHRPVPN', 'ike-group', ike_group]) + + self.cli_commit() + + swanctl_conf = read_file(swanctl_file) + swanctl_lines = [ + f'proposals = aes128-sha1-modp1024,aes256-sha1-prfsha1-modp1024', + f'version = 1', + f'rekey_time = {ike_lifetime}s', + f'rekey_time = {esp_lifetime}s', + f'esp_proposals = aes128-sha1-modp1024,aes256-sha1-modp1024,3des-md5-modp1024', + f'local_ts = dynamic[gre]', + f'remote_ts = dynamic[gre]', + f'mode = transport', + f'secret = {nhrp_secret}' + ] + for line in swanctl_lines: + self.assertIn(line, swanctl_conf) + + # There is only one NHRP test so no need to delete this globally in tearDown() + self.cli_delete(nhrp_path) + + def test_site_to_site_x509(self): + # Enable PKI + self.setupPKI() + + vti = 'vti20' + self.cli_set(vti_path + [vti, 'address', '192.168.0.1/31']) + + peer_ip = '172.18.254.202' + connection_name = 'office' + local_address = '172.18.254.201' + peer_base_path = base_path + ['site-to-site', 'peer', connection_name] + + self.cli_set(peer_base_path + ['authentication', 'local-id', peer_name]) + self.cli_set(peer_base_path + ['authentication', 'mode', 'x509']) + self.cli_set(peer_base_path + ['authentication', 'remote-id', 'peer2']) + self.cli_set(peer_base_path + ['authentication', 'x509', 'ca-certificate', ca_name]) + self.cli_set(peer_base_path + ['authentication', 'x509', 'ca-certificate', int_ca_name]) + self.cli_set(peer_base_path + ['authentication', 'x509', 'certificate', peer_name]) + self.cli_set(peer_base_path + ['connection-type', 'initiate']) + self.cli_set(peer_base_path + ['ike-group', ike_group]) + self.cli_set(peer_base_path + ['ikev2-reauth', 'inherit']) + self.cli_set(peer_base_path + ['local-address', local_address]) + self.cli_set(peer_base_path + ['remote-address', peer_ip]) + self.cli_set(peer_base_path + ['vti', 'bind', vti]) + self.cli_set(peer_base_path + ['vti', 'esp-group', esp_group]) + + self.cli_commit() + + swanctl_conf = read_file(swanctl_file) + tmp = peer_ip.replace('.', '-') + if_id = vti.lstrip('vti') + # The key defaults to 0 and will match any policies which similarly do + # not have a lookup key configuration - thus we shift the key by one + # to also support a vti0 interface + if_id = str(int(if_id) +1) + swanctl_lines = [ + f'{connection_name}', + f'version = 0', # key-exchange not set - defaulting to 0 for ikev1 and ikev2 + f'send_cert = always', + f'mobike = yes', + f'keyingtries = 0', + f'id = "{peer_name}"', + f'auth = pubkey', + f'certs = {peer_name}.pem', + f'proposals = aes128-sha1-modp1024', + f'esp_proposals = aes128-sha1-modp1024', + f'local_addrs = {local_address} # dhcp:no', + f'remote_addrs = {peer_ip}', + f'local_ts = 0.0.0.0/0,::/0', + f'remote_ts = 0.0.0.0/0,::/0', + f'updown = "/etc/ipsec.d/vti-up-down {vti}"', + f'if_id_in = {if_id}', # will be 11 for vti10 + f'if_id_out = {if_id}', + f'ipcomp = no', + f'mode = tunnel', + f'start_action = start', + ] + for line in swanctl_lines: + self.assertIn(line, swanctl_conf) + + swanctl_secrets_lines = [ + f'{connection_name}', + f'file = {peer_name}.pem', + ] + for line in swanctl_secrets_lines: + self.assertIn(line, swanctl_conf) + + # Check Root CA, Intermediate CA and Peer cert/key pair is present + self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{ca_name}.pem'))) + self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{int_ca_name}.pem'))) + self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem'))) + + # Disable PKI + self.tearDownPKI() + + + def test_flex_vpn_vips(self): + local_address = '192.0.2.5' + local_id = 'vyos-r1' + remote_id = 'vyos-r2' + peer_base_path = base_path + ['site-to-site', 'peer', connection_name] + + self.cli_set(tunnel_path + ['tun1', 'encapsulation', 'gre']) + self.cli_set(tunnel_path + ['tun1', 'source-address', local_address]) + + self.cli_set(base_path + ['interface', interface]) + self.cli_set(base_path + ['options', 'flexvpn']) + self.cli_set(base_path + ['options', 'interface', 'tun1']) + self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) + + # vpn ipsec auth psk <tag> id <x.x.x.x> + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_address]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret]) + + self.cli_set(peer_base_path + ['authentication', 'local-id', local_id]) + self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret']) + self.cli_set(peer_base_path + ['authentication', 'remote-id', remote_id]) + self.cli_set(peer_base_path + ['connection-type', 'initiate']) + self.cli_set(peer_base_path + ['ike-group', ike_group]) + self.cli_set(peer_base_path + ['default-esp-group', esp_group]) + self.cli_set(peer_base_path + ['local-address', local_address]) + self.cli_set(peer_base_path + ['remote-address', peer_ip]) + self.cli_set(peer_base_path + ['tunnel', '1', 'protocol', 'gre']) + + self.cli_set(peer_base_path + ['virtual-address', '203.0.113.55']) + self.cli_set(peer_base_path + ['virtual-address', '203.0.113.56']) + + self.cli_commit() + + # Verify strongSwan configuration + swanctl_conf = read_file(swanctl_file) + swanctl_conf_lines = [ + f'version = 2', + f'vips = 203.0.113.55, 203.0.113.56', + f'life_time = 3600s', # default value + f'local_addrs = {local_address} # dhcp:no', + f'remote_addrs = {peer_ip}', + f'{connection_name}-tunnel-1', + f'mode = tunnel', + ] + + for line in swanctl_conf_lines: + self.assertIn(line, swanctl_conf) + + swanctl_secrets_lines = [ + f'id-{regex_uuid4} = "{local_id}"', + f'id-{regex_uuid4} = "{remote_id}"', + f'id-{regex_uuid4} = "{peer_ip}"', + f'id-{regex_uuid4} = "{local_address}"', + f'secret = "{secret}"', + ] + + for line in swanctl_secrets_lines: + self.assertRegex(swanctl_conf, fr'{line}') + + # Verify charon configuration + charon_conf = read_file(charon_file) + charon_conf_lines = [ + f'# Cisco FlexVPN', + f'cisco_flexvpn = yes', + f'install_virtual_ip = yes', + f'install_virtual_ip_on = tun1', + ] + + for line in charon_conf_lines: + self.assertIn(line, charon_conf) + + + def test_remote_access(self): + # This is a known to be good configuration for Microsoft Windows 10 and Apple iOS 17 + self.setupPKI() + + ike_group = 'IKE-RW' + esp_group = 'ESP-RW' + + conn_name = 'vyos-rw' + local_address = '192.0.2.1' + ip_pool_name = 'ra-rw-ipv4' + username = 'vyos' + password = 'secret' + ike_lifetime = '7200' + eap_lifetime = '3600' + local_id = 'ipsec.vyos.net' + + name_servers = ['172.16.254.100', '172.16.254.101'] + prefix = '172.16.250.0/28' + + # IKE + self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) + self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime]) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'dh-group', '2']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'hash', 'sha256']) + + # ESP + self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime]) + self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'disable']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'hash', 'sha384']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'hash', 'sha1']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'hash', 'sha256']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-users', 'username', username, 'password', password]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name]) + # verify() - CA cert required for x509 auth + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name]) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'local-address', local_address]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name]) + + for ns in name_servers: + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', ns]) + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'prefix', prefix]) + + self.cli_commit() + + # verify applied configuration + swanctl_conf = read_file(swanctl_file) + swanctl_lines = [ + f'{conn_name}', + f'remote_addrs = %any', + f'local_addrs = {local_address}', + f'proposals = aes256-sha512-modp2048,aes256-sha256-modp2048,aes256-sha256-modp1024,aes128gcm128-sha256-modp2048', + f'version = 2', + f'send_certreq = no', + f'rekey_time = {ike_lifetime}s', + f'keyingtries = 0', + f'pools = {ip_pool_name}', + f'id = "{local_id}"', + f'auth = pubkey', + f'certs = peer1.pem', + f'auth = eap-mschapv2', + f'eap_id = %any', + f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256', + f'life_time = {eap_lifetime}s', + f'dpd_action = clear', + f'replay_window = 32', + f'inactivity = 28800', + f'local_ts = 0.0.0.0/0,::/0', + ] + for line in swanctl_lines: + self.assertIn(line, swanctl_conf) + + swanctl_secrets_lines = [ + f'eap-{conn_name}-{username}', + f'secret = "{password}"', + f'id-{conn_name}-{username} = "{username}"', + ] + for line in swanctl_secrets_lines: + self.assertIn(line, swanctl_conf) + + swanctl_pool_lines = [ + f'{ip_pool_name}', + f'addrs = {prefix}', + f'dns = {",".join(name_servers)}', + ] + for line in swanctl_pool_lines: + self.assertIn(line, swanctl_conf) + + # Check Root CA, Intermediate CA and Peer cert/key pair is present + self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{ca_name}.pem'))) + self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem'))) + + self.tearDownPKI() + + def test_remote_access_eap_tls(self): + # This is a known to be good configuration for Microsoft Windows 10 and Apple iOS 17 + self.setupPKI() + + ike_group = 'IKE-RW' + esp_group = 'ESP-RW' + + conn_name = 'vyos-rw' + local_address = '192.0.2.1' + ip_pool_name = 'ra-rw-ipv4' + username = 'vyos' + password = 'secret' + ike_lifetime = '7200' + eap_lifetime = '3600' + local_id = 'ipsec.vyos.net' + + name_servers = ['172.16.254.100', '172.16.254.101'] + prefix = '172.16.250.0/28' + + # IKE + self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) + self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime]) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'dh-group', '2']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'hash', 'sha256']) + + # ESP + self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime]) + self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'disable']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'hash', 'sha384']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'hash', 'sha1']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'hash', 'sha256']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id]) + # Use EAP-TLS auth instead of default EAP-MSCHAPv2 + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'client-mode', 'eap-tls']) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name]) + # verify() - CA cert required for x509 auth + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name]) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'local-address', local_address]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name]) + + for ns in name_servers: + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', ns]) + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'prefix', prefix]) + + self.cli_commit() + + # verify applied configuration + swanctl_conf = read_file(swanctl_file) + swanctl_lines = [ + f'{conn_name}', + f'remote_addrs = %any', + f'local_addrs = {local_address}', + f'proposals = aes256-sha512-modp2048,aes256-sha256-modp2048,aes256-sha256-modp1024,aes128gcm128-sha256-modp2048', + f'version = 2', + f'send_certreq = no', + f'rekey_time = {ike_lifetime}s', + f'keyingtries = 0', + f'pools = {ip_pool_name}', + f'id = "{local_id}"', + f'auth = pubkey', + f'certs = peer1.pem', + f'cacerts = MyVyOS-CA.pem', + f'auth = eap-tls', + f'eap_id = %any', + f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256', + f'life_time = {eap_lifetime}s', + f'dpd_action = clear', + f'inactivity = 28800', + f'local_ts = 0.0.0.0/0,::/0', + ] + for line in swanctl_lines: + self.assertIn(line, swanctl_conf) + + swanctl_pool_lines = [ + f'{ip_pool_name}', + f'addrs = {prefix}', + f'dns = {",".join(name_servers)}', + ] + for line in swanctl_pool_lines: + self.assertIn(line, swanctl_conf) + + # Check Root CA, Intermediate CA and Peer cert/key pair is present + self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{ca_name}.pem'))) + self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem'))) + + # Test setting of custom EAP ID + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'eap-id', 'eap-user@vyos.net']) + self.cli_commit() + self.assertIn(r'eap_id = eap-user@vyos.net', read_file(swanctl_file)) + + self.tearDownPKI() + + def test_remote_access_x509(self): + # This is a known to be good configuration for Microsoft Windows 10 and Apple iOS 17 + self.setupPKI() + + ike_group = 'IKE-RW' + esp_group = 'ESP-RW' + + conn_name = 'vyos-rw' + local_address = '192.0.2.1' + ip_pool_name = 'ra-rw-ipv4' + ike_lifetime = '7200' + eap_lifetime = '3600' + local_id = 'ipsec.vyos.net' + + name_servers = ['172.16.254.100', '172.16.254.101'] + prefix = '172.16.250.0/28' + + # IKE + self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) + self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime]) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'dh-group', '2']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'hash', 'sha256']) + + # ESP + self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime]) + self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'disable']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'hash', 'sha384']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'hash', 'sha1']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'hash', 'sha256']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id]) + # Use client-mode x509 instead of default EAP-MSCHAPv2 + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'client-mode', 'x509']) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name]) + # verify() - CA cert required for x509 auth + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', int_ca_name]) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'local-address', local_address]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name]) + + for ns in name_servers: + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', ns]) + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'prefix', prefix]) + + self.cli_commit() + + # verify applied configuration + swanctl_conf = read_file(swanctl_file) + swanctl_lines = [ + f'{conn_name}', + f'remote_addrs = %any', + f'local_addrs = {local_address}', + f'proposals = aes256-sha512-modp2048,aes256-sha256-modp2048,aes256-sha256-modp1024,aes128gcm128-sha256-modp2048', + f'version = 2', + f'send_certreq = no', + f'rekey_time = {ike_lifetime}s', + f'keyingtries = 0', + f'pools = {ip_pool_name}', + f'id = "{local_id}"', + f'auth = pubkey', + f'certs = peer1.pem', + f'cacerts = MyVyOS-CA.pem,MyVyOS-IntCA.pem', + f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256', + f'life_time = {eap_lifetime}s', + f'dpd_action = clear', + f'inactivity = 28800', + f'local_ts = 0.0.0.0/0,::/0', + ] + for line in swanctl_lines: + self.assertIn(line, swanctl_conf) + + swanctl_unexpected_lines = [ + f'auth = eap-', + f'eap_id' + ] + for unexpected_line in swanctl_unexpected_lines: + self.assertNotIn(unexpected_line, swanctl_conf) + + swanctl_pool_lines = [ + f'{ip_pool_name}', + f'addrs = {prefix}', + f'dns = {",".join(name_servers)}', + ] + for line in swanctl_pool_lines: + self.assertIn(line, swanctl_conf) + + # Check Root CA, Intermediate CA and Peer cert/key pair is present + self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{ca_name}.pem'))) + self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{int_ca_name}.pem'))) + self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem'))) + + self.tearDownPKI() + + def test_remote_access_dhcp_fail_handling(self): + # Skip process check - connection is not created for this test + self.skip_process_check = True + + # Interface for dhcp-interface + self.cli_set(ethernet_path + [interface, 'vif', vif, 'address', 'dhcp']) # Use VLAN to avoid getting IP from qemu dhcp server + + # This is a known to be good configuration for Microsoft Windows 10 and Apple iOS 17 + self.setupPKI() + + ike_group = 'IKE-RW' + esp_group = 'ESP-RW' + + conn_name = 'vyos-rw' + ip_pool_name = 'ra-rw-ipv4' + username = 'vyos' + password = 'secret' + ike_lifetime = '7200' + eap_lifetime = '3600' + local_id = 'ipsec.vyos.net' + + name_server = '172.16.254.100' + prefix = '172.16.250.0/28' + + # IKE + self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) + self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime]) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'hash', 'sha512']) + + # ESP + self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime]) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha512']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-users', 'username', username, 'password', password]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name]) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'dhcp-interface', f'{interface}.{vif}']) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name]) + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', name_server]) + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'prefix', prefix]) + + self.cli_commit() + + self.assertTrue(os.path.exists(dhcp_interfaces_file)) + + dhcp_interfaces = read_file(dhcp_interfaces_file) + self.assertIn(f'{interface}.{vif}', dhcp_interfaces) # Ensure dhcp interface was added for dhclient hook + + self.cli_delete(ethernet_path + [interface, 'vif', vif, 'address']) + + self.tearDownPKI() + + def test_remote_access_no_rekey(self): + # In some RA secnarios, disabling server-initiated rekey of IKE and CHILD SA is desired + self.setupPKI() + + ike_group = 'IKE-RW' + esp_group = 'ESP-RW' + + conn_name = 'vyos-rw' + local_address = '192.0.2.1' + ip_pool_name = 'ra-rw-ipv4' + ike_lifetime = '7200' + eap_lifetime = '3600' + local_id = 'ipsec.vyos.net' + + name_servers = ['172.16.254.100', '172.16.254.101'] + prefix = '172.16.250.0/28' + + # IKE + self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) + self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', '0']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'dh-group', '2']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'hash', 'sha256']) + + # ESP + self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime]) + self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'disable']) + self.cli_set(base_path + ['esp-group', esp_group, 'disable-rekey']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'hash', 'sha384']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'hash', 'sha1']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'hash', 'sha256']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id]) + # Use client-mode x509 instead of default EAP-MSCHAPv2 + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'client-mode', 'x509']) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name]) + # verify() - CA cert required for x509 auth + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', int_ca_name]) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'local-address', local_address]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name]) + + for ns in name_servers: + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', ns]) + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'prefix', prefix]) + + self.cli_commit() + + # verify applied configuration + swanctl_conf = read_file(swanctl_file) + swanctl_lines = [ + f'{conn_name}', + f'remote_addrs = %any', + f'local_addrs = {local_address}', + f'proposals = aes256-sha512-modp2048,aes256-sha256-modp2048,aes256-sha256-modp1024,aes128gcm128-sha256-modp2048', + f'version = 2', + f'send_certreq = no', + f'rekey_time = 0s', + f'keyingtries = 0', + f'pools = {ip_pool_name}', + f'id = "{local_id}"', + f'auth = pubkey', + f'certs = peer1.pem', + f'cacerts = MyVyOS-CA.pem,MyVyOS-IntCA.pem', + f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256', + f'life_time = {eap_lifetime}s', + f'rekey_time = 0s', + f'dpd_action = clear', + f'inactivity = 28800', + f'local_ts = 0.0.0.0/0,::/0', + ] + for line in swanctl_lines: + self.assertIn(line, swanctl_conf) + + swanctl_pool_lines = [ + f'{ip_pool_name}', + f'addrs = {prefix}', + f'dns = {",".join(name_servers)}', + ] + for line in swanctl_pool_lines: + self.assertIn(line, swanctl_conf) + + # Check Root CA, Intermediate CA and Peer cert/key pair is present + self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{ca_name}.pem'))) + self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{int_ca_name}.pem'))) + self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem'))) + + self.tearDownPKI() + + def test_remote_access_pool_range(self): + # Same as test_remote_access but using an IP pool range instead of prefix + self.setupPKI() + + ike_group = 'IKE-RW' + esp_group = 'ESP-RW' + + conn_name = 'vyos-rw' + local_address = '192.0.2.1' + ip_pool_name = 'ra-rw-ipv4' + username = 'vyos' + password = 'secret' + ike_lifetime = '7200' + eap_lifetime = '3600' + local_id = 'ipsec.vyos.net' + + name_servers = ['172.16.254.100', '172.16.254.101'] + range_start = '172.16.250.2' + range_stop = '172.16.250.254' + + # IKE + self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) + self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime]) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'dh-group', '2']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'hash', 'sha256']) + + # ESP + self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime]) + self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'disable']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'hash', 'sha384']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'hash', 'sha1']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'hash', 'sha256']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-users', 'username', username, 'password', password]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name]) + # verify() - CA cert required for x509 auth + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name]) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'local-address', local_address]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name]) + + for ns in name_servers: + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', ns]) + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'range', 'start', range_start]) + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'range', 'stop', range_stop]) + + self.cli_commit() + + # verify applied configuration + swanctl_conf = read_file(swanctl_file) + swanctl_lines = [ + f'{conn_name}', + f'remote_addrs = %any', + f'local_addrs = {local_address}', + f'proposals = aes256-sha512-modp2048,aes256-sha256-modp2048,aes256-sha256-modp1024,aes128gcm128-sha256-modp2048', + f'version = 2', + f'send_certreq = no', + f'rekey_time = {ike_lifetime}s', + f'keyingtries = 0', + f'pools = {ip_pool_name}', + f'id = "{local_id}"', + f'auth = pubkey', + f'certs = peer1.pem', + f'auth = eap-mschapv2', + f'eap_id = %any', + f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256', + f'life_time = {eap_lifetime}s', + f'dpd_action = clear', + f'replay_window = 32', + f'inactivity = 28800', + f'local_ts = 0.0.0.0/0,::/0', + ] + for line in swanctl_lines: + self.assertIn(line, swanctl_conf) + + swanctl_secrets_lines = [ + f'eap-{conn_name}-{username}', + f'secret = "{password}"', + f'id-{conn_name}-{username} = "{username}"', + ] + for line in swanctl_secrets_lines: + self.assertIn(line, swanctl_conf) + + swanctl_pool_lines = [ + f'{ip_pool_name}', + f'addrs = {range_start}-{range_stop}', + f'dns = {",".join(name_servers)}', + ] + for line in swanctl_pool_lines: + self.assertIn(line, swanctl_conf) + + # Check Root CA, Intermediate CA and Peer cert/key pair is present + self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{ca_name}.pem'))) + self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem'))) + + self.tearDownPKI() + + def test_remote_access_vti(self): + # Set up and use a VTI interface for the remote access VPN + self.setupPKI() + + ike_group = 'IKE-RW' + esp_group = 'ESP-RW' + + conn_name = 'vyos-rw' + local_address = '192.0.2.1' + vti = 'vti10' + ip_pool_name = 'ra-rw-ipv4' + username = 'vyos' + password = 'secret' + ike_lifetime = '7200' + eap_lifetime = '3600' + local_id = 'ipsec.vyos.net' + + name_servers = ['10.1.1.1'] + range_start = '10.1.1.10' + range_stop = '10.1.1.254' + + # VTI interface + self.cli_set(vti_path + [vti, 'address', '10.1.1.1/24']) + + # IKE + self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) + self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime]) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'dh-group', '2']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'hash', 'sha256']) + + # ESP + self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime]) + self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'disable']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'hash', 'sha384']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'hash', 'sha1']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'hash', 'sha256']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-users', 'username', username, 'password', password]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name]) + # verify() - CA cert required for x509 auth + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name]) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'bind', vti]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'local-address', local_address]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name]) + + for ns in name_servers: + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', ns]) + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'range', 'start', range_start]) + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'range', 'stop', range_stop]) + + self.cli_commit() + + # verify applied configuration + swanctl_conf = read_file(swanctl_file) + + if_id = vti.lstrip('vti') + # The key defaults to 0 and will match any policies which similarly do + # not have a lookup key configuration - thus we shift the key by one + # to also support a vti0 interface + if_id = str(int(if_id) +1) + + swanctl_lines = [ + f'{conn_name}', + f'remote_addrs = %any', + f'local_addrs = {local_address}', + f'proposals = aes256-sha512-modp2048,aes256-sha256-modp2048,aes256-sha256-modp1024,aes128gcm128-sha256-modp2048', + f'version = 2', + f'send_certreq = no', + f'rekey_time = {ike_lifetime}s', + f'keyingtries = 0', + f'pools = {ip_pool_name}', + f'id = "{local_id}"', + f'auth = pubkey', + f'certs = peer1.pem', + f'auth = eap-mschapv2', + f'eap_id = %any', + f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256', + f'life_time = {eap_lifetime}s', + f'dpd_action = clear', + f'replay_window = 32', + f'if_id_in = {if_id}', # will be 11 for vti10 - shifted by one + f'if_id_out = {if_id}', + f'inactivity = 28800', + f'local_ts = 0.0.0.0/0,::/0', + ] + for line in swanctl_lines: + self.assertIn(line, swanctl_conf) + + swanctl_secrets_lines = [ + f'eap-{conn_name}-{username}', + f'secret = "{password}"', + f'id-{conn_name}-{username} = "{username}"', + ] + for line in swanctl_secrets_lines: + self.assertIn(line, swanctl_conf) + + swanctl_pool_lines = [ + f'{ip_pool_name}', + f'addrs = {range_start}-{range_stop}', + f'dns = {",".join(name_servers)}', + ] + for line in swanctl_pool_lines: + self.assertIn(line, swanctl_conf) + + # Check Root CA, Intermediate CA and Peer cert/key pair is present + self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{ca_name}.pem'))) + self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem'))) + + # Remote access interfaces should be set to 'up' during configure + self.assertEqual(Interface(vti).get_admin_state(), 'up') + + # Delete the connection to verify the VTI interfaces is taken down + self.cli_delete(base_path + ['remote-access', 'connection', conn_name]) + self.cli_commit() + self.assertEqual(Interface(vti).get_admin_state(), 'down') + + self.tearDownPKI() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_vpn_l2tp.py b/smoketest/scripts/cli/test_vpn_l2tp.py new file mode 100644 index 0000000..07a7e29 --- /dev/null +++ b/smoketest/scripts/cli/test_vpn_l2tp.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_accel_ppp_test import BasicAccelPPPTest +from configparser import ConfigParser +from vyos.utils.process import cmd + + +class TestVPNL2TPServer(BasicAccelPPPTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['vpn', 'l2tp', 'remote-access'] + cls._config_file = '/run/accel-pppd/l2tp.conf' + cls._chap_secrets = '/run/accel-pppd/l2tp.chap-secrets' + cls._protocol_section = 'l2tp' + # call base-classes classmethod + super(TestVPNL2TPServer, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestVPNL2TPServer, cls).tearDownClass() + + def basic_protocol_specific_config(self): + pass + + def test_l2tp_server_authentication_protocols(self): + # Test configuration of local authentication protocols + self.basic_config() + + # explicitly test mschap-v2 - no special reason + self.set( ['authentication', 'protocols', 'mschap-v2']) + + # commit changes + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True) + conf.read(self._config_file) + + self.assertEqual(conf['modules']['auth_mschap_v2'], None) + + def test_vpn_l2tp_dependence_ipsec_swanctl(self): + # Test config vpn for tasks T3843 and T5926 + + base_path = ['vpn', 'l2tp', 'remote-access'] + # make precondition + self.cli_set(['interfaces', 'dummy', 'dum0', 'address', '203.0.113.1/32']) + self.cli_set(['vpn', 'ipsec', 'interface', 'dum0']) + + self.cli_commit() + # check ipsec apply to swanctl + self.assertEqual('', cmd('echo vyos | sudo -S swanctl -L ')) + + self.cli_set(base_path + ['authentication', 'local-users', 'username', 'foo', 'password', 'bar']) + self.cli_set(base_path + ['authentication', 'mode', 'local']) + self.cli_set(base_path + ['authentication', 'protocols', 'chap']) + self.cli_set(base_path + ['client-ip-pool', 'first', 'range', '10.200.100.100-10.200.100.110']) + self.cli_set(base_path + ['description', 'VPN - REMOTE']) + self.cli_set(base_path + ['name-server', '1.1.1.1']) + self.cli_set(base_path + ['ipsec-settings', 'authentication', 'mode', 'pre-shared-secret']) + self.cli_set(base_path + ['ipsec-settings', 'authentication', 'pre-shared-secret', 'SeCret']) + self.cli_set(base_path + ['ipsec-settings', 'ike-lifetime', '8600']) + self.cli_set(base_path + ['ipsec-settings', 'lifetime', '3600']) + self.cli_set(base_path + ['outside-address', '203.0.113.1']) + self.cli_set(base_path + ['gateway-address', '203.0.113.1']) + + self.cli_commit() + + # check l2tp apply to swanctl + self.assertTrue('l2tp_remote_access:' in cmd('echo vyos | sudo -S swanctl -L ')) + + self.cli_delete(['vpn', 'l2tp']) + self.cli_commit() + + # check l2tp apply to swanctl after delete config + self.assertEqual('', cmd('echo vyos | sudo -S swanctl -L ')) + + # need to correct tearDown test + self.basic_config() + self.cli_set(base_path + ['authentication', 'protocols', 'chap']) + self.cli_commit() + + def test_l2tp_radius_server(self): + base_path = ['vpn', 'l2tp', 'remote-access'] + radius_server = "192.0.2.22" + radius_key = "secretVyOS" + + self.cli_set(base_path + ['authentication', 'mode', 'radius']) + self.cli_set(base_path + ['gateway-address', '192.0.2.1']) + self.cli_set(base_path + ['client-ip-pool', 'SIMPLE-POOL', 'range', '192.0.2.0/24']) + self.cli_set(base_path + ['default-pool', 'SIMPLE-POOL']) + self.cli_set(base_path + ['authentication', 'radius', 'server', radius_server, 'key', radius_key]) + self.cli_set(base_path + ['authentication', 'radius', 'server', radius_server, 'priority', '10']) + self.cli_set(base_path + ['authentication', 'radius', 'server', radius_server, 'backup']) + + # commit changes + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True) + conf.read(self._config_file) + server = conf["radius"]["server"].split(",") + self.assertIn('weight=10', server) + self.assertIn('backup', server) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_vpn_openconnect.py b/smoketest/scripts/cli/test_vpn_openconnect.py new file mode 100644 index 0000000..dcce229 --- /dev/null +++ b/smoketest/scripts/cli/test_vpn_openconnect.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.template import ip_from_cidr +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +OCSERV_CONF = '/run/ocserv/ocserv.conf' +base_path = ['vpn', 'openconnect'] + +pki_path = ['pki'] + +cert_name = 'OCServ' +cert_data = """ +MIIDsTCCApmgAwIBAgIURNQMaYmRIP/d+/OPWPWmuwkYHbswDQYJKoZIhvcNAQEL +BQAwVzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcM +CVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzEQMA4GA1UEAwwHdnlvcy5pbzAeFw0y +NDA0MDIxNjQxMTRaFw0yNTA0MDIxNjQxMTRaMFcxCzAJBgNVBAYTAkdCMRMwEQYD +VQQIDApTb21lLVN0YXRlMRIwEAYDVQQHDAlTb21lLUNpdHkxDTALBgNVBAoMBFZ5 +T1MxEDAOBgNVBAMMB3Z5b3MuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDFeexWVV70fBLOxGofWYlcNxJ9JyLviAZZDXrBIYfQnSrYp51yMKRPTH1e +Sjr7gIxVArAqLoYFgo7frRDkCKg8/izTopxtBTV2XJkLqDGA7DOrtBhgj0zjmF0A +WWIWi83WHc+sTHSvIqNLCDAZgnnzf1ch3W/na10hBTnFX4Yv6CJ4I7doSIyWzaQr +RvUXfaNYnvege+RrG5LzkVGxD2EhHyBqfQ2mxvlgqICqKSZkL56a3c/MHAm+7MKl +2KbSGxwNDs+SpHrCgWVIsl9w0bN2NSAu6GzyfW7V+V1dkiCggLlxXGhGncPMiQ7T +M7GKQULnQl5o/15GkW72Tg6wUdDpAgMBAAGjdTBzMAwGA1UdEwEB/wQCMAAwDgYD +VR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB0GA1UdDgQWBBTtil1X +c6dXA6kxZtZCgjx9QPzeLDAfBgNVHSMEGDAWgBTKMZvYAW1thn/uxX1fpcbP5vKq +dzANBgkqhkiG9w0BAQsFAAOCAQEARjS+QYJDz+XTdwK/lMF1GhSdacGnOIWRsbRx +N7odsyBV7Ud5W+Py79n+/PRirw2+jAaGXFmmgdxrcjlM+dZnlO3X0QCIuNdODggD +0J/u1ICPdm9TcJ2lEdbIE2vm2Q9P5RdQ7En7zg8Wu+rcNPlIxd3pHFOMX79vOcgi +RkWWII6tyeeT9COYgXUbg37wf2LkVv4b5PcShrfkWZVFWKDKr1maJ+iMwcIlosOe +Gj3SKe7gKBuPbMRwtocqKAYbW1GH12tA49DNkvxVKxVqnP4nHkwgfOJdpcZAjlyb +gLkzVKInZwg5EvJ7qtSJirDap9jyuLTfr5TmxbcdEhmAqeS41A== +""" + +cert_key_data = """ +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDFeexWVV70fBLO +xGofWYlcNxJ9JyLviAZZDXrBIYfQnSrYp51yMKRPTH1eSjr7gIxVArAqLoYFgo7f +rRDkCKg8/izTopxtBTV2XJkLqDGA7DOrtBhgj0zjmF0AWWIWi83WHc+sTHSvIqNL +CDAZgnnzf1ch3W/na10hBTnFX4Yv6CJ4I7doSIyWzaQrRvUXfaNYnvege+RrG5Lz +kVGxD2EhHyBqfQ2mxvlgqICqKSZkL56a3c/MHAm+7MKl2KbSGxwNDs+SpHrCgWVI +sl9w0bN2NSAu6GzyfW7V+V1dkiCggLlxXGhGncPMiQ7TM7GKQULnQl5o/15GkW72 +Tg6wUdDpAgMBAAECggEACbR8bHZv9GT/9EshNLQ3n3a8wQuCLd0fWWi5A90sKbun +pj5/6uOVbP5DL7Xx4HgIrYmJyIZBI5aEg11Oi15vjOZ9o9MF4V0UVmJQ9TU0EEl2 +H/X5uA54MWaaCiaFFGWU3UqEG8wldJFSZCFyt7Y6scBW3b0JFF7+6dyyDPoCWWqh +cNR41Hv0T0eqfXGOXX1JcBlLbqy0QXXeFoLlxV3ouIgWgkKJk7u3vDWCVM/ofP0m +/GyZYWCEA2JljEQZaVgtk1afFoamrjM4doMiirk+Tix4yGno94HLJdDUynqdLNAd +ZdKunFVAJau17b1VVPyfgIvIaPRvSGQVQoXH6TuB2QKBgQD5LRYTxsd8WsOwlB2R +SBYdzDff7c3VuNSAYTp7O2MqWrsoXm2MxLzEJLJUen+jQphL6ti/ObdrSOnKF2So +SizYeJ1Irx4M4BPSdy/Yt3T/+e+Y4K7iQ7Pdvdc/dlZ5XuNHYzuA/F7Ft/9rhUy9 +jSdQYANX+7h8vL7YrEjvhMMMZQKBgQDK4mG4D7XowLlBWv1fK4n/ErWvYSxH/X+A +VVnLv4z4aZHyRS2nTfQnb8PKbHJ/65x9yZs8a+6HqE4CAH+0LfZuOI8qn9OksxPZ +7GuQk/FiVyGXtu18hzlfhzmb0ZTjAalZ5b68DOIhyZIHVketebhljXaB5bfwdIgt +7vTOfotANQKBgQCWiA5WVDgfgBXIjzJtmkcCKWV3+onnG4oFJLfXysDVzYpTkPhN +mm0PcbvqHTcOwiSPeIkIvS15usrCM++zW1xMSlF6n5Bf5t8Svr5BBlPAcJW2ncYJ +Gy2GQDHRPQRwvko/zkscWVpHyCieJCGAQc4GWHqspH2Hnd8Ntsc5K9NJoQKBgFR1 +5/5rM+yghr7pdT9wbbNtg4tuZbPWmYTAg3Bp3vLvaB22pOnYbwMX6SdU/Fm6qVxI +WMLPn+6Dp2337TICTGvYSemRvdb74hC/9ouquzuYUFjLg5Rq6vyU2+u9VUEnyOuu +1DePGXi9ZHh/d7mFSbmlKaesDWYh7StKJknsrmXdAoGBAOm+FnzryKkhIq/ELyT9 +8v4wr0lxCcAP3nNb/P5ocv3m7hRLIkf4S9k/gAL+gE/OtdesomQKjOz7noLO+I2H +rj6ZfC/lhPIRJ4XK5BqgqqH53Zcl/HDoaUjbpmyMvZVoQfUHLut8Y912R6mfm65z +qXl1L7EdHTY+SdoThNJTpmWb +""" + +ca_name = 'VyOS-CA' +ca_data = """ +MIIDnTCCAoWgAwIBAgIUFVRURZXSbQ7F0DiSZYfqY0gQORMwDQYJKoZIhvcNAQEL +BQAwVzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcM +CVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzEQMA4GA1UEAwwHdnlvcy5pbzAeFw0y +NDA0MDIxNjQxMDFaFw0yOTA0MDExNjQxMDFaMFcxCzAJBgNVBAYTAkdCMRMwEQYD +VQQIDApTb21lLVN0YXRlMRIwEAYDVQQHDAlTb21lLUNpdHkxDTALBgNVBAoMBFZ5 +T1MxEDAOBgNVBAMMB3Z5b3MuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCg7Mjl6+rs8Bdkjqgl2QDuHfrH2mTDCeB7WuNTnIz0BPDtlmwIdqhU7LdC +B/zUSABAa6LBe/Z/bKWCRKyq8fU2/4uWECe975IMXOfFdYT6KA78DROvOi32JZml +n0LAXV+538eb+g19xNtoBhPO8igiNevfkV+nJehRK/41ATj+assTOv87vaSX7Wqy +aP/ZqkIdQD9Kc3cqB4JsYjkWcniHL9yk4oY3cjKK8PJ1pi4FqgFHt2hA+Ic+NvbA +hc47K9otP8FM4jkSii3MZfHA6Czb43BtbR+YEiWPzBhzE2bCuIgeRUumMF1Z+CAT +6U7Cpx3XPh+Ac2RnDa8wKeQ1eqE1AgMBAAGjYTBfMA8GA1UdEwEB/wQFMAMBAf8w +DgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAd +BgNVHQ4EFgQUyjGb2AFtbYZ/7sV9X6XGz+byqncwDQYJKoZIhvcNAQELBQADggEB +AArGXCq92vtaUZt528lC34ENPL9bQ7nRAS/ojplAzM9reW3o56sfYWf1M8iwRsJT +LbAwSnVB929RLlDolNpLwpzd1XaMt61Zcx4MFQmQCd+40dfuvMhluZaxt+F9bC1Z +cA7uwe/2HrAIULq3sga9LzSph6dNuyd1rGchr4xHCJ7u4WcF0kqi0Hjcn9S/ppEc +ba2L3rRqZmCbe6Yngx+MS06jonGw0z8F6e8LMkcvJUlNMEC76P+5Byjp4xZGP+y3 +DtIfsfijpb+t1OUe75YmWflTFnHR9GlybNYTxGAl49mFw6LlS1kefXyPtfuReLmv +n+vZdJAWTq76zAPT3n9FClo= +""" + +ca_key_data = """ +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCg7Mjl6+rs8Bdk +jqgl2QDuHfrH2mTDCeB7WuNTnIz0BPDtlmwIdqhU7LdCB/zUSABAa6LBe/Z/bKWC +RKyq8fU2/4uWECe975IMXOfFdYT6KA78DROvOi32JZmln0LAXV+538eb+g19xNto +BhPO8igiNevfkV+nJehRK/41ATj+assTOv87vaSX7WqyaP/ZqkIdQD9Kc3cqB4Js +YjkWcniHL9yk4oY3cjKK8PJ1pi4FqgFHt2hA+Ic+NvbAhc47K9otP8FM4jkSii3M +ZfHA6Czb43BtbR+YEiWPzBhzE2bCuIgeRUumMF1Z+CAT6U7Cpx3XPh+Ac2RnDa8w +KeQ1eqE1AgMBAAECggEAEDDaoqVqmMWsONoQiWRMr2h1RZvPxP7OpuKVWiF3XgrM +Ob9HZc+Ybpj1dC+NDMekvNaHhMuF2Lqz6UgjDjzzVMH/x4yfDwFWUqebSxbglvGm +Vk4zg48JNkmArLT6GJQccD1XXjZZmqSOhagM4KalCpIdxfvgoZbTCa2xMSCLHS+1 +HCDcmpCoeXM6ZBPTn0NbjRDAqIzCwcq2veG7RSz040obk8h7nrdv7jhxRGmtPmPF +zKgGLNn6GnL7AwYVMiidjj/ntvM4B1OMs9MwUYbtpg98TWcWyu+ZRakUrnVf9z2a +IHCKyuJvke/PNqMgw+L8KV4/478XxWhXfl7K1F3nMQKBgQDRBUDYNFH0wC4MMWsA ++RGwyz7RlzACChDJCMtA/agbW06gUoE9UYf8KtLQQQYljlLJHxHGD72QnuM+sowG +GXnbD4BabA9TQiQUG5c6boznTy1uU1gt8T0Zl0mmC7vIMoMBVd5bb0qrZvuR123k +DGYn6crug9uvMIYSSlhGmBGTJQKBgQDFGC3vfkCyXzLoYy+RIs/rXgyBF1PUYQty +DgL0N811L0H7a8JhFnt4FvodUbxv2ob+1kIc9e3yXT6FsGyO7IDOnqgeQKy74bYq +VPZZuf1FOFb9fuxf00pn1FmhAF4OuSWkhVhrKkyrZwdD8ArjLK253J94dogjdKAY +fN1csaOA0QKBgD0zUZI8d4a3QoRVb+RACTr/t6v8nZTrR5DlX0XvP2qLKJFutuKy +XaOrEkDh2R/j9T9oNncMos+WhikUdEVQ7koC1u0i2LXjFtdAYN4+Akmz+DRmeNoy +2VYF4w2YP+pVR+B7OPkCtBVNuPkx3743Fy42mTGPMCKyjX8Lf59j5Tl1AoGBAI3s +k2dZqozHMIlWovIH92CtIKP0gFD2cJ94p3fklvZDSWgaeKYg4lffc8uZB/AjlAH9 +ly3ziZx0uIjcOc/RTg96/+SI/dls9xgUhjCmVVJ692ki9GMsau/JYaEl+pTvjcOi +ocDJfNwQHJM3Tx+3FII59DtyXyXo3T/E6kHNSMeBAoGAR9M48DTspv9OH1S7X6yR +6MtMY5ltsBmB3gPhQFxiDKBvARkIkAPqObQ9TG/VuOz2Purq0Oz7SHsY2jiFDd2K +EGo6JfG61NDdIhiQC99ztSgt7NtvSCnX22SfVDWoFxSK+tek7tvDVXAXCNy4ZESM +EUGJ6NDHImb80aF+xZ3wYKw= +""" + +PROCESS_NAME = 'ocserv-main' +config_file = '/run/ocserv/ocserv.conf' +auth_file = '/run/ocserv/ocpasswd' +otp_file = '/run/ocserv/users.oath' + +listen_if = 'dum116' +listen_address = '100.64.0.1/32' + +class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestVPNOpenConnect, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + cls.cli_set(cls, ['interfaces', 'dummy', listen_if, 'address', listen_address]) + + cls.cli_set(cls, pki_path + ['ca', cert_name, 'certificate', ca_data.replace('\n','')]) + cls.cli_set(cls, pki_path + ['ca', cert_name, 'private', 'key', ca_key_data.replace('\n','')]) + cls.cli_set(cls, pki_path + ['certificate', cert_name, 'certificate', cert_data.replace('\n','')]) + cls.cli_set(cls, pki_path + ['certificate', cert_name, 'private', 'key', cert_key_data.replace('\n','')]) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, pki_path) + cls.cli_delete(cls, ['interfaces', 'dummy', listen_if]) + super(TestVPNOpenConnect, cls).tearDownClass() + + def tearDown(self): + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(base_path) + self.cli_commit() + + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_ocserv(self): + user = 'vyos_user' + password = 'vyos_pass' + otp = '37500000026900000000200000000000' + v4_subnet = '192.0.2.0/24' + v6_prefix = '2001:db8:1000::/64' + v6_len = '126' + name_server = ['1.2.3.4', '1.2.3.5', '2001:db8::1'] + split_dns = ['vyos.net', 'vyos.io'] + + self.cli_set(base_path + ['authentication', 'local-users', 'username', user, 'password', password]) + self.cli_set(base_path + ['authentication', 'local-users', 'username', user, 'otp', 'key', otp]) + self.cli_set(base_path + ['authentication', 'mode', 'local', 'password-otp']) + + self.cli_set(base_path + ['network-settings', 'client-ip-settings', 'subnet', v4_subnet]) + self.cli_set(base_path + ['network-settings', 'client-ipv6-pool', 'prefix', v6_prefix]) + self.cli_set(base_path + ['network-settings', 'client-ipv6-pool', 'mask', v6_len]) + + for ns in name_server: + self.cli_set(base_path + ['network-settings', 'name-server', ns]) + for domain in split_dns: + self.cli_set(base_path + ['network-settings', 'split-dns', domain]) + + # SSL certificates are mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['ssl', 'ca-certificate', cert_name]) + self.cli_set(base_path + ['ssl', 'certificate', cert_name]) + + listen_ip_no_cidr = ip_from_cidr(listen_address) + self.cli_set(base_path + ['listen-address', listen_ip_no_cidr]) + + self.cli_commit() + + # Verify configuration + daemon_config = read_file(config_file) + + # Verify TLS string (with default setting) + self.assertIn('tls-priorities = "NORMAL:%SERVER_PRECEDENCE:%COMPAT:-RSA:-VERS-SSL3.0:-ARCFOUR-128:-VERS-TLS1.0:-VERS-TLS1.1"', daemon_config) + + # authentication mode local password-otp + self.assertIn(f'auth = "plain[passwd=/run/ocserv/ocpasswd,otp=/run/ocserv/users.oath]"', daemon_config) + self.assertIn(f'listen-host = {listen_ip_no_cidr}', daemon_config) + self.assertIn(f'ipv4-network = {v4_subnet}', daemon_config) + self.assertIn(f'ipv6-network = {v6_prefix}', daemon_config) + self.assertIn(f'ipv6-subnet-prefix = {v6_len}', daemon_config) + + # defaults + self.assertIn(f'tcp-port = 443', daemon_config) + self.assertIn(f'udp-port = 443', daemon_config) + + for ns in name_server: + self.assertIn(f'dns = {ns}', daemon_config) + for domain in split_dns: + self.assertIn(f'split-dns = {domain}', daemon_config) + + auth_config = read_file(auth_file) + self.assertIn(f'{user}:*:$', auth_config) + + otp_config = read_file(otp_file) + self.assertIn(f'HOTP/T30/6 {user} - {otp}', otp_config) + + + # Verify HTTP security headers + self.cli_set(base_path + ['http-security-headers']) + self.cli_commit() + + daemon_config = read_file(config_file) + + self.assertIn('included-http-headers = Strict-Transport-Security: max-age=31536000 ; includeSubDomains', daemon_config) + self.assertIn('included-http-headers = X-Frame-Options: deny', daemon_config) + self.assertIn('included-http-headers = X-Content-Type-Options: nosniff', daemon_config) + self.assertIn('included-http-headers = Content-Security-Policy: default-src "none"', daemon_config) + self.assertIn('included-http-headers = X-Permitted-Cross-Domain-Policies: none', daemon_config) + self.assertIn('included-http-headers = Referrer-Policy: no-referrer', daemon_config) + self.assertIn('included-http-headers = Clear-Site-Data: "cache","cookies","storage"', daemon_config) + self.assertIn('included-http-headers = Cross-Origin-Embedder-Policy: require-corp', daemon_config) + self.assertIn('included-http-headers = Cross-Origin-Opener-Policy: same-origin', daemon_config) + self.assertIn('included-http-headers = Cross-Origin-Resource-Policy: same-origin', daemon_config) + self.assertIn('included-http-headers = X-XSS-Protection: 0', daemon_config) + self.assertIn('included-http-headers = Pragma: no-cache', daemon_config) + self.assertIn('included-http-headers = Cache-control: no-store, no-cache', daemon_config) + + # Set TLS version to the highest security (v1.3 min) + self.cli_set(base_path + ['tls-version-min', '1.3']) + self.cli_commit() + + # Verify TLS string + daemon_config = read_file(config_file) + self.assertIn('tls-priorities = "NORMAL:%SERVER_PRECEDENCE:%COMPAT:-RSA:-VERS-SSL3.0:-ARCFOUR-128:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.2"', daemon_config) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_vpn_pptp.py b/smoketest/scripts/cli/test_vpn_pptp.py new file mode 100644 index 0000000..25d9a47 --- /dev/null +++ b/smoketest/scripts/cli/test_vpn_pptp.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_accel_ppp_test import BasicAccelPPPTest + +class TestVPNPPTPServer(BasicAccelPPPTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['vpn', 'pptp', 'remote-access'] + cls._config_file = '/run/accel-pppd/pptp.conf' + cls._chap_secrets = '/run/accel-pppd/pptp.chap-secrets' + cls._protocol_section = 'pptp' + # call base-classes classmethod + super(TestVPNPPTPServer, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestVPNPPTPServer, cls).tearDownClass() + + def basic_protocol_specific_config(self): + pass + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_vpn_sstp.py b/smoketest/scripts/cli/test_vpn_sstp.py new file mode 100644 index 0000000..1a3e1df --- /dev/null +++ b/smoketest/scripts/cli/test_vpn_sstp.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_accel_ppp_test import BasicAccelPPPTest +from vyos.utils.file import read_file + +pki_path = ['pki'] + +cert_data = """ +MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIw +WTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv +bWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIx +MDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNV +BAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlP +UzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3 +QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu ++JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3Lftz +ngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93 ++dm/LDnp7C0=""" + +key_data = """ +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx +2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7 +u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww +""" + +class TestVPNSSTPServer(BasicAccelPPPTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['vpn', 'sstp'] + cls._config_file = '/run/accel-pppd/sstp.conf' + cls._chap_secrets = '/run/accel-pppd/sstp.chap-secrets' + cls._protocol_section = 'sstp' + # call base-classes classmethod + super(TestVPNSSTPServer, cls).setUpClass() + + cls.cli_set(cls, pki_path + ['ca', 'sstp', 'certificate', cert_data.replace('\n','')]) + cls.cli_set(cls, pki_path + ['certificate', 'sstp', 'certificate', cert_data.replace('\n','')]) + cls.cli_set(cls, pki_path + ['certificate', 'sstp', 'private', 'key', key_data.replace('\n','')]) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, pki_path) + super(TestVPNSSTPServer, cls).tearDownClass() + + def basic_protocol_specific_config(self): + self.set(['ssl', 'ca-certificate', 'sstp']) + self.set(['ssl', 'certificate', 'sstp']) + + def test_accel_local_authentication(self): + # Change default port + port = '8443' + self.set(['port', port]) + + self.basic_config() + super().test_accel_local_authentication() + + config = read_file(self._config_file) + self.assertIn(f'port={port}', config) + + def test_sstp_host_name(self): + host_name = 'test.vyos.io' + self.set(['host-name', host_name]) + + self.basic_config() + self.cli_commit() + + config = read_file(self._config_file) + self.assertIn(f'host-name={host_name}', config) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py new file mode 100644 index 0000000..2bb6c91 --- /dev/null +++ b/smoketest/scripts/cli/test_vrf.py @@ -0,0 +1,599 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import os +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from json import loads +from jmespath import search + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Interface +from vyos.ifconfig import Section +from vyos.utils.file import read_file +from vyos.utils.network import get_interface_config +from vyos.utils.network import get_vrf_tableid +from vyos.utils.network import is_intf_addr_assigned +from vyos.utils.network import interface_exists +from vyos.utils.process import cmd +from vyos.utils.system import sysctl_read + +base_path = ['vrf'] +vrfs = ['red', 'green', 'blue', 'foo-bar', 'baz_foo'] +v4_protocols = ['any', 'babel', 'bgp', 'connected', 'eigrp', 'isis', 'kernel', 'ospf', 'rip', 'static', 'table'] +v6_protocols = ['any', 'babel', 'bgp', 'connected', 'isis', 'kernel', 'ospfv3', 'ripng', 'static', 'table'] + +class VRFTest(VyOSUnitTestSHIM.TestCase): + _interfaces = [] + + @classmethod + def setUpClass(cls): + # we need to filter out VLAN interfaces identified by a dot (.) + # in their name - just in case! + if 'TEST_ETH' in os.environ: + tmp = os.environ['TEST_ETH'].split() + cls._interfaces = tmp + else: + for tmp in Section.interfaces('ethernet', vlan=False): + cls._interfaces.append(tmp) + # call base-classes classmethod + super(VRFTest, cls).setUpClass() + + def setUp(self): + # VRF strict_most ist always enabled + tmp = read_file('/proc/sys/net/vrf/strict_mode') + self.assertEqual(tmp, '1') + + def tearDown(self): + # delete all VRFs + self.cli_delete(base_path) + self.cli_commit() + for vrf in vrfs: + self.assertFalse(interface_exists(vrf)) + + def test_vrf_vni_and_table_id(self): + base_table = '1000' + table = base_table + for vrf in vrfs: + base = base_path + ['name', vrf] + description = f'VyOS-VRF-{vrf}' + self.cli_set(base + ['description', description]) + + # check validate() - a table ID is mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base + ['table', table]) + self.cli_set(base + ['vni', table]) + if vrf == 'green': + self.cli_set(base + ['disable']) + + table = str(int(table) + 1) + + # commit changes + self.cli_commit() + + # Verify VRF configuration + table = base_table + iproute2_config = read_file('/etc/iproute2/rt_tables.d/vyos-vrf.conf') + for vrf in vrfs: + description = f'VyOS-VRF-{vrf}' + self.assertTrue(interface_exists(vrf)) + vrf_if = Interface(vrf) + # validate proper interface description + self.assertEqual(vrf_if.get_alias(), description) + # validate admin up/down state of VRF + state = 'up' + if vrf == 'green': + state = 'down' + self.assertEqual(vrf_if.get_admin_state(), state) + + # Test the iproute2 lookup file, syntax is as follows: + # + # # id vrf name comment + # 1000 red # VyOS-VRF-red + # 1001 green # VyOS-VRF-green + # ... + regex = f'{table}\s+{vrf}\s+#\s+{description}' + self.assertTrue(re.findall(regex, iproute2_config)) + + frrconfig = self.getFRRconfig(f'vrf {vrf}') + self.assertIn(f' vni {table}', frrconfig) + + self.assertEqual(int(table), get_vrf_tableid(vrf)) + + # Increment table ID for the next run + table = str(int(table) + 1) + + def test_vrf_loopbacks_ips(self): + table = '2000' + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', str(table)]) + table = str(int(table) + 1) + + # commit changes + self.cli_commit() + + # Verify VRF configuration + loopbacks = ['127.0.0.1', '::1'] + for vrf in vrfs: + # Ensure VRF was created + self.assertTrue(interface_exists(vrf)) + # Verify IP forwarding is 1 (enabled) + self.assertEqual(sysctl_read(f'net.ipv4.conf.{vrf}.forwarding'), '1') + self.assertEqual(sysctl_read(f'net.ipv6.conf.{vrf}.forwarding'), '1') + + # Test for proper loopback IP assignment + for addr in loopbacks: + self.assertTrue(is_intf_addr_assigned(vrf, addr)) + + def test_vrf_bind_all(self): + table = '2000' + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', str(table)]) + table = str(int(table) + 1) + + self.cli_set(base_path + ['bind-to-all']) + + # commit changes + self.cli_commit() + + # Verify VRF configuration + self.assertEqual(sysctl_read('net.ipv4.tcp_l3mdev_accept'), '1') + self.assertEqual(sysctl_read('net.ipv4.udp_l3mdev_accept'), '1') + + # If there is any VRF defined, strict_mode should be on + self.assertEqual(sysctl_read('net.vrf.strict_mode'), '1') + + def test_vrf_table_id_is_unalterable(self): + # Linux Kernel prohibits the change of a VRF table on the fly. + # VRF must be deleted and recreated! + table = '1000' + vrf = vrfs[0] + base = base_path + ['name', vrf] + self.cli_set(base + ['table', table]) + + # commit changes + self.cli_commit() + + # Check if VRF has been created + self.assertTrue(interface_exists(vrf)) + + table = str(int(table) + 1) + self.cli_set(base + ['table', table]) + # check validate() - table ID can not be altered! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_vrf_assign_interface(self): + vrf = vrfs[0] + table = '5000' + self.cli_set(['vrf', 'name', vrf, 'table', table]) + + for interface in self._interfaces: + section = Section.section(interface) + self.cli_set(['interfaces', section, interface, 'vrf', vrf]) + + # commit changes + self.cli_commit() + + # Verify VRF assignmant + for interface in self._interfaces: + tmp = get_interface_config(interface) + self.assertEqual(vrf, tmp['master']) + + # cleanup + section = Section.section(interface) + self.cli_delete(['interfaces', section, interface, 'vrf']) + + def test_vrf_static_route(self): + base_table = '100' + table = base_table + for vrf in vrfs: + next_hop = f'192.0.{table}.1' + prefix = f'10.0.{table}.0/24' + base = base_path + ['name', vrf] + + self.cli_set(base + ['vni', table]) + + # check validate() - a table ID is mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base + ['table', table]) + self.cli_set(base + ['protocols', 'static', 'route', prefix, 'next-hop', next_hop]) + + table = str(int(table) + 1) + + # commit changes + self.cli_commit() + + # Verify VRF configuration + table = base_table + for vrf in vrfs: + next_hop = f'192.0.{table}.1' + prefix = f'10.0.{table}.0/24' + + self.assertTrue(interface_exists(vrf)) + + frrconfig = self.getFRRconfig(f'vrf {vrf}') + self.assertIn(f' vni {table}', frrconfig) + self.assertIn(f' ip route {prefix} {next_hop}', frrconfig) + + # Increment table ID for the next run + table = str(int(table) + 1) + + def test_vrf_link_local_ip_addresses(self): + # Testcase for issue T4331 + table = '100' + vrf = 'orange' + interface = 'dum9998' + addresses = ['192.0.2.1/26', '2001:db8:9998::1/64', 'fe80::1/64'] + + for address in addresses: + self.cli_set(['interfaces', 'dummy', interface, 'address', address]) + + # Create dummy interfaces + self.cli_commit() + + # ... and verify IP addresses got assigned + for address in addresses: + self.assertTrue(is_intf_addr_assigned(interface, address)) + + # Move interface to VRF + self.cli_set(base_path + ['name', vrf, 'table', table]) + self.cli_set(['interfaces', 'dummy', interface, 'vrf', vrf]) + + # Apply VRF config + self.cli_commit() + # Ensure VRF got created + self.assertTrue(interface_exists(vrf)) + # ... and IP addresses are still assigned + for address in addresses: + self.assertTrue(is_intf_addr_assigned(interface, address)) + # Verify VRF table ID + self.assertEqual(int(table), get_vrf_tableid(vrf)) + + # Verify interface is assigned to VRF + tmp = get_interface_config(interface) + self.assertEqual(vrf, tmp['master']) + + # Delete Interface + self.cli_delete(['interfaces', 'dummy', interface]) + self.cli_commit() + + def test_vrf_disable_forwarding(self): + table = '2000' + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', table]) + self.cli_set(base + ['ip', 'disable-forwarding']) + self.cli_set(base + ['ipv6', 'disable-forwarding']) + table = str(int(table) + 1) + + # commit changes + self.cli_commit() + + # Verify VRF configuration + loopbacks = ['127.0.0.1', '::1'] + for vrf in vrfs: + # Ensure VRF was created + self.assertTrue(interface_exists(vrf)) + # Verify IP forwarding is 0 (disabled) + self.assertEqual(sysctl_read(f'net.ipv4.conf.{vrf}.forwarding'), '0') + self.assertEqual(sysctl_read(f'net.ipv6.conf.{vrf}.forwarding'), '0') + + def test_vrf_ip_protocol_route_map(self): + table = '6000' + + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', table]) + + for protocol in v4_protocols: + self.cli_set(['policy', 'route-map', f'route-map-{vrf}-{protocol}', 'rule', '10', 'action', 'permit']) + self.cli_set(base + ['ip', 'protocol', protocol, 'route-map', f'route-map-{vrf}-{protocol}']) + + table = str(int(table) + 1) + + self.cli_commit() + + # Verify route-map properly applied to FRR + for vrf in vrfs: + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra') + self.assertIn(f'vrf {vrf}', frrconfig) + for protocol in v4_protocols: + self.assertIn(f' ip protocol {protocol} route-map route-map-{vrf}-{protocol}', frrconfig) + + # Delete route-maps + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_delete(['policy', 'route-map', f'route-map-{vrf}-{protocol}']) + self.cli_delete(base + ['ip', 'protocol']) + + self.cli_commit() + + # Verify route-map properly is removed from FRR + for vrf in vrfs: + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra') + self.assertNotIn(f'vrf {vrf}', frrconfig) + + def test_vrf_ip_ipv6_protocol_non_existing_route_map(self): + table = '6100' + non_existing = 'non-existing' + + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', table]) + for protocol in v4_protocols: + self.cli_set(base + ['ip', 'protocol', protocol, 'route-map', f'v4-{non_existing}']) + for protocol in v6_protocols: + self.cli_set(base + ['ipv6', 'protocol', protocol, 'route-map', f'v6-{non_existing}']) + + table = str(int(table) + 1) + + # Both v4 and v6 route-maps do not exist yet + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(['policy', 'route-map', f'v4-{non_existing}', 'rule', '10', 'action', 'deny']) + + # v6 route-map does not exist yet + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(['policy', 'route-map', f'v6-{non_existing}', 'rule', '10', 'action', 'deny']) + + # Commit again + self.cli_commit() + + def test_vrf_ipv6_protocol_route_map(self): + table = '6200' + + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', table]) + + for protocol in v6_protocols: + route_map = f'route-map-{vrf}-{protocol.replace("ospfv3", "ospf6")}' + self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) + self.cli_set(base + ['ipv6', 'protocol', protocol, 'route-map', route_map]) + + table = str(int(table) + 1) + + self.cli_commit() + + # Verify route-map properly applied to FRR + for vrf in vrfs: + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra') + self.assertIn(f'vrf {vrf}', frrconfig) + for protocol in v6_protocols: + # VyOS and FRR use a different name for OSPFv3 (IPv6) + if protocol == 'ospfv3': + protocol = 'ospf6' + route_map = f'route-map-{vrf}-{protocol}' + self.assertIn(f' ipv6 protocol {protocol} route-map {route_map}', frrconfig) + + # Delete route-maps + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_delete(['policy', 'route-map', f'route-map-{vrf}-{protocol}']) + self.cli_delete(base + ['ipv6', 'protocol']) + + self.cli_commit() + + # Verify route-map properly is removed from FRR + for vrf in vrfs: + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra') + self.assertNotIn(f'vrf {vrf}', frrconfig) + + def test_vrf_vni_duplicates(self): + base_table = '6300' + table = base_table + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', str(table)]) + self.cli_set(base + ['vni', '100']) + table = str(int(table) + 1) + + # L3VNIs can only be used once + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + table = base_table + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['vni', str(table)]) + table = str(int(table) + 1) + + # commit changes + self.cli_commit() + + # Verify VRF configuration + table = base_table + for vrf in vrfs: + self.assertTrue(interface_exists(vrf)) + + frrconfig = self.getFRRconfig(f'vrf {vrf}') + self.assertIn(f' vni {table}', frrconfig) + # Increment table ID for the next run + table = str(int(table) + 1) + + def test_vrf_vni_add_change_remove(self): + base_table = '6300' + table = base_table + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', str(table)]) + self.cli_set(base + ['vni', str(table)]) + table = str(int(table) + 1) + + # commit changes + self.cli_commit() + + # Verify VRF configuration + table = base_table + for vrf in vrfs: + self.assertTrue(interface_exists(vrf)) + + frrconfig = self.getFRRconfig(f'vrf {vrf}') + self.assertIn(f' vni {table}', frrconfig) + # Increment table ID for the next run + table = str(int(table) + 1) + + # Now change all L3VNIs (increment 2) + # We must also change the base_table number as we probably could get + # duplicate VNI's during the test as VNIs are applied 1:1 to FRR + base_table = '5000' + table = base_table + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['vni', str(table)]) + table = str(int(table) + 2) + + # commit changes + self.cli_commit() + + # Verify VRF configuration + table = base_table + for vrf in vrfs: + self.assertTrue(interface_exists(vrf)) + + frrconfig = self.getFRRconfig(f'vrf {vrf}') + self.assertIn(f' vni {table}', frrconfig) + # Increment table ID for the next run + table = str(int(table) + 2) + + + # add a new VRF with VNI - this must not delete any existing VRF/VNI + purple = 'purple' + table = str(int(table) + 10) + self.cli_set(base_path + ['name', purple, 'table', table]) + self.cli_set(base_path + ['name', purple, 'vni', table]) + + # commit changes + self.cli_commit() + + # Verify VRF configuration + table = base_table + for vrf in vrfs: + self.assertTrue(interface_exists(vrf)) + + frrconfig = self.getFRRconfig(f'vrf {vrf}') + self.assertIn(f' vni {table}', frrconfig) + # Increment table ID for the next run + table = str(int(table) + 2) + + # Verify purple VRF/VNI + self.assertTrue(interface_exists(purple)) + table = str(int(table) + 10) + frrconfig = self.getFRRconfig(f'vrf {purple}') + self.assertIn(f' vni {table}', frrconfig) + + # Now delete all the VNIs + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_delete(base + ['vni']) + + # commit changes + self.cli_commit() + + # Verify no VNI is defined + for vrf in vrfs: + self.assertTrue(interface_exists(vrf)) + + frrconfig = self.getFRRconfig(f'vrf {vrf}') + self.assertNotIn('vni', frrconfig) + + # Verify purple VNI remains + self.assertTrue(interface_exists(purple)) + frrconfig = self.getFRRconfig(f'vrf {purple}') + self.assertIn(f' vni {table}', frrconfig) + + def test_vrf_ip_ipv6_nht(self): + table = '6910' + + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', table]) + self.cli_set(base + ['ip', 'nht', 'no-resolve-via-default']) + self.cli_set(base + ['ipv6', 'nht', 'no-resolve-via-default']) + + table = str(int(table) + 1) + + self.cli_commit() + + # Verify route-map properly applied to FRR + for vrf in vrfs: + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra') + self.assertIn(f'vrf {vrf}', frrconfig) + self.assertIn(f' no ip nht resolve-via-default', frrconfig) + self.assertIn(f' no ipv6 nht resolve-via-default', frrconfig) + + # Delete route-maps + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_delete(base + ['ip']) + self.cli_delete(base + ['ipv6']) + + self.cli_commit() + + # Verify route-map properly is removed from FRR + for vrf in vrfs: + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra') + self.assertNotIn(f' no ip nht resolve-via-default', frrconfig) + self.assertNotIn(f' no ipv6 nht resolve-via-default', frrconfig) + + def test_vrf_conntrack(self): + table = '8710' + nftables_rules = { + 'vrf_zones_ct_in': ['ct original zone set iifname map @ct_iface_map'], + 'vrf_zones_ct_out': ['ct original zone set oifname map @ct_iface_map'] + } + + self.cli_set(base_path + ['name', 'randomVRF', 'table', '1000']) + self.cli_commit() + + # Conntrack rules should not be present + for chain, rule in nftables_rules.items(): + self.verify_nftables_chain(rule, 'inet vrf_zones', chain, inverse=True) + + # conntrack is only enabled once NAT, NAT66 or firewalling is enabled + self.cli_set(['nat']) + + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', table]) + table = str(int(table) + 1) + # We need the commit inside the loop to trigger the bug in T6603 + self.cli_commit() + + # Conntrack rules should now be present + for chain, rule in nftables_rules.items(): + self.verify_nftables_chain(rule, 'inet vrf_zones', chain, inverse=False) + + # T6603: there should be only ONE entry for the iifname/oifname in the chains + tmp = loads(cmd('sudo nft -j list table inet vrf_zones')) + num_rules = len(search("nftables[].rule[].chain", tmp)) + # ['vrf_zones_ct_in', 'vrf_zones_ct_out'] + self.assertEqual(num_rules, 2) + + self.cli_delete(['nat']) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/system/test_config_mount.py b/smoketest/scripts/system/test_config_mount.py new file mode 100644 index 0000000..657158c --- /dev/null +++ b/smoketest/scripts/system/test_config_mount.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +class TestConfigDir(unittest.TestCase): + def test_config_dir(self): + self.assertTrue(os.path.isdir('/config')) + +if __name__ == '__main__': + unittest.main(verbosity=2) + diff --git a/smoketest/scripts/system/test_iproute2.py b/smoketest/scripts/system/test_iproute2.py new file mode 100644 index 0000000..2d2fe19 --- /dev/null +++ b/smoketest/scripts/system/test_iproute2.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +class TestIproute2(unittest.TestCase): + def test_ip_is_symlink(self): + # For an unknown reason VyOS 1.3.0-rc2 did not have a symlink from + # /usr/sbin/ip -> /bin/ip - verify this now and forever + real_file = '/bin/ip' + symlink = '/usr/sbin/ip' + self.assertTrue(os.path.islink(symlink)) + self.assertFalse(os.path.islink(real_file)) + self.assertEqual(os.readlink(symlink), real_file) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/system/test_kernel_options.py b/smoketest/scripts/system/test_kernel_options.py new file mode 100644 index 0000000..700e4ce --- /dev/null +++ b/smoketest/scripts/system/test_kernel_options.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import os +import platform +import unittest + +from vyos.utils.kernel import check_kmod + +kernel = platform.release() +class TestKernelModules(unittest.TestCase): + """ VyOS makes use of a lot of Kernel drivers, modules and features. The + required modules which are essential for VyOS should be tested that they are + available in the Kernel that is run. """ + + _config_data = None + + @classmethod + def setUpClass(cls): + import gzip + from vyos.utils.process import call + + super(TestKernelModules, cls).setUpClass() + CONFIG = '/proc/config.gz' + if not os.path.isfile(CONFIG): + check_kmod('configs') + + with gzip.open(CONFIG, 'rt') as f: + cls._config_data = f.read() + + def test_bond_interface(self): + # The bond/lacp interface must be enabled in the OS Kernel + for option in ['CONFIG_BONDING']: + tmp = re.findall(f'{option}=(y|m)', self._config_data) + self.assertTrue(tmp) + + def test_bridge_interface(self): + # The bridge interface must be enabled in the OS Kernel + for option in ['CONFIG_BRIDGE', + 'CONFIG_BRIDGE_IGMP_SNOOPING', + 'CONFIG_BRIDGE_VLAN_FILTERING']: + tmp = re.findall(f'{option}=(y|m)', self._config_data) + self.assertTrue(tmp) + + def test_dropmon_enabled(self): + options_to_check = [ + 'CONFIG_NET_DROP_MONITOR=y', + 'CONFIG_UPROBE_EVENTS=y', + 'CONFIG_BPF_EVENTS=y', + 'CONFIG_TRACEPOINTS=y' + ] + + for option in options_to_check: + self.assertIn(option, self._config_data) + + def test_synproxy_enabled(self): + options_to_check = [ + 'CONFIG_NFT_SYNPROXY', + 'CONFIG_IP_NF_TARGET_SYNPROXY' + ] + for option in options_to_check: + tmp = re.findall(f'{option}=(y|m)', self._config_data) + self.assertTrue(tmp) + + def test_qemu_support(self): + options_to_check = [ + 'CONFIG_VIRTIO_BLK', 'CONFIG_SCSI_VIRTIO', + 'CONFIG_VIRTIO_NET', 'CONFIG_VIRTIO_CONSOLE', + 'CONFIG_VIRTIO', 'CONFIG_VIRTIO_PCI', + 'CONFIG_VIRTIO_BALLOON', 'CONFIG_CRYPTO_DEV_VIRTIO', + 'CONFIG_X86_PLATFORM_DEVICES' + ] + for option in options_to_check: + tmp = re.findall(f'{option}=(y|m)', self._config_data) + self.assertTrue(tmp) + + def test_vmware_support(self): + for option in ['CONFIG_VMXNET3']: + tmp = re.findall(f'{option}=(y|m)', self._config_data) + self.assertTrue(tmp) + + def test_container_cgroup_support(self): + options_to_check = [ + 'CONFIG_CGROUPS', 'CONFIG_MEMCG', + 'CONFIG_CGROUP_PIDS', 'CONFIG_CGROUP_BPF' + ] + for option in options_to_check: + tmp = re.findall(f'{option}=(y|m)', self._config_data) + self.assertTrue(tmp) + + def test_ip_routing_support(self): + options_to_check = [ + 'CONFIG_IP_ADVANCED_ROUTER', 'CONFIG_IP_MULTIPLE_TABLES', + 'CONFIG_IP_ROUTE_MULTIPATH' + ] + for option in options_to_check: + tmp = re.findall(f'{option}=(y|m)', self._config_data) + self.assertTrue(tmp) + + def test_vfio(self): + options_to_check = [ + 'CONFIG_VFIO', 'CONFIG_VFIO_GROUP', 'CONFIG_VFIO_CONTAINER', + 'CONFIG_VFIO_IOMMU_TYPE1', 'CONFIG_VFIO_NOIOMMU', 'CONFIG_VFIO_VIRQFD' + ] + for option in options_to_check: + tmp = re.findall(f'{option}=(y|m)', self._config_data) + self.assertTrue(tmp) + + def test_container_cpu(self): + options_to_check = [ + 'CONFIG_CGROUP_SCHED', 'CONFIG_CPUSETS', 'CONFIG_CGROUP_CPUACCT', 'CONFIG_CFS_BANDWIDTH' + ] + for option in options_to_check: + tmp = re.findall(f'{option}=(y|m)', self._config_data) + self.assertTrue(tmp) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/system/test_module_load.py b/smoketest/scripts/system/test_module_load.py new file mode 100644 index 0000000..fc60c72 --- /dev/null +++ b/smoketest/scripts/system/test_module_load.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest +from vyos.utils.process import cmd + +modules = { + "intel": ["e1000", "e1000e", "igb", "ixgbe", "ixgbevf", "i40e", + "i40evf", "iavf"], + "intel_qat": ["qat_200xx", "qat_200xxvf", "qat_c3xxx", "qat_c3xxxvf", + "qat_c62x", "qat_c62xvf", "qat_d15xx", "qat_d15xxvf", + "qat_dh895xcc", "qat_dh895xccvf"], + "accel_ppp": ["ipoe", "vlan_mon"], + "openvpn": ["ovpn-dco-v2"] +} + +class TestKernelModules(unittest.TestCase): + def test_load_modules(self): + success = True + not_found = [] + for msk in modules: + not_found = [] + ms = modules[msk] + for m in ms: + # We want to uncover all modules that fail, + # not fail at the first one + try: + cmd(f'modprobe {m}') + except: + success = False + not_found.append(m) + + self.assertTrue(success, 'One or more modules not found: ' + ', '.join(not_found)) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/src/activation-scripts/20-ethernet_offload.py b/src/activation-scripts/20-ethernet_offload.py new file mode 100644 index 0000000..ca72135 --- /dev/null +++ b/src/activation-scripts/20-ethernet_offload.py @@ -0,0 +1,106 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3619: mirror Linux Kernel defaults for ethernet offloading options into VyOS +# CLI. See https://vyos.dev/T3619#102254 for all the details. +# T3787: Remove deprecated UDP fragmentation offloading option +# T6006: add to activation-scripts: migration-scripts/interfaces/20-to-21 +# T6716: Honor the configured offload settings and don't automatically add +# them to the config if the kernel has them set (unless its a live boot) + +from vyos.ethtool import Ethtool +from vyos.configtree import ConfigTree +from vyos.system.image import is_live_boot + +def activate(config: ConfigTree): + base = ['interfaces', 'ethernet'] + + if not config.exists(base): + return + + for ifname in config.list_nodes(base): + eth = Ethtool(ifname) + + # If GRO is enabled by the Kernel - we reflect this on the CLI. If GRO is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'gro']) + enabled, fixed = eth.get_generic_receive_offload() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'gro']) + elif is_live_boot() and enabled and not fixed: + config.set(base + [ifname, 'offload', 'gro']) + + # If GSO is enabled by the Kernel - we reflect this on the CLI. If GSO is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'gso']) + enabled, fixed = eth.get_generic_segmentation_offload() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'gso']) + elif is_live_boot() and enabled and not fixed: + config.set(base + [ifname, 'offload', 'gso']) + + # If LRO is enabled by the Kernel - we reflect this on the CLI. If LRO is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'lro']) + enabled, fixed = eth.get_large_receive_offload() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'lro']) + elif is_live_boot() and enabled and not fixed: + config.set(base + [ifname, 'offload', 'lro']) + + # If SG is enabled by the Kernel - we reflect this on the CLI. If SG is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'sg']) + enabled, fixed = eth.get_scatter_gather() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'sg']) + elif is_live_boot() and enabled and not fixed: + config.set(base + [ifname, 'offload', 'sg']) + + # If TSO is enabled by the Kernel - we reflect this on the CLI. If TSO is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'tso']) + enabled, fixed = eth.get_tcp_segmentation_offload() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'tso']) + elif is_live_boot() and enabled and not fixed: + config.set(base + [ifname, 'offload', 'tso']) + + # Remove deprecated UDP fragmentation offloading option + if config.exists(base + [ifname, 'offload', 'ufo']): + config.delete(base + [ifname, 'offload', 'ufo']) + + # Also while processing the interface configuration, not all adapters support + # changing the speed and duplex settings. If the desired speed and duplex + # values do not work for the NIC driver, we change them back to the default + # value of "auto" - which will be applied if the CLI node is deleted. + speed_path = base + [ifname, 'speed'] + duplex_path = base + [ifname, 'duplex'] + # speed and duplex must always be set at the same time if not set to "auto" + if config.exists(speed_path) and config.exists(duplex_path): + speed = config.return_value(speed_path) + duplex = config.return_value(duplex_path) + if speed != 'auto' and duplex != 'auto': + if not eth.check_speed_duplex(speed, duplex): + config.delete(speed_path) + config.delete(duplex_path) + + # Also while processing the interface configuration, not all adapters support + # changing disabling flow-control - or change this setting. If disabling + # flow-control is not supported by the NIC, we remove the setting from CLI + flow_control_path = base + [ifname, 'disable-flow-control'] + if config.exists(flow_control_path): + if not eth.check_flow_control(): + config.delete(flow_control_path) diff --git a/src/completion/list_bgp_neighbors.sh b/src/completion/list_bgp_neighbors.sh new file mode 100644 index 0000000..869a7ab --- /dev/null +++ b/src/completion/list_bgp_neighbors.sh @@ -0,0 +1,67 @@ +#!/bin/sh +# Copyright (C) 2021-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Return BGP neighbor addresses from CLI, can either request IPv4 only, IPv6 +# only or both address-family neighbors + +ipv4=0 +ipv6=0 +vrf="" + +while [[ "$#" -gt 0 ]]; do + case $1 in + -4|--ipv4) ipv4=1 ;; + -6|--ipv6) ipv6=1 ;; + -b|--both) ipv4=1; ipv6=1 ;; + --vrf) vrf="vrf name $2"; shift ;; + *) echo "Unknown parameter passed: $1" ;; + esac + shift +done + +declare -a vals +eval "vals=($(cli-shell-api listActiveNodes $vrf protocols bgp neighbor))" + +if [ $ipv4 -eq 1 ] && [ $ipv6 -eq 1 ]; then + echo -n '<x.x.x.x>' '<h:h:h:h:h:h:h:h>' ${vals[@]} +elif [ $ipv4 -eq 1 ] ; then + echo -n '<x.x.x.x> ' + for peer in "${vals[@]}" + do + ipaddrcheck --is-ipv4-single $peer + if [ $? -eq "0" ]; then + echo -n "$peer " + fi + done +elif [ $ipv6 -eq 1 ] ; then + echo -n '<h:h:h:h:h:h:h:h> ' + for peer in "${vals[@]}" + do + ipaddrcheck --is-ipv6-single $peer + if [ $? -eq "0" ]; then + echo -n "$peer " + fi + done +else + echo "Usage:" + echo "-4|--ipv4 list only IPv4 peers" + echo "-6|--ipv6 list only IPv6 peers" + echo "--both list both IP4 and IPv6 peers" + echo "--vrf <name> apply command to given VRF (optional)" + echo "" + exit 1 +fi + +exit 0 diff --git a/src/completion/list_consoles.sh b/src/completion/list_consoles.sh new file mode 100644 index 0000000..52278c4 --- /dev/null +++ b/src/completion/list_consoles.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +# For lines like `aliases "foo";`, regex matches everything between the quotes +grep -oP '(?<=aliases ").+(?=";)' /run/conserver/conserver.cf
\ No newline at end of file diff --git a/src/completion/list_container_sysctl_parameters.sh b/src/completion/list_container_sysctl_parameters.sh new file mode 100644 index 0000000..cf8d006 --- /dev/null +++ b/src/completion/list_container_sysctl_parameters.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +declare -a vals +eval "vals=($(/sbin/sysctl -N -a|grep -E '^(fs.mqueue|net)\.|^(kernel.msgmax|kernel.msgmnb|kernel.msgmni|kernel.sem|kernel.shmall|kernel.shmmax|kernel.shmmni|kernel.shm_rmid_forced)$'))" +echo ${vals[@]} +exit 0 diff --git a/src/completion/list_ddclient_protocols.sh b/src/completion/list_ddclient_protocols.sh new file mode 100644 index 0000000..6349816 --- /dev/null +++ b/src/completion/list_ddclient_protocols.sh @@ -0,0 +1,17 @@ +#!/bin/sh +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +echo -n $(ddclient -list-protocols | grep -vE 'cloudns|porkbun') diff --git a/src/completion/list_disks.py b/src/completion/list_disks.py new file mode 100644 index 0000000..0aa872a --- /dev/null +++ b/src/completion/list_disks.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Completion script used by show disks to collect physical disk + +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument("-e", "--exclude", type=str, help="Exclude specified device from the result list") +args = parser.parse_args() + +disks = set() +with open('/proc/partitions') as f: + table = f.read() + +for line in table.splitlines()[1:]: + fields = line.strip().split() + # probably an empty line at the top + if len(fields) == 0: + continue + disks.add(fields[3]) + +if 'loop0' in disks: + disks.remove('loop0') +if 'sr0' in disks: + disks.remove('sr0') + +if args.exclude: + disks.remove(args.exclude) + +for disk in disks: + print(disk) diff --git a/src/completion/list_dumpable_interfaces.py b/src/completion/list_dumpable_interfaces.py new file mode 100644 index 0000000..f974835 --- /dev/null +++ b/src/completion/list_dumpable_interfaces.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 + +# Extract the list of interfaces available for traffic dumps from tcpdump -D + +import re + +from vyos.utils.process import cmd + +if __name__ == '__main__': + out = cmd('tcpdump -D').split('\n') + intfs = " ".join(map(lambda s: re.search(r'\d+\.(\S+)\s', s).group(1), out)) + print(intfs) diff --git a/src/completion/list_esi.sh b/src/completion/list_esi.sh new file mode 100644 index 0000000..b8373fa --- /dev/null +++ b/src/completion/list_esi.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# This script is completion helper to list all valid ESEs that are visible to FRR + +esiJson=$(vtysh -c 'show evpn es json') +echo "$(echo "$esiJson" | jq -r '.[] | .esi')" diff --git a/src/completion/list_images.py b/src/completion/list_images.py new file mode 100644 index 0000000..eae29c0 --- /dev/null +++ b/src/completion/list_images.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import os +import sys + +from vyos.system.image import is_live_boot +from vyos.system.image import get_running_image + + +parser = argparse.ArgumentParser(description='list available system images') +parser.add_argument('--no-running', action='store_true', + help='do not display the currently running image') + +def get_images(omit_running: bool = False) -> list[str]: + if is_live_boot(): + return [] + images = os.listdir("/lib/live/mount/persistence/boot") + if omit_running: + images.remove(get_running_image()) + if 'grub' in images: + images.remove('grub') + if 'efi' in images: + images.remove('efi') + return sorted(images) + +if __name__ == '__main__': + args = parser.parse_args() + print("\n".join(get_images(omit_running=args.no_running))) + sys.exit(0) diff --git a/src/completion/list_ipoe.py b/src/completion/list_ipoe.py new file mode 100644 index 0000000..5a8f4b0 --- /dev/null +++ b/src/completion/list_ipoe.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# Copyright 2020-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import argparse +from vyos.utils.process import popen + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--selector', help='Selector: username|ifname|sid', required=True) + args = parser.parse_args() + + output, err = popen("accel-cmd -p 2002 show sessions {0}".format(args.selector)) + if not err: + res = output.split("\r\n") + # Delete header from list + del res[:2] + print(' '.join(res)) diff --git a/src/completion/list_ipsec_profile_tunnels.py b/src/completion/list_ipsec_profile_tunnels.py new file mode 100644 index 0000000..95a4ca3 --- /dev/null +++ b/src/completion/list_ipsec_profile_tunnels.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse + +from vyos.config import Config +from vyos.utils.dict import dict_search + +def get_tunnels_from_ipsecprofile(profile): + config = Config() + base = ['vpn', 'ipsec', 'profile', profile, 'bind'] + profile_conf = config.get_config_dict(base, effective=True, key_mangling=('-', '_')) + tunnels = [] + + try: + for tunnel in (dict_search('bind.tunnel', profile_conf) or []): + tunnels.append(tunnel) + except: + pass + + return tunnels + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("-p", "--profile", type=str, help="List tunnels per profile") + args = parser.parse_args() + + tunnels = [] + + tunnels = get_tunnels_from_ipsecprofile(args.profile) + + print(" ".join(tunnels)) diff --git a/src/completion/list_local_ips.sh b/src/completion/list_local_ips.sh new file mode 100644 index 0000000..32df8a8 --- /dev/null +++ b/src/completion/list_local_ips.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +ipv4=0 +ipv6=0 + +while [[ "$#" -gt 0 ]]; do + case $1 in + -4|--ipv4) ipv4=1 ;; + -6|--ipv6) ipv6=1 ;; + -b|--both) ipv4=1; ipv6=1 ;; + *) echo "Unknown parameter passed: $1" ;; + esac + shift +done + +if [ $ipv4 -eq 1 ] && [ $ipv6 -eq 1 ]; then + ip a | grep inet | awk '{print $2}' | sed -e /^fe80::/d | awk -F/ '{print $1}' | sort -u +elif [ $ipv4 -eq 1 ] ; then + ip a | grep 'inet ' | awk '{print $2}' | awk -F/ '{print $1}' | sort -u +elif [ $ipv6 -eq 1 ] ; then + ip a | grep 'inet6 ' | awk '{print $2}' | sed -e /^fe80::/d | awk -F/ '{print $1}' | sort -u +else + echo "Usage:" + echo "-4|--ipv4 list only IPv4 addresses" + echo "-6|--ipv6 list only IPv6 addresses" + echo "--both list both IP4 and IPv6 addresses" + echo "" + exit 1 +fi diff --git a/src/completion/list_login_ttys.py b/src/completion/list_login_ttys.py new file mode 100644 index 0000000..4d77a1b --- /dev/null +++ b/src/completion/list_login_ttys.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from vyos.utils.serial import get_serial_units + +if __name__ == '__main__': + # Autocomplete uses runtime state rather than the config tree, as a manual + # restart/cleanup may be needed for deleted devices. + tty_completions = [ '<text>' ] + [ x['device'] for x in get_serial_units() if 'device' in x ] + print(' '.join(tty_completions)) + + diff --git a/src/completion/list_openconnect_users.py b/src/completion/list_openconnect_users.py new file mode 100644 index 0000000..db2f4b4 --- /dev/null +++ b/src/completion/list_openconnect_users.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from vyos.config import Config +from vyos.utils.dict import dict_search + +def get_user_from_ocserv(): + config = Config() + base = ['vpn', 'openconnect', 'authentication', 'local-users', 'username'] + openconnect = config.get_config_dict(base, effective=True, key_mangling=('-', '_')) + users = [] + try: + for user in (dict_search('username', openconnect) or []): + users.append(user) + except: + pass + return users + +if __name__ == "__main__": + users = [] + users = get_user_from_ocserv() + print(" ".join(users)) + diff --git a/src/completion/list_openvpn_clients.py b/src/completion/list_openvpn_clients.py new file mode 100644 index 0000000..c1d8eae --- /dev/null +++ b/src/completion/list_openvpn_clients.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse + +from vyos.ifconfig import Section + +def get_client_from_interface(interface): + clients = [] + try: + with open('/run/openvpn/' + interface + '.status', 'r') as f: + dump = False + for line in f: + if line.startswith("Common Name,"): + dump = True + continue + if line.startswith("ROUTING TABLE"): + dump = False + continue + if dump: + # client entry in this file looks like + # client1,172.18.202.10:47495,2957,2851,Sat Aug 17 00:07:11 2019 + # we are only interested in the client name 'client1' + clients.append(line.split(',')[0]) + except: + pass + + return clients + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("-i", "--interface", type=str, help="List connected clients per interface") + parser.add_argument("-a", "--all", action='store_true', help="List all connected OpenVPN clients") + args = parser.parse_args() + + clients = [] + + if args.interface: + clients = get_client_from_interface(args.interface) + elif args.all: + for interface in Section.interfaces("openvpn"): + clients += get_client_from_interface(interface) + + print(" ".join(clients)) diff --git a/src/completion/list_openvpn_users.py b/src/completion/list_openvpn_users.py new file mode 100644 index 0000000..f2c6484 --- /dev/null +++ b/src/completion/list_openvpn_users.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse + +from vyos.config import Config +from vyos.utils.dict import dict_search + +def get_user_from_interface(interface): + config = Config() + base = ['interfaces', 'openvpn', interface] + openvpn = config.get_config_dict(base, effective=True, key_mangling=('-', '_')) + users = [] + + try: + for user in (dict_search('server.client', openvpn[interface]) or []): + users.append(user.split(',')[0]) + except: + pass + + return users + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("-i", "--interface", type=str, help="List users per interface") + args = parser.parse_args() + + users = [] + + users = get_user_from_interface(args.interface) + + print(" ".join(users)) diff --git a/src/completion/list_protocols.sh b/src/completion/list_protocols.sh new file mode 100644 index 0000000..e9d50a7 --- /dev/null +++ b/src/completion/list_protocols.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +grep -v '^#' /etc/protocols | awk 'BEGIN {ORS=""} {if ($3) {print TRS $1; TRS=" "}}' diff --git a/src/completion/list_raidset.sh b/src/completion/list_raidset.sh new file mode 100644 index 0000000..9ff3523 --- /dev/null +++ b/src/completion/list_raidset.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo -n `cat /proc/partitions | grep md | awk '{ print $4 }'` diff --git a/src/completion/list_sysctl_parameters.sh b/src/completion/list_sysctl_parameters.sh new file mode 100644 index 0000000..c111716 --- /dev/null +++ b/src/completion/list_sysctl_parameters.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +declare -a vals +eval "vals=($(/sbin/sysctl -N -a))" +echo ${vals[@]} +exit 0 diff --git a/src/completion/list_vni.sh b/src/completion/list_vni.sh new file mode 100644 index 0000000..f8bd4a9 --- /dev/null +++ b/src/completion/list_vni.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# This script is completion helper to list all configured VNIs that are visible to FRR + +vniJson=$(vtysh -c 'show evpn vni json') +echo "$(echo "$vniJson" | jq -r 'keys | .[]')" diff --git a/src/completion/list_webproxy_category.sh b/src/completion/list_webproxy_category.sh new file mode 100644 index 0000000..a5ad239 --- /dev/null +++ b/src/completion/list_webproxy_category.sh @@ -0,0 +1,5 @@ +#!/bin/sh +DB_DIR="/opt/vyatta/etc/config/url-filtering/squidguard/db/" +if [ -d ${DB_DIR} ]; then + ls -ald ${DB_DIR}/* | grep -E '^(d|l)' | awk '{print $9}' | sed s#${DB_DIR}/## +fi diff --git a/src/completion/list_wireless_phys.sh b/src/completion/list_wireless_phys.sh new file mode 100644 index 0000000..70b8d1f --- /dev/null +++ b/src/completion/list_wireless_phys.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +if [ -d /sys/class/ieee80211 ]; then + ls -x /sys/class/ieee80211 +fi diff --git a/src/completion/qos/list_traffic_match_group.py b/src/completion/qos/list_traffic_match_group.py new file mode 100644 index 0000000..015d7ad --- /dev/null +++ b/src/completion/qos/list_traffic_match_group.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from vyos.config import Config + + +def get_qos_traffic_match_group(): + config = Config() + base = ['qos', 'traffic-match-group'] + conf = config.get_config_dict(base, key_mangling=('-', '_')) + groups = [] + + for group in conf.get('traffic_match_group', []): + groups.append(group) + + return groups + + +if __name__ == "__main__": + groups = get_qos_traffic_match_group() + print(" ".join(groups)) + diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py new file mode 100644 index 0000000..14387cb --- /dev/null +++ b/src/conf_mode/container.py @@ -0,0 +1,525 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from decimal import Decimal +from hashlib import sha256 +from ipaddress import ip_address +from ipaddress import ip_network +from json import dumps as json_write + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configdict import node_changed +from vyos.configdict import is_node_changed +from vyos.configverify import verify_vrf +from vyos.ifconfig import Interface +from vyos.utils.cpu import get_core_count +from vyos.utils.file import write_file +from vyos.utils.process import call +from vyos.utils.process import cmd +from vyos.utils.process import run +from vyos.utils.network import interface_exists +from vyos.template import bracketize_ipv6 +from vyos.template import inc_ip +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 +from vyos.template import render +from vyos.xml_ref import default_value +from vyos import ConfigError +from vyos import airbag + +airbag.enable() + +config_containers = '/etc/containers/containers.conf' +config_registry = '/etc/containers/registries.conf' +config_storage = '/etc/containers/storage.conf' +systemd_unit_path = '/run/systemd/system' + + +def _cmd(command): + if os.path.exists('/tmp/vyos.container.debug'): + print(command) + return cmd(command) + + +def network_exists(name): + # Check explicit name for network, returns True if network exists + c = _cmd(f'podman network ls --quiet --filter name=^{name}$') + return bool(c) + + +# Common functions +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['container'] + container = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + for name in container.get('name', []): + # T5047: Any container related configuration changed? We only + # wan't to restart the required containers and not all of them ... + tmp = is_node_changed(conf, base + ['name', name]) + if tmp: + if 'container_restart' not in container: + container['container_restart'] = [name] + else: + container['container_restart'].append(name) + + # registry is a tagNode with default values - merge the list from + # default_values['registry'] into the tagNode variables + if 'registry' not in container: + container.update({'registry': {}}) + default_values = default_value(base + ['registry']) + for registry in default_values: + tmp = {registry: {}} + container['registry'] = dict_merge(tmp, container['registry']) + + # Delete container network, delete containers + tmp = node_changed(conf, base + ['network']) + if tmp: container.update({'network_remove': tmp}) + + tmp = node_changed(conf, base + ['name']) + if tmp: container.update({'container_remove': tmp}) + + return container + + +def verify(container): + # bail out early - looks like removal from running config + if not container: + return None + + # Add new container + if 'name' in container: + for name, container_config in container['name'].items(): + # Container image is a mandatory option + if 'image' not in container_config: + raise ConfigError(f'Container image for "{name}" is mandatory!') + + # Check if requested container image exists locally. If it does not + # exist locally - inform the user. This is required as there is a + # shared container image storage accross all VyOS images. A user can + # delete a container image from the system, boot into another version + # of VyOS and then it would fail to boot. This is to prevent any + # configuration error when container images are deleted from the + # global storage. A per image local storage would be a super waste + # of diskspace as there will be a full copy (up tu several GB/image) + # on upgrade. This is the "cheapest" and fastest solution in terms + # of image upgrade and deletion. + image = container_config['image'] + if run(f'podman image exists {image}') != 0: + Warning(f'Image "{image}" used in container "{name}" does not exist ' \ + f'locally. Please use "add container image {image}" to add it ' \ + f'to the system! Container "{name}" will not be started!') + + if 'cpu_quota' in container_config: + cores = get_core_count() + if Decimal(container_config['cpu_quota']) > cores: + raise ConfigError(f'Cannot set limit to more cores than available "{name}"!') + + if 'network' in container_config: + if len(container_config['network']) > 1: + raise ConfigError(f'Only one network can be specified for container "{name}"!') + + # Check if the specified container network exists + network_name = list(container_config['network'])[0] + if network_name not in container.get('network', {}): + raise ConfigError(f'Container network "{network_name}" does not exist!') + + if 'address' in container_config['network'][network_name]: + cnt_ipv4 = 0 + cnt_ipv6 = 0 + for address in container_config['network'][network_name]['address']: + network = None + if is_ipv4(address): + try: + network = [x for x in container['network'][network_name]['prefix'] if is_ipv4(x)][0] + cnt_ipv4 += 1 + except: + raise ConfigError(f'Network "{network_name}" does not contain an IPv4 prefix!') + elif is_ipv6(address): + try: + network = [x for x in container['network'][network_name]['prefix'] if is_ipv6(x)][0] + cnt_ipv6 += 1 + except: + raise ConfigError(f'Network "{network_name}" does not contain an IPv6 prefix!') + + # Specified container IP address must belong to network prefix + if ip_address(address) not in ip_network(network): + raise ConfigError(f'Used container address "{address}" not in network "{network}"!') + + # We can not use the first IP address of a network prefix as this is used by podman + if ip_address(address) == ip_network(network)[1]: + raise ConfigError(f'IP address "{address}" can not be used for a container, ' \ + 'reserved for the container engine!') + + if cnt_ipv4 > 1 or cnt_ipv6 > 1: + raise ConfigError(f'Only one IP address per address family can be used for ' \ + f'container "{name}". {cnt_ipv4} IPv4 and {cnt_ipv6} IPv6 address(es)!') + + if 'device' in container_config: + for dev, dev_config in container_config['device'].items(): + if 'source' not in dev_config: + raise ConfigError(f'Device "{dev}" has no source path configured!') + + if 'destination' not in dev_config: + raise ConfigError(f'Device "{dev}" has no destination path configured!') + + source = dev_config['source'] + if not os.path.exists(source): + raise ConfigError(f'Device "{dev}" source path "{source}" does not exist!') + + if 'sysctl' in container_config and 'parameter' in container_config['sysctl']: + for var, cfg in container_config['sysctl']['parameter'].items(): + if 'value' not in cfg: + raise ConfigError(f'sysctl parameter {var} has no value assigned!') + if var.startswith('net.') and 'allow_host_networks' in container_config: + raise ConfigError(f'sysctl parameter {var} cannot be set when using host networking!') + + if 'environment' in container_config: + for var, cfg in container_config['environment'].items(): + if 'value' not in cfg: + raise ConfigError(f'Environment variable {var} has no value assigned!') + + if 'label' in container_config: + for var, cfg in container_config['label'].items(): + if 'value' not in cfg: + raise ConfigError(f'Label variable {var} has no value assigned!') + + if 'volume' in container_config: + for volume, volume_config in container_config['volume'].items(): + if 'source' not in volume_config: + raise ConfigError(f'Volume "{volume}" has no source path configured!') + + if 'destination' not in volume_config: + raise ConfigError(f'Volume "{volume}" has no destination path configured!') + + source = volume_config['source'] + if not os.path.exists(source): + raise ConfigError(f'Volume "{volume}" source path "{source}" does not exist!') + + if 'port' in container_config: + for tmp in container_config['port']: + if not {'source', 'destination'} <= set(container_config['port'][tmp]): + raise ConfigError(f'Both "source" and "destination" must be specified for a port mapping!') + + # If 'allow-host-networks' or 'network' not set. + if 'allow_host_networks' not in container_config and 'network' not in container_config: + raise ConfigError(f'Must either set "network" or "allow-host-networks" for container "{name}"!') + + # Can not set both allow-host-networks and network at the same time + if {'allow_host_networks', 'network'} <= set(container_config): + raise ConfigError( + f'"allow-host-networks" and "network" for "{name}" cannot be both configured at the same time!') + + # gid cannot be set without uid + if 'gid' in container_config and 'uid' not in container_config: + raise ConfigError(f'Cannot set "gid" without "uid" for container') + + # Add new network + if 'network' in container: + for network, network_config in container['network'].items(): + v4_prefix = 0 + v6_prefix = 0 + # If ipv4-prefix not defined for user-defined network + if 'prefix' not in network_config: + raise ConfigError(f'prefix for network "{network}" must be defined!') + + for prefix in network_config['prefix']: + if is_ipv4(prefix): + v4_prefix += 1 + elif is_ipv6(prefix): + v6_prefix += 1 + + if v4_prefix > 1: + raise ConfigError(f'Only one IPv4 prefix can be defined for network "{network}"!') + if v6_prefix > 1: + raise ConfigError(f'Only one IPv6 prefix can be defined for network "{network}"!') + + # Verify VRF exists + verify_vrf(network_config) + + # A network attached to a container can not be deleted + if {'network_remove', 'name'} <= set(container): + for network in container['network_remove']: + for c, c_config in container['name'].items(): + if 'network' in c_config and network in c_config['network']: + raise ConfigError(f'Can not remove network "{network}", used by container "{c}"!') + + if 'registry' in container: + for registry, registry_config in container['registry'].items(): + if 'authentication' not in registry_config: + continue + if not {'username', 'password'} <= set(registry_config['authentication']): + raise ConfigError('Container registry requires both username and password to be set!') + + return None + + +def generate_run_arguments(name, container_config): + image = container_config['image'] + cpu_quota = container_config['cpu_quota'] + memory = container_config['memory'] + shared_memory = container_config['shared_memory'] + restart = container_config['restart'] + + # Add sysctl options + sysctl_opt = '' + if 'sysctl' in container_config and 'parameter' in container_config['sysctl']: + for k, v in container_config['sysctl']['parameter'].items(): + sysctl_opt += f" --sysctl {k}={v['value']}" + + # Add capability options. Should be in uppercase + capabilities = '' + if 'capability' in container_config: + for cap in container_config['capability']: + cap = cap.upper().replace('-', '_') + capabilities += f' --cap-add={cap}' + + # Add a host device to the container /dev/x:/dev/x + device = '' + if 'device' in container_config: + for dev, dev_config in container_config['device'].items(): + source_dev = dev_config['source'] + dest_dev = dev_config['destination'] + device += f' --device={source_dev}:{dest_dev}' + + # Check/set environment options "-e foo=bar" + env_opt = '' + if 'environment' in container_config: + for k, v in container_config['environment'].items(): + env_opt += f" --env \"{k}={v['value']}\"" + + # Check/set label options "--label foo=bar" + label = '' + if 'label' in container_config: + for k, v in container_config['label'].items(): + label += f" --label \"{k}={v['value']}\"" + + hostname = '' + if 'host_name' in container_config: + hostname = container_config['host_name'] + hostname = f'--hostname {hostname}' + + # Publish ports + port = '' + if 'port' in container_config: + protocol = '' + for portmap in container_config['port']: + protocol = container_config['port'][portmap]['protocol'] + sport = container_config['port'][portmap]['source'] + dport = container_config['port'][portmap]['destination'] + listen_addresses = container_config['port'][portmap].get('listen_address', []) + + # If listen_addresses is not empty, include them in the publish command + if listen_addresses: + for listen_address in listen_addresses: + port += f' --publish {bracketize_ipv6(listen_address)}:{sport}:{dport}/{protocol}' + else: + # If listen_addresses is empty, just include the standard publish command + port += f' --publish {sport}:{dport}/{protocol}' + + # Set uid and gid + uid = '' + if 'uid' in container_config: + uid = container_config['uid'] + if 'gid' in container_config: + uid += ':' + container_config['gid'] + uid = f'--user {uid}' + + # Bind volume + volume = '' + if 'volume' in container_config: + for vol, vol_config in container_config['volume'].items(): + svol = vol_config['source'] + dvol = vol_config['destination'] + mode = vol_config['mode'] + prop = vol_config['propagation'] + volume += f' --volume {svol}:{dvol}:{mode},{prop}' + + host_pid = '' + if 'allow_host_pid' in container_config: + host_pid = '--pid host' + + container_base_cmd = f'--detach --interactive --tty --replace {capabilities} --cpus {cpu_quota} {sysctl_opt} ' \ + f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \ + f'--name {name} {hostname} {device} {port} {volume} {env_opt} {label} {uid} {host_pid}' + + entrypoint = '' + if 'entrypoint' in container_config: + # it needs to be json-formatted with single quote on the outside + entrypoint = json_write(container_config['entrypoint'].split()).replace('"', """) + entrypoint = f'--entrypoint '{entrypoint}'' + + command = '' + if 'command' in container_config: + command = container_config['command'].strip() + + command_arguments = '' + if 'arguments' in container_config: + command_arguments = container_config['arguments'].strip() + + if 'allow_host_networks' in container_config: + return f'{container_base_cmd} --net host {entrypoint} {image} {command} {command_arguments}'.strip() + + ip_param = '' + networks = ",".join(container_config['network']) + for network in container_config['network']: + if 'address' not in container_config['network'][network]: + continue + for address in container_config['network'][network]['address']: + if is_ipv6(address): + ip_param += f' --ip6 {address}' + else: + ip_param += f' --ip {address}' + + return f'{container_base_cmd} --no-healthcheck --net {networks} {ip_param} {entrypoint} {image} {command} {command_arguments}'.strip() + + +def generate(container): + # bail out early - looks like removal from running config + if not container: + for file in [config_containers, config_registry, config_storage]: + if os.path.exists(file): + os.unlink(file) + return None + + if 'network' in container: + for network, network_config in container['network'].items(): + tmp = { + 'name': network, + 'id': sha256(f'{network}'.encode()).hexdigest(), + 'driver': 'bridge', + 'network_interface': f'pod-{network}', + 'subnets': [], + 'ipv6_enabled': False, + 'internal': False, + 'dns_enabled': True, + 'ipam_options': { + 'driver': 'host-local' + } + } + + if 'no_name_server' in network_config: + tmp['dns_enabled'] = False + + for prefix in network_config['prefix']: + net = {'subnet': prefix, 'gateway': inc_ip(prefix, 1)} + tmp['subnets'].append(net) + + if is_ipv6(prefix): + tmp['ipv6_enabled'] = True + + write_file(f'/etc/containers/networks/{network}.json', json_write(tmp, indent=2)) + + render(config_containers, 'container/containers.conf.j2', container) + render(config_registry, 'container/registries.conf.j2', container) + render(config_storage, 'container/storage.conf.j2', container) + + if 'name' in container: + for name, container_config in container['name'].items(): + if 'disable' in container_config: + continue + + file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service') + run_args = generate_run_arguments(name, container_config) + render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args, }, + formater=lambda _: _.replace(""", '"').replace("'", "'")) + + return None + + +def apply(container): + # Delete old containers if needed. We can't delete running container + # Option "--force" allows to delete containers with any status + if 'container_remove' in container: + for name in container['container_remove']: + file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service') + call(f'systemctl stop vyos-container-{name}.service') + if os.path.exists(file_path): + os.unlink(file_path) + + call('systemctl daemon-reload') + + # Delete old networks if needed + if 'network_remove' in container: + for network in container['network_remove']: + call(f'podman network rm {network} >/dev/null 2>&1') + + # Add container + disabled_new = False + if 'name' in container: + for name, container_config in container['name'].items(): + image = container_config['image'] + + if run(f'podman image exists {image}') != 0: + # container image does not exist locally - user already got + # informed by a WARNING in verfiy() - bail out early + continue + + if 'disable' in container_config: + # check if there is a container by that name running + tmp = _cmd('podman ps -a --format "{{.Names}}"') + if name in tmp: + file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service') + call(f'systemctl stop vyos-container-{name}.service') + if os.path.exists(file_path): + disabled_new = True + os.unlink(file_path) + continue + + if 'container_restart' in container and name in container['container_restart']: + cmd(f'systemctl restart vyos-container-{name}.service') + + if disabled_new: + call('systemctl daemon-reload') + + # Start network and assign it to given VRF if requested. this can only be done + # after the containers got started as the podman network interface will + # only be enabled by the first container and yet I do not know how to enable + # the network interface in advance + if 'network' in container: + for network, network_config in container['network'].items(): + network_name = f'pod-{network}' + # T5147: Networks are started only as soon as there is a consumer. + # If only a network is created in the first place, no need to assign + # it to a VRF as there's no consumer, yet. + if interface_exists(network_name): + tmp = Interface(network_name) + tmp.set_vrf(network_config.get('vrf', '')) + tmp.add_ipv6_eui64_address('fe80::/64') + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py new file mode 100644 index 0000000..5638a96 --- /dev/null +++ b/src/conf_mode/firewall.py @@ -0,0 +1,597 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import re + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdict import is_node_changed +from vyos.configdiff import get_config_diff, Diff +from vyos.configdep import set_dependents, call_dependents +from vyos.configverify import verify_interface_exists +from vyos.ethtool import Ethtool +from vyos.firewall import fqdn_config_parse +from vyos.firewall import geoip_update +from vyos.template import render +from vyos.utils.dict import dict_search_args +from vyos.utils.dict import dict_search_recursive +from vyos.utils.process import call +from vyos.utils.process import cmd +from vyos.utils.process import rc_cmd +from vyos import ConfigError +from vyos import airbag +from subprocess import run as subp_run + +airbag.enable() + +nftables_conf = '/run/nftables.conf' +sysctl_file = r'/run/sysctl/10-vyos-firewall.conf' + +valid_groups = [ + 'address_group', + 'domain_group', + 'network_group', + 'port_group', + 'interface_group', + ## Added for group ussage in bridge firewall + 'ipv4_address_group', + 'ipv6_address_group', + 'ipv4_network_group', + 'ipv6_network_group' +] + +nested_group_types = [ + 'address_group', 'network_group', 'mac_group', + 'port_group', 'ipv6_address_group', 'ipv6_network_group' +] + +snmp_change_type = { + 'unknown': 0, + 'add': 1, + 'delete': 2, + 'change': 3 +} +snmp_event_source = 1 +snmp_trap_mib = 'VYATTA-TRAP-MIB' +snmp_trap_name = 'mgmtEventTrap' + +def geoip_updated(conf, firewall): + diff = get_config_diff(conf) + node_diff = diff.get_child_nodes_diff(['firewall'], expand_nodes=Diff.DELETE, recursive=True) + + out = { + 'name': [], + 'ipv6_name': [], + 'deleted_name': [], + 'deleted_ipv6_name': [] + } + updated = False + + for key, path in dict_search_recursive(firewall, 'geoip'): + set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' + if (path[0] == 'ipv4'): + out['name'].append(set_name) + elif (path[0] == 'ipv6'): + set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}' + out['ipv6_name'].append(set_name) + + updated = True + + if 'delete' in node_diff: + for key, path in dict_search_recursive(node_diff['delete'], 'geoip'): + set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' + if (path[0] == 'ipv4'): + out['deleted_name'].append(set_name) + elif (path[0] == 'ipv6'): + set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' + out['deleted_ipv6_name'].append(set_name) + updated = True + + if updated: + return out + + return False + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['firewall'] + + firewall = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + + firewall['group_resync'] = bool('group' in firewall or is_node_changed(conf, base + ['group'])) + if firewall['group_resync']: + # Update nat and policy-route as firewall groups were updated + set_dependents('group_resync', conf) + + firewall['geoip_updated'] = geoip_updated(conf, firewall) + + fqdn_config_parse(firewall) + + set_dependents('conntrack', conf) + + return firewall + +def verify_jump_target(firewall, hook, jump_target, family, recursive=False): + targets_seen = [] + targets_pending = [jump_target] + + while targets_pending: + target = targets_pending.pop() + + if 'name' not in firewall[family]: + raise ConfigError(f'Invalid jump-target. Firewall {family} name {target} does not exist on the system') + elif target not in dict_search_args(firewall, family, 'name'): + raise ConfigError(f'Invalid jump-target. Firewall {family} name {target} does not exist on the system') + + target_rules = dict_search_args(firewall, family, 'name', target, 'rule') + no_ipsec_in = hook in ('output', ) + + if target_rules: + for target_rule_conf in target_rules.values(): + # Output hook types will not tolerate 'meta ipsec exists' matches even in jump targets: + if no_ipsec_in and (dict_search_args(target_rule_conf, 'ipsec', 'match_ipsec_in') is not None \ + or dict_search_args(target_rule_conf, 'ipsec', 'match_none_in') is not None): + raise ConfigError(f'Invalid jump-target for {hook}. Firewall {family} name {target} rules contain incompatible ipsec inbound matches') + # Make sure we're not looping back on ourselves somewhere: + if recursive and 'jump_target' in target_rule_conf: + child_target = target_rule_conf['jump_target'] + if child_target in targets_seen: + raise ConfigError(f'Loop detected in jump-targets, firewall {family} name {target} refers to previously traversed {family} name {child_target}') + targets_pending.append(child_target) + if len(targets_seen) == 7: + path_txt = ' -> '.join(targets_seen) + Warning(f'Deep nesting of jump targets has reached 8 levels deep, following the path {path_txt} -> {child_target}!') + + targets_seen.append(target) + +def verify_rule(firewall, family, hook, priority, rule_id, rule_conf): + if 'action' not in rule_conf: + raise ConfigError('Rule action must be defined') + + if 'jump' in rule_conf['action'] and 'jump_target' not in rule_conf: + raise ConfigError('Action set to jump, but no jump-target specified') + + if 'jump_target' in rule_conf: + if 'jump' not in rule_conf['action']: + raise ConfigError('jump-target defined, but action jump needed and it is not defined') + target = rule_conf['jump_target'] + if hook != 'name': # This is a bit clumsy, but consolidates a chunk of code. + verify_jump_target(firewall, hook, target, family, recursive=True) + else: + verify_jump_target(firewall, hook, target, family, recursive=False) + + if rule_conf['action'] == 'offload': + if 'offload_target' not in rule_conf: + raise ConfigError('Action set to offload, but no offload-target specified') + + offload_target = rule_conf['offload_target'] + + if not dict_search_args(firewall, 'flowtable', offload_target): + raise ConfigError(f'Invalid offload-target. Flowtable "{offload_target}" does not exist on the system') + + if rule_conf['action'] != 'synproxy' and 'synproxy' in rule_conf: + raise ConfigError('"synproxy" option allowed only for action synproxy') + if rule_conf['action'] == 'synproxy': + if 'state' in rule_conf: + raise ConfigError('For action "synproxy" state cannot be defined') + if not rule_conf.get('synproxy', {}).get('tcp'): + raise ConfigError('synproxy TCP MSS is not defined') + if rule_conf.get('protocol', {}) != 'tcp': + raise ConfigError('For action "synproxy" the protocol must be set to TCP') + + if 'queue_options' in rule_conf: + if 'queue' not in rule_conf['action']: + raise ConfigError('queue-options defined, but action queue needed and it is not defined') + if 'fanout' in rule_conf['queue_options'] and ('queue' not in rule_conf or '-' not in rule_conf['queue']): + raise ConfigError('queue-options fanout defined, then queue needs to be defined as a range') + + if 'queue' in rule_conf and 'queue' not in rule_conf['action']: + raise ConfigError('queue defined, but action queue needed and it is not defined') + + if 'fragment' in rule_conf: + if {'match_frag', 'match_non_frag'} <= set(rule_conf['fragment']): + raise ConfigError('Cannot specify both "match-frag" and "match-non-frag"') + + if 'limit' in rule_conf: + if 'rate' in rule_conf['limit']: + rate_int = re.sub(r'\D', '', rule_conf['limit']['rate']) + if int(rate_int) < 1: + raise ConfigError('Limit rate integer cannot be less than 1') + + if 'ipsec' in rule_conf: + if {'match_ipsec_in', 'match_none_in'} <= set(rule_conf['ipsec']): + raise ConfigError('Cannot specify both "match-ipsec" and "match-none"') + if {'match_ipsec_out', 'match_none_out'} <= set(rule_conf['ipsec']): + raise ConfigError('Cannot specify both "match-ipsec" and "match-none"') + + if 'recent' in rule_conf: + if not {'count', 'time'} <= set(rule_conf['recent']): + raise ConfigError('Recent "count" and "time" values must be defined') + + if 'gre' in rule_conf: + if dict_search_args(rule_conf, 'protocol') != 'gre': + raise ConfigError('Protocol must be gre when matching GRE flags and fields') + + if dict_search_args(rule_conf, 'gre', 'key'): + if dict_search_args(rule_conf, 'gre', 'version') == 'pptp': + raise ConfigError('GRE tunnel keys are not present in PPTP') + + if dict_search_args(rule_conf, 'gre', 'flags', 'checksum') is None: + # There is no builtin match in nftables for the GRE key, so we need to do a raw lookup. + # The offset of the key within the packet shifts depending on the C-flag. + # 99% of the time, nobody will have checksums enabled - it's usually a manual config option. + # We can either assume it is unset unless otherwise directed + # (confusing, requires doco to explain why it doesn't work sometimes) + # or, demand an explicit selection to be made for this specific match rule. + # This check enforces the latter. The user is free to create rules for both cases. + raise ConfigError('Matching GRE tunnel key requires an explicit checksum flag match. For most cases, use "gre flags checksum unset"') + + if dict_search_args(rule_conf, 'gre', 'flags', 'key', 'unset') is not None: + raise ConfigError('Matching GRE tunnel key implies "flags key", cannot specify "flags key unset"') + + gre_inner_proto = dict_search_args(rule_conf, 'gre', 'inner_proto') + if gre_inner_proto is not None: + try: + gre_inner_value = int(gre_inner_proto, 0) + if gre_inner_value < 0 or gre_inner_value > 65535: + raise ConfigError('inner-proto outside valid ethertype range 0-65535') + except ValueError: + pass # Symbolic constant, pre-validated before reaching here. + + tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') + if tcp_flags: + if dict_search_args(rule_conf, 'protocol') != 'tcp': + raise ConfigError('Protocol must be tcp when specifying tcp flags') + + not_flags = dict_search_args(rule_conf, 'tcp', 'flags', 'not') + if not_flags: + duplicates = [flag for flag in tcp_flags if flag in not_flags] + if duplicates: + raise ConfigError(f'Cannot match a tcp flag as set and not set') + + if 'protocol' in rule_conf: + if rule_conf['protocol'] == 'icmp' and family == 'ipv6': + raise ConfigError(f'Cannot match IPv4 ICMP protocol on IPv6, use ipv6-icmp') + if rule_conf['protocol'] == 'ipv6-icmp' and family == 'ipv4': + raise ConfigError(f'Cannot match IPv6 ICMP protocol on IPv4, use icmp') + + for side in ['destination', 'source']: + if side in rule_conf: + side_conf = rule_conf[side] + + if len({'address', 'fqdn', 'geoip'} & set(side_conf)) > 1: + raise ConfigError('Only one of address, fqdn or geoip can be specified') + + if 'group' in side_conf: + if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1: + raise ConfigError('Only one address-group, network-group or domain-group can be specified') + + for group in valid_groups: + if group in side_conf['group']: + group_name = side_conf['group'][group] + + if family == 'ipv6' and group in ['address_group', 'network_group']: + fw_group = f'ipv6_{group}' + elif family == 'bridge': + if group =='ipv4_address_group': + fw_group = 'address_group' + elif group == 'ipv4_network_group': + fw_group = 'network_group' + else: + fw_group = group + else: + fw_group = group + + error_group = fw_group.replace("_", "-") + + if group in ['address_group', 'network_group', 'domain_group']: + types = [t for t in ['address', 'fqdn', 'geoip'] if t in side_conf] + if types: + raise ConfigError(f'{error_group} and {types[0]} cannot both be defined') + + if group_name and group_name[0] == '!': + group_name = group_name[1:] + + group_obj = dict_search_args(firewall, 'group', fw_group, group_name) + + if group_obj is None: + raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule') + + if not group_obj: + Warning(f'{error_group} "{group_name}" has no members!') + + if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'): + if 'protocol' not in rule_conf: + raise ConfigError('Protocol must be defined if specifying a port or port-group') + + if rule_conf['protocol'] not in ['tcp', 'udp', 'tcp_udp']: + raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group') + + if 'port' in side_conf and dict_search_args(side_conf, 'group', 'port_group'): + raise ConfigError(f'{side} port-group and port cannot both be defined') + + if 'add_address_to_group' in rule_conf: + for type in ['destination_address', 'source_address']: + if type in rule_conf['add_address_to_group']: + if 'address_group' not in rule_conf['add_address_to_group'][type]: + raise ConfigError(f'Dynamic address group must be defined.') + else: + target = rule_conf['add_address_to_group'][type]['address_group'] + fwall_group = 'ipv6_address_group' if family == 'ipv6' else 'address_group' + group_obj = dict_search_args(firewall, 'group', 'dynamic_group', fwall_group, target) + if group_obj is None: + raise ConfigError(f'Invalid dynamic address group on firewall rule') + + if 'log_options' in rule_conf: + if 'log' not in rule_conf: + raise ConfigError('log-options defined, but log is not enable') + + if 'snapshot_length' in rule_conf['log_options'] and 'group' not in rule_conf['log_options']: + raise ConfigError('log-options snapshot-length defined, but log group is not define') + + if 'queue_threshold' in rule_conf['log_options'] and 'group' not in rule_conf['log_options']: + raise ConfigError('log-options queue-threshold defined, but log group is not define') + + for direction in ['inbound_interface','outbound_interface']: + if direction in rule_conf: + if 'name' in rule_conf[direction] and 'group' in rule_conf[direction]: + raise ConfigError(f'Cannot specify both interface group and interface name for {direction}') + if 'group' in rule_conf[direction]: + group_name = rule_conf[direction]['group'] + if group_name[0] == '!': + group_name = group_name[1:] + group_obj = dict_search_args(firewall, 'group', 'interface_group', group_name) + if group_obj is None: + raise ConfigError(f'Invalid interface group "{group_name}" on firewall rule') + if not group_obj: + Warning(f'interface-group "{group_name}" has no members!') + +def verify_nested_group(group_name, group, groups, seen): + if 'include' not in group: + return + + seen.append(group_name) + + for g in group['include']: + if g not in groups: + raise ConfigError(f'Nested group "{g}" does not exist') + + if g in seen: + raise ConfigError(f'Group "{group_name}" has a circular reference') + + if 'include' in groups[g]: + verify_nested_group(g, groups[g], groups, seen) + +def verify_hardware_offload(ifname): + ethtool = Ethtool(ifname) + enabled, fixed = ethtool.get_hw_tc_offload() + + if not enabled and fixed: + raise ConfigError(f'Interface "{ifname}" does not support hardware offload') + + if not enabled: + raise ConfigError(f'Interface "{ifname}" requires "offload hw-tc-offload"') + +def verify(firewall): + if 'flowtable' in firewall: + for flowtable, flowtable_conf in firewall['flowtable'].items(): + if 'interface' not in flowtable_conf: + raise ConfigError(f'Flowtable "{flowtable}" requires at least one interface') + + for ifname in flowtable_conf['interface']: + verify_interface_exists(firewall, ifname) + + if dict_search_args(flowtable_conf, 'offload') == 'hardware': + interfaces = flowtable_conf['interface'] + + for ifname in interfaces: + verify_hardware_offload(ifname) + + if 'group' in firewall: + for group_type in nested_group_types: + if group_type in firewall['group']: + groups = firewall['group'][group_type] + for group_name, group in groups.items(): + verify_nested_group(group_name, group, groups, []) + + for family in ['ipv4', 'ipv6', 'bridge']: + if family in firewall: + for chain in ['name','forward','input','output', 'prerouting']: + if chain in firewall[family]: + for priority, priority_conf in firewall[family][chain].items(): + if 'jump' in priority_conf['default_action'] and 'default_jump_target' not in priority_conf: + raise ConfigError('default-action set to jump, but no default-jump-target specified') + if 'default_jump_target' in priority_conf: + target = priority_conf['default_jump_target'] + if 'jump' not in priority_conf['default_action']: + raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined') + if priority_conf['default_jump_target'] == priority: + raise ConfigError(f'Loop detected on default-jump-target.') + if target not in dict_search_args(firewall[family], 'name'): + raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') + if 'rule' in priority_conf: + for rule_id, rule_conf in priority_conf['rule'].items(): + verify_rule(firewall, family, chain, priority, rule_id, rule_conf) + + local_zone = False + zone_interfaces = [] + + if 'zone' in firewall: + for zone, zone_conf in firewall['zone'].items(): + if 'local_zone' not in zone_conf and 'interface' not in zone_conf: + raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone') + + if 'local_zone' in zone_conf: + if local_zone: + raise ConfigError('There cannot be multiple local zones') + if 'interface' in zone_conf: + raise ConfigError('Local zone cannot have interfaces assigned') + if 'intra_zone_filtering' in zone_conf: + raise ConfigError('Local zone cannot use intra-zone-filtering') + local_zone = True + + if 'interface' in zone_conf: + found_duplicates = [intf for intf in zone_conf['interface'] if intf in zone_interfaces] + + if found_duplicates: + raise ConfigError(f'Interfaces cannot be assigned to multiple zones') + + zone_interfaces += zone_conf['interface'] + + if 'intra_zone_filtering' in zone_conf: + intra_zone = zone_conf['intra_zone_filtering'] + + if len(intra_zone) > 1: + raise ConfigError('Only one intra-zone-filtering action must be specified') + + if 'firewall' in intra_zone: + v4_name = dict_search_args(intra_zone, 'firewall', 'name') + if v4_name and not dict_search_args(firewall, 'ipv4', 'name', v4_name): + raise ConfigError(f'Firewall name "{v4_name}" does not exist') + + v6_name = dict_search_args(intra_zone, 'firewall', 'ipv6_name') + if v6_name and not dict_search_args(firewall, 'ipv6', 'name', v6_name): + raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') + + if not v4_name and not v6_name: + raise ConfigError('No firewall names specified for intra-zone-filtering') + + if 'from' in zone_conf: + for from_zone, from_conf in zone_conf['from'].items(): + if from_zone not in firewall['zone']: + raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"') + + v4_name = dict_search_args(from_conf, 'firewall', 'name') + if v4_name and not dict_search_args(firewall, 'ipv4', 'name', v4_name): + raise ConfigError(f'Firewall name "{v4_name}" does not exist') + + v6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name') + if v6_name and not dict_search_args(firewall, 'ipv6', 'name', v6_name): + raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') + + return None + +def generate(firewall): + if not os.path.exists(nftables_conf): + firewall['first_install'] = True + + if 'zone' in firewall: + for local_zone, local_zone_conf in firewall['zone'].items(): + if 'local_zone' not in local_zone_conf: + continue + + local_zone_conf['from_local'] = {} + + for zone, zone_conf in firewall['zone'].items(): + if zone == local_zone or 'from' not in zone_conf: + continue + if local_zone in zone_conf['from']: + local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone] + + render(nftables_conf, 'firewall/nftables.j2', firewall) + render(sysctl_file, 'firewall/sysctl-firewall.conf.j2', firewall) + return None + +def parse_firewall_error(output): + # Define the regex patterns to extract the error message and the comment + error_pattern = re.compile(r'Error:\s*(.*?)\n') + comment_pattern = re.compile(r'comment\s+"([^"]+)"') + error_output = [] + + # Find all error messages in the output + error_matches = error_pattern.findall(output) + # Find all comment matches in the output + comment_matches = comment_pattern.findall(output) + + if not error_matches or not comment_matches: + raise ConfigError(f'Unknown firewall error detected: {output}') + + error_output.append('Fail to apply firewall') + # Loop over the matches and process them + for error_message, comment in zip(error_matches, comment_matches): + # Parse the comment + parsed_entries = comment.split('-') + family = 'bridge' if parsed_entries[0] == 'bri' else parsed_entries[0] + if parsed_entries[1] == 'NAM': + chain = 'name' + elif parsed_entries[1] == 'FWD': + chain = 'forward' + elif parsed_entries[1] == 'INP': + chain = 'input' + elif parsed_entries[1] == 'OUT': + chain = 'output' + elif parsed_entries[1] == 'PRE': + chain = 'prerouting' + error_output.append(f'Error found on: firewall {family} {chain} {parsed_entries[2]} rule {parsed_entries[3]}') + error_output.append(f'\tError message: {error_message.strip()}') + + raise ConfigError('\n'.join(error_output)) + +def apply(firewall): + # Use nft -c option to check current configuration file + completed_process = subp_run(['nft', '-c', '--file', nftables_conf], capture_output=True) + install_result = completed_process.returncode + if install_result == 1: + # We need to handle firewall error + output = completed_process.stderr + parse_firewall_error(output.decode()) + + # No error detected during check, we can apply the new configuration + install_result, output = rc_cmd(f'nft --file {nftables_conf}') + # Double check just in case + if install_result == 1: + raise ConfigError(f'Failed to apply firewall: {output}') + + # Apply firewall global-options sysctl settings + cmd(f'sysctl -f {sysctl_file}') + + call_dependents() + + # T970 Enable a resolver (systemd daemon) that checks + # domain-group/fqdn addresses and update entries for domains by timeout + # If router loaded without internet connection or for synchronization + domain_action = 'stop' + if dict_search_args(firewall, 'group', 'domain_group') or firewall['ip_fqdn'] or firewall['ip6_fqdn']: + domain_action = 'restart' + call(f'systemctl {domain_action} vyos-domain-resolver.service') + + if firewall['geoip_updated']: + # Call helper script to Update set contents + if 'name' in firewall['geoip_updated'] or 'ipv6_name' in firewall['geoip_updated']: + print('Updating GeoIP. Please wait...') + geoip_update(firewall) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py new file mode 100644 index 0000000..c726db8 --- /dev/null +++ b/src/conf_mode/high-availability.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import os +import time + +from sys import exit +from ipaddress import ip_interface +from ipaddress import IPv4Interface +from ipaddress import IPv6Interface + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdict import leaf_node_changed +from vyos.ifconfig.vrrp import VRRP +from vyos.template import render +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 +from vyos.utils.network import is_ipv6_tentative +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + + +systemd_override = r'/run/systemd/system/keepalived.service.d/10-override.conf' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['high-availability'] + if not conf.exists(base): + return None + + ha = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, with_defaults=True) + + ## Get the sync group used for conntrack-sync + conntrack_path = ['service', 'conntrack-sync', 'failover-mechanism', 'vrrp', 'sync-group'] + if conf.exists(conntrack_path): + ha['conntrack_sync_group'] = conf.return_value(conntrack_path) + + if leaf_node_changed(conf, base + ['vrrp', 'snmp']): + ha.update({'restart_required': {}}) + + return ha + +def verify(ha): + if not ha or 'disable' in ha: + return None + + used_vrid_if = [] + if 'vrrp' in ha and 'group' in ha['vrrp']: + for group, group_config in ha['vrrp']['group'].items(): + # Check required fields + if 'vrid' not in group_config: + raise ConfigError(f'VRID is required but not set in VRRP group "{group}"') + + if 'interface' not in group_config: + raise ConfigError(f'Interface is required but not set in VRRP group "{group}"') + + if 'address' not in group_config: + raise ConfigError(f'Virtual IP address is required but not set in VRRP group "{group}"') + + if 'authentication' in group_config: + if not {'password', 'type'} <= set(group_config['authentication']): + raise ConfigError(f'Authentication requires both type and passwortd to be set in VRRP group "{group}"') + + if 'health_check' in group_config: + _validate_health_check(group, group_config) + + # Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction + # We also need to make sure VRID is not used twice on the same interface with the + # same address family. + + interface = group_config['interface'] + vrid = group_config['vrid'] + + # XXX: filter on map object is destructive, so we force it to list. + # Additionally, filter objects always evaluate to True, empty or not, + # so we force them to lists as well. + vaddrs = list(map(lambda i: ip_interface(i), group_config['address'])) + vaddrs4 = list(filter(lambda x: isinstance(x, IPv4Interface), vaddrs)) + vaddrs6 = list(filter(lambda x: isinstance(x, IPv6Interface), vaddrs)) + + if vaddrs4 and vaddrs6: + raise ConfigError(f'VRRP group "{group}" mixes IPv4 and IPv6 virtual addresses, this is not allowed.\n' \ + 'Create individual groups for IPv4 and IPv6!') + if vaddrs4: + tmp = {'interface': interface, 'vrid': vrid, 'ipver': 'IPv4'} + if tmp in used_vrid_if: + raise ConfigError(f'VRID "{vrid}" can only be used once on interface "{interface} with address family IPv4"!') + used_vrid_if.append(tmp) + + if 'hello_source_address' in group_config: + if is_ipv6(group_config['hello_source_address']): + raise ConfigError(f'VRRP group "{group}" uses IPv4 but hello-source-address is IPv6!') + + if 'peer_address' in group_config: + for peer_address in group_config['peer_address']: + if is_ipv6(peer_address): + raise ConfigError(f'VRRP group "{group}" uses IPv4 but peer-address is IPv6!') + + if vaddrs6: + tmp = {'interface': interface, 'vrid': vrid, 'ipver': 'IPv6'} + if tmp in used_vrid_if: + raise ConfigError(f'VRID "{vrid}" can only be used once on interface "{interface} with address family IPv6"!') + used_vrid_if.append(tmp) + + if 'hello_source_address' in group_config: + if is_ipv4(group_config['hello_source_address']): + raise ConfigError(f'VRRP group "{group}" uses IPv6 but hello-source-address is IPv4!') + + if 'peer_address' in group_config: + for peer_address in group_config['peer_address']: + if is_ipv4(peer_address): + raise ConfigError(f'VRRP group "{group}" uses IPv6 but peer-address is IPv4!') + # Check sync groups + if 'vrrp' in ha and 'sync_group' in ha['vrrp']: + for sync_group, sync_config in ha['vrrp']['sync_group'].items(): + if 'health_check' in sync_config: + _validate_health_check(sync_group, sync_config) + + if 'member' in sync_config: + for member in sync_config['member']: + if member not in ha['vrrp']['group']: + raise ConfigError(f'VRRP sync-group "{sync_group}" refers to VRRP group "{member}", '\ + 'but it does not exist!') + else: + ha['vrrp']['group'][member]['_is_sync_group_member'] = True + if ha['vrrp']['group'][member].get('health_check') is not None: + raise ConfigError( + f'Health check configuration for VRRP group "{member}" will remain unused ' + f'while it has member of sync group "{sync_group}" ' + f'Only sync group health check will be used' + ) + + # Virtual-server + if 'virtual_server' in ha: + for vs, vs_config in ha['virtual_server'].items(): + + if 'address' not in vs_config and 'fwmark' not in vs_config: + raise ConfigError('Either address or fwmark is required ' + f'but not set for virtual-server "{vs}"') + + if 'port' not in vs_config and 'fwmark' not in vs_config: + raise ConfigError(f'Port or fwmark is required but not set for virtual-server "{vs}"') + if 'port' in vs_config and 'fwmark' in vs_config: + raise ConfigError(f'Cannot set both port and fwmark for virtual-server "{vs}"') + if 'real_server' not in vs_config: + raise ConfigError(f'Real-server ip is required but not set for virtual-server "{vs}"') + # Real-server + for rs, rs_config in vs_config['real_server'].items(): + if 'port' not in rs_config: + raise ConfigError(f'Port is required but not set for virtual-server "{vs}" real-server "{rs}"') + + +def _validate_health_check(group, group_config): + health_check_types = ["script", "ping"] + from vyos.utils.dict import check_mutually_exclusive_options + try: + check_mutually_exclusive_options(group_config["health_check"], + health_check_types, required=True) + except ValueError: + Warning( + f'Health check configuration for VRRP group "{group}" will remain unused ' \ + f'until it has one of the following options: {health_check_types}') + # XXX: health check has default options so we need to remove it + # to avoid generating useless config statements in keepalived.conf + del group_config["health_check"] + + +def generate(ha): + if not ha or 'disable' in ha: + if os.path.isfile(systemd_override): + os.unlink(systemd_override) + return None + + render(VRRP.location['config'], 'high-availability/keepalived.conf.j2', ha) + render(systemd_override, 'high-availability/10-override.conf.j2', ha) + return None + +def apply(ha): + service_name = 'keepalived.service' + call('systemctl daemon-reload') + if not ha or 'disable' in ha: + call(f'systemctl stop {service_name}') + return None + + # Check if IPv6 address is tentative T5533 + for group, group_config in ha.get('vrrp', {}).get('group', {}).items(): + if 'hello_source_address' in group_config: + if is_ipv6(group_config['hello_source_address']): + ipv6_address = group_config['hello_source_address'] + interface = group_config['interface'] + checks = 20 + interval = 0.1 + for _ in range(checks): + if is_ipv6_tentative(interface, ipv6_address): + time.sleep(interval) + + systemd_action = 'reload-or-restart' + if 'restart_required' in ha: + systemd_action = 'restart' + + call(f'systemctl {systemd_action} {service_name}') + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py new file mode 100644 index 0000000..bbbfb03 --- /dev/null +++ b/src/conf_mode/interfaces_bonding.py @@ -0,0 +1,305 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configdict import leaf_node_changed +from vyos.configdict import is_member +from vyos.configdict import is_source_interface +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_eapol +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_vlan_config +from vyos.configverify import verify_vrf +from vyos.ifconfig import BondIf +from vyos.ifconfig.ethernet import EthernetIf +from vyos.ifconfig import Section +from vyos.template import render_to_string +from vyos.utils.assertion import assert_mac +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_to_paths_values +from vyos.utils.network import interface_exists +from vyos.configdict import has_address_configured +from vyos.configdict import has_vrf_configured +from vyos.configdep import set_dependents, call_dependents +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +def get_bond_mode(mode): + if mode == 'round-robin': + return 'balance-rr' + elif mode == 'active-backup': + return 'active-backup' + elif mode == 'xor-hash': + return 'balance-xor' + elif mode == 'broadcast': + return 'broadcast' + elif mode == '802.3ad': + return '802.3ad' + elif mode == 'transmit-load-balance': + return 'balance-tlb' + elif mode == 'adaptive-load-balance': + return 'balance-alb' + else: + raise ConfigError(f'invalid bond mode "{mode}"') + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'bonding'] + ifname, bond = get_interface_dict(conf, base, with_pki=True) + + # To make our own life easier transfor the list of member interfaces + # into a dictionary - we will use this to add additional information + # later on for each member + if 'member' in bond and 'interface' in bond['member']: + # convert list of member interfaces to a dictionary + bond['member']['interface'] = {k: {} for k in bond['member']['interface']} + + if 'mode' in bond: + bond['mode'] = get_bond_mode(bond['mode']) + + tmp = is_node_changed(conf, base + [ifname, 'mode']) + if tmp: bond['shutdown_required'] = {} + + tmp = is_node_changed(conf, base + [ifname, 'lacp-rate']) + if tmp: bond['shutdown_required'] = {} + + # determine which members have been removed + interfaces_removed = leaf_node_changed(conf, base + [ifname, 'member', 'interface']) + # Reset config level to interfaces + old_level = conf.get_level() + conf.set_level(['interfaces']) + + if interfaces_removed: + bond['shutdown_required'] = {} + if 'member' not in bond: + bond['member'] = {} + + tmp = {} + for interface in interfaces_removed: + # if member is deleted from bond, add dependencies to call + # ethernet commit again in apply function + # to apply options under ethernet section + set_dependents('ethernet', conf, interface) + section = Section.section(interface) # this will be 'ethernet' for 'eth0' + if conf.exists([section, interface, 'disable']): + tmp[interface] = {'disable': ''} + else: + tmp[interface] = {} + + # also present the interfaces to be removed from the bond as dictionary + bond['member']['interface_remove'] = tmp + + # Restore existing config level + conf.set_level(old_level) + + if dict_search('member.interface', bond): + for interface, interface_config in bond['member']['interface'].items(): + + interface_ethernet_config = conf.get_config_dict( + ['interfaces', 'ethernet', interface], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_defaults=False, + with_recursive_defaults=False) + + interface_config['config_paths'] = dict_to_paths_values(interface_ethernet_config) + + # Check if member interface is a new member + if not conf.exists_effective(base + [ifname, 'member', 'interface', interface]): + bond['shutdown_required'] = {} + interface_config['new_added'] = {} + + # Check if member interface is disabled + conf.set_level(['interfaces']) + + section = Section.section(interface) # this will be 'ethernet' for 'eth0' + if conf.exists([section, interface, 'disable']): + interface_config['disable'] = '' + + conf.set_level(old_level) + + # Check if member interface is already member of another bridge + tmp = is_member(conf, interface, 'bridge') + if tmp: interface_config['is_bridge_member'] = tmp + + # Check if member interface is already member of a bond + tmp = is_member(conf, interface, 'bonding') + for tmp in is_member(conf, interface, 'bonding'): + if bond['ifname'] == tmp: + continue + interface_config['is_bond_member'] = tmp + + # Check if member interface is used as source-interface on another interface + tmp = is_source_interface(conf, interface) + if tmp: interface_config['is_source_interface'] = tmp + + # bond members must not have an assigned address + tmp = has_address_configured(conf, interface) + if tmp: interface_config['has_address'] = {} + + # bond members must not have a VRF attached + tmp = has_vrf_configured(conf, interface) + if tmp: interface_config['has_vrf'] = {} + return bond + + +def verify(bond): + if 'deleted' in bond: + verify_bridge_delete(bond) + return None + + if 'arp_monitor' in bond: + if 'target' in bond['arp_monitor'] and len(bond['arp_monitor']['target']) > 16: + raise ConfigError('The maximum number of arp-monitor targets is 16') + + if 'interval' in bond['arp_monitor'] and int(bond['arp_monitor']['interval']) > 0: + if bond['mode'] in ['802.3ad', 'balance-tlb', 'balance-alb']: + raise ConfigError('ARP link monitoring does not work for mode 802.3ad, ' \ + 'transmit-load-balance or adaptive-load-balance') + + if 'primary' in bond: + if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: + raise ConfigError('Option primary - mode dependency failed, not' + 'supported in mode {mode}!'.format(**bond)) + + verify_mtu_ipv6(bond) + verify_address(bond) + verify_dhcpv6(bond) + verify_vrf(bond) + verify_mirror_redirect(bond) + verify_eapol(bond) + + # use common function to verify VLAN configuration + verify_vlan_config(bond) + + bond_name = bond['ifname'] + if dict_search('member.interface', bond): + for interface, interface_config in bond['member']['interface'].items(): + error_msg = f'Can not add interface "{interface}" to bond, ' + + if interface == 'lo': + raise ConfigError('Loopback interface "lo" can not be added to a bond') + + if not interface_exists(interface): + raise ConfigError(error_msg + 'it does not exist!') + + if 'is_bridge_member' in interface_config: + tmp = next(iter(interface_config['is_bridge_member'])) + raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') + + if 'is_bond_member' in interface_config: + tmp = next(iter(interface_config['is_bond_member'])) + raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') + + if 'is_source_interface' in interface_config: + tmp = interface_config['is_source_interface'] + raise ConfigError(error_msg + f'it is the source-interface of "{tmp}"!') + + if 'has_address' in interface_config: + raise ConfigError(error_msg + 'it has an address assigned!') + + if 'has_vrf' in interface_config: + raise ConfigError(error_msg + 'it has a VRF assigned!') + + if 'new_added' in interface_config and 'config_paths' in interface_config: + for option_path, option_value in interface_config['config_paths'].items(): + if option_path in EthernetIf.get_bond_member_allowed_options() : + continue + if option_path in BondIf.get_inherit_bond_options(): + continue + raise ConfigError(error_msg + f'it has a "{option_path.replace(".", " ")}" assigned!') + + if 'primary' in bond: + if bond['primary'] not in bond['member']['interface']: + raise ConfigError(f'Primary interface of bond "{bond_name}" must be a member interface') + + if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: + raise ConfigError('primary interface only works for mode active-backup, ' \ + 'transmit-load-balance or adaptive-load-balance') + + if 'system_mac' in bond: + if bond['mode'] != '802.3ad': + raise ConfigError('Actor MAC address only available in 802.3ad mode!') + + system_mac = bond['system_mac'] + try: + assert_mac(system_mac, test_all_zero=False) + except: + raise ConfigError(f'Cannot use a multicast MAC address "{system_mac}" as system-mac!') + + return None + +def generate(bond): + bond['frr_zebra_config'] = '' + if 'deleted' not in bond: + bond['frr_zebra_config'] = render_to_string('frr/evpn.mh.frr.j2', bond) + return None + +def apply(bond): + ifname = bond['ifname'] + b = BondIf(ifname) + if 'deleted' in bond: + # delete interface + b.remove() + else: + b.update(bond) + + if dict_search('member.interface_remove', bond): + try: + call_dependents() + except ConfigError: + raise ConfigError('Error in updating ethernet interface ' + 'after deleting it from bond') + + zebra_daemon = 'zebra' + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + # The route-map used for the FIB (zebra) is part of the zebra daemon + frr_cfg.load_configuration(zebra_daemon) + frr_cfg.modify_section(f'^interface {ifname}', stop_pattern='^exit', remove_stop_mark=True) + if 'frr_zebra_config' in bond: + frr_cfg.add_before(frr.default_add_before, bond['frr_zebra_config']) + frr_cfg.commit_configuration(zebra_daemon) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_bridge.py b/src/conf_mode/interfaces_bridge.py new file mode 100644 index 0000000..637db44 --- /dev/null +++ b/src/conf_mode/interfaces_bridge.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import node_changed +from vyos.configdict import is_member +from vyos.configdict import is_source_interface +from vyos.configdict import has_vlan_subinterface_configured +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_vrf +from vyos.ifconfig import BridgeIf +from vyos.configdict import has_address_configured +from vyos.configdict import has_vrf_configured +from vyos.configdep import set_dependents +from vyos.configdep import call_dependents +from vyos.utils.dict import dict_search +from vyos.utils.network import interface_exists +from vyos import ConfigError + +from vyos import airbag +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'bridge'] + ifname, bridge = get_interface_dict(conf, base) + + # determine which members have been removed + tmp = node_changed(conf, base + [ifname, 'member', 'interface']) + if tmp: + if 'member' in bridge: + bridge['member'].update({'interface_remove': {t: {} for t in tmp}}) + else: + bridge.update({'member': {'interface_remove': {t: {} for t in tmp}}}) + for interface in tmp: + # When using VXLAN member interfaces that are configured for Single + # VXLAN Device (SVD) we need to call the VXLAN conf-mode script to + # re-create VLAN to VNI mappings if required, but only if the interface + # is already live on the system - this must not be done on first commit + if interface.startswith('vxlan') and interface_exists(interface): + set_dependents('vxlan', conf, interface) + _, vxlan = get_interface_dict(conf, ['interfaces', 'vxlan'], ifname=interface) + bridge['member']['interface_remove'].update({interface: vxlan}) + # When using Wireless member interfaces we need to inform hostapd + # to properly set-up the bridge + elif interface.startswith('wlan') and interface_exists(interface): + set_dependents('wlan', conf, interface) + + if dict_search('member.interface', bridge) is not None: + for interface in list(bridge['member']['interface']): + # Check if member interface is already member of another bridge + tmp = is_member(conf, interface, 'bridge') + if tmp and bridge['ifname'] not in tmp: + bridge['member']['interface'][interface].update({'is_bridge_member' : tmp}) + + # Check if member interface is already member of a bond + tmp = is_member(conf, interface, 'bonding') + if tmp: bridge['member']['interface'][interface].update({'is_bond_member' : tmp}) + + # Check if member interface is used as source-interface on another interface + tmp = is_source_interface(conf, interface) + if tmp: bridge['member']['interface'][interface].update({'is_source_interface' : tmp}) + + # Bridge members must not have an assigned address + tmp = has_address_configured(conf, interface) + if tmp: bridge['member']['interface'][interface].update({'has_address' : ''}) + + # Bridge members must not have a VRF attached + tmp = has_vrf_configured(conf, interface) + if tmp: bridge['member']['interface'][interface].update({'has_vrf' : ''}) + + # VLAN-aware bridge members must not have VLAN interface configuration + tmp = has_vlan_subinterface_configured(conf,interface) + if 'enable_vlan' in bridge and tmp: + bridge['member']['interface'][interface].update({'has_vlan' : ''}) + + # When using VXLAN member interfaces that are configured for Single + # VXLAN Device (SVD) we need to call the VXLAN conf-mode script to + # re-create VLAN to VNI mappings if required, but only if the interface + # is already live on the system - this must not be done on first commit + if interface.startswith('vxlan') and interface_exists(interface): + set_dependents('vxlan', conf, interface) + # When using Wireless member interfaces we need to inform hostapd + # to properly set-up the bridge + elif interface.startswith('wlan') and interface_exists(interface): + set_dependents('wlan', conf, interface) + + # delete empty dictionary keys - no need to run code paths if nothing is there to do + if 'member' in bridge: + if 'interface' in bridge['member'] and len(bridge['member']['interface']) == 0: + del bridge['member']['interface'] + + if len(bridge['member']) == 0: + del bridge['member'] + + return bridge + +def verify(bridge): + # to delete interface or remove a member interface VXLAN first need to check if + # VXLAN does not require to be a member of a bridge interface + if dict_search('member.interface_remove', bridge): + for iface, iface_config in bridge['member']['interface_remove'].items(): + if iface.startswith('vxlan') and dict_search('parameters.neighbor_suppress', iface_config) != None: + raise ConfigError( + f'To detach interface {iface} from bridge you must first ' + f'disable "neighbor-suppress" parameter in the VXLAN interface {iface}' + ) + + if 'deleted' in bridge: + return None + + verify_dhcpv6(bridge) + verify_vrf(bridge) + verify_mirror_redirect(bridge) + + ifname = bridge['ifname'] + + if dict_search('member.interface', bridge): + for interface, interface_config in bridge['member']['interface'].items(): + error_msg = f'Can not add interface "{interface}" to bridge, ' + + if interface == 'lo': + raise ConfigError('Loopback interface "lo" can not be added to a bridge') + + if 'is_bridge_member' in interface_config: + tmp = next(iter(interface_config['is_bridge_member'])) + raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') + + if 'is_bond_member' in interface_config: + tmp = next(iter(interface_config['is_bond_member'])) + raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') + + if 'is_source_interface' in interface_config: + tmp = interface_config['is_source_interface'] + raise ConfigError(error_msg + f'it is the source-interface of "{tmp}"!') + + if 'has_address' in interface_config: + raise ConfigError(error_msg + 'it has an address assigned!') + + if 'has_vrf' in interface_config: + raise ConfigError(error_msg + 'it has a VRF assigned!') + + if 'enable_vlan' in bridge: + if 'has_vlan' in interface_config: + raise ConfigError(error_msg + 'it has VLAN subinterface(s) assigned!') + else: + for option in ['allowed_vlan', 'native_vlan']: + if option in interface_config: + raise ConfigError('Can not use VLAN options on non VLAN aware bridge') + + if 'enable_vlan' in bridge: + if dict_search('vif.1', bridge): + raise ConfigError(f'VLAN 1 sub interface cannot be set for VLAN aware bridge {ifname}, and VLAN 1 is always the parent interface') + else: + if dict_search('vif', bridge): + raise ConfigError(f'You must first activate "enable-vlan" of {ifname} bridge to use "vif"') + + return None + +def generate(bridge): + return None + +def apply(bridge): + br = BridgeIf(bridge['ifname']) + if 'deleted' in bridge: + # delete interface + br.remove() + else: + br.update(bridge) + + tmp = [] + if 'member' in bridge: + if 'interface_remove' in bridge['member']: + tmp.extend(bridge['member']['interface_remove']) + if 'interface' in bridge['member']: + tmp.extend(bridge['member']['interface']) + + for interface in tmp: + if interface.startswith(tuple(['vxlan', 'wlan'])) and interface_exists(interface): + try: + call_dependents() + except ConfigError: + raise ConfigError(f'Error updating member interface {interface} configuration after changing bridge!') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_dummy.py b/src/conf_mode/interfaces_dummy.py new file mode 100644 index 0000000..db768b9 --- /dev/null +++ b/src/conf_mode/interfaces_dummy.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_vrf +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mirror_redirect +from vyos.ifconfig import DummyIf +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'dummy'] + _, dummy = get_interface_dict(conf, base) + return dummy + +def verify(dummy): + if 'deleted' in dummy: + verify_bridge_delete(dummy) + return None + + verify_vrf(dummy) + verify_address(dummy) + verify_mirror_redirect(dummy) + + return None + +def generate(dummy): + return None + +def apply(dummy): + d = DummyIf(**dummy) + + # Remove dummy interface + if 'deleted' in dummy: + d.remove() + else: + d.update(dummy) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py new file mode 100644 index 0000000..34ce7bc --- /dev/null +++ b/src/conf_mode/interfaces_ethernet.py @@ -0,0 +1,360 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configverify import verify_address +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_mtu +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_vlan_config +from vyos.configverify import verify_vrf +from vyos.configverify import verify_bond_bridge_member +from vyos.configverify import verify_eapol +from vyos.ethtool import Ethtool +from vyos.ifconfig import EthernetIf +from vyos.ifconfig import BondIf +from vyos.template import render_to_string +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_to_paths_values +from vyos.utils.dict import dict_set +from vyos.utils.dict import dict_delete +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +def update_bond_options(conf: Config, eth_conf: dict) -> list: + """ + Return list of blocked options if interface is a bond member + :param conf: Config object + :type conf: Config + :param eth_conf: Ethernet config dictionary + :type eth_conf: dict + :return: List of blocked options + :rtype: list + """ + blocked_list = [] + bond_name = list(eth_conf['is_bond_member'].keys())[0] + config_without_defaults = conf.get_config_dict( + ['interfaces', 'ethernet', eth_conf['ifname']], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_defaults=False, + with_recursive_defaults=False) + config_with_defaults = conf.get_config_dict( + ['interfaces', 'ethernet', eth_conf['ifname']], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_defaults=True, + with_recursive_defaults=True) + bond_config_with_defaults = conf.get_config_dict( + ['interfaces', 'bonding', bond_name], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_defaults=True, + with_recursive_defaults=True) + eth_dict_paths = dict_to_paths_values(config_without_defaults) + eth_path_base = ['interfaces', 'ethernet', eth_conf['ifname']] + + #if option is configured under ethernet section + for option_path, option_value in eth_dict_paths.items(): + bond_option_value = dict_search(option_path, bond_config_with_defaults) + + #If option is allowed for changing then continue + if option_path in EthernetIf.get_bond_member_allowed_options(): + continue + # if option is inherited from bond then set valued from bond interface + if option_path in BondIf.get_inherit_bond_options(): + # If option equals to bond option then do nothing + if option_value == bond_option_value: + continue + else: + # if ethernet has option and bond interface has + # then copy it from bond + if bond_option_value is not None: + if is_node_changed(conf, eth_path_base + option_path.split('.')): + Warning( + f'Cannot apply "{option_path.replace(".", " ")}" to "{option_value}".' \ + f' Interface "{eth_conf["ifname"]}" is a bond member.' \ + f' Option is inherited from bond "{bond_name}"') + dict_set(option_path, bond_option_value, eth_conf) + continue + # if ethernet has option and bond interface does not have + # then delete it form dict and do not apply it + else: + if is_node_changed(conf, eth_path_base + option_path.split('.')): + Warning( + f'Cannot apply "{option_path.replace(".", " ")}".' \ + f' Interface "{eth_conf["ifname"]}" is a bond member.' \ + f' Option is inherited from bond "{bond_name}"') + dict_delete(option_path, eth_conf) + blocked_list.append(option_path) + + # if inherited option is not configured under ethernet section but configured under bond section + for option_path in BondIf.get_inherit_bond_options(): + bond_option_value = dict_search(option_path, bond_config_with_defaults) + if bond_option_value is not None: + if option_path not in eth_dict_paths: + if is_node_changed(conf, eth_path_base + option_path.split('.')): + Warning( + f'Cannot apply "{option_path.replace(".", " ")}" to "{dict_search(option_path, config_with_defaults)}".' \ + f' Interface "{eth_conf["ifname"]}" is a bond member. ' \ + f'Option is inherited from bond "{bond_name}"') + dict_set(option_path, bond_option_value, eth_conf) + eth_conf['bond_blocked_changes'] = blocked_list + return None + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + + base = ['interfaces', 'ethernet'] + ifname, ethernet = get_interface_dict(conf, base, with_pki=True) + + # T5862 - default MTU is not acceptable in some environments + # There are cloud environments available where the maximum supported + # ethernet MTU is e.g. 1450 bytes, thus we clamp this to the adapters + # maximum MTU value or 1500 bytes - whatever is lower + if 'mtu' not in ethernet: + try: + ethernet['mtu'] = '1500' + max_mtu = EthernetIf(ifname).get_max_mtu() + if max_mtu < int(ethernet['mtu']): + ethernet['mtu'] = str(max_mtu) + except: + pass + + if 'is_bond_member' in ethernet: + update_bond_options(conf, ethernet) + + tmp = is_node_changed(conf, base + [ifname, 'speed']) + if tmp: ethernet.update({'speed_duplex_changed': {}}) + + tmp = is_node_changed(conf, base + [ifname, 'duplex']) + if tmp: ethernet.update({'speed_duplex_changed': {}}) + + return ethernet + +def verify_speed_duplex(ethernet: dict, ethtool: Ethtool): + """ + Verify speed and duplex + :param ethernet: dictionary which is received from get_interface_dict + :type ethernet: dict + :param ethtool: Ethernet object + :type ethtool: Ethtool + """ + if ((ethernet['speed'] == 'auto' and ethernet['duplex'] != 'auto') or + (ethernet['speed'] != 'auto' and ethernet['duplex'] == 'auto')): + raise ConfigError( + 'Speed/Duplex missmatch. Must be both auto or manually configured') + + if ethernet['speed'] != 'auto' and ethernet['duplex'] != 'auto': + # We need to verify if the requested speed and duplex setting is + # supported by the underlaying NIC. + speed = ethernet['speed'] + duplex = ethernet['duplex'] + if not ethtool.check_speed_duplex(speed, duplex): + raise ConfigError( + f'Adapter does not support changing speed ' \ + f'and duplex settings to: {speed}/{duplex}!') + + +def verify_flow_control(ethernet: dict, ethtool: Ethtool): + """ + Verify flow control + :param ethernet: dictionary which is received from get_interface_dict + :type ethernet: dict + :param ethtool: Ethernet object + :type ethtool: Ethtool + """ + if 'disable_flow_control' in ethernet: + if not ethtool.check_flow_control(): + raise ConfigError( + 'Adapter does not support changing flow-control settings!') + + +def verify_ring_buffer(ethernet: dict, ethtool: Ethtool): + """ + Verify ring buffer + :param ethernet: dictionary which is received from get_interface_dict + :type ethernet: dict + :param ethtool: Ethernet object + :type ethtool: Ethtool + """ + if 'ring_buffer' in ethernet: + max_rx = ethtool.get_ring_buffer_max('rx') + if not max_rx: + raise ConfigError( + 'Driver does not support RX ring-buffer configuration!') + + max_tx = ethtool.get_ring_buffer_max('tx') + if not max_tx: + raise ConfigError( + 'Driver does not support TX ring-buffer configuration!') + + rx = dict_search('ring_buffer.rx', ethernet) + if rx and int(rx) > int(max_rx): + raise ConfigError(f'Driver only supports a maximum RX ring-buffer ' \ + f'size of "{max_rx}" bytes!') + + tx = dict_search('ring_buffer.tx', ethernet) + if tx and int(tx) > int(max_tx): + raise ConfigError(f'Driver only supports a maximum TX ring-buffer ' \ + f'size of "{max_tx}" bytes!') + + +def verify_offload(ethernet: dict, ethtool: Ethtool): + """ + Verify offloading capabilities + :param ethernet: dictionary which is received from get_interface_dict + :type ethernet: dict + :param ethtool: Ethernet object + :type ethtool: Ethtool + """ + if dict_search('offload.rps', ethernet) != None: + if not os.path.exists(f'/sys/class/net/{ethernet["ifname"]}/queues/rx-0/rps_cpus'): + raise ConfigError('Interface does not suport RPS!') + driver = ethtool.get_driver_name() + # T3342 - Xen driver requires special treatment + if driver == 'vif': + if int(ethernet['mtu']) > 1500 and dict_search('offload.sg', ethernet) == None: + raise ConfigError('Xen netback drivers requires scatter-gatter offloading '\ + 'for MTU size larger then 1500 bytes') + + +def verify_allowedbond_changes(ethernet: dict): + """ + Verify changed options if interface is in bonding + :param ethernet: dictionary which is received from get_interface_dict + :type ethernet: dict + """ + if 'bond_blocked_changes' in ethernet: + for option in ethernet['bond_blocked_changes']: + raise ConfigError(f'Cannot configure "{option.replace(".", " ")}"' \ + f' on interface "{ethernet["ifname"]}".' \ + f' Interface is a bond member') + +def verify(ethernet): + if 'deleted' in ethernet: + return None + if 'is_bond_member' in ethernet: + verify_bond_member(ethernet) + else: + verify_ethernet(ethernet) + + +def verify_bond_member(ethernet): + """ + Verification function for ethernet interface which is in bonding + :param ethernet: dictionary which is received from get_interface_dict + :type ethernet: dict + """ + ifname = ethernet['ifname'] + verify_interface_exists(ethernet, ifname) + verify_eapol(ethernet) + verify_mirror_redirect(ethernet) + ethtool = Ethtool(ifname) + verify_speed_duplex(ethernet, ethtool) + verify_flow_control(ethernet, ethtool) + verify_ring_buffer(ethernet, ethtool) + verify_offload(ethernet, ethtool) + verify_allowedbond_changes(ethernet) + +def verify_ethernet(ethernet): + """ + Verification function for simple ethernet interface + :param ethernet: dictionary which is received from get_interface_dict + :type ethernet: dict + """ + ifname = ethernet['ifname'] + verify_interface_exists(ethernet, ifname) + verify_mtu(ethernet) + verify_mtu_ipv6(ethernet) + verify_dhcpv6(ethernet) + verify_address(ethernet) + verify_vrf(ethernet) + verify_bond_bridge_member(ethernet) + verify_eapol(ethernet) + verify_mirror_redirect(ethernet) + ethtool = Ethtool(ifname) + # No need to check speed and duplex keys as both have default values. + verify_speed_duplex(ethernet, ethtool) + verify_flow_control(ethernet, ethtool) + verify_ring_buffer(ethernet, ethtool) + verify_offload(ethernet, ethtool) + # use common function to verify VLAN configuration + verify_vlan_config(ethernet) + return None + +def generate(ethernet): + if 'deleted' in ethernet: + return None + + ethernet['frr_zebra_config'] = '' + if 'deleted' not in ethernet: + ethernet['frr_zebra_config'] = render_to_string('frr/evpn.mh.frr.j2', ethernet) + + return None + +def apply(ethernet): + ifname = ethernet['ifname'] + + e = EthernetIf(ifname) + if 'deleted' in ethernet: + # delete interface + e.remove() + else: + e.update(ethernet) + + zebra_daemon = 'zebra' + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + # The route-map used for the FIB (zebra) is part of the zebra daemon + frr_cfg.load_configuration(zebra_daemon) + frr_cfg.modify_section(f'^interface {ifname}', stop_pattern='^exit', remove_stop_mark=True) + if 'frr_zebra_config' in ethernet: + frr_cfg.add_before(frr.default_add_before, ethernet['frr_zebra_config']) + frr_cfg.commit_configuration(zebra_daemon) + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_geneve.py b/src/conf_mode/interfaces_geneve.py new file mode 100644 index 0000000..007708d --- /dev/null +++ b/src/conf_mode/interfaces_geneve.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configverify import verify_address +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_bond_bridge_member +from vyos.configverify import verify_vrf +from vyos.ifconfig import GeneveIf +from vyos.utils.network import interface_exists +from vyos import ConfigError + +from vyos import airbag +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'geneve'] + ifname, geneve = get_interface_dict(conf, base) + + # GENEVE interfaces are picky and require recreation if certain parameters + # change. But a GENEVE interface should - of course - not be re-created if + # it's description or IP address is adjusted. Feels somehow logic doesn't it? + for cli_option in ['remote', 'vni', 'parameters']: + if is_node_changed(conf, base + [ifname, cli_option]): + geneve.update({'rebuild_required': {}}) + + return geneve + +def verify(geneve): + if 'deleted' in geneve: + verify_bridge_delete(geneve) + return None + + verify_mtu_ipv6(geneve) + verify_address(geneve) + verify_vrf(geneve) + verify_bond_bridge_member(geneve) + verify_mirror_redirect(geneve) + + if 'remote' not in geneve: + raise ConfigError('Remote side must be configured') + + if 'vni' not in geneve: + raise ConfigError('VNI must be configured') + + return None + + +def generate(geneve): + return None + +def apply(geneve): + # Check if GENEVE interface already exists + if 'rebuild_required' in geneve or 'delete' in geneve: + if interface_exists(geneve['ifname']): + g = GeneveIf(**geneve) + # GENEVE is super picky and the tunnel always needs to be recreated, + # thus we can simply always delete it first. + g.remove() + + if 'deleted' not in geneve: + # Finally create the new interface + g = GeneveIf(**geneve) + g.update(geneve) + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_input.py b/src/conf_mode/interfaces_input.py new file mode 100644 index 0000000..ad24884 --- /dev/null +++ b/src/conf_mode/interfaces_input.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_mirror_redirect +from vyos.ifconfig import InputIf +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at + least the interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'input'] + _, ifb = get_interface_dict(conf, base) + + return ifb + +def verify(ifb): + if 'deleted' in ifb: + return None + + verify_mirror_redirect(ifb) + return None + +def generate(ifb): + return None + +def apply(ifb): + d = InputIf(ifb['ifname']) + + # Remove input interface + if 'deleted' in ifb: + d.remove() + else: + d.update(ifb) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_l2tpv3.py b/src/conf_mode/interfaces_l2tpv3.py new file mode 100644 index 0000000..f0a7043 --- /dev/null +++ b/src/conf_mode/interfaces_l2tpv3.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_node_changed +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_bond_bridge_member +from vyos.configverify import verify_vrf +from vyos.ifconfig import L2TPv3If +from vyos.utils.kernel import check_kmod +from vyos.utils.network import is_addr_assigned +from vyos.utils.network import interface_exists +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +k_mod = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6'] + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'l2tpv3'] + ifname, l2tpv3 = get_interface_dict(conf, base) + + # To delete an l2tpv3 interface we need the current tunnel and session-id + if 'deleted' in l2tpv3: + tmp = leaf_node_changed(conf, base + [ifname, 'tunnel-id']) + # leaf_node_changed() returns a list + l2tpv3.update({'tunnel_id': tmp[0]}) + + tmp = leaf_node_changed(conf, base + [ifname, 'session-id']) + l2tpv3.update({'session_id': tmp[0]}) + + return l2tpv3 + +def verify(l2tpv3): + if 'deleted' in l2tpv3: + verify_bridge_delete(l2tpv3) + return None + + interface = l2tpv3['ifname'] + + for key in ['source_address', 'remote', 'tunnel_id', 'peer_tunnel_id', + 'session_id', 'peer_session_id']: + if key not in l2tpv3: + tmp = key.replace('_', '-') + raise ConfigError(f'Missing mandatory L2TPv3 option: "{tmp}"!') + + if not is_addr_assigned(l2tpv3['source_address']): + raise ConfigError('L2TPv3 source-address address "{source_address}" ' + 'not configured on any interface!'.format(**l2tpv3)) + + verify_mtu_ipv6(l2tpv3) + verify_address(l2tpv3) + verify_vrf(l2tpv3) + verify_bond_bridge_member(l2tpv3) + verify_mirror_redirect(l2tpv3) + return None + +def generate(l2tpv3): + return None + +def apply(l2tpv3): + check_kmod(k_mod) + + # Check if L2TPv3 interface already exists + if interface_exists(l2tpv3['ifname']): + # L2TPv3 is picky when changing tunnels/sessions, thus we can simply + # always delete it first. + l = L2TPv3If(**l2tpv3) + l.remove() + + if 'deleted' not in l2tpv3: + # Finally create the new interface + l = L2TPv3If(**l2tpv3) + l.update(l2tpv3) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_loopback.py b/src/conf_mode/interfaces_loopback.py new file mode 100644 index 0000000..a784e9e --- /dev/null +++ b/src/conf_mode/interfaces_loopback.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_mirror_redirect +from vyos.ifconfig import LoopbackIf +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'loopback'] + _, loopback = get_interface_dict(conf, base) + return loopback + +def verify(loopback): + verify_mirror_redirect(loopback) + return None + +def generate(loopback): + return None + +def apply(loopback): + l = LoopbackIf(**loopback) + if 'deleted' in loopback: + l.remove() + else: + l.update(loopback) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_macsec.py b/src/conf_mode/interfaces_macsec.py new file mode 100644 index 0000000..3ede437 --- /dev/null +++ b/src/conf_mode/interfaces_macsec.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configdict import is_source_interface +from vyos.configverify import verify_vrf +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_bond_bridge_member +from vyos.ifconfig import MACsecIf +from vyos.ifconfig import Interface +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.utils.network import interface_exists +from vyos.utils.process import is_systemd_service_running +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +# XXX: wpa_supplicant works on the source interface +wpa_suppl_conf = '/run/wpa_supplicant/{source_interface}.conf' + +# Constants +## gcm-aes-128 requires a 128bit long key - 32 characters (string) = 16byte = 128bit +GCM_AES_128_LEN: int = 32 +GCM_128_KEY_ERROR = 'gcm-aes-128 requires a 128bit long key!' +## gcm-aes-256 requires a 256bit long key - 64 characters (string) = 32byte = 256bit +GCM_AES_256_LEN: int = 64 +GCM_256_KEY_ERROR = 'gcm-aes-256 requires a 256bit long key!' + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'macsec'] + ifname, macsec = get_interface_dict(conf, base) + + # Check if interface has been removed + if 'deleted' in macsec: + source_interface = conf.return_effective_value(base + [ifname, 'source-interface']) + macsec.update({'source_interface': source_interface}) + + if is_node_changed(conf, base + [ifname, 'security']): + macsec.update({'shutdown_required': {}}) + + if is_node_changed(conf, base + [ifname, 'source_interface']): + macsec.update({'shutdown_required': {}}) + + if 'source_interface' in macsec: + tmp = is_source_interface(conf, macsec['source_interface'], ['macsec', 'pseudo-ethernet']) + if tmp and tmp != ifname: macsec.update({'is_source_interface' : tmp}) + + return macsec + + +def verify(macsec): + if 'deleted' in macsec: + verify_bridge_delete(macsec) + return None + + verify_source_interface(macsec) + verify_vrf(macsec) + verify_mtu_ipv6(macsec) + verify_address(macsec) + verify_bond_bridge_member(macsec) + verify_mirror_redirect(macsec) + + if dict_search('security.cipher', macsec) == None: + raise ConfigError('Cipher suite must be set for MACsec "{ifname}"'.format(**macsec)) + + if dict_search('security.encrypt', macsec) != None: + # Check that only static or MKA config is present + if dict_search('security.static', macsec) != None and (dict_search('security.mka.cak', macsec) != None or dict_search('security.mka.ckn', macsec) != None): + raise ConfigError('Only static or MKA can be used!') + + # Logic to check static configuration + if dict_search('security.static', macsec) != None: + # key must be defined + if dict_search('security.static.key', macsec) == None: + raise ConfigError('Static MACsec key must be defined.') + + tx_len = len(dict_search('security.static.key', macsec)) + + if dict_search('security.cipher', macsec) == 'gcm-aes-128' and tx_len != GCM_AES_128_LEN: + raise ConfigError(GCM_128_KEY_ERROR) + + if dict_search('security.cipher', macsec) == 'gcm-aes-256' and tx_len != GCM_AES_256_LEN: + raise ConfigError(GCM_256_KEY_ERROR) + + # Make sure at least one peer is defined + if 'peer' not in macsec['security']['static']: + raise ConfigError('Must have at least one peer defined for static MACsec') + + # For every enabled peer, make sure a MAC and key is defined + for peer, peer_config in macsec['security']['static']['peer'].items(): + if 'disable' not in peer_config and ('mac' not in peer_config or 'key' not in peer_config): + raise ConfigError('Every enabled MACsec static peer must have a MAC address and key defined!') + + # check key length against cipher suite + rx_len = len(peer_config['key']) + + if dict_search('security.cipher', macsec) == 'gcm-aes-128' and rx_len != GCM_AES_128_LEN: + raise ConfigError(GCM_128_KEY_ERROR) + + if dict_search('security.cipher', macsec) == 'gcm-aes-256' and rx_len != GCM_AES_256_LEN: + raise ConfigError(GCM_256_KEY_ERROR) + + # Logic to check MKA configuration + else: + if dict_search('security.mka.cak', macsec) == None or dict_search('security.mka.ckn', macsec) == None: + raise ConfigError('Missing mandatory MACsec security keys as encryption is enabled!') + + cak_len = len(dict_search('security.mka.cak', macsec)) + + if dict_search('security.cipher', macsec) == 'gcm-aes-128' and cak_len != GCM_AES_128_LEN: + raise ConfigError(GCM_128_KEY_ERROR) + + elif dict_search('security.cipher', macsec) == 'gcm-aes-256' and cak_len != GCM_AES_256_LEN: + raise ConfigError(GCM_256_KEY_ERROR) + + if 'source_interface' in macsec: + # MACsec adds a 40 byte overhead (32 byte MACsec + 8 bytes VLAN 802.1ad + # and 802.1q) - we need to check the underlaying MTU if our configured + # MTU is at least 40 bytes less then the MTU of our physical interface. + lower_mtu = Interface(macsec['source_interface']).get_mtu() + if lower_mtu < (int(macsec['mtu']) + 40): + raise ConfigError('MACsec overhead does not fit into underlaying device MTU,\n' \ + f'{lower_mtu} bytes is too small!') + + return None + + +def generate(macsec): + # Only generate wpa_supplicant config if using MKA + if dict_search('security.mka.cak', macsec): + render(wpa_suppl_conf.format(**macsec), 'macsec/wpa_supplicant.conf.j2', macsec) + return None + + +def apply(macsec): + systemd_service = 'wpa_supplicant-macsec@{source_interface}'.format(**macsec) + + # Remove macsec interface on deletion or mandatory parameter change + if 'deleted' in macsec or 'shutdown_required' in macsec: + call(f'systemctl stop {systemd_service}') + + if interface_exists(macsec['ifname']): + tmp = MACsecIf(**macsec) + tmp.remove() + + if 'deleted' in macsec: + # delete configuration on interface removal + if os.path.isfile(wpa_suppl_conf.format(**macsec)): + os.unlink(wpa_suppl_conf.format(**macsec)) + + return None + + # It is safe to "re-create" the interface always, there is a sanity + # check that the interface will only be create if its non existent + i = MACsecIf(**macsec) + i.update(macsec) + + # Only reload/restart if using MKA + if dict_search('security.mka.cak', macsec): + if not is_systemd_service_running(systemd_service) or 'shutdown_required' in macsec: + call(f'systemctl reload-or-restart {systemd_service}') + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_openvpn.py b/src/conf_mode/interfaces_openvpn.py new file mode 100644 index 0000000..8c1213e --- /dev/null +++ b/src/conf_mode/interfaces_openvpn.py @@ -0,0 +1,808 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import re + +from cryptography.hazmat.primitives.asymmetric import ec +from glob import glob +from sys import exit +from ipaddress import IPv4Address +from ipaddress import IPv4Network +from ipaddress import IPv6Address +from ipaddress import IPv6Network +from ipaddress import summarize_address_range +from secrets import SystemRandom +from shutil import rmtree + +from vyos.base import DeprecationWarning +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configverify import verify_vrf +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_bond_bridge_member +from vyos.ifconfig import VTunIf +from vyos.pki import load_dh_parameters +from vyos.pki import load_private_key +from vyos.pki import sort_ca_chain +from vyos.pki import verify_ca_chain +from vyos.pki import wrap_certificate +from vyos.pki import wrap_crl +from vyos.pki import wrap_dh_parameters +from vyos.pki import wrap_openvpn_key +from vyos.pki import wrap_private_key +from vyos.template import render +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_args +from vyos.utils.list import is_list_equal +from vyos.utils.file import makedir +from vyos.utils.file import read_file +from vyos.utils.file import write_file +from vyos.utils.kernel import check_kmod +from vyos.utils.kernel import unload_kmod +from vyos.utils.process import call +from vyos.utils.permission import chown +from vyos.utils.process import cmd +from vyos.utils.network import is_addr_assigned +from vyos.utils.network import interface_exists + +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +user = 'openvpn' +group = 'openvpn' + +cfg_dir = '/run/openvpn' +cfg_file = '/run/openvpn/{ifname}.conf' +otp_path = '/config/auth/openvpn' +otp_file = '/config/auth/openvpn/{ifname}-otp-secrets' +secret_chars = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567') +service_file = '/run/systemd/system/openvpn@{ifname}.service.d/20-override.conf' + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'openvpn'] + + ifname, openvpn = get_interface_dict(conf, base, with_pki=True) + openvpn['auth_user_pass_file'] = '/run/openvpn/{ifname}.pw'.format(**openvpn) + + if 'deleted' in openvpn: + return openvpn + + if is_node_changed(conf, base + [ifname, 'openvpn-option']): + openvpn.update({'restart_required': {}}) + if is_node_changed(conf, base + [ifname, 'enable-dco']): + openvpn.update({'restart_required': {}}) + + # We have to get the dict using 'get_config_dict' instead of 'get_interface_dict' + # as 'get_interface_dict' merges the defaults in, so we can not check for defaults in there. + tmp = conf.get_config_dict(base + [openvpn['ifname']], get_first_key=True) + + # We have to cleanup the config dict, as default values could enable features + # which are not explicitly enabled on the CLI. Example: server mfa totp + # originate comes with defaults, which will enable the + # totp plugin, even when not set via CLI so we + # need to check this first and drop those keys + if dict_search('server.mfa.totp', tmp) == None: + del openvpn['server']['mfa'] + + # OpenVPN Data-Channel-Offload (DCO) is a Kernel module. If loaded it applies to all + # OpenVPN interfaces. Check if DCO is used by any other interface instance. + tmp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + for interface, interface_config in tmp.items(): + # If one interface has DCO configured, enable it. No need to further check + # all other OpenVPN interfaces. We must use a dedicated key to indicate + # the Kernel module must be loaded or not. The per interface "offload.dco" + # key is required per OpenVPN interface instance. + if dict_search('offload.dco', interface_config) != None: + openvpn['module_load_dco'] = {} + break + + # Calculate the protocol modifier. This is concatenated to the protocol string to direct + # OpenVPN to use a specific IP protocol version. If unspecified, the kernel decides which + # type of socket to open. In server mode, an additional "ipv6-dual-stack" option forces + # binding the socket in IPv6 mode, which can also receive IPv4 traffic (when using the + # default "ipv6" mode, we specify "bind ipv6only" to disable kernel dual-stack behaviors). + if openvpn['ip_version'] == 'ipv4': + openvpn['protocol_modifier'] = '4' + elif openvpn['ip_version'] in ['ipv6', 'dual-stack']: + openvpn['protocol_modifier'] = '6' + else: + openvpn['protocol_modifier'] = '' + + return openvpn + +def is_ec_private_key(pki, cert_name): + if not pki or 'certificate' not in pki: + return False + if cert_name not in pki['certificate']: + return False + + pki_cert = pki['certificate'][cert_name] + if 'private' not in pki_cert or 'key' not in pki_cert['private']: + return False + + key = load_private_key(pki_cert['private']['key']) + return isinstance(key, ec.EllipticCurvePrivateKey) + +def verify_pki(openvpn): + pki = openvpn['pki'] + interface = openvpn['ifname'] + mode = openvpn['mode'] + shared_secret_key = dict_search_args(openvpn, 'shared_secret_key') + tls = dict_search_args(openvpn, 'tls') + + if not bool(shared_secret_key) ^ bool(tls): # xor check if only one is set + raise ConfigError('Must specify only one of "shared-secret-key" and "tls"') + + if mode in ['server', 'client'] and not tls: + raise ConfigError('Must specify "tls" for server and client modes') + + if not pki: + raise ConfigError('PKI is not configured') + + if shared_secret_key: + if not dict_search_args(pki, 'openvpn', 'shared_secret'): + raise ConfigError('There are no openvpn shared-secrets in PKI configuration') + + if shared_secret_key not in pki['openvpn']['shared_secret']: + raise ConfigError(f'Invalid shared-secret on openvpn interface {interface}') + + # If PSK settings are correct, warn about its deprecation + DeprecationWarning('OpenVPN shared-secret support will be removed in future '\ + 'VyOS versions. Please migrate your site-to-site tunnels to '\ + 'TLS. You can use self-signed certificates with peer fingerprint '\ + 'verification, consult the documentation for details.') + + if tls: + if mode == 'site-to-site': + # XXX: site-to-site with PSKs is the only mode that can work without TLS, + # so 'tls role' is not mandatory for it, + # but we need to check that if it uses peer certificate fingerprints rather than PSKs, + # then the TLS role is set + if ('shared_secret_key' not in tls) and ('role' not in tls): + raise ConfigError('"tls role" is required for site-to-site OpenVPN with TLS') + + if (mode in ['server', 'client']) and ('ca_certificate' not in tls): + raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface},\ + it is required in server and client modes') + else: + if ('ca_certificate' not in tls) and ('peer_fingerprint' not in tls): + raise ConfigError('Either "tls ca-certificate" or "tls peer-fingerprint" is required\ + on openvpn interface {interface} in site-to-site mode') + + if 'ca_certificate' in tls: + for ca_name in tls['ca_certificate']: + if ca_name not in pki['ca']: + raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}') + + if len(tls['ca_certificate']) > 1: + sorted_chain = sort_ca_chain(tls['ca_certificate'], pki['ca']) + if not verify_ca_chain(sorted_chain, pki['ca']): + raise ConfigError(f'CA certificates are not a valid chain') + + if mode != 'client' and 'auth_key' not in tls: + if 'certificate' not in tls: + raise ConfigError(f'Missing "tls certificate" on openvpn interface {interface}') + + if 'certificate' in tls: + if tls['certificate'] not in pki['certificate']: + raise ConfigError(f'Invalid certificate on openvpn interface {interface}') + + if dict_search_args(pki, 'certificate', tls['certificate'], 'private', 'password_protected') is not None: + raise ConfigError(f'Cannot use encrypted private key on openvpn interface {interface}') + + if 'dh_params' in tls: + if 'dh' not in pki: + raise ConfigError(f'pki dh is not configured') + proposed_dh = tls['dh_params'] + if proposed_dh not in pki['dh'].keys(): + raise ConfigError(f"pki dh '{proposed_dh}' is not configured") + + pki_dh = pki['dh'][tls['dh_params']] + dh_params = load_dh_parameters(pki_dh['parameters']) + dh_numbers = dh_params.parameter_numbers() + dh_bits = dh_numbers.p.bit_length() + + if dh_bits < 2048: + raise ConfigError(f'Minimum DH key-size is 2048 bits') + + + if 'auth_key' in tls or 'crypt_key' in tls: + if not dict_search_args(pki, 'openvpn', 'shared_secret'): + raise ConfigError('There are no openvpn shared-secrets in PKI configuration') + + if 'auth_key' in tls: + if tls['auth_key'] not in pki['openvpn']['shared_secret']: + raise ConfigError(f'Invalid auth-key on openvpn interface {interface}') + + if 'crypt_key' in tls: + if tls['crypt_key'] not in pki['openvpn']['shared_secret']: + raise ConfigError(f'Invalid crypt-key on openvpn interface {interface}') + +def verify(openvpn): + if 'deleted' in openvpn: + verify_bridge_delete(openvpn) + return None + + if 'mode' not in openvpn: + raise ConfigError('Must specify OpenVPN operation mode!') + + # + # OpenVPN client mode - VERIFY + # + if openvpn['mode'] == 'client': + if 'local_port' in openvpn: + raise ConfigError('Cannot specify "local-port" in client mode') + + if 'local_host' in openvpn: + raise ConfigError('Cannot specify "local-host" in client mode') + + if 'remote_host' not in openvpn: + raise ConfigError('Must specify "remote-host" in client mode') + + if openvpn['protocol'] == 'tcp-passive': + raise ConfigError('Protocol "tcp-passive" is not valid in client mode') + + if 'ip_version' in openvpn and openvpn['ip_version'] == 'dual-stack': + raise ConfigError('"ip-version dual-stack" is not supported in client mode') + + if dict_search('tls.dh_params', openvpn): + raise ConfigError('Cannot specify "tls dh-params" in client mode') + + # + # OpenVPN site-to-site - VERIFY + # + elif openvpn['mode'] == 'site-to-site': + if 'ip_version' in openvpn and openvpn['ip_version'] == 'dual-stack': + raise ConfigError('"ip-version dual-stack" is not supported in site-to-site mode') + + if 'local_address' not in openvpn and 'is_bridge_member' not in openvpn: + raise ConfigError('Must specify "local-address" or add interface to bridge') + + if 'local_address' in openvpn: + if len([addr for addr in openvpn['local_address'] if is_ipv4(addr)]) > 1: + raise ConfigError('Only one IPv4 local-address can be specified') + + if len([addr for addr in openvpn['local_address'] if is_ipv6(addr)]) > 1: + raise ConfigError('Only one IPv6 local-address can be specified') + + if openvpn['device_type'] == 'tun': + if 'remote_address' not in openvpn: + raise ConfigError('Must specify "remote-address"') + + if 'remote_address' in openvpn: + if len([addr for addr in openvpn['remote_address'] if is_ipv4(addr)]) > 1: + raise ConfigError('Only one IPv4 remote-address can be specified') + + if len([addr for addr in openvpn['remote_address'] if is_ipv6(addr)]) > 1: + raise ConfigError('Only one IPv6 remote-address can be specified') + + if not 'local_address' in openvpn: + raise ConfigError('"remote-address" requires "local-address"') + + v4loAddr = [addr for addr in openvpn['local_address'] if is_ipv4(addr)] + v4remAddr = [addr for addr in openvpn['remote_address'] if is_ipv4(addr)] + if v4loAddr and not v4remAddr: + raise ConfigError('IPv4 "local-address" requires IPv4 "remote-address"') + elif v4remAddr and not v4loAddr: + raise ConfigError('IPv4 "remote-address" requires IPv4 "local-address"') + + v6remAddr = [addr for addr in openvpn['remote_address'] if is_ipv6(addr)] + v6loAddr = [addr for addr in openvpn['local_address'] if is_ipv6(addr)] + if v6loAddr and not v6remAddr: + raise ConfigError('IPv6 "local-address" requires IPv6 "remote-address"') + elif v6remAddr and not v6loAddr: + raise ConfigError('IPv6 "remote-address" requires IPv6 "local-address"') + + if is_list_equal(v4loAddr, v4remAddr) or is_list_equal(v6loAddr, v6remAddr): + raise ConfigError('"local-address" and "remote-address" cannot be the same') + + if dict_search('local_host', openvpn) in dict_search('local_address', openvpn): + raise ConfigError('"local-address" cannot be the same as "local-host"') + + if dict_search('remote_host', openvpn) in dict_search('remote_address', openvpn): + raise ConfigError('"remote-address" and "remote-host" can not be the same') + + if openvpn['device_type'] == 'tap' and 'local_address' in openvpn: + # we can only have one local_address, this is ensured above + v4addr = None + for laddr in openvpn['local_address']: + if is_ipv4(laddr): + v4addr = laddr + break + + if v4addr in openvpn['local_address'] and 'subnet_mask' not in openvpn['local_address'][v4addr]: + raise ConfigError('Must specify IPv4 "subnet-mask" for local-address') + + if dict_search('encryption.data_ciphers', openvpn): + raise ConfigError('Cipher negotiation can only be used in client or server mode') + + else: + # checks for client-server or site-to-site bridged + if 'local_address' in openvpn or 'remote_address' in openvpn: + raise ConfigError('Cannot specify "local-address" or "remote-address" ' \ + 'in client/server or bridge mode') + + # + # OpenVPN server mode - VERIFY + # + if openvpn['mode'] == 'server': + if openvpn['protocol'] == 'tcp-active': + raise ConfigError('Protocol "tcp-active" is not valid in server mode') + + if dict_search('authentication.username', openvpn) or dict_search('authentication.password', openvpn): + raise ConfigError('Cannot specify "authentication" in server mode') + + if 'remote_port' in openvpn: + raise ConfigError('Cannot specify "remote-port" in server mode') + + if 'remote_host' in openvpn: + raise ConfigError('Cannot specify "remote-host" in server mode') + + tmp = dict_search('server.subnet', openvpn) + if tmp: + v4_subnets = len([subnet for subnet in tmp if is_ipv4(subnet)]) + v6_subnets = len([subnet for subnet in tmp if is_ipv6(subnet)]) + if v4_subnets > 1: + raise ConfigError('Cannot specify more than 1 IPv4 server subnet') + if v6_subnets > 1: + raise ConfigError('Cannot specify more than 1 IPv6 server subnet') + + for subnet in tmp: + if is_ipv4(subnet): + subnet = IPv4Network(subnet) + + if openvpn['device_type'] == 'tun' and subnet.prefixlen > 29: + raise ConfigError('Server subnets smaller than /29 with device type "tun" are not supported') + elif openvpn['device_type'] == 'tap' and subnet.prefixlen > 30: + raise ConfigError('Server subnets smaller than /30 with device type "tap" are not supported') + + for client in (dict_search('client', openvpn) or []): + if client['ip'] and not IPv4Address(client['ip'][0]) in subnet: + raise ConfigError(f'Client "{client["name"]}" IP {client["ip"][0]} not in server subnet {subnet}') + + else: + if 'is_bridge_member' not in openvpn: + raise ConfigError('Must specify "server subnet" or add interface to bridge in server mode') + + if hasattr(dict_search('server.client', openvpn), '__iter__'): + for client_k, client_v in dict_search('server.client', openvpn).items(): + if (client_v.get('ip') and len(client_v['ip']) > 1) or (client_v.get('ipv6_ip') and len(client_v['ipv6_ip']) > 1): + raise ConfigError(f'Server client "{client_k}": cannot specify more than 1 IPv4 and 1 IPv6 IP') + + if dict_search('server.bridge', openvpn): + # check if server bridge is a tap interfaces + if not openvpn['device_type'] == 'tap' and dict_search('server.bridge', openvpn): + raise ConfigError('Must specify "device-type tap" with server bridge mode') + elif not (dict_search('server.bridge.start', openvpn) and dict_search('server.bridge.stop', openvpn)): + raise ConfigError('Server bridge requires both start and stop addresses') + else: + v4PoolStart = IPv4Address(dict_search('server.bridge.start', openvpn)) + v4PoolStop = IPv4Address(dict_search('server.bridge.stop', openvpn)) + if v4PoolStart > v4PoolStop: + raise ConfigError(f'Server bridge start address {v4PoolStart} is larger than stop address {v4PoolStop}') + + v4PoolSize = int(v4PoolStop) - int(v4PoolStart) + if v4PoolSize >= 65536: + raise ConfigError(f'Server bridge is too large [{v4PoolStart} -> {v4PoolStop} = {v4PoolSize}], maximum is 65536 addresses.') + + if dict_search('server.client_ip_pool', openvpn): + if not (dict_search('server.client_ip_pool.start', openvpn) and dict_search('server.client_ip_pool.stop', openvpn)): + raise ConfigError('Server client-ip-pool requires both start and stop addresses') + else: + v4PoolStart = IPv4Address(dict_search('server.client_ip_pool.start', openvpn)) + v4PoolStop = IPv4Address(dict_search('server.client_ip_pool.stop', openvpn)) + if v4PoolStart > v4PoolStop: + raise ConfigError(f'Server client-ip-pool start address {v4PoolStart} is larger than stop address {v4PoolStop}') + + v4PoolSize = int(v4PoolStop) - int(v4PoolStart) + if v4PoolSize >= 65536: + raise ConfigError(f'Server client-ip-pool is too large [{v4PoolStart} -> {v4PoolStop} = {v4PoolSize}], maximum is 65536 addresses.') + + v4PoolNets = list(summarize_address_range(v4PoolStart, v4PoolStop)) + for client in (dict_search('client', openvpn) or []): + if client['ip']: + for v4PoolNet in v4PoolNets: + if IPv4Address(client['ip'][0]) in v4PoolNet: + print(f'Warning: Client "{client["name"]}" IP {client["ip"][0]} is in server IP pool, it is not reserved for this client.') + # configuring a client_ip_pool will set 'server ... nopool' which is currently incompatible with 'server-ipv6' (probably to be fixed upstream) + for subnet in (dict_search('server.subnet', openvpn) or []): + if is_ipv6(subnet): + raise ConfigError(f'Setting client-ip-pool is incompatible having an IPv6 server subnet.') + + for subnet in (dict_search('server.subnet', openvpn) or []): + if is_ipv6(subnet): + tmp = dict_search('client_ipv6_pool.base', openvpn) + if tmp: + if not dict_search('server.client_ip_pool', openvpn): + raise ConfigError('IPv6 server pool requires an IPv4 server pool') + + if int(tmp.split('/')[1]) >= 112: + raise ConfigError('IPv6 server pool must be larger than /112') + + # + # todo - weird logic + # + v6PoolStart = IPv6Address(tmp) + v6PoolStop = IPv6Network((v6PoolStart, openvpn['server_ipv6_pool_prefixlen']), strict=False)[-1] # don't remove the parentheses, it's a 2-tuple + v6PoolSize = int(v6PoolStop) - int(v6PoolStart) if int(openvpn['server_ipv6_pool_prefixlen']) > 96 else 65536 + if v6PoolSize < v4PoolSize: + raise ConfigError(f'IPv6 server pool must be at least as large as the IPv4 pool (current sizes: IPv6={v6PoolSize} IPv4={v4PoolSize})') + + v6PoolNets = list(summarize_address_range(v6PoolStart, v6PoolStop)) + for client in (dict_search('client', openvpn) or []): + if client['ipv6_ip']: + for v6PoolNet in v6PoolNets: + if IPv6Address(client['ipv6_ip'][0]) in v6PoolNet: + print(f'Warning: Client "{client["name"]}" IP {client["ipv6_ip"][0]} is in server IP pool, it is not reserved for this client.') + + if 'topology' in openvpn['server']: + if openvpn['server']['topology'] == 'net30': + DeprecationWarning('Topology net30 is deprecated '\ + 'and will be removed in future VyOS versions. '\ + 'Switch to "subnet" or "p2p"' + ) + + # add mfa users to the file the mfa plugin uses + if dict_search('server.mfa.totp', openvpn): + user_data = '' + if not os.path.isfile(otp_file.format(**openvpn)): + write_file(otp_file.format(**openvpn), user_data, + user=user, group=group, mode=0o644) + + ovpn_users = read_file(otp_file.format(**openvpn)) + for client in (dict_search('server.client', openvpn) or []): + exists = None + for ovpn_user in ovpn_users.split('\n'): + if re.search('^' + client + ' ', ovpn_user): + user_data += f'{ovpn_user}\n' + exists = 'true' + + if not exists: + random = SystemRandom() + totp_secret = ''.join(random.choice(secret_chars) for _ in range(16)) + user_data += f'{client} otp totp:sha1:base32:{totp_secret}::xxx *\n' + + write_file(otp_file.format(**openvpn), user_data, + user=user, group=group, mode=0o644) + + else: + # checks for both client and site-to-site go here + if dict_search('server.reject_unconfigured_clients', openvpn): + raise ConfigError('Option reject-unconfigured-clients only supported in server mode') + + if 'replace_default_route' in openvpn and 'remote_host' not in openvpn: + raise ConfigError('Cannot set "replace-default-route" without "remote-host"') + + # + # OpenVPN common verification section + # not depending on any operation mode + # + + # verify that local_host/remote_host match with any ip_version override + # specified (if a dns name is specified for remote_host, no attempt is made + # to verify that record resolves to an address of the configured family) + if 'local_host' in openvpn: + if openvpn['ip_version'] == 'ipv4' and is_ipv6(openvpn['local_host']): + raise ConfigError('Cannot use an IPv6 "local-host" with "ip-version ipv4"') + elif openvpn['ip_version'] == 'ipv6' and is_ipv4(openvpn['local_host']): + raise ConfigError('Cannot use an IPv4 "local-host" with "ip-version ipv6"') + elif openvpn['ip_version'] == 'dual-stack': + raise ConfigError('Cannot use "local-host" with "ip-version dual-stack". "dual-stack" is only supported when OpenVPN binds to all available interfaces.') + + if 'remote_host' in openvpn: + remote_hosts = dict_search('remote_host', openvpn) + for remote_host in remote_hosts: + if openvpn['ip_version'] == 'ipv4' and is_ipv6(remote_host): + raise ConfigError('Cannot use an IPv6 "remote-host" with "ip-version ipv4"') + elif openvpn['ip_version'] == 'ipv6' and is_ipv4(remote_host): + raise ConfigError('Cannot use an IPv4 "remote-host" with "ip-version ipv6"') + + # verify specified IP address is present on any interface on this system + if 'local_host' in openvpn: + if not is_addr_assigned(openvpn['local_host']): + print('local-host IP address "{local_host}" not assigned' \ + ' to any interface'.format(**openvpn)) + + # TCP active + if openvpn['protocol'] == 'tcp-active': + if 'local_port' in openvpn: + raise ConfigError('Cannot specify "local-port" with "tcp-active"') + + if 'remote_host' not in openvpn: + raise ConfigError('Must specify "remote-host" with "tcp-active"') + + # + # TLS/encryption + # + if 'shared_secret_key' in openvpn: + if dict_search('encryption.cipher', openvpn) in ['aes128gcm', 'aes192gcm', 'aes256gcm']: + raise ConfigError('GCM encryption with shared-secret-key not supported') + + if 'tls' in openvpn: + if {'auth_key', 'crypt_key'} <= set(openvpn['tls']): + raise ConfigError('TLS auth and crypt keys are mutually exclusive') + + tmp = dict_search('tls.role', openvpn) + if tmp: + if openvpn['mode'] in ['client', 'server']: + if not dict_search('tls.auth_key', openvpn): + raise ConfigError('Cannot specify "tls role" in client-server mode') + + if tmp == 'active': + if openvpn['protocol'] == 'tcp-passive': + raise ConfigError('Cannot specify "tcp-passive" when "tls role" is "active"') + + if dict_search('tls.dh_params', openvpn): + raise ConfigError('Cannot specify "tls dh-params" when "tls role" is "active"') + + elif tmp == 'passive': + if openvpn['protocol'] == 'tcp-active': + raise ConfigError('Cannot specify "tcp-active" when "tls role" is "passive"') + + if 'certificate' in openvpn['tls'] and is_ec_private_key(openvpn['pki'], openvpn['tls']['certificate']): + if 'dh_params' in openvpn['tls']: + print('Warning: using dh-params and EC keys simultaneously will ' \ + 'lead to DH ciphers being used instead of ECDH') + + if dict_search('encryption.cipher', openvpn): + raise ConfigError('"encryption cipher" option is deprecated for TLS mode. ' + 'Use "encryption data-ciphers" instead') + + if dict_search('encryption.cipher', openvpn) == 'none': + print('Warning: "encryption none" was specified!') + print('No encryption will be performed and data is transmitted in ' \ + 'plain text over the network!') + + verify_pki(openvpn) + + # + # Auth user/pass + # + if (dict_search('authentication.username', openvpn) and not + dict_search('authentication.password', openvpn)): + raise ConfigError('Password for authentication is missing') + + if (dict_search('authentication.password', openvpn) and not + dict_search('authentication.username', openvpn)): + raise ConfigError('Username for authentication is missing') + + verify_vrf(openvpn) + verify_bond_bridge_member(openvpn) + verify_mirror_redirect(openvpn) + + return None + +def generate_pki_files(openvpn): + pki = openvpn['pki'] + if not pki: + return None + + interface = openvpn['ifname'] + shared_secret_key = dict_search_args(openvpn, 'shared_secret_key') + tls = dict_search_args(openvpn, 'tls') + + if shared_secret_key: + pki_key = pki['openvpn']['shared_secret'][shared_secret_key] + key_path = os.path.join(cfg_dir, f'{interface}_shared.key') + write_file(key_path, wrap_openvpn_key(pki_key['key']), + user=user, group=group) + + if tls: + if 'ca_certificate' in tls: + cert_path = os.path.join(cfg_dir, f'{interface}_ca.pem') + crl_path = os.path.join(cfg_dir, f'{interface}_crl.pem') + + if os.path.exists(cert_path): + os.unlink(cert_path) + + if os.path.exists(crl_path): + os.unlink(crl_path) + + for cert_name in sort_ca_chain(tls['ca_certificate'], pki['ca']): + pki_ca = pki['ca'][cert_name] + + if 'certificate' in pki_ca: + write_file(cert_path, wrap_certificate(pki_ca['certificate']) + "\n", + user=user, group=group, mode=0o600, append=True) + + if 'crl' in pki_ca: + for crl in pki_ca['crl']: + write_file(crl_path, wrap_crl(crl) + "\n", user=user, group=group, + mode=0o600, append=True) + + openvpn['tls']['crl'] = True + + if 'certificate' in tls: + cert_name = tls['certificate'] + pki_cert = pki['certificate'][cert_name] + + if 'certificate' in pki_cert: + cert_path = os.path.join(cfg_dir, f'{interface}_cert.pem') + write_file(cert_path, wrap_certificate(pki_cert['certificate']), + user=user, group=group, mode=0o600) + + if 'private' in pki_cert and 'key' in pki_cert['private']: + key_path = os.path.join(cfg_dir, f'{interface}_cert.key') + write_file(key_path, wrap_private_key(pki_cert['private']['key']), + user=user, group=group, mode=0o600) + + openvpn['tls']['private_key'] = True + + if 'dh_params' in tls: + dh_name = tls['dh_params'] + pki_dh = pki['dh'][dh_name] + + if 'parameters' in pki_dh: + dh_path = os.path.join(cfg_dir, f'{interface}_dh.pem') + write_file(dh_path, wrap_dh_parameters(pki_dh['parameters']), + user=user, group=group, mode=0o600) + + if 'auth_key' in tls: + key_name = tls['auth_key'] + pki_key = pki['openvpn']['shared_secret'][key_name] + + if 'key' in pki_key: + key_path = os.path.join(cfg_dir, f'{interface}_auth.key') + write_file(key_path, wrap_openvpn_key(pki_key['key']), + user=user, group=group, mode=0o600) + + if 'crypt_key' in tls: + key_name = tls['crypt_key'] + pki_key = pki['openvpn']['shared_secret'][key_name] + + if 'key' in pki_key: + key_path = os.path.join(cfg_dir, f'{interface}_crypt.key') + write_file(key_path, wrap_openvpn_key(pki_key['key']), + user=user, group=group, mode=0o600) + + +def generate(openvpn): + if 'deleted' in openvpn: + # remove totp secrets file if totp is not configured + if os.path.isfile(otp_file.format(**openvpn)): + os.remove(otp_file.format(**openvpn)) + return None + + if 'disable' in openvpn: + return None + + interface = openvpn['ifname'] + directory = os.path.dirname(cfg_file.format(**openvpn)) + openvpn['plugin_dir'] = '/usr/lib/openvpn' + + # create base config directory on demand + makedir(directory, user, group) + # enforce proper permissions on /run/openvpn + chown(directory, user, group) + + # we can't know in advance which clients have been removed, + # thus all client configs will be removed and re-added on demand + ccd_dir = os.path.join(directory, 'ccd', interface) + if os.path.isdir(ccd_dir): + rmtree(ccd_dir, ignore_errors=True) + + # Remove systemd directories with overrides + service_dir = os.path.dirname(service_file.format(**openvpn)) + if os.path.isdir(service_dir): + rmtree(service_dir, ignore_errors=True) + + # create client config directory on demand + makedir(ccd_dir, user, group) + + # Fix file permissons for keys + generate_pki_files(openvpn) + + # Generate User/Password authentication file + if 'authentication' in openvpn: + render(openvpn['auth_user_pass_file'], 'openvpn/auth.pw.j2', openvpn, + user=user, group=group, permission=0o600) + else: + # delete old auth file if present + if os.path.isfile(openvpn['auth_user_pass_file']): + os.remove(openvpn['auth_user_pass_file']) + + # Generate client specific configuration + server_client = dict_search_args(openvpn, 'server', 'client') + if server_client: + for client, client_config in server_client.items(): + client_file = os.path.join(ccd_dir, client) + + # Our client need's to know its subnet mask ... + client_config['server_subnet'] = dict_search('server.subnet', openvpn) + + render(client_file, 'openvpn/client.conf.j2', client_config, + user=user, group=group) + + # we need to support quoting of raw parameters from OpenVPN CLI + # see https://vyos.dev/T1632 + render(cfg_file.format(**openvpn), 'openvpn/server.conf.j2', openvpn, + formater=lambda _: _.replace(""", '"'), user=user, group=group) + + # Render 20-override.conf for OpenVPN service + render(service_file.format(**openvpn), 'openvpn/service-override.conf.j2', openvpn, + formater=lambda _: _.replace(""", '"'), user=user, group=group) + # Reload systemd services config to apply an override + call(f'systemctl daemon-reload') + + return None + +def apply(openvpn): + interface = openvpn['ifname'] + + # Do some cleanup when OpenVPN is disabled/deleted + if 'deleted' in openvpn or 'disable' in openvpn: + call(f'systemctl stop openvpn@{interface}.service') + for cleanup_file in glob(f'/run/openvpn/{interface}.*'): + if os.path.isfile(cleanup_file): + os.unlink(cleanup_file) + + if interface_exists(interface): + VTunIf(interface).remove() + + # dynamically load/unload DCO Kernel extension if requested + dco_module = 'ovpn_dco_v2' + if 'module_load_dco' in openvpn: + check_kmod(dco_module) + else: + unload_kmod(dco_module) + + # Now bail out early if interface is disabled or got deleted + if 'deleted' in openvpn or 'disable' in openvpn: + return None + + # verify specified IP address is present on any interface on this system + # Allow to bind service to nonlocal address, if it virtaual-vrrp address + # or if address will be assign later + if 'local_host' in openvpn: + if not is_addr_assigned(openvpn['local_host']): + cmd('sysctl -w net.ipv4.ip_nonlocal_bind=1') + + # No matching OpenVPN process running - maybe it got killed or none + # existed - nevertheless, spawn new OpenVPN process + action = 'reload-or-restart' + if 'restart_required' in openvpn: + action = 'restart' + call(f'systemctl {action} openvpn@{interface}.service') + + o = VTunIf(**openvpn) + o.update(openvpn) + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_pppoe.py b/src/conf_mode/interfaces_pppoe.py new file mode 100644 index 0000000..412676c --- /dev/null +++ b/src/conf_mode/interfaces_pppoe.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configverify import verify_authentication +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_vrf +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_mirror_redirect +from vyos.ifconfig import PPPoEIf +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.process import is_systemd_service_running +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'pppoe'] + ifname, pppoe = get_interface_dict(conf, base) + + # We should only terminate the PPPoE session if critical parameters change. + # All parameters that can be changed on-the-fly (like interface description) + # should not lead to a reconnect! + for options in ['access-concentrator', 'connect-on-demand', 'service-name', + 'source-interface', 'vrf', 'no-default-route', + 'authentication', 'host_uniq']: + if is_node_changed(conf, base + [ifname, options]): + pppoe.update({'shutdown_required': {}}) + # bail out early - no need to further process other nodes + break + + if 'deleted' not in pppoe: + # We always set the MRU value to the MTU size. This code path only re-creates + # the old behavior if MRU is not set on the CLI. + if 'mru' not in pppoe: + pppoe['mru'] = pppoe['mtu'] + + return pppoe + +def verify(pppoe): + if 'deleted' in pppoe: + # bail out early + return None + + verify_source_interface(pppoe) + verify_authentication(pppoe) + verify_vrf(pppoe) + verify_mtu_ipv6(pppoe) + verify_mirror_redirect(pppoe) + + if {'connect_on_demand', 'vrf'} <= set(pppoe): + raise ConfigError('On-demand dialing and VRF can not be used at the same time') + + # both MTU and MRU have default values, thus we do not need to check + # if the key exists + if int(pppoe['mru']) > int(pppoe['mtu']): + raise ConfigError('PPPoE MRU needs to be lower then MTU!') + + return None + +def generate(pppoe): + # set up configuration file path variables where our templates will be + # rendered into + ifname = pppoe['ifname'] + config_pppoe = f'/etc/ppp/peers/{ifname}' + + if 'deleted' in pppoe or 'disable' in pppoe: + if os.path.exists(config_pppoe): + os.unlink(config_pppoe) + + return None + + # Create PPP configuration files + render(config_pppoe, 'pppoe/peer.j2', pppoe, permission=0o640) + + return None + +def apply(pppoe): + ifname = pppoe['ifname'] + if 'deleted' in pppoe or 'disable' in pppoe: + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = PPPoEIf(ifname) + p.remove() + call(f'systemctl stop ppp@{ifname}.service') + return None + + # reconnect should only be necessary when certain config options change, + # like ACS name, authentication ... (see get_config() for details) + if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or + 'shutdown_required' in pppoe): + + # cleanup system (e.g. FRR routes first) + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = PPPoEIf(ifname) + p.remove() + + call(f'systemctl restart ppp@{ifname}.service') + # When interface comes "live" a hook is called: + # /etc/ppp/ip-up.d/99-vyos-pppoe-callback + # which triggers PPPoEIf.update() + else: + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = PPPoEIf(ifname) + p.update(pppoe) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_pseudo-ethernet.py b/src/conf_mode/interfaces_pseudo-ethernet.py new file mode 100644 index 0000000..446beff --- /dev/null +++ b/src/conf_mode/interfaces_pseudo-ethernet.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configdict import is_source_interface +from vyos.configdict import is_node_changed +from vyos.configverify import verify_vrf +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_vlan_config +from vyos.configverify import verify_mtu_parent +from vyos.configverify import verify_mirror_redirect +from vyos.ifconfig import MACVLANIf +from vyos.utils.network import interface_exists +from vyos import ConfigError + +from vyos import airbag +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at + least the interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'pseudo-ethernet'] + ifname, peth = get_interface_dict(conf, base) + + mode = is_node_changed(conf, ['mode']) + if mode: peth.update({'shutdown_required' : {}}) + + if is_node_changed(conf, base + [ifname, 'mode']): + peth.update({'rebuild_required': {}}) + + if 'source_interface' in peth: + _, peth['parent'] = get_interface_dict(conf, ['interfaces', 'ethernet'], + peth['source_interface']) + # test if source-interface is maybe already used by another interface + tmp = is_source_interface(conf, peth['source_interface'], ['macsec']) + if tmp and tmp != ifname: peth.update({'is_source_interface' : tmp}) + + return peth + +def verify(peth): + if 'deleted' in peth: + verify_bridge_delete(peth) + return None + + verify_source_interface(peth) + verify_vrf(peth) + verify_address(peth) + verify_mtu_parent(peth, peth['parent']) + verify_mirror_redirect(peth) + # use common function to verify VLAN configuration + verify_vlan_config(peth) + + return None + +def generate(peth): + return None + +def apply(peth): + # Check if the MACVLAN interface already exists + if 'rebuild_required' in peth or 'deleted' in peth: + if interface_exists(peth['ifname']): + p = MACVLANIf(**peth) + # MACVLAN is always needs to be recreated, + # thus we can simply always delete it first. + p.remove() + + if 'deleted' not in peth: + p = MACVLANIf(**peth) + p.update(peth) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_sstpc.py b/src/conf_mode/interfaces_sstpc.py new file mode 100644 index 0000000..b9d7a74 --- /dev/null +++ b/src/conf_mode/interfaces_sstpc.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configverify import verify_authentication +from vyos.configverify import verify_vrf +from vyos.ifconfig import SSTPCIf +from vyos.pki import encode_certificate +from vyos.pki import find_chain +from vyos.pki import load_certificate +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.utils.process import is_systemd_service_running +from vyos.utils.file import write_file +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'sstpc'] + ifname, sstpc = get_interface_dict(conf, base, with_pki=True) + + # We should only terminate the SSTP client session if critical parameters + # change. All parameters that can be changed on-the-fly (like interface + # description) should not lead to a reconnect! + for options in ['authentication', 'no_peer_dns', 'no_default_route', + 'server', 'ssl']: + if is_node_changed(conf, base + [ifname, options]): + sstpc.update({'shutdown_required': {}}) + # bail out early - no need to further process other nodes + break + + return sstpc + +def verify(sstpc): + if 'deleted' in sstpc: + return None + + verify_authentication(sstpc) + verify_vrf(sstpc) + + if not dict_search('server', sstpc): + raise ConfigError('Remote SSTP server must be specified!') + + if not dict_search('ssl.ca_certificate', sstpc): + raise ConfigError('Missing mandatory CA certificate!') + + return None + +def generate(sstpc): + ifname = sstpc['ifname'] + config_sstpc = f'/etc/ppp/peers/{ifname}' + + sstpc['ca_file_path'] = f'/run/sstpc/{ifname}_ca-cert.pem' + + if 'deleted' in sstpc: + for file in [sstpc['ca_file_path'], config_sstpc]: + if os.path.exists(file): + os.unlink(file) + return None + + ca_name = sstpc['ssl']['ca_certificate'] + pki_ca_cert = sstpc['pki']['ca'][ca_name] + + loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) + loaded_ca_certs = {load_certificate(c['certificate']) + for c in sstpc['pki']['ca'].values()} if 'ca' in sstpc['pki'] else {} + + ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + + write_file(sstpc['ca_file_path'], '\n'.join(encode_certificate(c) for c in ca_full_chain)) + render(config_sstpc, 'sstp-client/peer.j2', sstpc, permission=0o640) + + return None + +def apply(sstpc): + ifname = sstpc['ifname'] + if 'deleted' in sstpc or 'disable' in sstpc: + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = SSTPCIf(ifname) + p.remove() + call(f'systemctl stop ppp@{ifname}.service') + return None + + # reconnect should only be necessary when specific options change, + # like server, authentication ... (see get_config() for details) + if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or + 'shutdown_required' in sstpc): + + # cleanup system (e.g. FRR routes first) + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = SSTPCIf(ifname) + p.remove() + + call(f'systemctl restart ppp@{ifname}.service') + # When interface comes "live" a hook is called: + # /etc/ppp/ip-up.d/96-vyos-sstpc-callback + # which triggers SSTPCIf.update() + else: + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = SSTPCIf(ifname) + p.update(sstpc) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_tunnel.py b/src/conf_mode/interfaces_tunnel.py new file mode 100644 index 0000000..98ef98d --- /dev/null +++ b/src/conf_mode/interfaces_tunnel.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 yOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_vrf +from vyos.configverify import verify_tunnel +from vyos.configverify import verify_bond_bridge_member +from vyos.ifconfig import Interface +from vyos.ifconfig import TunnelIf +from vyos.utils.dict import dict_search +from vyos.utils.network import get_interface_config +from vyos.utils.network import interface_exists +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least + the interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'tunnel'] + ifname, tunnel = get_interface_dict(conf, base) + + if 'deleted' not in tunnel: + tmp = is_node_changed(conf, base + [ifname, 'encapsulation']) + if tmp: tunnel.update({'encapsulation_changed': {}}) + + tmp = is_node_changed(conf, base + [ifname, 'parameters', 'ip', 'key']) + if tmp: tunnel.update({'key_changed': {}}) + + # We also need to inspect other configured tunnels as there are Kernel + # restrictions where we need to comply. E.g. GRE tunnel key can't be used + # twice, or with multiple GRE tunnels to the same location we must specify + # a GRE key + conf.set_level(base) + tunnel['other_tunnels'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + # delete our own instance from this dict + ifname = tunnel['ifname'] + del tunnel['other_tunnels'][ifname] + # if only one tunnel is present on the system, no need to keep this key + if len(tunnel['other_tunnels']) == 0: + del tunnel['other_tunnels'] + + # We must check if our interface is configured to be a DMVPN member + nhrp_base = ['protocols', 'nhrp', 'tunnel'] + conf.set_level(nhrp_base) + nhrp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if nhrp: tunnel.update({'nhrp' : list(nhrp.keys())}) + + if 'encapsulation' in tunnel and tunnel['encapsulation'] not in ['erspan', 'ip6erspan']: + del tunnel['parameters']['erspan'] + + return tunnel + +def verify(tunnel): + if 'deleted' in tunnel: + verify_bridge_delete(tunnel) + + if 'nhrp' in tunnel and tunnel['ifname'] in tunnel['nhrp']: + raise ConfigError('Tunnel used for NHRP, it can not be deleted!') + + return None + + verify_tunnel(tunnel) + + if tunnel['encapsulation'] in ['erspan', 'ip6erspan']: + if dict_search('parameters.ip.key', tunnel) == None: + raise ConfigError('ERSPAN requires ip key parameter!') + + # this is a default field + ver = int(tunnel['parameters']['erspan']['version']) + if ver == 1: + if 'hw_id' in tunnel['parameters']['erspan']: + raise ConfigError('ERSPAN version 1 does not support hw-id!') + if 'direction' in tunnel['parameters']['erspan']: + raise ConfigError('ERSPAN version 1 does not support direction!') + elif ver == 2: + if 'idx' in tunnel['parameters']['erspan']: + raise ConfigError('ERSPAN version 2 does not index parameter!') + if 'direction' not in tunnel['parameters']['erspan']: + raise ConfigError('ERSPAN version 2 requires direction to be set!') + + # If tunnel source is any and gre key is not set + interface = tunnel['ifname'] + if tunnel['encapsulation'] in ['gre'] and \ + dict_search('source_address', tunnel) == '0.0.0.0' and \ + dict_search('parameters.ip.key', tunnel) == None: + raise ConfigError(f'"parameters ip key" must be set for {interface} when '\ + 'encapsulation is GRE!') + + gre_encapsulations = ['gre', 'gretap'] + if tunnel['encapsulation'] in gre_encapsulations and 'other_tunnels' in tunnel: + # Check pairs tunnel source-address/encapsulation/key with exists tunnels. + # Prevent the same key for 2 tunnels with same source-address/encap. T2920 + for o_tunnel, o_tunnel_conf in tunnel['other_tunnels'].items(): + # no match on encapsulation - bail out + our_encapsulation = tunnel['encapsulation'] + their_encapsulation = o_tunnel_conf['encapsulation'] + if our_encapsulation in gre_encapsulations and their_encapsulation \ + not in gre_encapsulations: + continue + + our_address = dict_search('source_address', tunnel) + our_key = dict_search('parameters.ip.key', tunnel) + their_address = dict_search('source_address', o_tunnel_conf) + their_key = dict_search('parameters.ip.key', o_tunnel_conf) + if our_key != None: + if their_address == our_address and their_key == our_key: + raise ConfigError(f'Key "{our_key}" for source-address "{our_address}" ' \ + f'is already used for tunnel "{o_tunnel}"!') + else: + our_source_if = dict_search('source_interface', tunnel) + their_source_if = dict_search('source_interface', o_tunnel_conf) + our_remote = dict_search('remote', tunnel) + their_remote = dict_search('remote', o_tunnel_conf) + # If no IP GRE key is defined we can not have more then one GRE tunnel + # bound to any one interface/IP address and the same remote. This will + # result in a OS PermissionError: add tunnel "gre0" failed: File exists + if our_remote == their_remote: + if our_address is not None and their_address == our_address: + # If set to the same values, this is always a fail + raise ConfigError(f'Missing required "ip key" parameter when '\ + 'running more then one GRE based tunnel on the '\ + 'same source-address') + + if their_source_if == our_source_if and their_address == our_address: + # Note that lack of None check on these is deliberate. + # source-if and source-ip matching while unset (all None) is a fail + # source-ifs set and matching with unset source-ips is a fail + raise ConfigError(f'Missing required "ip key" parameter when '\ + 'running more then one GRE based tunnel on the '\ + 'same source-interface') + + # Keys are not allowed with ipip and sit tunnels + if tunnel['encapsulation'] in ['ipip', 'sit']: + if dict_search('parameters.ip.key', tunnel) != None: + raise ConfigError('Keys are not allowed with ipip and sit tunnels!') + + verify_mtu_ipv6(tunnel) + verify_address(tunnel) + verify_vrf(tunnel) + verify_bond_bridge_member(tunnel) + verify_mirror_redirect(tunnel) + + if 'source_interface' in tunnel: + verify_source_interface(tunnel) + + # TTL != 0 and nopmtudisc are incompatible, parameters and ip use default + # values, thus the keys are always present. + if dict_search('parameters.ip.no_pmtu_discovery', tunnel) != None: + if dict_search('parameters.ip.ttl', tunnel) != '0': + raise ConfigError('Disabled PMTU requires TTL set to "0"!') + if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']: + raise ConfigError('Can not disable PMTU discovery for given encapsulation') + + if dict_search('parameters.ip.ignore_df', tunnel) != None: + if tunnel['encapsulation'] not in ['gretap']: + raise ConfigError('Option ignore-df can only be used on GRETAP tunnels!') + + if dict_search('parameters.ip.no_pmtu_discovery', tunnel) == None: + raise ConfigError('Option ignore-df requires path MTU discovery to be disabled!') + + +def generate(tunnel): + return None + +def apply(tunnel): + interface = tunnel['ifname'] + # If a gretap tunnel is already existing we can not "simply" change local or + # remote addresses. This returns "Operation not supported" by the Kernel. + # There is no other solution to destroy and recreate the tunnel. + encap = '' + remote = '' + tmp = get_interface_config(interface) + if tmp: + encap = dict_search('linkinfo.info_kind', tmp) + remote = dict_search('linkinfo.info_data.remote', tmp) + + if ('deleted' in tunnel or 'encapsulation_changed' in tunnel or encap in + ['gretap', 'ip6gretap', 'erspan', 'ip6erspan'] or remote in ['any'] or + 'key_changed' in tunnel): + if interface_exists(interface): + tmp = Interface(interface) + tmp.remove() + if 'deleted' in tunnel: + return None + + tun = TunnelIf(**tunnel) + tun.update(tunnel) + + return None + +if __name__ == '__main__': + try: + c = get_config() + generate(c) + verify(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_virtual-ethernet.py b/src/conf_mode/interfaces_virtual-ethernet.py new file mode 100644 index 0000000..cb6104f --- /dev/null +++ b/src/conf_mode/interfaces_virtual-ethernet.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos import ConfigError +from vyos import airbag +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_vrf +from vyos.ifconfig import VethIf +from vyos.utils.network import interface_exists +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at + least the interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'virtual-ethernet'] + ifname, veth = get_interface_dict(conf, base) + + # We need to know all other veth related interfaces as veth requires a 1:1 + # mapping for the peer-names. The Linux kernel automatically creates both + # interfaces, the local one and the peer-name, but VyOS also needs a peer + # interfaces configrued on the CLI so we can assign proper IP addresses etc. + veth['other_interfaces'] = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + return veth + + +def verify(veth): + if 'deleted' in veth: + verify_bridge_delete(veth) + # Prevent to delete veth interface which used for another "vethX peer-name" + for iface, iface_config in veth['other_interfaces'].items(): + if veth['ifname'] in iface_config['peer_name']: + ifname = veth['ifname'] + raise ConfigError( + f'Cannot delete "{ifname}" used for "interface {iface} peer-name"' + ) + return None + + verify_vrf(veth) + verify_address(veth) + + if 'peer_name' not in veth: + raise ConfigError(f'Remote peer name must be set for "{veth["ifname"]}"!') + + peer_name = veth['peer_name'] + ifname = veth['ifname'] + + if veth['peer_name'] not in veth['other_interfaces']: + raise ConfigError(f'Used peer-name "{peer_name}" on interface "{ifname}" ' \ + 'is not configured!') + + if veth['other_interfaces'][peer_name]['peer_name'] != ifname: + raise ConfigError( + f'Configuration mismatch between "{ifname}" and "{peer_name}"!') + + if peer_name == ifname: + raise ConfigError( + f'Peer-name "{peer_name}" cannot be the same as interface "{ifname}"!') + + return None + + +def generate(peth): + return None + +def apply(veth): + # Check if the Veth interface already exists + if 'rebuild_required' in veth or 'deleted' in veth: + if interface_exists(veth['ifname']): + p = VethIf(**veth) + p.remove() + + if 'deleted' not in veth: + p = VethIf(**veth) + p.update(veth) + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_vti.py b/src/conf_mode/interfaces_vti.py new file mode 100644 index 0000000..20629c6 --- /dev/null +++ b/src/conf_mode/interfaces_vti.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_vrf +from vyos.ifconfig import VTIIf +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'vti'] + _, vti = get_interface_dict(conf, base) + return vti + +def verify(vti): + verify_vrf(vti) + verify_mirror_redirect(vti) + return None + +def generate(vti): + return None + +def apply(vti): + # Remove macsec interface + if 'deleted' in vti: + VTIIf(**vti).remove() + return None + + tmp = VTIIf(**vti) + tmp.update(vti) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_vxlan.py b/src/conf_mode/interfaces_vxlan.py new file mode 100644 index 0000000..68646e8 --- /dev/null +++ b/src/conf_mode/interfaces_vxlan.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_node_changed +from vyos.configdict import is_node_changed +from vyos.configdict import node_changed +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_bond_bridge_member +from vyos.configverify import verify_vrf +from vyos.ifconfig import Interface +from vyos.ifconfig import VXLANIf +from vyos.template import is_ipv6 +from vyos.utils.dict import dict_search +from vyos.utils.network import interface_exists +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least + the interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'vxlan'] + ifname, vxlan = get_interface_dict(conf, base) + + # VXLAN interfaces are picky and require recreation if certain parameters + # change. But a VXLAN interface should - of course - not be re-created if + # it's description or IP address is adjusted. Feels somehow logic doesn't it? + for cli_option in ['parameters', 'gpe', 'group', 'port', 'remote', + 'source-address', 'source-interface', 'vni']: + if is_node_changed(conf, base + [ifname, cli_option]): + vxlan.update({'rebuild_required': {}}) + break + + # When dealing with VNI filtering we need to know what VNI was actually removed, + # so build up a dict matching the vlan_to_vni structure but with removed values. + tmp = node_changed(conf, base + [ifname, 'vlan-to-vni'], recursive=True) + if tmp: + vxlan.update({'vlan_to_vni_removed': {}}) + for vlan in tmp: + vni = leaf_node_changed(conf, base + [ifname, 'vlan-to-vni', vlan, 'vni']) + vxlan['vlan_to_vni_removed'].update({vlan : {'vni' : vni[0]}}) + + # We need to verify that no other VXLAN tunnel is configured when external + # mode is in use - Linux Kernel limitation + conf.set_level(base) + vxlan['other_tunnels'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # This if-clause is just to be sure - it will always evaluate to true + ifname = vxlan['ifname'] + if ifname in vxlan['other_tunnels']: + del vxlan['other_tunnels'][ifname] + if len(vxlan['other_tunnels']) == 0: + del vxlan['other_tunnels'] + + return vxlan + +def verify(vxlan): + if 'deleted' in vxlan: + verify_bridge_delete(vxlan) + return None + + if int(vxlan['mtu']) < 1500: + Warning('RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU') + + if 'group' in vxlan: + if 'source_interface' not in vxlan: + raise ConfigError('Multicast VXLAN requires an underlaying interface') + verify_source_interface(vxlan) + + if not any(tmp in ['group', 'remote', 'source_address', 'source_interface'] for tmp in vxlan): + raise ConfigError('Group, remote, source-address or source-interface must be configured') + + if 'vni' not in vxlan and dict_search('parameters.external', vxlan) == None: + raise ConfigError('Must either configure VXLAN "vni" or use "external" CLI option!') + + if dict_search('parameters.external', vxlan) != None: + if 'vni' in vxlan: + raise ConfigError('Can not specify both "external" and "VNI"!') + + if 'other_tunnels' in vxlan: + # When multiple VXLAN interfaces are defined and "external" is used, + # all VXLAN interfaces need to have vni-filter enabled! + # See Linux Kernel commit f9c4bb0b245cee35ef66f75bf409c9573d934cf9 + other_vni_filter = False + for tunnel, tunnel_config in vxlan['other_tunnels'].items(): + if dict_search('parameters.vni_filter', tunnel_config) != None: + other_vni_filter = True + break + # eqivalent of the C foo ? 'a' : 'b' statement + vni_filter = True and (dict_search('parameters.vni_filter', vxlan) != None) or False + # If either one is enabled, so must be the other. Both can be off and both can be on + if (vni_filter and not other_vni_filter) or (not vni_filter and other_vni_filter): + raise ConfigError(f'Using multiple VXLAN interfaces with "external" '\ + 'requires all VXLAN interfaces to have "vni-filter" configured!') + + if not vni_filter and not other_vni_filter: + other_tunnels = ', '.join(vxlan['other_tunnels']) + raise ConfigError(f'Only one VXLAN tunnel is supported when "external" '\ + f'CLI option is used and "vni-filter" is unset. '\ + f'Additional tunnels: {other_tunnels}') + + if 'gpe' in vxlan and 'external' not in vxlan: + raise ConfigError(f'VXLAN-GPE is only supported when "external" '\ + f'CLI option is used.') + + if 'source_interface' in vxlan: + # VXLAN adds at least an overhead of 50 byte - we need to check the + # underlaying device if our VXLAN package is not going to be fragmented! + vxlan_overhead = 50 + if 'source_address' in vxlan and is_ipv6(vxlan['source_address']): + # IPv6 adds an extra 20 bytes overhead because the IPv6 header is 20 + # bytes larger than the IPv4 header - assuming no extra options are + # in use. + vxlan_overhead += 20 + + # If source_address is not used - check IPv6 'remote' list + elif 'remote' in vxlan: + if any(is_ipv6(a) for a in vxlan['remote']): + vxlan_overhead += 20 + + lower_mtu = Interface(vxlan['source_interface']).get_mtu() + if lower_mtu < (int(vxlan['mtu']) + vxlan_overhead): + raise ConfigError(f'Underlaying device MTU is to small ({lower_mtu} '\ + f'bytes) for VXLAN overhead ({vxlan_overhead} bytes!)') + + # Check for mixed IPv4 and IPv6 addresses + protocol = None + if 'source_address' in vxlan: + if is_ipv6(vxlan['source_address']): + protocol = 'ipv6' + else: + protocol = 'ipv4' + + if 'remote' in vxlan: + error_msg = 'Can not mix both IPv4 and IPv6 for VXLAN underlay' + for remote in vxlan['remote']: + if is_ipv6(remote): + if protocol == 'ipv4': + raise ConfigError(error_msg) + protocol = 'ipv6' + else: + if protocol == 'ipv6': + raise ConfigError(error_msg) + protocol = 'ipv4' + + if 'vlan_to_vni' in vxlan: + if 'is_bridge_member' not in vxlan: + raise ConfigError('VLAN to VNI mapping requires that VXLAN interface '\ + 'is member of a bridge interface!') + + vnis_used = [] + vlans_used = [] + for vif, vif_config in vxlan['vlan_to_vni'].items(): + if 'vni' not in vif_config: + raise ConfigError(f'Must define VNI for VLAN "{vif}"!') + vni = vif_config['vni'] + + err_msg = f'VLAN range "{vif}" does not match VNI range "{vni}"!' + vif_range, vni_range = list(map(int, vif.split('-'))), list(map(int, vni.split('-'))) + + if len(vif_range) != len(vni_range): + raise ConfigError(err_msg) + + if len(vif_range) > 1: + if vni_range[0] > vni_range[-1] or vif_range[0] > vif_range[-1]: + raise ConfigError('The upper bound of the range must be greater than the lower bound!') + vni_range = range(vni_range[0], vni_range[1] + 1) + vif_range = range(vif_range[0], vif_range[1] + 1) + + if len(vif_range) != len(vni_range): + raise ConfigError(err_msg) + + for vni_id in vni_range: + if vni_id in vnis_used: + raise ConfigError(f'VNI "{vni_id}" is already assigned to a different VLAN!') + vnis_used.append(vni_id) + + for vif_id in vif_range: + if vif_id in vlans_used: + raise ConfigError(f'VLAN "{vif_id}" is already in use!') + vlans_used.append(vif_id) + + if dict_search('parameters.neighbor_suppress', vxlan) != None: + if 'is_bridge_member' not in vxlan: + raise ConfigError('Neighbor suppression requires that VXLAN interface '\ + 'is member of a bridge interface!') + + verify_mtu_ipv6(vxlan) + verify_address(vxlan) + verify_vrf(vxlan) + verify_bond_bridge_member(vxlan) + verify_mirror_redirect(vxlan) + + # We use a defaultValue for port, thus it's always safe to use + if vxlan['port'] == '8472': + Warning('Starting from VyOS 1.4, the default port for VXLAN '\ + 'has been changed to 4789. This matches the IANA assigned '\ + 'standard port number!') + + return None + +def generate(vxlan): + return None + +def apply(vxlan): + # Check if the VXLAN interface already exists + if 'rebuild_required' in vxlan or 'delete' in vxlan: + if interface_exists(vxlan['ifname']): + v = VXLANIf(**vxlan) + # VXLAN is super picky and the tunnel always needs to be recreated, + # thus we can simply always delete it first. + v.remove() + + if 'deleted' not in vxlan: + # Finally create the new interface + v = VXLANIf(**vxlan) + v.update(vxlan) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_wireguard.py b/src/conf_mode/interfaces_wireguard.py new file mode 100644 index 0000000..7abdfdb --- /dev/null +++ b/src/conf_mode/interfaces_wireguard.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configverify import verify_vrf +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_bond_bridge_member +from vyos.ifconfig import WireGuardIf +from vyos.utils.kernel import check_kmod +from vyos.utils.network import check_port_availability +from vyos.utils.network import is_wireguard_key_pair +from vyos import ConfigError +from vyos import airbag +airbag.enable() + + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'wireguard'] + ifname, wireguard = get_interface_dict(conf, base) + + # Check if a port was changed + tmp = is_node_changed(conf, base + [ifname, 'port']) + if tmp: wireguard['port_changed'] = {} + + # T4702: If anything on a peer changes we remove the peer first and re-add it + if is_node_changed(conf, base + [ifname, 'peer']): + wireguard.update({'rebuild_required': {}}) + + return wireguard + +def verify(wireguard): + if 'deleted' in wireguard: + verify_bridge_delete(wireguard) + return None + + verify_mtu_ipv6(wireguard) + verify_address(wireguard) + verify_vrf(wireguard) + verify_bond_bridge_member(wireguard) + verify_mirror_redirect(wireguard) + + if 'private_key' not in wireguard: + raise ConfigError('Wireguard private-key not defined') + + if 'peer' not in wireguard: + raise ConfigError('At least one Wireguard peer is required!') + + if 'port' in wireguard and 'port_changed' in wireguard: + listen_port = int(wireguard['port']) + if check_port_availability('0.0.0.0', listen_port, 'udp') is not True: + raise ConfigError(f'UDP port {listen_port} is busy or unavailable and ' + 'cannot be used for the interface!') + + # run checks on individual configured WireGuard peer + public_keys = [] + for tmp in wireguard['peer']: + peer = wireguard['peer'][tmp] + + if 'allowed_ips' not in peer: + raise ConfigError(f'Wireguard allowed-ips required for peer "{tmp}"!') + + if 'public_key' not in peer: + raise ConfigError(f'Wireguard public-key required for peer "{tmp}"!') + + if ('address' in peer and 'port' not in peer) or ('port' in peer and 'address' not in peer): + raise ConfigError('Both Wireguard port and address must be defined ' + f'for peer "{tmp}" if either one of them is set!') + + if peer['public_key'] in public_keys: + raise ConfigError(f'Duplicate public-key defined on peer "{tmp}"') + + if 'disable' not in peer: + if is_wireguard_key_pair(wireguard['private_key'], peer['public_key']): + raise ConfigError(f'Peer "{tmp}" has the same public key as the interface "{wireguard["ifname"]}"') + + public_keys.append(peer['public_key']) + +def generate(wireguard): + return None + +def apply(wireguard): + check_kmod('wireguard') + + if 'rebuild_required' in wireguard or 'deleted' in wireguard: + wg = WireGuardIf(**wireguard) + # WireGuard only supports peer removal based on the configured public-key, + # by deleting the entire interface this is the shortcut instead of parsing + # out all peers and removing them one by one. + # + # Peer reconfiguration will always come with a short downtime while the + # WireGuard interface is recreated (see below) + wg.remove() + + # Create the new interface if required + if 'deleted' not in wireguard: + wg = WireGuardIf(**wireguard) + wg.update(wireguard) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_wireless.py b/src/conf_mode/interfaces_wireless.py new file mode 100644 index 0000000..d24675e --- /dev/null +++ b/src/conf_mode/interfaces_wireless.py @@ -0,0 +1,344 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit +from re import findall +from netaddr import EUI, mac_unix_expanded +from time import sleep + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import dict_merge +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_vlan_config +from vyos.configverify import verify_vrf +from vyos.configverify import verify_bond_bridge_member +from vyos.ifconfig import WiFiIf +from vyos.template import render +from vyos.utils.dict import dict_search +from vyos.utils.kernel import check_kmod +from vyos.utils.process import call +from vyos.utils.process import is_systemd_service_active +from vyos.utils.process import is_systemd_service_running +from vyos.utils.network import interface_exists +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +# XXX: wpa_supplicant works on the source interface +wpa_suppl_conf = '/run/wpa_supplicant/{ifname}.conf' +hostapd_conf = '/run/hostapd/{ifname}.conf' +hostapd_accept_station_conf = '/run/hostapd/{ifname}_station_accept.conf' +hostapd_deny_station_conf = '/run/hostapd/{ifname}_station_deny.conf' + +country_code_path = ['system', 'wireless', 'country-code'] + +def find_other_stations(conf, base, ifname): + """ + Only one wireless interface per phy can be in station mode - + find all interfaces attached to a phy which run in station mode + """ + old_level = conf.get_level() + conf.set_level(base) + dict = {} + for phy in os.listdir('/sys/class/ieee80211'): + list = [] + for interface in conf.list_nodes([]): + if interface == ifname: + continue + # the following node is mandatory + if conf.exists([interface, 'physical-device', phy]): + tmp = conf.return_value([interface, 'type']) + if tmp == 'station': + list.append(interface) + if list: + dict.update({phy: list}) + conf.set_level(old_level) + return dict + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'wireless'] + + _, wifi = get_interface_dict(conf, base) + + # retrieve global Wireless regulatory domain setting + if conf.exists(country_code_path): + wifi['country_code'] = conf.return_value(country_code_path) + + if 'deleted' not in wifi: + # then get_interface_dict provides default keys + if wifi.from_defaults(['security', 'wep']): # if not set by user + del wifi['security']['wep'] + if wifi.from_defaults(['security', 'wpa']): # if not set by user + del wifi['security']['wpa'] + + # XXX: Jinja2 can not operate on a dictionary key when it starts of with a number + if '40mhz_incapable' in (dict_search('capabilities.ht', wifi) or []): + wifi['capabilities']['ht']['fourtymhz_incapable'] = wifi['capabilities']['ht']['40mhz_incapable'] + del wifi['capabilities']['ht']['40mhz_incapable'] + + if dict_search('security.wpa', wifi) != None: + wpa_cipher = wifi['security']['wpa'].get('cipher') + wpa_mode = wifi['security']['wpa'].get('mode') + if not wpa_cipher: + tmp = None + if wpa_mode == 'wpa': + tmp = {'security': {'wpa': {'cipher' : ['TKIP', 'CCMP']}}} + elif wpa_mode == 'wpa2': + tmp = {'security': {'wpa': {'cipher' : ['CCMP']}}} + elif wpa_mode == 'both': + tmp = {'security': {'wpa': {'cipher' : ['CCMP', 'TKIP']}}} + elif wpa_mode == 'wpa3': + # According to WiFi specs (https://www.wi-fi.org/file/wpa3-specification) + # section 3.5: WPA3-Enterprise 192-bit mode + # WiFi NICs which would be able to connect to WPA3-Enterprise managed + # networks MUST support GCMP-256. + # Reasoning: Provided that chipsets would most likely _not_ be + # "private user only", they all would come with built-in support + # for GCMP-256. + tmp = {'security': {'wpa': {'cipher' : ['CCMP', 'CCMP-256', 'GCMP', 'GCMP-256']}}} + + if tmp: wifi = dict_merge(tmp, wifi) + + # Only one wireless interface per phy can be in station mode + tmp = find_other_stations(conf, base, wifi['ifname']) + if tmp: wifi['station_interfaces'] = tmp + + # used in hostapd.conf.j2 + wifi['hostapd_accept_station_conf'] = hostapd_accept_station_conf.format(**wifi) + wifi['hostapd_deny_station_conf'] = hostapd_deny_station_conf.format(**wifi) + + return wifi + +def verify(wifi): + if 'deleted' in wifi: + verify_bridge_delete(wifi) + return None + + if 'physical_device' not in wifi: + raise ConfigError('You must specify a physical-device "phy"') + + physical_device = wifi['physical_device'] + if not os.path.exists(f'/sys/class/ieee80211/{physical_device}'): + raise ConfigError(f'Wirelss interface PHY "{physical_device}" does not exist!') + + if 'type' not in wifi: + raise ConfigError('You must specify a WiFi mode') + + if 'ssid' not in wifi and wifi['type'] != 'monitor': + raise ConfigError('SSID must be configured unless type is set to "monitor"!') + + if wifi['type'] == 'access-point': + if 'country_code' not in wifi: + raise ConfigError(f'Wireless country-code is mandatory, use: '\ + f'"set {" ".join(country_code_path)}"!') + + if 'channel' not in wifi: + raise ConfigError('Wireless channel must be configured!') + + if 'capabilities' in wifi and 'he' in wifi['capabilities']: + if 'channel_set_width' not in wifi['capabilities']['he']: + raise ConfigError('Channel width must be configured!') + + # op_modes drawn from: + # https://w1.fi/cgit/hostap/tree/src/common/ieee802_11_common.c?id=195cc3d919503fb0d699d9a56a58a72602b25f51#n1525 + # 802.11ax (WiFi-6e - HE) can use up to 160MHz bandwidth channels + six_ghz_op_modes_he = ['131', '132', '133', '134', '135'] + # 802.11be (WiFi-7 - EHT) can use up to 320MHz bandwidth channels + six_ghz_op_modes_eht = six_ghz_op_modes_he.append('137') + if 'security' in wifi and 'wpa' in wifi['security'] and 'mode' in wifi['security']['wpa']: + if wifi['security']['wpa']['mode'] == 'wpa3': + if 'he' in wifi['capabilities']: + if wifi['capabilities']['he']['channel_set_width'] in six_ghz_op_modes_he: + if 'mgmt_frame_protection' not in wifi or wifi['mgmt_frame_protection'] != 'required': + raise ConfigError('Management Frame Protection (MFP) is required with WPA3 at 6GHz! Consider also enabling Beacon Frame Protection (BFP) if your device supports it.') + + if 'security' in wifi: + if {'wep', 'wpa'} <= set(wifi.get('security', {})): + raise ConfigError('Must either use WEP or WPA security!') + + if 'wep' in wifi['security']: + if 'key' in wifi['security']['wep'] and len(wifi['security']['wep']) > 4: + raise ConfigError('No more then 4 WEP keys configurable') + elif 'key' not in wifi['security']['wep']: + raise ConfigError('Security WEP configured - missing WEP keys!') + + elif 'wpa' in wifi['security']: + wpa = wifi['security']['wpa'] + if not any(i in ['passphrase', 'radius'] for i in wpa): + raise ConfigError('Misssing WPA key or RADIUS server') + + if 'username' in wpa: + if 'passphrase' not in wpa: + raise ConfigError('WPA-Enterprise configured - missing passphrase!') + elif 'passphrase' in wpa: + # check if passphrase meets the regex .{8,63} + if len(wpa['passphrase']) < 8 or len(wpa['passphrase']) > 63: + raise ConfigError('WPA passphrase must be between 8 and 63 characters long') + if 'radius' in wpa: + if 'server' in wpa['radius']: + for server in wpa['radius']['server']: + if 'key' not in wpa['radius']['server'][server]: + raise ConfigError(f'Missing RADIUS shared secret key for server: {server}') + + if 'capabilities' in wifi: + capabilities = wifi['capabilities'] + if 'vht' in capabilities: + if 'ht' not in capabilities: + raise ConfigError('Specify HT flags if you want to use VHT!') + + if {'beamform', 'antenna_count'} <= set(capabilities.get('vht', {})): + if capabilities['vht']['antenna_count'] == '1': + raise ConfigError('Cannot use beam forming with just one antenna!') + + if capabilities['vht']['beamform'] == 'single-user-beamformer': + if int(capabilities['vht']['antenna_count']) < 3: + # Nasty Gotcha: see lines 708-721 in: + # https://w1.fi/cgit/hostap/tree/hostapd/hostapd.conf?h=hostap_2_10&id=cff80b4f7d3c0a47c052e8187d671710f48939e4#n708 + raise ConfigError('Single-user beam former requires at least 3 antennas!') + + if 'station_interfaces' in wifi and wifi['type'] == 'station': + phy = wifi['physical_device'] + if phy in wifi['station_interfaces']: + if len(wifi['station_interfaces'][phy]) > 0: + raise ConfigError('Only one station per wireless physical interface possible!') + + verify_address(wifi) + verify_vrf(wifi) + verify_bond_bridge_member(wifi) + verify_mirror_redirect(wifi) + + # use common function to verify VLAN configuration + verify_vlan_config(wifi) + + return None + +def generate(wifi): + check_kmod('mac80211') + + interface = wifi['ifname'] + + # Delete config files if interface is removed + if 'deleted' in wifi: + if os.path.isfile(hostapd_conf.format(**wifi)): + os.unlink(hostapd_conf.format(**wifi)) + if os.path.isfile(hostapd_accept_station_conf.format(**wifi)): + os.unlink(hostapd_accept_station_conf.format(**wifi)) + if os.path.isfile(hostapd_deny_station_conf.format(**wifi)): + os.unlink(hostapd_deny_station_conf.format(**wifi)) + if os.path.isfile(wpa_suppl_conf.format(**wifi)): + os.unlink(wpa_suppl_conf.format(**wifi)) + + return None + + if 'mac' not in wifi: + # http://wiki.stocksy.co.uk/wiki/Multiple_SSIDs_with_hostapd + # generate locally administered MAC address from used phy interface + with open('/sys/class/ieee80211/{physical_device}/addresses'.format(**wifi), 'r') as f: + # some PHYs tend to have multiple interfaces and thus supply multiple MAC + # addresses - we only need the first one for our calculation + tmp = f.readline().rstrip() + tmp = EUI(tmp).value + # mask last nibble from the MAC address + tmp &= 0xfffffffffff0 + # set locally administered bit in MAC address + tmp |= 0x020000000000 + # we now need to add an offset to our MAC address indicating this + # subinterfaces index + tmp += int(findall(r'\d+', interface)[0]) + + # convert integer to "real" MAC address representation + mac = EUI(hex(tmp).split('x')[-1]) + # change dialect to use : as delimiter instead of - + mac.dialect = mac_unix_expanded + wifi['mac'] = str(mac) + + # render appropriate new config files depending on access-point or station mode + if wifi['type'] == 'access-point': + render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.j2', wifi) + render(hostapd_accept_station_conf.format(**wifi), 'wifi/hostapd_accept_station.conf.j2', wifi) + render(hostapd_deny_station_conf.format(**wifi), 'wifi/hostapd_deny_station.conf.j2', wifi) + + elif wifi['type'] == 'station': + render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.j2', wifi) + + return None + +def apply(wifi): + interface = wifi['ifname'] + # From systemd source code: + # If there's a stop job queued before we enter the DEAD state, we shouldn't act on Restart=, + # in order to not undo what has already been enqueued. */ + # + # It was found that calling restart on hostapd will (4 out of 10 cases) deactivate + # the service instead of restarting it, when it was not yet properly stopped + # systemd[1]: hostapd@wlan1.service: Deactivated successfully. + # Thus kill all WIFI service and start them again after it's ensured nothing lives + call(f'systemctl stop hostapd@{interface}.service') + call(f'systemctl stop wpa_supplicant@{interface}.service') + + if 'deleted' in wifi: + WiFiIf(**wifi).remove() + return None + + while (is_systemd_service_running(f'hostapd@{interface}.service') or \ + is_systemd_service_active(f'hostapd@{interface}.service')): + sleep(0.250) # wait 250ms + + # Finally create the new interface + w = WiFiIf(**wifi) + w.update(wifi) + + # Enable/Disable interface - interface is always placed in + # administrative down state in WiFiIf class + if 'disable' not in wifi: + # Wait until interface was properly added to the Kernel + ii = 0 + while not (interface_exists(interface) and ii < 20): + sleep(0.250) # wait 250ms + ii += 1 + + # Physical interface is now configured. Proceed by starting hostapd or + # wpa_supplicant daemon. When type is monitor we can just skip this. + if wifi['type'] == 'access-point': + call(f'systemctl start hostapd@{interface}.service') + + elif wifi['type'] == 'station': + call(f'systemctl start wpa_supplicant@{interface}.service') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interfaces_wwan.py b/src/conf_mode/interfaces_wwan.py new file mode 100644 index 0000000..230eb14 --- /dev/null +++ b/src/conf_mode/interfaces_wwan.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit +from time import sleep + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configverify import verify_authentication +from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_vrf +from vyos.ifconfig import WWANIf +from vyos.utils.dict import dict_search +from vyos.utils.process import cmd +from vyos.utils.process import call +from vyos.utils.process import DEVNULL +from vyos.utils.process import is_systemd_service_active +from vyos.utils.file import write_file +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +service_name = 'ModemManager.service' +cron_script = '/etc/cron.d/vyos-wwan' + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'wwan'] + ifname, wwan = get_interface_dict(conf, base) + + # We should only terminate the WWAN session if critical parameters change. + # All parameters that can be changed on-the-fly (like interface description) + # should not lead to a reconnect! + tmp = is_node_changed(conf, base + [ifname, 'address']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = is_node_changed(conf, base + [ifname, 'apn']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = is_node_changed(conf, base + [ifname, 'disable']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = is_node_changed(conf, base + [ifname, 'vrf']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = is_node_changed(conf, base + [ifname, 'authentication']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = is_node_changed(conf, base + [ifname, 'ipv6', 'address', 'autoconf']) + if tmp: wwan.update({'shutdown_required': {}}) + + # We need to know the amount of other WWAN interfaces as ModemManager needs + # to be started or stopped. + wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # This if-clause is just to be sure - it will always evaluate to true + if ifname in wwan['other_interfaces']: + del wwan['other_interfaces'][ifname] + if len(wwan['other_interfaces']) == 0: + del wwan['other_interfaces'] + + return wwan + +def verify(wwan): + if 'deleted' in wwan: + return None + + ifname = wwan['ifname'] + if not 'apn' in wwan: + raise ConfigError(f'No APN configured for "{ifname}"!') + + verify_interface_exists(wwan, ifname) + verify_authentication(wwan) + verify_vrf(wwan) + verify_mirror_redirect(wwan) + + return None + +def generate(wwan): + if 'deleted' in wwan: + # We are the last WWAN interface - there are no other ones remaining + # thus the cronjob needs to go away, too + if 'other_interfaces' not in wwan: + if os.path.exists(cron_script): + os.unlink(cron_script) + return None + + # Install cron triggered helper script to re-dial WWAN interfaces on + # disconnect - e.g. happens during RF signal loss. The script watches every + # WWAN interface - so there is only one instance. + if not os.path.exists(cron_script): + write_file(cron_script, '*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py\n') + + return None + +def apply(wwan): + # ModemManager is required to dial WWAN connections - one instance is + # required to serve all modems. Activate ModemManager on first invocation + # of any WWAN interface. + if not is_systemd_service_active(service_name): + cmd(f'systemctl start {service_name}') + + counter = 100 + # Wait until a modem is detected and then we can continue + while counter > 0: + counter -= 1 + tmp = cmd('mmcli -L') + if tmp != 'No modems were found': + break + sleep(0.250) + + if 'shutdown_required' in wwan: + # we only need the modem number. wwan0 -> 0, wwan1 -> 1 + modem = wwan['ifname'].lstrip('wwan') + base_cmd = f'mmcli --modem {modem}' + # Number of bearers is limited - always disconnect first + cmd(f'{base_cmd} --simple-disconnect') + + w = WWANIf(wwan['ifname']) + if 'deleted' in wwan or 'disable' in wwan: + w.remove() + + # We are the last WWAN interface - there are no other WWAN interfaces + # remaining, thus we can stop ModemManager and free resources. + if 'other_interfaces' not in wwan: + cmd(f'systemctl stop {service_name}') + # Clean CRON helper script which is used for to re-connect when + # RF signal is lost + if os.path.exists(cron_script): + os.unlink(cron_script) + + return None + + if 'shutdown_required' in wwan: + ip_type = 'ipv4' + slaac = dict_search('ipv6.address.autoconf', wwan) != None + if 'address' in wwan: + if 'dhcp' in wwan['address'] and ('dhcpv6' in wwan['address'] or slaac): + ip_type = 'ipv4v6' + elif 'dhcpv6' in wwan['address'] or slaac: + ip_type = 'ipv6' + elif 'dhcp' in wwan['address']: + ip_type = 'ipv4' + + options = f'ip-type={ip_type},apn=' + wwan['apn'] + if 'authentication' in wwan: + options += ',user={username},password={password}'.format(**wwan['authentication']) + + command = f'{base_cmd} --simple-connect="{options}"' + call(command, stdout=DEVNULL) + + w.update(wwan) + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/load-balancing_reverse-proxy.py b/src/conf_mode/load-balancing_reverse-proxy.py new file mode 100644 index 0000000..17226ef --- /dev/null +++ b/src/conf_mode/load-balancing_reverse-proxy.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit +from shutil import rmtree + +from vyos.config import Config +from vyos.configverify import verify_pki_certificate +from vyos.configverify import verify_pki_ca_certificate +from vyos.utils.dict import dict_search +from vyos.utils.process import call +from vyos.utils.network import check_port_availability +from vyos.utils.network import is_listen_port_bind_service +from vyos.pki import find_chain +from vyos.pki import load_certificate +from vyos.pki import load_private_key +from vyos.pki import encode_certificate +from vyos.pki import encode_private_key +from vyos.template import render +from vyos.utils.file import write_file +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +load_balancing_dir = '/run/haproxy' +load_balancing_conf_file = f'{load_balancing_dir}/haproxy.cfg' +systemd_service = 'haproxy.service' +systemd_override = '/run/systemd/system/haproxy.service.d/10-override.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['load-balancing', 'reverse-proxy'] + if not conf.exists(base): + return None + lb = conf.get_config_dict(base, + get_first_key=True, + key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + with_recursive_defaults=True, + with_pki=True) + + return lb + +def verify(lb): + if not lb: + return None + + if 'backend' not in lb or 'service' not in lb: + raise ConfigError(f'"service" and "backend" must be configured!') + + for front, front_config in lb['service'].items(): + if 'port' not in front_config: + raise ConfigError(f'"{front} service port" must be configured!') + + # Check if bind address:port are used by another service + tmp_address = front_config.get('address', '0.0.0.0') + tmp_port = front_config['port'] + if check_port_availability(tmp_address, int(tmp_port), 'tcp') is not True and \ + not is_listen_port_bind_service(int(tmp_port), 'haproxy'): + raise ConfigError(f'"TCP" port "{tmp_port}" is used by another service') + + for back, back_config in lb['backend'].items(): + if 'http_check' in back_config: + http_check = back_config['http_check'] + if 'expect' in http_check and 'status' in http_check['expect'] and 'string' in http_check['expect']: + raise ConfigError(f'"expect status" and "expect string" can not be configured together!') + + if 'health_check' in back_config: + if back_config['mode'] != 'tcp': + raise ConfigError(f'backend "{back}" can only be configured with {back_config["health_check"]} ' + + f'health-check whilst in TCP mode!') + if 'http_check' in back_config: + raise ConfigError(f'backend "{back}" cannot be configured with both http-check and health-check!') + + if 'server' not in back_config: + raise ConfigError(f'"{back} server" must be configured!') + + for bk_server, bk_server_conf in back_config['server'].items(): + if 'address' not in bk_server_conf or 'port' not in bk_server_conf: + raise ConfigError(f'"backend {back} server {bk_server} address and port" must be configured!') + + if {'send_proxy', 'send_proxy_v2'} <= set(bk_server_conf): + raise ConfigError(f'Cannot use both "send-proxy" and "send-proxy-v2" for server "{bk_server}"') + + if 'ssl' in back_config: + if {'no_verify', 'ca_certificate'} <= set(back_config['ssl']): + raise ConfigError(f'backend {back} cannot have both ssl options no-verify and ca-certificate set!') + + # Check if http-response-headers are configured in any frontend/backend where mode != http + for group in ['service', 'backend']: + for config_name, config in lb[group].items(): + if 'http_response_headers' in config and config['mode'] != 'http': + raise ConfigError(f'{group} {config_name} must be set to http mode to use http_response_headers!') + + for front, front_config in lb['service'].items(): + for cert in dict_search('ssl.certificate', front_config) or []: + verify_pki_certificate(lb, cert) + + for back, back_config in lb['backend'].items(): + tmp = dict_search('ssl.ca_certificate', back_config) + if tmp: verify_pki_ca_certificate(lb, tmp) + + +def generate(lb): + if not lb: + # Delete /run/haproxy/haproxy.cfg + config_files = [load_balancing_conf_file, systemd_override] + for file in config_files: + if os.path.isfile(file): + os.unlink(file) + # Delete old directories + if os.path.isdir(load_balancing_dir): + rmtree(load_balancing_dir, ignore_errors=True) + + return None + + # Create load-balance dir + if not os.path.isdir(load_balancing_dir): + os.mkdir(load_balancing_dir) + + loaded_ca_certs = {load_certificate(c['certificate']) + for c in lb['pki']['ca'].values()} if 'ca' in lb['pki'] else {} + + # SSL Certificates for frontend + for front, front_config in lb['service'].items(): + if 'ssl' not in front_config: + continue + + if 'certificate' in front_config['ssl']: + cert_names = front_config['ssl']['certificate'] + + for cert_name in cert_names: + pki_cert = lb['pki']['certificate'][cert_name] + cert_file_path = os.path.join(load_balancing_dir, f'{cert_name}.pem') + cert_key_path = os.path.join(load_balancing_dir, f'{cert_name}.pem.key') + + loaded_pki_cert = load_certificate(pki_cert['certificate']) + cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs) + + write_file(cert_file_path, + '\n'.join(encode_certificate(c) for c in cert_full_chain)) + + if 'private' in pki_cert and 'key' in pki_cert['private']: + loaded_key = load_private_key(pki_cert['private']['key'], passphrase=None, wrap_tags=True) + key_pem = encode_private_key(loaded_key, passphrase=None) + write_file(cert_key_path, key_pem) + + # SSL Certificates for backend + for back, back_config in lb['backend'].items(): + if 'ssl' not in back_config: + continue + + if 'ca_certificate' in back_config['ssl']: + ca_name = back_config['ssl']['ca_certificate'] + ca_cert_file_path = os.path.join(load_balancing_dir, f'{ca_name}.pem') + ca_chains = [] + + pki_ca_cert = lb['pki']['ca'][ca_name] + loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) + ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + ca_chains.append('\n'.join(encode_certificate(c) for c in ca_full_chain)) + write_file(ca_cert_file_path, '\n'.join(ca_chains)) + + render(load_balancing_conf_file, 'load-balancing/haproxy.cfg.j2', lb) + render(systemd_override, 'load-balancing/override_haproxy.conf.j2', lb) + + return None + +def apply(lb): + call('systemctl daemon-reload') + if not lb: + call(f'systemctl stop {systemd_service}') + else: + call(f'systemctl reload-or-restart {systemd_service}') + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/load-balancing_wan.py b/src/conf_mode/load-balancing_wan.py new file mode 100644 index 0000000..5da0b90 --- /dev/null +++ b/src/conf_mode/load-balancing_wan.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit +from shutil import rmtree + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdep import set_dependents, call_dependents +from vyos.utils.process import cmd +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +load_balancing_dir = '/run/load-balance' +load_balancing_conf_file = f'{load_balancing_dir}/wlb.conf' +systemd_service = 'vyos-wan-load-balance.service' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['load-balancing', 'wan'] + lb = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + # prune limit key if not set by user + for rule in lb.get('rule', []): + if lb.from_defaults(['rule', rule, 'limit']): + del lb['rule'][rule]['limit'] + + set_dependents('conntrack', conf) + + return lb + + +def verify(lb): + if not lb: + return None + + if 'interface_health' not in lb: + raise ConfigError( + 'A valid WAN load-balance configuration requires an interface with a nexthop!' + ) + + for interface, interface_config in lb['interface_health'].items(): + if 'nexthop' not in interface_config: + raise ConfigError( + f'interface-health {interface} nexthop must be specified!') + + if 'test' in interface_config: + for test_rule, test_config in interface_config['test'].items(): + if 'type' in test_config: + if test_config['type'] == 'user-defined' and 'test_script' not in test_config: + raise ConfigError( + f'test {test_rule} script must be defined for test-script!' + ) + + if 'rule' not in lb: + Warning( + 'At least one rule with an (outbound) interface must be defined for WAN load balancing to be active!' + ) + else: + for rule, rule_config in lb['rule'].items(): + if 'inbound_interface' not in rule_config: + raise ConfigError(f'rule {rule} inbound-interface must be specified!') + if {'failover', 'exclude'} <= set(rule_config): + raise ConfigError(f'rule {rule} failover cannot be configured with exclude!') + if {'limit', 'exclude'} <= set(rule_config): + raise ConfigError(f'rule {rule} limit cannot be used with exclude!') + if 'interface' not in rule_config: + if 'exclude' not in rule_config: + Warning( + f'rule {rule} will be inactive because no (outbound) interfaces have been defined for this rule' + ) + for direction in {'source', 'destination'}: + if direction in rule_config: + if 'protocol' in rule_config and 'port' in rule_config[ + direction]: + if rule_config['protocol'] not in {'tcp', 'udp'}: + raise ConfigError('ports can only be specified when protocol is "tcp" or "udp"') + + +def generate(lb): + if not lb: + # Delete /run/load-balance/wlb.conf + if os.path.isfile(load_balancing_conf_file): + os.unlink(load_balancing_conf_file) + # Delete old directories + if os.path.isdir(load_balancing_dir): + rmtree(load_balancing_dir, ignore_errors=True) + if os.path.exists('/var/run/load-balance/wlb.out'): + os.unlink('/var/run/load-balance/wlb.out') + + return None + + # Create load-balance dir + if not os.path.isdir(load_balancing_dir): + os.mkdir(load_balancing_dir) + + render(load_balancing_conf_file, 'load-balancing/wlb.conf.j2', lb) + + return None + + +def apply(lb): + if not lb: + try: + cmd(f'systemctl stop {systemd_service}') + except Exception as e: + print(f"Error message: {e}") + + else: + cmd('sudo sysctl -w net.netfilter.nf_conntrack_acct=1') + cmd(f'systemctl restart {systemd_service}') + + call_dependents() + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py new file mode 100644 index 0000000..39803fa --- /dev/null +++ b/src/conf_mode/nat.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdep import set_dependents, call_dependents +from vyos.template import render +from vyos.template import is_ip_network +from vyos.utils.kernel import check_kmod +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_args +from vyos.utils.process import cmd +from vyos.utils.process import run +from vyos.utils.network import is_addr_assigned +from vyos.utils.network import interface_exists +from vyos import ConfigError + +from vyos import airbag +airbag.enable() + +k_mod = ['nft_nat', 'nft_chain_nat'] + +nftables_nat_config = '/run/nftables_nat.conf' +nftables_static_nat_conf = '/run/nftables_static-nat-rules.nft' + +valid_groups = [ + 'address_group', + 'domain_group', + 'network_group', + 'port_group' +] + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['nat'] + nat = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + set_dependents('conntrack', conf) + + if not conf.exists(base): + nat['deleted'] = '' + return nat + + nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + # Remove dynamic firewall groups if present: + if 'dynamic_group' in nat['firewall_group']: + del nat['firewall_group']['dynamic_group'] + + return nat + +def verify_rule(config, err_msg, groups_dict): + """ Common verify steps used for both source and destination NAT """ + + if (dict_search('translation.port', config) != None or + dict_search('translation.redirect.port', config) != None or + dict_search('destination.port', config) != None or + dict_search('source.port', config)): + + if config['protocol'] not in ['tcp', 'udp', 'tcp_udp']: + raise ConfigError(f'{err_msg} ports can only be specified when '\ + 'protocol is either tcp, udp or tcp_udp!') + + for side in ['destination', 'source']: + if side in config: + side_conf = config[side] + + if len({'address', 'fqdn'} & set(side_conf)) > 1: + raise ConfigError('Only one of address, fqdn or geoip can be specified') + + if 'group' in side_conf: + if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1: + raise ConfigError('Only one address-group, network-group or domain-group can be specified') + + for group in valid_groups: + if group in side_conf['group']: + group_name = side_conf['group'][group] + error_group = group.replace("_", "-") + + if group in ['address_group', 'network_group', 'domain_group']: + types = [t for t in ['address', 'fqdn'] if t in side_conf] + if types: + raise ConfigError(f'{error_group} and {types[0]} cannot both be defined') + + if group_name and group_name[0] == '!': + group_name = group_name[1:] + + group_obj = dict_search_args(groups_dict, group, group_name) + + if group_obj is None: + raise ConfigError(f'Invalid {error_group} "{group_name}" on nat rule') + + if not group_obj: + Warning(f'{error_group} "{group_name}" has no members!') + + if dict_search_args(side_conf, 'group', 'port_group'): + if 'protocol' not in config: + raise ConfigError('Protocol must be defined if specifying a port-group') + + if config['protocol'] not in ['tcp', 'udp', 'tcp_udp']: + raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port-group') + + if 'load_balance' in config: + for item in ['source-port', 'destination-port']: + if item in config['load_balance']['hash'] and config['protocol'] not in ['tcp', 'udp']: + raise ConfigError('Protocol must be tcp or udp when specifying hash ports') + count = 0 + if 'backend' in config['load_balance']: + for member in config['load_balance']['backend']: + weight = config['load_balance']['backend'][member]['weight'] + count = count + int(weight) + if count != 100: + Warning(f'Sum of weight for nat load balance rule is not 100. You may get unexpected behaviour') + +def verify(nat): + if not nat or 'deleted' in nat: + # no need to verify the CLI as NAT is going to be deactivated + return None + + if dict_search('source.rule', nat): + for rule, config in dict_search('source.rule', nat).items(): + err_msg = f'Source NAT configuration error in rule {rule}:' + + if 'outbound_interface' in config: + if 'name' in config['outbound_interface'] and 'group' in config['outbound_interface']: + raise ConfigError(f'{err_msg} cannot specify both interface group and interface name for nat source rule "{rule}"') + elif 'name' in config['outbound_interface']: + interface_name = config['outbound_interface']['name'] + if interface_name not in 'any': + if interface_name.startswith('!'): + interface_name = interface_name[1:] + if not interface_exists(interface_name): + Warning(f'Interface "{interface_name}" for source NAT rule "{rule}" does not exist!') + else: + group_name = config['outbound_interface']['group'] + if group_name[0] == '!': + group_name = group_name[1:] + group_obj = dict_search_args(nat['firewall_group'], 'interface_group', group_name) + if group_obj is None: + raise ConfigError(f'Invalid interface group "{group_name}" on source nat rule') + if not group_obj: + Warning(f'interface-group "{group_name}" has no members!') + + if not dict_search('translation.address', config) and not dict_search('translation.port', config): + if 'exclude' not in config and 'backend' not in config['load_balance']: + raise ConfigError(f'{err_msg} translation requires address and/or port') + + addr = dict_search('translation.address', config) + if addr != None and addr != 'masquerade' and not is_ip_network(addr): + for ip in addr.split('-'): + if not is_addr_assigned(ip): + Warning(f'IP address {ip} does not exist on the system!') + + # common rule verification + verify_rule(config, err_msg, nat['firewall_group']) + + if dict_search('destination.rule', nat): + for rule, config in dict_search('destination.rule', nat).items(): + err_msg = f'Destination NAT configuration error in rule {rule}:' + + if 'inbound_interface' in config: + if 'name' in config['inbound_interface'] and 'group' in config['inbound_interface']: + raise ConfigError(f'{err_msg} cannot specify both interface group and interface name for destination nat rule "{rule}"') + elif 'name' in config['inbound_interface']: + interface_name = config['inbound_interface']['name'] + if interface_name not in 'any': + if interface_name.startswith('!'): + interface_name = interface_name[1:] + if not interface_exists(interface_name): + Warning(f'Interface "{interface_name}" for destination NAT rule "{rule}" does not exist!') + else: + group_name = config['inbound_interface']['group'] + if group_name[0] == '!': + group_name = group_name[1:] + group_obj = dict_search_args(nat['firewall_group'], 'interface_group', group_name) + if group_obj is None: + raise ConfigError(f'Invalid interface group "{group_name}" on destination nat rule') + if not group_obj: + Warning(f'interface-group "{group_name}" has no members!') + + if not dict_search('translation.address', config) and not dict_search('translation.port', config) and 'redirect' not in config['translation']: + if 'exclude' not in config and 'backend' not in config['load_balance']: + raise ConfigError(f'{err_msg} translation requires address and/or port') + + # common rule verification + verify_rule(config, err_msg, nat['firewall_group']) + + if dict_search('static.rule', nat): + for rule, config in dict_search('static.rule', nat).items(): + err_msg = f'Static NAT configuration error in rule {rule}:' + + if 'inbound_interface' not in config: + raise ConfigError(f'{err_msg} inbound-interface not specified') + + # common rule verification + verify_rule(config, err_msg, nat['firewall_group']) + + return None + +def generate(nat): + if not os.path.exists(nftables_nat_config): + nat['first_install'] = True + + render(nftables_nat_config, 'firewall/nftables-nat.j2', nat) + render(nftables_static_nat_conf, 'firewall/nftables-static-nat.j2', nat) + + # dry-run newly generated configuration + tmp = run(f'nft --check --file {nftables_nat_config}') + if tmp > 0: + raise ConfigError('Configuration file errors encountered!') + + tmp = run(f'nft --check --file {nftables_static_nat_conf}') + if tmp > 0: + raise ConfigError('Configuration file errors encountered!') + + return None + +def apply(nat): + check_kmod(k_mod) + + cmd(f'nft --file {nftables_nat_config}') + cmd(f'nft --file {nftables_static_nat_conf}') + + if not nat or 'deleted' in nat: + os.unlink(nftables_nat_config) + os.unlink(nftables_static_nat_conf) + + call_dependents() + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/nat64.py b/src/conf_mode/nat64.py new file mode 100644 index 0000000..df501ce --- /dev/null +++ b/src/conf_mode/nat64.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# pylint: disable=empty-docstring,missing-module-docstring + +import csv +import os +import re + +from ipaddress import IPv6Network, IPv6Address +from json import dumps as json_write + +from vyos import ConfigError +from vyos import airbag +from vyos.config import Config +from vyos.configdict import is_node_changed +from vyos.utils.dict import dict_search +from vyos.utils.file import write_file +from vyos.utils.kernel import check_kmod +from vyos.utils.process import cmd +from vyos.utils.process import run + +airbag.enable() + +INSTANCE_REGEX = re.compile(r"instance-(\d+)") +JOOL_CONFIG_DIR = "/run/jool" + + +def get_config(config: Config | None = None) -> None: + if config is None: + config = Config() + + base = ["nat64"] + nat64 = config.get_config_dict(base, key_mangling=("-", "_"), get_first_key=True) + + return nat64 + + +def verify(nat64) -> None: + check_kmod(["jool"]) + base_src = ["nat64", "source", "rule"] + + # Load in existing instances so we can destroy any unknown + lines = cmd("jool instance display --csv").splitlines() + for _, instance, _ in csv.reader(lines): + match = INSTANCE_REGEX.fullmatch(instance) + if not match: + # FIXME: Instances that don't match should be ignored but WARN'ed to the user + continue + num = match.group(1) + + rules = nat64.setdefault("source", {}).setdefault("rule", {}) + # Mark it for deletion + if num not in rules: + rules[num] = {"deleted": True} + continue + + # If the user changes the mode, recreate the instance else Jool fails with: + # Jool error: Sorry; you can't change an instance's framework for now. + if is_node_changed(config, base_src + [f"instance-{num}", "mode"]): + rules[num]["recreate"] = True + + # If the user changes the pool6, recreate the instance else Jool fails with: + # Jool error: Sorry; you can't change a NAT64 instance's pool6 for now. + if dict_search("source.prefix", rules[num]) and is_node_changed( + config, + base_src + [num, "source", "prefix"], + ): + rules[num]["recreate"] = True + + if not nat64: + # nothing left to do + return + + if dict_search("source.rule", nat64): + # Ensure only 1 netfilter instance per namespace + nf_rules = filter( + lambda i: "deleted" not in i and i.get('mode') == "netfilter", + nat64["source"]["rule"].values(), + ) + next(nf_rules, None) # Discard the first element + if next(nf_rules, None) is not None: + raise ConfigError( + "Jool permits only 1 NAT64 netfilter instance (per network namespace)" + ) + + for rule, instance in nat64["source"]["rule"].items(): + if "deleted" in instance: + continue + + # Verify that source.prefix is set and is a /96 + if not dict_search("source.prefix", instance): + raise ConfigError(f"Source NAT64 rule {rule} missing source prefix") + src_prefix = IPv6Network(instance["source"]["prefix"]) + if src_prefix.prefixlen != 96: + raise ConfigError(f"Source NAT64 rule {rule} source prefix must be /96") + if (int(src_prefix[0]) & int(IPv6Address('0:0:0:0:ff00::'))) != 0: + raise ConfigError( + f'Source NAT64 rule {rule} source prefix is not RFC6052-compliant: ' + 'bits 64 to 71 (9th octet) must be zeroed' + ) + + pools = dict_search("translation.pool", instance) + if pools: + for num, pool in pools.items(): + if "address" not in pool: + raise ConfigError( + f"Source NAT64 rule {rule} translation pool " + f"{num} missing address/prefix" + ) + if "port" not in pool: + raise ConfigError( + f"Source NAT64 rule {rule} translation pool " + f"{num} missing port(-range)" + ) + + +def generate(nat64) -> None: + if not nat64: + return + + os.makedirs(JOOL_CONFIG_DIR, exist_ok=True) + + if dict_search("source.rule", nat64): + for rule, instance in nat64["source"]["rule"].items(): + if "deleted" in instance: + # Delete the unused instance file + os.unlink(os.path.join(JOOL_CONFIG_DIR, f"instance-{rule}.json")) + continue + + name = f"instance-{rule}" + config = { + "instance": name, + "framework": "netfilter", + "global": { + "pool6": instance["source"]["prefix"], + "manually-enabled": "disable" not in instance, + }, + # "bib": [], + } + + if "description" in instance: + config["comment"] = instance["description"] + + if dict_search("translation.pool", instance): + pool4 = [] + # mark + mark = '' + if dict_search("match.mark", instance): + mark = instance["match"]["mark"] + + for pool in instance["translation"]["pool"].values(): + if "disable" in pool: + continue + + protos = pool.get("protocol", {}).keys() or ("tcp", "udp", "icmp") + for proto in protos: + obj = { + "protocol": proto.upper(), + "prefix": pool["address"], + "port range": pool["port"], + } + if mark: + obj["mark"] = int(mark) + if "description" in pool: + obj["comment"] = pool["description"] + + pool4.append(obj) + + if pool4: + config["pool4"] = pool4 + + write_file(f'{JOOL_CONFIG_DIR}/{name}.json', json_write(config, indent=2)) + + +def apply(nat64) -> None: + if not nat64: + unload_kmod(['jool']) + return + + if dict_search("source.rule", nat64): + # Deletions first to avoid conflicts + for rule, instance in nat64["source"]["rule"].items(): + if not any(k in instance for k in ("deleted", "recreate")): + continue + + ret = run(f"jool instance remove instance-{rule}") + if ret != 0: + raise ConfigError( + f"Failed to remove nat64 source rule {rule} (jool instance instance-{rule})" + ) + + # Now creations + for rule, instance in nat64["source"]["rule"].items(): + if "deleted" in instance: + continue + + name = f"instance-{rule}" + ret = run(f"jool -i {name} file handle {JOOL_CONFIG_DIR}/{name}.json") + if ret != 0: + raise ConfigError(f"Failed to set jool instance {name}") + + +if __name__ == "__main__": + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py new file mode 100644 index 0000000..95dfae3 --- /dev/null +++ b/src/conf_mode/nat66.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdep import set_dependents, call_dependents +from vyos.template import render +from vyos.utils.dict import dict_search +from vyos.utils.kernel import check_kmod +from vyos.utils.network import interface_exists +from vyos.utils.process import cmd +from vyos.utils.process import run +from vyos.template import is_ipv6 +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +k_mod = ['nft_nat', 'nft_chain_nat'] + +nftables_nat66_config = '/run/nftables_nat66.nft' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['nat66'] + nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + set_dependents('conntrack', conf) + + if not conf.exists(base): + nat['deleted'] = '' + return nat + + nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + # Remove dynamic firewall groups if present: + if 'dynamic_group' in nat['firewall_group']: + del nat['firewall_group']['dynamic_group'] + + return nat + +def verify(nat): + if not nat or 'deleted' in nat: + # no need to verify the CLI as NAT66 is going to be deactivated + return None + + if dict_search('source.rule', nat): + for rule, config in dict_search('source.rule', nat).items(): + err_msg = f'Source NAT66 configuration error in rule {rule}:' + + if 'outbound_interface' in config: + if 'name' in config['outbound_interface'] and 'group' in config['outbound_interface']: + raise ConfigError(f'{err_msg} cannot specify both interface group and interface name for nat source rule "{rule}"') + elif 'name' in config['outbound_interface']: + interface_name = config['outbound_interface']['name'] + if interface_name not in 'any': + if interface_name.startswith('!'): + interface_name = interface_name[1:] + if not interface_exists(interface_name): + Warning(f'Interface "{interface_name}" for source NAT66 rule "{rule}" does not exist!') + + addr = dict_search('translation.address', config) + if addr != None: + if addr != 'masquerade' and not is_ipv6(addr): + raise ConfigError(f'IPv6 address {addr} is not a valid address') + else: + if 'exclude' not in config: + raise ConfigError(f'{err_msg} translation address not specified') + + prefix = dict_search('source.prefix', config) + if prefix != None: + if not is_ipv6(prefix): + raise ConfigError(f'{err_msg} source-prefix not specified') + + if dict_search('destination.rule', nat): + for rule, config in dict_search('destination.rule', nat).items(): + err_msg = f'Destination NAT66 configuration error in rule {rule}:' + + if 'inbound_interface' in config: + if 'name' in config['inbound_interface'] and 'group' in config['inbound_interface']: + raise ConfigError(f'{err_msg} cannot specify both interface group and interface name for destination nat rule "{rule}"') + elif 'name' in config['inbound_interface']: + interface_name = config['inbound_interface']['name'] + if interface_name not in 'any': + if interface_name.startswith('!'): + interface_name = interface_name[1:] + if not interface_exists(interface_name): + Warning(f'Interface "{interface_name}" for destination NAT66 rule "{rule}" does not exist!') + + if 'destination' in config and 'group' in config['destination']: + if len({'address_group', 'network_group', 'domain_group'} & set(config['destination']['group'])) > 1: + raise ConfigError('Only one address-group, network-group or domain-group can be specified') + + return None + +def generate(nat): + if not os.path.exists(nftables_nat66_config): + nat['first_install'] = True + + render(nftables_nat66_config, 'firewall/nftables-nat66.j2', nat) + + # dry-run newly generated configuration + tmp = run(f'nft --check --file {nftables_nat66_config}') + if tmp > 0: + raise ConfigError('Configuration file errors encountered!') + + return None + +def apply(nat): + check_kmod(k_mod) + + cmd(f'nft --file {nftables_nat66_config}') + + if not nat or 'deleted' in nat: + os.unlink(nftables_nat66_config) + + call_dependents() + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/nat_cgnat.py b/src/conf_mode/nat_cgnat.py new file mode 100644 index 0000000..3484e58 --- /dev/null +++ b/src/conf_mode/nat_cgnat.py @@ -0,0 +1,475 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import ipaddress +import jmespath +import logging +import os + +from sys import exit +from logging.handlers import SysLogHandler + +from vyos.config import Config +from vyos.configdict import is_node_changed +from vyos.template import render +from vyos.utils.process import cmd +from vyos.utils.process import run +from vyos import ConfigError +from vyos import airbag + +airbag.enable() + + +nftables_cgnat_config = '/run/nftables-cgnat.nft' + +# Logging +logger = logging.getLogger('cgnat') +logger.setLevel(logging.DEBUG) + +syslog_handler = SysLogHandler(address="/dev/log") +syslog_handler.setLevel(logging.INFO) + +formatter = logging.Formatter('%(name)s: %(message)s') +syslog_handler.setFormatter(formatter) + +logger.addHandler(syslog_handler) + + +class IPOperations: + def __init__(self, ip_prefix: str): + self.ip_prefix = ip_prefix + self.ip_network = ipaddress.ip_network(ip_prefix) if '/' in ip_prefix else None + + def get_ips_count(self) -> int: + """Returns the number of IPs in a prefix or range. + + Example: + % ip = IPOperations('192.0.2.0/30') + % ip.get_ips_count() + 4 + % ip = IPOperations('192.0.2.0-192.0.2.2') + % ip.get_ips_count() + 3 + """ + if '-' in self.ip_prefix: + start_ip, end_ip = self.ip_prefix.split('-') + start_ip = ipaddress.ip_address(start_ip) + end_ip = ipaddress.ip_address(end_ip) + return int(end_ip) - int(start_ip) + 1 + elif '/31' in self.ip_prefix: + return 2 + elif '/32' in self.ip_prefix: + return 1 + else: + return sum( + 1 + for _ in [self.ip_network.network_address] + + list(self.ip_network.hosts()) + + [self.ip_network.broadcast_address] + ) + + def convert_prefix_to_list_ips(self) -> list: + """Converts a prefix or IP range to a list of IPs including the network and broadcast addresses. + + Example: + % ip = IPOperations('192.0.2.0/30') + % ip.convert_prefix_to_list_ips() + ['192.0.2.0', '192.0.2.1', '192.0.2.2', '192.0.2.3'] + % + % ip = IPOperations('192.0.0.1-192.0.2.5') + % ip.convert_prefix_to_list_ips() + ['192.0.2.1', '192.0.2.2', '192.0.2.3', '192.0.2.4', '192.0.2.5'] + """ + if '-' in self.ip_prefix: + start_ip, end_ip = self.ip_prefix.split('-') + start_ip = ipaddress.ip_address(start_ip) + end_ip = ipaddress.ip_address(end_ip) + return [ + str(ipaddress.ip_address(ip)) + for ip in range(int(start_ip), int(end_ip) + 1) + ] + elif '/31' in self.ip_prefix: + return [ + str(ip) + for ip in [ + self.ip_network.network_address, + self.ip_network.broadcast_address, + ] + ] + elif '/32' in self.ip_prefix: + return [str(self.ip_network.network_address)] + else: + return [ + str(ip) + for ip in [self.ip_network.network_address] + + list(self.ip_network.hosts()) + + [self.ip_network.broadcast_address] + ] + + def get_prefix_by_ip_range(self) -> list[ipaddress.IPv4Network]: + """Return the common prefix for the address range + + Example: + % ip = IPOperations('100.64.0.1-100.64.0.5') + % ip.get_prefix_by_ip_range() + [IPv4Network('100.64.0.1/32'), IPv4Network('100.64.0.2/31'), IPv4Network('100.64.0.4/31')] + """ + # We do not need to convert the IP range to network + # if it is already in network format + if self.ip_network: + return [self.ip_network] + + # Raise an error if the IP range is not in the correct format + if '-' not in self.ip_prefix: + raise ValueError( + 'Invalid IP range format. Please provide the IP range in CIDR format or with "-" separator.' + ) + # Split the IP range and convert it to IP address objects + range_start, range_end = self.ip_prefix.split('-') + range_start = ipaddress.IPv4Address(range_start) + range_end = ipaddress.IPv4Address(range_end) + + # Return the summarized IP networks list + return list(ipaddress.summarize_address_range(range_start, range_end)) + + +def _delete_conntrack_entries(source_prefixes: list[ipaddress.IPv4Network]) -> None: + """Delete all conntrack entries for the list of prefixes""" + for source_prefix in source_prefixes: + run(f'conntrack -D -s {source_prefix}') + + +def generate_port_rules( + external_hosts: list, + internal_hosts: list, + port_count: int, + global_port_range: str = '1024-65535', +) -> list: + """Generates a list of nftables option rules for the batch file. + + Args: + external_hosts (list): A list of external host IPs. + internal_hosts (list): A list of internal host IPs. + port_count (int): The number of ports required per host. + global_port_range (str): The global port range to be used. Default is '1024-65535'. + + Returns: + list: A list containing two elements: + - proto_map_elements (list): A list of proto map elements. + - other_map_elements (list): A list of other map elements. + """ + rules = [] + proto_map_elements = [] + other_map_elements = [] + start_port, end_port = map(int, global_port_range.split('-')) + total_possible_ports = (end_port - start_port) + 1 + + # Calculate the required number of ports per host + required_ports_per_host = port_count + current_port = start_port + current_external_index = 0 + + for internal_host in internal_hosts: + external_host = external_hosts[current_external_index] + next_end_port = current_port + required_ports_per_host - 1 + + # If the port range exceeds the end_port, move to the next external host + while next_end_port > end_port: + current_external_index = (current_external_index + 1) % len(external_hosts) + external_host = external_hosts[current_external_index] + current_port = start_port + next_end_port = current_port + required_ports_per_host - 1 + + proto_map_elements.append( + f'{internal_host} : {external_host} . {current_port}-{next_end_port}' + ) + other_map_elements.append(f'{internal_host} : {external_host}') + + current_port = next_end_port + 1 + if current_port > end_port: + current_port = start_port + current_external_index += 1 # Move to the next external host + + return [proto_map_elements, other_map_elements] + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['nat', 'cgnat'] + config = conf.get_config_dict( + base, + get_first_key=True, + key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + with_recursive_defaults=True, + ) + + effective_config = conf.get_config_dict( + base, + get_first_key=True, + key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + effective=True, + ) + + # Check if the pool configuration has changed + if not conf.exists(base) or is_node_changed(conf, base + ['pool']): + config['delete_conntrack_entries'] = {} + + # add running config + if effective_config: + config['effective'] = effective_config + + if not conf.exists(base): + config['deleted'] = {} + + return config + + +def verify(config): + # bail out early - looks like removal from running config + if 'deleted' in config: + return None + + if 'pool' not in config: + raise ConfigError(f'Pool must be defined!') + if 'rule' not in config: + raise ConfigError(f'Rule must be defined!') + + for pool in ('external', 'internal'): + if pool not in config['pool']: + raise ConfigError(f'{pool} pool must be defined!') + for pool_name, pool_config in config['pool'][pool].items(): + if 'range' not in pool_config: + raise ConfigError( + f'Range for "{pool} pool {pool_name}" must be defined!' + ) + + external_pools_query = "keys(pool.external)" + external_pools: list = jmespath.search(external_pools_query, config) + internal_pools_query = "keys(pool.internal)" + internal_pools: list = jmespath.search(internal_pools_query, config) + + used_external_pools = {} + used_internal_pools = {} + for rule, rule_config in config['rule'].items(): + if 'source' not in rule_config: + raise ConfigError(f'Rule "{rule}" source pool must be defined!') + if 'pool' not in rule_config['source']: + raise ConfigError(f'Rule "{rule}" source pool must be defined!') + + if 'translation' not in rule_config: + raise ConfigError(f'Rule "{rule}" translation pool must be defined!') + + # Check if pool exists + internal_pool = rule_config['source']['pool'] + if internal_pool not in internal_pools: + raise ConfigError(f'Internal pool "{internal_pool}" does not exist!') + external_pool = rule_config['translation']['pool'] + if external_pool not in external_pools: + raise ConfigError(f'External pool "{external_pool}" does not exist!') + + # Check pool duplication in different rules + if external_pool in used_external_pools: + raise ConfigError( + f'External pool "{external_pool}" is already used in rule ' + f'{used_external_pools[external_pool]} and cannot be used in ' + f'rule {rule}!' + ) + + if internal_pool in used_internal_pools: + raise ConfigError( + f'Internal pool "{internal_pool}" is already used in rule ' + f'{used_internal_pools[internal_pool]} and cannot be used in ' + f'rule {rule}!' + ) + + used_external_pools[external_pool] = rule + used_internal_pools[internal_pool] = rule + + # Check calculation for allocation + external_port_range: str = config['pool']['external'][external_pool]['external_port_range'] + + external_ip_ranges: list = list( + config['pool']['external'][external_pool]['range'] + ) + internal_ip_ranges: list = config['pool']['internal'][internal_pool]['range'] + start_port, end_port = map(int, external_port_range.split('-')) + ports_per_range_count: int = (end_port - start_port) + 1 + + external_list_hosts_count = [] + external_list_hosts = [] + internal_list_hosts_count = [] + internal_list_hosts = [] + for ext_range in external_ip_ranges: + # External hosts count + e_count = IPOperations(ext_range).get_ips_count() + external_list_hosts_count.append(e_count) + # External hosts list + e_hosts = IPOperations(ext_range).convert_prefix_to_list_ips() + external_list_hosts.extend(e_hosts) + for int_range in internal_ip_ranges: + # Internal hosts count + i_count = IPOperations(int_range).get_ips_count() + internal_list_hosts_count.append(i_count) + # Internal hosts list + i_hosts = IPOperations(int_range).convert_prefix_to_list_ips() + internal_list_hosts.extend(i_hosts) + + external_host_count = sum(external_list_hosts_count) + internal_host_count = sum(internal_list_hosts_count) + ports_per_user: int = int( + config['pool']['external'][external_pool]['per_user_limit']['port'] + ) + users_per_extip = ports_per_range_count // ports_per_user + max_users = users_per_extip * external_host_count + + if internal_host_count > max_users: + raise ConfigError( + f'Rule "{rule}" does not have enough ports available for the ' + f'specified parameters' + ) + + +def generate(config): + if 'deleted' in config: + return None + + proto_maps = [] + other_maps = [] + + for rule, rule_config in config['rule'].items(): + ext_pool_name: str = rule_config['translation']['pool'] + int_pool_name: str = rule_config['source']['pool'] + + # Sort the external ranges by sequence + external_ranges: list = sorted( + config['pool']['external'][ext_pool_name]['range'], + key=lambda r: int(config['pool']['external'][ext_pool_name]['range'][r].get('seq', 999999)) + ) + internal_ranges: list = [range for range in config['pool']['internal'][int_pool_name]['range']] + external_list_hosts_count = [] + external_list_hosts = [] + internal_list_hosts_count = [] + internal_list_hosts = [] + + for ext_range in external_ranges: + # External hosts count + e_count = IPOperations(ext_range).get_ips_count() + external_list_hosts_count.append(e_count) + # External hosts list + e_hosts = IPOperations(ext_range).convert_prefix_to_list_ips() + external_list_hosts.extend(e_hosts) + + for int_range in internal_ranges: + # Internal hosts count + i_count = IPOperations(int_range).get_ips_count() + internal_list_hosts_count.append(i_count) + # Internal hosts list + i_hosts = IPOperations(int_range).convert_prefix_to_list_ips() + internal_list_hosts.extend(i_hosts) + + external_host_count = sum(external_list_hosts_count) + internal_host_count = sum(internal_list_hosts_count) + ports_per_user = int( + jmespath.search(f'pool.external."{ext_pool_name}".per_user_limit.port', config) + ) + external_port_range: str = jmespath.search( + f'pool.external."{ext_pool_name}".external_port_range', config + ) + + rule_proto_maps, rule_other_maps = generate_port_rules( + external_list_hosts, internal_list_hosts, ports_per_user, external_port_range + ) + + proto_maps.extend(rule_proto_maps) + other_maps.extend(rule_other_maps) + + config['proto_map_elements'] = ', '.join(proto_maps) + config['other_map_elements'] = ', '.join(other_maps) + + render(nftables_cgnat_config, 'firewall/nftables-cgnat.j2', config) + + # dry-run newly generated configuration + tmp = run(f'nft --check --file {nftables_cgnat_config}') + if tmp > 0: + raise ConfigError('Configuration file errors encountered!') + + +def apply(config): + if 'deleted' in config: + # Cleanup cgnat + cmd('nft delete table ip cgnat') + if os.path.isfile(nftables_cgnat_config): + os.unlink(nftables_cgnat_config) + else: + cmd(f'nft --file {nftables_cgnat_config}') + + # Delete conntrack entries + # if the pool configuration has changed + if 'delete_conntrack_entries' in config and 'effective' in config: + # Prepare the list of internal pool prefixes + internal_pool_prefix_list: list[ipaddress.IPv4Network] = [] + + # Get effective rules configurations + for rule_config in config['effective'].get('rule', {}).values(): + # Get effective internal pool configuration + internal_pool = rule_config['source']['pool'] + # Find the internal IP ranges for the internal pool + internal_ip_ranges: list[str] = config['effective']['pool']['internal'][ + internal_pool + ]['range'] + # Get the IP prefixes for the internal IP range + for internal_range in internal_ip_ranges: + ip_prefix: list[ipaddress.IPv4Network] = IPOperations( + internal_range + ).get_prefix_by_ip_range() + # Add the IP prefixes to the list of all internal pool prefixes + internal_pool_prefix_list += ip_prefix + + # Delete required sources for conntrack + _delete_conntrack_entries(internal_pool_prefix_list) + + # Logging allocations + if 'log_allocation' in config: + allocations = config['proto_map_elements'] + allocations = allocations.split(',') + for allocation in allocations: + try: + # Split based on the delimiters used in the nft data format + internal_host, rest = allocation.split(' : ') + external_host, port_range = rest.split(' . ') + # Log the parsed data + logger.info( + f'Internal host: {internal_host.lstrip()}, external host: {external_host}, Port range: {port_range}') + except ValueError as e: + # Log error message + logger.error(f"Error processing line '{allocation}': {e}") + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/netns.py b/src/conf_mode/netns.py new file mode 100644 index 0000000..b57e46a --- /dev/null +++ b/src/conf_mode/netns.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configdict import node_changed +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos import ConfigError +from vyos import airbag +airbag.enable() + + +def netns_interfaces(c, match): + """ + get NETNS bound interfaces + """ + matched = [] + old_level = c.get_level() + c.set_level(['interfaces']) + section = c.get_config_dict([], get_first_key=True) + for type in section: + interfaces = section[type] + for name in interfaces: + interface = interfaces[name] + if 'netns' in interface: + v = interface.get('netns', '') + if v == match: + matched.append(name) + + c.set_level(old_level) + return matched + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['netns'] + netns = conf.get_config_dict(base, get_first_key=True, + no_tag_node_value_mangle=True) + + # determine which NETNS has been removed + for name in node_changed(conf, base + ['name']): + if 'netns_remove' not in netns: + netns.update({'netns_remove' : {}}) + + netns['netns_remove'][name] = {} + # get NETNS bound interfaces + interfaces = netns_interfaces(conf, name) + if interfaces: netns['netns_remove'][name]['interface'] = interfaces + + return netns + +def verify(netns): + # ensure NETNS is not assigned to any interface + if 'netns_remove' in netns: + for name, config in netns['netns_remove'].items(): + if 'interface' in config: + raise ConfigError(f'Can not remove network namespace "{name}", it '\ + f'still has member interfaces!') + + if 'name' in netns: + for name, config in netns['name'].items(): + # no tests (yet) + pass + + return None + +def generate(netns): + if not netns: + return None + + return None + + +def apply(netns): + + for tmp in (dict_search('netns_remove', netns) or []): + if os.path.isfile(f'/run/netns/{tmp}'): + call(f'ip netns del {tmp}') + + if 'name' in netns: + for name, config in netns['name'].items(): + if not os.path.isfile(f'/run/netns/{name}'): + call(f'ip netns add {name}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py new file mode 100644 index 0000000..215b22b --- /dev/null +++ b/src/conf_mode/pki.py @@ -0,0 +1,486 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import argv +from sys import exit + +from vyos.config import Config +from vyos.config import config_dict_merge +from vyos.configdep import set_dependents +from vyos.configdep import call_dependents +from vyos.configdict import node_changed +from vyos.configdiff import Diff +from vyos.configdiff import get_config_diff +from vyos.defaults import directories +from vyos.pki import is_ca_certificate +from vyos.pki import load_certificate +from vyos.pki import load_public_key +from vyos.pki import load_openssh_public_key +from vyos.pki import load_openssh_private_key +from vyos.pki import load_private_key +from vyos.pki import load_crl +from vyos.pki import load_dh_parameters +from vyos.utils.boot import boot_configuration_complete +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_args +from vyos.utils.dict import dict_search_recursive +from vyos.utils.process import call +from vyos.utils.process import cmd +from vyos.utils.process import is_systemd_service_active +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +vyos_certbot_dir = directories['certbot'] + +# keys to recursively search for under specified path +sync_search = [ + { + 'keys': ['certificate'], + 'path': ['service', 'https'], + }, + { + 'keys': ['certificate', 'ca_certificate'], + 'path': ['interfaces', 'ethernet'], + }, + { + 'keys': ['certificate', 'ca_certificate', 'dh_params', 'shared_secret_key', 'auth_key', 'crypt_key'], + 'path': ['interfaces', 'openvpn'], + }, + { + 'keys': ['ca_certificate'], + 'path': ['interfaces', 'sstpc'], + }, + { + 'keys': ['certificate', 'ca_certificate'], + 'path': ['load_balancing', 'reverse_proxy'], + }, + { + 'keys': ['key'], + 'path': ['protocols', 'rpki', 'cache'], + }, + { + 'keys': ['certificate', 'ca_certificate', 'local_key', 'remote_key'], + 'path': ['vpn', 'ipsec'], + }, + { + 'keys': ['certificate', 'ca_certificate'], + 'path': ['vpn', 'openconnect'], + }, + { + 'keys': ['certificate', 'ca_certificate'], + 'path': ['vpn', 'sstp'], + }, + { + 'keys': ['certificate', 'ca_certificate'], + 'path': ['service', 'stunnel'], + } +] + +# key from other config nodes -> key in pki['changed'] and pki +sync_translate = { + 'certificate': 'certificate', + 'ca_certificate': 'ca', + 'dh_params': 'dh', + 'local_key': 'key_pair', + 'remote_key': 'key_pair', + 'shared_secret_key': 'openvpn', + 'auth_key': 'openvpn', + 'crypt_key': 'openvpn', + 'key': 'openssh', +} + +def certbot_delete(certificate): + if not boot_configuration_complete(): + return + if os.path.exists(f'{vyos_certbot_dir}/renewal/{certificate}.conf'): + cmd(f'certbot delete --non-interactive --config-dir {vyos_certbot_dir} --cert-name {certificate}') + +def certbot_request(name: str, config: dict, dry_run: bool=True): + # We do not call certbot when booting the system - there is no need to do so and + # request new certificates during boot/image upgrade as the certbot configuration + # is stored persistent under /config - thus we do not open the door to transient + # errors + if not boot_configuration_complete(): + return + + domains = '--domains ' + ' --domains '.join(config['domain_name']) + tmp = f'certbot certonly --non-interactive --config-dir {vyos_certbot_dir} --cert-name {name} '\ + f'--standalone --agree-tos --no-eff-email --expand --server {config["url"]} '\ + f'--email {config["email"]} --key-type rsa --rsa-key-size {config["rsa_key_size"]} '\ + f'{domains}' + if 'listen_address' in config: + tmp += f' --http-01-address {config["listen_address"]}' + # verify() does not need to actually request a cert but only test for plausability + if dry_run: + tmp += ' --dry-run' + + cmd(tmp, raising=ConfigError, message=f'ACME certbot request failed for "{name}"!') + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['pki'] + + pki = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + if len(argv) > 1 and argv[1] == 'certbot_renew': + pki['certbot_renew'] = {} + + tmp = node_changed(conf, base + ['ca'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD) + if tmp: + if 'changed' not in pki: pki.update({'changed':{}}) + pki['changed'].update({'ca' : tmp}) + + tmp = node_changed(conf, base + ['certificate'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD) + if tmp: + if 'changed' not in pki: pki.update({'changed':{}}) + pki['changed'].update({'certificate' : tmp}) + + tmp = node_changed(conf, base + ['dh'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD) + if tmp: + if 'changed' not in pki: pki.update({'changed':{}}) + pki['changed'].update({'dh' : tmp}) + + tmp = node_changed(conf, base + ['key-pair'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD) + if tmp: + if 'changed' not in pki: pki.update({'changed':{}}) + pki['changed'].update({'key_pair' : tmp}) + + tmp = node_changed(conf, base + ['openssh'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD) + if tmp: + if 'changed' not in pki: pki.update({'changed':{}}) + pki['changed'].update({'openssh' : tmp}) + + tmp = node_changed(conf, base + ['openvpn', 'shared-secret'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD) + if tmp: + if 'changed' not in pki: pki.update({'changed':{}}) + pki['changed'].update({'openvpn' : tmp}) + + # We only merge on the defaults of there is a configuration at all + if conf.exists(base): + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = conf.get_config_defaults(**pki.kwargs, recursive=True) + # remove ACME default configuration if unused by CLI + if 'certificate' in pki: + for name, cert_config in pki['certificate'].items(): + if 'acme' not in cert_config: + # Remove ACME default values + del default_values['certificate'][name]['acme'] + + # merge CLI and default dictionary + pki = config_dict_merge(default_values, pki) + + # Certbot triggered an external renew of the certificates. + # Mark all ACME based certificates as "changed" to trigger + # update of dependent services + if 'certificate' in pki and 'certbot_renew' in pki: + renew = [] + for name, cert_config in pki['certificate'].items(): + if 'acme' in cert_config: + renew.append(name) + # If triggered externally by certbot, certificate key is not present in changed + if 'changed' not in pki: pki.update({'changed':{}}) + pki['changed'].update({'certificate' : renew}) + + # We need to get the entire system configuration to verify that we are not + # deleting a certificate that is still referenced somewhere! + pki['system'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + D = get_config_diff(conf) + + for search in sync_search: + for key in search['keys']: + changed_key = sync_translate[key] + if 'changed' not in pki or changed_key not in pki['changed']: + continue + + for item_name in pki['changed'][changed_key]: + node_present = False + if changed_key == 'openvpn': + node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name) + else: + node_present = dict_search_args(pki, changed_key, item_name) + + if node_present: + search_dict = dict_search_args(pki['system'], *search['path']) + if not search_dict: + continue + for found_name, found_path in dict_search_recursive(search_dict, key): + if isinstance(found_name, list) and item_name not in found_name: + continue + + if isinstance(found_name, str) and found_name != item_name: + continue + + path = search['path'] + path_str = ' '.join(path + found_path) + #print(f'PKI: Updating config: {path_str} {item_name}') + + if path[0] == 'interfaces': + ifname = found_path[0] + if not D.node_changed_presence(path + [ifname]): + set_dependents(path[1], conf, ifname) + else: + if not D.node_changed_presence(path): + set_dependents(path[1], conf) + + return pki + +def is_valid_certificate(raw_data): + # If it loads correctly we're good, or return False + return load_certificate(raw_data, wrap_tags=True) + +def is_valid_ca_certificate(raw_data): + # Check if this is a valid certificate with CA attributes + cert = load_certificate(raw_data, wrap_tags=True) + if not cert: + return False + return is_ca_certificate(cert) + +def is_valid_public_key(raw_data): + # If it loads correctly we're good, or return False + return load_public_key(raw_data, wrap_tags=True) + +def is_valid_private_key(raw_data, protected=False): + # If it loads correctly we're good, or return False + # With encrypted private keys, we always return true as we cannot ask for password to verify + if protected: + return True + return load_private_key(raw_data, passphrase=None, wrap_tags=True) + +def is_valid_openssh_public_key(raw_data, type): + # If it loads correctly we're good, or return False + return load_openssh_public_key(raw_data, type) + +def is_valid_openssh_private_key(raw_data, protected=False): + # If it loads correctly we're good, or return False + # With encrypted private keys, we always return true as we cannot ask for password to verify + if protected: + return True + return load_openssh_private_key(raw_data, passphrase=None, wrap_tags=True) + +def is_valid_crl(raw_data): + # If it loads correctly we're good, or return False + return load_crl(raw_data, wrap_tags=True) + +def is_valid_dh_parameters(raw_data): + # If it loads correctly we're good, or return False + return load_dh_parameters(raw_data, wrap_tags=True) + +def verify(pki): + if not pki: + return None + + if 'ca' in pki: + for name, ca_conf in pki['ca'].items(): + if 'certificate' in ca_conf: + if not is_valid_ca_certificate(ca_conf['certificate']): + raise ConfigError(f'Invalid certificate on CA certificate "{name}"') + + if 'private' in ca_conf and 'key' in ca_conf['private']: + private = ca_conf['private'] + protected = 'password_protected' in private + + if not is_valid_private_key(private['key'], protected): + raise ConfigError(f'Invalid private key on CA certificate "{name}"') + + if 'crl' in ca_conf: + ca_crls = ca_conf['crl'] + if isinstance(ca_crls, str): + ca_crls = [ca_crls] + + for crl in ca_crls: + if not is_valid_crl(crl): + raise ConfigError(f'Invalid CRL on CA certificate "{name}"') + + if 'certificate' in pki: + for name, cert_conf in pki['certificate'].items(): + if 'certificate' in cert_conf: + if not is_valid_certificate(cert_conf['certificate']): + raise ConfigError(f'Invalid certificate on certificate "{name}"') + + if 'private' in cert_conf and 'key' in cert_conf['private']: + private = cert_conf['private'] + protected = 'password_protected' in private + + if not is_valid_private_key(private['key'], protected): + raise ConfigError(f'Invalid private key on certificate "{name}"') + + if 'acme' in cert_conf: + if 'domain_name' not in cert_conf['acme']: + raise ConfigError(f'At least one domain-name is required to request '\ + f'certificate for "{name}" via ACME!') + + if 'email' not in cert_conf['acme']: + raise ConfigError(f'An email address is required to request '\ + f'certificate for "{name}" via ACME!') + + if 'certbot_renew' not in pki: + # Only run the ACME command if something on this entity changed, + # as this is time intensive + tmp = dict_search('changed.certificate', pki) + if tmp != None and name in tmp: + certbot_request(name, cert_conf['acme']) + + if 'dh' in pki: + for name, dh_conf in pki['dh'].items(): + if 'parameters' in dh_conf: + if not is_valid_dh_parameters(dh_conf['parameters']): + raise ConfigError(f'Invalid DH parameters on "{name}"') + + if 'key_pair' in pki: + for name, key_conf in pki['key_pair'].items(): + if 'public' in key_conf and 'key' in key_conf['public']: + if not is_valid_public_key(key_conf['public']['key']): + raise ConfigError(f'Invalid public key on key-pair "{name}"') + + if 'private' in key_conf and 'key' in key_conf['private']: + private = key_conf['private'] + protected = 'password_protected' in private + if not is_valid_private_key(private['key'], protected): + raise ConfigError(f'Invalid private key on key-pair "{name}"') + + if 'openssh' in pki: + for name, key_conf in pki['openssh'].items(): + if 'public' in key_conf and 'key' in key_conf['public']: + if 'type' not in key_conf['public']: + raise ConfigError(f'Must define OpenSSH public key type for "{name}"') + if not is_valid_openssh_public_key(key_conf['public']['key'], key_conf['public']['type']): + raise ConfigError(f'Invalid OpenSSH public key "{name}"') + + if 'private' in key_conf and 'key' in key_conf['private']: + private = key_conf['private'] + protected = 'password_protected' in private + if not is_valid_openssh_private_key(private['key'], protected): + raise ConfigError(f'Invalid OpenSSH private key "{name}"') + + if 'x509' in pki: + if 'default' in pki['x509']: + default_values = pki['x509']['default'] + if 'country' in default_values: + country = default_values['country'] + if len(country) != 2 or not country.isalpha(): + raise ConfigError(f'Invalid default country value. Value must be 2 alpha characters.') + + if 'changed' in pki: + # if the list is getting longer, we can move to a dict() and also embed the + # search key as value from line 173 or 176 + for search in sync_search: + for key in search['keys']: + changed_key = sync_translate[key] + + if changed_key not in pki['changed']: + continue + + for item_name in pki['changed'][changed_key]: + node_present = False + if changed_key == 'openvpn': + node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name) + else: + node_present = dict_search_args(pki, changed_key, item_name) + + if not node_present: + search_dict = dict_search_args(pki['system'], *search['path']) + + if not search_dict: + continue + + for found_name, found_path in dict_search_recursive(search_dict, key): + if found_name == item_name: + path_str = " ".join(search['path'] + found_path) + raise ConfigError(f'PKI object "{item_name}" still in use by "{path_str}"') + + return None + +def generate(pki): + if not pki: + return None + + # Certbot renewal only needs to re-trigger the services to load up the + # new PEM file + if 'certbot_renew' in pki: + return None + + certbot_list = [] + certbot_list_on_disk = [] + if os.path.exists(f'{vyos_certbot_dir}/live'): + certbot_list_on_disk = [f.path.split('/')[-1] for f in os.scandir(f'{vyos_certbot_dir}/live') if f.is_dir()] + + if 'certificate' in pki: + changed_certificates = dict_search('changed.certificate', pki) + for name, cert_conf in pki['certificate'].items(): + if 'acme' in cert_conf: + certbot_list.append(name) + # generate certificate if not found on disk + if name not in certbot_list_on_disk: + certbot_request(name, cert_conf['acme'], dry_run=False) + elif changed_certificates != None and name in changed_certificates: + # when something for the certificate changed, we should delete it + if name in certbot_list_on_disk: + certbot_delete(name) + certbot_request(name, cert_conf['acme'], dry_run=False) + + # Cleanup certbot configuration and certificates if no longer in use by CLI + # Get foldernames under vyos_certbot_dir which each represent a certbot cert + if os.path.exists(f'{vyos_certbot_dir}/live'): + for cert in certbot_list_on_disk: + if cert not in certbot_list: + # certificate is no longer active on the CLI - remove it + certbot_delete(cert) + + return None + +def apply(pki): + systemd_certbot_name = 'certbot.timer' + if not pki: + call(f'systemctl stop {systemd_certbot_name}') + return None + + has_certbot = False + if 'certificate' in pki: + for name, cert_conf in pki['certificate'].items(): + if 'acme' in cert_conf: + has_certbot = True + break + + if not has_certbot: + call(f'systemctl stop {systemd_certbot_name}') + elif has_certbot and not is_systemd_service_active(systemd_certbot_name): + call(f'systemctl restart {systemd_certbot_name}') + + if 'changed' in pki: + call_dependents() + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py new file mode 100644 index 0000000..a5963e7 --- /dev/null +++ b/src/conf_mode/policy.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.template import render_to_string +from vyos.utils.dict import dict_search +from vyos import ConfigError +from vyos import frr +from vyos import airbag + +airbag.enable() + + +def community_action_compatibility(actions: dict) -> bool: + """ + Check compatibility of values in community and large community sections + :param actions: dictionary with community + :type actions: dict + :return: true if compatible, false if not + :rtype: bool + """ + if ('none' in actions) and ('replace' in actions or 'add' in actions): + return False + if 'replace' in actions and 'add' in actions: + return False + if ('delete' in actions) and ('none' in actions or 'replace' in actions): + return False + return True + + +def extcommunity_action_compatibility(actions: dict) -> bool: + """ + Check compatibility of values in extended community sections + :param actions: dictionary with community + :type actions: dict + :return: true if compatible, false if not + :rtype: bool + """ + if ('none' in actions) and ( + 'rt' in actions or 'soo' in actions or 'bandwidth' in actions or 'bandwidth_non_transitive' in actions): + return False + if ('bandwidth_non_transitive' in actions) and ('bandwidth' not in actions): + return False + return True + +def routing_policy_find(key, dictionary): + # Recursively traverse a dictionary and extract the value assigned to + # a given key as generator object. This is made for routing policies, + # thus also import/export is checked + for k, v in dictionary.items(): + if k == key: + if isinstance(v, dict): + for a, b in v.items(): + if a in ['import', 'export']: + yield b + else: + yield v + elif isinstance(v, dict): + for result in routing_policy_find(key, v): + yield result + elif isinstance(v, list): + for d in v: + if isinstance(d, dict): + for result in routing_policy_find(key, d): + yield result + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['policy'] + policy = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # We also need some additional information from the config, prefix-lists + # and route-maps for instance. They will be used in verify(). + # + # XXX: one MUST always call this without the key_mangling() option! See + # vyos.configverify.verify_common_route_maps() for more information. + tmp = conf.get_config_dict(['protocols'], key_mangling=('-', '_'), + no_tag_node_value_mangle=True) + # Merge policy dict into "regular" config dict + policy = dict_merge(tmp, policy) + return policy + + +def verify(policy): + if not policy: + return None + + for policy_type in ['access_list', 'access_list6', 'as_path_list', + 'community_list', 'extcommunity_list', + 'large_community_list', + 'prefix_list', 'prefix_list6', 'route_map']: + # Bail out early and continue with next policy type + if policy_type not in policy: + continue + + # instance can be an ACL name/number, prefix-list name or route-map name + for instance, instance_config in policy[policy_type].items(): + # If no rule was found within the instance ... sad, but we can leave + # early as nothing needs to be verified + if 'rule' not in instance_config: + continue + + # human readable instance name (hypen instead of underscore) + policy_hr = policy_type.replace('_', '-') + entries = [] + for rule, rule_config in instance_config['rule'].items(): + mandatory_error = f'must be specified for "{policy_hr} {instance} rule {rule}"!' + if 'action' not in rule_config: + raise ConfigError(f'Action {mandatory_error}') + + if policy_type == 'access_list': + if 'source' not in rule_config: + raise ConfigError(f'A source {mandatory_error}') + + if int(instance) in range(100, 200) or int( + instance) in range(2000, 2700): + if 'destination' not in rule_config: + raise ConfigError( + f'A destination {mandatory_error}') + + if policy_type == 'access_list6': + if 'source' not in rule_config: + raise ConfigError(f'A source {mandatory_error}') + + if policy_type in ['as_path_list', 'community_list', + 'extcommunity_list', + 'large_community_list']: + if 'regex' not in rule_config: + raise ConfigError(f'A regex {mandatory_error}') + + if policy_type in ['prefix_list', 'prefix_list6']: + if 'prefix' not in rule_config: + raise ConfigError(f'A prefix {mandatory_error}') + + if rule_config in entries: + raise ConfigError( + f'Rule "{rule}" contains a duplicate prefix definition!') + entries.append(rule_config) + + # route-maps tend to be a bit more complex so they get their own verify() section + if 'route_map' in policy: + for route_map, route_map_config in policy['route_map'].items(): + if 'rule' not in route_map_config: + continue + + for rule, rule_config in route_map_config['rule'].items(): + # Action 'deny' cannot be used with "continue" or "on-match" + # FRR does not validate it T4827, T6676 + if rule_config['action'] == 'deny' and ('continue' in rule_config or 'on_match' in rule_config): + raise ConfigError(f'rule {rule} "continue" or "on-match" cannot be used with action deny!') + + # Specified community-list must exist + tmp = dict_search('match.community.community_list', + rule_config) + if tmp and tmp not in policy.get('community_list', []): + raise ConfigError(f'community-list {tmp} does not exist!') + + # Specified extended community-list must exist + tmp = dict_search('match.extcommunity', rule_config) + if tmp and tmp not in policy.get('extcommunity_list', []): + raise ConfigError( + f'extcommunity-list {tmp} does not exist!') + + # Specified large-community-list must exist + tmp = dict_search('match.large_community.large_community_list', + rule_config) + if tmp and tmp not in policy.get('large_community_list', []): + raise ConfigError( + f'large-community-list {tmp} does not exist!') + + # Specified prefix-list must exist + tmp = dict_search('match.ip.address.prefix_list', rule_config) + if tmp and tmp not in policy.get('prefix_list', []): + raise ConfigError(f'prefix-list {tmp} does not exist!') + + # Specified prefix-list must exist + tmp = dict_search('match.ipv6.address.prefix_list', + rule_config) + if tmp and tmp not in policy.get('prefix_list6', []): + raise ConfigError(f'prefix-list6 {tmp} does not exist!') + + # Specified access_list6 in nexthop must exist + tmp = dict_search('match.ipv6.nexthop.access_list', + rule_config) + if tmp and tmp not in policy.get('access_list6', []): + raise ConfigError(f'access_list6 {tmp} does not exist!') + + # Specified prefix-list6 in nexthop must exist + tmp = dict_search('match.ipv6.nexthop.prefix_list', + rule_config) + if tmp and tmp not in policy.get('prefix_list6', []): + raise ConfigError(f'prefix-list6 {tmp} does not exist!') + + tmp = dict_search('set.community.delete', rule_config) + if tmp and tmp not in policy.get('community_list', []): + raise ConfigError(f'community-list {tmp} does not exist!') + + tmp = dict_search('set.large_community.delete', + rule_config) + if tmp and tmp not in policy.get('large_community_list', []): + raise ConfigError( + f'large-community-list {tmp} does not exist!') + + if 'set' in rule_config: + rule_action = rule_config['set'] + if 'community' in rule_action: + if not community_action_compatibility( + rule_action['community']): + raise ConfigError( + f'Unexpected combination between action replace, add, delete or none in community') + if 'large_community' in rule_action: + if not community_action_compatibility( + rule_action['large_community']): + raise ConfigError( + f'Unexpected combination between action replace, add, delete or none in large-community') + if 'extcommunity' in rule_action: + if not extcommunity_action_compatibility( + rule_action['extcommunity']): + raise ConfigError( + f'Unexpected combination between none, rt, soo, bandwidth, bandwidth-non-transitive in extended-community') + # When routing protocols are active some use prefix-lists, route-maps etc. + # to apply the systems routing policy to the learned or redistributed routes. + # When the "routing policy" changes and policies, route-maps etc. are deleted, + # it is our responsibility to verify that the policy can not be deleted if it + # is used by any routing protocol + if 'protocols' in policy: + for policy_type in ['access_list', 'access_list6', 'as_path_list', + 'community_list', + 'extcommunity_list', 'large_community_list', + 'prefix_list', 'route_map']: + if policy_type in policy: + for policy_name in list(set(routing_policy_find(policy_type, + policy[ + 'protocols']))): + found = False + if policy_name in policy[policy_type]: + found = True + # BGP uses prefix-list for selecting both an IPv4 or IPv6 AFI related + # list - we need to go the extra mile here and check both prefix-lists + if policy_type == 'prefix_list' and 'prefix_list6' in policy and policy_name in \ + policy['prefix_list6']: + found = True + if not found: + tmp = policy_type.replace('_', '-') + raise ConfigError( + f'Can not delete {tmp} "{policy_name}", still in use!') + + return None + + +def generate(policy): + if not policy: + return None + policy['new_frr_config'] = render_to_string('frr/policy.frr.j2', policy) + return None + + +def apply(policy): + bgp_daemon = 'bgpd' + zebra_daemon = 'zebra' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + # The route-map used for the FIB (zebra) is part of the zebra daemon + frr_cfg.load_configuration(bgp_daemon) + frr_cfg.modify_section(r'^bgp as-path access-list .*') + frr_cfg.modify_section(r'^bgp community-list .*') + frr_cfg.modify_section(r'^bgp extcommunity-list .*') + frr_cfg.modify_section(r'^bgp large-community-list .*') + frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', + remove_stop_mark=True) + if 'new_frr_config' in policy: + frr_cfg.add_before(frr.default_add_before, policy['new_frr_config']) + frr_cfg.commit_configuration(bgp_daemon) + + # The route-map used for the FIB (zebra) is part of the zebra daemon + frr_cfg.load_configuration(zebra_daemon) + frr_cfg.modify_section(r'^access-list .*') + frr_cfg.modify_section(r'^ipv6 access-list .*') + frr_cfg.modify_section(r'^ip prefix-list .*') + frr_cfg.modify_section(r'^ipv6 prefix-list .*') + frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', + remove_stop_mark=True) + if 'new_frr_config' in policy: + frr_cfg.add_before(frr.default_add_before, policy['new_frr_config']) + frr_cfg.commit_configuration(zebra_daemon) + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/policy_local-route.py b/src/conf_mode/policy_local-route.py new file mode 100644 index 0000000..331fd97 --- /dev/null +++ b/src/conf_mode/policy_local-route.py @@ -0,0 +1,310 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from itertools import product +from sys import exit + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configdict import node_changed +from vyos.configdict import leaf_node_changed +from vyos.configverify import verify_interface_exists +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + + if config: + conf = config + else: + conf = Config() + base = ['policy'] + + pbr = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + for route in ['local_route', 'local_route6']: + dict_id = 'rule_remove' if route == 'local_route' else 'rule6_remove' + route_key = 'local-route' if route == 'local_route' else 'local-route6' + base_rule = base + [route_key, 'rule'] + + # delete policy local-route + dict = {} + tmp = node_changed(conf, base_rule, key_mangling=('-', '_')) + if tmp: + for rule in (tmp or []): + src = leaf_node_changed(conf, base_rule + [rule, 'source', 'address']) + src_port = leaf_node_changed(conf, base_rule + [rule, 'source', 'port']) + fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) + iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface']) + dst = leaf_node_changed(conf, base_rule + [rule, 'destination', 'address']) + dst_port = leaf_node_changed(conf, base_rule + [rule, 'destination', 'port']) + table = leaf_node_changed(conf, base_rule + [rule, 'set', 'table']) + proto = leaf_node_changed(conf, base_rule + [rule, 'protocol']) + rule_def = {} + if src: + rule_def = dict_merge({'source': {'address': src}}, rule_def) + if src_port: + rule_def = dict_merge({'source': {'port': src_port}}, rule_def) + if fwmk: + rule_def = dict_merge({'fwmark' : fwmk}, rule_def) + if iif: + rule_def = dict_merge({'inbound_interface' : iif}, rule_def) + if dst: + rule_def = dict_merge({'destination': {'address': dst}}, rule_def) + if dst_port: + rule_def = dict_merge({'destination': {'port': dst_port}}, rule_def) + if table: + rule_def = dict_merge({'table' : table}, rule_def) + if proto: + rule_def = dict_merge({'protocol' : proto}, rule_def) + dict = dict_merge({dict_id : {rule : rule_def}}, dict) + pbr.update(dict) + + if not route in pbr: + continue + + # delete policy local-route rule x source x.x.x.x + # delete policy local-route rule x fwmark x + # delete policy local-route rule x destination x.x.x.x + if 'rule' in pbr[route]: + for rule, rule_config in pbr[route]['rule'].items(): + src = leaf_node_changed(conf, base_rule + [rule, 'source', 'address']) + src_port = leaf_node_changed(conf, base_rule + [rule, 'source', 'port']) + fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) + iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface']) + dst = leaf_node_changed(conf, base_rule + [rule, 'destination', 'address']) + dst_port = leaf_node_changed(conf, base_rule + [rule, 'destination', 'port']) + table = leaf_node_changed(conf, base_rule + [rule, 'set', 'table']) + proto = leaf_node_changed(conf, base_rule + [rule, 'protocol']) + # keep track of changes in configuration + # otherwise we might remove an existing node although nothing else has changed + changed = False + + rule_def = {} + # src is None if there are no changes to src + if src is None: + # if src hasn't changed, include it in the removal selector + # if a new selector is added, we have to remove all previous rules without this selector + # to make sure we remove all previous rules with this source(s), it will be included + if 'source' in rule_config: + if 'address' in rule_config['source']: + rule_def = dict_merge({'source': {'address': rule_config['source']['address']}}, rule_def) + else: + # if src is not None, it's previous content will be returned + # this can be an empty array if it's just being set, or the previous value + # either way, something has to be changed and we only want to remove previous values + changed = True + # set the old value for removal if it's not empty + if len(src) > 0: + rule_def = dict_merge({'source': {'address': src}}, rule_def) + + # source port + if src_port is None: + if 'source' in rule_config: + if 'port' in rule_config['source']: + tmp = rule_config['source']['port'] + if isinstance(tmp, str): + tmp = [tmp] + rule_def = dict_merge({'source': {'port': tmp}}, rule_def) + else: + changed = True + if len(src_port) > 0: + rule_def = dict_merge({'source': {'port': src_port}}, rule_def) + + # fwmark + if fwmk is None: + if 'fwmark' in rule_config: + tmp = rule_config['fwmark'] + if isinstance(tmp, str): + tmp = [tmp] + rule_def = dict_merge({'fwmark': tmp}, rule_def) + else: + changed = True + if len(fwmk) > 0: + rule_def = dict_merge({'fwmark' : fwmk}, rule_def) + + # inbound-interface + if iif is None: + if 'inbound_interface' in rule_config: + rule_def = dict_merge({'inbound_interface': rule_config['inbound_interface']}, rule_def) + else: + changed = True + if len(iif) > 0: + rule_def = dict_merge({'inbound_interface' : iif}, rule_def) + + # destination address + if dst is None: + if 'destination' in rule_config: + if 'address' in rule_config['destination']: + rule_def = dict_merge({'destination': {'address': rule_config['destination']['address']}}, rule_def) + else: + changed = True + if len(dst) > 0: + rule_def = dict_merge({'destination': {'address': dst}}, rule_def) + + # destination port + if dst_port is None: + if 'destination' in rule_config: + if 'port' in rule_config['destination']: + tmp = rule_config['destination']['port'] + if isinstance(tmp, str): + tmp = [tmp] + rule_def = dict_merge({'destination': {'port': tmp}}, rule_def) + else: + changed = True + if len(dst_port) > 0: + rule_def = dict_merge({'destination': {'port': dst_port}}, rule_def) + + # table + if table is None: + if 'set' in rule_config and 'table' in rule_config['set']: + rule_def = dict_merge({'table': [rule_config['set']['table']]}, rule_def) + else: + changed = True + if len(table) > 0: + rule_def = dict_merge({'table' : table}, rule_def) + + # protocol + if proto is None: + if 'protocol' in rule_config: + tmp = rule_config['protocol'] + if isinstance(tmp, str): + tmp = [tmp] + rule_def = dict_merge({'protocol': tmp}, rule_def) + else: + changed = True + if len(proto) > 0: + rule_def = dict_merge({'protocol' : proto}, rule_def) + + if changed: + dict = dict_merge({dict_id : {rule : rule_def}}, dict) + pbr.update(dict) + + return pbr + +def verify(pbr): + # bail out early - looks like removal from running config + if not pbr: + return None + + for route in ['local_route', 'local_route6']: + if not route in pbr: + continue + + pbr_route = pbr[route] + if 'rule' in pbr_route: + for rule in pbr_route['rule']: + if ( + 'source' not in pbr_route['rule'][rule] and + 'destination' not in pbr_route['rule'][rule] and + 'fwmark' not in pbr_route['rule'][rule] and + 'inbound_interface' not in pbr_route['rule'][rule] and + 'protocol' not in pbr_route['rule'][rule] + ): + raise ConfigError('Source or destination address or fwmark or inbound-interface or protocol is required!') + + if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']: + raise ConfigError('Table set is required!') + + if 'inbound_interface' in pbr_route['rule'][rule]: + interface = pbr_route['rule'][rule]['inbound_interface'] + verify_interface_exists(pbr, interface) + + return None + +def generate(pbr): + if not pbr: + return None + + return None + +def apply(pbr): + if not pbr: + return None + + # Delete old rule if needed + for rule_rm in ['rule_remove', 'rule6_remove']: + if rule_rm in pbr: + v6 = " -6" if rule_rm == 'rule6_remove' else "" + + for rule, rule_config in pbr[rule_rm].items(): + source = rule_config.get('source', {}).get('address', ['']) + source_port = rule_config.get('source', {}).get('port', ['']) + destination = rule_config.get('destination', {}).get('address', ['']) + destination_port = rule_config.get('destination', {}).get('port', ['']) + fwmark = rule_config.get('fwmark', ['']) + inbound_interface = rule_config.get('inbound_interface', ['']) + protocol = rule_config.get('protocol', ['']) + table = rule_config.get('table', ['']) + + for src, dst, src_port, dst_port, fwmk, iif, proto, table in product( + source, destination, source_port, destination_port, + fwmark, inbound_interface, protocol, table): + f_src = '' if src == '' else f' from {src} ' + f_src_port = '' if src_port == '' else f' sport {src_port} ' + f_dst = '' if dst == '' else f' to {dst} ' + f_dst_port = '' if dst_port == '' else f' dport {dst_port} ' + f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} ' + f_iif = '' if iif == '' else f' iif {iif} ' + f_proto = '' if proto == '' else f' ipproto {proto} ' + f_table = '' if table == '' else f' lookup {table} ' + + call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_proto}{f_src_port}{f_dst_port}{f_fwmk}{f_iif}{f_table}') + + # Generate new config + for route in ['local_route', 'local_route6']: + if not route in pbr: + continue + + v6 = " -6" if route == 'local_route6' else "" + pbr_route = pbr[route] + + if 'rule' in pbr_route: + for rule, rule_config in pbr_route['rule'].items(): + table = rule_config['set'].get('table', '') + source = rule_config.get('source', {}).get('address', ['all']) + source_port = rule_config.get('source', {}).get('port', '') + destination = rule_config.get('destination', {}).get('address', ['all']) + destination_port = rule_config.get('destination', {}).get('port', '') + fwmark = rule_config.get('fwmark', '') + inbound_interface = rule_config.get('inbound_interface', '') + protocol = rule_config.get('protocol', '') + + for src in source: + f_src = f' from {src} ' if src else '' + for dst in destination: + f_dst = f' to {dst} ' if dst else '' + f_src_port = f' sport {source_port} ' if source_port else '' + f_dst_port = f' dport {destination_port} ' if destination_port else '' + f_fwmk = f' fwmark {fwmark} ' if fwmark else '' + f_iif = f' iif {inbound_interface} ' if inbound_interface else '' + f_proto = f' ipproto {protocol} ' if protocol else '' + + call(f'ip{v6} rule add prio {rule}{f_src}{f_dst}{f_proto}{f_src_port}{f_dst_port}{f_fwmk}{f_iif} lookup {table}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/policy_route.py b/src/conf_mode/policy_route.py new file mode 100644 index 0000000..223175b --- /dev/null +++ b/src/conf_mode/policy_route.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from json import loads +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.template import render +from vyos.utils.dict import dict_search_args +from vyos.utils.process import cmd +from vyos.utils.process import run +from vyos.utils.network import get_vrf_tableid +from vyos.defaults import rt_global_table +from vyos.defaults import rt_global_vrf +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +mark_offset = 0x7FFFFFFF +nftables_conf = '/run/nftables_policy.conf' + +valid_groups = [ + 'address_group', + 'domain_group', + 'network_group', + 'port_group', + 'interface_group' +] + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['policy'] + + policy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + policy['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + # Remove dynamic firewall groups if present: + if 'dynamic_group' in policy['firewall_group']: + del policy['firewall_group']['dynamic_group'] + + return policy + +def verify_rule(policy, name, rule_conf, ipv6, rule_id): + icmp = 'icmp' if not ipv6 else 'icmpv6' + if icmp in rule_conf: + icmp_defined = False + if 'type_name' in rule_conf[icmp]: + icmp_defined = True + if 'code' in rule_conf[icmp] or 'type' in rule_conf[icmp]: + raise ConfigError(f'{name} rule {rule_id}: Cannot use ICMP type/code with ICMP type-name') + if 'code' in rule_conf[icmp]: + icmp_defined = True + if 'type' not in rule_conf[icmp]: + raise ConfigError(f'{name} rule {rule_id}: ICMP code can only be defined if ICMP type is defined') + if 'type' in rule_conf[icmp]: + icmp_defined = True + + if icmp_defined and 'protocol' not in rule_conf or rule_conf['protocol'] != icmp: + raise ConfigError(f'{name} rule {rule_id}: ICMP type/code or type-name can only be defined if protocol is ICMP') + + if 'set' in rule_conf: + if 'tcp_mss' in rule_conf['set']: + tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') + if not tcp_flags or 'syn' not in tcp_flags: + raise ConfigError(f'{name} rule {rule_id}: TCP SYN flag must be set to modify TCP-MSS') + + if 'vrf' in rule_conf['set'] and 'table' in rule_conf['set']: + raise ConfigError(f'{name} rule {rule_id}: Cannot set both forwarding route table and VRF') + + tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') + if tcp_flags: + if dict_search_args(rule_conf, 'protocol') != 'tcp': + raise ConfigError('Protocol must be tcp when specifying tcp flags') + + not_flags = dict_search_args(rule_conf, 'tcp', 'flags', 'not') + if not_flags: + duplicates = [flag for flag in tcp_flags if flag in not_flags] + if duplicates: + raise ConfigError(f'Cannot match a tcp flag as set and not set') + + for side in ['destination', 'source']: + if side in rule_conf: + side_conf = rule_conf[side] + + if 'group' in side_conf: + if len({'address_group', 'domain_group', 'network_group'} & set(side_conf['group'])) > 1: + raise ConfigError('Only one address-group, domain-group or network-group can be specified') + + for group in valid_groups: + if group in side_conf['group']: + group_name = side_conf['group'][group] + + if group_name.startswith('!'): + group_name = group_name[1:] + + fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group + error_group = fw_group.replace("_", "-") + group_obj = dict_search_args(policy['firewall_group'], fw_group, group_name) + + if group_obj is None: + raise ConfigError(f'Invalid {error_group} "{group_name}" on policy route rule') + + if not group_obj: + Warning(f'{error_group} "{group_name}" has no members') + + if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'): + if 'protocol' not in rule_conf: + raise ConfigError('Protocol must be defined if specifying a port or port-group') + + if rule_conf['protocol'] not in ['tcp', 'udp', 'tcp_udp']: + raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group') + +def verify(policy): + for route in ['route', 'route6']: + ipv6 = route == 'route6' + if route in policy: + for name, pol_conf in policy[route].items(): + if 'rule' in pol_conf: + for rule_id, rule_conf in pol_conf['rule'].items(): + verify_rule(policy, name, rule_conf, ipv6, rule_id) + + return None + +def generate(policy): + if not os.path.exists(nftables_conf): + policy['first_install'] = True + + render(nftables_conf, 'firewall/nftables-policy.j2', policy) + return None + +def apply_table_marks(policy): + for route in ['route', 'route6']: + if route in policy: + cmd_str = 'ip' if route == 'route' else 'ip -6' + tables = [] + for name, pol_conf in policy[route].items(): + if 'rule' in pol_conf: + for rule_id, rule_conf in pol_conf['rule'].items(): + vrf_table_id = None + set_table = dict_search_args(rule_conf, 'set', 'table') + set_vrf = dict_search_args(rule_conf, 'set', 'vrf') + if set_vrf: + if set_vrf == 'default': + vrf_table_id = rt_global_vrf + else: + vrf_table_id = get_vrf_tableid(set_vrf) + elif set_table: + if set_table == 'main': + vrf_table_id = rt_global_table + else: + vrf_table_id = set_table + if vrf_table_id is not None: + vrf_table_id = int(vrf_table_id) + if vrf_table_id in tables: + continue + tables.append(vrf_table_id) + table_mark = mark_offset - vrf_table_id + cmd(f'{cmd_str} rule add pref {vrf_table_id} fwmark {table_mark} table {vrf_table_id}') + +def cleanup_table_marks(): + for cmd_str in ['ip', 'ip -6']: + json_rules = cmd(f'{cmd_str} -j -N rule list') + rules = loads(json_rules) + for rule in rules: + if 'fwmark' not in rule or 'table' not in rule: + continue + fwmark = rule['fwmark'] + table = int(rule['table']) + if fwmark[:2] == '0x': + fwmark = int(fwmark, 16) + if (int(fwmark) == (mark_offset - table)): + cmd(f'{cmd_str} rule del fwmark {fwmark} table {table}') + +def apply(policy): + install_result = run(f'nft --file {nftables_conf}') + if install_result == 1: + raise ConfigError('Failed to apply policy based routing') + + if 'first_install' not in policy: + cleanup_table_marks() + + apply_table_marks(policy) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_babel.py b/src/conf_mode/protocols_babel.py new file mode 100644 index 0000000..90b6e4a --- /dev/null +++ b/src/conf_mode/protocols_babel.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.config import config_dict_merge +from vyos.configdict import dict_merge +from vyos.configdict import node_changed +from vyos.configverify import verify_access_list +from vyos.configverify import verify_prefix_list +from vyos.utils.dict import dict_search +from vyos.template import render_to_string +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['protocols', 'babel'] + babel = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) + + # FRR has VRF support for different routing daemons. As interfaces belong + # to VRFs - or the global VRF, we need to check for changed interfaces so + # that they will be properly rendered for the FRR config. Also this eases + # removal of interfaces from the running configuration. + interfaces_removed = node_changed(conf, base + ['interface']) + if interfaces_removed: + babel['interface_removed'] = list(interfaces_removed) + + # Bail out early if configuration tree does not exist + if not conf.exists(base): + babel.update({'deleted' : ''}) + return babel + + # We have gathered the dict representation of the CLI, but there are default + # values which we need to update into the dictionary retrieved. + default_values = conf.get_config_defaults(base, key_mangling=('-', '_'), + get_first_key=True, + recursive=True) + + # merge in default values + babel = config_dict_merge(default_values, babel) + + # We also need some additional information from the config, prefix-lists + # and route-maps for instance. They will be used in verify(). + # + # XXX: one MUST always call this without the key_mangling() option! See + # vyos.configverify.verify_common_route_maps() for more information. + tmp = conf.get_config_dict(['policy']) + # Merge policy dict into "regular" config dict + babel = dict_merge(tmp, babel) + return babel + +def verify(babel): + if not babel: + return None + + # verify distribute_list + if "distribute_list" in babel: + acl_keys = { + "ipv4": [ + "distribute_list.ipv4.access_list.in", + "distribute_list.ipv4.access_list.out", + ], + "ipv6": [ + "distribute_list.ipv6.access_list.in", + "distribute_list.ipv6.access_list.out", + ] + } + prefix_list_keys = { + "ipv4": [ + "distribute_list.ipv4.prefix_list.in", + "distribute_list.ipv4.prefix_list.out", + ], + "ipv6":[ + "distribute_list.ipv6.prefix_list.in", + "distribute_list.ipv6.prefix_list.out", + ] + } + for address_family in ["ipv4", "ipv6"]: + for iface_key in babel["distribute_list"].get(address_family, {}).get("interface", {}).keys(): + acl_keys[address_family].extend([ + f"distribute_list.{address_family}.interface.{iface_key}.access_list.in", + f"distribute_list.{address_family}.interface.{iface_key}.access_list.out" + ]) + prefix_list_keys[address_family].extend([ + f"distribute_list.{address_family}.interface.{iface_key}.prefix_list.in", + f"distribute_list.{address_family}.interface.{iface_key}.prefix_list.out" + ]) + + for address_family, keys in acl_keys.items(): + for key in keys: + acl = dict_search(key, babel) + if acl: + verify_access_list(acl, babel, version='6' if address_family == 'ipv6' else '') + + for address_family, keys in prefix_list_keys.items(): + for key in keys: + prefix_list = dict_search(key, babel) + if prefix_list: + verify_prefix_list(prefix_list, babel, version='6' if address_family == 'ipv6' else '') + + +def generate(babel): + if not babel or 'deleted' in babel: + return None + + babel['new_frr_config'] = render_to_string('frr/babeld.frr.j2', babel) + return None + +def apply(babel): + babel_daemon = 'babeld' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + frr_cfg.load_configuration(babel_daemon) + frr_cfg.modify_section('^router babel', stop_pattern='^exit', remove_stop_mark=True) + + for key in ['interface', 'interface_removed']: + if key not in babel: + continue + for interface in babel[key]: + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) + + if 'new_frr_config' in babel: + frr_cfg.add_before(frr.default_add_before, babel['new_frr_config']) + frr_cfg.commit_configuration(babel_daemon) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py new file mode 100644 index 0000000..1361bb1 --- /dev/null +++ b/src/conf_mode/protocols_bfd.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from vyos.config import Config +from vyos.configverify import verify_vrf +from vyos.template import is_ipv6 +from vyos.template import render_to_string +from vyos.utils.network import is_ipv6_link_local +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['protocols', 'bfd'] + bfd = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + # Bail out early if configuration tree does not exist + if not conf.exists(base): + return bfd + + bfd = conf.merge_defaults(bfd, recursive=True) + + return bfd + +def verify(bfd): + if not bfd: + return None + + if 'peer' in bfd: + for peer, peer_config in bfd['peer'].items(): + # IPv6 link local peers require an explicit local address/interface + if is_ipv6_link_local(peer): + if 'source' not in peer_config or len(peer_config['source']) < 2: + raise ConfigError('BFD IPv6 link-local peers require explicit local address and interface setting') + + # IPv6 peers require an explicit local address + if is_ipv6(peer): + if 'source' not in peer_config or 'address' not in peer_config['source']: + raise ConfigError('BFD IPv6 peers require explicit local address setting') + + if 'multihop' in peer_config: + # multihop require source address + if 'source' not in peer_config or 'address' not in peer_config['source']: + raise ConfigError('BFD multihop require source address') + + # multihop and echo-mode cannot be used together + if 'echo_mode' in peer_config: + raise ConfigError('BFD multihop and echo-mode cannot be used together') + + # multihop doesn't accept interface names + if 'source' in peer_config and 'interface' in peer_config['source']: + raise ConfigError('BFD multihop and source interface cannot be used together') + + if 'minimum_ttl' in peer_config and 'multihop' not in peer_config: + raise ConfigError('Minimum TTL is only available for multihop BFD sessions!') + + if 'profile' in peer_config: + profile_name = peer_config['profile'] + if 'profile' not in bfd or profile_name not in bfd['profile']: + raise ConfigError(f'BFD profile "{profile_name}" does not exist!') + + if 'vrf' in peer_config: + verify_vrf(peer_config) + + return None + +def generate(bfd): + if not bfd: + return None + bfd['new_frr_config'] = render_to_string('frr/bfdd.frr.j2', bfd) + +def apply(bfd): + bfd_daemon = 'bfdd' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + frr_cfg.load_configuration(bfd_daemon) + frr_cfg.modify_section('^bfd', stop_pattern='^exit', remove_stop_mark=True) + if 'new_frr_config' in bfd: + frr_cfg.add_before(frr.default_add_before, bfd['new_frr_config']) + frr_cfg.commit_configuration(bfd_daemon) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py new file mode 100644 index 0000000..22f0200 --- /dev/null +++ b/src/conf_mode/protocols_bgp.py @@ -0,0 +1,655 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit +from sys import argv + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configdict import node_changed +from vyos.configverify import verify_prefix_list +from vyos.configverify import verify_route_map +from vyos.configverify import verify_vrf +from vyos.template import is_ip +from vyos.template import is_interface +from vyos.template import render_to_string +from vyos.utils.dict import dict_search +from vyos.utils.network import get_interface_vrf +from vyos.utils.network import is_addr_assigned +from vyos.utils.process import process_named_running +from vyos.utils.process import call +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + vrf = None + if len(argv) > 1: + vrf = argv[1] + + base_path = ['protocols', 'bgp'] + + # eqivalent of the C foo ? 'a' : 'b' statement + base = vrf and ['vrf', 'name', vrf, 'protocols', 'bgp'] or base_path + bgp = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + bgp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # Remove per interface MPLS configuration - get a list if changed + # nodes under the interface tagNode + interfaces_removed = node_changed(conf, base + ['interface']) + if interfaces_removed: + bgp['interface_removed'] = list(interfaces_removed) + + # Assign the name of our VRF context. This MUST be done before the return + # statement below, else on deletion we will delete the default instance + # instead of the VRF instance. + if vrf: + bgp.update({'vrf' : vrf}) + # We can not delete the BGP VRF instance if there is a L3VNI configured + # FRR L3VNI must be deleted first otherwise we will see error: + # "FRR error: Please unconfigure l3vni 3000" + tmp = ['vrf', 'name', vrf, 'vni'] + if conf.exists_effective(tmp): + bgp.update({'vni' : conf.return_effective_value(tmp)}) + # We can safely delete ourself from the dependent vrf list + if vrf in bgp['dependent_vrfs']: + del bgp['dependent_vrfs'][vrf] + + bgp['dependent_vrfs'].update({'default': {'protocols': { + 'bgp': conf.get_config_dict(base_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True)}}}) + + if not conf.exists(base): + # If bgp instance is deleted then mark it + bgp.update({'deleted' : ''}) + return bgp + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + bgp = conf.merge_defaults(bgp, recursive=True) + + # We also need some additional information from the config, prefix-lists + # and route-maps for instance. They will be used in verify(). + # + # XXX: one MUST always call this without the key_mangling() option! See + # vyos.configverify.verify_common_route_maps() for more information. + tmp = conf.get_config_dict(['policy']) + # Merge policy dict into "regular" config dict + bgp = dict_merge(tmp, bgp) + + return bgp + + +def verify_vrf_as_import(search_vrf_name: str, afi_name: str, vrfs_config: dict) -> bool: + """ + :param search_vrf_name: search vrf name in import list + :type search_vrf_name: str + :param afi_name: afi/safi name + :type afi_name: str + :param vrfs_config: configuration dependents vrfs + :type vrfs_config: dict + :return: if vrf in import list retrun true else false + :rtype: bool + """ + for vrf_name, vrf_config in vrfs_config.items(): + import_list = dict_search( + f'protocols.bgp.address_family.{afi_name}.import.vrf', + vrf_config) + if import_list: + if search_vrf_name in import_list: + return True + return False + +def verify_vrf_import_options(afi_config: dict) -> bool: + """ + Search if afi contains one of options + :param afi_config: afi/safi + :type afi_config: dict + :return: if vrf contains rd and route-target options return true else false + :rtype: bool + """ + options = [ + f'rd.vpn.export', + f'route_target.vpn.import', + f'route_target.vpn.export', + f'route_target.vpn.both' + ] + for option in options: + if dict_search(option, afi_config): + return True + return False + +def verify_vrf_import(vrf_name: str, vrfs_config: dict, afi_name: str) -> bool: + """ + Verify if vrf exists and contain options + :param vrf_name: name of VRF + :type vrf_name: str + :param vrfs_config: dependent vrfs config + :type vrfs_config: dict + :param afi_name: afi/safi name + :type afi_name: str + :return: if vrf contains rd and route-target options return true else false + :rtype: bool + """ + if vrf_name != 'default': + verify_vrf({'vrf': vrf_name}) + if dict_search(f'{vrf_name}.protocols.bgp.address_family.{afi_name}', + vrfs_config): + afi_config = \ + vrfs_config[vrf_name]['protocols']['bgp']['address_family'][ + afi_name] + if verify_vrf_import_options(afi_config): + return True + return False + +def verify_vrflist_import(afi_name: str, afi_config: dict, vrfs_config: dict) -> bool: + """ + Call function to verify + if scpecific vrf contains rd and route-target + options return true else false + + :param afi_name: afi/safi name + :type afi_name: str + :param afi_config: afi/safi configuration + :type afi_config: dict + :param vrfs_config: dependent vrfs config + :type vrfs_config:dict + :return: if vrf contains rd and route-target options return true else false + :rtype: bool + """ + for vrf_name in afi_config['import']['vrf']: + if verify_vrf_import(vrf_name, vrfs_config, afi_name): + return True + return False + +def verify_remote_as(peer_config, bgp_config): + if 'remote_as' in peer_config: + return peer_config['remote_as'] + + if 'peer_group' in peer_config: + peer_group_name = peer_config['peer_group'] + tmp = dict_search(f'peer_group.{peer_group_name}.remote_as', bgp_config) + if tmp: return tmp + + if 'interface' in peer_config: + if 'remote_as' in peer_config['interface']: + return peer_config['interface']['remote_as'] + + if 'peer_group' in peer_config['interface']: + peer_group_name = peer_config['interface']['peer_group'] + tmp = dict_search(f'peer_group.{peer_group_name}.remote_as', bgp_config) + if tmp: return tmp + + if 'v6only' in peer_config['interface']: + if 'remote_as' in peer_config['interface']['v6only']: + return peer_config['interface']['v6only']['remote_as'] + if 'peer_group' in peer_config['interface']['v6only']: + peer_group_name = peer_config['interface']['v6only']['peer_group'] + tmp = dict_search(f'peer_group.{peer_group_name}.remote_as', bgp_config) + if tmp: return tmp + + return None + +def verify_afi(peer_config, bgp_config): + # If address_family configured under neighboor + if 'address_family' in peer_config: + return True + + # If address_family configured under peer-group + # if neighbor interface configured + peer_group_name = None + if dict_search('interface.peer_group', peer_config): + peer_group_name = peer_config['interface']['peer_group'] + elif dict_search('interface.v6only.peer_group', peer_config): + peer_group_name = peer_config['interface']['v6only']['peer_group'] + + # if neighbor IP configured. + if 'peer_group' in peer_config: + peer_group_name = peer_config['peer_group'] + if peer_group_name: + tmp = dict_search(f'peer_group.{peer_group_name}.address_family', bgp_config) + if tmp: return True + return False + +def verify(bgp): + if 'deleted' in bgp: + if 'vrf' in bgp: + # Cannot delete vrf if it exists in import vrf list in other vrfs + for tmp_afi in ['ipv4_unicast', 'ipv6_unicast']: + if verify_vrf_as_import(bgp['vrf'], tmp_afi, bgp['dependent_vrfs']): + raise ConfigError(f'Cannot delete VRF instance "{bgp["vrf"]}", ' \ + 'unconfigure "import vrf" commands!') + else: + # We are running in the default VRF context, thus we can not delete + # our main BGP instance if there are dependent BGP VRF instances. + if 'dependent_vrfs' in bgp: + for vrf, vrf_options in bgp['dependent_vrfs'].items(): + if vrf != 'default': + if dict_search('protocols.bgp', vrf_options): + raise ConfigError('Cannot delete default BGP instance, ' \ + 'dependent VRF instance(s) exist(s)!') + if 'vni' in vrf_options: + raise ConfigError('Cannot delete default BGP instance, ' \ + 'dependent L3VNI exists!') + + return None + + if 'system_as' not in bgp: + raise ConfigError('BGP system-as number must be defined!') + + # Verify BMP + if 'bmp' in bgp: + # check bmp flag "bgpd -d -F traditional --daemon -A 127.0.0.1 -M rpki -M bmp" + if not process_named_running('bgpd', 'bmp'): + raise ConfigError( + f'"bmp" flag is not found in bgpd. Configure "set system frr bmp" and restart bgp process' + ) + # check bmp target + if 'target' in bgp['bmp']: + for target, target_config in bgp['bmp']['target'].items(): + if 'address' not in target_config: + raise ConfigError(f'BMP target "{target}" address must be defined!') + + # Verify vrf on interface and bgp section + if 'interface' in bgp: + for interface in bgp['interface']: + error_msg = f'Interface "{interface}" belongs to different VRF instance' + tmp = get_interface_vrf(interface) + if 'vrf' in bgp: + if bgp['vrf'] != tmp: + vrf = bgp['vrf'] + raise ConfigError(f'{error_msg} "{vrf}"!') + elif tmp != 'default': + raise ConfigError(f'{error_msg} "{tmp}"!') + + peer_groups_context = dict() + # Common verification for both peer-group and neighbor statements + for neighbor in ['neighbor', 'peer_group']: + # bail out early if there is no neighbor or peer-group statement + # this also saves one indention level + if neighbor not in bgp: + continue + + for peer, peer_config in bgp[neighbor].items(): + # Only regular "neighbor" statement can have a peer-group set + # Check if the configure peer-group exists + if 'peer_group' in peer_config: + peer_group = peer_config['peer_group'] + if 'peer_group' not in bgp or peer_group not in bgp['peer_group']: + raise ConfigError(f'Specified peer-group "{peer_group}" for '\ + f'neighbor "{neighbor}" does not exist!') + + if 'remote_as' in peer_config: + is_ibgp = True + if peer_config['remote_as'] != 'internal' and \ + peer_config['remote_as'] != bgp['system_as']: + is_ibgp = False + + if peer_group not in peer_groups_context: + peer_groups_context[peer_group] = is_ibgp + elif peer_groups_context[peer_group] != is_ibgp: + raise ConfigError(f'Peer-group members must be ' + f'all internal or all external') + + if 'local_role' in peer_config: + #Ensure Local Role has only one value. + if len(peer_config['local_role']) > 1: + raise ConfigError(f'Only one local role can be specified for peer "{peer}"!') + + if 'local_as' in peer_config: + if len(peer_config['local_as']) > 1: + raise ConfigError(f'Only one local-as number can be specified for peer "{peer}"!') + + # Neighbor local-as override can not be the same as the local-as + # we use for this BGP instane! + asn = list(peer_config['local_as'].keys())[0] + if asn == bgp['system_as']: + raise ConfigError('Cannot have local-as same as system-as number') + + # Neighbor AS specified for local-as and remote-as can not be the same + if dict_search('remote_as', peer_config) == asn and neighbor != 'peer_group': + raise ConfigError(f'Neighbor "{peer}" has local-as specified which is '\ + 'the same as remote-as, this is not allowed!') + + # ttl-security and ebgp-multihop can't be used in the same configration + if 'ebgp_multihop' in peer_config and 'ttl_security' in peer_config: + raise ConfigError('You can not set both ebgp-multihop and ttl-security hops') + + # interface and ebgp-multihop can't be used in the same configration + if 'ebgp_multihop' in peer_config and 'interface' in peer_config: + raise ConfigError(f'Ebgp-multihop can not be used with directly connected '\ + f'neighbor "{peer}"') + + # Check if neighbor has both override capability and strict capability match + # configured at the same time. + if 'override_capability' in peer_config and 'strict_capability_match' in peer_config: + raise ConfigError(f'Neighbor "{peer}" cannot have both override-capability and '\ + 'strict-capability-match configured at the same time!') + + # Check spaces in the password + if 'password' in peer_config and ' ' in peer_config['password']: + raise ConfigError('Whitespace is not allowed in passwords!') + + # Some checks can/must only be done on a neighbor and not a peer-group + if neighbor == 'neighbor': + # remote-as must be either set explicitly for the neighbor + # or for the entire peer-group + if not verify_remote_as(peer_config, bgp): + raise ConfigError(f'Neighbor "{peer}" remote-as must be set!') + + if not verify_afi(peer_config, bgp): + Warning(f'BGP neighbor "{peer}" requires address-family!') + + # Peer-group member cannot override remote-as of peer-group + if 'peer_group' in peer_config: + peer_group = peer_config['peer_group'] + if 'remote_as' in peer_config and 'remote_as' in bgp['peer_group'][peer_group]: + raise ConfigError(f'Peer-group member "{peer}" cannot override remote-as of peer-group "{peer_group}"!') + if 'interface' in peer_config: + if 'peer_group' in peer_config['interface']: + peer_group = peer_config['interface']['peer_group'] + if 'remote_as' in peer_config['interface'] and 'remote_as' in bgp['peer_group'][peer_group]: + raise ConfigError(f'Peer-group member "{peer}" cannot override remote-as of peer-group "{peer_group}"!') + if 'v6only' in peer_config['interface']: + if 'peer_group' in peer_config['interface']['v6only']: + peer_group = peer_config['interface']['v6only']['peer_group'] + if 'remote_as' in peer_config['interface']['v6only'] and 'remote_as' in bgp['peer_group'][peer_group]: + raise ConfigError(f'Peer-group member "{peer}" cannot override remote-as of peer-group "{peer_group}"!') + + # Only checks for ipv4 and ipv6 neighbors + # Check if neighbor address is assigned as system interface address + vrf = None + vrf_error_msg = f' in default VRF!' + if 'vrf' in bgp: + vrf = bgp['vrf'] + vrf_error_msg = f' in VRF "{vrf}"!' + + if is_ip(peer) and is_addr_assigned(peer, vrf): + raise ConfigError(f'Can not configure local address as neighbor "{peer}"{vrf_error_msg}') + elif is_interface(peer): + if 'peer_group' in peer_config: + raise ConfigError(f'peer-group must be set under the interface node of "{peer}"') + if 'remote_as' in peer_config: + raise ConfigError(f'remote-as must be set under the interface node of "{peer}"') + if 'source_interface' in peer_config['interface']: + raise ConfigError(f'"source-interface" option not allowed for neighbor "{peer}"') + + # Local-AS allowed only for EBGP peers + if 'local_as' in peer_config: + remote_as = verify_remote_as(peer_config, bgp) + if remote_as == bgp['system_as']: + raise ConfigError(f'local-as configured for "{peer}", allowed only for eBGP peers!') + + for afi in ['ipv4_unicast', 'ipv4_multicast', 'ipv4_labeled_unicast', 'ipv4_flowspec', + 'ipv6_unicast', 'ipv6_multicast', 'ipv6_labeled_unicast', 'ipv6_flowspec', + 'l2vpn_evpn']: + # Bail out early if address family is not configured + if 'address_family' not in peer_config or afi not in peer_config['address_family']: + continue + + # Check if neighbor has both ipv4 unicast and ipv4 labeled unicast configured at the same time. + if 'ipv4_unicast' in peer_config['address_family'] and 'ipv4_labeled_unicast' in peer_config['address_family']: + raise ConfigError(f'Neighbor "{peer}" cannot have both ipv4-unicast and ipv4-labeled-unicast configured at the same time!') + + # Check if neighbor has both ipv6 unicast and ipv6 labeled unicast configured at the same time. + if 'ipv6_unicast' in peer_config['address_family'] and 'ipv6_labeled_unicast' in peer_config['address_family']: + raise ConfigError(f'Neighbor "{peer}" cannot have both ipv6-unicast and ipv6-labeled-unicast configured at the same time!') + + afi_config = peer_config['address_family'][afi] + + if 'conditionally_advertise' in afi_config: + if 'advertise_map' not in afi_config['conditionally_advertise']: + raise ConfigError('Must speficy advertise-map when conditionally-advertise is in use!') + # Verify advertise-map (which is a route-map) exists + verify_route_map(afi_config['conditionally_advertise']['advertise_map'], bgp) + + if ('exist_map' not in afi_config['conditionally_advertise'] and + 'non_exist_map' not in afi_config['conditionally_advertise']): + raise ConfigError('Must either speficy exist-map or non-exist-map when ' \ + 'conditionally-advertise is in use!') + + if {'exist_map', 'non_exist_map'} <= set(afi_config['conditionally_advertise']): + raise ConfigError('Can not specify both exist-map and non-exist-map for ' \ + 'conditionally-advertise!') + + if 'exist_map' in afi_config['conditionally_advertise']: + verify_route_map(afi_config['conditionally_advertise']['exist_map'], bgp) + + if 'non_exist_map' in afi_config['conditionally_advertise']: + verify_route_map(afi_config['conditionally_advertise']['non_exist_map'], bgp) + + # T4332: bgp deterministic-med cannot be disabled while addpath-tx-bestpath-per-AS is in use + if 'addpath_tx_per_as' in afi_config: + if dict_search('parameters.deterministic_med', bgp) == None: + raise ConfigError('addpath-tx-per-as requires BGP deterministic-med paramtere to be set!') + + # Validate if configured Prefix list exists + if 'prefix_list' in afi_config: + for tmp in ['import', 'export']: + if tmp not in afi_config['prefix_list']: + # bail out early + continue + if afi == 'ipv4_unicast': + verify_prefix_list(afi_config['prefix_list'][tmp], bgp) + elif afi == 'ipv6_unicast': + verify_prefix_list(afi_config['prefix_list'][tmp], bgp, version='6') + + if 'route_map' in afi_config: + for tmp in ['import', 'export']: + if tmp in afi_config['route_map']: + verify_route_map(afi_config['route_map'][tmp], bgp) + + if 'route_reflector_client' in afi_config: + peer_group_as = peer_config.get('remote_as') + + if peer_group_as is None or (peer_group_as != 'internal' and peer_group_as != bgp['system_as']): + raise ConfigError('route-reflector-client only supported for iBGP peers') + else: + if 'peer_group' in peer_config: + peer_group_as = dict_search(f'peer_group.{peer_group}.remote_as', bgp) + if peer_group_as is None or (peer_group_as != 'internal' and peer_group_as != bgp['system_as']): + raise ConfigError('route-reflector-client only supported for iBGP peers') + + # T5833 not all AFIs are supported for VRF + if 'vrf' in bgp and 'address_family' in peer_config: + unsupported_vrf_afi = { + 'ipv4_flowspec', + 'ipv6_flowspec', + 'ipv4_labeled_unicast', + 'ipv6_labeled_unicast', + 'ipv4_vpn', + 'ipv6_vpn', + } + for afi in peer_config['address_family']: + if afi in unsupported_vrf_afi: + raise ConfigError( + f"VRF is not allowed for address-family '{afi.replace('_', '-')}'" + ) + + # Throw an error if a peer group is not configured for allow range + for prefix in dict_search('listen.range', bgp) or []: + # we can not use dict_search() here as prefix contains dots ... + if 'peer_group' not in bgp['listen']['range'][prefix]: + raise ConfigError(f'Listen range for prefix "{prefix}" has no peer group configured.') + + peer_group = bgp['listen']['range'][prefix]['peer_group'] + if 'peer_group' not in bgp or peer_group not in bgp['peer_group']: + raise ConfigError(f'Peer-group "{peer_group}" for listen range "{prefix}" does not exist!') + + if not verify_remote_as(bgp['listen']['range'][prefix], bgp): + raise ConfigError(f'Peer-group "{peer_group}" requires remote-as to be set!') + + # Throw an error if the global administrative distance parameters aren't all filled out. + if dict_search('parameters.distance.global', bgp) != None: + for key in ['external', 'internal', 'local']: + if dict_search(f'parameters.distance.global.{key}', bgp) == None: + raise ConfigError('Missing mandatory configuration option for '\ + f'global administrative distance {key}!') + + # TCP keepalive requires all three parameters to be set + if dict_search('parameters.tcp_keepalive', bgp) != None: + if not {'idle', 'interval', 'probes'} <= set(bgp['parameters']['tcp_keepalive']): + raise ConfigError('TCP keepalive incomplete - idle, keepalive and probes must be set') + + # Address Family specific validation + if 'address_family' in bgp: + for afi, afi_config in bgp['address_family'].items(): + if 'distance' in afi_config: + # Throw an error if the address family specific administrative + # distance parameters aren't all filled out. + for key in ['external', 'internal', 'local']: + if key not in afi_config['distance']: + raise ConfigError('Missing mandatory configuration option for '\ + f'{afi} administrative distance {key}!') + + if afi in ['ipv4_unicast', 'ipv6_unicast']: + vrf_name = bgp['vrf'] if dict_search('vrf', bgp) else 'default' + # Verify if currant VRF contains rd and route-target options + # and does not exist in import list in other VRFs + if dict_search(f'rd.vpn.export', afi_config): + if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): + raise ConfigError( + 'Command "import vrf" conflicts with "rd vpn export" command!') + if not dict_search('parameters.router_id', bgp): + Warning(f'BGP "router-id" is required when using "rd" and "route-target"!') + + if dict_search('route_target.vpn.both', afi_config): + if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): + raise ConfigError( + 'Command "import vrf" conflicts with "route-target vpn both" command!') + if dict_search('route_target.vpn.export', afi_config): + raise ConfigError( + 'Command "route-target vpn export" conflicts '\ + 'with "route-target vpn both" command!') + if dict_search('route_target.vpn.import', afi_config): + raise ConfigError( + 'Command "route-target vpn import" conflicts '\ + 'with "route-target vpn both" command!') + + if dict_search('route_target.vpn.import', afi_config): + if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): + raise ConfigError( + 'Command "import vrf conflicts" with "route-target vpn import" command!') + + if dict_search('route_target.vpn.export', afi_config): + if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): + raise ConfigError( + 'Command "import vrf" conflicts with "route-target vpn export" command!') + + # Verify if VRFs in import do not contain rd + # and route-target options + if dict_search('import.vrf', afi_config) is not None: + # Verify if VRF with import does not contain rd + # and route-target options + if verify_vrf_import_options(afi_config): + raise ConfigError( + 'Please unconfigure "import vrf" commands before using vpn commands in the same VRF!') + # Verify if VRFs in import list do not contain rd + # and route-target options + if verify_vrflist_import(afi, afi_config, bgp['dependent_vrfs']): + raise ConfigError( + 'Please unconfigure import vrf commands before using vpn commands in dependent VRFs!') + + # FRR error: please unconfigure vpn to vrf commands before + # using import vrf commands + if 'vpn' in afi_config['import'] or dict_search('export.vpn', afi_config) != None: + raise ConfigError('Please unconfigure VPN to VRF commands before '\ + 'using "import vrf" commands!') + + # Verify that the export/import route-maps do exist + for export_import in ['export', 'import']: + tmp = dict_search(f'route_map.vpn.{export_import}', afi_config) + if tmp: verify_route_map(tmp, bgp) + + # per-vrf sid and per-af sid are mutually exclusive + if 'sid' in afi_config and 'sid' in bgp: + raise ConfigError('SID per VRF and SID per address-family are mutually exclusive!') + + # Checks only required for L2VPN EVPN + if afi in ['l2vpn_evpn']: + if 'vni' in afi_config: + for vni, vni_config in afi_config['vni'].items(): + if 'rd' in vni_config and 'advertise_all_vni' not in afi_config: + raise ConfigError('BGP EVPN "rd" requires "advertise-all-vni" to be set!') + if 'route_target' in vni_config and 'advertise_all_vni' not in afi_config: + raise ConfigError('BGP EVPN "route-target" requires "advertise-all-vni" to be set!') + + return None + +def generate(bgp): + if not bgp or 'deleted' in bgp: + return None + + bgp['frr_bgpd_config'] = render_to_string('frr/bgpd.frr.j2', bgp) + return None + +def apply(bgp): + if 'deleted' in bgp: + # We need to ensure that the L3VNI is deleted first. + # This is not possible with old config backend + # priority bug + if {'vrf', 'vni'} <= set(bgp): + call('vtysh -c "conf t" -c "vrf {vrf}" -c "no vni {vni}"'.format(**bgp)) + + bgp_daemon = 'bgpd' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + # Generate empty helper string which can be ammended to FRR commands, it + # will be either empty (default VRF) or contain the "vrf <name" statement + vrf = '' + if 'vrf' in bgp: + vrf = ' vrf ' + bgp['vrf'] + + frr_cfg.load_configuration(bgp_daemon) + + # Remove interface specific config + for key in ['interface', 'interface_removed']: + if key not in bgp: + continue + for interface in bgp[key]: + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) + + frr_cfg.modify_section(f'^router bgp \d+{vrf}', stop_pattern='^exit', remove_stop_mark=True) + if 'frr_bgpd_config' in bgp: + frr_cfg.add_before(frr.default_add_before, bgp['frr_bgpd_config']) + frr_cfg.commit_configuration(bgp_daemon) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py new file mode 100644 index 0000000..c13e52a --- /dev/null +++ b/src/conf_mode/protocols_eigrp.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit +from sys import argv + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configverify import verify_vrf +from vyos.template import render_to_string +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + vrf = None + if len(argv) > 1: + vrf = argv[1] + + base_path = ['protocols', 'eigrp'] + + # eqivalent of the C foo ? 'a' : 'b' statement + base = vrf and ['vrf', 'name', vrf, 'protocols', 'eigrp'] or base_path + eigrp = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + # Assign the name of our VRF context. This MUST be done before the return + # statement below, else on deletion we will delete the default instance + # instead of the VRF instance. + if vrf: eigrp.update({'vrf' : vrf}) + + if not conf.exists(base): + eigrp.update({'deleted' : ''}) + if not vrf: + # We are running in the default VRF context, thus we can not delete + # our main EIGRP instance if there are dependent EIGRP VRF instances. + eigrp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + return eigrp + + # We also need some additional information from the config, prefix-lists + # and route-maps for instance. They will be used in verify(). + # + # XXX: one MUST always call this without the key_mangling() option! See + # vyos.configverify.verify_common_route_maps() for more information. + tmp = conf.get_config_dict(['policy']) + # Merge policy dict into "regular" config dict + eigrp = dict_merge(tmp, eigrp) + + return eigrp + +def verify(eigrp): + if not eigrp or 'deleted' in eigrp: + return + + if 'system_as' not in eigrp: + raise ConfigError('EIGRP system-as must be defined!') + + if 'vrf' in eigrp: + verify_vrf(eigrp) + +def generate(eigrp): + if not eigrp or 'deleted' in eigrp: + return None + + eigrp['frr_eigrpd_config'] = render_to_string('frr/eigrpd.frr.j2', eigrp) + +def apply(eigrp): + eigrp_daemon = 'eigrpd' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + # Generate empty helper string which can be ammended to FRR commands, it + # will be either empty (default VRF) or contain the "vrf <name" statement + vrf = '' + if 'vrf' in eigrp: + vrf = ' vrf ' + eigrp['vrf'] + + frr_cfg.load_configuration(eigrp_daemon) + frr_cfg.modify_section(f'^router eigrp \d+{vrf}', stop_pattern='^exit', remove_stop_mark=True) + if 'frr_eigrpd_config' in eigrp: + frr_cfg.add_before(frr.default_add_before, eigrp['frr_eigrpd_config']) + frr_cfg.commit_configuration(eigrp_daemon) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_failover.py b/src/conf_mode/protocols_failover.py new file mode 100644 index 0000000..e7e44db --- /dev/null +++ b/src/conf_mode/protocols_failover.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json + +from pathlib import Path + +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag + +airbag.enable() + + +service_name = 'vyos-failover' +service_conf = Path(f'/run/{service_name}.conf') +systemd_service = '/run/systemd/system/vyos-failover.service' +rt_proto_failover = '/etc/iproute2/rt_protos.d/failover.conf' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['protocols', 'failover'] + failover = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) + + # Set default values only if we set config + if failover.get('route') is not None: + failover = conf.merge_defaults(failover, recursive=True) + + return failover + +def verify(failover): + # bail out early - looks like removal from running config + if not failover: + return None + + if 'route' not in failover: + raise ConfigError(f'Failover "route" is mandatory!') + + for route, route_config in failover['route'].items(): + if not route_config.get('next_hop'): + raise ConfigError(f'Next-hop for "{route}" is mandatory!') + + for next_hop, next_hop_config in route_config.get('next_hop').items(): + if 'interface' not in next_hop_config: + raise ConfigError(f'Interface for route "{route}" next-hop "{next_hop}" is mandatory!') + + if not next_hop_config.get('check'): + raise ConfigError(f'Check target for next-hop "{next_hop}" is mandatory!') + + if 'target' not in next_hop_config['check']: + raise ConfigError(f'Check target for next-hop "{next_hop}" is mandatory!') + + check_type = next_hop_config['check']['type'] + if check_type == 'tcp' and 'port' not in next_hop_config['check']: + raise ConfigError(f'Check port for next-hop "{next_hop}" and type TCP is mandatory!') + + return None + +def generate(failover): + if not failover: + service_conf.unlink(missing_ok=True) + return None + + # Add own rt_proto 'failover' + # Helps to detect all own routes 'proto failover' + with open(rt_proto_failover, 'w') as f: + f.write('111 failover\n') + + # Write configuration file + conf_json = json.dumps(failover, indent=4) + service_conf.write_text(conf_json) + render(systemd_service, 'protocols/systemd_vyos_failover_service.j2', failover) + + return None + +def apply(failover): + if not failover: + call(f'systemctl stop {service_name}.service') + call('ip route flush protocol failover') + else: + call('systemctl daemon-reload') + call(f'systemctl restart {service_name}.service') + call(f'ip route flush protocol failover') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_igmp-proxy.py b/src/conf_mode/protocols_igmp-proxy.py new file mode 100644 index 0000000..9a07adf --- /dev/null +++ b/src/conf_mode/protocols_igmp-proxy.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configverify import verify_interface_exists +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/etc/igmpproxy.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['protocols', 'igmp-proxy'] + igmp_proxy = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_defaults=True) + + if conf.exists(['protocols', 'igmp']): + igmp_proxy.update({'igmp_configured': ''}) + + if conf.exists(['protocols', 'pim']): + igmp_proxy.update({'pim_configured': ''}) + + return igmp_proxy + +def verify(igmp_proxy): + # bail out early - looks like removal from running config + if not igmp_proxy or 'disable' in igmp_proxy: + return None + + if 'igmp_configured' in igmp_proxy or 'pim_configured' in igmp_proxy: + raise ConfigError('Can not configure both IGMP proxy and PIM '\ + 'at the same time') + + # at least two interfaces are required, one upstream and one downstream + if 'interface' not in igmp_proxy or len(igmp_proxy['interface']) < 2: + raise ConfigError('Must define exactly one upstream and at least one ' \ + 'downstream interface!') + + upstream = 0 + for interface, config in igmp_proxy['interface'].items(): + verify_interface_exists(igmp_proxy, interface) + if dict_search('role', config) == 'upstream': + upstream += 1 + + if upstream == 0: + raise ConfigError('At least 1 upstream interface is required!') + elif upstream > 1: + raise ConfigError('Only 1 upstream interface allowed!') + + return None + +def generate(igmp_proxy): + # bail out early - looks like removal from running config + if not igmp_proxy: + return None + + # bail out early - service is disabled, but inform user + if 'disable' in igmp_proxy: + Warning('IGMP Proxy will be deactivated because it is disabled') + return None + + render(config_file, 'igmp-proxy/igmpproxy.conf.j2', igmp_proxy) + + return None + +def apply(igmp_proxy): + if not igmp_proxy or 'disable' in igmp_proxy: + # IGMP Proxy support is removed in the commit + call('systemctl stop igmpproxy.service') + if os.path.exists(config_file): + os.unlink(config_file) + else: + call('systemctl restart igmpproxy.service') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py new file mode 100644 index 0000000..ba2f3cf --- /dev/null +++ b/src/conf_mode/protocols_isis.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit +from sys import argv + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configdict import node_changed +from vyos.configverify import verify_common_route_maps +from vyos.configverify import verify_interface_exists +from vyos.ifconfig import Interface +from vyos.utils.dict import dict_search +from vyos.utils.network import get_interface_config +from vyos.template import render_to_string +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + vrf = None + if len(argv) > 1: + vrf = argv[1] + + base_path = ['protocols', 'isis'] + + # eqivalent of the C foo ? 'a' : 'b' statement + base = vrf and ['vrf', 'name', vrf, 'protocols', 'isis'] or base_path + isis = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # Assign the name of our VRF context. This MUST be done before the return + # statement below, else on deletion we will delete the default instance + # instead of the VRF instance. + if vrf: isis['vrf'] = vrf + + # FRR has VRF support for different routing daemons. As interfaces belong + # to VRFs - or the global VRF, we need to check for changed interfaces so + # that they will be properly rendered for the FRR config. Also this eases + # removal of interfaces from the running configuration. + interfaces_removed = node_changed(conf, base + ['interface']) + if interfaces_removed: + isis['interface_removed'] = list(interfaces_removed) + + # Bail out early if configuration tree does no longer exist. this must + # be done after retrieving the list of interfaces to be removed. + if not conf.exists(base): + isis.update({'deleted' : ''}) + return isis + + # merge in default values + isis = conf.merge_defaults(isis, recursive=True) + + # We also need some additional information from the config, prefix-lists + # and route-maps for instance. They will be used in verify(). + # + # XXX: one MUST always call this without the key_mangling() option! See + # vyos.configverify.verify_common_route_maps() for more information. + tmp = conf.get_config_dict(['policy']) + # Merge policy dict into "regular" config dict + isis = dict_merge(tmp, isis) + + return isis + +def verify(isis): + # bail out early - looks like removal from running config + if not isis or 'deleted' in isis: + return None + + if 'net' not in isis: + raise ConfigError('Network entity is mandatory!') + + # last byte in IS-IS area address must be 0 + tmp = isis['net'].split('.') + if int(tmp[-1]) != 0: + raise ConfigError('Last byte of IS-IS network entity title must always be 0!') + + verify_common_route_maps(isis) + + # If interface not set + if 'interface' not in isis: + raise ConfigError('Interface used for routing updates is mandatory!') + + for interface in isis['interface']: + verify_interface_exists(isis, interface) + # Interface MTU must be >= configured lsp-mtu + mtu = Interface(interface).get_mtu() + area_mtu = isis['lsp_mtu'] + # Recommended maximum PDU size = interface MTU - 3 bytes + recom_area_mtu = mtu - 3 + if mtu < int(area_mtu) or int(area_mtu) > recom_area_mtu: + raise ConfigError(f'Interface {interface} has MTU {mtu}, ' \ + f'current area MTU is {area_mtu}! \n' \ + f'Recommended area lsp-mtu {recom_area_mtu} or less ' \ + '(calculated on MTU size).') + + if 'vrf' in isis: + # If interface specific options are set, we must ensure that the + # interface is bound to our requesting VRF. Due to the VyOS + # priorities the interface is bound to the VRF after creation of + # the VRF itself, and before any routing protocol is configured. + vrf = isis['vrf'] + tmp = get_interface_config(interface) + if 'master' not in tmp or tmp['master'] != vrf: + raise ConfigError(f'Interface "{interface}" is not a member of VRF "{vrf}"!') + + # If md5 and plaintext-password set at the same time + for password in ['area_password', 'domain_password']: + if password in isis: + if {'md5', 'plaintext_password'} <= set(isis[password]): + tmp = password.replace('_', '-') + raise ConfigError(f'Can use either md5 or plaintext-password for {tmp}!') + + # If one param from delay set, but not set others + if 'spf_delay_ietf' in isis: + required_timers = ['holddown', 'init_delay', 'long_delay', 'short_delay', 'time_to_learn'] + exist_timers = [] + for elm_timer in required_timers: + if elm_timer in isis['spf_delay_ietf']: + exist_timers.append(elm_timer) + + exist_timers = set(required_timers).difference(set(exist_timers)) + if len(exist_timers) > 0: + raise ConfigError('All types of spf-delay must be configured. Missing: ' + ', '.join(exist_timers).replace('_', '-')) + + # If Redistribute set, but level don't set + if 'redistribute' in isis: + proc_level = isis.get('level','').replace('-','_') + for afi in ['ipv4', 'ipv6']: + if afi not in isis['redistribute']: + continue + + for proto, proto_config in isis['redistribute'][afi].items(): + if 'level_1' not in proto_config and 'level_2' not in proto_config: + raise ConfigError(f'Redistribute level-1 or level-2 should be specified in ' \ + f'"protocols isis redistribute {afi} {proto}"!') + + for redistr_level, redistr_config in proto_config.items(): + if proc_level and proc_level != 'level_1_2' and proc_level != redistr_level: + raise ConfigError(f'"protocols isis redistribute {afi} {proto} {redistr_level}" ' \ + f'can not be used with \"protocols isis level {proc_level}\"!') + + # Segment routing checks + if dict_search('segment_routing.global_block', isis): + g_high_label_value = dict_search('segment_routing.global_block.high_label_value', isis) + g_low_label_value = dict_search('segment_routing.global_block.low_label_value', isis) + + # If segment routing global block high or low value is blank, throw error + if not (g_low_label_value or g_high_label_value): + raise ConfigError('Segment routing global-block requires both low and high value!') + + # If segment routing global block low value is higher than the high value, throw error + if int(g_low_label_value) > int(g_high_label_value): + raise ConfigError('Segment routing global-block low value must be lower than high value') + + if dict_search('segment_routing.local_block', isis): + if dict_search('segment_routing.global_block', isis) == None: + raise ConfigError('Segment routing local-block requires global-block to be configured!') + + l_high_label_value = dict_search('segment_routing.local_block.high_label_value', isis) + l_low_label_value = dict_search('segment_routing.local_block.low_label_value', isis) + + # If segment routing local-block high or low value is blank, throw error + if not (l_low_label_value or l_high_label_value): + raise ConfigError('Segment routing local-block requires both high and low value!') + + # If segment routing local-block low value is higher than the high value, throw error + if int(l_low_label_value) > int(l_high_label_value): + raise ConfigError('Segment routing local-block low value must be lower than high value') + + # local-block most live outside global block + global_range = range(int(g_low_label_value), int(g_high_label_value) +1) + local_range = range(int(l_low_label_value), int(l_high_label_value) +1) + + # Check for overlapping ranges + if list(set(global_range) & set(local_range)): + raise ConfigError(f'Segment-Routing Global Block ({g_low_label_value}/{g_high_label_value}) '\ + f'conflicts with Local Block ({l_low_label_value}/{l_high_label_value})!') + + # Check for a blank or invalid value per prefix + if dict_search('segment_routing.prefix', isis): + for prefix, prefix_config in isis['segment_routing']['prefix'].items(): + if 'absolute' in prefix_config: + if prefix_config['absolute'].get('value') is None: + raise ConfigError(f'Segment routing prefix {prefix} absolute value cannot be blank.') + elif 'index' in prefix_config: + if prefix_config['index'].get('value') is None: + raise ConfigError(f'Segment routing prefix {prefix} index value cannot be blank.') + + # Check for explicit-null and no-php-flag configured at the same time per prefix + if dict_search('segment_routing.prefix', isis): + for prefix, prefix_config in isis['segment_routing']['prefix'].items(): + if 'absolute' in prefix_config: + if ("explicit_null" in prefix_config['absolute']) and ("no_php_flag" in prefix_config['absolute']): + raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\ + f'and no-php-flag configured at the same time.') + elif 'index' in prefix_config: + if ("explicit_null" in prefix_config['index']) and ("no_php_flag" in prefix_config['index']): + raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\ + f'and no-php-flag configured at the same time.') + + # Check for index ranges being larger than the segment routing global block + if dict_search('segment_routing.global_block', isis): + g_high_label_value = dict_search('segment_routing.global_block.high_label_value', isis) + g_low_label_value = dict_search('segment_routing.global_block.low_label_value', isis) + g_label_difference = int(g_high_label_value) - int(g_low_label_value) + if dict_search('segment_routing.prefix', isis): + for prefix, prefix_config in isis['segment_routing']['prefix'].items(): + if 'index' in prefix_config: + index_size = isis['segment_routing']['prefix'][prefix]['index']['value'] + if int(index_size) > int(g_label_difference): + raise ConfigError(f'Segment routing prefix {prefix} cannot have an '\ + f'index base size larger than the SRGB label base.') + + # Check for LFA tiebreaker index duplication + if dict_search('fast_reroute.lfa.local.tiebreaker', isis): + comparison_dictionary = {} + for item, item_options in isis['fast_reroute']['lfa']['local']['tiebreaker'].items(): + for index, index_options in item_options.items(): + for index_value, index_value_options in index_options.items(): + if index_value not in comparison_dictionary.keys(): + comparison_dictionary[index_value] = [item] + else: + comparison_dictionary[index_value].append(item) + for index, index_length in comparison_dictionary.items(): + if int(len(index_length)) > 1: + raise ConfigError(f'LFA index {index} cannot have more than one tiebreaker configured.') + + # Check for LFA priority-limit configured multiple times per level + if dict_search('fast_reroute.lfa.local.priority_limit', isis): + comparison_dictionary = {} + for priority, priority_options in isis['fast_reroute']['lfa']['local']['priority_limit'].items(): + for level, level_options in priority_options.items(): + if level not in comparison_dictionary.keys(): + comparison_dictionary[level] = [priority] + else: + comparison_dictionary[level].append(priority) + for level, level_length in comparison_dictionary.items(): + if int(len(level_length)) > 1: + raise ConfigError(f'LFA priority-limit on {level.replace("_", "-")} cannot have more than one priority configured.') + + # Check for LFA remote prefix list configured with more than one list + if dict_search('fast_reroute.lfa.remote.prefix_list', isis): + if int(len(isis['fast_reroute']['lfa']['remote']['prefix_list'].items())) > 1: + raise ConfigError(f'LFA remote prefix-list has more than one configured. Cannot have more than one configured.') + + return None + +def generate(isis): + if not isis or 'deleted' in isis: + return None + + isis['frr_isisd_config'] = render_to_string('frr/isisd.frr.j2', isis) + return None + +def apply(isis): + isis_daemon = 'isisd' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + # Generate empty helper string which can be ammended to FRR commands, it + # will be either empty (default VRF) or contain the "vrf <name" statement + vrf = '' + if 'vrf' in isis: + vrf = ' vrf ' + isis['vrf'] + + frr_cfg.load_configuration(isis_daemon) + frr_cfg.modify_section(f'^router isis VyOS{vrf}', stop_pattern='^exit', remove_stop_mark=True) + + for key in ['interface', 'interface_removed']: + if key not in isis: + continue + for interface in isis[key]: + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) + + if 'frr_isisd_config' in isis: + frr_cfg.add_before(frr.default_add_before, isis['frr_isisd_config']) + + frr_cfg.commit_configuration(isis_daemon) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py new file mode 100644 index 0000000..ad164db --- /dev/null +++ b/src/conf_mode/protocols_mpls.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from glob import glob +from vyos.config import Config +from vyos.template import render_to_string +from vyos.utils.dict import dict_search +from vyos.utils.file import read_file +from vyos.utils.system import sysctl_write +from vyos.configverify import verify_interface_exists +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +config_file = r'/tmp/ldpd.frr' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['protocols', 'mpls'] + + mpls = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + return mpls + +def verify(mpls): + # If no config, then just bail out early. + if not mpls: + return None + + if 'interface' in mpls: + for interface in mpls['interface']: + verify_interface_exists(mpls, interface) + + # Checks to see if LDP is properly configured + if 'ldp' in mpls: + # If router ID not defined + if 'router_id' not in mpls['ldp']: + raise ConfigError('Router ID missing. An LDP router id is mandatory!') + + # If interface not set + if 'interface' not in mpls['ldp']: + raise ConfigError('LDP interfaces are missing. An LDP interface is mandatory!') + + # If transport addresses are not set + if not dict_search('ldp.discovery.transport_ipv4_address', mpls) and \ + not dict_search('ldp.discovery.transport_ipv6_address', mpls): + raise ConfigError('LDP transport address missing!') + + return None + +def generate(mpls): + # If there's no MPLS config generated, create dictionary key with no value. + if not mpls or 'deleted' in mpls: + return None + + mpls['frr_ldpd_config'] = render_to_string('frr/ldpd.frr.j2', mpls) + return None + +def apply(mpls): + ldpd_damon = 'ldpd' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + frr_cfg.load_configuration(ldpd_damon) + frr_cfg.modify_section(f'^mpls ldp', stop_pattern='^exit', remove_stop_mark=True) + + if 'frr_ldpd_config' in mpls: + frr_cfg.add_before(frr.default_add_before, mpls['frr_ldpd_config']) + frr_cfg.commit_configuration(ldpd_damon) + + # Set number of entries in the platform label tables + labels = '0' + if 'interface' in mpls: + labels = '1048575' + sysctl_write('net.mpls.platform_labels', labels) + + # Check for changes in global MPLS options + if 'parameters' in mpls: + # Choose whether to copy IP TTL to MPLS header TTL + if 'no_propagate_ttl' in mpls['parameters']: + sysctl_write('net.mpls.ip_ttl_propagate', 0) + # Choose whether to limit maximum MPLS header TTL + if 'maximum_ttl' in mpls['parameters']: + ttl = mpls['parameters']['maximum_ttl'] + sysctl_write('net.mpls.default_ttl', ttl) + else: + # Set default global MPLS options if not defined. + sysctl_write('net.mpls.ip_ttl_propagate', 1) + sysctl_write('net.mpls.default_ttl', 255) + + # Enable and disable MPLS processing on interfaces per configuration + if 'interface' in mpls: + system_interfaces = [] + # Populate system interfaces list with local MPLS capable interfaces + for interface in glob('/proc/sys/net/mpls/conf/*'): + system_interfaces.append(os.path.basename(interface)) + # This is where the comparison is done on if an interface needs to be enabled/disabled. + for system_interface in system_interfaces: + interface_state = read_file(f'/proc/sys/net/mpls/conf/{system_interface}/input') + if '1' in interface_state: + if system_interface not in mpls['interface']: + system_interface = system_interface.replace('.', '/') + sysctl_write(f'net.mpls.conf.{system_interface}.input', 0) + elif '0' in interface_state: + if system_interface in mpls['interface']: + system_interface = system_interface.replace('.', '/') + sysctl_write(f'net.mpls.conf.{system_interface}.input', 1) + else: + system_interfaces = [] + # If MPLS interfaces are not configured, set MPLS processing disabled + for interface in glob('/proc/sys/net/mpls/conf/*'): + system_interfaces.append(os.path.basename(interface)) + for system_interface in system_interfaces: + system_interface = system_interface.replace('.', '/') + sysctl_write(f'net.mpls.conf.{system_interface}.input', 0) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py new file mode 100644 index 0000000..0bd68b7 --- /dev/null +++ b/src/conf_mode/protocols_nhrp.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from vyos.config import Config +from vyos.configdict import node_changed +from vyos.template import render +from vyos.utils.process import run +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +opennhrp_conf = '/run/opennhrp/opennhrp.conf' +nhrp_nftables_conf = '/run/nftables_nhrp.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['protocols', 'nhrp'] + + nhrp = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + nhrp['del_tunnels'] = node_changed(conf, base + ['tunnel']) + + if not conf.exists(base): + return nhrp + + nhrp['if_tunnel'] = conf.get_config_dict(['interfaces', 'tunnel'], key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + nhrp['profile_map'] = {} + profile = conf.get_config_dict(['vpn', 'ipsec', 'profile'], key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + for name, profile_conf in profile.items(): + if 'bind' in profile_conf and 'tunnel' in profile_conf['bind']: + interfaces = profile_conf['bind']['tunnel'] + if isinstance(interfaces, str): + interfaces = [interfaces] + for interface in interfaces: + nhrp['profile_map'][interface] = name + + return nhrp + +def verify(nhrp): + if 'tunnel' in nhrp: + for name, nhrp_conf in nhrp['tunnel'].items(): + if not nhrp['if_tunnel'] or name not in nhrp['if_tunnel']: + raise ConfigError(f'Tunnel interface "{name}" does not exist') + + tunnel_conf = nhrp['if_tunnel'][name] + + if 'encapsulation' not in tunnel_conf or tunnel_conf['encapsulation'] != 'gre': + raise ConfigError(f'Tunnel "{name}" is not an mGRE tunnel') + + if 'remote' in tunnel_conf: + raise ConfigError(f'Tunnel "{name}" cannot have a remote address defined') + + if 'map' in nhrp_conf: + for map_name, map_conf in nhrp_conf['map'].items(): + if 'nbma_address' not in map_conf: + raise ConfigError(f'nbma-address missing on map {map_name} on tunnel {name}') + + if 'dynamic_map' in nhrp_conf: + for map_name, map_conf in nhrp_conf['dynamic_map'].items(): + if 'nbma_domain_name' not in map_conf: + raise ConfigError(f'nbma-domain-name missing on dynamic-map {map_name} on tunnel {name}') + return None + +def generate(nhrp): + if not os.path.exists(nhrp_nftables_conf): + nhrp['first_install'] = True + + render(opennhrp_conf, 'nhrp/opennhrp.conf.j2', nhrp) + render(nhrp_nftables_conf, 'nhrp/nftables.conf.j2', nhrp) + return None + +def apply(nhrp): + nft_rc = run(f'nft --file {nhrp_nftables_conf}') + if nft_rc != 0: + raise ConfigError('Failed to apply NHRP tunnel firewall rules') + + action = 'restart' if nhrp and 'tunnel' in nhrp else 'stop' + service_rc = run(f'systemctl {action} opennhrp.service') + if service_rc != 0: + raise ConfigError(f'Failed to {action} the NHRP service') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_openfabric.py b/src/conf_mode/protocols_openfabric.py new file mode 100644 index 0000000..8e8c50c --- /dev/null +++ b/src/conf_mode/protocols_openfabric.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdict import node_changed +from vyos.configverify import verify_interface_exists +from vyos.template import render_to_string +from vyos import ConfigError +from vyos import frr +from vyos import airbag + +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base_path = ['protocols', 'openfabric'] + + openfabric = conf.get_config_dict(base_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # Remove per domain MPLS configuration - get a list of all changed Openfabric domains + # (removed and added) so that they will be properly rendered for the FRR config. + openfabric['domains_all'] = list(conf.list_nodes(' '.join(base_path) + f' domain') + + node_changed(conf, base_path + ['domain'])) + + # Get a list of all interfaces + openfabric['interfaces_all'] = [] + for domain in openfabric['domains_all']: + interfaces_modified = list(node_changed(conf, base_path + ['domain', domain, 'interface']) + + conf.list_nodes(' '.join(base_path) + f' domain {domain} interface')) + openfabric['interfaces_all'].extend(interfaces_modified) + + if not conf.exists(base_path): + openfabric.update({'deleted': ''}) + + return openfabric + +def verify(openfabric): + # bail out early - looks like removal from running config + if not openfabric or 'deleted' in openfabric: + return None + + if 'net' not in openfabric: + raise ConfigError('Network entity is mandatory!') + + # last byte in OpenFabric area address must be 0 + tmp = openfabric['net'].split('.') + if int(tmp[-1]) != 0: + raise ConfigError('Last byte of OpenFabric network entity title must always be 0!') + + if 'domain' not in openfabric: + raise ConfigError('OpenFabric domain name is mandatory!') + + interfaces_used = [] + + for domain, domain_config in openfabric['domain'].items(): + # If interface not set + if 'interface' not in domain_config: + raise ConfigError(f'Interface used for routing updates in OpenFabric "{domain}" is mandatory!') + + for iface, iface_config in domain_config['interface'].items(): + verify_interface_exists(openfabric, iface) + + # interface can be activated only on one OpenFabric instance + if iface in interfaces_used: + raise ConfigError(f'Interface {iface} is already used in different OpenFabric instance!') + + if 'address_family' not in iface_config or len(iface_config['address_family']) < 1: + raise ConfigError(f'Need to specify address family for the interface "{iface}"!') + + # If md5 and plaintext-password set at the same time + if 'password' in iface_config: + if {'md5', 'plaintext_password'} <= set(iface_config['password']): + raise ConfigError(f'Can use either md5 or plaintext-password for password for the interface!') + + if iface == 'lo' and 'passive' not in iface_config: + Warning('For loopback interface passive mode is implied!') + + interfaces_used.append(iface) + + # If md5 and plaintext-password set at the same time + password = 'domain_password' + if password in domain_config: + if {'md5', 'plaintext_password'} <= set(domain_config[password]): + raise ConfigError(f'Can use either md5 or plaintext-password for domain-password!') + + return None + +def generate(openfabric): + if not openfabric or 'deleted' in openfabric: + return None + + openfabric['frr_fabricd_config'] = render_to_string('frr/fabricd.frr.j2', openfabric) + return None + +def apply(openfabric): + openfabric_daemon = 'fabricd' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + frr_cfg.load_configuration(openfabric_daemon) + for domain in openfabric['domains_all']: + frr_cfg.modify_section(f'^router openfabric {domain}', stop_pattern='^exit', remove_stop_mark=True) + + for interface in openfabric['interfaces_all']: + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) + + if 'frr_fabricd_config' in openfabric: + frr_cfg.add_before(frr.default_add_before, openfabric['frr_fabricd_config']) + + frr_cfg.commit_configuration(openfabric_daemon) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py new file mode 100644 index 0000000..7347c4f --- /dev/null +++ b/src/conf_mode/protocols_ospf.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit +from sys import argv + +from vyos.config import Config +from vyos.config import config_dict_merge +from vyos.configdict import dict_merge +from vyos.configdict import node_changed +from vyos.configverify import verify_common_route_maps +from vyos.configverify import verify_route_map +from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_access_list +from vyos.template import render_to_string +from vyos.utils.dict import dict_search +from vyos.utils.network import get_interface_config +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + vrf = None + if len(argv) > 1: + vrf = argv[1] + + base_path = ['protocols', 'ospf'] + + # eqivalent of the C foo ? 'a' : 'b' statement + base = vrf and ['vrf', 'name', vrf, 'protocols', 'ospf'] or base_path + ospf = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) + + # Assign the name of our VRF context. This MUST be done before the return + # statement below, else on deletion we will delete the default instance + # instead of the VRF instance. + if vrf: ospf['vrf'] = vrf + + # FRR has VRF support for different routing daemons. As interfaces belong + # to VRFs - or the global VRF, we need to check for changed interfaces so + # that they will be properly rendered for the FRR config. Also this eases + # removal of interfaces from the running configuration. + interfaces_removed = node_changed(conf, base + ['interface']) + if interfaces_removed: + ospf['interface_removed'] = list(interfaces_removed) + + # Bail out early if configuration tree does no longer exist. this must + # be done after retrieving the list of interfaces to be removed. + if not conf.exists(base): + ospf.update({'deleted' : ''}) + return ospf + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = conf.get_config_defaults(**ospf.kwargs, recursive=True) + + # We have to cleanup the default dict, as default values could enable features + # which are not explicitly enabled on the CLI. Example: default-information + # originate comes with a default metric-type of 2, which will enable the + # entire default-information originate tree, even when not set via CLI so we + # need to check this first and probably drop that key. + if dict_search('default_information.originate', ospf) is None: + del default_values['default_information'] + if 'mpls_te' not in ospf: + del default_values['mpls_te'] + if 'graceful_restart' not in ospf: + del default_values['graceful_restart'] + for area_num in default_values.get('area', []): + if dict_search(f'area.{area_num}.area_type.nssa', ospf) is None: + del default_values['area'][area_num]['area_type']['nssa'] + + for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']: + if dict_search(f'redistribute.{protocol}', ospf) is None: + del default_values['redistribute'][protocol] + if not bool(default_values['redistribute']): + del default_values['redistribute'] + + for interface in ospf.get('interface', []): + # We need to reload the defaults on every pass b/c of + # hello-multiplier dependency on dead-interval + # If hello-multiplier is set, we need to remove the default from + # dead-interval. + if 'hello_multiplier' in ospf['interface'][interface]: + del default_values['interface'][interface]['dead_interval'] + + ospf = config_dict_merge(default_values, ospf) + + # We also need some additional information from the config, prefix-lists + # and route-maps for instance. They will be used in verify(). + # + # XXX: one MUST always call this without the key_mangling() option! See + # vyos.configverify.verify_common_route_maps() for more information. + tmp = conf.get_config_dict(['policy']) + # Merge policy dict into "regular" config dict + ospf = dict_merge(tmp, ospf) + + return ospf + +def verify(ospf): + if not ospf: + return None + + verify_common_route_maps(ospf) + + # As we can have a default-information route-map, we need to validate it! + route_map_name = dict_search('default_information.originate.route_map', ospf) + if route_map_name: verify_route_map(route_map_name, ospf) + + # Validate if configured Access-list exists + if 'area' in ospf: + networks = [] + for area, area_config in ospf['area'].items(): + if 'import_list' in area_config: + acl_import = area_config['import_list'] + if acl_import: verify_access_list(acl_import, ospf) + if 'export_list' in area_config: + acl_export = area_config['export_list'] + if acl_export: verify_access_list(acl_export, ospf) + + if 'network' in area_config: + for network in area_config['network']: + if network in networks: + raise ConfigError(f'Network "{network}" already defined in different area!') + networks.append(network) + + if 'interface' in ospf: + for interface, interface_config in ospf['interface'].items(): + verify_interface_exists(ospf, interface) + # One can not use dead-interval and hello-multiplier at the same + # time. FRR will only activate the last option set via CLI. + if {'hello_multiplier', 'dead_interval'} <= set(interface_config): + raise ConfigError(f'Can not use hello-multiplier and dead-interval ' \ + f'concurrently for {interface}!') + + # One can not use the "network <prefix> area <id>" command and an + # per interface area assignment at the same time. FRR will error + # out using: "Please remove all network commands first." + if 'area' in ospf and 'area' in interface_config: + for area, area_config in ospf['area'].items(): + if 'network' in area_config: + raise ConfigError('Can not use OSPF interface area and area ' \ + 'network configuration at the same time!') + + # If interface specific options are set, we must ensure that the + # interface is bound to our requesting VRF. Due to the VyOS + # priorities the interface is bound to the VRF after creation of + # the VRF itself, and before any routing protocol is configured. + if 'vrf' in ospf: + vrf = ospf['vrf'] + tmp = get_interface_config(interface) + if 'master' not in tmp or tmp['master'] != vrf: + raise ConfigError(f'Interface "{interface}" is not a member of VRF "{vrf}"!') + + # Segment routing checks + if dict_search('segment_routing.global_block', ospf): + g_high_label_value = dict_search('segment_routing.global_block.high_label_value', ospf) + g_low_label_value = dict_search('segment_routing.global_block.low_label_value', ospf) + + # If segment routing global block high or low value is blank, throw error + if not (g_low_label_value or g_high_label_value): + raise ConfigError('Segment routing global-block requires both low and high value!') + + # If segment routing global block low value is higher than the high value, throw error + if int(g_low_label_value) > int(g_high_label_value): + raise ConfigError('Segment routing global-block low value must be lower than high value') + + if dict_search('segment_routing.local_block', ospf): + if dict_search('segment_routing.global_block', ospf) == None: + raise ConfigError('Segment routing local-block requires global-block to be configured!') + + l_high_label_value = dict_search('segment_routing.local_block.high_label_value', ospf) + l_low_label_value = dict_search('segment_routing.local_block.low_label_value', ospf) + + # If segment routing local-block high or low value is blank, throw error + if not (l_low_label_value or l_high_label_value): + raise ConfigError('Segment routing local-block requires both high and low value!') + + # If segment routing local-block low value is higher than the high value, throw error + if int(l_low_label_value) > int(l_high_label_value): + raise ConfigError('Segment routing local-block low value must be lower than high value') + + # local-block most live outside global block + global_range = range(int(g_low_label_value), int(g_high_label_value) +1) + local_range = range(int(l_low_label_value), int(l_high_label_value) +1) + + # Check for overlapping ranges + if list(set(global_range) & set(local_range)): + raise ConfigError(f'Segment-Routing Global Block ({g_low_label_value}/{g_high_label_value}) '\ + f'conflicts with Local Block ({l_low_label_value}/{l_high_label_value})!') + + # Check for a blank or invalid value per prefix + if dict_search('segment_routing.prefix', ospf): + for prefix, prefix_config in ospf['segment_routing']['prefix'].items(): + if 'index' in prefix_config: + if prefix_config['index'].get('value') is None: + raise ConfigError(f'Segment routing prefix {prefix} index value cannot be blank.') + + # Check for explicit-null and no-php-flag configured at the same time per prefix + if dict_search('segment_routing.prefix', ospf): + for prefix, prefix_config in ospf['segment_routing']['prefix'].items(): + if 'index' in prefix_config: + if ("explicit_null" in prefix_config['index']) and ("no_php_flag" in prefix_config['index']): + raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\ + f'and no-php-flag configured at the same time.') + + # Check for index ranges being larger than the segment routing global block + if dict_search('segment_routing.global_block', ospf): + g_high_label_value = dict_search('segment_routing.global_block.high_label_value', ospf) + g_low_label_value = dict_search('segment_routing.global_block.low_label_value', ospf) + g_label_difference = int(g_high_label_value) - int(g_low_label_value) + if dict_search('segment_routing.prefix', ospf): + for prefix, prefix_config in ospf['segment_routing']['prefix'].items(): + if 'index' in prefix_config: + index_size = ospf['segment_routing']['prefix'][prefix]['index']['value'] + if int(index_size) > int(g_label_difference): + raise ConfigError(f'Segment routing prefix {prefix} cannot have an '\ + f'index base size larger than the SRGB label base.') + + # Check route summarisation + if 'summary_address' in ospf: + for prefix, prefix_options in ospf['summary_address'].items(): + if {'tag', 'no_advertise'} <= set(prefix_options): + raise ConfigError(f'Can not set both "tag" and "no-advertise" for Type-5 '\ + f'and Type-7 route summarisation of "{prefix}"!') + + return None + +def generate(ospf): + if not ospf or 'deleted' in ospf: + return None + + ospf['frr_ospfd_config'] = render_to_string('frr/ospfd.frr.j2', ospf) + return None + +def apply(ospf): + ospf_daemon = 'ospfd' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + # Generate empty helper string which can be ammended to FRR commands, it + # will be either empty (default VRF) or contain the "vrf <name" statement + vrf = '' + if 'vrf' in ospf: + vrf = ' vrf ' + ospf['vrf'] + + frr_cfg.load_configuration(ospf_daemon) + frr_cfg.modify_section(f'^router ospf{vrf}', stop_pattern='^exit', remove_stop_mark=True) + + for key in ['interface', 'interface_removed']: + if key not in ospf: + continue + for interface in ospf[key]: + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) + + if 'frr_ospfd_config' in ospf: + frr_cfg.add_before(frr.default_add_before, ospf['frr_ospfd_config']) + + frr_cfg.commit_configuration(ospf_daemon) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py new file mode 100644 index 0000000..60c2a9b --- /dev/null +++ b/src/conf_mode/protocols_ospfv3.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit +from sys import argv + +from vyos.config import Config +from vyos.config import config_dict_merge +from vyos.configdict import dict_merge +from vyos.configdict import node_changed +from vyos.configverify import verify_common_route_maps +from vyos.configverify import verify_route_map +from vyos.configverify import verify_interface_exists +from vyos.template import render_to_string +from vyos.ifconfig import Interface +from vyos.utils.dict import dict_search +from vyos.utils.network import get_interface_config +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + vrf = None + if len(argv) > 1: + vrf = argv[1] + + base_path = ['protocols', 'ospfv3'] + + # eqivalent of the C foo ? 'a' : 'b' statement + base = vrf and ['vrf', 'name', vrf, 'protocols', 'ospfv3'] or base_path + ospfv3 = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + # Assign the name of our VRF context. This MUST be done before the return + # statement below, else on deletion we will delete the default instance + # instead of the VRF instance. + if vrf: ospfv3['vrf'] = vrf + + # FRR has VRF support for different routing daemons. As interfaces belong + # to VRFs - or the global VRF, we need to check for changed interfaces so + # that they will be properly rendered for the FRR config. Also this eases + # removal of interfaces from the running configuration. + interfaces_removed = node_changed(conf, base + ['interface']) + if interfaces_removed: + ospfv3['interface_removed'] = list(interfaces_removed) + + # Bail out early if configuration tree does no longer exist. this must + # be done after retrieving the list of interfaces to be removed. + if not conf.exists(base): + ospfv3.update({'deleted' : ''}) + return ospfv3 + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = conf.get_config_defaults(**ospfv3.kwargs, + recursive=True) + + # We have to cleanup the default dict, as default values could enable features + # which are not explicitly enabled on the CLI. Example: default-information + # originate comes with a default metric-type of 2, which will enable the + # entire default-information originate tree, even when not set via CLI so we + # need to check this first and probably drop that key. + if dict_search('default_information.originate', ospfv3) is None: + del default_values['default_information'] + if 'graceful_restart' not in ospfv3: + del default_values['graceful_restart'] + + for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'ripng', 'static']: + if dict_search(f'redistribute.{protocol}', ospfv3) is None: + del default_values['redistribute'][protocol] + if not bool(default_values['redistribute']): + del default_values['redistribute'] + + default_values.pop('interface', {}) + + # merge in remaining default values + ospfv3 = config_dict_merge(default_values, ospfv3) + + # We also need some additional information from the config, prefix-lists + # and route-maps for instance. They will be used in verify(). + # + # XXX: one MUST always call this without the key_mangling() option! See + # vyos.configverify.verify_common_route_maps() for more information. + tmp = conf.get_config_dict(['policy']) + # Merge policy dict into "regular" config dict + ospfv3 = dict_merge(tmp, ospfv3) + + return ospfv3 + +def verify(ospfv3): + if not ospfv3: + return None + + verify_common_route_maps(ospfv3) + + # As we can have a default-information route-map, we need to validate it! + route_map_name = dict_search('default_information.originate.route_map', ospfv3) + if route_map_name: verify_route_map(route_map_name, ospfv3) + + if 'area' in ospfv3: + for area, area_config in ospfv3['area'].items(): + if 'area_type' in area_config: + if len(area_config['area_type']) > 1: + raise ConfigError(f'Can only configure one area-type for OSPFv3 area "{area}"!') + if 'range' in area_config: + for range, range_config in area_config['range'].items(): + if {'not_advertise', 'advertise'} <= range_config.keys(): + raise ConfigError(f'"not-advertise" and "advertise" for "range {range}" cannot be both configured at the same time!') + + if 'interface' in ospfv3: + for interface, interface_config in ospfv3['interface'].items(): + verify_interface_exists(ospfv3, interface) + if 'ifmtu' in interface_config: + mtu = Interface(interface).get_mtu() + if int(interface_config['ifmtu']) > int(mtu): + raise ConfigError(f'OSPFv3 ifmtu can not exceed physical MTU of "{mtu}"') + + # If interface specific options are set, we must ensure that the + # interface is bound to our requesting VRF. Due to the VyOS + # priorities the interface is bound to the VRF after creation of + # the VRF itself, and before any routing protocol is configured. + if 'vrf' in ospfv3: + vrf = ospfv3['vrf'] + tmp = get_interface_config(interface) + if 'master' not in tmp or tmp['master'] != vrf: + raise ConfigError(f'Interface "{interface}" is not a member of VRF "{vrf}"!') + + return None + +def generate(ospfv3): + if not ospfv3 or 'deleted' in ospfv3: + return None + + ospfv3['new_frr_config'] = render_to_string('frr/ospf6d.frr.j2', ospfv3) + return None + +def apply(ospfv3): + ospf6_daemon = 'ospf6d' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + # Generate empty helper string which can be ammended to FRR commands, it + # will be either empty (default VRF) or contain the "vrf <name" statement + vrf = '' + if 'vrf' in ospfv3: + vrf = ' vrf ' + ospfv3['vrf'] + + frr_cfg.load_configuration(ospf6_daemon) + frr_cfg.modify_section(f'^router ospf6{vrf}', stop_pattern='^exit', remove_stop_mark=True) + + for key in ['interface', 'interface_removed']: + if key not in ospfv3: + continue + for interface in ospfv3[key]: + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) + + if 'new_frr_config' in ospfv3: + frr_cfg.add_before(frr.default_add_before, ospfv3['new_frr_config']) + + frr_cfg.commit_configuration(ospf6_daemon) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py new file mode 100644 index 0000000..79294a1 --- /dev/null +++ b/src/conf_mode/protocols_pim.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from ipaddress import IPv4Address +from ipaddress import IPv4Network +from signal import SIGTERM +from sys import exit + +from vyos.config import Config +from vyos.config import config_dict_merge +from vyos.configdict import node_changed +from vyos.configverify import verify_interface_exists +from vyos.utils.process import process_named_running +from vyos.utils.process import call +from vyos.template import render_to_string +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +RESERVED_MC_NET = '224.0.0.0/24' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['protocols', 'pim'] + + pim = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + # We can not run both IGMP proxy and PIM at the same time - get IGMP + # proxy status + if conf.exists(['protocols', 'igmp-proxy']): + pim.update({'igmp_proxy_enabled' : {}}) + + # FRR has VRF support for different routing daemons. As interfaces belong + # to VRFs - or the global VRF, we need to check for changed interfaces so + # that they will be properly rendered for the FRR config. Also this eases + # removal of interfaces from the running configuration. + interfaces_removed = node_changed(conf, base + ['interface']) + if interfaces_removed: + pim['interface_removed'] = list(interfaces_removed) + + # Bail out early if configuration tree does no longer exist. this must + # be done after retrieving the list of interfaces to be removed. + if not conf.exists(base): + pim.update({'deleted' : ''}) + return pim + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = conf.get_config_defaults(**pim.kwargs, recursive=True) + + # We have to cleanup the default dict, as default values could enable features + # which are not explicitly enabled on the CLI. Example: default-information + # originate comes with a default metric-type of 2, which will enable the + # entire default-information originate tree, even when not set via CLI so we + # need to check this first and probably drop that key. + for interface in pim.get('interface', []): + # We need to reload the defaults on every pass b/c of + # hello-multiplier dependency on dead-interval + # If hello-multiplier is set, we need to remove the default from + # dead-interval. + if 'igmp' not in pim['interface'][interface]: + del default_values['interface'][interface]['igmp'] + + pim = config_dict_merge(default_values, pim) + return pim + +def verify(pim): + if not pim or 'deleted' in pim: + return None + + if 'igmp_proxy_enabled' in pim: + raise ConfigError('IGMP proxy and PIM cannot be configured at the same time!') + + if 'interface' not in pim: + raise ConfigError('PIM require defined interfaces!') + + for interface, interface_config in pim['interface'].items(): + verify_interface_exists(pim, interface) + + # Check join group in reserved net + if 'igmp' in interface_config and 'join' in interface_config['igmp']: + for join_addr in interface_config['igmp']['join']: + if IPv4Address(join_addr) in IPv4Network(RESERVED_MC_NET): + raise ConfigError(f'Groups within {RESERVED_MC_NET} are reserved and cannot be joined!') + + if 'rp' in pim: + if 'address' not in pim['rp']: + raise ConfigError('PIM rendezvous point needs to be defined!') + + # Check unique multicast groups + unique = [] + pim_base_error = 'PIM rendezvous point group' + for address, address_config in pim['rp']['address'].items(): + if 'group' not in address_config: + raise ConfigError(f'{pim_base_error} should be defined for "{address}"!') + + # Check if it is a multicast group + for gr_addr in address_config['group']: + if not IPv4Network(gr_addr).is_multicast: + raise ConfigError(f'{pim_base_error} "{gr_addr}" is not a multicast group!') + if gr_addr in unique: + raise ConfigError(f'{pim_base_error} must be unique!') + unique.append(gr_addr) + +def generate(pim): + if not pim or 'deleted' in pim: + return None + pim['frr_pimd_config'] = render_to_string('frr/pimd.frr.j2', pim) + return None + +def apply(pim): + pim_daemon = 'pimd' + pim_pid = process_named_running(pim_daemon) + + if not pim or 'deleted' in pim: + if 'deleted' in pim: + os.kill(int(pim_pid), SIGTERM) + + return None + + if not pim_pid: + call('/usr/lib/frr/pimd -d -F traditional --daemon -A 127.0.0.1') + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + frr_cfg.load_configuration(pim_daemon) + frr_cfg.modify_section(f'^ip pim') + frr_cfg.modify_section(f'^ip igmp') + + for key in ['interface', 'interface_removed']: + if key not in pim: + continue + for interface in pim[key]: + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) + + if 'frr_pimd_config' in pim: + frr_cfg.add_before(frr.default_add_before, pim['frr_pimd_config']) + frr_cfg.commit_configuration(pim_daemon) + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_pim6.py b/src/conf_mode/protocols_pim6.py new file mode 100644 index 0000000..581ffe2 --- /dev/null +++ b/src/conf_mode/protocols_pim6.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from ipaddress import IPv6Address +from ipaddress import IPv6Network +from sys import exit + +from vyos.config import Config +from vyos.config import config_dict_merge +from vyos.configdict import node_changed +from vyos.configverify import verify_interface_exists +from vyos.template import render_to_string +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['protocols', 'pim6'] + pim6 = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, with_recursive_defaults=True) + + # FRR has VRF support for different routing daemons. As interfaces belong + # to VRFs - or the global VRF, we need to check for changed interfaces so + # that they will be properly rendered for the FRR config. Also this eases + # removal of interfaces from the running configuration. + interfaces_removed = node_changed(conf, base + ['interface']) + if interfaces_removed: + pim6['interface_removed'] = list(interfaces_removed) + + # Bail out early if configuration tree does no longer exist. this must + # be done after retrieving the list of interfaces to be removed. + if not conf.exists(base): + pim6.update({'deleted' : ''}) + return pim6 + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = conf.get_config_defaults(**pim6.kwargs, recursive=True) + + pim6 = config_dict_merge(default_values, pim6) + return pim6 + +def verify(pim6): + if not pim6 or 'deleted' in pim6: + return + + for interface, interface_config in pim6.get('interface', {}).items(): + verify_interface_exists(pim6, interface) + if 'mld' in interface_config: + mld = interface_config['mld'] + for group in mld.get('join', {}).keys(): + # Validate multicast group address + if not IPv6Address(group).is_multicast: + raise ConfigError(f"{group} is not a multicast group") + + if 'rp' in pim6: + if 'address' not in pim6['rp']: + raise ConfigError('PIM6 rendezvous point needs to be defined!') + + # Check unique multicast groups + unique = [] + pim_base_error = 'PIM6 rendezvous point group' + + if {'address', 'prefix-list6'} <= set(pim6['rp']): + raise ConfigError(f'{pim_base_error} supports either address or a prefix-list!') + + for address, address_config in pim6['rp']['address'].items(): + if 'group' not in address_config: + raise ConfigError(f'{pim_base_error} should be defined for "{address}"!') + + # Check if it is a multicast group + for gr_addr in address_config['group']: + if not IPv6Network(gr_addr).is_multicast: + raise ConfigError(f'{pim_base_error} "{gr_addr}" is not a multicast group!') + if gr_addr in unique: + raise ConfigError(f'{pim_base_error} must be unique!') + unique.append(gr_addr) + +def generate(pim6): + if not pim6 or 'deleted' in pim6: + return + pim6['new_frr_config'] = render_to_string('frr/pim6d.frr.j2', pim6) + return None + +def apply(pim6): + if pim6 is None: + return + + pim6_daemon = 'pim6d' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + frr_cfg.load_configuration(pim6_daemon) + + for key in ['interface', 'interface_removed']: + if key not in pim6: + continue + for interface in pim6[key]: + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) + + if 'new_frr_config' in pim6: + frr_cfg.add_before(frr.default_add_before, pim6['new_frr_config']) + frr_cfg.commit_configuration(pim6_daemon) + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py new file mode 100644 index 0000000..9afac54 --- /dev/null +++ b/src/conf_mode/protocols_rip.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configdict import node_changed +from vyos.configverify import verify_common_route_maps +from vyos.configverify import verify_access_list +from vyos.configverify import verify_prefix_list +from vyos.utils.dict import dict_search +from vyos.template import render_to_string +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['protocols', 'rip'] + rip = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + # FRR has VRF support for different routing daemons. As interfaces belong + # to VRFs - or the global VRF, we need to check for changed interfaces so + # that they will be properly rendered for the FRR config. Also this eases + # removal of interfaces from the running configuration. + interfaces_removed = node_changed(conf, base + ['interface']) + if interfaces_removed: + rip['interface_removed'] = list(interfaces_removed) + + # Bail out early if configuration tree does not exist + if not conf.exists(base): + rip.update({'deleted' : ''}) + return rip + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + rip = conf.merge_defaults(rip, recursive=True) + + # We also need some additional information from the config, prefix-lists + # and route-maps for instance. They will be used in verify(). + # + # XXX: one MUST always call this without the key_mangling() option! See + # vyos.configverify.verify_common_route_maps() for more information. + tmp = conf.get_config_dict(['policy']) + # Merge policy dict into "regular" config dict + rip = dict_merge(tmp, rip) + + return rip + +def verify(rip): + if not rip: + return None + + verify_common_route_maps(rip) + + acl_in = dict_search('distribute_list.access_list.in', rip) + if acl_in: verify_access_list(acl_in, rip) + + acl_out = dict_search('distribute_list.access_list.out', rip) + if acl_out: verify_access_list(acl_out, rip) + + prefix_list_in = dict_search('distribute_list.prefix-list.in', rip) + if prefix_list_in: verify_prefix_list(prefix_list_in, rip) + + prefix_list_out = dict_search('distribute_list.prefix_list.out', rip) + if prefix_list_out: verify_prefix_list(prefix_list_out, rip) + + if 'interface' in rip: + for interface, interface_options in rip['interface'].items(): + if 'authentication' in interface_options: + if {'md5', 'plaintext_password'} <= set(interface_options['authentication']): + raise ConfigError('Can not use both md5 and plaintext-password at the same time!') + if 'split_horizon' in interface_options: + if {'disable', 'poison_reverse'} <= set(interface_options['split_horizon']): + raise ConfigError(f'You can not have "split-horizon poison-reverse" enabled ' \ + f'with "split-horizon disable" for "{interface}"!') + +def generate(rip): + if not rip or 'deleted' in rip: + return None + + rip['new_frr_config'] = render_to_string('frr/ripd.frr.j2', rip) + return None + +def apply(rip): + rip_daemon = 'ripd' + zebra_daemon = 'zebra' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + # The route-map used for the FIB (zebra) is part of the zebra daemon + frr_cfg.load_configuration(zebra_daemon) + frr_cfg.modify_section('^ip protocol rip route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') + frr_cfg.commit_configuration(zebra_daemon) + + frr_cfg.load_configuration(rip_daemon) + frr_cfg.modify_section('^key chain \S+', stop_pattern='^exit', remove_stop_mark=True) + frr_cfg.modify_section('^router rip', stop_pattern='^exit', remove_stop_mark=True) + + for key in ['interface', 'interface_removed']: + if key not in rip: + continue + for interface in rip[key]: + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) + + if 'new_frr_config' in rip: + frr_cfg.add_before(frr.default_add_before, rip['new_frr_config']) + frr_cfg.commit_configuration(rip_daemon) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py new file mode 100644 index 0000000..23416ff --- /dev/null +++ b/src/conf_mode/protocols_ripng.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configverify import verify_common_route_maps +from vyos.configverify import verify_access_list +from vyos.configverify import verify_prefix_list +from vyos.utils.dict import dict_search +from vyos.template import render_to_string +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['protocols', 'ripng'] + ripng = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + # Bail out early if configuration tree does not exist + if not conf.exists(base): + return ripng + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + ripng = conf.merge_defaults(ripng, recursive=True) + + # We also need some additional information from the config, prefix-lists + # and route-maps for instance. They will be used in verify(). + # + # XXX: one MUST always call this without the key_mangling() option! See + # vyos.configverify.verify_common_route_maps() for more information. + tmp = conf.get_config_dict(['policy']) + # Merge policy dict into "regular" config dict + ripng = dict_merge(tmp, ripng) + + return ripng + +def verify(ripng): + if not ripng: + return None + + verify_common_route_maps(ripng) + + acl_in = dict_search('distribute_list.access_list.in', ripng) + if acl_in: verify_access_list(acl_in, ripng, version='6') + + acl_out = dict_search('distribute_list.access_list.out', ripng) + if acl_out: verify_access_list(acl_out, ripng, version='6') + + prefix_list_in = dict_search('distribute_list.prefix_list.in', ripng) + if prefix_list_in: verify_prefix_list(prefix_list_in, ripng, version='6') + + prefix_list_out = dict_search('distribute_list.prefix_list.out', ripng) + if prefix_list_out: verify_prefix_list(prefix_list_out, ripng, version='6') + + if 'interface' in ripng: + for interface, interface_options in ripng['interface'].items(): + if 'authentication' in interface_options: + if {'md5', 'plaintext_password'} <= set(interface_options['authentication']): + raise ConfigError('Can not use both md5 and plaintext-password at the same time!') + if 'split_horizon' in interface_options: + if {'disable', 'poison_reverse'} <= set(interface_options['split_horizon']): + raise ConfigError(f'You can not have "split-horizon poison-reverse" enabled ' \ + f'with "split-horizon disable" for "{interface}"!') + +def generate(ripng): + if not ripng: + ripng['new_frr_config'] = '' + return None + + ripng['new_frr_config'] = render_to_string('frr/ripngd.frr.j2', ripng) + return None + +def apply(ripng): + ripng_daemon = 'ripngd' + zebra_daemon = 'zebra' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + # The route-map used for the FIB (zebra) is part of the zebra daemon + frr_cfg.load_configuration(zebra_daemon) + frr_cfg.modify_section('^ipv6 protocol ripng route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') + frr_cfg.commit_configuration(zebra_daemon) + + frr_cfg.load_configuration(ripng_daemon) + frr_cfg.modify_section('key chain \S+', stop_pattern='^exit', remove_stop_mark=True) + frr_cfg.modify_section('interface \S+', stop_pattern='^exit', remove_stop_mark=True) + frr_cfg.modify_section('^router ripng', stop_pattern='^exit', remove_stop_mark=True) + if 'new_frr_config' in ripng: + frr_cfg.add_before(frr.default_add_before, ripng['new_frr_config']) + frr_cfg.commit_configuration(ripng_daemon) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py new file mode 100644 index 0000000..a59ecf3 --- /dev/null +++ b/src/conf_mode/protocols_rpki.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from glob import glob +from sys import exit + +from vyos.config import Config +from vyos.pki import wrap_openssh_public_key +from vyos.pki import wrap_openssh_private_key +from vyos.template import render_to_string +from vyos.utils.dict import dict_search_args +from vyos.utils.file import write_file +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +rpki_ssh_key_base = '/run/frr/id_rpki' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['protocols', 'rpki'] + + rpki = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, with_pki=True) + # Bail out early if configuration tree does not exist + if not conf.exists(base): + rpki.update({'deleted' : ''}) + return rpki + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + rpki = conf.merge_defaults(rpki, recursive=True) + + return rpki + +def verify(rpki): + if not rpki: + return None + + if 'cache' in rpki: + preferences = [] + for peer, peer_config in rpki['cache'].items(): + for mandatory in ['port', 'preference']: + if mandatory not in peer_config: + raise ConfigError(f'RPKI cache "{peer}" {mandatory} must be defined!') + + if 'preference' in peer_config: + preference = peer_config['preference'] + if preference in preferences: + raise ConfigError(f'RPKI cache with preference {preference} already configured!') + preferences.append(preference) + + if 'ssh' in peer_config: + if 'username' not in peer_config['ssh']: + raise ConfigError('RPKI+SSH requires username to be defined!') + + if 'key' not in peer_config['ssh'] or 'openssh' not in rpki['pki']: + raise ConfigError('RPKI+SSH requires key to be defined!') + + if peer_config['ssh']['key'] not in rpki['pki']['openssh']: + raise ConfigError('RPKI+SSH key not found on PKI subsystem!') + + return None + +def generate(rpki): + for key in glob(f'{rpki_ssh_key_base}*'): + os.unlink(key) + + if not rpki: + return + + if 'cache' in rpki: + for cache, cache_config in rpki['cache'].items(): + if 'ssh' in cache_config: + key_name = cache_config['ssh']['key'] + public_key_data = dict_search_args(rpki['pki'], 'openssh', key_name, 'public', 'key') + public_key_type = dict_search_args(rpki['pki'], 'openssh', key_name, 'public', 'type') + private_key_data = dict_search_args(rpki['pki'], 'openssh', key_name, 'private', 'key') + + cache_config['ssh']['public_key_file'] = f'{rpki_ssh_key_base}_{cache}.pub' + cache_config['ssh']['private_key_file'] = f'{rpki_ssh_key_base}_{cache}' + + write_file(cache_config['ssh']['public_key_file'], wrap_openssh_public_key(public_key_data, public_key_type)) + write_file(cache_config['ssh']['private_key_file'], wrap_openssh_private_key(private_key_data)) + + rpki['new_frr_config'] = render_to_string('frr/rpki.frr.j2', rpki) + + return None + +def apply(rpki): + bgp_daemon = 'bgpd' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + frr_cfg.load_configuration(bgp_daemon) + frr_cfg.modify_section('^rpki', stop_pattern='^exit', remove_stop_mark=True) + if 'new_frr_config' in rpki: + frr_cfg.add_before(frr.default_add_before, rpki['new_frr_config']) + + frr_cfg.commit_configuration(bgp_daemon) + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_segment-routing.py b/src/conf_mode/protocols_segment-routing.py new file mode 100644 index 0000000..b36c2ca --- /dev/null +++ b/src/conf_mode/protocols_segment-routing.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import node_changed +from vyos.template import render_to_string +from vyos.utils.dict import dict_search +from vyos.utils.system import sysctl_write +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['protocols', 'segment-routing'] + sr = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True) + + # FRR has VRF support for different routing daemons. As interfaces belong + # to VRFs - or the global VRF, we need to check for changed interfaces so + # that they will be properly rendered for the FRR config. Also this eases + # removal of interfaces from the running configuration. + interfaces_removed = node_changed(conf, base + ['interface']) + if interfaces_removed: + sr['interface_removed'] = list(interfaces_removed) + + import pprint + pprint.pprint(sr) + return sr + +def verify(sr): + if 'srv6' in sr: + srv6_enable = False + if 'interface' in sr: + for interface, interface_config in sr['interface'].items(): + if 'srv6' in interface_config: + srv6_enable = True + break + if not srv6_enable: + raise ConfigError('SRv6 should be enabled on at least one interface!') + return None + +def generate(sr): + if not sr: + return None + + sr['new_frr_config'] = render_to_string('frr/zebra.segment_routing.frr.j2', sr) + return None + +def apply(sr): + zebra_daemon = 'zebra' + + if 'interface_removed' in sr: + for interface in sr['interface_removed']: + # Disable processing of IPv6-SR packets + sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0') + + if 'interface' in sr: + for interface, interface_config in sr['interface'].items(): + # Accept or drop SR-enabled IPv6 packets on this interface + if 'srv6' in interface_config: + sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '1') + # Define HMAC policy for ingress SR-enabled packets on this interface + # It's a redundant check as HMAC has a default value - but better safe + # then sorry + tmp = dict_search('srv6.hmac', interface_config) + if tmp == 'accept': + sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '0') + elif tmp == 'drop': + sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '1') + elif tmp == 'ignore': + sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '-1') + else: + sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0') + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + frr_cfg.load_configuration(zebra_daemon) + frr_cfg.modify_section(r'^segment-routing') + if 'new_frr_config' in sr: + frr_cfg.add_before(frr.default_add_before, sr['new_frr_config']) + frr_cfg.commit_configuration(zebra_daemon) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py new file mode 100644 index 0000000..a237321 --- /dev/null +++ b/src/conf_mode/protocols_static.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit +from sys import argv + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configdict import get_dhcp_interfaces +from vyos.configdict import get_pppoe_interfaces +from vyos.configverify import verify_common_route_maps +from vyos.configverify import verify_vrf +from vyos.template import render +from vyos.template import render_to_string +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +config_file = '/etc/iproute2/rt_tables.d/vyos-static.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + vrf = None + if len(argv) > 1: + vrf = argv[1] + + base_path = ['protocols', 'static'] + # eqivalent of the C foo ? 'a' : 'b' statement + base = vrf and ['vrf', 'name', vrf, 'protocols', 'static'] or base_path + static = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) + + # Assign the name of our VRF context + if vrf: static['vrf'] = vrf + + # We also need some additional information from the config, prefix-lists + # and route-maps for instance. They will be used in verify(). + # + # XXX: one MUST always call this without the key_mangling() option! See + # vyos.configverify.verify_common_route_maps() for more information. + tmp = conf.get_config_dict(['policy']) + # Merge policy dict into "regular" config dict + static = dict_merge(tmp, static) + + # T3680 - get a list of all interfaces currently configured to use DHCP + tmp = get_dhcp_interfaces(conf, vrf) + if tmp: static.update({'dhcp' : tmp}) + tmp = get_pppoe_interfaces(conf, vrf) + if tmp: static.update({'pppoe' : tmp}) + + return static + +def verify(static): + verify_common_route_maps(static) + + for route in ['route', 'route6']: + # if there is no route(6) key in the dictionary we can immediately + # bail out early + if route not in static: + continue + + # When leaking routes to other VRFs we must ensure that the destination + # VRF exists + for prefix, prefix_options in static[route].items(): + # both the interface and next-hop CLI node can have a VRF subnode, + # thus we check this using a for loop + for type in ['interface', 'next_hop']: + if type in prefix_options: + for interface, interface_config in prefix_options[type].items(): + verify_vrf(interface_config) + + if {'blackhole', 'reject'} <= set(prefix_options): + raise ConfigError(f'Can not use both blackhole and reject for '\ + 'prefix "{prefix}"!') + + return None + +def generate(static): + if not static: + return None + + # Put routing table names in /etc/iproute2/rt_tables + render(config_file, 'iproute2/static.conf.j2', static) + static['new_frr_config'] = render_to_string('frr/staticd.frr.j2', static) + return None + +def apply(static): + static_daemon = 'staticd' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + frr_cfg.load_configuration(static_daemon) + + if 'vrf' in static: + vrf = static['vrf'] + frr_cfg.modify_section(f'^vrf {vrf}', stop_pattern='^exit-vrf', remove_stop_mark=True) + else: + frr_cfg.modify_section(r'^ip route .*') + frr_cfg.modify_section(r'^ipv6 route .*') + + if 'new_frr_config' in static: + frr_cfg.add_before(frr.default_add_before, static['new_frr_config']) + frr_cfg.commit_configuration(static_daemon) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_static_arp.py b/src/conf_mode/protocols_static_arp.py new file mode 100644 index 0000000..b141f11 --- /dev/null +++ b/src/conf_mode/protocols_static_arp.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import node_changed +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['protocols', 'static', 'arp'] + arp = conf.get_config_dict(base, get_first_key=True) + + if 'interface' in arp: + for interface in arp['interface']: + tmp = node_changed(conf, base + ['interface', interface, 'address'], recursive=True) + if tmp: arp['interface'][interface].update({'address_old' : tmp}) + + return arp + +def verify(arp): + pass + +def generate(arp): + pass + +def apply(arp): + if not arp: + return None + + if 'interface' in arp: + for interface, interface_config in arp['interface'].items(): + # Delete old static ARP assignments first + if 'address_old' in interface_config: + for address in interface_config['address_old']: + call(f'ip neigh del {address} dev {interface}') + + # Add new static ARP entries to interface + if 'address' not in interface_config: + continue + for address, address_config in interface_config['address'].items(): + mac = address_config['mac'] + call(f'ip neigh replace {address} lladdr {mac} dev {interface}') + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_static_multicast.py b/src/conf_mode/protocols_static_multicast.py new file mode 100644 index 0000000..d323ceb --- /dev/null +++ b/src/conf_mode/protocols_static_multicast.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +from ipaddress import IPv4Address +from sys import exit + +from vyos import ConfigError +from vyos import frr +from vyos.config import Config +from vyos.template import render_to_string + +from vyos import airbag +airbag.enable() + +config_file = r'/tmp/static_mcast.frr' + +# Get configuration for static multicast route +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + mroute = { + 'old_mroute' : {}, + 'mroute' : {} + } + + base_path = "protocols static multicast" + + if not (conf.exists(base_path) or conf.exists_effective(base_path)): + return None + + conf.set_level(base_path) + + # Get multicast effective routes + for route in conf.list_effective_nodes('route'): + mroute['old_mroute'][route] = {} + for next_hop in conf.list_effective_nodes('route {0} next-hop'.format(route)): + mroute['old_mroute'][route].update({ + next_hop : conf.return_value('route {0} next-hop {1} distance'.format(route, next_hop)) + }) + + # Get multicast effective interface-routes + for route in conf.list_effective_nodes('interface-route'): + if not route in mroute['old_mroute']: + mroute['old_mroute'][route] = {} + for next_hop in conf.list_effective_nodes('interface-route {0} next-hop-interface'.format(route)): + mroute['old_mroute'][route].update({ + next_hop : conf.return_value('interface-route {0} next-hop-interface {1} distance'.format(route, next_hop)) + }) + + # Get multicast routes + for route in conf.list_nodes('route'): + mroute['mroute'][route] = {} + for next_hop in conf.list_nodes('route {0} next-hop'.format(route)): + mroute['mroute'][route].update({ + next_hop : conf.return_value('route {0} next-hop {1} distance'.format(route, next_hop)) + }) + + # Get multicast interface-routes + for route in conf.list_nodes('interface-route'): + if not route in mroute['mroute']: + mroute['mroute'][route] = {} + for next_hop in conf.list_nodes('interface-route {0} next-hop-interface'.format(route)): + mroute['mroute'][route].update({ + next_hop : conf.return_value('interface-route {0} next-hop-interface {1} distance'.format(route, next_hop)) + }) + + return mroute + +def verify(mroute): + if mroute is None: + return None + + for route in mroute['mroute']: + route = route.split('/') + if IPv4Address(route[0]) < IPv4Address('224.0.0.0'): + raise ConfigError(route + " not a multicast network") + + +def generate(mroute): + if mroute is None: + return None + + mroute['new_frr_config'] = render_to_string('frr/static_mcast.frr.j2', mroute) + return None + + +def apply(mroute): + if mroute is None: + return None + static_daemon = 'staticd' + + frr_cfg = frr.FRRConfig() + frr_cfg.load_configuration(static_daemon) + + if 'old_mroute' in mroute: + for route_gr in mroute['old_mroute']: + for nh in mroute['old_mroute'][route_gr]: + if mroute['old_mroute'][route_gr][nh]: + frr_cfg.modify_section(f'^ip mroute {route_gr} {nh} {mroute["old_mroute"][route_gr][nh]}') + else: + frr_cfg.modify_section(f'^ip mroute {route_gr} {nh}') + + if 'new_frr_config' in mroute: + frr_cfg.add_before(frr.default_add_before, mroute['new_frr_config']) + + frr_cfg.commit_configuration(static_daemon) + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_static_neighbor-proxy.py b/src/conf_mode/protocols_static_neighbor-proxy.py new file mode 100644 index 0000000..8a1ea1d --- /dev/null +++ b/src/conf_mode/protocols_static_neighbor-proxy.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['protocols', 'static', 'neighbor-proxy'] + config = conf.get_config_dict(base, get_first_key=True) + + return config + +def verify(config): + if 'arp' in config: + for neighbor, neighbor_conf in config['arp'].items(): + if 'interface' not in neighbor_conf: + raise ConfigError( + f"ARP neighbor-proxy for '{neighbor}' requires an interface to be set!" + ) + + if 'nd' in config: + for neighbor, neighbor_conf in config['nd'].items(): + if 'interface' not in neighbor_conf: + raise ConfigError( + f"ARP neighbor-proxy for '{neighbor}' requires an interface to be set!" + ) + +def generate(config): + pass + +def apply(config): + if not config: + # Cleanup proxy + call('ip neighbor flush proxy') + call('ip -6 neighbor flush proxy') + return None + + # Add proxy ARP + if 'arp' in config: + # Cleanup entries before config + call('ip neighbor flush proxy') + for neighbor, neighbor_conf in config['arp'].items(): + for interface in neighbor_conf.get('interface'): + call(f'ip neighbor add proxy {neighbor} dev {interface}') + + # Add proxy NDP + if 'nd' in config: + # Cleanup entries before config + call('ip -6 neighbor flush proxy') + for neighbor, neighbor_conf in config['nd'].items(): + for interface in neighbor_conf['interface']: + call(f'ip -6 neighbor add proxy {neighbor} dev {interface}') + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py new file mode 100644 index 0000000..7dfad31 --- /dev/null +++ b/src/conf_mode/qos.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit +from netifaces import interfaces + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdep import set_dependents +from vyos.configdep import call_dependents +from vyos.configdict import dict_merge +from vyos.configverify import verify_interface_exists +from vyos.ifconfig import Section +from vyos.qos import CAKE +from vyos.qos import DropTail +from vyos.qos import FairQueue +from vyos.qos import FQCodel +from vyos.qos import Limiter +from vyos.qos import NetEm +from vyos.qos import Priority +from vyos.qos import RandomDetect +from vyos.qos import RateLimiter +from vyos.qos import RoundRobin +from vyos.qos import TrafficShaper +from vyos.qos import TrafficShaperHFSC +from vyos.utils.dict import dict_search_recursive +from vyos.utils.process import run +from vyos import ConfigError +from vyos import airbag +from vyos.xml_ref import relative_defaults + + +airbag.enable() + +map_vyops_tc = { + 'cake' : CAKE, + 'drop_tail' : DropTail, + 'fair_queue' : FairQueue, + 'fq_codel' : FQCodel, + 'limiter' : Limiter, + 'network_emulator' : NetEm, + 'priority_queue' : Priority, + 'random_detect' : RandomDetect, + 'rate_control' : RateLimiter, + 'round_robin' : RoundRobin, + 'shaper' : TrafficShaper, + 'shaper_hfsc' : TrafficShaperHFSC, +} + +def get_shaper(qos, interface_config, direction): + policy_name = interface_config[direction] + # An interface might have a QoS configuration, search the used + # configuration referenced by this. Path will hold the dict element + # referenced by the config, as this will be of sort: + # + # ['policy', 'drop_tail', 'foo-dtail'] <- we are only interested in + # drop_tail as the policy/shaper type + _, path = next(dict_search_recursive(qos, policy_name)) + shaper_type = path[1] + shaper_config = qos['policy'][shaper_type][policy_name] + + return (map_vyops_tc[shaper_type], shaper_config) + + +def _clean_conf_dict(conf): + """ + Delete empty nodes from config e.g. + match ADDRESS30 { + ip { + source {} + } + } + """ + if isinstance(conf, dict): + return {node: _clean_conf_dict(val) for node, val in conf.items() if val != {} and _clean_conf_dict(val) != {}} + else: + return conf + + +def _get_group_filters(config: dict, group_name: str, visited=None) -> dict: + filters = dict() + if not visited: + visited = [group_name, ] + else: + if group_name in visited: + return filters + visited.append(group_name) + + for filter, filter_config in config.get(group_name, {}).items(): + if filter == 'match': + for match, match_config in filter_config.items(): + filters[f'{group_name}-{match}'] = match_config + elif filter == 'match_group': + for group in filter_config: + filters.update(_get_group_filters(config, group, visited)) + + return filters + + +def _get_group_match(config:dict, group_name:str) -> dict: + match = dict() + for key, val in _get_group_filters(config, group_name).items(): + # delete duplicate matches + if val not in match.values(): + match[key] = val + + return match + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['qos'] + if not conf.exists(base): + return None + + qos = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + for ifname in interfaces(): + if_node = Section.get_config_path(ifname) + + if not if_node: + continue + + path = f'interfaces {if_node}' + if conf.exists(f'{path} mirror') or conf.exists(f'{path} redirect'): + type_node = path.split(" ")[1] # return only interface type node + set_dependents(type_node, conf, ifname.split(".")[0]) + + for policy in qos.get('policy', []): + if policy in ['random_detect']: + for rd_name in list(qos['policy'][policy]): + # There are eight precedence levels - ensure all are present + # to be filled later down with the appropriate default values + default_p_val = relative_defaults( + ['qos', 'policy', 'random-detect', rd_name, 'precedence'], + {'precedence': {'0': {}}}, + get_first_key=True, recursive=True + )['0'] + default_p_val = {key.replace('-', '_'): value for key, value in default_p_val.items()} + default_precedence = { + 'precedence': {'0': default_p_val, '1': default_p_val, + '2': default_p_val, '3': default_p_val, + '4': default_p_val, '5': default_p_val, + '6': default_p_val, '7': default_p_val}} + + qos['policy']['random_detect'][rd_name] = dict_merge( + default_precedence, qos['policy']['random_detect'][rd_name]) + + qos = conf.merge_defaults(qos, recursive=True) + + if 'traffic_match_group' in qos: + for group, group_config in qos['traffic_match_group'].items(): + if 'match_group' in group_config: + qos['traffic_match_group'][group]['match'] = _get_group_match(qos['traffic_match_group'], group) + + for policy in qos.get('policy', []): + for p_name, p_config in qos['policy'][policy].items(): + # cleanup empty match config + if 'class' in p_config: + for cls, cls_config in p_config['class'].items(): + if 'match_group' in cls_config: + # merge group match to match + for group in cls_config['match_group']: + for match, match_conf in qos['traffic_match_group'].get(group, {'match': {}})['match'].items(): + if 'match' not in cls_config: + cls_config['match'] = dict() + if match in cls_config['match']: + cls_config['match'][f'{group}-{match}'] = match_conf + else: + cls_config['match'][match] = match_conf + + if 'match' in cls_config: + cls_config['match'] = _clean_conf_dict(cls_config['match']) + if cls_config['match'] == {}: + del cls_config['match'] + + return qos + + +def _verify_match(cls_config: dict) -> None: + if 'match' in cls_config: + for match, match_config in cls_config['match'].items(): + if {'ip', 'ipv6'} <= set(match_config): + raise ConfigError( + f'Can not use both IPv6 and IPv4 in one match ({match})!') + + +def _verify_match_group_exist(cls_config, qos): + if 'match_group' in cls_config: + for group in cls_config['match_group']: + if 'traffic_match_group' not in qos or group not in qos['traffic_match_group']: + Warning(f'Match group "{group}" does not exist!') + + +def verify(qos): + if not qos or 'interface' not in qos: + return None + + # network policy emulator + # reorder rerquires delay to be set + if 'policy' in qos: + for policy_type in qos['policy']: + for policy, policy_config in qos['policy'][policy_type].items(): + # a policy with it's given name is only allowed to exist once + # on the system. This is because an interface selects a policy + # for ingress/egress traffic, and thus there can only be one + # policy with a given name. + # + # We check if the policy name occurs more then once - error out + # if this is true + counter = 0 + for _, path in dict_search_recursive(qos['policy'], policy): + counter += 1 + if counter > 1: + raise ConfigError(f'Conflicting policy name "{policy}", already in use!') + + if 'class' in policy_config: + for cls, cls_config in policy_config['class'].items(): + # bandwidth is not mandatory for priority-queue - that is why this is on the exception list + if 'bandwidth' not in cls_config and policy_type not in ['priority_queue', 'round_robin', 'shaper_hfsc']: + raise ConfigError(f'Bandwidth must be defined for policy "{policy}" class "{cls}"!') + _verify_match(cls_config) + _verify_match_group_exist(cls_config, qos) + if policy_type in ['random_detect']: + if 'precedence' in policy_config: + for precedence, precedence_config in policy_config['precedence'].items(): + max_tr = int(precedence_config['maximum_threshold']) + if {'maximum_threshold', 'minimum_threshold'} <= set(precedence_config): + min_tr = int(precedence_config['minimum_threshold']) + if min_tr >= max_tr: + raise ConfigError(f'Policy "{policy}" uses min-threshold "{min_tr}" >= max-threshold "{max_tr}"!') + + if {'maximum_threshold', 'queue_limit'} <= set(precedence_config): + queue_lim = int(precedence_config['queue_limit']) + if queue_lim < max_tr: + raise ConfigError(f'Policy "{policy}" uses queue-limit "{queue_lim}" < max-threshold "{max_tr}"!') + if policy_type in ['priority_queue']: + if 'default' not in policy_config: + raise ConfigError(f'Policy {policy} misses "default" class!') + if 'default' in policy_config: + if 'bandwidth' not in policy_config['default'] and policy_type not in ['priority_queue', 'round_robin', 'shaper_hfsc']: + raise ConfigError('Bandwidth not defined for default traffic!') + + # we should check interface ingress/egress configuration after verifying that + # the policy name is used only once - this makes the logic easier! + for interface, interface_config in qos['interface'].items(): + for direction in ['egress', 'ingress']: + # bail out early if shaper for given direction is not used at all + if direction not in interface_config: + continue + + policy_name = interface_config[direction] + if 'policy' not in qos or list(dict_search_recursive(qos['policy'], policy_name)) == []: + raise ConfigError(f'Selected QoS policy "{policy_name}" does not exist!') + + shaper_type, shaper_config = get_shaper(qos, interface_config, direction) + tmp = shaper_type(interface).get_direction() + if direction not in tmp: + raise ConfigError(f'Selected QoS policy on interface "{interface}" only supports "{tmp}"!') + + if 'traffic_match_group' in qos: + for group, group_config in qos['traffic_match_group'].items(): + _verify_match(group_config) + _verify_match_group_exist(group_config, qos) + + return None + + +def generate(qos): + if not qos or 'interface' not in qos: + return None + + return None + +def apply(qos): + # Always delete "old" shapers first + for interface in interfaces(): + # Ignore errors (may have no qdisc) + run(f'tc qdisc del dev {interface} parent ffff:') + run(f'tc qdisc del dev {interface} root') + + call_dependents() + + if not qos or 'interface' not in qos: + return None + + for interface, interface_config in qos['interface'].items(): + if not verify_interface_exists(qos, interface, state_required=True, warning_only=True): + # When shaper is bound to a dialup (e.g. PPPoE) interface it is + # possible that it is yet not availbale when to QoS code runs. + # Skip the configuration and inform the user via warning_only=True + continue + + for direction in ['egress', 'ingress']: + # bail out early if shaper for given direction is not used at all + if direction not in interface_config: + continue + + shaper_type, shaper_config = get_shaper(qos, interface_config, direction) + tmp = shaper_type(interface) + tmp.update(shaper_config, direction) + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_aws_glb.py b/src/conf_mode/service_aws_glb.py new file mode 100644 index 0000000..d1ed5a0 --- /dev/null +++ b/src/conf_mode/service_aws_glb.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +systemd_service = 'aws-gwlbtun.service' +systemd_override = '/run/systemd/system/aws-gwlbtun.service.d/10-override.conf' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'aws', 'glb'] + if not conf.exists(base): + return None + + glb = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + return glb + + +def verify(glb): + # bail out early - looks like removal from running config + if not glb: + return None + + +def generate(glb): + if not glb: + return None + + render(systemd_override, 'aws/override_aws_gwlbtun.conf.j2', glb) + + +def apply(glb): + call('systemctl daemon-reload') + if not glb: + call(f'systemctl stop {systemd_service}') + else: + call(f'systemctl restart {systemd_service}') + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_broadcast-relay.py b/src/conf_mode/service_broadcast-relay.py new file mode 100644 index 0000000..d359547 --- /dev/null +++ b/src/conf_mode/service_broadcast-relay.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2017-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from glob import glob +from netifaces import AF_INET +from sys import exit + +from vyos.config import Config +from vyos.configverify import verify_interface_exists +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.network import is_afi_configured +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file_base = r'/etc/default/udp-broadcast-relay' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'broadcast-relay'] + + relay = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + return relay + +def verify(relay): + if not relay or 'disabled' in relay: + return None + + for instance, config in relay.get('id', {}).items(): + # we don't have to check this instance when it's disabled + if 'disabled' in config: + continue + + # we certainly require a UDP port to listen to + if 'port' not in config: + raise ConfigError(f'Port number is mandatory for UDP broadcast relay "{instance}"') + + # Relaying data without two interface is kinda senseless ... + if len(config.get('interface', [])) < 2: + raise ConfigError('At least two interfaces are required for UDP broadcast relay "{instance}"') + + for interface in config.get('interface', []): + verify_interface_exists(relay, interface) + if not is_afi_configured(interface, AF_INET): + raise ConfigError(f'Interface "{interface}" has no IPv4 address configured!') + + return None + +def generate(relay): + if not relay or 'disabled' in relay: + return None + + for config in glob(config_file_base + '*'): + os.remove(config) + + for instance, config in relay.get('id').items(): + # we don't have to check this instance when it's disabled + if 'disabled' in config: + continue + + config['instance'] = instance + render(config_file_base + instance, 'bcast-relay/udp-broadcast-relay.j2', + config) + + return None + +def apply(relay): + # first stop all running services + call('systemctl stop udp-broadcast-relay@*.service') + + if not relay or 'disable' in relay: + return None + + # start only required service instances + for instance, config in relay.get('id').items(): + # we don't have to check this instance when it's disabled + if 'disabled' in config: + continue + + call(f'systemctl start udp-broadcast-relay@{instance}.service') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_config-sync.py b/src/conf_mode/service_config-sync.py new file mode 100644 index 0000000..4b8a7f6 --- /dev/null +++ b/src/conf_mode/service_config-sync.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import json +from pathlib import Path + +from vyos.config import Config +from vyos import ConfigError +from vyos import airbag + +airbag.enable() + + +service_conf = Path(f'/run/config_sync_conf.conf') +post_commit_dir = '/run/scripts/commit/post-hooks.d' +post_commit_file_src = '/usr/libexec/vyos/vyos_config_sync.py' +post_commit_file = f'{post_commit_dir}/vyos_config_sync' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'config-sync'] + if not conf.exists(base): + return None + config = conf.get_config_dict(base, get_first_key=True, + with_recursive_defaults=True) + + return config + + +def verify(config): + # bail out early - looks like removal from running config + if not config: + return None + + if 'mode' not in config: + raise ConfigError(f'config-sync mode is mandatory!') + + for option in ['secondary', 'section']: + if option not in config: + raise ConfigError(f"config-sync '{option}' is not configured!") + + if 'address' not in config['secondary']: + raise ConfigError(f'secondary address is mandatory!') + if 'key' not in config['secondary']: + raise ConfigError(f'secondary key is mandatory!') + + +def generate(config): + if not config: + + if os.path.exists(post_commit_file): + os.unlink(post_commit_file) + + if service_conf.exists(): + service_conf.unlink() + + return None + + # Write configuration file + conf_json = json.dumps(config, indent=4) + service_conf.write_text(conf_json) + + # Create post commit dir + if not os.path.isdir(post_commit_dir): + os.makedirs(post_commit_dir) + + # Symlink from helpers to post-commit + if not os.path.exists(post_commit_file): + os.symlink(post_commit_file_src, post_commit_file) + + return None + + +def apply(config): + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_conntrack-sync.py b/src/conf_mode/service_conntrack-sync.py new file mode 100644 index 0000000..3a233a1 --- /dev/null +++ b/src/conf_mode/service_conntrack-sync.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit +from vyos.config import Config +from vyos.configverify import verify_interface_exists +from vyos.utils.dict import dict_search +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.utils.process import call +from vyos.utils.process import run +from vyos.template import render +from vyos.template import get_ipv4 +from vyos.utils.network import is_addr_assigned +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = '/run/conntrackd/conntrackd.conf' + +def resync_vrrp(): + tmp = run('/usr/libexec/vyos/conf_mode/high-availability.py') + if tmp > 0: + print('ERROR: error restarting VRRP daemon!') + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'conntrack-sync'] + if not conf.exists(base): + return None + + conntrack = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, with_defaults=True) + + conntrack['hash_size'] = read_file('/sys/module/nf_conntrack/parameters/hashsize') + conntrack['table_size'] = read_file('/proc/sys/net/netfilter/nf_conntrack_max') + + conntrack['vrrp'] = conf.get_config_dict(['high-availability', 'vrrp', 'sync-group'], + get_first_key=True) + + return conntrack + +def verify(conntrack): + if not conntrack: + return None + + if 'interface' not in conntrack: + raise ConfigError('Interface not defined!') + + has_peer = False + for interface, interface_config in conntrack['interface'].items(): + verify_interface_exists(conntrack, interface) + # Interface must not only exist, it must also carry an IP address + if len(get_ipv4(interface)) < 1: + raise ConfigError(f'Interface {interface} requires an IP address!') + if 'peer' in interface_config: + has_peer = True + + # If one interface runs in unicast mode instead of multicast, so must all the + # others, else conntrackd will error out with: "cannot use UDP with other + # dedicated link protocols" + if has_peer: + for interface, interface_config in conntrack['interface'].items(): + if 'peer' not in interface_config: + raise ConfigError('Can not mix unicast and multicast mode!') + + if 'expect_sync' in conntrack: + if len(conntrack['expect_sync']) > 1 and 'all' in conntrack['expect_sync']: + raise ConfigError('Can not configure expect-sync "all" with other protocols!') + + if 'listen_address' in conntrack: + for address in conntrack['listen_address']: + if not is_addr_assigned(address): + raise ConfigError(f'Specified listen-address {address} not assigned to any interface!') + + vrrp_group = dict_search('failover_mechanism.vrrp.sync_group', conntrack) + if vrrp_group == None: + raise ConfigError(f'No VRRP sync-group defined!') + if vrrp_group not in conntrack['vrrp']: + raise ConfigError(f'VRRP sync-group {vrrp_group} not configured!') + + return None + +def generate(conntrack): + if not conntrack: + if os.path.isfile(config_file): + os.unlink(config_file) + return None + + render(config_file, 'conntrackd/conntrackd.conf.j2', conntrack) + + return None + +def apply(conntrack): + systemd_service = 'conntrackd.service' + if not conntrack: + # Failover mechanism daemon should be indicated that it no longer needs + # to execute conntrackd actions on transition. This is only required + # once when conntrackd is stopped and taken out of service! + if process_named_running('conntrackd'): + resync_vrrp() + + call(f'systemctl stop {systemd_service}') + return None + + # Failover mechanism daemon should be indicated that it needs to execute + # conntrackd actions on transition. This is only required once when conntrackd + # is started the first time! + if not process_named_running('conntrackd'): + resync_vrrp() + + call(f'systemctl reload-or-restart {systemd_service}') + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py new file mode 100644 index 0000000..b112add --- /dev/null +++ b/src/conf_mode/service_console-server.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit +from psutil import process_iter + +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError + +config_file = '/run/conserver/conserver.cf' +dropbear_systemd_file = '/run/systemd/system/dropbear@{port}.service.d/override.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'console-server'] + + # Retrieve CLI representation as dictionary + proxy = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) + # The retrieved dictionary will look something like this: + # + # {'device': {'usb0b2.4p1.0': {'speed': '9600'}, + # 'usb0b2.4p1.1': {'data_bits': '8', + # 'parity': 'none', + # 'speed': '115200', + # 'stop_bits': '2'}}} + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + proxy = conf.merge_defaults(proxy, recursive=True) + + return proxy + +def verify(proxy): + if not proxy: + return None + + aliases = [] + processes = process_iter(['name', 'cmdline']) + if 'device' in proxy: + for device, device_config in proxy['device'].items(): + for process in processes: + if 'agetty' in process.name() and device in process.cmdline(): + raise ConfigError(f'Port "{device}" already provides a '\ + 'console used by "system console"!') + + if 'speed' not in device_config: + raise ConfigError(f'Port "{device}" requires speed to be set!') + + if 'ssh' in device_config and 'port' not in device_config['ssh']: + raise ConfigError(f'Port "{device}" requires SSH port to be set!') + + if 'alias' in device_config: + if device_config['alias'] in aliases: + raise ConfigError("Console aliases must be unique") + else: + aliases.append(device_config['alias']) + + return None + +def generate(proxy): + if not proxy: + return None + + render(config_file, 'conserver/conserver.conf.j2', proxy) + if 'device' in proxy: + for device, device_config in proxy['device'].items(): + if 'ssh' not in device_config: + continue + + tmp = { + 'device' : device, + 'port' : device_config['ssh']['port'], + } + render(dropbear_systemd_file.format(**tmp), + 'conserver/dropbear@.service.j2', tmp) + + return None + +def apply(proxy): + call('systemctl daemon-reload') + call('systemctl stop dropbear@*.service conserver-server.service') + + if not proxy: + if os.path.isfile(config_file): + os.unlink(config_file) + return None + + call('systemctl restart conserver-server.service') + + if 'device' in proxy: + for device, device_config in proxy['device'].items(): + if 'ssh' not in device_config: + continue + port = device_config['ssh']['port'] + call(f'systemctl restart dropbear@{port}.service') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_dhcp-relay.py b/src/conf_mode/service_dhcp-relay.py new file mode 100644 index 0000000..37d7088 --- /dev/null +++ b/src/conf_mode/service_dhcp-relay.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.template import render +from vyos.base import Warning +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/run/dhcp-relay/dhcrelay.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'dhcp-relay'] + if not conf.exists(base): + return None + + relay = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + return relay + +def verify(relay): + # bail out early - looks like removal from running config + if not relay or 'disable' in relay: + return None + + if 'lo' in (dict_search('interface', relay) or []): + raise ConfigError('DHCP relay does not support the loopback interface.') + + if 'server' not in relay : + raise ConfigError('No DHCP relay server(s) configured.\n' \ + 'At least one DHCP relay server required.') + + if 'interface' in relay: + Warning('DHCP relay interface is DEPRECATED - please use upstream-interface and listen-interface instead!') + if 'upstream_interface' in relay or 'listen_interface' in relay: + raise ConfigError('<interface> configuration is not compatible with upstream/listen interface') + else: + Warning('<interface> is going to be deprecated.\n' \ + 'Please use <listen-interface> and <upstream-interface>') + + if 'upstream_interface' in relay and 'listen_interface' not in relay: + raise ConfigError('No listen-interface configured') + if 'listen_interface' in relay and 'upstream_interface' not in relay: + raise ConfigError('No upstream-interface configured') + + return None + +def generate(relay): + # bail out early - looks like removal from running config + if not relay or 'disable' in relay: + return None + + render(config_file, 'dhcp-relay/dhcrelay.conf.j2', relay) + return None + +def apply(relay): + # bail out early - looks like removal from running config + service_name = 'isc-dhcp-relay.service' + if not relay or 'disable' in relay: + call(f'systemctl stop {service_name}') + if os.path.exists(config_file): + os.unlink(config_file) + return None + + call(f'systemctl restart {service_name}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_dhcp-server.py b/src/conf_mode/service_dhcp-server.py new file mode 100644 index 0000000..e89448e --- /dev/null +++ b/src/conf_mode/service_dhcp-server.py @@ -0,0 +1,430 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from glob import glob +from ipaddress import ip_address +from ipaddress import ip_network +from netaddr import IPRange +from sys import exit + +from vyos.config import Config +from vyos.pki import wrap_certificate +from vyos.pki import wrap_private_key +from vyos.template import render +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_args +from vyos.utils.file import chmod_775 +from vyos.utils.file import chown +from vyos.utils.file import makedir +from vyos.utils.file import write_file +from vyos.utils.process import call +from vyos.utils.network import interface_exists +from vyos.utils.network import is_subnet_connected +from vyos.utils.network import is_addr_assigned +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +ctrl_config_file = '/run/kea/kea-ctrl-agent.conf' +ctrl_socket = '/run/kea/dhcp4-ctrl-socket' +config_file = '/run/kea/kea-dhcp4.conf' +lease_file = '/config/dhcp/dhcp4-leases.csv' +lease_file_glob = '/config/dhcp/dhcp4-leases*' +systemd_override = r'/run/systemd/system/kea-ctrl-agent.service.d/10-override.conf' +user_group = '_kea' + +ca_cert_file = '/run/kea/kea-failover-ca.pem' +cert_file = '/run/kea/kea-failover.pem' +cert_key_file = '/run/kea/kea-failover-key.pem' + +def dhcp_slice_range(exclude_list, range_dict): + """ + This function is intended to slice a DHCP range. What does it mean? + + Lets assume we have a DHCP range from '192.0.2.1' to '192.0.2.100' + but want to exclude address '192.0.2.74' and '192.0.2.75'. We will + pass an input 'range_dict' in the format: + {'start' : '192.0.2.1', 'stop' : '192.0.2.100' } + and we will receive an output list of: + [{'start' : '192.0.2.1' , 'stop' : '192.0.2.73' }, + {'start' : '192.0.2.76', 'stop' : '192.0.2.100' }] + The resulting list can then be used in turn to build the proper dhcpd + configuration file. + """ + output = [] + # exclude list must be sorted for this to work + exclude_list = sorted(exclude_list) + range_start = range_dict['start'] + range_stop = range_dict['stop'] + range_last_exclude = '' + + for e in exclude_list: + if (ip_address(e) >= ip_address(range_start)) and \ + (ip_address(e) <= ip_address(range_stop)): + range_last_exclude = e + + for e in exclude_list: + if (ip_address(e) >= ip_address(range_start)) and \ + (ip_address(e) <= ip_address(range_stop)): + + # Build new address range ending one address before exclude address + r = { + 'start' : range_start, + 'stop' : str(ip_address(e) -1) + } + # On the next run our address range will start one address after + # the exclude address + range_start = str(ip_address(e) + 1) + + # on subsequent exclude addresses we can not + # append them to our output + if not (ip_address(r['start']) > ip_address(r['stop'])): + # Everything is fine, add range to result + output.append(r) + + # Take care of last IP address range spanning from the last exclude + # address (+1) to the end of the initial configured range + if ip_address(e) == ip_address(range_last_exclude): + r = { + 'start': str(ip_address(e) + 1), + 'stop': str(range_stop) + } + if not (ip_address(r['start']) > ip_address(r['stop'])): + output.append(r) + else: + # if the excluded address was not part of the range, we simply return + # the entire ranga again + if not range_last_exclude: + if range_dict not in output: + output.append(range_dict) + + return output + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'dhcp-server'] + if not conf.exists(base): + return None + + dhcp = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + if 'shared_network_name' in dhcp: + for network, network_config in dhcp['shared_network_name'].items(): + if 'subnet' in network_config: + for subnet, subnet_config in network_config['subnet'].items(): + # If exclude IP addresses are defined we need to slice them out of + # the defined ranges + if {'exclude', 'range'} <= set(subnet_config): + new_range_id = 0 + new_range_dict = {} + for r, r_config in subnet_config['range'].items(): + for slice in dhcp_slice_range(subnet_config['exclude'], r_config): + new_range_dict.update({new_range_id : slice}) + new_range_id +=1 + + dhcp['shared_network_name'][network]['subnet'][subnet].update( + {'range' : new_range_dict}) + + if len(dhcp['high_availability']) == 1: + ## only default value for mode is set, need to remove ha node + del dhcp['high_availability'] + else: + if dict_search('high_availability.certificate', dhcp): + dhcp['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) + + return dhcp + +def verify(dhcp): + # bail out early - looks like removal from running config + if not dhcp or 'disable' in dhcp: + return None + + # If DHCP is enabled we need one share-network + if 'shared_network_name' not in dhcp: + raise ConfigError('No DHCP shared networks configured.\n' \ + 'At least one DHCP shared network must be configured.') + + # Inspect shared-network/subnet + listen_ok = False + subnets = [] + shared_networks = len(dhcp['shared_network_name']) + disabled_shared_networks = 0 + + subnet_ids = [] + + # A shared-network requires a subnet definition + for network, network_config in dhcp['shared_network_name'].items(): + if 'disable' in network_config: + disabled_shared_networks += 1 + + if 'subnet' not in network_config: + raise ConfigError(f'No subnets defined for {network}. At least one\n' \ + 'lease subnet must be configured.') + + for subnet, subnet_config in network_config['subnet'].items(): + if 'subnet_id' not in subnet_config: + raise ConfigError(f'Unique subnet ID not specified for subnet "{subnet}"') + + if subnet_config['subnet_id'] in subnet_ids: + raise ConfigError(f'Subnet ID for subnet "{subnet}" is not unique') + + subnet_ids.append(subnet_config['subnet_id']) + + # All delivered static routes require a next-hop to be set + if 'static_route' in subnet_config: + for route, route_option in subnet_config['static_route'].items(): + if 'next_hop' not in route_option: + raise ConfigError(f'DHCP static-route "{route}" requires router to be defined!') + + # Check if DHCP address range is inside configured subnet declaration + if 'range' in subnet_config: + networks = [] + for range, range_config in subnet_config['range'].items(): + if not {'start', 'stop'} <= set(range_config): + raise ConfigError(f'DHCP range "{range}" start and stop address must be defined!') + + # Start/Stop address must be inside network + for key in ['start', 'stop']: + if ip_address(range_config[key]) not in ip_network(subnet): + raise ConfigError(f'DHCP range "{range}" {key} address not within shared-network "{network}, {subnet}"!') + + # Stop address must be greater or equal to start address + if ip_address(range_config['stop']) < ip_address(range_config['start']): + raise ConfigError(f'DHCP range "{range}" stop address must be greater or equal\n' \ + 'to the ranges start address!') + + for network in networks: + start = range_config['start'] + stop = range_config['stop'] + if start in network: + raise ConfigError(f'Range "{range}" start address "{start}" already part of another range!') + if stop in network: + raise ConfigError(f'Range "{range}" stop address "{stop}" already part of another range!') + + tmp = IPRange(range_config['start'], range_config['stop']) + networks.append(tmp) + + # Exclude addresses must be in bound + if 'exclude' in subnet_config: + for exclude in subnet_config['exclude']: + if ip_address(exclude) not in ip_network(subnet): + raise ConfigError(f'Excluded IP address "{exclude}" not within shared-network "{network}, {subnet}"!') + + # At least one DHCP address range or static-mapping required + if 'range' not in subnet_config and 'static_mapping' not in subnet_config: + raise ConfigError(f'No DHCP address range or active static-mapping configured\n' \ + f'within shared-network "{network}, {subnet}"!') + + if 'static_mapping' in subnet_config: + # Static mappings require just a MAC address (will use an IP from the dynamic pool if IP is not set) + used_ips = [] + used_mac = [] + used_duid = [] + for mapping, mapping_config in subnet_config['static_mapping'].items(): + if 'ip_address' in mapping_config: + if ip_address(mapping_config['ip_address']) not in ip_network(subnet): + raise ConfigError(f'Configured static lease address for mapping "{mapping}" is\n' \ + f'not within shared-network "{network}, {subnet}"!') + + if ('mac' not in mapping_config and 'duid' not in mapping_config) or \ + ('mac' in mapping_config and 'duid' in mapping_config): + raise ConfigError(f'Either MAC address or Client identifier (DUID) is required for ' + f'static mapping "{mapping}" within shared-network "{network}, {subnet}"!') + + if 'disable' not in mapping_config: + if mapping_config['ip_address'] in used_ips: + raise ConfigError(f'Configured IP address for static mapping "{mapping}" already exists on another static mapping') + used_ips.append(mapping_config['ip_address']) + + if 'disable' not in mapping_config: + if 'mac' in mapping_config: + if mapping_config['mac'] in used_mac: + raise ConfigError(f'Configured MAC address for static mapping "{mapping}" already exists on another static mapping') + used_mac.append(mapping_config['mac']) + + if 'duid' in mapping_config: + if mapping_config['duid'] in used_duid: + raise ConfigError(f'Configured DUID for static mapping "{mapping}" already exists on another static mapping') + used_duid.append(mapping_config['duid']) + + # There must be one subnet connected to a listen interface. + # This only counts if the network itself is not disabled! + if 'disable' not in network_config: + if is_subnet_connected(subnet, primary=False): + listen_ok = True + + # Subnets must be non overlapping + if subnet in subnets: + raise ConfigError(f'Configured subnets must be unique! Subnet "{subnet}"\n' + 'defined multiple times!') + subnets.append(subnet) + + # Check for overlapping subnets + net = ip_network(subnet) + for n in subnets: + net2 = ip_network(n) + if (net != net2): + if net.overlaps(net2): + raise ConfigError(f'Conflicting subnet ranges: "{net}" overlaps "{net2}"!') + + # Prevent 'disable' for shared-network if only one network is configured + if (shared_networks - disabled_shared_networks) < 1: + raise ConfigError(f'At least one shared network must be active!') + + if 'high_availability' in dhcp: + for key in ['name', 'remote', 'source_address', 'status']: + if key not in dhcp['high_availability']: + tmp = key.replace('_', '-') + raise ConfigError(f'DHCP high-availability requires "{tmp}" to be specified!') + + if len({'certificate', 'ca_certificate'} & set(dhcp['high_availability'])) == 1: + raise ConfigError(f'DHCP secured high-availability requires both certificate and CA certificate') + + if 'certificate' in dhcp['high_availability']: + cert_name = dhcp['high_availability']['certificate'] + + if cert_name not in dhcp['pki']['certificate']: + raise ConfigError(f'Invalid certificate specified for DHCP high-availability') + + if not dict_search_args(dhcp['pki']['certificate'], cert_name, 'certificate'): + raise ConfigError(f'Invalid certificate specified for DHCP high-availability') + + if not dict_search_args(dhcp['pki']['certificate'], cert_name, 'private', 'key'): + raise ConfigError(f'Missing private key on certificate specified for DHCP high-availability') + + if 'ca_certificate' in dhcp['high_availability']: + ca_cert_name = dhcp['high_availability']['ca_certificate'] + if ca_cert_name not in dhcp['pki']['ca']: + raise ConfigError(f'Invalid CA certificate specified for DHCP high-availability') + + if not dict_search_args(dhcp['pki']['ca'], ca_cert_name, 'certificate'): + raise ConfigError(f'Invalid CA certificate specified for DHCP high-availability') + + for address in (dict_search('listen_address', dhcp) or []): + if is_addr_assigned(address, include_vrf=True): + listen_ok = True + # no need to probe further networks, we have one that is valid + continue + else: + raise ConfigError(f'listen-address "{address}" not configured on any interface') + + if not listen_ok: + raise ConfigError('None of the configured subnets have an appropriate primary IP address on any\n' + 'broadcast interface configured, nor was there an explicit listen-address\n' + 'configured for serving DHCP relay packets!') + + if 'listen_address' in dhcp and 'listen_interface' in dhcp: + raise ConfigError(f'Cannot define listen-address and listen-interface at the same time') + + for interface in (dict_search('listen_interface', dhcp) or []): + if not interface_exists(interface): + raise ConfigError(f'listen-interface "{interface}" does not exist') + + return None + +def generate(dhcp): + # bail out early - looks like removal from running config + if not dhcp or 'disable' in dhcp: + return None + + dhcp['lease_file'] = lease_file + dhcp['machine'] = os.uname().machine + + # Create directory for lease file if necessary + lease_dir = os.path.dirname(lease_file) + if not os.path.isdir(lease_dir): + makedir(lease_dir, group='vyattacfg') + chmod_775(lease_dir) + + # Ensure correct permissions on lease files + backups + for file in glob(lease_file_glob): + chown(file, user=user_group, group='vyattacfg') + + # Create lease file if necessary and let kea own it - 'kea-lfc' expects it that way + if not os.path.exists(lease_file): + write_file(lease_file, '', user=user_group, group=user_group, mode=0o644) + + for f in [cert_file, cert_key_file, ca_cert_file]: + if os.path.exists(f): + os.unlink(f) + + if 'high_availability' in dhcp: + if 'certificate' in dhcp['high_availability']: + cert_name = dhcp['high_availability']['certificate'] + cert_data = dhcp['pki']['certificate'][cert_name]['certificate'] + key_data = dhcp['pki']['certificate'][cert_name]['private']['key'] + write_file(cert_file, wrap_certificate(cert_data), user=user_group, mode=0o600) + write_file(cert_key_file, wrap_private_key(key_data), user=user_group, mode=0o600) + + dhcp['high_availability']['cert_file'] = cert_file + dhcp['high_availability']['cert_key_file'] = cert_key_file + + if 'ca_certificate' in dhcp['high_availability']: + ca_cert_name = dhcp['high_availability']['ca_certificate'] + ca_cert_data = dhcp['pki']['ca'][ca_cert_name]['certificate'] + write_file(ca_cert_file, wrap_certificate(ca_cert_data), user=user_group, mode=0o600) + + dhcp['high_availability']['ca_cert_file'] = ca_cert_file + + render(systemd_override, 'dhcp-server/10-override.conf.j2', dhcp) + + render(ctrl_config_file, 'dhcp-server/kea-ctrl-agent.conf.j2', dhcp, user=user_group, group=user_group) + render(config_file, 'dhcp-server/kea-dhcp4.conf.j2', dhcp, user=user_group, group=user_group) + + return None + +def apply(dhcp): + services = ['kea-ctrl-agent', 'kea-dhcp4-server', 'kea-dhcp-ddns-server'] + + if not dhcp or 'disable' in dhcp: + for service in services: + call(f'systemctl stop {service}.service') + + if os.path.exists(config_file): + os.unlink(config_file) + + return None + + for service in services: + action = 'restart' + + if service == 'kea-dhcp-ddns-server' and 'dynamic_dns_update' not in dhcp: + action = 'stop' + + if service == 'kea-ctrl-agent' and 'high_availability' not in dhcp: + action = 'stop' + + call(f'systemctl {action} {service}.service') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_dhcpv6-relay.py b/src/conf_mode/service_dhcpv6-relay.py new file mode 100644 index 0000000..6537ca3 --- /dev/null +++ b/src/conf_mode/service_dhcpv6-relay.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.ifconfig import Interface +from vyos.template import render +from vyos.template import is_ipv6 +from vyos.utils.process import call +from vyos.utils.network import is_ipv6_link_local +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = '/run/dhcp-relay/dhcrelay6.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'dhcpv6-relay'] + if not conf.exists(base): + return None + + relay = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + return relay + +def verify(relay): + # bail out early - looks like removal from running config + if not relay or 'disable' in relay: + return None + + if 'upstream_interface' not in relay: + raise ConfigError('At least one upstream interface required!') + for interface, config in relay['upstream_interface'].items(): + if 'address' not in config: + raise ConfigError('DHCPv6 server required for upstream ' \ + f'interface {interface}!') + + if 'listen_interface' not in relay: + raise ConfigError('At least one listen interface required!') + + # DHCPv6 relay requires at least one global unicat address assigned to the + # interface + for interface in relay['listen_interface']: + has_global = False + for addr in Interface(interface).get_addr(): + if is_ipv6(addr) and not is_ipv6_link_local(addr): + has_global = True + if not has_global: + raise ConfigError(f'Interface {interface} does not have global '\ + 'IPv6 address assigned!') + + return None + +def generate(relay): + # bail out early - looks like removal from running config + if not relay or 'disable' in relay: + return None + + render(config_file, 'dhcp-relay/dhcrelay6.conf.j2', relay) + return None + +def apply(relay): + # bail out early - looks like removal from running config + service_name = 'isc-dhcp-relay6.service' + if not relay or 'disable' in relay: + # DHCPv6 relay support is removed in the commit + call(f'systemctl stop {service_name}') + if os.path.exists(config_file): + os.unlink(config_file) + return None + + call(f'systemctl restart {service_name}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_dhcpv6-server.py b/src/conf_mode/service_dhcpv6-server.py new file mode 100644 index 0000000..7af8800 --- /dev/null +++ b/src/conf_mode/service_dhcpv6-server.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from glob import glob +from ipaddress import ip_address +from ipaddress import ip_network +from sys import exit + +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.file import chmod_775 +from vyos.utils.file import chown +from vyos.utils.file import makedir +from vyos.utils.file import write_file +from vyos.utils.dict import dict_search +from vyos.utils.network import is_subnet_connected +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = '/run/kea/kea-dhcp6.conf' +ctrl_socket = '/run/kea/dhcp6-ctrl-socket' +lease_file = '/config/dhcp/dhcp6-leases.csv' +lease_file_glob = '/config/dhcp/dhcp6-leases*' +user_group = '_kea' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'dhcpv6-server'] + if not conf.exists(base): + return None + + dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + return dhcpv6 + +def verify(dhcpv6): + # bail out early - looks like removal from running config + if not dhcpv6 or 'disable' in dhcpv6: + return None + + # If DHCP is enabled we need one share-network + if 'shared_network_name' not in dhcpv6: + raise ConfigError('No DHCPv6 shared networks configured. At least '\ + 'one DHCPv6 shared network must be configured.') + + # Inspect shared-network/subnet + subnets = [] + subnet_ids = [] + listen_ok = False + for network, network_config in dhcpv6['shared_network_name'].items(): + # A shared-network requires a subnet definition + if 'subnet' not in network_config: + raise ConfigError(f'No DHCPv6 lease subnets configured for "{network}". '\ + 'At least one lease subnet must be configured for '\ + 'each shared network!') + + for subnet, subnet_config in network_config['subnet'].items(): + if 'subnet_id' not in subnet_config: + raise ConfigError(f'Unique subnet ID not specified for subnet "{subnet}"') + + if subnet_config['subnet_id'] in subnet_ids: + raise ConfigError(f'Subnet ID for subnet "{subnet}" is not unique') + + subnet_ids.append(subnet_config['subnet_id']) + + if 'range' in subnet_config: + range6_start = [] + range6_stop = [] + + for num, range_config in subnet_config['range'].items(): + if 'start' in range_config: + start = range_config['start'] + + if 'stop' not in range_config: + raise ConfigError(f'Range stop address for start "{start}" is not defined!') + stop = range_config['stop'] + + # Start address must be inside network + if not ip_address(start) in ip_network(subnet): + raise ConfigError(f'Range start address "{start}" is not in subnet "{subnet}"!') + + # Stop address must be inside network + if not ip_address(stop) in ip_network(subnet): + raise ConfigError(f'Range stop address "{stop}" is not in subnet "{subnet}"!') + + # Stop address must be greater or equal to start address + if not ip_address(stop) >= ip_address(start): + raise ConfigError(f'Range stop address "{stop}" must be greater than or equal ' \ + f'to the range start address "{start}"!') + + # DHCPv6 range start address must be unique - two ranges can't + # start with the same address - makes no sense + if start in range6_start: + raise ConfigError(f'Conflicting DHCPv6 lease range: '\ + f'Pool start address "{start}" defined multiple times!') + + range6_start.append(start) + + # DHCPv6 range stop address must be unique - two ranges can't + # end with the same address - makes no sense + if stop in range6_stop: + raise ConfigError(f'Conflicting DHCPv6 lease range: '\ + f'Pool stop address "{stop}" defined multiple times!') + + range6_stop.append(stop) + + if 'prefix' in range_config: + prefix = range_config['prefix'] + + if not ip_network(prefix).subnet_of(ip_network(subnet)): + raise ConfigError(f'Range prefix "{prefix}" is not in subnet "{subnet}"') + + # Prefix delegation sanity checks + if 'prefix_delegation' in subnet_config: + if 'prefix' not in subnet_config['prefix_delegation']: + raise ConfigError('prefix-delegation prefix not defined!') + + for prefix, prefix_config in subnet_config['prefix_delegation']['prefix'].items(): + if 'delegated_length' not in prefix_config: + raise ConfigError(f'Delegated IPv6 prefix length for "{prefix}" '\ + f'must be configured') + + if 'prefix_length' not in prefix_config: + raise ConfigError('Length of delegated IPv6 prefix must be configured') + + if prefix_config['prefix_length'] > prefix_config['delegated_length']: + raise ConfigError('Length of delegated IPv6 prefix must be within parent prefix') + + if 'excluded_prefix' in prefix_config: + if 'excluded_prefix_length' not in prefix_config: + raise ConfigError('Length of excluded IPv6 prefix must be configured') + + prefix_len = prefix_config['prefix_length'] + prefix_obj = ip_network(f'{prefix}/{prefix_len}') + + excluded_prefix = prefix_config['excluded_prefix'] + excluded_len = prefix_config['excluded_prefix_length'] + excluded_obj = ip_network(f'{excluded_prefix}/{excluded_len}') + + if excluded_len <= prefix_config['delegated_length']: + raise ConfigError('Excluded IPv6 prefix must be smaller than delegated prefix') + + if not excluded_obj.subnet_of(prefix_obj): + raise ConfigError(f'Excluded prefix "{excluded_prefix}" does not exist in the prefix') + + # Static mappings don't require anything (but check if IP is in subnet if it's set) + if 'static_mapping' in subnet_config: + for mapping, mapping_config in subnet_config['static_mapping'].items(): + if 'ipv6_address' in mapping_config: + # Static address must be in subnet + if ip_address(mapping_config['ipv6_address']) not in ip_network(subnet): + raise ConfigError(f'static-mapping address for mapping "{mapping}" is not in subnet "{subnet}"!') + + if ('mac' not in mapping_config and 'duid' not in mapping_config) or \ + ('mac' in mapping_config and 'duid' in mapping_config): + raise ConfigError(f'Either MAC address or Client identifier (DUID) is required for ' + f'static mapping "{mapping}" within shared-network "{network}, {subnet}"!') + + if 'option' in subnet_config: + if 'vendor_option' in subnet_config['option']: + if len(dict_search('option.vendor_option.cisco.tftp_server', subnet_config)) > 2: + raise ConfigError(f'No more than two Cisco tftp-servers should be defined for subnet "{subnet}"!') + + # Subnets must be unique + if subnet in subnets: + raise ConfigError(f'DHCPv6 subnets must be unique! Subnet {subnet} defined multiple times!') + + subnets.append(subnet) + + # DHCPv6 requires at least one configured address range or one static mapping + # (FIXME: is not actually checked right now?) + + # There must be one subnet connected to a listen interface if network is not disabled. + if 'disable' not in network_config: + if is_subnet_connected(subnet): + listen_ok = True + + # DHCPv6 subnet must not overlap. ISC DHCP also complains about overlapping + # subnets: "Warning: subnet 2001:db8::/32 overlaps subnet 2001:db8:1::/32" + net = ip_network(subnet) + for n in subnets: + net2 = ip_network(n) + if (net != net2): + if net.overlaps(net2): + raise ConfigError('DHCPv6 conflicting subnet ranges: {0} overlaps {1}'.format(net, net2)) + + if not listen_ok: + raise ConfigError('None of the DHCPv6 subnets are connected to a subnet6 on '\ + 'this machine. At least one subnet6 must be connected such that '\ + 'DHCPv6 listens on an interface!') + + + return None + +def generate(dhcpv6): + # bail out early - looks like removal from running config + if not dhcpv6 or 'disable' in dhcpv6: + return None + + dhcpv6['lease_file'] = lease_file + dhcpv6['machine'] = os.uname().machine + + # Create directory for lease file if necessary + lease_dir = os.path.dirname(lease_file) + if not os.path.isdir(lease_dir): + makedir(lease_dir, group='vyattacfg') + chmod_775(lease_dir) + + # Ensure correct permissions on lease files + backups + for file in glob(lease_file_glob): + chown(file, user=user_group, group='vyattacfg') + + # Create lease file if necessary and let kea own it - 'kea-lfc' expects it that way + if not os.path.exists(lease_file): + write_file(lease_file, '', user=user_group, group=user_group, mode=0o644) + + render(config_file, 'dhcp-server/kea-dhcp6.conf.j2', dhcpv6, user=user_group, group=user_group) + return None + +def apply(dhcpv6): + # bail out early - looks like removal from running config + service_name = 'kea-dhcp6-server.service' + if not dhcpv6 or 'disable' in dhcpv6: + # DHCP server is removed in the commit + call(f'systemctl stop {service_name}') + if os.path.exists(config_file): + os.unlink(config_file) + return None + + call(f'systemctl restart {service_name}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_dns_dynamic.py b/src/conf_mode/service_dns_dynamic.py new file mode 100644 index 0000000..5f53038 --- /dev/null +++ b/src/conf_mode/service_dns_dynamic.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import re +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configverify import verify_interface_exists +from vyos.configverify import dynamic_interface_pattern +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.network import interface_exists +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/run/ddclient/ddclient.conf' +systemd_override = r'/run/systemd/system/ddclient.service.d/override.conf' + +# Protocols that require zone +zone_necessary = ['cloudflare', 'digitalocean', 'godaddy', 'hetzner', 'gandi', + 'nfsn', 'nsupdate'] +zone_supported = zone_necessary + ['dnsexit2', 'zoneedit1'] + +# Protocols that do not require username +username_unnecessary = ['1984', 'cloudflare', 'cloudns', 'digitalocean', 'dnsexit2', + 'duckdns', 'freemyip', 'hetzner', 'keysystems', 'njalla', + 'nsupdate', 'regfishde'] + +# Protocols that support TTL +ttl_supported = ['cloudflare', 'dnsexit2', 'gandi', 'hetzner', 'godaddy', 'nfsn', + 'nsupdate'] + +# Protocols that support both IPv4 and IPv6 +dualstack_supported = ['cloudflare', 'digitalocean', 'dnsexit2', 'duckdns', + 'dyndns2', 'easydns', 'freedns', 'hetzner', 'infomaniak', + 'njalla'] + +# dyndns2 protocol in ddclient honors dual stack for selective servers +# because of the way it is implemented in ddclient +dyndns_dualstack_servers = ['members.dyndns.org', 'dynv6.com'] + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'dns', 'dynamic'] + if not conf.exists(base): + return None + + dyndns = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + dyndns['config_file'] = config_file + return dyndns + +def verify(dyndns): + # bail out early - looks like removal from running config + if not dyndns or 'name' not in dyndns: + return None + + # Dynamic DNS service provider - configuration validation + for service, config in dyndns['name'].items(): + error_msg_req = f'is required for Dynamic DNS service "{service}"' + error_msg_uns = f'is not supported for Dynamic DNS service "{service}"' + + for field in ['protocol', 'address', 'host_name']: + if field not in config: + raise ConfigError(f'"{field.replace("_", "-")}" {error_msg_req}') + + if not any(x in config['address'] for x in ['interface', 'web']): + raise ConfigError(f'Either "interface" or "web" {error_msg_req} ' + f'with protocol "{config["protocol"]}"') + if all(x in config['address'] for x in ['interface', 'web']): + raise ConfigError(f'Both "interface" and "web" at the same time {error_msg_uns} ' + f'with protocol "{config["protocol"]}"') + + # If dyndns address is an interface, ensure that the interface exists + # and warn if a non-active dynamic interface is used + if 'interface' in config['address']: + tmp = re.compile(dynamic_interface_pattern) + # exclude check interface for dynamic interfaces + if tmp.match(config['address']['interface']): + if not interface_exists(config['address']['interface']): + Warning(f'Interface "{config["address"]["interface"]}" does not exist yet and ' + f'cannot be used for Dynamic DNS service "{service}" until it is up!') + else: + verify_interface_exists(dyndns, config['address']['interface']) + + if 'web' in config['address']: + # If 'skip' is specified, 'url' is required as well + if 'skip' in config['address']['web'] and 'url' not in config['address']['web']: + raise ConfigError(f'"url" along with "skip" {error_msg_req} ' + f'with protocol "{config["protocol"]}"') + if 'url' in config['address']['web']: + # Warn if using checkip.dyndns.org, as it does not support HTTPS + # See: https://github.com/ddclient/ddclient/issues/597 + if re.search("^(https?://)?checkip\.dyndns\.org", config['address']['web']['url']): + Warning(f'"checkip.dyndns.org" does not support HTTPS requests for IP address ' + f'lookup. Please use a different IP address lookup service.') + + # RFC2136 uses 'key' instead of 'password' + if config['protocol'] != 'nsupdate' and 'password' not in config: + raise ConfigError(f'"password" {error_msg_req}') + + # Other RFC2136 specific configuration validation + if config['protocol'] == 'nsupdate': + if 'password' in config: + raise ConfigError(f'"password" {error_msg_uns} with protocol "{config["protocol"]}"') + for field in ['server', 'key']: + if field not in config: + raise ConfigError(f'"{field}" {error_msg_req} with protocol "{config["protocol"]}"') + + if config['protocol'] in zone_necessary and 'zone' not in config: + raise ConfigError(f'"zone" {error_msg_req} with protocol "{config["protocol"]}"') + + if config['protocol'] not in zone_supported and 'zone' in config: + raise ConfigError(f'"zone" {error_msg_uns} with protocol "{config["protocol"]}"') + + if config['protocol'] not in username_unnecessary and 'username' not in config: + raise ConfigError(f'"username" {error_msg_req} with protocol "{config["protocol"]}"') + + if config['protocol'] not in ttl_supported and 'ttl' in config: + raise ConfigError(f'"ttl" {error_msg_uns} with protocol "{config["protocol"]}"') + + if config['ip_version'] == 'both': + if config['protocol'] not in dualstack_supported: + raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} ' + f'with protocol "{config["protocol"]}"') + # dyndns2 protocol in ddclient honors dual stack only for dyn.com (dyndns.org) + if config['protocol'] == 'dyndns2' and 'server' in config and config['server'] not in dyndns_dualstack_servers: + raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} ' + f'for "{config["server"]}" with protocol "{config["protocol"]}"') + + if {'wait_time', 'expiry_time'} <= config.keys() and int(config['expiry_time']) < int(config['wait_time']): + raise ConfigError(f'"expiry-time" must be greater than "wait-time" for ' + f'Dynamic DNS service "{service}"') + + return None + +def generate(dyndns): + # bail out early - looks like removal from running config + if not dyndns or 'name' not in dyndns: + return None + + render(config_file, 'dns-dynamic/ddclient.conf.j2', dyndns, permission=0o600) + render(systemd_override, 'dns-dynamic/override.conf.j2', dyndns) + return None + +def apply(dyndns): + systemd_service = 'ddclient.service' + # Reload systemd manager configuration + call('systemctl daemon-reload') + + # bail out early - looks like removal from running config + if not dyndns or 'name' not in dyndns: + call(f'systemctl stop {systemd_service}') + if os.path.exists(config_file): + os.unlink(config_file) + else: + call(f'systemctl reload-or-restart {systemd_service}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_dns_forwarding.py b/src/conf_mode/service_dns_forwarding.py new file mode 100644 index 0000000..e3bdbc9 --- /dev/null +++ b/src/conf_mode/service_dns_forwarding.py @@ -0,0 +1,402 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit +from glob import glob + +from vyos.config import Config +from vyos.hostsd_client import Client as hostsd_client +from vyos.template import render +from vyos.template import bracketize_ipv6 +from vyos.utils.network import interface_exists +from vyos.utils.process import call +from vyos.utils.permission import chown +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +pdns_rec_user_group = 'pdns' +pdns_rec_run_dir = '/run/pdns-recursor' +pdns_rec_lua_conf_file = f'{pdns_rec_run_dir}/recursor.conf.lua' +pdns_rec_hostsd_lua_conf_file = f'{pdns_rec_run_dir}/recursor.vyos-hostsd.conf.lua' +pdns_rec_hostsd_zones_file = f'{pdns_rec_run_dir}/recursor.forward-zones.conf' +pdns_rec_config_file = f'{pdns_rec_run_dir}/recursor.conf' +pdns_rec_systemd_override = '/run/systemd/system/pdns-recursor.service.d/override.conf' + +hostsd_tag = 'static' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'dns', 'forwarding'] + if not conf.exists(base): + return None + + dns = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + dns['config_file'] = pdns_rec_config_file + dns['config_dir'] = os.path.dirname(pdns_rec_config_file) + + # some additions to the default dictionary + if 'system' in dns: + base_nameservers = ['system', 'name-server'] + if conf.exists(base_nameservers): + dns.update({'system_name_server': conf.return_values(base_nameservers)}) + + if 'authoritative_domain' in dns: + dns['authoritative_zones'] = [] + dns['authoritative_zone_errors'] = [] + for node in dns['authoritative_domain']: + zonedata = dns['authoritative_domain'][node] + if ('disable' in zonedata) or (not 'records' in zonedata): + continue + zone = { + 'name': node, + 'file': "{}/zone.{}.conf".format(pdns_rec_run_dir, node), + 'records': [], + } + + recorddata = zonedata['records'] + + for rtype in [ 'a', 'aaaa', 'cname', 'mx', 'ns', 'ptr', 'txt', 'spf', 'srv', 'naptr' ]: + if rtype not in recorddata: + continue + for subnode in recorddata[rtype]: + if 'disable' in recorddata[rtype][subnode]: + continue + + rdata = recorddata[rtype][subnode] + + if rtype in [ 'a', 'aaaa' ]: + if not 'address' in rdata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one address is required') + continue + + if subnode == 'any': + subnode = '*' + + for address in rdata['address']: + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': address + }) + elif rtype in ['cname', 'ptr']: + if not 'target' in rdata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: target is required') + continue + + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '{}.'.format(rdata['target']) + }) + elif rtype == 'ns': + if not 'target' in rdata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one target is required') + continue + + for target in rdata['target']: + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': f'{target}.' + }) + + elif rtype == 'mx': + if not 'server' in rdata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one server is required') + continue + + for servername in rdata['server']: + serverdata = rdata['server'][servername] + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '{} {}.'.format(serverdata['priority'], servername) + }) + elif rtype == 'txt': + if not 'value' in rdata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one value is required') + continue + + for value in rdata['value']: + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': "\"{}\"".format(value.replace("\"", "\\\"")) + }) + elif rtype == 'spf': + if not 'value' in rdata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: value is required') + continue + + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '"{}"'.format(rdata['value'].replace("\"", "\\\"")) + }) + elif rtype == 'srv': + if not 'entry' in rdata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one entry is required') + continue + + for entryno in rdata['entry']: + entrydata = rdata['entry'][entryno] + if not 'hostname' in entrydata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: hostname is required for entry {entryno}') + continue + + if not 'port' in entrydata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: port is required for entry {entryno}') + continue + + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '{} {} {} {}.'.format(entrydata['priority'], entrydata['weight'], entrydata['port'], entrydata['hostname']) + }) + elif rtype == 'naptr': + if not 'rule' in rdata: + dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one rule is required') + continue + + for ruleno in rdata['rule']: + ruledata = rdata['rule'][ruleno] + flags = "" + if 'lookup-srv' in ruledata: + flags += "S" + if 'lookup-a' in ruledata: + flags += "A" + if 'resolve-uri' in ruledata: + flags += "U" + if 'protocol-specific' in ruledata: + flags += "P" + + if 'order' in ruledata: + order = ruledata['order'] + else: + order = ruleno + + if 'regexp' in ruledata: + regexp= ruledata['regexp'].replace("\"", "\\\"") + else: + regexp = '' + + if ruledata['replacement']: + replacement = '{}.'.format(ruledata['replacement']) + else: + replacement = '' + + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '{} {} "{}" "{}" "{}" {}'.format(order, ruledata['preference'], flags, ruledata['service'], regexp, replacement) + }) + + dns['authoritative_zones'].append(zone) + + if 'zone_cache' in dns: + # convert refresh interval to sec: + for _, zone_conf in dns['zone_cache'].items(): + if 'options' in zone_conf \ + and 'refresh' in zone_conf['options']: + + if 'on_reload' in zone_conf['options']['refresh']: + interval = 0 + else: + interval = zone_conf['options']['refresh']['interval'] + zone_conf['options']['refresh']['interval'] = interval + + return dns + +def verify(dns): + # bail out early - looks like removal from running config + if not dns: + return None + + if 'listen_address' not in dns: + raise ConfigError('DNS forwarding requires a listen-address') + + if 'allow_from' not in dns: + raise ConfigError('DNS forwarding requires an allow-from network') + + # we can not use dict_search() when testing for domain servers + # as a domain will contains dot's which is out dictionary delimiter. + if 'domain' in dns: + for domain in dns['domain']: + if 'name_server' not in dns['domain'][domain]: + raise ConfigError(f'No server configured for domain {domain}!') + + if 'dns64_prefix' in dns: + dns_prefix = dns['dns64_prefix'].split('/')[1] + # RFC 6147 requires prefix /96 + if int(dns_prefix) != 96: + raise ConfigError('DNS 6to4 prefix must be of length /96') + + if ('authoritative_zone_errors' in dns) and dns['authoritative_zone_errors']: + for error in dns['authoritative_zone_errors']: + print(error) + raise ConfigError('Invalid authoritative records have been defined') + + if 'system' in dns: + if not 'system_name_server' in dns: + print('Warning: No "system name-server" configured') + + if 'zone_cache' in dns: + for name, conf in dns['zone_cache'].items(): + if ('source' not in conf) \ + or ('url' in conf['source'] and 'axfr' in conf['source']): + raise ConfigError(f'Invalid configuration for zone "{name}": ' + f'Please select one source type "url" or "axfr".') + + return None + + +def generate(dns): + # bail out early - looks like removal from running config + if not dns: + return None + + render(pdns_rec_systemd_override, 'dns-forwarding/override.conf.j2', dns) + + render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.j2', dns, + user=pdns_rec_user_group, group=pdns_rec_user_group) + + render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.j2', dns, + user=pdns_rec_user_group, group=pdns_rec_user_group) + + render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.j2', dns, + user=pdns_rec_user_group, group=pdns_rec_user_group) + + for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'): + os.unlink(zone_filename) + + if 'authoritative_zones' in dns: + for zone in dns['authoritative_zones']: + render(zone['file'], 'dns-forwarding/recursor.zone.conf.j2', + zone, user=pdns_rec_user_group, group=pdns_rec_user_group) + + + # if vyos-hostsd didn't create its files yet, create them (empty) + for file in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]: + with open(file, 'a'): + pass + chown(file, user=pdns_rec_user_group, group=pdns_rec_user_group) + + return None + +def apply(dns): + systemd_service = 'pdns-recursor.service' + # Reload systemd manager configuration + call('systemctl daemon-reload') + + if not dns: + # DNS forwarding is removed in the commit + call(f'systemctl stop {systemd_service}') + + if os.path.isfile(pdns_rec_config_file): + os.unlink(pdns_rec_config_file) + + for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'): + os.unlink(zone_filename) + else: + ### first apply vyos-hostsd config + hc = hostsd_client() + + # add static nameservers to hostsd so they can be joined with other + # sources + hc.delete_name_servers([hostsd_tag]) + if 'name_server' in dns: + # 'name_server' is of the form + # {'192.0.2.1': {'port': 53}, '2001:db8::1': {'port': 853}, ...} + # canonicalize them as ['192.0.2.1:53', '[2001:db8::1]:853', ...] + nslist = [(lambda h, p: f"{bracketize_ipv6(h)}:{p['port']}")(h, p) + for (h, p) in dns['name_server'].items()] + hc.add_name_servers({hostsd_tag: nslist}) + + # delete all nameserver tags + hc.delete_name_server_tags_recursor(hc.get_name_server_tags_recursor()) + + ## add nameserver tags - the order determines the nameserver order! + # our own tag (static) + hc.add_name_server_tags_recursor([hostsd_tag]) + + if 'system' in dns: + hc.add_name_server_tags_recursor(['system']) + else: + hc.delete_name_server_tags_recursor(['system']) + + # add dhcp nameserver tags for configured interfaces + if 'system_name_server' in dns: + for interface in dns['system_name_server']: + # system_name_server key contains both IP addresses and interface + # names (DHCP) to use DNS servers. We need to check if the + # value is an interface name - only if this is the case, add the + # interface based DNS forwarder. + if interface_exists(interface): + hc.add_name_server_tags_recursor(['dhcp-' + interface, + 'dhcpv6-' + interface ]) + + # hostsd will generate the forward-zones file + # the list and keys() are required as get returns a dict, not list + hc.delete_forward_zones(list(hc.get_forward_zones().keys())) + if 'domain' in dns: + zones = dns['domain'] + for domain in zones.keys(): + # 'name_server' is of the form + # {'192.0.2.1': {'port': 53}, '2001:db8::1': {'port': 853}, ...} + # canonicalize them as ['192.0.2.1:53', '[2001:db8::1]:853', ...] + zones[domain]['name_server'] = [(lambda h, p: f"{bracketize_ipv6(h)}:{p['port']}")(h, p) + for (h, p) in zones[domain]['name_server'].items()] + hc.add_forward_zones(zones) + + # hostsd generates NTAs for the authoritative zones + # the list and keys() are required as get returns a dict, not list + hc.delete_authoritative_zones(list(hc.get_authoritative_zones())) + if 'authoritative_zones' in dns: + hc.add_authoritative_zones(list(map(lambda zone: zone['name'], dns['authoritative_zones']))) + + # call hostsd to generate forward-zones and its lua-config-file + hc.apply() + + ### finally (re)start pdns-recursor + call(f'systemctl reload-or-restart {systemd_service}') + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_event-handler.py b/src/conf_mode/service_event-handler.py new file mode 100644 index 0000000..5028ef5 --- /dev/null +++ b/src/conf_mode/service_event-handler.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json +from pathlib import Path + +from vyos.config import Config +from vyos.utils.dict import dict_search +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag + +airbag.enable() + +service_name = 'vyos-event-handler' +service_conf = Path(f'/run/{service_name}.conf') + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'event-handler', 'event'] + config = conf.get_config_dict(base, + get_first_key=True, + no_tag_node_value_mangle=True) + + return config + + +def verify(config): + # bail out early - looks like removal from running config + if not config: + return None + + for name, event_config in config.items(): + if not dict_search('filter.pattern', event_config) or not dict_search( + 'script.path', event_config): + raise ConfigError( + 'Event-handler: both pattern and script path items are mandatory' + ) + + if dict_search('script.environment.message', event_config): + raise ConfigError( + 'Event-handler: "message" environment variable is reserved for log message text' + ) + + +def generate(config): + if not config: + # Remove old config and return + service_conf.unlink(missing_ok=True) + return None + + # Write configuration file + conf_json = json.dumps(config, indent=4) + service_conf.write_text(conf_json) + + return None + + +def apply(config): + if config: + call(f'systemctl restart {service_name}.service') + else: + call(f'systemctl stop {service_name}.service') + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_https.py b/src/conf_mode/service_https.py new file mode 100644 index 0000000..9e58b4c --- /dev/null +++ b/src/conf_mode/service_https.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import socket +import sys +import json + +from time import sleep + +from vyos.base import Warning +from vyos.config import Config +from vyos.config import config_dict_merge +from vyos.configverify import verify_vrf +from vyos.configverify import verify_pki_certificate +from vyos.configverify import verify_pki_ca_certificate +from vyos.configverify import verify_pki_dh_parameters +from vyos.defaults import api_config_state +from vyos.pki import wrap_certificate +from vyos.pki import wrap_private_key +from vyos.pki import wrap_dh_parameters +from vyos.template import render +from vyos.utils.dict import dict_search +from vyos.utils.process import call +from vyos.utils.process import is_systemd_service_active +from vyos.utils.network import check_port_availability +from vyos.utils.network import is_listen_port_bind_service +from vyos.utils.file import write_file +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = '/etc/nginx/sites-enabled/default' +systemd_override = r'/run/systemd/system/nginx.service.d/override.conf' +cert_dir = '/run/nginx/certs' + +user = 'www-data' +group = 'www-data' + +systemd_service_api = '/run/systemd/system/vyos-http-api.service' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'https'] + if not conf.exists(base): + return None + + https = conf.get_config_dict(base, get_first_key=True, + key_mangling=('-', '_'), + with_pki=True) + + # store path to API config file for later use in templates + https['api_config_state'] = api_config_state + # get fully qualified system hsotname + https['hostname'] = socket.getfqdn() + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = conf.get_config_defaults(**https.kwargs, recursive=True) + if 'api' not in https or 'graphql' not in https['api']: + del default_values['api'] + + # merge CLI and default dictionary + https = config_dict_merge(default_values, https) + return https + +def verify(https): + if https is None: + return None + + if dict_search('certificates.certificate', https) != None: + verify_pki_certificate(https, https['certificates']['certificate']) + + tmp = dict_search('certificates.ca_certificate', https) + if tmp != None: verify_pki_ca_certificate(https, tmp) + + tmp = dict_search('certificates.dh_params', https) + if tmp != None: verify_pki_dh_parameters(https, tmp, 2048) + + else: + Warning('No certificate specified, using build-in self-signed certificates. '\ + 'Do not use them in a production environment!') + + # Check if server port is already in use by a different appliaction + listen_address = ['0.0.0.0'] + port = int(https['port']) + if 'listen_address' in https: + listen_address = https['listen_address'] + + for address in listen_address: + if not check_port_availability(address, port, 'tcp') and not is_listen_port_bind_service(port, 'nginx'): + raise ConfigError(f'TCP port "{port}" is used by another service!') + + verify_vrf(https) + + # Verify API server settings, if present + if 'api' in https: + keys = dict_search('api.keys.id', https) + gql_auth_type = dict_search('api.graphql.authentication.type', https) + + # If "api graphql" is not defined and `gql_auth_type` is None, + # there's certainly no JWT auth option, and keys are required + jwt_auth = (gql_auth_type == "token") + + # Check for incomplete key configurations in every case + valid_keys_exist = False + if keys: + for k in keys: + if 'key' not in keys[k]: + raise ConfigError(f'Missing HTTPS API key string for key id "{k}"') + else: + valid_keys_exist = True + + # If only key-based methods are enabled, + # fail the commit if no valid key configurations are found + if (not valid_keys_exist) and (not jwt_auth): + raise ConfigError('At least one HTTPS API key is required unless GraphQL token authentication is enabled!') + + if (not valid_keys_exist) and jwt_auth: + Warning(f'API keys are not configured: classic (non-GraphQL) API will be unavailable!') + + return None + +def generate(https): + if https is None: + for file in [systemd_service_api, config_file, systemd_override]: + if os.path.exists(file): + os.unlink(file) + return None + + if 'api' in https: + render(systemd_service_api, 'https/vyos-http-api.service.j2', https) + with open(api_config_state, 'w') as f: + json.dump(https['api'], f, indent=2) + else: + if os.path.exists(systemd_service_api): + os.unlink(systemd_service_api) + + # get certificate data + if 'certificates' in https and 'certificate' in https['certificates']: + cert_name = https['certificates']['certificate'] + pki_cert = https['pki']['certificate'][cert_name] + + cert_path = os.path.join(cert_dir, f'{cert_name}_cert.pem') + key_path = os.path.join(cert_dir, f'{cert_name}_key.pem') + + server_cert = str(wrap_certificate(pki_cert['certificate'])) + + # Append CA certificate if specified to form a full chain + if 'ca_certificate' in https['certificates']: + ca_cert = https['certificates']['ca_certificate'] + server_cert += '\n' + str(wrap_certificate(https['pki']['ca'][ca_cert]['certificate'])) + + write_file(cert_path, server_cert, user=user, group=group, mode=0o644) + write_file(key_path, wrap_private_key(pki_cert['private']['key']), + user=user, group=group, mode=0o600) + + tmp_path = {'cert_path': cert_path, 'key_path': key_path} + + if 'dh_params' in https['certificates']: + dh_name = https['certificates']['dh_params'] + pki_dh = https['pki']['dh'][dh_name] + if 'parameters' in pki_dh: + dh_path = os.path.join(cert_dir, f'{dh_name}_dh.pem') + write_file(dh_path, wrap_dh_parameters(pki_dh['parameters']), + user=user, group=group, mode=0o600) + tmp_path.update({'dh_file' : dh_path}) + + https['certificates'].update(tmp_path) + + render(config_file, 'https/nginx.default.j2', https) + render(systemd_override, 'https/override.conf.j2', https) + return None + +def apply(https): + # Reload systemd manager configuration + call('systemctl daemon-reload') + http_api_service_name = 'vyos-http-api.service' + https_service_name = 'nginx.service' + + if https is None: + if is_systemd_service_active(http_api_service_name): + call(f'systemctl stop {http_api_service_name}') + call(f'systemctl stop {https_service_name}') + return + + if 'api' in https: + call(f'systemctl reload-or-restart {http_api_service_name}') + # Let uvicorn settle before (possibly) restarting nginx + sleep(1) + elif is_systemd_service_active(http_api_service_name): + call(f'systemctl stop {http_api_service_name}') + + call(f'systemctl reload-or-restart {https_service_name}') + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) diff --git a/src/conf_mode/service_ids_ddos-protection.py b/src/conf_mode/service_ids_ddos-protection.py new file mode 100644 index 0000000..276a71f --- /dev/null +++ b/src/conf_mode/service_ids_ddos-protection.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/run/fastnetmon/fastnetmon.conf' +networks_list = r'/run/fastnetmon/networks_list' +excluded_networks_list = r'/run/fastnetmon/excluded_networks_list' +attack_dir = '/var/log/fastnetmon_attacks' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'ids', 'ddos-protection'] + if not conf.exists(base): + return None + + fastnetmon = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + return fastnetmon + +def verify(fastnetmon): + if not fastnetmon: + return None + + if 'mode' not in fastnetmon: + raise ConfigError('Specify operating mode!') + + if fastnetmon.get('mode') == 'mirror' and 'listen_interface' not in fastnetmon: + raise ConfigError("Incorrect settings for 'mode mirror': must specify interface(s) for traffic mirroring") + + if fastnetmon.get('mode') == 'sflow' and 'listen_address' not in fastnetmon.get('sflow', {}): + raise ConfigError("Incorrect settings for 'mode sflow': must specify sFlow 'listen-address'") + + if 'alert_script' in fastnetmon: + if os.path.isfile(fastnetmon['alert_script']): + # Check script permissions + if not os.access(fastnetmon['alert_script'], os.X_OK): + raise ConfigError('Script "{alert_script}" is not executable!'.format(fastnetmon['alert_script'])) + else: + raise ConfigError('File "{alert_script}" does not exists!'.format(fastnetmon)) + +def generate(fastnetmon): + if not fastnetmon: + for file in [config_file, networks_list]: + if os.path.isfile(file): + os.unlink(file) + + return None + + # Create dir for log attack details + if not os.path.exists(attack_dir): + os.mkdir(attack_dir) + + render(config_file, 'ids/fastnetmon.j2', fastnetmon) + render(networks_list, 'ids/fastnetmon_networks_list.j2', fastnetmon) + render(excluded_networks_list, 'ids/fastnetmon_excluded_networks_list.j2', fastnetmon) + return None + +def apply(fastnetmon): + systemd_service = 'fastnetmon.service' + if not fastnetmon: + # Stop fastnetmon service if removed + call(f'systemctl stop {systemd_service}') + else: + call(f'systemctl reload-or-restart {systemd_service}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py new file mode 100644 index 0000000..c7e3ef0 --- /dev/null +++ b/src/conf_mode/service_ipoe-server.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_accel_dict +from vyos.configverify import verify_interface_exists +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.accel_ppp_util import get_pools_in_order +from vyos.accel_ppp_util import verify_accel_ppp_name_servers +from vyos.accel_ppp_util import verify_accel_ppp_wins_servers +from vyos.accel_ppp_util import verify_accel_ppp_ip_pool +from vyos.accel_ppp_util import verify_accel_ppp_authentication +from vyos import ConfigError +from vyos import airbag +airbag.enable() + + +ipoe_conf = '/run/accel-pppd/ipoe.conf' +ipoe_chap_secrets = '/run/accel-pppd/ipoe.chap-secrets' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'ipoe-server'] + if not conf.exists(base): + return None + + # retrieve common dictionary keys + ipoe = get_accel_dict(conf, base, ipoe_chap_secrets) + + if dict_search('client_ip_pool', ipoe): + # Multiple named pools require ordered values T5099 + ipoe['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', ipoe)) + + ipoe['server_type'] = 'ipoe' + return ipoe + + +def verify(ipoe): + if not ipoe: + return None + + if 'interface' not in ipoe: + raise ConfigError('No IPoE interface configured') + + for interface, iface_config in ipoe['interface'].items(): + verify_interface_exists(ipoe, interface, warning_only=True) + if 'client_subnet' in iface_config and 'vlan' in iface_config: + raise ConfigError('Option "client-subnet" and "vlan" are mutually exclusive, ' + 'use "client-ip-pool" instead!') + if 'vlan_mon' in iface_config and not 'vlan' in iface_config: + raise ConfigError('Option "vlan-mon" requires "vlan" to be set!') + + verify_accel_ppp_authentication(ipoe, local_users=False) + verify_accel_ppp_ip_pool(ipoe) + verify_accel_ppp_name_servers(ipoe) + verify_accel_ppp_wins_servers(ipoe) + + return None + + +def generate(ipoe): + if not ipoe: + return None + + render(ipoe_conf, 'accel-ppp/ipoe.config.j2', ipoe) + + if dict_search('authentication.mode', ipoe) == 'local': + render(ipoe_chap_secrets, 'accel-ppp/chap-secrets.ipoe.j2', + ipoe, permission=0o640) + return None + + +def apply(ipoe): + systemd_service = 'accel-ppp@ipoe.service' + if ipoe == None: + call(f'systemctl stop {systemd_service}') + for file in [ipoe_conf, ipoe_chap_secrets]: + if os.path.exists(file): + os.unlink(file) + + return None + + call(f'systemctl reload-or-restart {systemd_service}') + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_lldp.py b/src/conf_mode/service_lldp.py new file mode 100644 index 0000000..04b1db8 --- /dev/null +++ b/src/conf_mode/service_lldp.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2017-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.utils.network import is_addr_assigned +from vyos.utils.network import is_loopback_addr +from vyos.version import get_version_data +from vyos.utils.process import call +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = "/etc/default/lldpd" +vyos_config_file = "/etc/lldpd.d/01-vyos.conf" +base = ['service', 'lldp'] + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + if not conf.exists(base): + return {} + + lldp = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + if conf.exists(['service', 'snmp']): + lldp['system_snmp_enabled'] = '' + + version_data = get_version_data() + lldp['version'] = version_data['version'] + + # prune location information if not set by user + for interface in lldp.get('interface', []): + if lldp.from_defaults(['interface', interface, 'location']): + del lldp['interface'][interface]['location'] + elif lldp.from_defaults(['interface', interface, 'location','coordinate_based']): + del lldp['interface'][interface]['location']['coordinate_based'] + + return lldp + +def verify(lldp): + # bail out early - looks like removal from running config + if lldp is None: + return + + if 'management_address' in lldp: + for address in lldp['management_address']: + message = f'LLDP management address "{address}" is invalid' + if is_loopback_addr(address): + Warning(f'{message} - loopback address') + elif not is_addr_assigned(address): + Warning(f'{message} - not assigned to any interface') + + if 'interface' in lldp: + for interface, interface_config in lldp['interface'].items(): + # bail out early if no location info present in interface config + if 'location' not in interface_config: + continue + if 'coordinate_based' in interface_config['location']: + if not {'latitude', 'latitude'} <= set(interface_config['location']['coordinate_based']): + raise ConfigError(f'Must define both longitude and latitude for "{interface}" location!') + + # check options + if 'snmp' in lldp: + if 'system_snmp_enabled' not in lldp: + raise ConfigError('SNMP must be configured to enable LLDP SNMP!') + + +def generate(lldp): + # bail out early - looks like removal from running config + if lldp is None: + return + + render(config_file, 'lldp/lldpd.j2', lldp) + render(vyos_config_file, 'lldp/vyos.conf.j2', lldp) + +def apply(lldp): + systemd_service = 'lldpd.service' + if lldp: + # start/restart lldp service + call(f'systemctl restart {systemd_service}') + else: + # LLDP service has been terminated + call(f'systemctl stop {systemd_service}') + if os.path.isfile(config_file): + os.unlink(config_file) + if os.path.isfile(vyos_config_file): + os.unlink(vyos_config_file) + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_mdns_repeater.py b/src/conf_mode/service_mdns_repeater.py new file mode 100644 index 0000000..b0ece03 --- /dev/null +++ b/src/conf_mode/service_mdns_repeater.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2017-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from json import loads +from sys import exit +from netifaces import ifaddresses, AF_INET, AF_INET6 + +from vyos.config import Config +from vyos.configverify import verify_interface_exists +from vyos.ifconfig.vrrp import VRRP +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = '/run/avahi-daemon/avahi-daemon.conf' +systemd_override = r'/run/systemd/system/avahi-daemon.service.d/override.conf' +vrrp_running_file = '/run/mdns_vrrp_active' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'mdns', 'repeater'] + if not conf.exists(base): + return None + + mdns = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + if mdns: + mdns['vrrp_exists'] = conf.exists('high-availability vrrp') + mdns['config_file'] = config_file + + return mdns + +def verify(mdns): + if not mdns or 'disable' in mdns: + return None + + # We need at least two interfaces to repeat mDNS advertisments + if 'interface' not in mdns or len(mdns['interface']) < 2: + raise ConfigError('mDNS repeater requires at least 2 configured interfaces!') + + # For mdns-repeater to work it is essential that the interfaces has + # an IPv4 address assigned + for interface in mdns['interface']: + verify_interface_exists(mdns, interface) + + if mdns['ip_version'] in ['ipv4', 'both'] and AF_INET not in ifaddresses(interface): + raise ConfigError('mDNS repeater requires an IPv4 address to be ' + f'configured on interface "{interface}"') + + if mdns['ip_version'] in ['ipv6', 'both'] and AF_INET6 not in ifaddresses(interface): + raise ConfigError('mDNS repeater requires an IPv6 address to be ' + f'configured on interface "{interface}"') + + return None + +# Get VRRP states from interfaces, returns only interfaces where state is MASTER +def get_vrrp_master(interfaces): + json_data = loads(VRRP.collect('json')) + for group in json_data: + if 'data' in group: + if 'ifp_ifname' in group['data']: + iface = group['data']['ifp_ifname'] + state = group['data']['state'] # 2 = Master + if iface in interfaces and state != 2: + interfaces.remove(iface) + return interfaces + +def generate(mdns): + if not mdns: + return None + + if 'disable' in mdns: + print('Warning: mDNS repeater will be deactivated because it is disabled') + return None + + if mdns['vrrp_exists'] and 'vrrp_disable' in mdns: + mdns['interface'] = get_vrrp_master(mdns['interface']) + + if len(mdns['interface']) < 2: + return None + + render(config_file, 'mdns-repeater/avahi-daemon.conf.j2', mdns) + render(systemd_override, 'mdns-repeater/override.conf.j2', mdns) + return None + +def apply(mdns): + systemd_service = 'avahi-daemon.service' + # Reload systemd manager configuration + call('systemctl daemon-reload') + + if not mdns or 'disable' in mdns: + call(f'systemctl stop {systemd_service}') + if os.path.exists(config_file): + os.unlink(config_file) + + if os.path.exists(vrrp_running_file): + os.unlink(vrrp_running_file) + else: + if 'vrrp_disable' not in mdns and os.path.exists(vrrp_running_file): + os.unlink(vrrp_running_file) + + if mdns['vrrp_exists'] and 'vrrp_disable' in mdns: + if not os.path.exists(vrrp_running_file): + os.mknod(vrrp_running_file) # vrrp script looks for this file to update mdns repeater + + if len(mdns['interface']) < 2: + call(f'systemctl stop {systemd_service}') + return None + + call(f'systemctl restart {systemd_service}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py new file mode 100644 index 0000000..db870aa --- /dev/null +++ b/src/conf_mode/service_monitoring_telegraf.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import socket +import json + +from sys import exit +from shutil import rmtree + +from vyos.config import Config +from vyos.configdict import is_node_changed +from vyos.configverify import verify_vrf +from vyos.ifconfig import Section +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.permission import chown +from vyos.utils.process import cmd +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +cache_dir = f'/etc/telegraf/.cache' +config_telegraf = f'/run/telegraf/telegraf.conf' +custom_scripts_dir = '/etc/telegraf/custom_scripts' +syslog_telegraf = '/etc/rsyslog.d/50-telegraf.conf' +systemd_override = '/run/systemd/system/telegraf.service.d/10-override.conf' + +def get_nft_filter_chains(): + """ Get nft chains for table filter """ + try: + nft = cmd('nft --json list table ip vyos_filter') + except Exception: + print('nft table ip vyos_filter not found') + return [] + nft = json.loads(nft) + chain_list = [] + + for output in nft['nftables']: + if 'chain' in output: + chain = output['chain']['name'] + chain_list.append(chain) + + return chain_list + +def get_hostname() -> str: + try: + hostname = socket.getfqdn() + except socket.gaierror: + hostname = socket.gethostname() + return hostname + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'monitoring', 'telegraf'] + if not conf.exists(base): + return None + + monitoring = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + tmp = is_node_changed(conf, base + ['vrf']) + if tmp: monitoring.update({'restart_required': {}}) + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + monitoring = conf.merge_defaults(monitoring, recursive=True) + + monitoring['custom_scripts_dir'] = custom_scripts_dir + monitoring['hostname'] = get_hostname() + monitoring['interfaces_ethernet'] = Section.interfaces('ethernet', vlan=False) + if conf.exists('firewall'): + monitoring['nft_chains'] = get_nft_filter_chains() + + # Redefine azure group-metrics 'single-table' and 'table-per-metric' + if 'azure_data_explorer' in monitoring: + if 'single-table' in monitoring['azure_data_explorer']['group_metrics']: + monitoring['azure_data_explorer']['group_metrics'] = 'SingleTable' + else: + monitoring['azure_data_explorer']['group_metrics'] = 'TablePerMetric' + # Set azure env + if 'authentication' in monitoring['azure_data_explorer']: + auth_config = monitoring['azure_data_explorer']['authentication'] + if {'client_id', 'client_secret', 'tenant_id'} <= set(auth_config): + os.environ['AZURE_CLIENT_ID'] = auth_config['client_id'] + os.environ['AZURE_CLIENT_SECRET'] = auth_config['client_secret'] + os.environ['AZURE_TENANT_ID'] = auth_config['tenant_id'] + + # Ignore default XML values if config doesn't exists + # Delete key from dict + if not conf.exists(base + ['influxdb']): + del monitoring['influxdb'] + + if not conf.exists(base + ['prometheus-client']): + del monitoring['prometheus_client'] + + if not conf.exists(base + ['azure-data-explorer']): + del monitoring['azure_data_explorer'] + + if not conf.exists(base + ['loki']): + del monitoring['loki'] + + return monitoring + +def verify(monitoring): + # bail out early - looks like removal from running config + if not monitoring: + return None + + verify_vrf(monitoring) + + # Verify influxdb + if 'influxdb' in monitoring: + if 'authentication' not in monitoring['influxdb'] or \ + 'organization' not in monitoring['influxdb']['authentication'] or \ + 'token' not in monitoring['influxdb']['authentication']: + raise ConfigError(f'influxdb authentication "organization and token" are mandatory!') + + if 'url' not in monitoring['influxdb']: + raise ConfigError(f'Monitoring influxdb "url" is mandatory!') + + # Verify azure-data-explorer + if 'azure_data_explorer' in monitoring: + if 'authentication' not in monitoring['azure_data_explorer'] or \ + 'client_id' not in monitoring['azure_data_explorer']['authentication'] or \ + 'client_secret' not in monitoring['azure_data_explorer']['authentication'] or \ + 'tenant_id' not in monitoring['azure_data_explorer']['authentication']: + raise ConfigError(f'Authentication "client-id, client-secret and tenant-id" are mandatory!') + + if 'database' not in monitoring['azure_data_explorer']: + raise ConfigError(f'Monitoring "database" is mandatory!') + + if 'url' not in monitoring['azure_data_explorer']: + raise ConfigError(f'Monitoring "url" is mandatory!') + + if monitoring['azure_data_explorer']['group_metrics'] == 'SingleTable' and \ + 'table' not in monitoring['azure_data_explorer']: + raise ConfigError(f'Monitoring "table" name for single-table mode is mandatory!') + + # Verify Splunk + if 'splunk' in monitoring: + if 'authentication' not in monitoring['splunk'] or \ + 'token' not in monitoring['splunk']['authentication']: + raise ConfigError(f'Authentication "organization and token" are mandatory!') + + if 'url' not in monitoring['splunk']: + raise ConfigError(f'Monitoring splunk "url" is mandatory!') + + # Verify Loki + if 'loki' in monitoring: + if 'url' not in monitoring['loki']: + raise ConfigError(f'Monitoring loki "url" is mandatory!') + if 'authentication' in monitoring['loki']: + if ( + 'username' not in monitoring['loki']['authentication'] + or 'password' not in monitoring['loki']['authentication'] + ): + raise ConfigError( + f'Authentication "username" and "password" are mandatory!' + ) + + return None + +def generate(monitoring): + if not monitoring: + # Delete config and systemd files + config_files = [config_telegraf, systemd_override, syslog_telegraf] + for file in config_files: + if os.path.isfile(file): + os.unlink(file) + + # Delete old directories + if os.path.isdir(cache_dir): + rmtree(cache_dir, ignore_errors=True) + + return None + + # Create telegraf cache dir + if not os.path.exists(cache_dir): + os.makedirs(cache_dir) + + chown(cache_dir, 'telegraf', 'telegraf') + + # Create custome scripts dir + if not os.path.exists(custom_scripts_dir): + os.mkdir(custom_scripts_dir) + + # Render telegraf configuration and systemd override + render(config_telegraf, 'telegraf/telegraf.j2', monitoring, user='telegraf', group='telegraf') + render(systemd_override, 'telegraf/override.conf.j2', monitoring) + render(syslog_telegraf, 'telegraf/syslog_telegraf.j2', monitoring) + + return None + +def apply(monitoring): + # Reload systemd manager configuration + systemd_service = 'telegraf.service' + call('systemctl daemon-reload') + if not monitoring: + call(f'systemctl stop {systemd_service}') + return + + # we need to restart the service if e.g. the VRF name changed + systemd_action = 'reload-or-restart' + if 'restart_required' in monitoring: + systemd_action = 'restart' + + call(f'systemctl {systemd_action} {systemd_service}') + + # Telegraf include custom rsyslog config changes + call('systemctl reload-or-restart rsyslog') + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_monitoring_zabbix-agent.py b/src/conf_mode/service_monitoring_zabbix-agent.py new file mode 100644 index 0000000..98d8a32 --- /dev/null +++ b/src/conf_mode/service_monitoring_zabbix-agent.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + + +service_name = 'zabbix-agent2' +service_conf = f'/run/zabbix/{service_name}.conf' +systemd_override = r'/run/systemd/system/zabbix-agent2.service.d/10-override.conf' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'monitoring', 'zabbix-agent'] + + if not conf.exists(base): + return None + + config = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True) + + # Cut the / from the end, /tmp/ => /tmp + if 'directory' in config and config['directory'].endswith('/'): + config['directory'] = config['directory'][:-1] + + return config + + +def verify(config): + # bail out early - looks like removal from running config + if config is None: + return + + if 'server' not in config: + raise ConfigError('Server is required!') + + +def generate(config): + # bail out early - looks like removal from running config + if config is None: + # Remove old config and return + config_files = [service_conf, systemd_override] + for file in config_files: + if os.path.isfile(file): + os.unlink(file) + + return None + + # Write configuration file + render(service_conf, 'zabbix-agent/zabbix-agent.conf.j2', config) + render(systemd_override, 'zabbix-agent/10-override.conf.j2', config) + + return None + + +def apply(config): + call('systemctl daemon-reload') + if config: + call(f'systemctl restart {service_name}.service') + else: + call(f'systemctl stop {service_name}.service') + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_ndp-proxy.py b/src/conf_mode/service_ndp-proxy.py new file mode 100644 index 0000000..024ad79 --- /dev/null +++ b/src/conf_mode/service_ndp-proxy.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configverify import verify_interface_exists +from vyos.utils.process import call +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +systemd_service = 'ndppd.service' +ndppd_config = '/run/ndppd/ndppd.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'ndp-proxy'] + if not conf.exists(base): + return None + + ndpp = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + return ndpp + +def verify(ndpp): + if not ndpp: + return None + + if 'interface' in ndpp: + for interface, interface_config in ndpp['interface'].items(): + verify_interface_exists(ndpp, interface) + + if 'rule' in interface_config: + for rule, rule_config in interface_config['rule'].items(): + if rule_config['mode'] == 'interface' and 'interface' not in rule_config: + raise ConfigError(f'Rule "{rule}" uses interface mode but no interface defined!') + + if rule_config['mode'] != 'interface' and 'interface' in rule_config: + if interface_config['mode'] != 'interface' and 'interface' in interface_config: + raise ConfigError(f'Rule "{rule}" does not use interface mode, thus interface can not be defined!') + + return None + +def generate(ndpp): + if not ndpp: + return None + + render(ndppd_config, 'ndppd/ndppd.conf.j2', ndpp) + return None + +def apply(ndpp): + if not ndpp: + call(f'systemctl stop {systemd_service}') + if os.path.isfile(ndppd_config): + os.unlink(ndppd_config) + return None + + call(f'systemctl reload-or-restart {systemd_service}') + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_ntp.py b/src/conf_mode/service_ntp.py new file mode 100644 index 0000000..32563aa --- /dev/null +++ b/src/conf_mode/service_ntp.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from vyos.config import Config +from vyos.config import config_dict_merge +from vyos.configdict import is_node_changed +from vyos.configverify import verify_vrf +from vyos.configverify import verify_interface_exists +from vyos.utils.process import call +from vyos.utils.permission import chmod_750 +from vyos.utils.network import get_interface_config +from vyos.template import render +from vyos.template import is_ipv4 +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/run/chrony/chrony.conf' +systemd_override = r'/run/systemd/system/chrony.service.d/override.conf' +user_group = '_chrony' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'ntp'] + if not conf.exists(base): + return None + + ntp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + ntp['config_file'] = config_file + ntp['user'] = user_group + + tmp = is_node_changed(conf, base + ['vrf']) + if tmp: ntp.update({'restart_required': {}}) + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = conf.get_config_defaults(**ntp.kwargs, recursive=True) + # Only defined PTP default port, if PTP feature is in use + if 'ptp' not in ntp: + del default_values['ptp'] + + ntp = config_dict_merge(default_values, ntp) + return ntp + +def verify(ntp): + # bail out early - looks like removal from running config + if not ntp: + return None + + if 'server' not in ntp: + raise ConfigError('NTP server not configured') + + verify_vrf(ntp) + + if 'interface' in ntp: + # If ntpd should listen on a given interface, ensure it exists + interface = ntp['interface'] + verify_interface_exists(ntp, interface) + + # If we run in a VRF, our interface must belong to this VRF, too + if 'vrf' in ntp: + tmp = get_interface_config(interface) + vrf_name = ntp['vrf'] + if 'master' not in tmp or tmp['master'] != vrf_name: + raise ConfigError(f'NTP runs in VRF "{vrf_name}" - "{interface}" '\ + f'does not belong to this VRF!') + + if 'listen_address' in ntp: + ipv4_addresses = 0 + ipv6_addresses = 0 + for address in ntp['listen_address']: + if is_ipv4(address): + ipv4_addresses += 1 + else: + ipv6_addresses += 1 + if ipv4_addresses > 1: + raise ConfigError(f'NTP Only admits one ipv4 value for listen-address parameter ') + if ipv6_addresses > 1: + raise ConfigError(f'NTP Only admits one ipv6 value for listen-address parameter ') + + if 'server' in ntp: + for host, server in ntp['server'].items(): + if 'ptp' in server: + if 'ptp' not in ntp: + raise ConfigError('PTP must be enabled for the NTP service '\ + f'before it can be used for server "{host}"') + else: + break + + return None + +def generate(ntp): + # bail out early - looks like removal from running config + if not ntp: + return None + + render(config_file, 'chrony/chrony.conf.j2', ntp, user=user_group, group=user_group) + render(systemd_override, 'chrony/override.conf.j2', ntp, user=user_group, group=user_group) + + # Ensure proper permission for chrony command socket + config_dir = os.path.dirname(config_file) + chmod_750(config_dir) + + return None + +def apply(ntp): + systemd_service = 'chrony.service' + # Reload systemd manager configuration + call('systemctl daemon-reload') + + if not ntp: + # NTP support is removed in the commit + call(f'systemctl stop {systemd_service}') + if os.path.exists(config_file): + os.unlink(config_file) + if os.path.isfile(systemd_override): + os.unlink(systemd_override) + return + + # we need to restart the service if e.g. the VRF name changed + systemd_action = 'reload-or-restart' + if 'restart_required' in ntp: + systemd_action = 'restart' + + call(f'systemctl {systemd_action} {systemd_service}') + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py new file mode 100644 index 0000000..ac697c5 --- /dev/null +++ b/src/conf_mode/service_pppoe-server.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_accel_dict +from vyos.configdict import is_node_changed +from vyos.configverify import verify_interface_exists +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.accel_ppp_util import verify_accel_ppp_name_servers +from vyos.accel_ppp_util import verify_accel_ppp_wins_servers +from vyos.accel_ppp_util import verify_accel_ppp_authentication +from vyos.accel_ppp_util import verify_accel_ppp_ip_pool +from vyos.accel_ppp_util import get_pools_in_order +from vyos import ConfigError +from vyos import airbag + +airbag.enable() + +pppoe_conf = r'/run/accel-pppd/pppoe.conf' +pppoe_chap_secrets = r'/run/accel-pppd/pppoe.chap-secrets' + +def convert_pado_delay(pado_delay): + new_pado_delay = {'delays_without_sessions': [], + 'delays_with_sessions': []} + for delay, sessions in pado_delay.items(): + if not sessions: + new_pado_delay['delays_without_sessions'].append(delay) + else: + new_pado_delay['delays_with_sessions'].append((delay, int(sessions['sessions']))) + return new_pado_delay + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'pppoe-server'] + if not conf.exists(base): + return None + + # retrieve common dictionary keys + pppoe = get_accel_dict(conf, base, pppoe_chap_secrets) + + if dict_search('client_ip_pool', pppoe): + # Multiple named pools require ordered values T5099 + pppoe['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', pppoe)) + + if dict_search('pado_delay', pppoe): + pado_delay = dict_search('pado_delay', pppoe) + pppoe['pado_delay'] = convert_pado_delay(pado_delay) + + # reload-or-restart does not implemented in accel-ppp + # use this workaround until it will be implemented + # https://phabricator.accel-ppp.org/T3 + conditions = [is_node_changed(conf, base + ['client-ip-pool']), + is_node_changed(conf, base + ['client-ipv6-pool']), + is_node_changed(conf, base + ['interface'])] + if any(conditions): + pppoe.update({'restart_required': {}}) + pppoe['server_type'] = 'pppoe' + return pppoe + +def verify_pado_delay(pppoe): + if 'pado_delay' in pppoe: + pado_delay = pppoe['pado_delay'] + + delays_without_sessions = pado_delay['delays_without_sessions'] + if 'disable' in delays_without_sessions: + raise ConfigError( + 'Number of sessions must be specified for "pado-delay disable"' + ) + + if len(delays_without_sessions) > 1: + raise ConfigError( + f'Cannot add more then ONE pado-delay without sessions, ' + f'but {len(delays_without_sessions)} were set' + ) + + if 'disable' in [delay[0] for delay in pado_delay['delays_with_sessions']]: + # need to sort delays by sessions to verify if there is no delay + # for sessions after disabling + sorted_pado_delay = sorted(pado_delay['delays_with_sessions'], key=lambda k_v: k_v[1]) + last_delay = sorted_pado_delay[-1] + + if last_delay[0] != 'disable': + raise ConfigError( + f'Cannot add pado-delay after disabled sessions, but ' + f'"pado-delay {last_delay[0]} sessions {last_delay[1]}" was set' + ) + +def verify(pppoe): + if not pppoe: + return None + + verify_accel_ppp_authentication(pppoe) + verify_accel_ppp_ip_pool(pppoe) + verify_accel_ppp_name_servers(pppoe) + verify_accel_ppp_wins_servers(pppoe) + verify_pado_delay(pppoe) + + if 'interface' not in pppoe: + raise ConfigError('At least one listen interface must be defined!') + + # Check is interface exists in the system + for interface, interface_config in pppoe['interface'].items(): + verify_interface_exists(pppoe, interface, warning_only=True) + + if 'vlan_mon' in interface_config and not 'vlan' in interface_config: + raise ConfigError('Option "vlan-mon" requires "vlan" to be set!') + + return None + + +def generate(pppoe): + if not pppoe: + return None + + render(pppoe_conf, 'accel-ppp/pppoe.config.j2', pppoe) + + if dict_search('authentication.mode', pppoe) == 'local': + render(pppoe_chap_secrets, 'accel-ppp/chap-secrets.config_dict.j2', + pppoe, permission=0o640) + return None + + +def apply(pppoe): + systemd_service = 'accel-ppp@pppoe.service' + if not pppoe: + call(f'systemctl stop {systemd_service}') + for file in [pppoe_conf, pppoe_chap_secrets]: + if os.path.exists(file): + os.unlink(file) + return None + + if 'restart_required' in pppoe: + call(f'systemctl restart {systemd_service}') + else: + call(f'systemctl reload-or-restart {systemd_service}') + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py new file mode 100644 index 0000000..88d767b --- /dev/null +++ b/src/conf_mode/service_router-advert.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit +from ipaddress import IPv6Network + +from vyos.base import Warning +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/run/radvd/radvd.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'router-advert'] + rtradv = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + return rtradv + +def verify(rtradv): + if not rtradv: + return None + + if 'interface' not in rtradv: + return None + + for interface, interface_config in rtradv['interface'].items(): + interval_max = int(interface_config['interval']['max']) + + if 'prefix' in interface_config: + for prefix, prefix_config in interface_config['prefix'].items(): + valid_lifetime = prefix_config['valid_lifetime'] + if valid_lifetime == 'infinity': + valid_lifetime = 4294967295 + + preferred_lifetime = prefix_config['preferred_lifetime'] + if preferred_lifetime == 'infinity': + preferred_lifetime = 4294967295 + + if not (int(valid_lifetime) >= int(preferred_lifetime)): + raise ConfigError('Prefix valid-lifetime must be greater then or equal to preferred-lifetime') + + if 'nat64prefix' in interface_config: + nat64_supported_lengths = [32, 40, 48, 56, 64, 96] + for prefix, prefix_config in interface_config['nat64prefix'].items(): + if IPv6Network(prefix).prefixlen not in nat64_supported_lengths: + raise ConfigError(f'Invalid NAT64 prefix length for "{prefix}", can only be one of: /' + ', /'.join(nat64_supported_lengths)) + + if int(prefix_config['valid_lifetime']) < interval_max: + raise ConfigError(f'NAT64 valid-lifetime must not be smaller then "interval max" which is "{interval_max}"!') + + if 'name_server' in interface_config: + if len(interface_config['name_server']) > 3: + raise ConfigError('No more then 3 IPv6 name-servers supported!') + + if 'name_server_lifetime' in interface_config: + # man page states: + # The maximum duration how long the RDNSS entries are used for name + # resolution. A value of 0 means the nameserver must no longer be + # used. The value, if not 0, must be at least MaxRtrAdvInterval. To + # ensure stale RDNSS info gets removed in a timely fashion, this + # should not be greater than 2*MaxRtrAdvInterval. + lifetime = int(interface_config['name_server_lifetime']) + if lifetime > 0: + if lifetime < int(interval_max): + raise ConfigError(f'RDNSS lifetime must be at least "{interval_max}" seconds!') + if lifetime > 2* interval_max: + Warning(f'RDNSS lifetime should not exceed "{2 * interval_max}" which is two times "interval max"!') + + return None + +def generate(rtradv): + if not rtradv: + return None + + render(config_file, 'router-advert/radvd.conf.j2', rtradv, permission=0o644) + return None + +def apply(rtradv): + systemd_service = 'radvd.service' + if not rtradv: + # bail out early - looks like removal from running config + call(f'systemctl stop {systemd_service}') + if os.path.exists(config_file): + os.unlink(config_file) + + return None + + call(f'systemctl reload-or-restart {systemd_service}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_salt-minion.py b/src/conf_mode/service_salt-minion.py new file mode 100644 index 0000000..edf74b0 --- /dev/null +++ b/src/conf_mode/service_salt-minion.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from socket import gethostname +from sys import exit +from urllib3 import PoolManager + +from vyos.base import Warning +from vyos.config import Config +from vyos.configverify import verify_interface_exists +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.permission import chown +from vyos import ConfigError + +from vyos import airbag +airbag.enable() + +config_file = r'/etc/salt/minion' +master_keyfile = r'/opt/vyatta/etc/config/salt/pki/minion/master_sign.pub' + +user='minion' +group='vyattacfg' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'salt-minion'] + + if not conf.exists(base): + return None + + salt = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + # ID default is dynamic thus we can not use defaults() + if 'id' not in salt: + salt['id'] = gethostname() + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + salt = conf.merge_defaults(salt, recursive=True) + + if not conf.exists(base): + return None + else: + conf.set_level(base) + + return salt + +def verify(salt): + if not salt: + return None + + if 'hash' in salt and salt['hash'] == 'sha1': + Warning('Do not use sha1 hashing algorithm, upgrade to sha256 or later!') + + if 'source_interface' in salt: + verify_interface_exists(salt, salt['source_interface']) + + return None + +def generate(salt): + if not salt: + return None + + render(config_file, 'salt-minion/minion.j2', salt, user=user, group=group) + + if not os.path.exists(master_keyfile): + if 'master_key' in salt: + req = PoolManager().request('GET', salt['master_key'], preload_content=False) + with open(master_keyfile, 'wb') as f: + while True: + data = req.read(1024) + if not data: + break + f.write(data) + + req.release_conn() + chown(master_keyfile, user, group) + + return None + +def apply(salt): + service_name = 'salt-minion.service' + if not salt: + # Salt removed from running config + call(f'systemctl stop {service_name}') + if os.path.exists(config_file): + os.unlink(config_file) + else: + call(f'systemctl restart {service_name}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_sla.py b/src/conf_mode/service_sla.py new file mode 100644 index 0000000..ba5e645 --- /dev/null +++ b/src/conf_mode/service_sla.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +owamp_config_dir = '/etc/owamp-server' +owamp_config_file = f'{owamp_config_dir}/owamp-server.conf' +systemd_override_owamp = r'/run/systemd/system/owamp-server.d/20-override.conf' + +twamp_config_dir = '/etc/twamp-server' +twamp_config_file = f'{twamp_config_dir}/twamp-server.conf' +systemd_override_twamp = r'/run/systemd/system/twamp-server.d/20-override.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'sla'] + if not conf.exists(base): + return None + + sla = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + # Ignore default XML values if config doesn't exists + # Delete key from dict + if not conf.exists(base + ['owamp-server']): + del sla['owamp_server'] + if not conf.exists(base + ['twamp-server']): + del sla['twamp_server'] + + return sla + +def verify(sla): + if not sla: + return None + +def generate(sla): + if not sla: + return None + + render(owamp_config_file, 'sla/owamp-server.conf.j2', sla) + render(systemd_override_owamp, 'sla/owamp-override.conf.j2', sla) + + render(twamp_config_file, 'sla/twamp-server.conf.j2', sla) + render(systemd_override_twamp, 'sla/twamp-override.conf.j2', sla) + + return None + +def apply(sla): + owamp_service = 'owamp-server.service' + twamp_service = 'twamp-server.service' + + call('systemctl daemon-reload') + + if not sla or 'owamp_server' not in sla: + call(f'systemctl stop {owamp_service}') + + if os.path.exists(owamp_config_file): + os.unlink(owamp_config_file) + + if not sla or 'twamp_server' not in sla: + call(f'systemctl stop {twamp_service}') + if os.path.exists(twamp_config_file): + os.unlink(twamp_config_file) + + if sla and 'owamp_server' in sla: + call(f'systemctl reload-or-restart {owamp_service}') + + if sla and 'twamp_server' in sla: + call(f'systemctl reload-or-restart {twamp_service}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_snmp.py b/src/conf_mode/service_snmp.py new file mode 100644 index 0000000..c9c0ed9 --- /dev/null +++ b/src/conf_mode/service_snmp.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configverify import verify_vrf +from vyos.snmpv3_hashgen import plaintext_to_md5 +from vyos.snmpv3_hashgen import plaintext_to_sha1 +from vyos.snmpv3_hashgen import random +from vyos.template import render +from vyos.utils.configfs import delete_cli_node +from vyos.utils.configfs import add_cli_node +from vyos.utils.dict import dict_search +from vyos.utils.network import is_addr_assigned +from vyos.utils.process import call +from vyos.utils.permission import chmod_755 +from vyos.version import get_version_data +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file_client = r'/etc/snmp/snmp.conf' +config_file_daemon = r'/etc/snmp/snmpd.conf' +config_file_access = r'/usr/share/snmp/snmpd.conf' +config_file_user = r'/var/lib/snmp/snmpd.conf' +default_script_dir = r'/config/user-data/' +systemd_override = r'/run/systemd/system/snmpd.service.d/override.conf' +systemd_service = 'snmpd.service' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'snmp'] + + snmp = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + if not conf.exists(base): + snmp.update({'deleted' : ''}) + + if conf.exists(['service', 'lldp', 'snmp']): + snmp.update({'lldp_snmp' : ''}) + + if 'deleted' in snmp: + return snmp + + version_data = get_version_data() + snmp['version'] = version_data['version'] + + # create an internal snmpv3 user of the form 'vyosxxxxxxxxxxxxxxxx' + snmp['vyos_user'] = 'vyos' + random(8) + snmp['vyos_user_pass'] = random(16) + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + snmp = conf.merge_defaults(snmp, recursive=True) + + if 'listen_address' in snmp: + # Always listen on localhost if an explicit address has been configured + # This is a safety measure to not end up with invalid listen addresses + # that are not configured on this system. See https://vyos.dev/T850 + if '127.0.0.1' not in snmp['listen_address']: + tmp = {'127.0.0.1': {'port': '161'}} + snmp['listen_address'] = dict_merge(tmp, snmp['listen_address']) + + if '::1' not in snmp['listen_address']: + tmp = {'::1': {'port': '161'}} + snmp['listen_address'] = dict_merge(tmp, snmp['listen_address']) + + if 'script_extensions' in snmp and 'extension_name' in snmp['script_extensions']: + for key, val in snmp['script_extensions']['extension_name'].items(): + if 'script' not in val: + continue + script_path = val['script'] + # if script has not absolute path, use pre configured path + if not os.path.isabs(script_path): + script_path = os.path.join(default_script_dir, script_path) + + snmp['script_extensions']['extension_name'][key]['script'] = script_path + + return snmp + + +def verify(snmp): + if 'deleted' in snmp: + return None + + if {'deleted', 'lldp_snmp'} <= set(snmp): + raise ConfigError('Can not delete SNMP service, as LLDP still uses SNMP!') + + ### check if the configured script actually exist + if 'script_extensions' in snmp and 'extension_name' in snmp['script_extensions']: + for extension, extension_opt in snmp['script_extensions']['extension_name'].items(): + if 'script' not in extension_opt: + raise ConfigError(f'Script extension "{extension}" requires an actual script to be configured!') + + tmp = extension_opt['script'] + if not os.path.isfile(tmp): + Warning(f'script "{tmp}" does not exist!') + else: + chmod_755(extension_opt['script']) + + if 'listen_address' in snmp: + for address in snmp['listen_address']: + # We only wan't to configure addresses that exist on the system. + # Hint the user if they don't exist + if 'vrf' in snmp: + vrf_name = snmp['vrf'] + if not is_addr_assigned(address, vrf_name) and address not in ['::1','127.0.0.1']: + raise ConfigError(f'SNMP listen address "{address}" not configured in vrf "{vrf_name}"!') + elif not is_addr_assigned(address): + raise ConfigError(f'SNMP listen address "{address}" not configured in default vrf!') + + if 'trap_target' in snmp: + for trap, trap_config in snmp['trap_target'].items(): + if 'community' not in trap_config: + raise ConfigError(f'Trap target "{trap}" requires a community to be set!') + + if 'oid_enable' in snmp: + Warning(f'Custom OIDs are enabled and may lead to system instability and high resource consumption') + + + verify_vrf(snmp) + + # bail out early if SNMP v3 is not configured + if 'v3' not in snmp: + return None + + if 'user' in snmp['v3']: + for user, user_config in snmp['v3']['user'].items(): + if 'group' not in user_config: + raise ConfigError(f'Group membership required for user "{user}"!') + + if 'plaintext_password' not in user_config['auth'] and 'encrypted_password' not in user_config['auth']: + raise ConfigError(f'Must specify authentication encrypted-password or plaintext-password for user "{user}"!') + + if 'plaintext_password' not in user_config['privacy'] and 'encrypted_password' not in user_config['privacy']: + raise ConfigError(f'Must specify privacy encrypted-password or plaintext-password for user "{user}"!') + + if 'group' in snmp['v3']: + for group, group_config in snmp['v3']['group'].items(): + if 'seclevel' not in group_config: + raise ConfigError(f'Must configure "seclevel" for group "{group}"!') + if 'view' not in group_config: + raise ConfigError(f'Must configure "view" for group "{group}"!') + + # Check if 'view' exists + view = group_config['view'] + if 'view' not in snmp['v3'] or view not in snmp['v3']['view']: + raise ConfigError(f'You must create view "{view}" first!') + + if 'view' in snmp['v3']: + for view, view_config in snmp['v3']['view'].items(): + if 'oid' not in view_config: + raise ConfigError(f'Must configure an "oid" for view "{view}"!') + + if 'trap_target' in snmp['v3']: + for trap, trap_config in snmp['v3']['trap_target'].items(): + if 'plaintext_password' not in trap_config['auth'] and 'encrypted_password' not in trap_config['auth']: + raise ConfigError(f'Must specify one of authentication encrypted-password or plaintext-password for trap "{trap}"!') + + if {'plaintext_password', 'encrypted_password'} <= set(trap_config['auth']): + raise ConfigError(f'Can not specify both authentication encrypted-password and plaintext-password for trap "{trap}"!') + + if 'plaintext_password' not in trap_config['privacy'] and 'encrypted_password' not in trap_config['privacy']: + raise ConfigError(f'Must specify one of privacy encrypted-password or plaintext-password for trap "{trap}"!') + + if {'plaintext_password', 'encrypted_password'} <= set(trap_config['privacy']): + raise ConfigError(f'Can not specify both privacy encrypted-password and plaintext-password for trap "{trap}"!') + + if 'type' not in trap_config: + raise ConfigError('SNMP v3 trap "type" must be specified!') + + return None + +def generate(snmp): + # As we are manipulating the snmpd user database we have to stop it first! + # This is even save if service is going to be removed + call(f'systemctl stop {systemd_service}') + # Clean config files + config_files = [config_file_client, config_file_daemon, + config_file_access, config_file_user, systemd_override] + for file in config_files: + if os.path.isfile(file): + os.unlink(file) + + if 'deleted' in snmp: + return None + + if 'v3' in snmp: + # SNMPv3 uses a hashed password. If CLI defines a plaintext password, + # we will hash it in the background and replace the CLI node! + if 'user' in snmp['v3']: + for user, user_config in snmp['v3']['user'].items(): + if dict_search('auth.type', user_config) == 'sha': + hash = plaintext_to_sha1 + else: + hash = plaintext_to_md5 + + if dict_search('auth.plaintext_password', user_config) is not None: + tmp = hash(dict_search('auth.plaintext_password', user_config), + dict_search('v3.engineid', snmp)) + + snmp['v3']['user'][user]['auth']['encrypted_password'] = tmp + del snmp['v3']['user'][user]['auth']['plaintext_password'] + + cli_base = ['service', 'snmp', 'v3', 'user', user, 'auth'] + delete_cli_node(cli_base + ['plaintext-password']) + add_cli_node(cli_base + ['encrypted-password'], value=tmp) + + if dict_search('privacy.plaintext_password', user_config) is not None: + tmp = hash(dict_search('privacy.plaintext_password', user_config), + dict_search('v3.engineid', snmp)) + + snmp['v3']['user'][user]['privacy']['encrypted_password'] = tmp + del snmp['v3']['user'][user]['privacy']['plaintext_password'] + + cli_base = ['service', 'snmp', 'v3', 'user', user, 'privacy'] + delete_cli_node(cli_base + ['plaintext-password']) + add_cli_node(cli_base + ['encrypted-password'], value=tmp) + + # Write client config file + render(config_file_client, 'snmp/etc.snmp.conf.j2', snmp) + # Write server config file + render(config_file_daemon, 'snmp/etc.snmpd.conf.j2', snmp) + # Write access rights config file + render(config_file_access, 'snmp/usr.snmpd.conf.j2', snmp) + # Write access rights config file + render(config_file_user, 'snmp/var.snmpd.conf.j2', snmp) + # Write daemon configuration file + render(systemd_override, 'snmp/override.conf.j2', snmp) + + return None + +def apply(snmp): + # Always reload systemd manager configuration + call('systemctl daemon-reload') + + if 'deleted' in snmp: + return None + + # start SNMP daemon + call(f'systemctl reload-or-restart {systemd_service}') + + # Enable AgentX in FRR + # This should be done for each daemon individually because common command + # works only if all the daemons started with SNMP support + # Following daemons from FRR 9.0/stable have SNMP module compiled in VyOS + frr_daemons_list = ['zebra', 'bgpd', 'ospf6d', 'ospfd', 'ripd', 'isisd', 'ldpd'] + for frr_daemon in frr_daemons_list: + call(f'vtysh -c "configure terminal" -d {frr_daemon} -c "agentx" >/dev/null') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_ssh.py b/src/conf_mode/service_ssh.py new file mode 100644 index 0000000..9abdd33 --- /dev/null +++ b/src/conf_mode/service_ssh.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit +from syslog import syslog +from syslog import LOG_INFO + +from vyos.config import Config +from vyos.configdict import is_node_changed +from vyos.configverify import verify_vrf +from vyos.utils.process import call +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/run/sshd/sshd_config' + +sshguard_config_file = '/etc/sshguard/sshguard.conf' +sshguard_whitelist = '/etc/sshguard/whitelist' + +key_rsa = '/etc/ssh/ssh_host_rsa_key' +key_dsa = '/etc/ssh/ssh_host_dsa_key' +key_ed25519 = '/etc/ssh/ssh_host_ed25519_key' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'ssh'] + if not conf.exists(base): + return None + + ssh = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + tmp = is_node_changed(conf, base + ['vrf']) + if tmp: ssh.update({'restart_required': {}}) + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + ssh = conf.merge_defaults(ssh, recursive=True) + + # pass config file path - used in override template + ssh['config_file'] = config_file + + # Ignore default XML values if config doesn't exists + # Delete key from dict + if not conf.exists(base + ['dynamic-protection']): + del ssh['dynamic_protection'] + + return ssh + +def verify(ssh): + if not ssh: + return None + + if 'rekey' in ssh and 'data' not in ssh['rekey']: + raise ConfigError(f'Rekey data is required!') + + verify_vrf(ssh) + return None + +def generate(ssh): + if not ssh: + if os.path.isfile(config_file): + os.unlink(config_file) + + return None + + # This usually happens only once on a fresh system, SSH keys need to be + # freshly generted, one per every system! + if not os.path.isfile(key_rsa): + syslog(LOG_INFO, 'SSH RSA host key not found, generating new key!') + call(f'ssh-keygen -q -N "" -t rsa -f {key_rsa}') + if not os.path.isfile(key_dsa): + syslog(LOG_INFO, 'SSH DSA host key not found, generating new key!') + call(f'ssh-keygen -q -N "" -t dsa -f {key_dsa}') + if not os.path.isfile(key_ed25519): + syslog(LOG_INFO, 'SSH ed25519 host key not found, generating new key!') + call(f'ssh-keygen -q -N "" -t ed25519 -f {key_ed25519}') + + render(config_file, 'ssh/sshd_config.j2', ssh) + + if 'dynamic_protection' in ssh: + render(sshguard_config_file, 'ssh/sshguard_config.j2', ssh) + render(sshguard_whitelist, 'ssh/sshguard_whitelist.j2', ssh) + + return None + +def apply(ssh): + systemd_service_ssh = 'ssh.service' + systemd_service_sshguard = 'sshguard.service' + if not ssh: + # SSH access is removed in the commit + call(f'systemctl stop ssh@*.service') + call(f'systemctl stop {systemd_service_sshguard}') + return None + + if 'dynamic_protection' not in ssh: + call(f'systemctl stop {systemd_service_sshguard}') + else: + call(f'systemctl reload-or-restart {systemd_service_sshguard}') + + # we need to restart the service if e.g. the VRF name changed + systemd_action = 'reload-or-restart' + if 'restart_required' in ssh: + # this is only true if something for the VRFs changed, thus we + # stop all VRF services and only restart then new ones + call(f'systemctl stop ssh@*.service') + systemd_action = 'restart' + + for vrf in ssh['vrf']: + call(f'systemctl {systemd_action} ssh@{vrf}.service') + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_stunnel.py b/src/conf_mode/service_stunnel.py new file mode 100644 index 0000000..8ec7625 --- /dev/null +++ b/src/conf_mode/service_stunnel.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +from shutil import rmtree + +from sys import exit + +from netifaces import AF_INET +from psutil import net_if_addrs + +from vyos.config import Config +from vyos.configverify import verify_pki_ca_certificate +from vyos.configverify import verify_pki_certificate +from vyos.pki import encode_certificate +from vyos.pki import encode_private_key +from vyos.pki import find_chain +from vyos.pki import load_certificate +from vyos.pki import load_private_key +from vyos.utils.dict import dict_search +from vyos.utils.file import makedir +from vyos.utils.file import write_file +from vyos.utils.network import check_port_availability +from vyos.utils.network import is_listen_port_bind_service +from vyos.utils.process import call +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +stunnel_dir = '/run/stunnel' +config_file = f'{stunnel_dir}/stunnel.conf' +stunnel_ca_dir = f'{stunnel_dir}/ca' +stunnel_psk_dir = f'{stunnel_dir}/psk' + +# config based on +# http://man.he.net/man8/stunnel4 + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'stunnel'] + if not conf.exists(base): + return None + + stunnel = conf.get_config_dict(base, + get_first_key=True, + key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + with_recursive_defaults=True, + with_pki=True) + stunnel['config_file'] = config_file + return stunnel + + +def verify(stunnel): + if not stunnel: + return None + + stunnel_listen_addresses = list() + for mode, conf in stunnel.items(): + if mode not in ['server', 'client']: + continue + + for app, app_conf in conf.items(): + # connect, listen, exec and some protocols e.g. socks on server mode are endpoints. + endpoints = 0 + if 'socks' == app_conf.get('protocol') and mode == 'server': + if 'connect' in app_conf: + raise ConfigError("The 'connect' option cannot be used with the 'socks' protocol in server mode.") + endpoints += 1 + + for item in ['connect', 'listen']: + if item in app_conf: + endpoints += 1 + if 'port' not in app_conf[item]: + raise ConfigError(f'{mode} [{app}]: {item} port number is required!') + elif item == 'listen': + raise ConfigError(f'{mode} [{app}]: {item} port number is required!') + + if endpoints != 2: + raise ConfigError(f'{mode} [{app}]: connect port number is required!') + + if 'address' in app_conf['listen']: + laddresses = [dict_search('listen.address', app_conf)] + else: + laddresses = list() + ifaces = net_if_addrs() + for iface_name, iface_addresses in ifaces.items(): + for iface_addr in iface_addresses: + if iface_addr.family == AF_INET: + laddresses.append(iface_addr.address) + + lport = int(dict_search('listen.port', app_conf)) + + for address in laddresses: + if f'{address}:{lport}' in stunnel_listen_addresses: + raise ConfigError( + f'{mode} [{app}]: Address {address}:{lport} already ' + f'in use by other stunnel service') + + stunnel_listen_addresses.append(f'{address}:{lport}') + if not check_port_availability(address, lport, 'tcp') and \ + not is_listen_port_bind_service(lport, 'stunnel'): + raise ConfigError( + f'{mode} [{app}]: Address {address}:{lport} already in use') + + if 'options' in app_conf: + protocol = app_conf.get('protocol') + if protocol not in ['connect', 'smtp']: + raise ConfigError("Additional option is only supported in the 'connect' and 'smtp' protocols.") + if protocol == 'smtp' and ('domain' in app_conf['options'] or 'host' in app_conf['options']): + raise ConfigError("Protocol 'smtp' does not support options 'domain' and 'host'.") + + # set default authentication option + if 'authentication' not in app_conf['options']: + app_conf['options']['authentication'] = 'basic' if protocol == 'connect' else 'plain' + + for option, option_config in app_conf['options'].items(): + if option == 'authentication': + if protocol == 'connect' and option_config not in ['basic', 'ntlm']: + raise ConfigError("Supported authentication types for the 'connect' protocol are 'basic' or 'ntlm'") + elif protocol == 'smtp' and option_config not in ['plain', 'login']: + raise ConfigError("Supported authentication types for the 'smtp' protocol are 'plain' or 'login'") + if option == 'host': + if 'address' not in option_config: + raise ConfigError('Address is required for option host.') + if 'port' not in option_config: + raise ConfigError('Port is required for option host.') + + # check pki certs + for key in ['ca_certificate', 'certificate']: + tmp = dict_search(f'ssl.{key}', app_conf) + if mode == 'server' and key != 'ca_certificate' and not tmp and 'psk' not in app_conf: + raise ConfigError(f'{mode} [{app}]: TLS server needs a certificate or PSK') + if tmp: + if key == 'ca_certificate': + for ca_cert in tmp: + verify_pki_ca_certificate(stunnel, ca_cert) + else: + verify_pki_certificate(stunnel, tmp) + + #check psk + if 'psk' in app_conf: + for psk, psk_conf in app_conf['psk'].items(): + if 'id' not in psk_conf or 'secret' not in psk_conf: + raise ConfigError( + f'Authentication psk "{psk}" missing "id" or "secret"') + + +def generate(stunnel): + if not stunnel or ('client' not in stunnel and 'server' not in stunnel): + if os.path.isdir(stunnel_dir): + rmtree(stunnel_dir, ignore_errors=True) + + return None + makedir(stunnel_dir) + + exist_files = list() + current_files = [config_file, config_file.replace('.conf', 'pid')] + for root, dirs, files in os.walk(stunnel_dir): + for file in files: + exist_files.append(os.path.join(root, file)) + + loaded_ca_certs = {load_certificate(c['certificate']) + for c in stunnel['pki']['ca'].values()} if 'pki' in stunnel and 'ca' in stunnel['pki'] else {} + + for mode, conf in stunnel.items(): + if mode not in ['server', 'client']: + continue + + for app, app_conf in conf.items(): + if 'ssl' in app_conf: + if 'certificate' in app_conf['ssl']: + cert_name = app_conf['ssl']['certificate'] + + pki_cert = stunnel['pki']['certificate'][cert_name] + cert_file_path = os.path.join(stunnel_dir, + f'{mode}-{app}-{cert_name}.pem') + cert_key_path = os.path.join(stunnel_dir, + f'{mode}-{app}-{cert_name}.pem.key') + app_conf['ssl']['cert'] = cert_file_path + + loaded_pki_cert = load_certificate(pki_cert['certificate']) + cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs) + + write_file(cert_file_path, + '\n'.join(encode_certificate(c) for c in cert_full_chain)) + current_files.append(cert_file_path) + + if 'private' in pki_cert and 'key' in pki_cert['private']: + app_conf['ssl']['cert_key'] = cert_key_path + loaded_key = load_private_key(pki_cert['private']['key'], + passphrase=None, wrap_tags=True) + key_pem = encode_private_key(loaded_key, passphrase=None) + write_file(cert_key_path, key_pem, mode=0o600) + current_files.append(cert_key_path) + + if 'ca_certificate' in app_conf['ssl']: + app_conf['ssl']['ca_path'] = stunnel_ca_dir + app_conf['ssl']['ca_file'] = f'{mode}-{app}-ca.pem' + ca_cert_file_path = os.path.join(stunnel_ca_dir, app_conf['ssl']['ca_file']) + ca_chains = [] + + for ca_name in app_conf['ssl']['ca_certificate']: + pki_ca_cert = stunnel['pki']['ca'][ca_name] + loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) + ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + ca_chains.append( + '\n'.join(encode_certificate(c) for c in ca_full_chain)) + + write_file(ca_cert_file_path, '\n'.join(ca_chains)) + current_files.append(ca_cert_file_path) + + if 'psk' in app_conf: + psk_data = list() + psk_file_path = os.path.join(stunnel_psk_dir, f'{mode}_{app}.txt') + + for _, psk_conf in app_conf['psk'].items(): + psk_data.append(f'{psk_conf["id"]}:{psk_conf["secret"]}') + + write_file(psk_file_path, '\n'.join(psk_data)) + app_conf['psk']['file'] = psk_file_path + current_files.append(psk_file_path) + + for file in exist_files: + if file not in current_files: + os.unlink(file) + + render(config_file, 'stunnel/stunnel_config.j2', stunnel) + + +def apply(stunnel): + if not stunnel or ('client' not in stunnel and 'server' not in stunnel): + call('systemctl stop stunnel.service') + else: + call('systemctl restart stunnel.service') + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_suricata.py b/src/conf_mode/service_suricata.py new file mode 100644 index 0000000..1ce1701 --- /dev/null +++ b/src/conf_mode/service_suricata.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = '/run/suricata/suricata.yaml' +rotate_file = '/etc/logrotate.d/suricata' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'suricata'] + + if not conf.exists(base): + return None + + suricata = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, with_recursive_defaults=True) + + return suricata + +# https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search +def topological_sort(source): + sorted_nodes = [] + permanent_marks = set() + temporary_marks = set() + + def visit(n, v): + if n in permanent_marks: + return + if n in temporary_marks: + raise ConfigError('At least one cycle exists in the referenced groups') + + temporary_marks.add(n) + + for m in v.get('group', []): + m = m.lstrip('!').replace('-', '_') + if m not in source: + raise ConfigError(f'Undefined referenced group "{m}"') + visit(m, source[m]) + + temporary_marks.remove(n) + permanent_marks.add(n) + sorted_nodes.append((n, v)) + + while len(permanent_marks) < len(source): + n = next(n for n in source.keys() if n not in permanent_marks) + visit(n, source[n]) + + return sorted_nodes + +def verify(suricata): + if not suricata: + return None + + if 'interface' not in suricata: + raise ConfigError('No interfaces configured!') + + if 'address_group' not in suricata: + raise ConfigError('No address-group configured!') + + if 'port_group' not in suricata: + raise ConfigError('No port-group configured!') + + try: + topological_sort(suricata['address_group']) + except (ConfigError,StopIteration) as e: + raise ConfigError(f'Invalid address-group: {e}') + + try: + topological_sort(suricata['port_group']) + except (ConfigError,StopIteration) as e: + raise ConfigError(f'Invalid port-group: {e}') + +def generate(suricata): + if not suricata: + for file in [config_file, rotate_file]: + if os.path.isfile(file): + os.unlink(file) + + return None + + # Config-related formatters + def to_var(s:str): + return s.replace('-','_').upper() + + def to_val(s:str): + return s.replace('-',':') + + def to_ref(s:str): + if s[0] == '!': + return '!$' + to_var(s[1:]) + return '$' + to_var(s) + + def to_config(kind:str): + def format_group(group): + (name, value) = group + property = [to_val(property) for property in value.get(kind,[])] + group = [to_ref(group) for group in value.get('group',[])] + return (to_var(name), property + group) + return format_group + + # Format the address group + suricata['address_group'] = map(to_config('address'), + topological_sort(suricata['address_group'])) + + # Format the port group + suricata['port_group'] = map(to_config('port'), + topological_sort(suricata['port_group'])) + + render(config_file, 'ids/suricata.j2', {'suricata': suricata}) + render(rotate_file, 'ids/suricata_logrotate.j2', suricata) + return None + +def apply(suricata): + systemd_service = 'suricata.service' + if not suricata or 'interface' not in suricata: + # Stop suricata service if removed + call(f'systemctl stop {systemd_service}') + else: + Warning('To fetch the latest rules, use "update suricata"; ' + 'To periodically fetch the latest rules, ' + 'use the task scheduler!') + call(f'systemctl restart {systemd_service}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_tftp-server.py b/src/conf_mode/service_tftp-server.py new file mode 100644 index 0000000..5b7303c --- /dev/null +++ b/src/conf_mode/service_tftp-server.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import pwd + +from copy import deepcopy +from glob import glob +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configverify import verify_vrf +from vyos.template import render +from vyos.template import is_ipv4 +from vyos.utils.process import call +from vyos.utils.permission import chmod_755 +from vyos.utils.network import is_addr_assigned +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/etc/default/tftpd' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'tftp-server'] + if not conf.exists(base): + return None + + tftpd = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + return tftpd + +def verify(tftpd): + # bail out early - looks like removal from running config + if not tftpd: + return None + + # Configuring allowed clients without a server makes no sense + if 'directory' not in tftpd: + raise ConfigError('TFTP root directory must be configured!') + + if 'listen_address' not in tftpd: + raise ConfigError('TFTP server listen address must be configured!') + + for address, address_config in tftpd['listen_address'].items(): + if not is_addr_assigned(address): + Warning(f'TFTP server listen address "{address}" not ' \ + 'assigned to any interface!') + verify_vrf(address_config) + + return None + +def generate(tftpd): + # cleanup any available configuration file + # files will be recreated on demand + for i in glob(config_file + '*'): + os.unlink(i) + + # bail out early - looks like removal from running config + if tftpd is None: + return None + + idx = 0 + for address, address_config in tftpd['listen_address'].items(): + config = deepcopy(tftpd) + port = tftpd['port'] + if is_ipv4(address): + config['listen_address'] = f'{address}:{port} -4' + else: + config['listen_address'] = f'[{address}]:{port} -6' + + if 'vrf' in address_config: + config['vrf'] = address_config['vrf'] + + file = config_file + str(idx) + render(file, 'tftp-server/default.j2', config) + idx = idx + 1 + + return None + +def apply(tftpd): + # stop all services first - then we will decide + call('systemctl stop tftpd@*.service') + + # bail out early - e.g. service deletion + if tftpd is None: + return None + + tftp_root = tftpd['directory'] + if not os.path.exists(tftp_root): + os.makedirs(tftp_root) + chmod_755(tftp_root) + + # get UNIX uid for user 'tftp' + tftp_uid = pwd.getpwnam('tftp').pw_uid + tftp_gid = pwd.getpwnam('tftp').pw_gid + + # get UNIX uid for tftproot directory + dir_uid = os.stat(tftp_root).st_uid + dir_gid = os.stat(tftp_root).st_gid + + # adjust uid/gid of tftproot directory if files don't belong to user tftp + if (tftp_uid != dir_uid) or (tftp_gid != dir_gid): + os.chown(tftp_root, tftp_uid, tftp_gid) + + idx = 0 + for address in tftpd['listen_address']: + call(f'systemctl restart tftpd@{idx}.service') + idx = idx + 1 + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_webproxy.py b/src/conf_mode/service_webproxy.py new file mode 100644 index 0000000..12ae413 --- /dev/null +++ b/src/conf_mode/service_webproxy.py @@ -0,0 +1,252 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from shutil import rmtree +from sys import exit + +from vyos.config import Config +from vyos.config import config_dict_merge +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.permission import chmod_755 +from vyos.utils.dict import dict_search +from vyos.utils.file import write_file +from vyos.utils.network import is_addr_assigned +from vyos.base import Warning +from vyos import ConfigError +from vyos import airbag + +airbag.enable() + +squid_config_file = '/etc/squid/squid.conf' +squidguard_config_file = '/etc/squidguard/squidGuard.conf' +squidguard_db_dir = '/opt/vyatta/etc/config/url-filtering/squidguard/db' +user_group = 'proxy' + + +def check_blacklist_categorydb(config_section): + if 'block_category' in config_section: + for category in config_section['block_category']: + check_categorydb(category) + if 'allow_category' in config_section: + for category in config_section['allow_category']: + check_categorydb(category) + + +def check_categorydb(category: str): + """ + Check if category's db exist + :param category: + :type str: + """ + path_to_cat: str = f'{squidguard_db_dir}/{category}' + if not os.path.exists(f'{path_to_cat}/domains.db') \ + and not os.path.exists(f'{path_to_cat}/urls.db') \ + and not os.path.exists(f'{path_to_cat}/expressions.db'): + Warning(f'DB of category {category} does not exist.\n ' + f'Use [update webproxy blacklists] ' + f'or delete undefined category!') + + +def generate_sg_rule_localdb(category, list_type, role, proxy): + if not category or not list_type or not role: + return None + + cat_ = category.replace('-', '_') + + if role == 'default': + path_to_cat = f'{cat_}' + else: + path_to_cat = f'rule.{role}.{cat_}' + if isinstance( + dict_search(f'url_filtering.squidguard.{path_to_cat}', proxy), + list): + # local block databases must be generated "on-the-fly" + tmp = { + 'squidguard_db_dir': squidguard_db_dir, + 'category': f'{category}-{role}', + 'list_type': list_type, + 'rule': role + } + sg_tmp_file = '/tmp/sg.conf' + db_file = f'{category}-{role}/{list_type}' + domains = '\n'.join( + dict_search(f'url_filtering.squidguard.{path_to_cat}', proxy)) + # local file + write_file(f'{squidguard_db_dir}/{category}-{role}/local', '', + user=user_group, group=user_group) + # database input file + write_file(f'{squidguard_db_dir}/{db_file}', domains, + user=user_group, group=user_group) + + # temporary config file, deleted after generation + render(sg_tmp_file, 'squid/sg_acl.conf.j2', tmp, + user=user_group, group=user_group) + + call( + f'su - {user_group} -c "squidGuard -d -c {sg_tmp_file} -C {db_file}"') + + if os.path.exists(sg_tmp_file): + os.unlink(sg_tmp_file) + else: + # if category is not part of our configuration, clean out the + # squidguard lists + tmp = f'{squidguard_db_dir}/{category}-{role}' + if os.path.exists(tmp): + rmtree(f'{squidguard_db_dir}/{category}-{role}') + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'webproxy'] + if not conf.exists(base): + return None + + proxy = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = conf.get_config_defaults(**proxy.kwargs, + recursive=True) + + # if no authentication method is supplied, no need to add defaults + if not dict_search('authentication.method', proxy): + default_values.pop('authentication') + # if no url_filteringurl-filtering method is supplied, no need to add defaults + if 'url_filtering' not in proxy: + default_values.pop('url_filtering') + else: + # store path to squidGuard config, used when generating Squid config + proxy['squidguard_conf'] = squidguard_config_file + proxy['squidguard_db_dir'] = squidguard_db_dir + + proxy = config_dict_merge(default_values, proxy) + + return proxy + + +def verify(proxy): + if not proxy: + return None + + if 'listen_address' not in proxy: + raise ConfigError('listen-address needs to be configured!') + + ldap_auth = dict_search('authentication.method', proxy) == 'ldap' + + for address, config in proxy['listen_address'].items(): + if ldap_auth and 'disable_transparent' not in config: + raise ConfigError('Authentication can not be configured when ' \ + 'proxy is in transparent mode') + + if 'outgoing_address' in proxy: + address = proxy['outgoing_address'] + if not is_addr_assigned(address): + raise ConfigError( + f'outgoing-address "{address}" not assigned on any interface!') + + if 'authentication' in proxy: + if 'method' not in proxy['authentication']: + raise ConfigError('proxy authentication method required!') + + if ldap_auth: + ldap_config = proxy['authentication']['ldap'] + + if 'server' not in ldap_config: + raise ConfigError( + 'LDAP authentication enabled, but no server set') + + if 'password' in ldap_config and 'bind_dn' not in ldap_config: + raise ConfigError( + 'LDAP password can not be set when base-dn is undefined!') + + if 'bind_dn' in ldap_config and 'password' not in ldap_config: + raise ConfigError( + 'LDAP bind DN can not be set without password!') + + if 'base_dn' not in ldap_config: + raise ConfigError('LDAP base-dn must be set!') + + if 'cache_peer' in proxy: + for peer, config in proxy['cache_peer'].items(): + if 'address' not in config: + raise ConfigError(f'Cache-peer "{peer}" address must be set!') + + +def generate(proxy): + if not proxy: + return None + + render(squid_config_file, 'squid/squid.conf.j2', proxy) + render(squidguard_config_file, 'squid/squidGuard.conf.j2', proxy) + + cat_dict = { + 'local-block': 'domains', + 'local-block-keyword': 'expressions', + 'local-block-url': 'urls', + 'local-ok': 'domains', + 'local-ok-url': 'urls' + } + if dict_search(f'url_filtering.squidguard', proxy) is not None: + squidgard_config_section = proxy['url_filtering']['squidguard'] + + for category, list_type in cat_dict.items(): + generate_sg_rule_localdb(category, list_type, 'default', proxy) + check_blacklist_categorydb(squidgard_config_section) + + if 'rule' in squidgard_config_section: + for rule in squidgard_config_section['rule']: + rule_config_section = squidgard_config_section['rule'][ + rule] + for category, list_type in cat_dict.items(): + generate_sg_rule_localdb(category, list_type, rule, proxy) + check_blacklist_categorydb(rule_config_section) + + return None + + +def apply(proxy): + if not proxy: + # proxy is removed in the commit + call('systemctl stop squid.service') + + if os.path.exists(squid_config_file): + os.unlink(squid_config_file) + if os.path.exists(squidguard_config_file): + os.unlink(squidguard_config_file) + + return None + + if os.path.exists(squidguard_db_dir): + chmod_755(squidguard_db_dir) + call('systemctl reload-or-restart squid.service') + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_acceleration.py b/src/conf_mode/system_acceleration.py new file mode 100644 index 0000000..d2cf44f --- /dev/null +++ b/src/conf_mode/system_acceleration.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import re + +from sys import exit + +from vyos.config import Config +from vyos.utils.process import popen +from vyos.utils.process import run +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +qat_init_script = '/etc/init.d/qat_service' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + data = {} + + if conf.exists(['system', 'acceleration', 'qat']): + data.update({'qat_enable' : ''}) + + if conf.exists(['vpn', 'ipsec']): + data.update({'ipsec' : ''}) + + if conf.exists(['interfaces', 'openvpn']): + data.update({'openvpn' : ''}) + + return data + + +def vpn_control(action, force_ipsec=False): + # XXX: Should these commands report failure? + if action == 'restore' and force_ipsec: + return run('ipsec start') + + return run(f'ipsec {action}') + + +def verify(qat): + if 'qat_enable' not in qat: + return + + # Check if QAT service installed + if not os.path.exists(qat_init_script): + raise ConfigError('QAT init script not found') + + # Check if QAT device exist + output, err = popen('lspci -nn', decode='utf-8') + if not err: + # PCI id | Chipset + # 19e2 -> C3xx + # 37c8 -> C62x + # 0435 -> DH895 + # 6f54 -> D15xx + # 18ee -> QAT_200XX + data = re.findall( + '(8086:19e2)|(8086:37c8)|(8086:0435)|(8086:6f54)|(8086:18ee)', output) + # If QAT devices found + if not data: + raise ConfigError('No QAT acceleration device found') + +def generate(qat): + return + +def apply(qat): + # Shutdown VPN service which can use QAT + if 'ipsec' in qat: + vpn_control('stop') + + # Enable/Disable QAT service + if 'qat_enable' in qat: + run(f'{qat_init_script} start') + else: + run(f'{qat_init_script} stop') + + # Recover VPN service + if 'ipsec' in qat: + vpn_control('start') + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + apply(c) + except ConfigError as e: + print(e) + vpn_control('restore', force_ipsec=('ipsec' in c)) + exit(1) diff --git a/src/conf_mode/system_config-management.py b/src/conf_mode/system_config-management.py new file mode 100644 index 0000000..c681a84 --- /dev/null +++ b/src/conf_mode/system_config-management.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys + +from vyos import ConfigError +from vyos.config import Config +from vyos.config_mgmt import ConfigMgmt +from vyos.config_mgmt import commit_post_hook_dir, commit_hooks + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['system', 'config-management'] + if not conf.exists(base): + return None + + mgmt = ConfigMgmt(config=conf) + + return mgmt + +def verify(_mgmt): + return + +def generate(mgmt): + if mgmt is None: + return + + mgmt.initialize_revision() + +def apply(mgmt): + if mgmt is None: + return + + locations = mgmt.locations + archive_target = os.path.join(commit_post_hook_dir, + commit_hooks['commit_archive']) + if locations: + try: + os.symlink('/usr/bin/config-mgmt', archive_target) + except FileExistsError: + pass + except OSError as exc: + raise ConfigError from exc + else: + try: + os.unlink(archive_target) + except FileNotFoundError: + pass + except OSError as exc: + raise ConfigError from exc + + revisions = mgmt.max_revisions + revision_target = os.path.join(commit_post_hook_dir, + commit_hooks['commit_revision']) + if revisions > 0: + try: + os.symlink('/usr/bin/config-mgmt', revision_target) + except FileExistsError: + pass + except OSError as exc: + raise ConfigError from exc + else: + try: + os.unlink(revision_target) + except FileNotFoundError: + pass + except OSError as exc: + raise ConfigError from exc + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) diff --git a/src/conf_mode/system_conntrack.py b/src/conf_mode/system_conntrack.py new file mode 100644 index 0000000..2529445 --- /dev/null +++ b/src/conf_mode/system_conntrack.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import json +import os + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdep import set_dependents, call_dependents +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_args +from vyos.utils.dict import dict_search_recursive +from vyos.utils.file import write_file +from vyos.utils.process import cmd, call +from vyos.utils.process import rc_cmd +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +conntrack_config = r'/etc/modprobe.d/vyatta_nf_conntrack.conf' +sysctl_file = r'/run/sysctl/10-vyos-conntrack.conf' +nftables_ct_file = r'/run/nftables-ct.conf' +vyos_conntrack_logger_config = r'/run/vyos-conntrack-logger.conf' + +# Every ALG (Application Layer Gateway) consists of either a Kernel Object +# also called a Kernel Module/Driver or some rules present in iptables +module_map = { + 'ftp': { + 'ko': ['nf_nat_ftp', 'nf_conntrack_ftp'], + 'nftables': ['tcp dport {21} ct helper set "ftp_tcp" return'] + }, + 'h323': { + 'ko': ['nf_nat_h323', 'nf_conntrack_h323'], + 'nftables': ['udp dport {1719} ct helper set "ras_udp" return', + 'tcp dport {1720} ct helper set "q931_tcp" return'] + }, + 'nfs': { + 'nftables': ['tcp dport {111} ct helper set "rpc_tcp" return', + 'udp dport {111} ct helper set "rpc_udp" return'] + }, + 'pptp': { + 'ko': ['nf_nat_pptp', 'nf_conntrack_pptp'], + 'nftables': ['tcp dport {1723} ct helper set "pptp_tcp" return'], + 'ipv4': True + }, + 'rtsp': { + 'ko': ['nf_nat_rtsp', 'nf_conntrack_rtsp'], + 'nftables': ['tcp dport {554} ct helper set "rtsp_tcp" return'], + 'ipv4': True + }, + 'sip': { + 'ko': ['nf_nat_sip', 'nf_conntrack_sip'], + 'nftables': ['tcp dport {5060,5061} ct helper set "sip_tcp" return', + 'udp dport {5060,5061} ct helper set "sip_udp" return'] + }, + 'sqlnet': { + 'nftables': ['tcp dport {1521,1525,1536} ct helper set "tns_tcp" return'] + }, + 'tftp': { + 'ko': ['nf_nat_tftp', 'nf_conntrack_tftp'], + 'nftables': ['udp dport {69} ct helper set "tftp_udp" return'] + }, +} + +valid_groups = [ + 'address_group', + 'domain_group', + 'network_group', + 'port_group' +] + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'conntrack'] + + conntrack = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + conntrack['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + conntrack['ipv4_nat_action'] = 'accept' if conf.exists(['nat']) else 'return' + conntrack['ipv6_nat_action'] = 'accept' if conf.exists(['nat66']) else 'return' + conntrack['wlb_action'] = 'accept' if conf.exists(['load-balancing', 'wan']) else 'return' + conntrack['wlb_local_action'] = conf.exists(['load-balancing', 'wan', 'enable-local-traffic']) + + conntrack['module_map'] = module_map + + if conf.exists(['service', 'conntrack-sync']): + set_dependents('conntrack_sync', conf) + + # If conntrack status changes, VRF zone rules need updating + if conf.exists(['vrf']): + set_dependents('vrf', conf) + + return conntrack + + +def verify(conntrack): + for inet in ['ipv4', 'ipv6']: + if dict_search_args(conntrack, 'ignore', inet, 'rule') != None: + for rule, rule_config in conntrack['ignore'][inet]['rule'].items(): + if dict_search('destination.port', rule_config) or \ + dict_search('destination.group.port_group', rule_config) or \ + dict_search('source.port', rule_config) or \ + dict_search('source.group.port_group', rule_config): + if 'protocol' not in rule_config or rule_config['protocol'] not in ['tcp', 'udp']: + raise ConfigError(f'Port requires tcp or udp as protocol in rule {rule}') + + tcp_flags = dict_search_args(rule_config, 'tcp', 'flags') + if tcp_flags: + if dict_search_args(rule_config, 'protocol') != 'tcp': + raise ConfigError('Protocol must be tcp when specifying tcp flags') + + not_flags = dict_search_args(rule_config, 'tcp', 'flags', 'not') + if not_flags: + duplicates = [flag for flag in tcp_flags if flag in not_flags] + if duplicates: + raise ConfigError(f'Cannot match a tcp flag as set and not set') + + for side in ['destination', 'source']: + if side in rule_config: + side_conf = rule_config[side] + + if 'group' in side_conf: + if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1: + raise ConfigError('Only one address-group, network-group or domain-group can be specified') + + for group in valid_groups: + if group in side_conf['group']: + group_name = side_conf['group'][group] + error_group = group.replace("_", "-") + + if group in ['address_group', 'network_group', 'domain_group']: + if 'address' in side_conf: + raise ConfigError(f'{error_group} and address cannot both be defined') + + if group_name and group_name[0] == '!': + group_name = group_name[1:] + + if inet == 'ipv6': + group = f'ipv6_{group}' + + group_obj = dict_search_args(conntrack['firewall'], 'group', group, group_name) + + if group_obj is None: + raise ConfigError(f'Invalid {error_group} "{group_name}" on ignore rule') + + if not group_obj: + Warning(f'{error_group} "{group_name}" has no members!') + + Warning(f'It is prefered to define {inet} conntrack ignore rules in <firewall {inet} prerouting raw> section') + + if dict_search_args(conntrack, 'timeout', 'custom', inet, 'rule') != None: + for rule, rule_config in conntrack['timeout']['custom'][inet]['rule'].items(): + if 'protocol' not in rule_config: + raise ConfigError(f'Conntrack custom timeout rule {rule} requires protocol tcp or udp') + else: + if 'tcp' in rule_config['protocol'] and 'udp' in rule_config['protocol']: + raise ConfigError(f'conntrack custom timeout rule {rule} - Cant use both tcp and udp protocol') + return None + +def generate(conntrack): + if not os.path.exists(nftables_ct_file): + conntrack['first_install'] = True + + if 'log' not in conntrack: + # Remove old conntrack-logger config and return + if os.path.exists(vyos_conntrack_logger_config): + os.unlink(vyos_conntrack_logger_config) + + # Determine if conntrack is needed + conntrack['ipv4_firewall_action'] = 'return' + conntrack['ipv6_firewall_action'] = 'return' + + if dict_search_args(conntrack['firewall'], 'global_options', 'state_policy') != None: + conntrack['ipv4_firewall_action'] = 'accept' + conntrack['ipv6_firewall_action'] = 'accept' + else: + for rules, path in dict_search_recursive(conntrack['firewall'], 'rule'): + if any(('state' in rule_conf or 'connection_status' in rule_conf or 'offload_target' in rule_conf) for rule_conf in rules.values()): + if path[0] == 'ipv4': + conntrack['ipv4_firewall_action'] = 'accept' + elif path[0] == 'ipv6': + conntrack['ipv6_firewall_action'] = 'accept' + + render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.j2', conntrack) + render(sysctl_file, 'conntrack/sysctl.conf.j2', conntrack) + render(nftables_ct_file, 'conntrack/nftables-ct.j2', conntrack) + + if 'log' in conntrack: + log_conf_json = json.dumps(conntrack['log'], indent=4) + write_file(vyos_conntrack_logger_config, log_conf_json) + + return None + +def apply(conntrack): + # Depending on the enable/disable state of the ALG (Application Layer Gateway) + # modules we need to either insmod or rmmod the helpers. + + add_modules = [] + rm_modules = [] + + for module, module_config in module_map.items(): + if dict_search_args(conntrack, 'modules', module) is None: + if 'ko' in module_config: + unloaded = [mod for mod in module_config['ko'] if os.path.exists(f'/sys/module/{mod}')] + rm_modules.extend(unloaded) + else: + if 'ko' in module_config: + add_modules.extend(module_config['ko']) + + # Add modules before nftables uses them + if add_modules: + module_str = ' '.join(add_modules) + cmd(f'modprobe -a {module_str}') + + # Load new nftables ruleset + install_result, output = rc_cmd(f'nft --file {nftables_ct_file}') + if install_result == 1: + raise ConfigError(f'Failed to apply configuration: {output}') + + # Remove modules after nftables stops using them + if rm_modules: + module_str = ' '.join(rm_modules) + cmd(f'rmmod {module_str}') + + try: + call_dependents() + except ConfigError: + # Ignore config errors on dependent due to being called too early. Example: + # ConfigError("ConfigError('Interface ethN requires an IP address!')") + pass + + # We silently ignore all errors + # See: https://bugzilla.redhat.com/show_bug.cgi?id=1264080 + cmd(f'sysctl -f {sysctl_file}') + + if 'log' in conntrack: + call(f'systemctl restart vyos-conntrack-logger.service') + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_console.py b/src/conf_mode/system_console.py new file mode 100644 index 0000000..b380e05 --- /dev/null +++ b/src/conf_mode/system_console.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +from pathlib import Path + +from vyos.config import Config +from vyos.utils.process import call +from vyos.utils.serial import restart_login_consoles +from vyos.system import grub_util +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +by_bus_dir = '/dev/serial/by-bus' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'console'] + + # retrieve configuration at once + console = conf.get_config_dict(base, get_first_key=True) + + # bail out early if no serial console is configured + if 'device' not in console: + return console + + for device, device_config in console['device'].items(): + if 'speed' not in device_config and device.startswith('hvc'): + # XEN console has a different default console speed + console['device'][device]['speed'] = 38400 + + console = conf.merge_defaults(console, recursive=True) + + return console + +def verify(console): + if not console or 'device' not in console: + return None + + for device in console['device']: + if device.startswith('usb'): + # It is much easiert to work with the native ttyUSBn name when using + # getty, but that name may change across reboots - depending on the + # amount of connected devices. We will resolve the fixed device name + # to its dynamic device file - and create a new dict entry for it. + by_bus_device = f'{by_bus_dir}/{device}' + # If the device name still starts with usbXXX no matching tty was found + # and it can not be used as a serial interface + if not os.path.isdir(by_bus_dir) or not os.path.exists(by_bus_device): + raise ConfigError(f'Device {device} does not support beeing used as tty') + + return None + +def generate(console): + base_dir = '/run/systemd/system' + # Remove all serial-getty configuration files in advance + for root, dirs, files in os.walk(base_dir): + for basename in files: + if 'serial-getty' in basename: + os.unlink(os.path.join(root, basename)) + + if not console or 'device' not in console: + return None + + # replace keys in the config for ttyUSB items to use them in `apply()` later + for device in console['device'].copy(): + if device.startswith('usb'): + # It is much easiert to work with the native ttyUSBn name when using + # getty, but that name may change across reboots - depending on the + # amount of connected devices. We will resolve the fixed device name + # to its dynamic device file - and create a new dict entry for it. + by_bus_device = f'{by_bus_dir}/{device}' + if os.path.isdir(by_bus_dir) and os.path.exists(by_bus_device): + device_updated = os.path.basename(os.readlink(by_bus_device)) + + # replace keys in the config to use them in `apply()` later + console['device'][device_updated] = console['device'][device] + del console['device'][device] + else: + raise ConfigError(f'Device {device} does not support beeing used as tty') + + for device, device_config in console['device'].items(): + config_file = base_dir + f'/serial-getty@{device}.service' + Path(f'{base_dir}/getty.target.wants').mkdir(exist_ok=True) + getty_wants_symlink = base_dir + f'/getty.target.wants/serial-getty@{device}.service' + + render(config_file, 'getty/serial-getty.service.j2', device_config) + os.symlink(config_file, getty_wants_symlink) + + # GRUB + # For existing serial line change speed (if necessary) + # Only applys to ttyS0 + if 'ttyS0' not in console['device']: + return None + + speed = console['device']['ttyS0']['speed'] + grub_util.update_console_speed(speed) + + return None + +def apply(console): + # Reset screen blanking + call('/usr/bin/setterm -blank 0 -powersave off -powerdown 0 -term linux </dev/tty1 >/dev/tty1 2>&1') + # Reload systemd manager configuration + call('systemctl daemon-reload') + + # Service control moved to vyos.utils.serial to unify checks and prompts. + # If users are connected, we want to show an informational message on completing + # the process, but not halt configuration processing with an interactive prompt. + restart_login_consoles(prompt_user=False, quiet=False) + + if not console: + return None + + if 'powersave' in console.keys(): + # Configure screen blank powersaving on VGA console + call('/usr/bin/setterm -blank 15 -powersave powerdown -powerdown 60 -term linux </dev/tty1 >/dev/tty1 2>&1') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_flow-accounting.py b/src/conf_mode/system_flow-accounting.py new file mode 100644 index 0000000..a12ee36 --- /dev/null +++ b/src/conf_mode/system_flow-accounting.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import re + +from sys import exit +from ipaddress import ip_address + +from vyos.config import Config +from vyos.config import config_dict_merge +from vyos.configverify import verify_vrf +from vyos.configverify import verify_interface_exists +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.process import cmd +from vyos.utils.process import run +from vyos.utils.network import is_addr_assigned +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +uacctd_conf_path = '/run/pmacct/uacctd.conf' +systemd_service = 'uacctd.service' +systemd_override = f'/run/systemd/system/{systemd_service}.d/override.conf' +nftables_nflog_table = 'raw' +nftables_nflog_chain = 'VYOS_PREROUTING_HOOK' +egress_nftables_nflog_table = 'inet mangle' +egress_nftables_nflog_chain = 'FORWARD' + +# get nftables rule dict for chain in table +def _nftables_get_nflog(chain, table): + # define list with rules + rules = [] + + # prepare regex for parsing rules + rule_pattern = '[io]ifname "(?P<interface>[\w\.\*\-]+)".*handle (?P<handle>[\d]+)' + rule_re = re.compile(rule_pattern) + + # run nftables, save output and split it by lines + nftables_command = f'nft -a list chain {table} {chain}' + tmp = cmd(nftables_command, message='Failed to get flows list') + # parse each line and add information to list + for current_rule in tmp.splitlines(): + if 'FLOW_ACCOUNTING_RULE' not in current_rule: + continue + current_rule_parsed = rule_re.search(current_rule) + if current_rule_parsed: + groups = current_rule_parsed.groupdict() + rules.append({ 'interface': groups["interface"], 'table': table, 'handle': groups["handle"] }) + + # return list with rules + return rules + +def _nftables_config(configured_ifaces, direction, length=None): + # define list of nftables commands to modify settings + nftable_commands = [] + nftables_chain = nftables_nflog_chain + nftables_table = nftables_nflog_table + + if direction == "egress": + nftables_chain = egress_nftables_nflog_chain + nftables_table = egress_nftables_nflog_table + + # prepare extended list with configured interfaces + configured_ifaces_extended = [] + for iface in configured_ifaces: + configured_ifaces_extended.append({ 'iface': iface }) + + # get currently configured interfaces with nftables rules + active_nflog_rules = _nftables_get_nflog(nftables_chain, nftables_table) + + # compare current active list with configured one and delete excessive interfaces, add missed + active_nflog_ifaces = [] + for rule in active_nflog_rules: + interface = rule['interface'] + if interface not in configured_ifaces: + table = rule['table'] + handle = rule['handle'] + nftable_commands.append(f'nft delete rule {table} {nftables_chain} handle {handle}') + else: + active_nflog_ifaces.append({ + 'iface': interface, + }) + + # do not create new rules for already configured interfaces + for iface in active_nflog_ifaces: + if iface in active_nflog_ifaces and iface in configured_ifaces_extended: + configured_ifaces_extended.remove(iface) + + # create missed rules + for iface_extended in configured_ifaces_extended: + iface = iface_extended['iface'] + iface_prefix = "o" if direction == "egress" else "i" + rule_definition = f'{iface_prefix}ifname "{iface}" counter log group 2 snaplen {length} queue-threshold 100 comment "FLOW_ACCOUNTING_RULE"' + nftable_commands.append(f'nft insert rule {nftables_table} {nftables_chain} {rule_definition}') + # Also add IPv6 ingres logging + if nftables_table == nftables_nflog_table: + nftable_commands.append(f'nft insert rule ip6 {nftables_table} {nftables_chain} {rule_definition}') + + # change nftables + for command in nftable_commands: + cmd(command, raising=ConfigError) + + +def _nftables_trigger_setup(operation: str) -> None: + """Add a dummy rule to unlock the main pmacct loop with a packet-trigger + + Args: + operation (str): 'add' or 'delete' a trigger + """ + # check if a chain exists + table_exists = False + if run('nft -snj list table ip pmacct') == 0: + table_exists = True + + if operation == 'delete' and table_exists: + nft_cmd: str = 'nft delete table ip pmacct' + cmd(nft_cmd, raising=ConfigError) + if operation == 'add' and not table_exists: + nft_cmds: list[str] = [ + 'nft add table ip pmacct', + 'nft add chain ip pmacct pmacct_out { type filter hook output priority raw - 50 \\; policy accept \\; }', + 'nft add rule ip pmacct pmacct_out oif lo ip daddr 127.0.254.0 counter log group 2 snaplen 1 queue-threshold 0 comment NFLOG_TRIGGER' + ] + for nft_cmd in nft_cmds: + cmd(nft_cmd, raising=ConfigError) + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'flow-accounting'] + if not conf.exists(base): + return None + + flow_accounting = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + # We have gathered the dict representation of the CLI, but there are + # default values which we need to conditionally update into the + # dictionary retrieved. + default_values = conf.get_config_defaults(**flow_accounting.kwargs, + recursive=True) + + # delete individual flow type defaults - should only be added if user + # sets this feature + for flow_type in ['sflow', 'netflow']: + if flow_type not in flow_accounting and flow_type in default_values: + del default_values[flow_type] + + flow_accounting = config_dict_merge(default_values, flow_accounting) + + return flow_accounting + +def verify(flow_config): + if not flow_config: + return None + + # check if at least one collector is enabled + if 'sflow' not in flow_config and 'netflow' not in flow_config and 'disable_imt' in flow_config: + raise ConfigError('You need to configure at least sFlow or NetFlow, ' \ + 'or not set "disable-imt" for flow-accounting!') + + # Check if at least one interface is configured + if 'interface' not in flow_config: + raise ConfigError('Flow accounting requires at least one interface to ' \ + 'be configured!') + + # check that all configured interfaces exists in the system + for interface in flow_config['interface']: + verify_interface_exists(flow_config, interface, warning_only=True) + + # check sFlow configuration + if 'sflow' in flow_config: + # check if at least one sFlow collector is configured + if 'server' not in flow_config['sflow']: + raise ConfigError('You need to configure at least one sFlow server!') + + # check that all sFlow collectors use the same IP protocol version + sflow_collector_ipver = None + for server in flow_config['sflow']['server']: + if sflow_collector_ipver: + if sflow_collector_ipver != ip_address(server).version: + raise ConfigError("All sFlow servers must use the same IP protocol") + else: + sflow_collector_ipver = ip_address(server).version + + # check if vrf is defined for Sflow + verify_vrf(flow_config) + sflow_vrf = None + if 'vrf' in flow_config: + sflow_vrf = flow_config['vrf'] + + # check agent-id for sFlow: we should avoid mixing IPv4 agent-id with IPv6 collectors and vice-versa + for server in flow_config['sflow']['server']: + if 'agent_address' in flow_config['sflow']: + if ip_address(server).version != ip_address(flow_config['sflow']['agent_address']).version: + raise ConfigError('IPv4 and IPv6 addresses can not be mixed in "sflow agent-address" and "sflow '\ + 'server". You need to set the same IP version for both "agent-address" and '\ + 'all sFlow servers') + + if 'agent_address' in flow_config['sflow']: + tmp = flow_config['sflow']['agent_address'] + if not is_addr_assigned(tmp, sflow_vrf): + raise ConfigError(f'Configured "sflow agent-address {tmp}" does not exist in the system!') + + # Check if configured sflow source-address exist in the system + if 'source_address' in flow_config['sflow']: + if not is_addr_assigned(flow_config['sflow']['source_address'], sflow_vrf): + tmp = flow_config['sflow']['source_address'] + raise ConfigError(f'Configured "sflow source-address {tmp}" does not exist on the system!') + + # check NetFlow configuration + if 'netflow' in flow_config: + # check if vrf is defined for netflow + netflow_vrf = None + if 'vrf' in flow_config: + netflow_vrf = flow_config['vrf'] + + # check if at least one NetFlow collector is configured if NetFlow configuration is presented + if 'server' not in flow_config['netflow']: + raise ConfigError('You need to configure at least one NetFlow server!') + + # Check if configured netflow source-address exist in the system + if 'source_address' in flow_config['netflow']: + if not is_addr_assigned(flow_config['netflow']['source_address'], netflow_vrf): + tmp = flow_config['netflow']['source_address'] + raise ConfigError(f'Configured "netflow source-address {tmp}" does not exist on the system!') + + # Check if engine-id compatible with selected protocol version + if 'engine_id' in flow_config['netflow']: + v5_filter = '^(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]):(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$' + v9v10_filter = '^(\d|[1-9]\d{1,8}|[1-3]\d{9}|4[01]\d{8}|42[0-8]\d{7}|429[0-3]\d{6}|4294[0-8]\d{5}|42949[0-5]\d{4}|429496[0-6]\d{3}|4294967[01]\d{2}|42949672[0-8]\d|429496729[0-5])$' + engine_id = flow_config['netflow']['engine_id'] + version = flow_config['netflow']['version'] + + if flow_config['netflow']['version'] == '5': + regex_filter = re.compile(v5_filter) + if not regex_filter.search(engine_id): + raise ConfigError(f'You cannot use NetFlow engine-id "{engine_id}" '\ + f'together with NetFlow protocol version "{version}"!') + else: + regex_filter = re.compile(v9v10_filter) + if not regex_filter.search(flow_config['netflow']['engine_id']): + raise ConfigError(f'Can not use NetFlow engine-id "{engine_id}" together '\ + f'with NetFlow protocol version "{version}"!') + + # return True if all checks were passed + return True + +def generate(flow_config): + if not flow_config: + return None + + render(uacctd_conf_path, 'pmacct/uacctd.conf.j2', flow_config) + render(systemd_override, 'pmacct/override.conf.j2', flow_config) + # Reload systemd manager configuration + call('systemctl daemon-reload') + +def apply(flow_config): + # Check if flow-accounting was removed and define command + if not flow_config: + _nftables_config([], 'ingress') + _nftables_config([], 'egress') + + # Stop flow-accounting daemon and remove configuration file + call(f'systemctl stop {systemd_service}') + if os.path.exists(uacctd_conf_path): + os.unlink(uacctd_conf_path) + + # must be done after systemctl + _nftables_trigger_setup('delete') + + return + + # Start/reload flow-accounting daemon + call(f'systemctl restart {systemd_service}') + + # configure nftables rules for defined interfaces + if 'interface' in flow_config: + _nftables_config(flow_config['interface'], 'ingress', flow_config['packet_length']) + + # configure egress the same way if configured otherwise remove it + if 'enable_egress' in flow_config: + _nftables_config(flow_config['interface'], 'egress', flow_config['packet_length']) + else: + _nftables_config([], 'egress') + + # add a trigger for signal processing + _nftables_trigger_setup('add') + + +if __name__ == '__main__': + try: + config = get_config() + verify(config) + generate(config) + apply(config) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_frr.py b/src/conf_mode/system_frr.py new file mode 100644 index 0000000..d9ac543 --- /dev/null +++ b/src/conf_mode/system_frr.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos import ConfigError +from vyos.base import Warning +from vyos.config import Config +from vyos.logger import syslog +from vyos.template import render_to_string +from vyos.utils.boot import boot_configuration_complete +from vyos.utils.file import read_file +from vyos.utils.file import write_file +from vyos.utils.process import call + +from vyos import airbag +airbag.enable() + +# path to daemons config and config status files +config_file = '/etc/frr/daemons' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['system', 'frr'] + frr_config = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + return frr_config + +def verify(frr_config): + # Nothing to verify here + pass + +def generate(frr_config): + # read daemons config file + daemons_config_current = read_file(config_file) + # generate new config file + daemons_config_new = render_to_string('frr/daemons.frr.tmpl', frr_config) + # update configuration file if this is necessary + if daemons_config_new != daemons_config_current: + syslog.warning('FRR daemons configuration file need to be changed') + write_file(config_file, daemons_config_new) + frr_config['config_file_changed'] = True + +def apply(frr_config): + # display warning to user + if boot_configuration_complete() and frr_config.get('config_file_changed'): + # Since FRR restart is not safe thing, better to give + # control over this to users + Warning('You need to reboot the router (preferred) or restart '\ + 'FRR to apply changes in modules settings') + + # restart FRR automatically + # During initial boot this should be safe in most cases + if not boot_configuration_complete() and frr_config.get('config_file_changed'): + syslog.warning('Restarting FRR to apply changes in modules') + call(f'systemctl restart frr.service') + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_host-name.py b/src/conf_mode/system_host-name.py new file mode 100644 index 0000000..3f245f1 --- /dev/null +++ b/src/conf_mode/system_host-name.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import sys +import copy + +import vyos.hostsd_client + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdict import leaf_node_changed +from vyos.ifconfig import Section +from vyos.template import is_ip +from vyos.utils.process import cmd +from vyos.utils.process import call +from vyos.utils.process import process_named_running +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +default_config_data = { + 'hostname': 'vyos', + 'domain_name': '', + 'domain_search': [], + 'nameserver': [], + 'nameservers_dhcp_interfaces': {}, + 'snmpd_restart_reqired': False, + 'static_host_mapping': {} +} + +hostsd_tag = 'system' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + hosts = copy.deepcopy(default_config_data) + + hosts['hostname'] = conf.return_value(['system', 'host-name']) + + base = ['system'] + if leaf_node_changed(conf, base + ['host-name']) or leaf_node_changed(conf, base + ['domain-name']): + hosts['snmpd_restart_reqired'] = True + + # This may happen if the config is not loaded yet, + # e.g. if run by cloud-init + if not hosts['hostname']: + hosts['hostname'] = default_config_data['hostname'] + + if conf.exists(['system', 'domain-name']): + hosts['domain_name'] = conf.return_value(['system', 'domain-name']) + hosts['domain_search'].append(hosts['domain_name']) + + if conf.exists(['system', 'domain-search']): + for search in conf.return_values(['system', 'domain-search']): + hosts['domain_search'].append(search) + + if conf.exists(['system', 'name-server']): + for ns in conf.return_values(['system', 'name-server']): + if is_ip(ns): + hosts['nameserver'].append(ns) + else: + tmp = '' + config_path = Section.get_config_path(ns) + if conf.exists(['interfaces', config_path, 'address']): + tmp = conf.return_values(['interfaces', config_path, 'address']) + + hosts['nameservers_dhcp_interfaces'].update({ ns : tmp }) + + # system static-host-mapping + for hn in conf.list_nodes(['system', 'static-host-mapping', 'host-name']): + hosts['static_host_mapping'][hn] = {} + hosts['static_host_mapping'][hn]['address'] = conf.return_values(['system', 'static-host-mapping', 'host-name', hn, 'inet']) + hosts['static_host_mapping'][hn]['aliases'] = conf.return_values(['system', 'static-host-mapping', 'host-name', hn, 'alias']) + + return hosts + + +def verify(hosts): + if hosts is None: + return None + + # pattern $VAR(@) "^[[:alnum:]][-.[:alnum:]]*[[:alnum:]]$" ; "invalid host name $VAR(@)" + hostname_regex = re.compile("^[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]$") + if not hostname_regex.match(hosts['hostname']): + raise ConfigError('Invalid host name ' + hosts["hostname"]) + + # pattern $VAR(@) "^.{1,63}$" ; "invalid host-name length" + length = len(hosts['hostname']) + if length < 1 or length > 63: + raise ConfigError( + 'Invalid host-name length, must be less than 63 characters') + + all_static_host_mapping_addresses = [] + # static mappings alias hostname + for host, hostprops in hosts['static_host_mapping'].items(): + if not hostprops['address']: + raise ConfigError(f'IP address required for static-host-mapping "{host}"') + all_static_host_mapping_addresses.append(hostprops['address']) + for a in hostprops['aliases']: + if not hostname_regex.match(a) and len(a) != 0: + raise ConfigError(f'Invalid alias "{a}" in static-host-mapping "{host}"') + + for interface, interface_config in hosts['nameservers_dhcp_interfaces'].items(): + # Warnin user if interface does not have DHCP or DHCPv6 configured + if not set(interface_config).intersection(['dhcp', 'dhcpv6']): + Warning(f'"{interface}" is not a DHCP interface but uses DHCP name-server option!') + + return None + + +def generate(config): + pass + +def apply(config): + if config is None: + return None + + ## Send the updated data to vyos-hostsd + try: + hc = vyos.hostsd_client.Client() + + hc.set_host_name(config['hostname'], config['domain_name']) + + hc.delete_search_domains([hostsd_tag]) + if config['domain_search']: + hc.add_search_domains({hostsd_tag: config['domain_search']}) + + hc.delete_name_servers([hostsd_tag]) + if config['nameserver']: + hc.add_name_servers({hostsd_tag: config['nameserver']}) + + # add our own tag's (system) nameservers and search to resolv.conf + hc.delete_name_server_tags_system(hc.get_name_server_tags_system()) + hc.add_name_server_tags_system([hostsd_tag]) + + # this will add the dhcp client nameservers to resolv.conf + for intf in config['nameservers_dhcp_interfaces']: + hc.add_name_server_tags_system([f'dhcp-{intf}', f'dhcpv6-{intf}']) + + hc.delete_hosts([hostsd_tag]) + if config['static_host_mapping']: + hc.add_hosts({hostsd_tag: config['static_host_mapping']}) + + hc.apply() + except vyos.hostsd_client.VyOSHostsdError as e: + raise ConfigError(str(e)) + + ## Actually update the hostname -- vyos-hostsd doesn't do that + + # No domain name -- the Debian way. + hostname_new = config['hostname'] + + # rsyslog runs into a race condition at boot time with systemd + # restart rsyslog only if the hostname changed. + hostname_old = cmd('hostnamectl --static') + call(f'hostnamectl set-hostname --static {hostname_new}') + + # Restart services that use the hostname + if hostname_new != hostname_old: + call("systemctl restart rsyslog.service") + + # If SNMP is running, restart it too + if process_named_running('snmpd') and config['snmpd_restart_reqired']: + call('systemctl restart snmpd.service') + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) diff --git a/src/conf_mode/system_ip.py b/src/conf_mode/system_ip.py new file mode 100644 index 0000000..c8a91fd --- /dev/null +++ b/src/conf_mode/system_ip.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configverify import verify_route_map +from vyos.template import render_to_string +from vyos.utils.dict import dict_search +from vyos.utils.file import write_file +from vyos.utils.process import is_systemd_service_active +from vyos.utils.system import sysctl_write +from vyos.configdep import set_dependents +from vyos.configdep import call_dependents +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'ip'] + + opt = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + # When working with FRR we need to know the corresponding address-family + opt['afi'] = 'ip' + + # We also need the route-map information from the config + # + # XXX: one MUST always call this without the key_mangling() option! See + # vyos.configverify.verify_common_route_maps() for more information. + tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'], + get_first_key=True)}} + # Merge policy dict into "regular" config dict + opt = dict_merge(tmp, opt) + + # If IPv4 ARP table size is set here and also manually in sysctl, the more + # fine grained value from sysctl must win + set_dependents('sysctl', conf) + + return opt + +def verify(opt): + if 'protocol' in opt: + for protocol, protocol_options in opt['protocol'].items(): + if 'route_map' in protocol_options: + verify_route_map(protocol_options['route_map'], opt) + return + +def generate(opt): + opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt) + return + +def apply(opt): + # Apply ARP threshold values + # table_size has a default value - thus the key always exists + size = int(dict_search('arp.table_size', opt)) + # Amount upon reaching which the records begin to be cleared immediately + sysctl_write('net.ipv4.neigh.default.gc_thresh3', size) + # Amount after which the records begin to be cleaned after 5 seconds + sysctl_write('net.ipv4.neigh.default.gc_thresh2', size // 2) + # Minimum number of stored records is indicated which is not cleared + sysctl_write('net.ipv4.neigh.default.gc_thresh1', size // 8) + + # enable/disable IPv4 forwarding + tmp = dict_search('disable_forwarding', opt) + value = '0' if (tmp != None) else '1' + write_file('/proc/sys/net/ipv4/conf/all/forwarding', value) + + # configure multipath + tmp = dict_search('multipath.ignore_unreachable_nexthops', opt) + value = '1' if (tmp != None) else '0' + sysctl_write('net.ipv4.fib_multipath_use_neigh', value) + + tmp = dict_search('multipath.layer4_hashing', opt) + value = '1' if (tmp != None) else '0' + sysctl_write('net.ipv4.fib_multipath_hash_policy', value) + + # configure TCP options (defaults as of Linux 6.4) + tmp = dict_search('tcp.mss.probing', opt) + if tmp is None: + value = 0 + elif tmp == 'on-icmp-black-hole': + value = 1 + elif tmp == 'force': + value = 2 + else: + # Shouldn't happen + raise ValueError("TCP MSS probing is neither 'on-icmp-black-hole' nor 'force'!") + sysctl_write('net.ipv4.tcp_mtu_probing', value) + + tmp = dict_search('tcp.mss.base', opt) + value = '1024' if (tmp is None) else tmp + sysctl_write('net.ipv4.tcp_base_mss', value) + + tmp = dict_search('tcp.mss.floor', opt) + value = '48' if (tmp is None) else tmp + sysctl_write('net.ipv4.tcp_mtu_probe_floor', value) + + # During startup of vyos-router that brings up FRR, the service is not yet + # running when this script is called first. Skip this part and wait for initial + # commit of the configuration to trigger this statement + if is_systemd_service_active('frr.service'): + zebra_daemon = 'zebra' + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + # The route-map used for the FIB (zebra) is part of the zebra daemon + frr_cfg.load_configuration(zebra_daemon) + frr_cfg.modify_section(r'no ip nht resolve-via-default') + frr_cfg.modify_section(r'ip protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') + if 'frr_zebra_config' in opt: + frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) + frr_cfg.commit_configuration(zebra_daemon) + + call_dependents() + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_ipv6.py b/src/conf_mode/system_ipv6.py new file mode 100644 index 0000000..a2442d0 --- /dev/null +++ b/src/conf_mode/system_ipv6.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configverify import verify_route_map +from vyos.template import render_to_string +from vyos.utils.dict import dict_search +from vyos.utils.file import write_file +from vyos.utils.process import is_systemd_service_active +from vyos.utils.system import sysctl_write +from vyos.configdep import set_dependents +from vyos.configdep import call_dependents +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'ipv6'] + + opt = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + # When working with FRR we need to know the corresponding address-family + opt['afi'] = 'ipv6' + + # We also need the route-map information from the config + # + # XXX: one MUST always call this without the key_mangling() option! See + # vyos.configverify.verify_common_route_maps() for more information. + tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'], + get_first_key=True)}} + # Merge policy dict into "regular" config dict + opt = dict_merge(tmp, opt) + + # If IPv6 neighbor table size is set here and also manually in sysctl, the more + # fine grained value from sysctl must win + set_dependents('sysctl', conf) + + return opt + +def verify(opt): + if 'protocol' in opt: + for protocol, protocol_options in opt['protocol'].items(): + if 'route_map' in protocol_options: + verify_route_map(protocol_options['route_map'], opt) + return + +def generate(opt): + opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt) + return + +def apply(opt): + # configure multipath + tmp = dict_search('multipath.layer4_hashing', opt) + value = '1' if (tmp != None) else '0' + sysctl_write('net.ipv6.fib_multipath_hash_policy', value) + + # Apply ND threshold values + # table_size has a default value - thus the key always exists + size = int(dict_search('neighbor.table_size', opt)) + # Amount upon reaching which the records begin to be cleared immediately + sysctl_write('net.ipv6.neigh.default.gc_thresh3', size) + # Amount after which the records begin to be cleaned after 5 seconds + sysctl_write('net.ipv6.neigh.default.gc_thresh2', size // 2) + # Minimum number of stored records is indicated which is not cleared + sysctl_write('net.ipv6.neigh.default.gc_thresh1', size // 8) + + # enable/disable IPv6 forwarding + tmp = dict_search('disable_forwarding', opt) + value = '0' if (tmp != None) else '1' + write_file('/proc/sys/net/ipv6/conf/all/forwarding', value) + + # configure IPv6 strict-dad + tmp = dict_search('strict_dad', opt) + value = '2' if (tmp != None) else '1' + for root, dirs, files in os.walk('/proc/sys/net/ipv6/conf'): + for name in files: + if name == 'accept_dad': + write_file(os.path.join(root, name), value) + + # During startup of vyos-router that brings up FRR, the service is not yet + # running when this script is called first. Skip this part and wait for initial + # commit of the configuration to trigger this statement + if is_systemd_service_active('frr.service'): + zebra_daemon = 'zebra' + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + # The route-map used for the FIB (zebra) is part of the zebra daemon + frr_cfg.load_configuration(zebra_daemon) + frr_cfg.modify_section(r'no ipv6 nht resolve-via-default') + frr_cfg.modify_section(r'ipv6 protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') + if 'frr_zebra_config' in opt: + frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) + frr_cfg.commit_configuration(zebra_daemon) + + call_dependents() + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_lcd.py b/src/conf_mode/system_lcd.py new file mode 100644 index 0000000..eb88224 --- /dev/null +++ b/src/conf_mode/system_lcd.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# +# Copyright 2020-2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.utils.process import call +from vyos.utils.system import find_device_file +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +lcdd_conf = '/run/LCDd/LCDd.conf' +lcdproc_conf = '/run/lcdproc/lcdproc.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'lcd'] + lcd = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) + # Return (possibly empty) dictionary + return lcd + +def verify(lcd): + if not lcd: + return None + + if 'model' in lcd and lcd['model'] in ['sdec']: + # This is a fixed LCD display, no device needed - bail out early + return None + + if not {'device', 'model'} <= set(lcd): + raise ConfigError('Both device and driver must be set!') + + return None + +def generate(lcd): + if not lcd: + return None + + if 'device' in lcd: + lcd['device'] = find_device_file(lcd['device']) + + # Render config file for daemon LCDd + render(lcdd_conf, 'lcd/LCDd.conf.j2', lcd) + # Render config file for client lcdproc + render(lcdproc_conf, 'lcd/lcdproc.conf.j2', lcd) + + return None + +def apply(lcd): + if not lcd: + call('systemctl stop lcdproc.service LCDd.service') + + for file in [lcdd_conf, lcdproc_conf]: + if os.path.exists(file): + os.remove(file) + else: + # Restart server + call('systemctl restart LCDd.service lcdproc.service') + + return None + +if __name__ == '__main__': + try: + config_dict = get_config() + verify(config_dict) + generate(config_dict) + apply(config_dict) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_login.py b/src/conf_mode/system_login.py new file mode 100644 index 0000000..439fa64 --- /dev/null +++ b/src/conf_mode/system_login.py @@ -0,0 +1,413 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from passlib.hosts import linux_context +from psutil import users +from pwd import getpwall +from pwd import getpwnam +from pwd import getpwuid +from sys import exit +from time import sleep + +from vyos.config import Config +from vyos.configverify import verify_vrf +from vyos.template import render +from vyos.template import is_ipv4 +from vyos.utils.auth import get_current_user +from vyos.utils.configfs import delete_cli_node +from vyos.utils.configfs import add_cli_node +from vyos.utils.dict import dict_search +from vyos.utils.file import chown +from vyos.utils.process import cmd +from vyos.utils.process import call +from vyos.utils.process import run +from vyos.utils.process import DEVNULL +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +autologout_file = "/etc/profile.d/autologout.sh" +limits_file = "/etc/security/limits.d/10-vyos.conf" +radius_config_file = "/etc/pam_radius_auth.conf" +tacacs_pam_config_file = "/etc/tacplus_servers" +tacacs_nss_config_file = "/etc/tacplus_nss.conf" +nss_config_file = "/etc/nsswitch.conf" + +# Minimum UID used when adding system users +MIN_USER_UID: int = 1000 +# Maximim UID used when adding system users +MAX_USER_UID: int = 59999 +# LOGIN_TIMEOUT from /etc/loign.defs minus 10 sec +MAX_RADIUS_TIMEOUT: int = 50 +# MAX_RADIUS_TIMEOUT divided by 2 sec (minimum recomended timeout) +MAX_RADIUS_COUNT: int = 8 +# Maximum number of supported TACACS servers +MAX_TACACS_COUNT: int = 8 + +# List of local user accounts that must be preserved +SYSTEM_USER_SKIP_LIST: list = ['radius_user', 'radius_priv_user', 'tacacs0', 'tacacs1', + 'tacacs2', 'tacacs3', 'tacacs4', 'tacacs5', 'tacacs6', + 'tacacs7', 'tacacs8', 'tacacs9', 'tacacs10',' tacacs11', + 'tacacs12', 'tacacs13', 'tacacs14', 'tacacs15'] + +def get_local_users(): + """Return list of dynamically allocated users (see Debian Policy Manual)""" + local_users = [] + for s_user in getpwall(): + if getpwnam(s_user.pw_name).pw_uid < MIN_USER_UID: + continue + if getpwnam(s_user.pw_name).pw_uid > MAX_USER_UID: + continue + if s_user.pw_name in SYSTEM_USER_SKIP_LIST: + continue + local_users.append(s_user.pw_name) + + return local_users + +def get_shadow_password(username): + with open('/etc/shadow') as f: + for user in f.readlines(): + items = user.split(":") + if username == items[0]: + return items[1] + return None + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'login'] + login = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + # users no longer existing in the running configuration need to be deleted + local_users = get_local_users() + cli_users = [] + if 'user' in login: + cli_users = list(login['user']) + + # prune TACACS global defaults if not set by user + if login.from_defaults(['tacacs']): + del login['tacacs'] + # same for RADIUS + if login.from_defaults(['radius']): + del login['radius'] + + # create a list of all users, cli and users + all_users = list(set(local_users + cli_users)) + # We will remove any normal users that dos not exist in the current + # configuration. This can happen if user is added but configuration was not + # saved and system is rebooted. + rm_users = [tmp for tmp in all_users if tmp not in cli_users] + if rm_users: login.update({'rm_users' : rm_users}) + + return login + +def verify(login): + if 'rm_users' in login: + # This check is required as the script is also executed from vyos-router + # init script and there is no SUDO_USER environment variable available + # during system boot. + tmp = get_current_user() + if tmp in login['rm_users']: + raise ConfigError(f'Attempting to delete current user: {tmp}') + + if 'user' in login: + system_users = getpwall() + for user, user_config in login['user'].items(): + # Linux system users range up until UID 1000, we can not create a + # VyOS CLI user which already exists as system user + for s_user in system_users: + if s_user.pw_name == user and s_user.pw_uid < MIN_USER_UID: + raise ConfigError(f'User "{user}" can not be created, conflict with local system account!') + + for pubkey, pubkey_options in (dict_search('authentication.public_keys', user_config) or {}).items(): + if 'type' not in pubkey_options: + raise ConfigError(f'Missing type for public-key "{pubkey}"!') + if 'key' not in pubkey_options: + raise ConfigError(f'Missing key for public-key "{pubkey}"!') + + if {'radius', 'tacacs'} <= set(login): + raise ConfigError('Using both RADIUS and TACACS at the same time is not supported!') + + # At lease one RADIUS server must not be disabled + if 'radius' in login: + if 'server' not in login['radius']: + raise ConfigError('No RADIUS server defined!') + sum_timeout: int = 0 + radius_servers_count: int = 0 + fail = True + for server, server_config in dict_search('radius.server', login).items(): + if 'key' not in server_config: + raise ConfigError(f'RADIUS server "{server}" requires key!') + if 'disable' not in server_config: + sum_timeout += int(server_config['timeout']) + radius_servers_count += 1 + fail = False + + if fail: + raise ConfigError('All RADIUS servers are disabled') + + if radius_servers_count > MAX_RADIUS_COUNT: + raise ConfigError(f'Number of RADIUS servers exceeded maximum of {MAX_RADIUS_COUNT}!') + + if sum_timeout > MAX_RADIUS_TIMEOUT: + raise ConfigError('Sum of RADIUS servers timeouts ' + 'has to be less or eq 50 sec') + + verify_vrf(login['radius']) + + if 'source_address' in login['radius']: + ipv4_count = 0 + ipv6_count = 0 + for address in login['radius']['source_address']: + if is_ipv4(address): ipv4_count += 1 + else: ipv6_count += 1 + + if ipv4_count > 1: + raise ConfigError('Only one IPv4 source-address can be set!') + if ipv6_count > 1: + raise ConfigError('Only one IPv6 source-address can be set!') + + if 'tacacs' in login: + tacacs_servers_count: int = 0 + fail = True + for server, server_config in dict_search('tacacs.server', login).items(): + if 'key' not in server_config: + raise ConfigError(f'TACACS server "{server}" requires key!') + if 'disable' not in server_config: + tacacs_servers_count += 1 + fail = False + + if fail: + raise ConfigError('All RADIUS servers are disabled') + + if tacacs_servers_count > MAX_TACACS_COUNT: + raise ConfigError(f'Number of TACACS servers exceeded maximum of {MAX_TACACS_COUNT}!') + + verify_vrf(login['tacacs']) + + if 'max_login_session' in login and 'timeout' not in login: + raise ConfigError('"login timeout" must be configured!') + + return None + + +def generate(login): + # calculate users encrypted password + if 'user' in login: + for user, user_config in login['user'].items(): + tmp = dict_search('authentication.plaintext_password', user_config) + if tmp: + encrypted_password = linux_context.hash(tmp) + login['user'][user]['authentication']['encrypted_password'] = encrypted_password + del login['user'][user]['authentication']['plaintext_password'] + + # Set default commands for re-adding user with encrypted password + del_user_plain = ['system', 'login', 'user', user, 'authentication', 'plaintext-password'] + add_user_encrypt = ['system', 'login', 'user', user, 'authentication', 'encrypted-password'] + + delete_cli_node(del_user_plain) + add_cli_node(add_user_encrypt, value=encrypted_password) + + else: + try: + if get_shadow_password(user) == dict_search('authentication.encrypted_password', user_config): + # If the current encrypted bassword matches the encrypted password + # from the config - do not update it. This will remove the encrypted + # value from the system logs. + # + # The encrypted password will be set only once during the first boot + # after an image upgrade. + del login['user'][user]['authentication']['encrypted_password'] + except: + pass + + ### RADIUS based user authentication + if 'radius' in login: + render(radius_config_file, 'login/pam_radius_auth.conf.j2', login, + permission=0o600, user='root', group='root') + else: + if os.path.isfile(radius_config_file): + os.unlink(radius_config_file) + + ### TACACS+ based user authentication + if 'tacacs' in login: + render(tacacs_pam_config_file, 'login/tacplus_servers.j2', login, + permission=0o644, user='root', group='root') + render(tacacs_nss_config_file, 'login/tacplus_nss.conf.j2', login, + permission=0o644, user='root', group='root') + else: + if os.path.isfile(tacacs_pam_config_file): + os.unlink(tacacs_pam_config_file) + if os.path.isfile(tacacs_nss_config_file): + os.unlink(tacacs_nss_config_file) + + # NSS must always be present on the system + render(nss_config_file, 'login/nsswitch.conf.j2', login, + permission=0o644, user='root', group='root') + + # /etc/security/limits.d/10-vyos.conf + if 'max_login_session' in login: + render(limits_file, 'login/limits.j2', login, + permission=0o644, user='root', group='root') + else: + if os.path.isfile(limits_file): + os.unlink(limits_file) + + if 'timeout' in login: + render(autologout_file, 'login/autologout.j2', login, + permission=0o755, user='root', group='root') + else: + if os.path.isfile(autologout_file): + os.unlink(autologout_file) + + return None + + +def apply(login): + enable_otp = False + if 'user' in login: + for user, user_config in login['user'].items(): + # make new user using vyatta shell and make home directory (-m), + # default group of 100 (users) + command = 'useradd --create-home --no-user-group ' + # check if user already exists: + if user in get_local_users(): + # update existing account + command = 'usermod' + + # all accounts use /bin/vbash + command += ' --shell /bin/vbash' + # we need to use '' quotes when passing formatted data to the shell + # else it will not work as some data parts are lost in translation + tmp = dict_search('authentication.encrypted_password', user_config) + if tmp: command += f" --password '{tmp}'" + + tmp = dict_search('full_name', user_config) + if tmp: command += f" --comment '{tmp}'" + + tmp = dict_search('home_directory', user_config) + if tmp: command += f" --home '{tmp}'" + else: command += f" --home '/home/{user}'" + + command += f' --groups frr,frrvty,vyattacfg,sudo,adm,dip,disk,_kea {user}' + try: + cmd(command) + # we should not rely on the value stored in user_config['home_directory'], as a + # crazy user will choose username root or any other system user which will fail. + # + # XXX: Should we deny using root at all? + home_dir = getpwnam(user).pw_dir + # always re-render SSH keys with appropriate permissions + render(f'{home_dir}/.ssh/authorized_keys', 'login/authorized_keys.j2', + user_config, permission=0o600, + formater=lambda _: _.replace(""", '"'), + user=user, group='users') + except Exception as e: + raise ConfigError(f'Adding user "{user}" raised exception: "{e}"') + + # T5875: ensure UID is properly set on home directory if user is re-added + # the home directory will always exist, as it's created above by --create-home, + # retrieve current owner of home directory and adjust on demand + dir_owner = None + try: + dir_owner = getpwuid(os.stat(home_dir).st_uid).pw_name + except: + pass + + if dir_owner != user: + chown(home_dir, user=user, recursive=True) + + # Generate 2FA/MFA One-Time-Pad configuration + if dict_search('authentication.otp.key', user_config): + enable_otp = True + render(f'{home_dir}/.google_authenticator', 'login/pam_otp_ga.conf.j2', + user_config, permission=0o400, user=user, group='users') + else: + # delete configuration as it's not enabled for the user + if os.path.exists(f'{home_dir}/.google_authenticator'): + os.remove(f'{home_dir}/.google_authenticator') + + # Lock/Unlock local user account + lock_unlock = '--unlock' + if 'disable' in user_config: + lock_unlock = '--lock' + cmd(f'usermod {lock_unlock} {user}') + + if 'rm_users' in login: + for user in login['rm_users']: + try: + # Disable user to prevent re-login + call(f'usermod -s /sbin/nologin {user}') + + # Logout user if he is still logged in + if user in list(set([tmp[0] for tmp in users()])): + print(f'{user} is logged in, forcing logout!') + # re-run command until user is logged out + while run(f'pkill -HUP -u {user}'): + sleep(0.250) + + # Remove user account but leave home directory in place. Re-run + # command until user is removed - userdel might return 8 as + # SSH sessions are not all yet properly cleaned away, thus we + # simply re-run the command until the account wen't away + while run(f'userdel {user}', stderr=DEVNULL): + sleep(0.250) + + except Exception as e: + raise ConfigError(f'Deleting user "{user}" raised exception: {e}') + + # Enable/disable RADIUS in PAM configuration + cmd('pam-auth-update --disable radius-mandatory radius-optional') + if 'radius' in login: + if login['radius'].get('security_mode', '') == 'mandatory': + pam_profile = 'radius-mandatory' + else: + pam_profile = 'radius-optional' + cmd(f'pam-auth-update --enable {pam_profile}') + + # Enable/disable TACACS+ in PAM configuration + cmd('pam-auth-update --disable tacplus-mandatory tacplus-optional') + if 'tacacs' in login: + if login['tacacs'].get('security_mode', '') == 'mandatory': + pam_profile = 'tacplus-mandatory' + else: + pam_profile = 'tacplus-optional' + cmd(f'pam-auth-update --enable {pam_profile}') + + # Enable/disable Google authenticator + cmd('pam-auth-update --disable mfa-google-authenticator') + if enable_otp: + cmd(f'pam-auth-update --enable mfa-google-authenticator') + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_login_banner.py b/src/conf_mode/system_login_banner.py new file mode 100644 index 0000000..923e1bf --- /dev/null +++ b/src/conf_mode/system_login_banner.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit +from copy import deepcopy + +from vyos.config import Config +from vyos.template import render +from vyos.utils.file import write_file +from vyos.version import get_version_data +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +PRELOGIN_FILE = r'/etc/issue' +PRELOGIN_NET_FILE = r'/etc/issue.net' +POSTLOGIN_FILE = r'/etc/motd' + +default_config_data = { + 'issue': 'Welcome to VyOS - \\n \\l\n\n', + 'issue_net': '' +} + +def get_config(config=None): + banner = deepcopy(default_config_data) + banner['version_data'] = get_version_data() + + if config: + conf = config + else: + conf = Config() + base_level = ['system', 'login', 'banner'] + + if not conf.exists(base_level): + return banner + else: + conf.set_level(base_level) + + # Post-Login banner + if conf.exists(['post-login']): + tmp = conf.return_value(['post-login']) + # post-login banner can be empty as well + if tmp: + tmp = tmp.replace('\\n','\n') + tmp = tmp.replace('\\t','\t') + # always add newline character + tmp += '\n' + else: + tmp = '' + + banner['motd'] = tmp + + # Pre-Login banner + if conf.exists(['pre-login']): + tmp = conf.return_value(['pre-login']) + # pre-login banner can be empty as well + if tmp: + tmp = tmp.replace('\\n','\n') + tmp = tmp.replace('\\t','\t') + # always add newline character + tmp += '\n' + else: + tmp = '' + + banner['issue'] = banner['issue_net'] = tmp + + return banner + +def verify(banner): + pass + +def generate(banner): + pass + +def apply(banner): + write_file(PRELOGIN_FILE, banner['issue']) + write_file(PRELOGIN_NET_FILE, banner['issue_net']) + if 'motd' in banner: + write_file(POSTLOGIN_FILE, banner['motd']) + else: + render(POSTLOGIN_FILE, 'login/default_motd.j2', banner, + permission=0o644, user='root', group='root') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_logs.py b/src/conf_mode/system_logs.py new file mode 100644 index 0000000..8ad4875 --- /dev/null +++ b/src/conf_mode/system_logs.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos import ConfigError +from vyos import airbag +from vyos.config import Config +from vyos.logger import syslog +from vyos.template import render +from vyos.utils.dict import dict_search +airbag.enable() + +# path to logrotate configs +logrotate_atop_file = '/etc/logrotate.d/vyos-atop' +logrotate_rsyslog_file = '/etc/logrotate.d/vyos-rsyslog' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['system', 'logs'] + logs_config = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + return logs_config + + +def verify(logs_config): + # Nothing to verify here + pass + + +def generate(logs_config): + # get configuration for logrotate atop + logrotate_atop = dict_search('logrotate.atop', logs_config) + # generate new config file for atop + syslog.debug('Adding logrotate config for atop') + render(logrotate_atop_file, 'logs/logrotate/vyos-atop.j2', logrotate_atop) + + # get configuration for logrotate rsyslog + logrotate_rsyslog = dict_search('logrotate.messages', logs_config) + # generate new config file for rsyslog + syslog.debug('Adding logrotate config for rsyslog') + render(logrotate_rsyslog_file, 'logs/logrotate/vyos-rsyslog.j2', + logrotate_rsyslog) + + +def apply(logs_config): + # No further actions needed + pass + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py new file mode 100644 index 0000000..a84572f --- /dev/null +++ b/src/conf_mode/system_option.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit +from time import sleep + + +from vyos.config import Config +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_interface_exists +from vyos.system import grub_util +from vyos.template import render +from vyos.utils.cpu import get_cpus +from vyos.utils.dict import dict_search +from vyos.utils.file import write_file +from vyos.utils.kernel import check_kmod +from vyos.utils.process import cmd +from vyos.utils.process import is_systemd_service_running +from vyos.utils.network import is_addr_assigned +from vyos.utils.network import is_intf_addr_assigned +from vyos.configdep import set_dependents +from vyos.configdep import call_dependents +from vyos import ConfigError +from vyos import airbag + +airbag.enable() + +curlrc_config = r'/etc/curlrc' +ssh_config = r'/etc/ssh/ssh_config.d/91-vyos-ssh-client-options.conf' +systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target' +usb_autosuspend = r'/etc/udev/rules.d/40-usb-autosuspend.rules' +kernel_dynamic_debug = r'/sys/kernel/debug/dynamic_debug/control' +time_format_to_locale = {'12-hour': 'en_US.UTF-8', '24-hour': 'en_GB.UTF-8'} + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'option'] + options = conf.get_config_dict( + base, key_mangling=('-', '_'), get_first_key=True, with_recursive_defaults=True + ) + + if 'performance' in options: + # Update IPv4/IPv6 and sysctl options after tuned applied it's settings + set_dependents('ip_ipv6', conf) + set_dependents('sysctl', conf) + + return options + + +def verify(options): + if 'http_client' in options: + config = options['http_client'] + if 'source_interface' in config: + verify_interface_exists(options, config['source_interface']) + + if {'source_address', 'source_interface'} <= set(config): + raise ConfigError( + 'Can not define both HTTP source-interface and source-address' + ) + + if 'source_address' in config: + if not is_addr_assigned(config['source_address']): + raise ConfigError('No interface with give address specified!') + + if 'ssh_client' in options: + config = options['ssh_client'] + if 'source_address' in config: + address = config['source_address'] + if not is_addr_assigned(config['source_address']): + raise ConfigError('No interface with address "{address}" configured!') + + if 'source_interface' in config: + # verify_source_interface reuires key 'ifname' + config['ifname'] = config['source_interface'] + verify_source_interface(config) + if 'source_address' in config: + address = config['source_address'] + interface = config['source_interface'] + if not is_intf_addr_assigned(interface, address): + raise ConfigError( + f'Address "{address}" not assigned on interface "{interface}"!' + ) + + if 'kernel' in options: + cpu_vendor = get_cpus()[0]['vendor_id'] + if 'amd_pstate_driver' in options['kernel'] and cpu_vendor != 'AuthenticAMD': + raise ConfigError( + f'AMD pstate driver cannot be used with "{cpu_vendor}" CPU!' + ) + + return None + + +def generate(options): + render(curlrc_config, 'system/curlrc.j2', options) + render(ssh_config, 'system/ssh_config.j2', options) + render(usb_autosuspend, 'system/40_usb_autosuspend.j2', options) + + cmdline_options = [] + if 'kernel' in options: + if 'disable_mitigations' in options['kernel']: + cmdline_options.append('mitigations=off') + if 'disable_power_saving' in options['kernel']: + cmdline_options.append('intel_idle.max_cstate=0 processor.max_cstate=1') + if 'amd_pstate_driver' in options['kernel']: + mode = options['kernel']['amd_pstate_driver'] + cmdline_options.append( + f'initcall_blacklist=acpi_cpufreq_init amd_pstate={mode}' + ) + grub_util.update_kernel_cmdline_options(' '.join(cmdline_options)) + + return None + + +def apply(options): + # System bootup beep + beep_service = 'vyos-beep.service' + if 'startup_beep' in options: + cmd(f'systemctl enable {beep_service}') + else: + cmd(f'systemctl disable {beep_service}') + + # Ctrl-Alt-Delete action + if os.path.exists(systemd_action_file): + os.unlink(systemd_action_file) + if 'ctrl_alt_delete' in options: + if options['ctrl_alt_delete'] == 'reboot': + os.symlink('/lib/systemd/system/reboot.target', systemd_action_file) + elif options['ctrl_alt_delete'] == 'poweroff': + os.symlink('/lib/systemd/system/poweroff.target', systemd_action_file) + + # Configure HTTP client + if 'http_client' not in options: + if os.path.exists(curlrc_config): + os.unlink(curlrc_config) + + # Configure SSH client + if 'ssh_client' not in options: + if os.path.exists(ssh_config): + os.unlink(ssh_config) + + # Reboot system on kernel panic + timeout = '0' + if 'reboot_on_panic' in options: + timeout = '60' + with open('/proc/sys/kernel/panic', 'w') as f: + f.write(timeout) + + # tuned - performance tuning + if 'performance' in options: + cmd('systemctl restart tuned.service') + # wait until daemon has started before sending configuration + while not is_systemd_service_running('tuned.service'): + sleep(0.250) + cmd('tuned-adm profile network-{performance}'.format(**options)) + else: + cmd('systemctl stop tuned.service') + + call_dependents() + + # Keyboard layout - there will be always the default key inside the dict + # but we check for key existence anyway + if 'keyboard_layout' in options: + cmd('loadkeys {keyboard_layout}'.format(**options)) + + # Enable/diable root-partition-auto-resize SystemD service + if 'root_partition_auto_resize' in options: + cmd('systemctl enable root-partition-auto-resize.service') + else: + cmd('systemctl disable root-partition-auto-resize.service') + + # Time format 12|24-hour + if 'time_format' in options: + time_format = time_format_to_locale.get(options['time_format']) + cmd(f'localectl set-locale LC_TIME={time_format}') + + # Reload UDEV, required for USB auto suspend + cmd('udevadm control --reload-rules') + + # Enable/disable dynamic debugging for kernel modules + modules = ['wireguard'] + modules_enabled = dict_search('kernel.debug', options) or [] + for module in modules: + if module in modules_enabled: + check_kmod(module) + write_file(kernel_dynamic_debug, f'module {module} +p') + else: + write_file(kernel_dynamic_debug, f'module {module} -p') + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_proxy.py b/src/conf_mode/system_proxy.py new file mode 100644 index 0000000..079c43e --- /dev/null +++ b/src/conf_mode/system_proxy.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +proxy_def = r'/etc/profile.d/vyos-system-proxy.sh' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'proxy'] + if not conf.exists(base): + return None + + proxy = conf.get_config_dict(base, get_first_key=True) + return proxy + +def verify(proxy): + if not proxy: + return + + if 'url' not in proxy or 'port' not in proxy: + raise ConfigError('Proxy URL and port require a value') + + if ('username' in proxy and 'password' not in proxy) or \ + ('username' not in proxy and 'password' in proxy): + raise ConfigError('Both username and password need to be defined!') + +def generate(proxy): + if not proxy: + if os.path.isfile(proxy_def): + os.unlink(proxy_def) + return + + render(proxy_def, 'system/proxy.j2', proxy, permission=0o755) + +def apply(proxy): + pass + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_sflow.py b/src/conf_mode/system_sflow.py new file mode 100644 index 0000000..41119b4 --- /dev/null +++ b/src/conf_mode/system_sflow.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configverify import verify_vrf +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.network import is_addr_assigned +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +hsflowd_conf_path = '/run/sflow/hsflowd.conf' +systemd_service = 'hsflowd.service' +systemd_override = f'/run/systemd/system/{systemd_service}.d/override.conf' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'sflow'] + if not conf.exists(base): + return None + + sflow = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + return sflow + +def verify(sflow): + if not sflow: + return None + + # Check if configured sflow agent-address exist in the system + if 'agent_address' in sflow: + tmp = sflow['agent_address'] + if not is_addr_assigned(tmp): + raise ConfigError( + f'Configured "sflow agent-address {tmp}" does not exist in the system!' + ) + + # Check if at least one interface is configured + if 'interface' not in sflow: + raise ConfigError( + 'sFlow requires at least one interface to be configured!') + + # Check if at least one server is configured + if 'server' not in sflow: + raise ConfigError('You need to configure at least one sFlow server!') + + verify_vrf(sflow) + return None + +def generate(sflow): + if not sflow: + return None + + render(hsflowd_conf_path, 'sflow/hsflowd.conf.j2', sflow) + render(systemd_override, 'sflow/override.conf.j2', sflow) + # Reload systemd manager configuration + call('systemctl daemon-reload') + +def apply(sflow): + if not sflow: + # Stop flow-accounting daemon and remove configuration file + call(f'systemctl stop {systemd_service}') + if os.path.exists(hsflowd_conf_path): + os.unlink(hsflowd_conf_path) + return + + # Start/reload flow-accounting daemon + call(f'systemctl restart {systemd_service}') + +if __name__ == '__main__': + try: + config = get_config() + verify(config) + generate(config) + apply(config) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_sysctl.py b/src/conf_mode/system_sysctl.py new file mode 100644 index 0000000..f6b0202 --- /dev/null +++ b/src/conf_mode/system_sysctl.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import cmd +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/run/sysctl/99-vyos-sysctl.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'sysctl'] + if not conf.exists(base): + return None + + sysctl = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + return sysctl + +def verify(sysctl): + return None + +def generate(sysctl): + if not sysctl: + if os.path.isfile(config_file): + os.unlink(config_file) + return None + + render(config_file, 'system/sysctl.conf.j2', sysctl) + return None + +def apply(sysctl): + if not sysctl: + return None + + # We silently ignore all errors + # See: https://bugzilla.redhat.com/show_bug.cgi?id=1264080 + cmd(f'sysctl -f {config_file}') + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_syslog.py b/src/conf_mode/system_syslog.py new file mode 100644 index 0000000..eb2f02e --- /dev/null +++ b/src/conf_mode/system_syslog.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdict import is_node_changed +from vyos.configverify import verify_vrf +from vyos.utils.process import call +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +rsyslog_conf = '/etc/rsyslog.d/00-vyos.conf' +logrotate_conf = '/etc/logrotate.d/vyos-rsyslog' +systemd_override = r'/run/systemd/system/rsyslog.service.d/override.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'syslog'] + if not conf.exists(base): + return None + + syslog = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + syslog.update({ 'logrotate' : logrotate_conf }) + + tmp = is_node_changed(conf, base + ['vrf']) + if tmp: syslog.update({'restart_required': {}}) + + syslog = conf.merge_defaults(syslog, recursive=True) + if syslog.from_defaults(['global']): + del syslog['global'] + + if ( + 'global' in syslog + and 'preserve_fqdn' in syslog['global'] + and conf.exists(['system', 'host-name']) + and conf.exists(['system', 'domain-name']) + ): + hostname = conf.return_value(['system', 'host-name']) + domain = conf.return_value(['system', 'domain-name']) + fqdn = f'{hostname}.{domain}' + syslog['global']['local_host_name'] = fqdn + + return syslog + +def verify(syslog): + if not syslog: + return None + + if 'host' in syslog: + for host, host_options in syslog['host'].items(): + if 'protocol' in host_options and host_options['protocol'] == 'udp': + if 'format' in host_options and 'octet_counted' in host_options['format']: + Warning(f'Syslog UDP transport for "{host}" should not use octet-counted format!') + + verify_vrf(syslog) + +def generate(syslog): + if not syslog: + if os.path.exists(rsyslog_conf): + os.unlink(rsyslog_conf) + if os.path.exists(logrotate_conf): + os.unlink(logrotate_conf) + + return None + + render(rsyslog_conf, 'rsyslog/rsyslog.conf.j2', syslog) + render(systemd_override, 'rsyslog/override.conf.j2', syslog) + render(logrotate_conf, 'rsyslog/logrotate.j2', syslog) + + # Reload systemd manager configuration + call('systemctl daemon-reload') + return None + +def apply(syslog): + systemd_socket = 'syslog.socket' + systemd_service = 'syslog.service' + if not syslog: + call(f'systemctl stop {systemd_service} {systemd_socket}') + return None + + # we need to restart the service if e.g. the VRF name changed + systemd_action = 'reload-or-restart' + if 'restart_required' in syslog: + systemd_action = 'restart' + + call(f'systemctl {systemd_action} {systemd_service}') + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_task-scheduler.py b/src/conf_mode/system_task-scheduler.py new file mode 100644 index 0000000..129be5d --- /dev/null +++ b/src/conf_mode/system_task-scheduler.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2017 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +import os +import re +import sys + +from vyos.config import Config +from vyos import ConfigError + +from vyos import airbag +airbag.enable() + +crontab_file = "/etc/cron.d/vyos-crontab" + + +def format_task(minute="*", hour="*", day="*", dayofweek="*", month="*", user="root", rawspec=None, command=""): + fmt_full = "{minute} {hour} {day} {month} {dayofweek} {user} {command}\n" + fmt_raw = "{spec} {user} {command}\n" + + if rawspec is None: + s = fmt_full.format(minute=minute, hour=hour, day=day, + dayofweek=dayofweek, month=month, command=command, user=user) + else: + s = fmt_raw.format(spec=rawspec, user=user, command=command) + + return s + +def split_interval(s): + result = re.search(r"(\d+)([mdh]?)", s) + value = int(result.group(1)) + suffix = result.group(2) + return( (value, suffix) ) + +def make_command(executable, arguments): + if arguments: + return("sg vyattacfg \"{0} {1}\"".format(executable, arguments)) + else: + return("sg vyattacfg \"{0}\"".format(executable)) + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + conf.set_level("system task-scheduler task") + task_names = conf.list_nodes("") + tasks = [] + + for name in task_names: + interval = conf.return_value("{0} interval".format(name)) + spec = conf.return_value("{0} crontab-spec".format(name)) + executable = conf.return_value("{0} executable path".format(name)) + args = conf.return_value("{0} executable arguments".format(name)) + task = { + "name": name, + "interval": interval, + "spec": spec, + "executable": executable, + "args": args + } + tasks.append(task) + + return tasks + +def verify(tasks): + for task in tasks: + if not task["interval"] and not task["spec"]: + raise ConfigError("Invalid task {0}: must define either interval or crontab-spec".format(task["name"])) + + if task["interval"]: + if task["spec"]: + raise ConfigError("Invalid task {0}: cannot use interval and crontab-spec at the same time".format(task["name"])) + + if not re.match(r"^\d+[mdh]?$", task["interval"]): + raise(ConfigError("Invalid interval {0} in task {1}: interval should be a number optionally followed by m, h, or d".format(task["name"], task["interval"]))) + else: + # Check if values are within allowed range + value, suffix = split_interval(task["interval"]) + + if not suffix or suffix == "m": + if value > 60: + raise ConfigError("Invalid task {0}: interval in minutes must not exceed 60".format(task["name"])) + elif suffix == "h": + if value > 24: + raise ConfigError("Invalid task {0}: interval in hours must not exceed 24".format(task["name"])) + elif suffix == "d": + if value > 31: + raise ConfigError("Invalid task {0}: interval in days must not exceed 31".format(task["name"])) + + if not task["executable"]: + raise ConfigError("Invalid task {0}: executable is not defined".format(task["name"])) + else: + # Check if executable exists and is executable + if not (os.path.isfile(task["executable"]) and os.access(task["executable"], os.X_OK)): + raise ConfigError("Invalid task {0}: file {1} does not exist or is not executable".format(task["name"], task["executable"])) + +def generate(tasks): + crontab_header = "### Generated by vyos-update-crontab.py ###\n" + if len(tasks) == 0: + if os.path.exists(crontab_file): + os.remove(crontab_file) + else: + pass + else: + crontab_lines = [] + for task in tasks: + command = make_command(task["executable"], task["args"]) + if task["spec"]: + line = format_task(command=command, rawspec=task["spec"]) + else: + value, suffix = split_interval(task["interval"]) + if not suffix or suffix == "m": + line = format_task(command=command, minute="*/{0}".format(value)) + elif suffix == "h": + line = format_task(command=command, minute="0", hour="*/{0}".format(value)) + elif suffix == "d": + line = format_task(command=command, minute="0", hour="0", day="*/{0}".format(value)) + crontab_lines.append(line) + + with open(crontab_file, 'w') as f: + f.write(crontab_header) + f.writelines(crontab_lines) + +def apply(config): + # No daemon restarts etc. needed for cron + pass + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) diff --git a/src/conf_mode/system_timezone.py b/src/conf_mode/system_timezone.py new file mode 100644 index 0000000..39770fd --- /dev/null +++ b/src/conf_mode/system_timezone.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys + +from copy import deepcopy +from vyos.config import Config +from vyos import ConfigError +from vyos.utils.process import call + +from vyos import airbag +airbag.enable() + +default_config_data = { + 'name': 'UTC' +} + +def get_config(config=None): + tz = deepcopy(default_config_data) + if config: + conf = config + else: + conf = Config() + if conf.exists('system time-zone'): + tz['name'] = conf.return_value('system time-zone') + + return tz + +def verify(tz): + pass + +def generate(tz): + pass + +def apply(tz): + call('/usr/bin/timedatectl set-timezone {}'.format(tz['name'])) + call('systemctl restart rsyslog') + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) diff --git a/src/conf_mode/system_update-check.py b/src/conf_mode/system_update-check.py new file mode 100644 index 0000000..71ac13e --- /dev/null +++ b/src/conf_mode/system_update-check.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json + +from pathlib import Path +from sys import exit + +from vyos.config import Config +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +base = ['system', 'update-check'] +service_name = 'vyos-system-update' +service_conf = Path(f'/run/{service_name}.conf') +motd_file = Path('/run/motd.d/10-vyos-update') + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + if not conf.exists(base): + return None + + config = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + return config + + +def verify(config): + # bail out early - looks like removal from running config + if config is None: + return + + if 'url' not in config: + raise ConfigError('URL is required!') + + +def generate(config): + # bail out early - looks like removal from running config + if config is None: + # Remove old config and return + service_conf.unlink(missing_ok=True) + # MOTD used in /run/motd.d/10-update + motd_file.unlink(missing_ok=True) + return None + + # Write configuration file + conf_json = json.dumps(config, indent=4) + service_conf.write_text(conf_json) + + return None + + +def apply(config): + if config: + if 'auto_check' in config: + call(f'systemctl restart {service_name}.service') + else: + call(f'systemctl stop {service_name}.service') + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system_wireless.py b/src/conf_mode/system_wireless.py new file mode 100644 index 0000000..e0ca0ab --- /dev/null +++ b/src/conf_mode/system_wireless.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.config import Config +from vyos.configdep import set_dependents +from vyos.configdep import call_dependents +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['system', 'wireless'] + interface_base = ['interfaces', 'wireless'] + + wireless = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) + + + if conf.exists(interface_base): + wireless['interfaces'] = conf.list_nodes(interface_base) + for interface in wireless['interfaces']: + set_dependents('wireless', conf, interface) + + return wireless + +def verify(wireless): + pass + +def generate(wireless): + pass + +def apply(wireless): + if 'interfaces' in wireless: + call_dependents() + pass + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py new file mode 100644 index 0000000..ca0c365 --- /dev/null +++ b/src/conf_mode/vpn_ipsec.py @@ -0,0 +1,745 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import ipaddress +import os +import re +import jmespath + +from sys import exit +from time import sleep +from ipaddress import ip_address +from netaddr import IPNetwork +from netaddr import IPRange + +from vyos.config import Config +from vyos.config import config_dict_merge +from vyos.configdep import set_dependents +from vyos.configdep import call_dependents +from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_node_changed +from vyos.configverify import verify_interface_exists +from vyos.configverify import dynamic_interface_pattern +from vyos.defaults import directories +from vyos.ifconfig import Interface +from vyos.pki import encode_public_key +from vyos.pki import load_private_key +from vyos.pki import wrap_certificate +from vyos.pki import wrap_crl +from vyos.pki import wrap_public_key +from vyos.pki import wrap_private_key +from vyos.template import ip_from_cidr +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 +from vyos.template import render +from vyos.utils.network import is_ipv6_link_local +from vyos.utils.network import interface_exists +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_args +from vyos.utils.process import call +from vyos.utils.vti_updown_db import vti_updown_db_exists +from vyos.utils.vti_updown_db import open_vti_updown_db_for_create_or_update +from vyos.utils.vti_updown_db import remove_vti_updown_db +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +dhcp_wait_attempts = 2 +dhcp_wait_sleep = 1 + +swanctl_dir = '/etc/swanctl' +charon_conf = '/etc/strongswan.d/charon.conf' +charon_dhcp_conf = '/etc/strongswan.d/charon/dhcp.conf' +charon_radius_conf = '/etc/strongswan.d/charon/eap-radius.conf' +interface_conf = '/etc/strongswan.d/interfaces_use.conf' +swanctl_conf = f'{swanctl_dir}/swanctl.conf' + +default_install_routes = 'yes' + +vici_socket = '/var/run/charon.vici' + +CERT_PATH = f'{swanctl_dir}/x509/' +PUBKEY_PATH = f'{swanctl_dir}/pubkey/' +KEY_PATH = f'{swanctl_dir}/private/' +CA_PATH = f'{swanctl_dir}/x509ca/' +CRL_PATH = f'{swanctl_dir}/x509crl/' + +DHCP_HOOK_IFLIST = '/tmp/ipsec_dhcp_interfaces' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['vpn', 'ipsec'] + l2tp_base = ['vpn', 'l2tp', 'remote-access', 'ipsec-settings'] + if not conf.exists(base): + return None + + # retrieve common dictionary keys + ipsec = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_pki=True) + + # We have to cleanup the default dict, as default values could + # enable features which are not explicitly enabled on the + # CLI. E.g. dead-peer-detection defaults should not be injected + # unless the feature is explicitly opted in to by setting the + # top-level node + default_values = conf.get_config_defaults(**ipsec.kwargs, recursive=True) + + if 'ike_group' in ipsec: + for name, ike in ipsec['ike_group'].items(): + if 'dead_peer_detection' not in ike: + del default_values['ike_group'][name]['dead_peer_detection'] + + ipsec = config_dict_merge(default_values, ipsec) + + ipsec['dhcp_interfaces'] = set() + ipsec['enabled_vti_interfaces'] = set() + ipsec['persistent_vti_interfaces'] = set() + ipsec['dhcp_no_address'] = {} + ipsec['install_routes'] = 'no' if conf.exists(base + ["options", "disable-route-autoinstall"]) else default_install_routes + ipsec['interface_change'] = leaf_node_changed(conf, base + ['interface']) + ipsec['nhrp_exists'] = conf.exists(['protocols', 'nhrp', 'tunnel']) + + if ipsec['nhrp_exists']: + set_dependents('nhrp', conf) + + tmp = conf.get_config_dict(l2tp_base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True) + if tmp: + ipsec['l2tp'] = conf.merge_defaults(tmp, recursive=True) + ipsec['l2tp_outside_address'] = conf.return_value(['vpn', 'l2tp', 'remote-access', 'outside-address']) + ipsec['l2tp_ike_default'] = 'aes256-sha1-modp1024,3des-sha1-modp1024' + ipsec['l2tp_esp_default'] = 'aes256-sha1,3des-sha1' + + # Collect the interface dicts for any refernced VTI interfaces in + # case we need to bring the interface up + ipsec['vti_interface_dicts'] = {} + + if 'site_to_site' in ipsec and 'peer' in ipsec['site_to_site']: + for peer, peer_conf in ipsec['site_to_site']['peer'].items(): + if 'vti' in peer_conf: + if 'bind' in peer_conf['vti']: + vti_interface = peer_conf['vti']['bind'] + if vti_interface not in ipsec['vti_interface_dicts']: + _, vti = get_interface_dict(conf, ['interfaces', 'vti'], vti_interface) + ipsec['vti_interface_dicts'][vti_interface] = vti + + if 'remote_access' in ipsec: + if 'connection' in ipsec['remote_access']: + for name, ra_conf in ipsec['remote_access']['connection'].items(): + if 'bind' in ra_conf: + vti_interface = ra_conf['bind'] + if vti_interface not in ipsec['vti_interface_dicts']: + _, vti = get_interface_dict(conf, ['interfaces', 'vti'], vti_interface) + ipsec['vti_interface_dicts'][vti_interface] = vti + + return ipsec + +def get_dhcp_address(iface): + addresses = Interface(iface).get_addr() + if not addresses: + return None + for address in addresses: + if not is_ipv6_link_local(address): + return ip_from_cidr(address) + return None + +def verify_pki_x509(pki, x509_conf): + if not pki or 'ca' not in pki or 'certificate' not in pki: + raise ConfigError(f'PKI is not configured') + + cert_name = x509_conf['certificate'] + + for ca_cert_name in x509_conf['ca_certificate']: + if not dict_search_args(pki, 'ca', ca_cert_name, 'certificate'): + raise ConfigError(f'Missing CA certificate on specified PKI CA certificate "{ca_cert_name}"') + + if not dict_search_args(pki, 'certificate', cert_name, 'certificate'): + raise ConfigError(f'Missing certificate on specified PKI certificate "{cert_name}"') + + if not dict_search_args(pki, 'certificate', cert_name, 'private', 'key'): + raise ConfigError(f'Missing private key on specified PKI certificate "{cert_name}"') + + return True + +def verify_pki_rsa(pki, rsa_conf): + if not pki or 'key_pair' not in pki: + raise ConfigError(f'PKI is not configured') + + local_key = rsa_conf['local_key'] + remote_key = rsa_conf['remote_key'] + + if not dict_search_args(pki, 'key_pair', local_key, 'private', 'key'): + raise ConfigError(f'Missing private key on specified local-key "{local_key}"') + + if not dict_search_args(pki, 'key_pair', remote_key, 'public', 'key'): + raise ConfigError(f'Missing public key on specified remote-key "{remote_key}"') + + return True + +def verify(ipsec): + if not ipsec: + return None + + if 'authentication' in ipsec: + if 'psk' in ipsec['authentication']: + for psk, psk_config in ipsec['authentication']['psk'].items(): + if 'id' not in psk_config or 'secret' not in psk_config: + raise ConfigError(f'Authentication psk "{psk}" missing "id" or "secret"') + + if 'interface' in ipsec: + tmp = re.compile(dynamic_interface_pattern) + for interface in ipsec['interface']: + # exclude check interface for dynamic interfaces + if tmp.match(interface): + verify_interface_exists(ipsec, interface, warning_only=True) + else: + verify_interface_exists(ipsec, interface) + + if 'l2tp' in ipsec: + if 'esp_group' in ipsec['l2tp']: + if 'esp_group' not in ipsec or ipsec['l2tp']['esp_group'] not in ipsec['esp_group']: + raise ConfigError(f"Invalid esp-group on L2TP remote-access config") + + if 'ike_group' in ipsec['l2tp']: + if 'ike_group' not in ipsec or ipsec['l2tp']['ike_group'] not in ipsec['ike_group']: + raise ConfigError(f"Invalid ike-group on L2TP remote-access config") + + if 'authentication' not in ipsec['l2tp']: + raise ConfigError(f'Missing authentication settings on L2TP remote-access config') + + if 'mode' not in ipsec['l2tp']['authentication']: + raise ConfigError(f'Missing authentication mode on L2TP remote-access config') + + if not ipsec['l2tp_outside_address']: + raise ConfigError(f'Missing outside-address on L2TP remote-access config') + + if ipsec['l2tp']['authentication']['mode'] == 'pre-shared-secret': + if 'pre_shared_secret' not in ipsec['l2tp']['authentication']: + raise ConfigError(f'Missing pre shared secret on L2TP remote-access config') + + if ipsec['l2tp']['authentication']['mode'] == 'x509': + if 'x509' not in ipsec['l2tp']['authentication']: + raise ConfigError(f'Missing x509 settings on L2TP remote-access config') + + x509 = ipsec['l2tp']['authentication']['x509'] + + if 'ca_certificate' not in x509 or 'certificate' not in x509: + raise ConfigError(f'Missing x509 certificates on L2TP remote-access config') + + verify_pki_x509(ipsec['pki'], x509) + + if 'profile' in ipsec: + for profile, profile_conf in ipsec['profile'].items(): + if 'esp_group' in profile_conf: + if 'esp_group' not in ipsec or profile_conf['esp_group'] not in ipsec['esp_group']: + raise ConfigError(f"Invalid esp-group on {profile} profile") + else: + raise ConfigError(f"Missing esp-group on {profile} profile") + + if 'ike_group' in profile_conf: + if 'ike_group' not in ipsec or profile_conf['ike_group'] not in ipsec['ike_group']: + raise ConfigError(f"Invalid ike-group on {profile} profile") + else: + raise ConfigError(f"Missing ike-group on {profile} profile") + + if 'authentication' not in profile_conf: + raise ConfigError(f"Missing authentication on {profile} profile") + + if 'remote_access' in ipsec: + if 'connection' in ipsec['remote_access']: + for name, ra_conf in ipsec['remote_access']['connection'].items(): + if 'local_address' not in ra_conf and 'dhcp_interface' not in ra_conf: + raise ConfigError(f"Missing local-address or dhcp-interface on remote-access connection {name}") + + if 'dhcp_interface' in ra_conf: + dhcp_interface = ra_conf['dhcp_interface'] + + verify_interface_exists(ipsec, dhcp_interface) + dhcp_base = directories['isc_dhclient_dir'] + + if not os.path.exists(f'{dhcp_base}/dhclient_{dhcp_interface}.conf'): + raise ConfigError(f"Invalid dhcp-interface on remote-access connection {name}") + + if 'disable' not in ra_conf: + ipsec['dhcp_interfaces'].add(dhcp_interface) + + address = get_dhcp_address(dhcp_interface) + count = 0 + while not address and count < dhcp_wait_attempts: + address = get_dhcp_address(dhcp_interface) + count += 1 + sleep(dhcp_wait_sleep) + + if not address: + ipsec['dhcp_no_address'][f'ra_{name}'] = dhcp_interface + print(f"Failed to get address from dhcp-interface on remote-access connection {name} -- skipped") + continue + + if 'esp_group' in ra_conf: + if 'esp_group' not in ipsec or ra_conf['esp_group'] not in ipsec['esp_group']: + raise ConfigError(f"Invalid esp-group on {name} remote-access config") + else: + raise ConfigError(f"Missing esp-group on {name} remote-access config") + + if 'ike_group' in ra_conf: + if 'ike_group' not in ipsec or ra_conf['ike_group'] not in ipsec['ike_group']: + raise ConfigError(f"Invalid ike-group on {name} remote-access config") + + ike = ra_conf['ike_group'] + if dict_search(f'ike_group.{ike}.key_exchange', ipsec) != 'ikev2': + raise ConfigError('IPsec remote-access connections requires IKEv2!') + + else: + raise ConfigError(f"Missing ike-group on {name} remote-access config") + + if 'authentication' not in ra_conf: + raise ConfigError(f"Missing authentication on {name} remote-access config") + + if ra_conf['authentication']['server_mode'] == 'x509': + if 'x509' not in ra_conf['authentication']: + raise ConfigError(f"Missing x509 settings on {name} remote-access config") + + x509 = ra_conf['authentication']['x509'] + + if 'ca_certificate' not in x509 or 'certificate' not in x509: + raise ConfigError(f"Missing x509 certificates on {name} remote-access config") + + verify_pki_x509(ipsec['pki'], x509) + elif ra_conf['authentication']['server_mode'] == 'pre-shared-secret': + if 'pre_shared_secret' not in ra_conf['authentication']: + raise ConfigError(f"Missing pre-shared-key on {name} remote-access config") + + if 'client_mode' not in ra_conf['authentication']: + raise ConfigError('Client authentication method is required!') + + if dict_search('authentication.client_mode', ra_conf) == 'eap-radius': + if dict_search('remote_access.radius.server', ipsec) == None: + raise ConfigError('RADIUS authentication requires at least one server') + + if 'bind' in ra_conf: + vti_interface = ra_conf['bind'] + if not interface_exists(vti_interface): + raise ConfigError(f'VTI interface {vti_interface} for remote-access connection {name} does not exist!') + + if 'disable' not in ra_conf: + ipsec['enabled_vti_interfaces'].add(vti_interface) + # remote access VPN interfaces are always up regardless of whether clients are connected + ipsec['persistent_vti_interfaces'].add(vti_interface) + + if 'pool' in ra_conf: + if {'dhcp', 'radius'} <= set(ra_conf['pool']): + raise ConfigError(f'Can not use both DHCP and RADIUS for address allocation '\ + f'at the same time for "{name}"!') + + if 'dhcp' in ra_conf['pool'] and len(ra_conf['pool']) > 1: + raise ConfigError(f'Can not use DHCP and a predefined address pool for "{name}"!') + + if 'radius' in ra_conf['pool'] and len(ra_conf['pool']) > 1: + raise ConfigError(f'Can not use RADIUS and a predefined address pool for "{name}"!') + + for pool in ra_conf['pool']: + if pool == 'dhcp': + if dict_search('remote_access.dhcp.server', ipsec) == None: + raise ConfigError('IPsec DHCP server is not configured!') + elif pool == 'radius': + if dict_search('remote_access.radius.server', ipsec) == None: + raise ConfigError('IPsec RADIUS server is not configured!') + + if dict_search('authentication.client_mode', ra_conf) != 'eap-radius': + raise ConfigError('RADIUS IP pool requires eap-radius client authentication!') + + elif 'pool' not in ipsec['remote_access'] or pool not in ipsec['remote_access']['pool']: + raise ConfigError(f'Requested pool "{pool}" does not exist!') + + if 'pool' in ipsec['remote_access']: + pool_networks = [] + for pool, pool_config in ipsec['remote_access']['pool'].items(): + if 'prefix' not in pool_config and 'range' not in pool_config: + raise ConfigError(f'Mandatory prefix or range must be specified for pool "{pool}"!') + + if 'prefix' in pool_config and 'range' in pool_config: + raise ConfigError(f'Only one of prefix or range can be specified for pool "{pool}"!') + + if 'prefix' in pool_config: + range_is_ipv4 = is_ipv4(pool_config['prefix']) + range_is_ipv6 = is_ipv6(pool_config['prefix']) + + net = IPNetwork(pool_config['prefix']) + start = net.first + stop = net.last + for network in pool_networks: + if start in network or stop in network: + raise ConfigError(f'Prefix for pool "{pool}" is already part of another pool\'s range!') + + tmp = IPRange(start, stop) + pool_networks.append(tmp) + + if 'range' in pool_config: + range_config = pool_config['range'] + if not {'start', 'stop'} <= set(range_config.keys()): + raise ConfigError(f'Range start and stop address must be defined for pool "{pool}"!') + + range_both_ipv4 = is_ipv4(range_config['start']) and is_ipv4(range_config['stop']) + range_both_ipv6 = is_ipv6(range_config['start']) and is_ipv6(range_config['stop']) + + if not (range_both_ipv4 or range_both_ipv6): + raise ConfigError(f'Range start and stop must be of the same address family for pool "{pool}"!') + + if ip_address(range_config['stop']) < ip_address(range_config['start']): + raise ConfigError(f'Range stop address must be greater or equal\n' \ + 'to the range\'s start address for pool "{pool}"!') + + range_is_ipv4 = is_ipv4(range_config['start']) + range_is_ipv6 = is_ipv6(range_config['start']) + + start = range_config['start'] + stop = range_config['stop'] + for network in pool_networks: + if start in network: + raise ConfigError(f'Range "{range}" start address "{start}" already part of another pool\'s range!') + if stop in network: + raise ConfigError(f'Range "{range}" stop address "{stop}" already part of another pool\'s range!') + + tmp = IPRange(start, stop) + pool_networks.append(tmp) + + if 'name_server' in pool_config: + if len(pool_config['name_server']) > 2: + raise ConfigError(f'Only two name-servers are supported for remote-access pool "{pool}"!') + + for ns in pool_config['name_server']: + v4_addr_and_ns = is_ipv4(ns) and not range_is_ipv4 + v6_addr_and_ns = is_ipv6(ns) and not range_is_ipv6 + if v4_addr_and_ns or v6_addr_and_ns: + raise ConfigError('Must use both IPv4 or IPv6 addresses for pool prefix/range and name-server addresses!') + + if 'exclude' in pool_config: + for exclude in pool_config['exclude']: + v4_addr_and_exclude = is_ipv4(exclude) and not range_is_ipv4 + v6_addr_and_exclude = is_ipv6(exclude) and not range_is_ipv6 + if v4_addr_and_exclude or v6_addr_and_exclude: + raise ConfigError('Must use both IPv4 or IPv6 addresses for pool prefix/range and exclude prefixes!') + + if 'radius' in ipsec['remote_access'] and 'server' in ipsec['remote_access']['radius']: + for server, server_config in ipsec['remote_access']['radius']['server'].items(): + if 'key' not in server_config: + raise ConfigError(f'Missing RADIUS secret key for server "{server}"') + + if 'site_to_site' in ipsec and 'peer' in ipsec['site_to_site']: + for peer, peer_conf in ipsec['site_to_site']['peer'].items(): + has_default_esp = False + # Peer name it is swanctl connection name and shouldn't contain dots or colons, T4118 + if bool(re.search(':|\.', peer)): + raise ConfigError(f'Incorrect peer name "{peer}" ' + f'Peer name can contain alpha-numeric letters, hyphen and underscore') + + if 'remote_address' not in peer_conf: + print(f'You should set correct remote-address "peer {peer} remote-address x.x.x.x"\n') + + if 'default_esp_group' in peer_conf: + has_default_esp = True + if 'esp_group' not in ipsec or peer_conf['default_esp_group'] not in ipsec['esp_group']: + raise ConfigError(f"Invalid esp-group on site-to-site peer {peer}") + + if 'ike_group' in peer_conf: + if 'ike_group' not in ipsec or peer_conf['ike_group'] not in ipsec['ike_group']: + raise ConfigError(f"Invalid ike-group on site-to-site peer {peer}") + else: + raise ConfigError(f"Missing ike-group on site-to-site peer {peer}") + + if 'authentication' not in peer_conf or 'mode' not in peer_conf['authentication']: + raise ConfigError(f"Missing authentication on site-to-site peer {peer}") + + if {'id', 'use_x509_id'} <= set(peer_conf['authentication']): + raise ConfigError(f"Manually set peer id and use-x509-id are mutually exclusive!") + + if peer_conf['authentication']['mode'] == 'x509': + if 'x509' not in peer_conf['authentication']: + raise ConfigError(f"Missing x509 settings on site-to-site peer {peer}") + + x509 = peer_conf['authentication']['x509'] + + if 'ca_certificate' not in x509 or 'certificate' not in x509: + raise ConfigError(f"Missing x509 certificates on site-to-site peer {peer}") + + verify_pki_x509(ipsec['pki'], x509) + elif peer_conf['authentication']['mode'] == 'rsa': + if 'rsa' not in peer_conf['authentication']: + raise ConfigError(f"Missing RSA settings on site-to-site peer {peer}") + + rsa = peer_conf['authentication']['rsa'] + + if 'local_key' not in rsa: + raise ConfigError(f"Missing RSA local-key on site-to-site peer {peer}") + + if 'remote_key' not in rsa: + raise ConfigError(f"Missing RSA remote-key on site-to-site peer {peer}") + + verify_pki_rsa(ipsec['pki'], rsa) + + if 'local_address' not in peer_conf and 'dhcp_interface' not in peer_conf: + raise ConfigError(f"Missing local-address or dhcp-interface on site-to-site peer {peer}") + + if 'dhcp_interface' in peer_conf: + dhcp_interface = peer_conf['dhcp_interface'] + + verify_interface_exists(ipsec, dhcp_interface) + dhcp_base = directories['isc_dhclient_dir'] + + if not os.path.exists(f'{dhcp_base}/dhclient_{dhcp_interface}.conf'): + raise ConfigError(f"Invalid dhcp-interface on site-to-site peer {peer}") + + if 'disable' not in peer_conf: + ipsec['dhcp_interfaces'].add(dhcp_interface) + + address = get_dhcp_address(dhcp_interface) + count = 0 + while not address and count < dhcp_wait_attempts: + address = get_dhcp_address(dhcp_interface) + count += 1 + sleep(dhcp_wait_sleep) + + if not address: + ipsec['dhcp_no_address'][f'peer_{peer}'] = dhcp_interface + print(f"Failed to get address from dhcp-interface on site-to-site peer {peer} -- skipped") + continue + + if 'vti' in peer_conf: + if 'local_address' in peer_conf and 'dhcp_interface' in peer_conf: + raise ConfigError(f"A single local-address or dhcp-interface is required when using VTI on site-to-site peer {peer}") + + if 'bind' in peer_conf['vti']: + vti_interface = peer_conf['vti']['bind'] + if not interface_exists(vti_interface): + raise ConfigError(f'VTI interface {vti_interface} for site-to-site peer {peer} does not exist!') + if 'disable' not in peer_conf: + ipsec['enabled_vti_interfaces'].add(vti_interface) + + if 'vti' not in peer_conf and 'tunnel' not in peer_conf: + raise ConfigError(f"No VTI or tunnel specified on site-to-site peer {peer}") + + if 'tunnel' in peer_conf: + for tunnel, tunnel_conf in peer_conf['tunnel'].items(): + if 'esp_group' not in tunnel_conf and not has_default_esp: + raise ConfigError(f"Missing esp-group on tunnel {tunnel} for site-to-site peer {peer}") + + esp_group_name = tunnel_conf['esp_group'] if 'esp_group' in tunnel_conf else peer_conf['default_esp_group'] + + if esp_group_name not in ipsec['esp_group']: + raise ConfigError(f"Invalid esp-group on tunnel {tunnel} for site-to-site peer {peer}") + + esp_group = ipsec['esp_group'][esp_group_name] + + if 'mode' in esp_group and esp_group['mode'] == 'transport': + if 'protocol' in tunnel_conf and ((peer in ['any', '0.0.0.0']) or ('local_address' not in peer_conf or peer_conf['local_address'] in ['any', '0.0.0.0'])): + raise ConfigError(f"Fixed local-address or peer required when a protocol is defined with ESP transport mode on tunnel {tunnel} for site-to-site peer {peer}") + + if ('local' in tunnel_conf and 'prefix' in tunnel_conf['local']) or ('remote' in tunnel_conf and 'prefix' in tunnel_conf['remote']): + raise ConfigError(f"Local/remote prefix cannot be used with ESP transport mode on tunnel {tunnel} for site-to-site peer {peer}") + +def cleanup_pki_files(): + for path in [CERT_PATH, CA_PATH, CRL_PATH, KEY_PATH, PUBKEY_PATH]: + if not os.path.exists(path): + continue + for file in os.listdir(path): + file_path = os.path.join(path, file) + if os.path.isfile(file_path): + os.unlink(file_path) + +def generate_pki_files_x509(pki, x509_conf): + for ca_cert_name in x509_conf['ca_certificate']: + ca_cert_data = dict_search_args(pki, 'ca', ca_cert_name, 'certificate') + ca_cert_crls = dict_search_args(pki, 'ca', ca_cert_name, 'crl') or [] + crl_index = 1 + + with open(os.path.join(CA_PATH, f'{ca_cert_name}.pem'), 'w') as f: + f.write(wrap_certificate(ca_cert_data)) + + for crl in ca_cert_crls: + with open(os.path.join(CRL_PATH, f'{ca_cert_name}_{crl_index}.pem'), 'w') as f: + f.write(wrap_crl(crl)) + crl_index += 1 + + cert_name = x509_conf['certificate'] + cert_data = dict_search_args(pki, 'certificate', cert_name, 'certificate') + key_data = dict_search_args(pki, 'certificate', cert_name, 'private', 'key') + protected = 'passphrase' in x509_conf + + with open(os.path.join(CERT_PATH, f'{cert_name}.pem'), 'w') as f: + f.write(wrap_certificate(cert_data)) + + with open(os.path.join(KEY_PATH, f'x509_{cert_name}.pem'), 'w') as f: + f.write(wrap_private_key(key_data, protected)) + +def generate_pki_files_rsa(pki, rsa_conf): + local_key_name = rsa_conf['local_key'] + local_key_data = dict_search_args(pki, 'key_pair', local_key_name, 'private', 'key') + protected = 'passphrase' in rsa_conf + remote_key_name = rsa_conf['remote_key'] + remote_key_data = dict_search_args(pki, 'key_pair', remote_key_name, 'public', 'key') + + local_key = load_private_key(local_key_data, rsa_conf['passphrase'] if protected else None) + + with open(os.path.join(KEY_PATH, f'rsa_{local_key_name}.pem'), 'w') as f: + f.write(wrap_private_key(local_key_data, protected)) + + with open(os.path.join(PUBKEY_PATH, f'{local_key_name}.pem'), 'w') as f: + f.write(encode_public_key(local_key.public_key())) + + with open(os.path.join(PUBKEY_PATH, f'{remote_key_name}.pem'), 'w') as f: + f.write(wrap_public_key(remote_key_data)) + +def generate(ipsec): + cleanup_pki_files() + + if not ipsec: + for config_file in [charon_dhcp_conf, charon_radius_conf, interface_conf, swanctl_conf]: + if os.path.isfile(config_file): + os.unlink(config_file) + render(charon_conf, 'ipsec/charon.j2', {'install_routes': default_install_routes}) + return + + if ipsec['dhcp_interfaces']: + with open(DHCP_HOOK_IFLIST, 'w') as f: + f.write(" ".join(ipsec['dhcp_interfaces'])) + elif os.path.exists(DHCP_HOOK_IFLIST): + os.unlink(DHCP_HOOK_IFLIST) + + for path in [swanctl_dir, CERT_PATH, CA_PATH, CRL_PATH, PUBKEY_PATH]: + if not os.path.exists(path): + os.mkdir(path, mode=0o755) + + if not os.path.exists(KEY_PATH): + os.mkdir(KEY_PATH, mode=0o700) + + if 'l2tp' in ipsec: + if 'authentication' in ipsec['l2tp'] and 'x509' in ipsec['l2tp']['authentication']: + generate_pki_files_x509(ipsec['pki'], ipsec['l2tp']['authentication']['x509']) + + if 'remote_access' in ipsec and 'connection' in ipsec['remote_access']: + for rw, rw_conf in ipsec['remote_access']['connection'].items(): + if f'ra_{rw}' in ipsec['dhcp_no_address']: + continue + + local_ip = '' + if 'local_address' in rw_conf: + local_ip = rw_conf['local_address'] + elif 'dhcp_interface' in rw_conf: + local_ip = get_dhcp_address(rw_conf['dhcp_interface']) + + ipsec['remote_access']['connection'][rw]['local_address'] = local_ip + + if 'authentication' in rw_conf and 'x509' in rw_conf['authentication']: + generate_pki_files_x509(ipsec['pki'], rw_conf['authentication']['x509']) + + if 'site_to_site' in ipsec and 'peer' in ipsec['site_to_site']: + for peer, peer_conf in ipsec['site_to_site']['peer'].items(): + if f'peer_{peer}' in ipsec['dhcp_no_address']: + continue + + if peer_conf['authentication']['mode'] == 'x509': + generate_pki_files_x509(ipsec['pki'], peer_conf['authentication']['x509']) + elif peer_conf['authentication']['mode'] == 'rsa': + generate_pki_files_rsa(ipsec['pki'], peer_conf['authentication']['rsa']) + + local_ip = '' + if 'local_address' in peer_conf: + local_ip = peer_conf['local_address'] + elif 'dhcp_interface' in peer_conf: + local_ip = get_dhcp_address(peer_conf['dhcp_interface']) + + ipsec['site_to_site']['peer'][peer]['local_address'] = local_ip + + if 'tunnel' in peer_conf: + for tunnel, tunnel_conf in peer_conf['tunnel'].items(): + local_prefixes = dict_search_args(tunnel_conf, 'local', 'prefix') + remote_prefixes = dict_search_args(tunnel_conf, 'remote', 'prefix') + + if not local_prefixes or not remote_prefixes: + continue + + passthrough = None + + for local_prefix in local_prefixes: + for remote_prefix in remote_prefixes: + local_net = ipaddress.ip_network(local_prefix) + remote_net = ipaddress.ip_network(remote_prefix) + if local_net.overlaps(remote_net): + if passthrough is None: + passthrough = [] + passthrough.append(local_prefix) + + ipsec['site_to_site']['peer'][peer]['tunnel'][tunnel]['passthrough'] = passthrough + + # auth psk <tag> dhcp-interface <xxx> + if jmespath.search('authentication.psk.*.dhcp_interface', ipsec): + for psk, psk_config in ipsec['authentication']['psk'].items(): + if 'dhcp_interface' in psk_config: + for iface in psk_config['dhcp_interface']: + id = get_dhcp_address(iface) + if id: + ipsec['authentication']['psk'][psk]['id'].append(id) + + render(charon_conf, 'ipsec/charon.j2', ipsec) + render(charon_dhcp_conf, 'ipsec/charon/dhcp.conf.j2', ipsec) + render(charon_radius_conf, 'ipsec/charon/eap-radius.conf.j2', ipsec) + render(interface_conf, 'ipsec/interfaces_use.conf.j2', ipsec) + render(swanctl_conf, 'ipsec/swanctl.conf.j2', ipsec) + + +def apply(ipsec): + systemd_service = 'strongswan.service' + if not ipsec: + call(f'systemctl stop {systemd_service}') + + if vti_updown_db_exists(): + remove_vti_updown_db() + + else: + call(f'systemctl reload-or-restart {systemd_service}') + + if ipsec['enabled_vti_interfaces']: + with open_vti_updown_db_for_create_or_update() as db: + db.removeAllOtherInterfaces(ipsec['enabled_vti_interfaces']) + db.setPersistentInterfaces(ipsec['persistent_vti_interfaces']) + db.commit(lambda interface: ipsec['vti_interface_dicts'][interface]) + elif vti_updown_db_exists(): + remove_vti_updown_db() + + if ipsec.get('nhrp_exists', False): + try: + call_dependents() + except ConfigError: + # Ignore config errors on dependent due to being called too early. Example: + # ConfigError("ConfigError('Interface ethN requires an IP address!')") + pass + + +if __name__ == '__main__': + try: + ipsec = get_config() + verify(ipsec) + generate(ipsec) + apply(ipsec) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py new file mode 100644 index 0000000..04ccbce --- /dev/null +++ b/src/conf_mode/vpn_l2tp.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configdep import call_dependents, set_dependents +from vyos.configdict import get_accel_dict +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.accel_ppp_util import verify_accel_ppp_name_servers +from vyos.accel_ppp_util import verify_accel_ppp_wins_servers +from vyos.accel_ppp_util import verify_accel_ppp_authentication +from vyos.accel_ppp_util import verify_accel_ppp_ip_pool +from vyos.accel_ppp_util import get_pools_in_order +from vyos import ConfigError + +from vyos import airbag +airbag.enable() + + +l2tp_conf = '/run/accel-pppd/l2tp.conf' +l2tp_chap_secrets = '/run/accel-pppd/l2tp.chap-secrets' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['vpn', 'l2tp', 'remote-access'] + + set_dependents('ipsec', conf) + + if not conf.exists(base): + return None + + # retrieve common dictionary keys + l2tp = get_accel_dict(conf, base, l2tp_chap_secrets) + if dict_search('client_ip_pool', l2tp): + # Multiple named pools require ordered values T5099 + l2tp['ordered_named_pools'] = get_pools_in_order( + dict_search('client_ip_pool', l2tp)) + l2tp['server_type'] = 'l2tp' + return l2tp + + +def verify(l2tp): + if not l2tp: + return None + + verify_accel_ppp_authentication(l2tp) + verify_accel_ppp_ip_pool(l2tp) + verify_accel_ppp_name_servers(l2tp) + verify_accel_ppp_wins_servers(l2tp) + + return None + + +def generate(l2tp): + if not l2tp: + return None + + render(l2tp_conf, 'accel-ppp/l2tp.config.j2', l2tp) + + if dict_search('authentication.mode', l2tp) == 'local': + render(l2tp_chap_secrets, 'accel-ppp/chap-secrets.config_dict.j2', + l2tp, permission=0o640) + + return None + + +def apply(l2tp): + if not l2tp: + call('systemctl stop accel-ppp@l2tp.service') + for file in [l2tp_chap_secrets, l2tp_conf]: + if os.path.exists(file): + os.unlink(file) + else: + call('systemctl restart accel-ppp@l2tp.service') + + call_dependents() + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py new file mode 100644 index 0000000..4278513 --- /dev/null +++ b/src/conf_mode/vpn_openconnect.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configverify import verify_pki_certificate +from vyos.configverify import verify_pki_ca_certificate +from vyos.pki import find_chain +from vyos.pki import encode_certificate +from vyos.pki import load_certificate +from vyos.pki import wrap_private_key +from vyos.template import render +from vyos.utils.dict import dict_search +from vyos.utils.file import write_file +from vyos.utils.network import check_port_availability +from vyos.utils.network import is_listen_port_bind_service +from vyos.utils.process import call +from vyos.utils.process import is_systemd_service_running +from vyos import ConfigError +from passlib.hash import sha512_crypt +from time import sleep + +from vyos import airbag +airbag.enable() + +cfg_dir = '/run/ocserv' +ocserv_conf = cfg_dir + '/ocserv.conf' +ocserv_passwd = cfg_dir + '/ocpasswd' +ocserv_otp_usr = cfg_dir + '/users.oath' +radius_cfg = cfg_dir + '/radiusclient.conf' +radius_servers = cfg_dir + '/radius_servers' + +# Generate hash from user cleartext password +def get_hash(password): + return sha512_crypt.hash(password) + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['vpn', 'openconnect'] + if not conf.exists(base): + return None + + ocserv = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True, + with_pki=True) + + return ocserv + +def verify(ocserv): + if ocserv is None: + return None + # Check if listen-ports not binded other services + # It can be only listen by 'ocserv-main' + for proto, port in ocserv.get('listen_ports').items(): + if check_port_availability(ocserv['listen_address'], int(port), proto) is not True and \ + not is_listen_port_bind_service(int(port), 'ocserv-main'): + raise ConfigError(f'"{proto}" port "{port}" is used by another service') + + # Check accounting + if "accounting" in ocserv: + if "mode" in ocserv["accounting"] and "radius" in ocserv["accounting"]["mode"]: + if not origin["accounting"]['radius']['server']: + raise ConfigError('OpenConnect accounting mode radius requires at least one RADIUS server') + if "authentication" not in ocserv or "mode" not in ocserv["authentication"]: + raise ConfigError('Accounting depends on OpenConnect authentication configuration') + elif "radius" not in ocserv["authentication"]["mode"]: + raise ConfigError('RADIUS accounting must be used with RADIUS authentication') + + # Check authentication + if "authentication" in ocserv: + if "mode" in ocserv["authentication"]: + if ("local" in ocserv["authentication"]["mode"] and + "radius" in ocserv["authentication"]["mode"]): + raise ConfigError('OpenConnect authentication modes are mutually-exclusive, remove either local or radius from your configuration') + if "radius" in ocserv["authentication"]["mode"]: + if not ocserv["authentication"]['radius']['server']: + raise ConfigError('OpenConnect authentication mode radius requires at least one RADIUS server') + if "local" in ocserv["authentication"]["mode"]: + if not ocserv.get("authentication", {}).get("local_users"): + raise ConfigError('OpenConnect mode local required at least one user') + if not ocserv["authentication"]["local_users"]["username"]: + raise ConfigError('OpenConnect mode local required at least one user') + else: + # For OTP mode: verify that each local user has an OTP key + if "otp" in ocserv["authentication"]["mode"]["local"]: + users_wo_key = [] + for user, user_config in ocserv["authentication"]["local_users"]["username"].items(): + # User has no OTP key defined + if dict_search('otp.key', user_config) == None: + users_wo_key.append(user) + if users_wo_key: + raise ConfigError(f'OTP enabled, but no OTP key is configured for these users:\n{users_wo_key}') + # For password (and default) mode: verify that each local user has password + if "password" in ocserv["authentication"]["mode"]["local"] or "otp" not in ocserv["authentication"]["mode"]["local"]: + users_wo_pswd = [] + for user in ocserv["authentication"]["local_users"]["username"]: + if not "password" in ocserv["authentication"]["local_users"]["username"][user]: + users_wo_pswd.append(user) + if users_wo_pswd: + raise ConfigError(f'password required for users:\n{users_wo_pswd}') + + # Validate that if identity-based-config is configured all child config nodes are set + if 'identity_based_config' in ocserv["authentication"]: + if 'disabled' not in ocserv["authentication"]["identity_based_config"]: + Warning("Identity based configuration files is a 3rd party addition. Use at your own risk, this might break the ocserv daemon!") + if 'mode' not in ocserv["authentication"]["identity_based_config"]: + raise ConfigError('OpenConnect radius identity-based-config enabled but mode not selected') + elif 'group' in ocserv["authentication"]["identity_based_config"]["mode"] and "radius" not in ocserv["authentication"]["mode"]: + raise ConfigError('OpenConnect config-per-group must be used with radius authentication') + if 'directory' not in ocserv["authentication"]["identity_based_config"]: + raise ConfigError('OpenConnect identity-based-config enabled but directory not set') + if 'default_config' not in ocserv["authentication"]["identity_based_config"]: + raise ConfigError('OpenConnect identity-based-config enabled but default-config not set') + else: + raise ConfigError('OpenConnect authentication mode required') + else: + raise ConfigError('OpenConnect authentication credentials required') + + # Check ssl + if 'ssl' not in ocserv: + raise ConfigError('SSL missing on OpenConnect config!') + + if 'certificate' not in ocserv['ssl']: + raise ConfigError('SSL certificate missing on OpenConnect config!') + verify_pki_certificate(ocserv, ocserv['ssl']['certificate']) + + if 'ca_certificate' in ocserv['ssl']: + for ca_cert in ocserv['ssl']['ca_certificate']: + verify_pki_ca_certificate(ocserv, ca_cert) + + # Check network settings + if "network_settings" in ocserv: + if "push_route" in ocserv["network_settings"]: + # Replace default route + if "0.0.0.0/0" in ocserv["network_settings"]["push_route"]: + ocserv["network_settings"]["push_route"].remove("0.0.0.0/0") + ocserv["network_settings"]["push_route"].append("default") + else: + ocserv["network_settings"]["push_route"] = ["default"] + else: + raise ConfigError('OpenConnect network settings required!') + +def generate(ocserv): + if not ocserv: + return None + + if "radius" in ocserv["authentication"]["mode"]: + if dict_search(ocserv, 'accounting.mode.radius'): + # Render radius client configuration + render(radius_cfg, 'ocserv/radius_conf.j2', ocserv) + merged_servers = ocserv["accounting"]["radius"]["server"] | ocserv["authentication"]["radius"]["server"] + # Render radius servers + # Merge the accounting and authentication servers into a single dictionary + render(radius_servers, 'ocserv/radius_servers.j2', {'server': merged_servers}) + else: + # Render radius client configuration + render(radius_cfg, 'ocserv/radius_conf.j2', ocserv) + # Render radius servers + render(radius_servers, 'ocserv/radius_servers.j2', ocserv["authentication"]["radius"]) + elif "local" in ocserv["authentication"]["mode"]: + # if mode "OTP", generate OTP users file parameters + if "otp" in ocserv["authentication"]["mode"]["local"]: + if "local_users" in ocserv["authentication"]: + for user in ocserv["authentication"]["local_users"]["username"]: + # OTP token type from CLI parameters: + otp_interval = str(ocserv["authentication"]["local_users"]["username"][user]["otp"].get("interval")) + token_type = ocserv["authentication"]["local_users"]["username"][user]["otp"].get("token_type") + otp_length = str(ocserv["authentication"]["local_users"]["username"][user]["otp"].get("otp_length")) + if token_type == "hotp-time": + otp_type = "HOTP/T" + otp_interval + elif token_type == "hotp-event": + otp_type = "HOTP/E" + else: + otp_type = "HOTP/T" + otp_interval + ocserv["authentication"]["local_users"]["username"][user]["otp"]["token_tmpl"] = otp_type + "/" + otp_length + # if there is a password, generate hash + if "password" in ocserv["authentication"]["mode"]["local"] or not "otp" in ocserv["authentication"]["mode"]["local"]: + if "local_users" in ocserv["authentication"]: + for user in ocserv["authentication"]["local_users"]["username"]: + ocserv["authentication"]["local_users"]["username"][user]["hash"] = get_hash(ocserv["authentication"]["local_users"]["username"][user]["password"]) + + if "password-otp" in ocserv["authentication"]["mode"]["local"]: + # Render local users ocpasswd + render(ocserv_passwd, 'ocserv/ocserv_passwd.j2', ocserv["authentication"]["local_users"]) + # Render local users OTP keys + render(ocserv_otp_usr, 'ocserv/ocserv_otp_usr.j2', ocserv["authentication"]["local_users"]) + elif "password" in ocserv["authentication"]["mode"]["local"]: + # Render local users ocpasswd + render(ocserv_passwd, 'ocserv/ocserv_passwd.j2', ocserv["authentication"]["local_users"]) + elif "otp" in ocserv["authentication"]["mode"]["local"]: + # Render local users OTP keys + render(ocserv_otp_usr, 'ocserv/ocserv_otp_usr.j2', ocserv["authentication"]["local_users"]) + else: + # Render local users ocpasswd + render(ocserv_passwd, 'ocserv/ocserv_passwd.j2', ocserv["authentication"]["local_users"]) + else: + if "local_users" in ocserv["authentication"]: + for user in ocserv["authentication"]["local_users"]["username"]: + ocserv["authentication"]["local_users"]["username"][user]["hash"] = get_hash(ocserv["authentication"]["local_users"]["username"][user]["password"]) + # Render local users + render(ocserv_passwd, 'ocserv/ocserv_passwd.j2', ocserv["authentication"]["local_users"]) + + if "ssl" in ocserv: + cert_file_path = os.path.join(cfg_dir, 'cert.pem') + cert_key_path = os.path.join(cfg_dir, 'cert.key') + + + if 'certificate' in ocserv['ssl']: + cert_name = ocserv['ssl']['certificate'] + pki_cert = ocserv['pki']['certificate'][cert_name] + + loaded_pki_cert = load_certificate(pki_cert['certificate']) + loaded_ca_certs = {load_certificate(c['certificate']) + for c in ocserv['pki']['ca'].values()} if 'ca' in ocserv['pki'] else {} + + cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs) + + write_file(cert_file_path, + '\n'.join(encode_certificate(c) for c in cert_full_chain)) + + if 'private' in pki_cert and 'key' in pki_cert['private']: + write_file(cert_key_path, wrap_private_key(pki_cert['private']['key'])) + + if 'ca_certificate' in ocserv['ssl']: + ca_cert_file_path = os.path.join(cfg_dir, 'ca.pem') + ca_chains = [] + + for ca_name in ocserv['ssl']['ca_certificate']: + pki_ca_cert = ocserv['pki']['ca'][ca_name] + loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) + ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + ca_chains.append( + '\n'.join(encode_certificate(c) for c in ca_full_chain)) + + write_file(ca_cert_file_path, '\n'.join(ca_chains)) + + # Render config + render(ocserv_conf, 'ocserv/ocserv_config.j2', ocserv) + + +def apply(ocserv): + if not ocserv: + call('systemctl stop ocserv.service') + for file in [ocserv_conf, ocserv_passwd, ocserv_otp_usr]: + if os.path.exists(file): + os.unlink(file) + else: + call('systemctl reload-or-restart ocserv.service') + counter = 0 + while True: + # exit early when service runs + if is_systemd_service_running("ocserv.service"): + break + sleep(0.250) + if counter > 5: + raise ConfigError('OpenConnect failed to start, check the logs for details') + break + counter += 1 + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py new file mode 100644 index 0000000..c0d8330 --- /dev/null +++ b/src/conf_mode/vpn_pptp.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +from sys import exit + + +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.accel_ppp_util import verify_accel_ppp_name_servers +from vyos.accel_ppp_util import verify_accel_ppp_wins_servers +from vyos.accel_ppp_util import verify_accel_ppp_authentication +from vyos.accel_ppp_util import verify_accel_ppp_ip_pool +from vyos.accel_ppp_util import get_pools_in_order +from vyos import ConfigError +from vyos.configdict import get_accel_dict + +from vyos import airbag +airbag.enable() + +pptp_conf = '/run/accel-pppd/pptp.conf' +pptp_chap_secrets = '/run/accel-pppd/pptp.chap-secrets' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['vpn', 'pptp', 'remote-access'] + if not conf.exists(base): + return None + + # retrieve common dictionary keys + pptp = get_accel_dict(conf, base, pptp_chap_secrets) + + if dict_search('client_ip_pool', pptp): + # Multiple named pools require ordered values T5099 + pptp['ordered_named_pools'] = get_pools_in_order( + dict_search('client_ip_pool', pptp)) + pptp['chap_secrets_file'] = pptp_chap_secrets + pptp['server_type'] = 'pptp' + return pptp + + +def verify(pptp): + if not pptp: + return None + + verify_accel_ppp_authentication(pptp) + verify_accel_ppp_ip_pool(pptp) + verify_accel_ppp_name_servers(pptp) + verify_accel_ppp_wins_servers(pptp) + + +def generate(pptp): + if not pptp: + return None + + render(pptp_conf, 'accel-ppp/pptp.config.j2', pptp) + + if dict_search('authentication.mode', pptp) == 'local': + render(pptp_chap_secrets, 'accel-ppp/chap-secrets.config_dict.j2', + pptp, permission=0o640) + + return None + + +def apply(pptp): + if not pptp: + call('systemctl stop accel-ppp@pptp.service') + for file in [pptp_conf, pptp_chap_secrets]: + if os.path.exists(file): + os.unlink(file) + + return None + + call('systemctl restart accel-ppp@pptp.service') + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py new file mode 100644 index 0000000..7490fd0 --- /dev/null +++ b/src/conf_mode/vpn_sstp.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_accel_dict +from vyos.configverify import verify_pki_certificate +from vyos.configverify import verify_pki_ca_certificate +from vyos.pki import wrap_certificate +from vyos.pki import wrap_private_key +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.network import check_port_availability +from vyos.utils.dict import dict_search +from vyos.accel_ppp_util import verify_accel_ppp_name_servers +from vyos.accel_ppp_util import verify_accel_ppp_wins_servers +from vyos.accel_ppp_util import verify_accel_ppp_authentication +from vyos.accel_ppp_util import verify_accel_ppp_ip_pool +from vyos.accel_ppp_util import get_pools_in_order +from vyos.utils.network import is_listen_port_bind_service +from vyos.utils.file import write_file +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +cfg_dir = '/run/accel-pppd' +sstp_conf = '/run/accel-pppd/sstp.conf' +sstp_chap_secrets = '/run/accel-pppd/sstp.chap-secrets' + +cert_file_path = os.path.join(cfg_dir, 'sstp-cert.pem') +cert_key_path = os.path.join(cfg_dir, 'sstp-cert.key') +ca_cert_file_path = os.path.join(cfg_dir, 'sstp-ca.pem') + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['vpn', 'sstp'] + if not conf.exists(base): + return None + + # retrieve common dictionary keys + sstp = get_accel_dict(conf, base, sstp_chap_secrets, with_pki=True) + if dict_search('client_ip_pool', sstp): + # Multiple named pools require ordered values T5099 + sstp['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', sstp)) + + sstp['server_type'] = 'sstp' + return sstp + + +def verify(sstp): + if not sstp: + return None + + port = sstp.get('port') + proto = 'tcp' + if check_port_availability('0.0.0.0', int(port), proto) is not True and \ + not is_listen_port_bind_service(int(port), 'accel-pppd'): + raise ConfigError(f'"{proto}" port "{port}" is used by another service') + + verify_accel_ppp_authentication(sstp) + verify_accel_ppp_ip_pool(sstp) + verify_accel_ppp_name_servers(sstp) + verify_accel_ppp_wins_servers(sstp) + + if 'ssl' not in sstp: + raise ConfigError('SSL missing on SSTP config!') + + if 'certificate' not in sstp['ssl']: + raise ConfigError('SSL certificate missing on SSTP config!') + verify_pki_certificate(sstp, sstp['ssl']['certificate']) + + if 'ca_certificate' not in sstp['ssl']: + raise ConfigError('SSL CA certificate missing on SSTP config!') + verify_pki_ca_certificate(sstp, sstp['ssl']['ca_certificate']) + + +def generate(sstp): + if not sstp: + return None + + # accel-cmd reload doesn't work so any change results in a restart of the daemon + render(sstp_conf, 'accel-ppp/sstp.config.j2', sstp) + + cert_name = sstp['ssl']['certificate'] + pki_cert = sstp['pki']['certificate'][cert_name] + + ca_cert_name = sstp['ssl']['ca_certificate'] + pki_ca = sstp['pki']['ca'][ca_cert_name] + write_file(cert_file_path, wrap_certificate(pki_cert['certificate'])) + write_file(cert_key_path, wrap_private_key(pki_cert['private']['key'])) + write_file(ca_cert_file_path, wrap_certificate(pki_ca['certificate'])) + + if dict_search('authentication.mode', sstp) == 'local': + render(sstp_chap_secrets, 'accel-ppp/chap-secrets.config_dict.j2', + sstp, permission=0o640) + else: + if os.path.exists(sstp_chap_secrets): + os.unlink(sstp_chap_secrets) + + return sstp + + +def apply(sstp): + systemd_service = 'accel-ppp@sstp.service' + if not sstp: + call(f'systemctl stop {systemd_service}') + for file in [sstp_chap_secrets, sstp_conf]: + if os.path.exists(file): + os.unlink(file) + return None + + call(f'systemctl reload-or-restart {systemd_service}') + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py new file mode 100644 index 0000000..72b178c --- /dev/null +++ b/src/conf_mode/vrf.py @@ -0,0 +1,364 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit +from jmespath import search +from json import loads + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configdict import node_changed +from vyos.configverify import verify_route_map +from vyos.firewall import conntrack_required +from vyos.ifconfig import Interface +from vyos.template import render +from vyos.template import render_to_string +from vyos.utils.dict import dict_search +from vyos.utils.network import get_vrf_tableid +from vyos.utils.network import get_vrf_members +from vyos.utils.network import interface_exists +from vyos.utils.process import call +from vyos.utils.process import cmd +from vyos.utils.process import popen +from vyos.utils.system import sysctl_write +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +config_file = '/etc/iproute2/rt_tables.d/vyos-vrf.conf' +k_mod = ['vrf'] + +nftables_table = 'inet vrf_zones' +nftables_rules = { + 'vrf_zones_ct_in': 'counter ct original zone set iifname map @ct_iface_map', + 'vrf_zones_ct_out': 'counter ct original zone set oifname map @ct_iface_map' +} + +def has_rule(af : str, priority : int, table : str=None): + """ + Check if a given ip rule exists + $ ip --json -4 rule show + [{'l3mdev': None, 'priority': 1000, 'src': 'all'}, + {'action': 'unreachable', 'l3mdev': None, 'priority': 2000, 'src': 'all'}, + {'priority': 32765, 'src': 'all', 'table': 'local'}, + {'priority': 32766, 'src': 'all', 'table': 'main'}, + {'priority': 32767, 'src': 'all', 'table': 'default'}] + """ + if af not in ['-4', '-6']: + raise ValueError() + command = f'ip --detail --json {af} rule show' + for tmp in loads(cmd(command)): + if 'priority' in tmp and 'table' in tmp: + if tmp['priority'] == priority and tmp['table'] == table: + return True + elif 'priority' in tmp and table in tmp: + # l3mdev table has a different layout + if tmp['priority'] == priority: + return True + return False + +def is_nft_vrf_zone_rule_setup() -> bool: + """ + Check if an nftables connection tracking rule already exists + """ + tmp = loads(cmd('sudo nft -j list table inet vrf_zones')) + num_rules = len(search("nftables[].rule[].chain", tmp)) + return bool(num_rules) + +def vrf_interfaces(c, match): + matched = [] + old_level = c.get_level() + c.set_level(['interfaces']) + section = c.get_config_dict([], get_first_key=True) + for type in section: + interfaces = section[type] + for name in interfaces: + interface = interfaces[name] + if 'vrf' in interface: + v = interface.get('vrf', '') + if v == match: + matched.append(name) + + c.set_level(old_level) + return matched + +def vrf_routing(c, match): + matched = [] + old_level = c.get_level() + c.set_level(['protocols', 'vrf']) + if match in c.list_nodes([]): + matched.append(match) + + c.set_level(old_level) + return matched + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['vrf'] + vrf = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, get_first_key=True) + + # determine which VRF has been removed + for name in node_changed(conf, base + ['name']): + if 'vrf_remove' not in vrf: + vrf.update({'vrf_remove' : {}}) + + vrf['vrf_remove'][name] = {} + # get VRF bound interfaces + interfaces = vrf_interfaces(conf, name) + if interfaces: vrf['vrf_remove'][name]['interface'] = interfaces + # get VRF bound routing instances + routes = vrf_routing(conf, name) + if routes: vrf['vrf_remove'][name]['route'] = routes + + if 'name' in vrf: + vrf['conntrack'] = conntrack_required(conf) + + # We also need the route-map information from the config + # + # XXX: one MUST always call this without the key_mangling() option! See + # vyos.configverify.verify_common_route_maps() for more information. + tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'], + get_first_key=True)}} + + # Merge policy dict into "regular" config dict + vrf = dict_merge(tmp, vrf) + return vrf + +def verify(vrf): + # ensure VRF is not assigned to any interface + if 'vrf_remove' in vrf: + for name, config in vrf['vrf_remove'].items(): + if 'interface' in config: + raise ConfigError(f'Can not remove VRF "{name}", it still has '\ + f'member interfaces!') + if 'route' in config: + raise ConfigError(f'Can not remove VRF "{name}", it still has '\ + f'static routes installed!') + + if 'name' in vrf: + reserved_names = ["add", "all", "broadcast", "default", "delete", "dev", + "get", "inet", "mtu", "link", "type", "vrf"] + table_ids = [] + for name, vrf_config in vrf['name'].items(): + # Reserved VRF names + if name in reserved_names: + raise ConfigError(f'VRF name "{name}" is reserved and connot be used!') + + # table id is mandatory + if 'table' not in vrf_config: + raise ConfigError(f'VRF "{name}" table id is mandatory!') + + # routing table id can't be changed - OS restriction + if interface_exists(name): + tmp = get_vrf_tableid(name) + if tmp and tmp != int(vrf_config['table']): + raise ConfigError(f'VRF "{name}" table id modification not possible!') + + # VRF routing table ID must be unique on the system + if 'table' in vrf_config and vrf_config['table'] in table_ids: + raise ConfigError(f'VRF "{name}" table id is not unique!') + table_ids.append(vrf_config['table']) + + tmp = dict_search('ip.protocol', vrf_config) + if tmp != None: + for protocol, protocol_options in tmp.items(): + if 'route_map' in protocol_options: + verify_route_map(protocol_options['route_map'], vrf) + + tmp = dict_search('ipv6.protocol', vrf_config) + if tmp != None: + for protocol, protocol_options in tmp.items(): + if 'route_map' in protocol_options: + verify_route_map(protocol_options['route_map'], vrf) + + return None + + +def generate(vrf): + # Render iproute2 VR helper names + render(config_file, 'iproute2/vrf.conf.j2', vrf) + # Render VRF Kernel/Zebra route-map filters + vrf['frr_zebra_config'] = render_to_string('frr/zebra.vrf.route-map.frr.j2', vrf) + + return None + +def apply(vrf): + # Documentation + # + # - https://github.com/torvalds/linux/blob/master/Documentation/networking/vrf.txt + # - https://github.com/Mellanox/mlxsw/wiki/Virtual-Routing-and-Forwarding-(VRF) + # - https://github.com/Mellanox/mlxsw/wiki/L3-Tunneling + # - https://netdevconf.info/1.1/proceedings/slides/ahern-vrf-tutorial.pdf + # - https://netdevconf.info/1.2/slides/oct6/02_ahern_what_is_l3mdev_slides.pdf + + # set the default VRF global behaviour + bind_all = '0' + if 'bind_to_all' in vrf: + bind_all = '1' + sysctl_write('net.ipv4.tcp_l3mdev_accept', bind_all) + sysctl_write('net.ipv4.udp_l3mdev_accept', bind_all) + + for tmp in (dict_search('vrf_remove', vrf) or []): + if interface_exists(tmp): + # T5492: deleting a VRF instance may leafe processes running + # (e.g. dhclient) as there is a depedency ordering issue in the CLI. + # We need to ensure that we stop the dhclient processes first so + # a proper DHCLP RELEASE message is sent + for interface in get_vrf_members(tmp): + vrf_iface = Interface(interface) + vrf_iface.set_dhcp(False) + vrf_iface.set_dhcpv6(False) + + # Remove nftables conntrack zone map item + nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{tmp}" }}' + # Check if deleting is possible first to avoid raising errors + _, err = popen(f'nft --check {nft_del_element}') + if not err: + # Remove map element + cmd(f'nft {nft_del_element}') + + # Delete the VRF Kernel interface + call(f'ip link delete dev {tmp}') + + if 'name' in vrf: + # Linux routing uses rules to find tables - routing targets are then + # looked up in those tables. If the lookup got a matching route, the + # process ends. + # + # TL;DR; first table with a matching entry wins! + # + # You can see your routing table lookup rules using "ip rule", sadly the + # local lookup is hit before any VRF lookup. Pinging an addresses from the + # VRF will usually find a hit in the local table, and never reach the VRF + # routing table - this is usually not what you want. Thus we will + # re-arrange the tables and move the local lookup further down once VRFs + # are enabled. + # + # Thanks to https://stbuehler.de/blog/article/2020/02/29/using_vrf__virtual_routing_and_forwarding__on_linux.html + + for afi in ['-4', '-6']: + # move lookup local to pref 32765 (from 0) + if not has_rule(afi, 32765, 'local'): + call(f'ip {afi} rule add pref 32765 table local') + if has_rule(afi, 0, 'local'): + call(f'ip {afi} rule del pref 0') + # make sure that in VRFs after failed lookup in the VRF specific table + # nothing else is reached + if not has_rule(afi, 1000, 'l3mdev'): + # this should be added by the kernel when a VRF is created + # add it here for completeness + call(f'ip {afi} rule add pref 1000 l3mdev protocol kernel') + + # add another rule with an unreachable target which only triggers in VRF context + # if a route could not be reached + if not has_rule(afi, 2000, 'l3mdev'): + call(f'ip {afi} rule add pref 2000 l3mdev unreachable') + + nft_vrf_zone_rule_setup = False + for name, config in vrf['name'].items(): + table = config['table'] + if not interface_exists(name): + # For each VRF apart from your default context create a VRF + # interface with a separate routing table + call(f'ip link add {name} type vrf table {table}') + + # set VRF description for e.g. SNMP monitoring + vrf_if = Interface(name) + # We also should add proper loopback IP addresses to the newly added + # VRF for services bound to the loopback address (SNMP, NTP) + vrf_if.add_addr('127.0.0.1/8') + vrf_if.add_addr('::1/128') + # add VRF description if available + vrf_if.set_alias(config.get('description', '')) + + # Enable/Disable IPv4 forwarding + tmp = dict_search('ip.disable_forwarding', config) + value = '0' if (tmp != None) else '1' + vrf_if.set_ipv4_forwarding(value) + # Enable/Disable IPv6 forwarding + tmp = dict_search('ipv6.disable_forwarding', config) + value = '0' if (tmp != None) else '1' + vrf_if.set_ipv6_forwarding(value) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + vrf_if.set_admin_state(state) + # Add nftables conntrack zone map item + nft_add_element = f'add element inet vrf_zones ct_iface_map {{ "{name}" : {table} }}' + cmd(f'nft {nft_add_element}') + + # Only call into nftables as long as there is nothing setup to avoid wasting + # CPU time and thus lenghten the commit process + if not nft_vrf_zone_rule_setup: + nft_vrf_zone_rule_setup = is_nft_vrf_zone_rule_setup() + # Install nftables conntrack rules only once + if vrf['conntrack'] and not nft_vrf_zone_rule_setup: + for chain, rule in nftables_rules.items(): + cmd(f'nft add rule inet vrf_zones {chain} {rule}') + + if 'name' not in vrf or not vrf['conntrack']: + for chain, rule in nftables_rules.items(): + cmd(f'nft flush chain inet vrf_zones {chain}') + + # Return default ip rule values + if 'name' not in vrf: + for afi in ['-4', '-6']: + # move lookup local to pref 0 (from 32765) + if not has_rule(afi, 0, 'local'): + call(f'ip {afi} rule add pref 0 from all lookup local') + if has_rule(afi, 32765, 'local'): + call(f'ip {afi} rule del pref 32765 table local') + + if has_rule(afi, 1000, 'l3mdev'): + call(f'ip {afi} rule del pref 1000 l3mdev protocol kernel') + if has_rule(afi, 2000, 'l3mdev'): + call(f'ip {afi} rule del pref 2000 l3mdev unreachable') + + # Apply FRR filters + zebra_daemon = 'zebra' + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + # The route-map used for the FIB (zebra) is part of the zebra daemon + frr_cfg.load_configuration(zebra_daemon) + frr_cfg.modify_section(f'^vrf .+', stop_pattern='^exit-vrf', remove_stop_mark=True) + if 'frr_zebra_config' in vrf: + frr_cfg.add_before(frr.default_add_before, vrf['frr_zebra_config']) + frr_cfg.commit_configuration(zebra_daemon) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/etc/bash_completion.d/vyatta-op b/src/etc/bash_completion.d/vyatta-op new file mode 100644 index 0000000..8ac2d9b --- /dev/null +++ b/src/etc/bash_completion.d/vyatta-op @@ -0,0 +1,685 @@ +# vyatta bash operational mode completion +# **** License **** +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# This code was originally developed by Vyatta, Inc. +# Portions created by Vyatta are Copyright (C) 2006, 2007 Vyatta, Inc. +# All Rights Reserved. +# +# Author: Tom Grennan +# Date: 2007 +# Description: setup bash completion for Vyatta operational commands +# +# **** End License **** + +test -z "$_vyatta_less_options" && \ + declare -r _vyatta_less_options="\ + --QUIT-AT-EOF\ + --quit-if-one-screen\ + --RAW-CONTROL-CHARS\ + --squeeze-blank-lines\ + --no-init" +test -z "$_vyatta_default_pager" && \ + declare -r _vyatta_default_pager="less \ + --buffers=64\ + --auto-buffers\ + --no-lessopen\ + $_vyatta_less_options" +test -z "$VYATTA_PAGER" && \ + declare -x VYATTA_PAGER=$_vyatta_default_pager + +_vyatta_op_do_key_bindings () +{ + if [[ "$SHELL" != "/bin/vbash" && "$SHELL" != "/sbin/radius_shell" ]]; then + # only do bindings if vbash and radius_shell + return + fi + nullglob_save=$(shopt -p nullglob) + shopt -u nullglob + case "$-" in + *i*) + bind '"?": possible-completions' + bind 'set show-all-if-ambiguous on' + bind_cmds=$(grep '^bind .* # vyatta key binding$' $HOME/.bashrc) + eval $bind_cmds + ;; + esac + eval $nullglob_save +} + +_vyatta_op_do_key_bindings + +test -f /etc/default/vyatta && \ + source /etc/default/vyatta + +test ! -d "$vyatta_op_templates" && \ + return 0 + +case "$-" in + *i*) + declare -r _vyatta_op_last_comp_init='>>>>>>LASTCOMP<<<<<<' + ;; +esac +declare _vyatta_op_last_comp=${_vyatta_op_last_comp_init} +declare _vyatta_op_node_path +declare -a _vyatta_op_noncompletions _vyatta_op_completions +declare -x -a _vyatta_pipe_noncompletions _vyatta_pipe_completions +declare _vyatta_comptype +declare -x -a reply +declare -a _vyatta_operator_allowed + +if [[ "$VYATTA_USER_LEVEL_DIR" != "/opt/vyatta/etc/shell/level/admin" ]]; then + _vyatta_operator_allowed=( $(cat $VYATTA_USER_LEVEL_DIR/allowed-op) ) +fi + +declare -a functions +functions=( /opt/vyatta/share/vyatta-op/functions/interpreter/* ) + +for file in "${functions[@]}";do + source $file; +done + +# $1: label +# #2...: strings +_vyatta_op_debug () +{ + echo -ne \\n$1: + shift + for s ; do + echo -ne " \"$s\"" + done +} + +# this is needed to provide original "default completion" behavior. +# see "vyatta-cfg" completion script for details. +_vyatta_op_default_expand () +{ + local wc=${#COMP_WORDS[@]} + if [[ "${COMP_WORDS[0]}" =~ "/" ]]; then + # if we are looking for a directory on the first completion then do directory completions + _filedir_xspec_vyos + elif (( wc < 2 )) || + [[ $COMP_CWORD -eq 0 ]] || + [[ $1 == $2 ]]; then + _vyatta_op_expand "$@" + else + # after the first word => cannot be vyatta command so use original default + _filedir_xspec_vyos + fi +} + +# $1: label +# $2...: help +_vyatta_op_print_help () +{ + local label=$1 help=$2 + if [ ${#label} -eq 0 ] ; then + return + elif [ ${#help} -eq 0 ] ; then + echo -ne "\n $label" + elif [ ${#label} -lt 6 ] ; then + echo -ne "\n $label\t\t\t$help" + elif [ ${#label} -lt 14 ] ; then + echo -ne "\n $label\t\t$help" + elif [ ${#label} -lt 21 ] ; then + echo -ne "\n $label\t$help" + else + echo -ne "\n $label\n\t\t\t$help" + fi +} + +# $1: $cur +# $2...: possible completions +_vyatta_op_help () +{ + local restore_shopts=$( shopt -p extglob nullglob | tr \\n \; ) + shopt -u nullglob + local cur=$1; shift + local ndef node_tag_help node_run help last_help + + ndef=${_vyatta_op_node_path}/node.tag/node.def + [ -f $ndef ] && \ + node_tag_help=$( _vyatta_op_get_node_def_field $ndef help ) + + ndef=${_vyatta_op_node_path}/node.def + [ -f $ndef ] && \ + node_run=$( _vyatta_op_get_node_def_field $ndef run ) + + if [[ "$1" == "<nocomps>" ]]; then + eval "$restore_shopts" + return + fi + echo -en "\nPossible completions:" + if [ -z "$cur" -a -n "$node_run" ]; then + _vyatta_op_print_help '<Enter>' "Execute the current command" + fi + if [ $# -eq 0 ];then + _vyatta_op_print_help '<text>' "$node_tag_help" + eval "$restore_shopts" + return + fi + for comp ; do + if [[ "$comp" == "<Enter>" ]]; then + continue + fi + if [ -z "$comp" ] ; then + if [ "X$node_tag_help" == "X$last_help" ] ; then + help="" + else + last_help=$node_tag_help + help=$node_tag_help + fi + _vyatta_op_print_help '*' "$help" + elif [[ -z "$cur" || $comp == ${cur}* ]] ; then + ndef=${_vyatta_op_node_path}/$comp/node.def + if [ -f $ndef ] ; then + help=$( _vyatta_op_get_node_def_field $ndef help ) + else + help=$node_tag_help + fi + if [ "X$help" == "X$last_help" ] ; then + help="" + else + last_help=$help + fi + _vyatta_op_print_help "$comp" "$help" + fi + done + eval "$restore_shopts" +} + +_vyatta_op_set_node_path () +{ + local node + _vyatta_op_node_path=$vyatta_op_templates + for (( i=0 ; i<COMP_CWORD ; i++ )) ; do + # expand the command so completion continues to work with short versions + if [[ "${COMP_WORDS[i]}" == "*" ]]; then + node="node.tag" # user defined wildcars are always tag nodes + else + node=$(_vyatta_op_conv_node_path $_vyatta_op_node_path ${COMP_WORDS[i]}) + fi + if [ -f "${_vyatta_op_node_path}/$node/node.def" ] ; then + _vyatta_op_node_path+=/$node + elif [ -f ${_vyatta_op_node_path}/node.tag/node.def ] ; then + _vyatta_op_node_path+=/node.tag + else + return 1 + fi + done +} + +_vyatta_op_set_completions () +{ + local -a allowed completions + local cur=$1 + local restore_shopts=$( shopt -p extglob nullglob | tr \\n \; ) + for ndef in ${_vyatta_op_node_path}/*/node.def ; do + if [[ $ndef == */node.tag/node.def ]] ; then + local acmd=$( _vyatta_op_get_node_def_field $ndef allowed ) + shopt -u extglob nullglob + local -a a=($( eval "$acmd" )) + eval "$restore_shopts" + + if [ ${#a[@]} -ne 0 ] ; then + allowed+=( "${a[@]}" ) + else + allowed+=( "<text>" ) + fi + else + local sdir=${ndef%/*} + allowed+=( ${sdir##*/} ) + fi + done + + # donot complete entries like <HOSTNAME> or <A.B.C.D> + _vyatta_op_noncompletions=( ) + completions=( ) + + # make runable commands have a non-comp + ndef=${_vyatta_op_node_path}/node.def + [ -f $ndef ] && \ + node_run=$( _vyatta_op_get_node_def_field $ndef run ) + if [ -z "$cur" -a -n "$node_run" ]; then + _vyatta_op_noncompletions+=('<Enter>') + fi + + for (( i=0 ; i<${#allowed[@]} ; i++ )) ; do + if [[ "${allowed[i]}" == \<*\> ]] ; then + _vyatta_op_noncompletions+=( "${allowed[i]}" ) + else + if [[ "$VYATTA_USER_LEVEL_DIR" == "/opt/vyatta/etc/shell/level/admin" ]]; then + completions+=( ${allowed[i]} ) + elif is_elem_of ${allowed[i]} _vyatta_operator_allowed; then + completions+=( ${allowed[i]} ) + elif [[ $_vyatta_op_node_path == $vyatta_op_templates ]];then + continue + else + completions+=( ${allowed[i]} ) + fi + fi + done + + # Prefix filter the non empty completions + if [ -n "$cur" ]; then + _vyatta_op_completions=() + get_prefix_filtered_list "$cur" completions _vyatta_op_completions + _vyatta_op_completions=($( printf "%s\n" ${_vyatta_op_completions[@]} | sort -u )) + else + _vyatta_op_completions=($( printf "%s\n" ${completions[@]} | sort -u )) + fi + #shopt -s nullglob +} + +_vyatta_op_comprely_needs_ambiguity () +{ + local -a uniq + + [ ${#COMPREPLY[@]} -eq 1 ] && return + + uniq=( `printf "%s\n" ${COMPREPLY[@]} | cut -c1 | sort -u` ) + + [ ${#uniq[@]} -eq 1 ] && return + false +} + +_vyatta_op_invalid_completion () +{ + local tpath=$vyatta_op_templates + local -a args + local i=1 + for arg in "${COMP_WORDS[@]}"; do + arg=( $(_vyatta_op_conv_node_path $tpath $arg) ) # expand the arguments + # output proper error message based on the above expansion + if [[ "${arg[1]}" == "ambiguous" ]]; then + echo -ne "\n\n Ambiguous command: ${args[@]} [$arg]\n" + local -a cmds=( $(compgen -d $tpath/$arg) ) + _vyatta_op_node_path=$tpath + local comps=$(_vyatta_op_help $arg ${cmds[@]##*/}) + echo -ne "$comps" | sed -e 's/^P/ P/' + break + elif [[ "${arg[1]}" == "invalid" ]]; then + echo -ne "\n\n Invalid command: ${args[@]} [$arg]" + break + fi + + if [ -f "$tpath/$arg/node.def" ] ; then + tpath+=/$arg + elif [ -f $tpath/node.tag/node.def ] ; then + tpath+=/node.tag + else + echo -ne "\n\n Invalid command: ${args[@]} [$arg]" >&2 + break + fi + args[$i]=$arg + let "i+=1" + if [ $[${#COMP_WORDS[@]}+1] -eq $i ];then + _vyatta_op_help "" \ + "${_vyatta_op_noncompletions[@]}" \ + "${_vyatta_op_completions[@]}" \ + | ${VYATTA_PAGER:-cat} + fi + done +} + +_vyatta_op_expand () +{ + # We need nospace here and we have to append our own spaces + compopt -o nospace + + local restore_shopts=$( shopt -p extglob nullglob | tr \\n \; ) + shopt -s extglob nullglob + local cur="" + local _has_comptype=0 + local current_prefix=$2 + local current_word=$3 + _vyatta_comptype="" + + if (( ${#COMP_WORDS[@]} > 0 )); then + cur=${COMP_WORDS[COMP_CWORD]} + else + (( COMP_CWORD = ${#COMP_WORDS[@]} )) + fi + + if _vyatta_pipe_completion "${COMP_WORDS[@]}"; then + if [ "${COMP_WORDS[*]}" == "$_vyatta_op_last_comp" ] || + [ ${#_vyatta_pipe_completions[@]} -eq 0 ]; then + _vyatta_do_pipe_help + COMPREPLY=( "" " " ) + _vyatta_op_last_comp=${_vyatta_op_last_comp_init} + else + COMPREPLY=( "${_vyatta_pipe_completions[@]}" ) + _vyatta_op_last_comp="${COMP_WORDS[*]}" + if [ ${#COMPREPLY[@]} -eq 1 ]; then + COMPREPLY=( "${COMPREPLY[0]} " ) + fi + fi + eval "$restore_shopts" + return + fi + + # this needs to be done on every completion even if it is the 'same' comp. + # The cursor can be at different places in the string. + # this will lead to unexpected cases if setting the node path isn't attempted + # each time. + if ! _vyatta_op_set_node_path ; then + echo -ne \\a + _vyatta_op_invalid_completion + COMPREPLY=( "" " " ) + eval "$restore_shopts" + return 1 + fi + + if [ "${COMP_WORDS[*]:0:$[$COMP_CWORD+1]}" != "$_vyatta_op_last_comp" ] ; then + _vyatta_set_comptype + case $_vyatta_comptype in + 'imagefiles') + _has_comptype=1 + _vyatta_image_file_complete + ;; + *) + _has_comptype=0 + if [[ -z "$current_word" ]]; then + _vyatta_op_set_completions $cur + else + _vyatta_op_set_completions $current_prefix + fi + ;; + esac + fi + if [[ $_has_comptype == 1 ]]; then + COMPREPLY=( "${_vyatta_op_completions[@]}" ) + else + COMPREPLY=($( compgen -W "${_vyatta_op_completions[*]}" -- $current_prefix )) + fi + + # if the last command line arg is empty and we have + # an empty completion option (meaning wild card), + # append a blank(s) to the completion array to force ambiguity + if [ -z "$current_prefix" -a -n "$current_word" ] || + [[ "${COMPREPLY[0]}" =~ "$cur" ]]; then + for comp ; do + if [ -z "$comp" ] ; then + if [ ${#COMPREPLY[@]} -eq 0 ] ; then + COMPREPLY=( " " "" ) + elif _vyatta_op_comprely_needs_ambiguity ; then + COMPREPLY+=( " " ) + fi + fi + done + fi + # Set this environment to enable and disable debugging on the fly + if [[ $DBG_OP_COMPS -eq 1 ]]; then + echo -e "\nCurrent: '$cur'" + echo -e "Current word: '$current_word'" + echo -e "Current prefix: '$current_prefix'" + echo "Number of comps: ${#_vyatta_op_completions[*]}" + echo "Number of non-comps: ${#_vyatta_op_noncompletions[*]}" + echo "_vyatta_op_completions: '${_vyatta_op_completions[*]}'" + echo "COMPREPLY: '${COMPREPLY[@]}'" + echo "CWORD: $COMP_CWORD" + echo "Last comp: '$_vyatta_op_last_comp'" + echo -e "Current comp: '${COMP_WORDS[*]:0:$[$COMP_CWORD+1]}'\n" + fi + + # This is non obvious... + # To have completion continue to work when working with words that aren't the last word, + # we have to set nospace at the beginning of this script and then append the spaces here. + if [ ${#COMPREPLY[@]} -eq 1 ] && + [[ $_has_comptype -ne 1 ]]; then + COMPREPLY=( "${COMPREPLY[0]} " ) + fi + # if there are no completions then handle invalid commands + if [ ${#_vyatta_op_noncompletions[@]} -eq 0 ] && + [ ${#_vyatta_op_completions[@]} -eq 0 ]; then + _vyatta_op_invalid_completion + COMPREPLY=( "" " " ) + _vyatta_op_last_comp=${_vyatta_op_last_comp_init} + elif [ ${#COMPREPLY[@]} -eq 0 ] && + [ -n "$current_prefix" ]; then + _vyatta_op_invalid_completion + COMPREPLY=( "" " " ) + _vyatta_op_last_comp=${_vyatta_op_last_comp_init} + # Stop completions from getting stuck + elif [ ${#_vyatta_op_completions[@]} -eq 1 ] && + [ -n "$cur" ] && + [[ "${COMPREPLY[0]}" =~ "$cur" ]]; then + _vyatta_op_last_comp=${_vyatta_op_last_comp_init} + elif [ ${#_vyatta_op_completions[@]} -eq 1 ] && + [ -n "$current_prefix" ] && + [[ "${COMPREPLY[0]}" =~ "$current_prefix" ]]; then + _vyatta_op_last_comp=${_vyatta_op_last_comp_init} + # if there are no completions then always show the non-comps + elif [ "${COMP_WORDS[*]:0:$[$COMP_CWORD+1]}" == "$_vyatta_op_last_comp" ] || + [ ${#_vyatta_op_completions[@]} -eq 0 ] || + [ -z "$cur" ]; then + _vyatta_op_help "$current_prefix" \ + "${_vyatta_op_noncompletions[@]}" \ + "${_vyatta_op_completions[@]}" \ + | ${VYATTA_PAGER:-cat} + COMPREPLY=( "" " " ) + _vyatta_op_last_comp=${_vyatta_op_last_comp_init} + else + _vyatta_op_last_comp="${COMP_WORDS[*]:0:$[$COMP_CWORD+1]}" + fi + + eval "$restore_shopts" +} + +# "pipe" functions +count () +{ + wc -l +} + +match () +{ + grep -E -e "$1" +} + +no-match () +{ + grep -E -v -e "$1" +} + +no-more () +{ + cat +} + +strip-private () +{ + ${vyos_libexec_dir}/strip-private.py +} + +commands () +{ + if [ "$_OFR_CONFIGURE" != "" ]; then + if $(cli-shell-api sessionChanged); then + echo "You have uncommited changes, please commit them before using the commands pipe" + else + vyos-config-to-commands + fi + else + echo "commands pipe is not supported in operational mode" + fi +} + +json () +{ + if [ "$_OFR_CONFIGURE" != "" ]; then + if $(cli-shell-api sessionChanged); then + echo "You have uncommited changes, please commit them before using the JSON pipe" + else + vyos-config-to-json + fi + else + echo "JSON pipe is not supported in operational mode" + fi +} + +# pipe command help +# $1: command +_vyatta_pipe_help () +{ + local help="No help text available" + case "$1" in + count) help="Count the number of lines in the output";; + match) help="Only output lines that match specified pattern";; + no-match) help="Only output lines that do not match specified pattern";; + more) help="Paginate the output";; + no-more) help="Do not paginate the output";; + strip-private) help="Remove private information from the config";; + commands) help="Convert config to set commands";; + json) help="Convert config to JSON";; + '<pattern>') help="Pattern for matching";; + esac + echo -n "$help" +} + +_vyatta_do_pipe_help () +{ + local help='' + if (( ${#_vyatta_pipe_completions[@]} + ${#_vyatta_pipe_noncompletions[@]} + == 0 )); then + return + fi + echo -en "\nPossible completions:" + for comp in "${_vyatta_pipe_completions[@]}" \ + "${_vyatta_pipe_noncompletions[@]}"; do + _vyatta_op_print_help "$comp" "$(_vyatta_pipe_help "$comp")" + done +} + +# pipe completion +# $@: words +_vyatta_pipe_completion () +{ + local -a pipe_cmd=() + local -a all_cmds=( 'count' 'match' 'no-match' 'more' 'no-more' 'strip-private' 'commands' 'json' ) + local found=0 + _vyatta_pipe_completions=() + _vyatta_pipe_noncompletions=() + + for word in "$@"; do + if [[ "$found" == "1" || "$word" == "|" ]]; then + pipe_cmd+=( "$word" ) + found=1 + fi + done + if (( found == 0 )); then + return 1 + fi + if (( ${#pipe_cmd[@]} == 1 )); then + # "|" only + _vyatta_pipe_completions=( "${all_cmds[@]}" ) + return 0 + fi + if (( ${#pipe_cmd[@]} == 2 )); then + # "|<space, chars, or space+chars>" + _vyatta_pipe_completions=($(compgen -W "${all_cmds[*]}" -- ${pipe_cmd[1]})) + return 0 + fi + if (( ${#pipe_cmd[@]} == 3 )); then + # "|<chars or space+chars><space or space+chars>" + case "${pipe_cmd[1]}" in + match|no-match) _vyatta_pipe_noncompletions=( '<pattern>' );; + esac + return 0 + fi + return 0 +} + +# comptype +_vyatta_set_comptype () +{ + local comptype + unset _vyatta_comptype + for ndef in ${_vyatta_op_node_path}/*/node.def ; do + if [[ $ndef == */node.tag/node.def ]] ; then + local comptype=$( _vyatta_op_get_node_def_field $ndef comptype ) + if [[ $comptype == "imagefiles" ]] ; then + _vyatta_comptype=$comptype + return 0 + else + _vyatta_comptype="" + return 1 + fi + else + _vyatta_comptype="" + return 1 + fi + done +} + +_filedir_xspec_vyos() +{ + local cur prev words cword + _init_completion || return + + _tilde "$cur" || return 0 + + local IFS=$'\n' xspec=${_xspec[${1##*/}]} tmp + local -a toks + + toks=( $( + compgen -d -- "$(quote_readline "$cur")" | { + while read -r tmp; do + printf '%s\n' $tmp + done + } + )) + + # Munge xspec to contain uppercase version too + # http://thread.gmane.org/gmane.comp.shells.bash.bugs/15294/focus=15306 + eval xspec="${xspec}" + local matchop=! + if [[ $xspec == !* ]]; then + xspec=${xspec#!} + matchop=@ + fi + xspec="$matchop($xspec|${xspec^^})" + + toks+=( $( + eval compgen -f -X "!$xspec" -- "\$(quote_readline "\$cur")" | { + while read -r tmp; do + [[ -n $tmp ]] && printf '%s\n' $tmp + done + } + )) + + if [[ ${#toks[@]} -ne 0 ]]; then + compopt -o filenames + COMPREPLY=( "${toks[@]}" ) + fi +} + +nullglob_save=$( shopt -p nullglob ) +shopt -s nullglob +for f in ${vyatta_datadir}/vyatta-op/functions/allowed/* ; do + source $f +done +eval $nullglob_save +unset nullglob_save + +# don't initialize if we are in configure mode +if [ "$_OFR_CONFIGURE" == "ok" ]; then + return 0 +fi + +if [[ "$VYATTA_USER_LEVEL_DIR" != "/opt/vyatta/etc/shell/level/admin" ]]; then + vyatta_unpriv_init $@ +else + _vyatta_op_init $@ +fi + +### Local Variables: +### mode: shell-script +### End: diff --git a/src/etc/commit/post-hooks.d/00vyos-sync b/src/etc/commit/post-hooks.d/00vyos-sync new file mode 100644 index 0000000..8ec732d --- /dev/null +++ b/src/etc/commit/post-hooks.d/00vyos-sync @@ -0,0 +1,7 @@ +#!/bin/sh +# When power is lost right after a commit modified files, the +# system can be corrupted and e.g. login is no longer possible. +# Always sync files to the backend storage after a commit. +# https://vyos.dev/T4975 +sync + diff --git a/src/etc/cron.d/vyos-geoip b/src/etc/cron.d/vyos-geoip new file mode 100644 index 0000000..9bb38a8 --- /dev/null +++ b/src/etc/cron.d/vyos-geoip @@ -0,0 +1 @@ +30 4 * * 1 root sg vyattacfg "/usr/libexec/vyos/geoip-update.py --force" >/tmp/geoip-update.log 2>&1 diff --git a/src/etc/default/vyatta b/src/etc/default/vyatta new file mode 100644 index 0000000..e5fa3bb --- /dev/null +++ b/src/etc/default/vyatta @@ -0,0 +1,217 @@ +# **** License **** +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# This code was originally developed by Vyatta, Inc. +# Portions created by Vyatta are Copyright (C) 2006, 2007 Vyatta, Inc. +# All Rights Reserved. + +# declare configured Vyatta shell environment variables + +# first set vars per args of the "source /etc/default/vyatta VAR=FOO" +_vyatta_extglob=$(shopt -p extglob) +shopt -s extglob +for arg ; do + [[ $arg == *=* ]] && \ + eval declare -x $arg +done +eval $_vyatta_extglob +unset _vyatta_extglob + +{ + # These declarations must go within braces in order to be able to silence + # readonly variable errors. + + for var in prefix exec_prefix datarootdir ; do + eval test -n \"\$$var\" \&\& _vyatta_save_$var=\$$var + done + + prefix=/opt/vyatta + exec_prefix=${prefix} + datarootdir=${prefix}/share + + if test -z "$vyatta_prefix" ; then + if test -n "/opt/vyatta" ; then + declare -x -r vyatta_prefix=/opt/vyatta + declare -x -r vyos_prefix=/opt/vyatta + else + declare -x -r vyatta_prefix=/opt/vyatta + declare -x -r vyos_prefix=/opt/vyatta + fi + fi + if test -z "$vyatta_exec_prefix" ; then + if test -n "${prefix}" ; then + declare -x -r vyatta_prefix=${prefix} + declare -x -r vyos_prefix=${prefix} + else + declare -x -r vyatta_prefix=$vyatta_prefix + declare -x -r vyos_prefix=$vyatta_prefix + fi + fi + if test -z "$vyatta_datarootdir" ; then + if test -n "${prefix}/share" ; then + declare -x -r vyatta_datarootdir=${prefix}/share + declare -x -r vyos_datarootdir=${prefix}/share + else + declare -x -r vyatta_datarootdir=$vyatta_prefix/share + declare -x -r vyos_datarootdir=$vyatta_prefix/share + fi + fi + if test -z "$vyatta_bindir" ; then + if test -n "${exec_prefix}/bin" ; then + declare -x -r vyatta_bindir=${exec_prefix}/bin + else + declare -x -r vyatta_bindir=$vyatta_exec_prefix/bin + fi + fi + if test -z "$vyatta_sbindir" ; then + if test -n "${exec_prefix}/sbin" ; then + declare -x -r vyatta_sbindir=${exec_prefix}/sbin + else + declare -x -r vyatta_sbindir=$vyatta_exec_prefix/sbin + fi + fi + if test -z "$vyatta_libdir" ; then + if test -n "${exec_prefix}/lib" ; then + declare -x -r vyatta_libdir=${exec_prefix}/lib + declare -x -r vyos_libdir=${exec_prefix}/lib + else + declare -x -r vyatta_libdir=$vyatta_exec_prefix/lib + declare -x -r vyos_libdir=$vyatta_exec_prefix/lib + fi + fi + if test -z "$vyatta_libexecdir" ; then + if test -n "${exec_prefix}/libexec" ; then + declare -x -r vyatta_libexecdir=${exec_prefix}/libexec + else + declare -x -r vyatta_libexecdir=$vyatta_exec_prefix/libexec + fi + fi + if test -z "$vyatta_datadir" ; then + if test -n "${datarootdir}" ; then + declare -x -r vyatta_datadir=${datarootdir} + declare -x -r vyos_datadir=${datarootdir} + else + declare -x -r vyatta_datadir=$vyatta_datarootdir + declare -x -r vyos_datadir=$vyatta_datarootdir + fi + fi + if test -z "$vyatta_htmldir" ; then + if test -n "${docdir}" ; then + declare -x -r vyatta_htmldir=${docdir} + else + declare -x -r vyatta_htmldir=$vyatta_datarootdir/html + fi + fi + if test -z "$vyatta_infodir" ; then + if test -n "${prefix}/share/info" ; then + declare -x -r vyatta_infodir=${prefix}/share/info + else + declare -x -r vyatta_infodir=$vyatta_datarootdir/info + fi + fi + if test -z "$vyatta_mandir" ; then + if test -n "${prefix}/share/man" ; then + declare -x -r vyatta_htmldir=${prefix}/share/man + else + declare -x -r vyatta_htmldir=$vyatta_datarootdir/man + fi + fi + if test -z "$vyatta_localedir" ; then + if test -n "${datarootdir}/locale" ; then + declare -x -r vyatta_localedir=${datarootdir}/locale + else + declare -x -r vyatta_localedir=$vyatta_datarootdir/locale + fi + fi + if test -z "$vyatta_localstatedir" ; then + if test -n "${prefix}/var" ; then + declare -x -r vyatta_localstatedir=${prefix}/var + else + declare -x -r vyatta_localstatedir=$vyatta_prefix/var + fi + fi + if test -z "$vyatta_sharedstatedir" ; then + if test -n "${prefix}/com" ; then + declare -x -r vyatta_sharedstatedir=${prefix}/com + else + declare -x -r vyatta_sharedstatedir=$vyatta_prefix/com + fi + fi + if test -z "$vyatta_sysconfdir" ; then + if test -n "${prefix}/etc" ; then + declare -x -r vyatta_sysconfdir=${prefix}/etc + else + declare -x -r vyatta_sysconfdir=$vyatta_prefix/etc + fi + fi + if test -z "$vyatta_op_templates" ; then + declare -x -r vyatta_op_templates=$vyatta_datadir/vyatta-op/templates + declare -x -r vyos_op_templates=$vyatta_datadir/vyatta-op/templates + fi + if test -z "$vyatta_cfg_templates" ; then + declare -x -r vyatta_cfg_templates=$vyatta_datadir/vyatta-cfg/templates + declare -x -r vyos_cfg_templates=$vyatta_datadir/vyatta-cfg/templates + fi + if test -z "$vyatta_configdir" ; then + declare -x -r vyatta_configdir=$vyatta_prefix/config + declare -x -r vyos_configdir=$vyatta_prefix/config + fi + + for var in prefix exec_prefix datarootdir ; do + eval test -n \"\$_vyatta_save_$var\" \&\& $var=\$_vyatta_save_$var + done + + # It's not like we do, or should support installing VyOS at a different prefix + declare -x -r vyos_libexec_dir=/usr/libexec/vyos + declare -x -r vyos_bin_dir=/usr/bin + declare -x -r vyos_sbin_dir=/usr/sbin + declare -x -r vyos_share_dir=/usr/share + + if test -z "$vyos_conf_scripts_dir" ; then + declare -x -r vyos_conf_scripts_dir=$vyos_libexec_dir/conf_mode + fi + if test -z "$vyos_op_scripts_dir" ; then + declare -x -r vyos_op_scripts_dir=$vyos_libexec_dir/op_mode + fi + if test -z "$vyos_completion_dir" ; then + declare -x -r vyos_completion_dir=$vyos_libexec_dir/completion + fi + if test -z "$vyos_validators_dir" ; then + declare -x -r vyos_validators_dir=$vyos_libexec_dir/validators + fi + if test -z "$vyos_data_dir" ; then + declare -x -r vyos_data_dir=$vyos_share_dir/vyos + fi + if test -z "$vyos_persistence_dir" ; then + UNION_NAME=$(cat /proc/cmdline | sed -e s+^.*vyos-union=++ | sed -e 's/ .*$//') + declare -x -r vyos_persistence_dir="/usr/lib/live/mount/persistence/${UNION_NAME}" + fi + if test -z "$vyos_rootfs_dir" ; then + ROOTFS=$(mount -t squashfs | grep loop0 | cut -d' ' -f3) + declare -x -r vyos_rootfs_dir="${ROOTFS}" + fi + if test -z "$VRF" ; then + VRF=$(ip vrf identify) + [ -n "$VRF" ] && declare -x -r VRF="${VRF}" + fi + if test -z "$NETNS" ; then + NETNS=$(ip netns identify) + [ -n "$NETNS" ] && declare -x -r NETNS="${NETNS}" + fi + +} 2>/dev/null || : + +[ -r /etc/default/vyatta-cfg ] && source /etc/default/vyatta-cfg + +[ -r /etc/default/vyatta-local-env ] && source /etc/default/vyatta-local-env + +### Local Variables: +### mode: shell-script +### End: diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/01-vyos-logging b/src/etc/dhcp/dhclient-enter-hooks.d/01-vyos-logging new file mode 100644 index 0000000..121fb21 --- /dev/null +++ b/src/etc/dhcp/dhclient-enter-hooks.d/01-vyos-logging @@ -0,0 +1,20 @@ +# enable logging +LOG_ENABLE="yes" +LOG_STDERR="no" +LOG_TAG="dhclient-script-vyos" + +function logmsg () { + # log message to journal + case $1 in + error) LOG_PRIO="daemon.err" ;; + info) LOG_PRIO="daemon.info" ;; + esac + + if [ "${LOG_ENABLE}" == "yes" ] ; then + if [ "${LOG_STDERR}" == "yes" ] ; then + /usr/bin/logger -e --id=$$ -s -p ${LOG_PRIO} -t ${LOG_TAG} "${@:2}" + else + /usr/bin/logger -e --id=$$ -p ${LOG_PRIO} -t ${LOG_TAG} "${@:2}" + fi + fi +} diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/02-vyos-stopdhclient b/src/etc/dhcp/dhclient-enter-hooks.d/02-vyos-stopdhclient new file mode 100644 index 0000000..ae6bf9f --- /dev/null +++ b/src/etc/dhcp/dhclient-enter-hooks.d/02-vyos-stopdhclient @@ -0,0 +1,38 @@ +# skip all of this if dhclient-script running by stop command defined below +if [ -z ${CONTROLLED_STOP} ] ; then + # stop dhclient for this interface, if it is not current one + # get PID for current dhclient + current_dhclient=`ps --no-headers --format ppid --pid $$ | awk '{ print \$1 }'` + + # get PID for master process (current can be a fork) + master_dhclient=`ps --no-headers --format ppid --pid $current_dhclient | awk '{ print \$1 }'` + + # get IP version for current dhclient + ipversion_arg=`ps --no-headers --format args --pid $current_dhclient | awk 'match(\$0, /\s-(4|6)\s/, IPV) { printf("%s", IPV[1]) }'` + + # get list of all dhclient running for current interface + if [[ $ipversion_arg == "6" ]]; then + dhclients_pids=(`pgrep -f "dhclient.*\s-6\s.*\s$interface(\s|$)"`) + else + dhclients_pids=(`ps --no-headers --format pid,args -C dhclient | awk "{ if(match(\\$0, /\s${interface}(\s|$)/) && !match(\\$0, /\s-6\s/)) printf(\"%s\n\", \\$1) }"`) + fi + + logmsg info "Current dhclient PID: $current_dhclient, Parent PID: $master_dhclient, IP version: $ipversion_arg, All dhclients for interface $interface: ${dhclients_pids[@]}" + # stop all dhclients for current interface, except current one + for dhclient in ${dhclients_pids[@]}; do + if ([ $dhclient -ne $current_dhclient ] && [ $dhclient -ne $master_dhclient ]); then + # get path to PID-file of dhclient process + local dhclient_pidfile=`ps --no-headers --format args --pid $dhclient | awk 'match(\$0, ".*-pf (/.*pid) .*", PF) { print PF[1] }'` + # get path to lease-file of dhclient process + local dhclient_leasefile=`ps --no-headers --format args --pid $dhclient | awk 'match(\$0, ".*-lf (/\\\S*leases) .*", LF) { print LF[1] }'` + # stop dhclient with native command - this will run dhclient-script with correct reason unlike simple kill + logmsg info "Stopping dhclient with PID: ${dhclient}, PID file: ${dhclient_pidfile}, Leases file: ${dhclient_leasefile}" + if [[ -e $dhclient_pidfile ]]; then + dhclient -e CONTROLLED_STOP=yes -x -pf $dhclient_pidfile -lf $dhclient_leasefile + else + logmsg error "PID file $dhclient_pidfile does not exists, killing dhclient with SIGTERM signal" + kill -s 15 ${dhclient} + fi + fi + done +fi diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper new file mode 100644 index 0000000..2a1c5a7 --- /dev/null +++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper @@ -0,0 +1,110 @@ +# redefine ip command to use FRR when it is available + +# default route distance +IF_METRIC=${IF_METRIC:-210} + +# Check if interface is inside a VRF +VRF_OPTION=$(/usr/sbin/ip -j -d link show ${interface} | awk '{if(match($0, /.*"master":"(\w+)".*"info_slave_kind":"vrf"/, IFACE_DETAILS)) printf("vrf %s", IFACE_DETAILS[1])}') + +# get status of FRR +function frr_alive () { + /usr/lib/frr/watchfrr.sh all_status + if [ "$?" -eq "0" ] ; then + logmsg info "FRR status: running" + return 0 + else + logmsg info "FRR status: not running" + return 1 + fi +} + +# convert ip route command to vtysh +function iptovtysh () { + # prepare variables for vtysh command + local VTYSH_ACTION=$3 + local VTYSH_NETADDR="" + local VTYSH_GATEWAY="" + local VTYSH_DEV="" + local VTYSH_TAG="210" + local VTYSH_DISTANCE=$IF_METRIC + # convert default route to 0.0.0.0/0 + if [ "$4" == "default" ] ; then + VTYSH_NETADDR="0.0.0.0/0" + else + VTYSH_NETADDR=$4 + fi + # add /32 to ip addresses without netmasks + if [[ ! $VTYSH_NETADDR =~ ^.*/[[:digit:]]+$ ]] ; then + VTYSH_NETADDR="$VTYSH_NETADDR/32" + fi + shift 4 + # get gateway address + if [ "$1" == "via" ] ; then + VTYSH_GATEWAY=$2 + shift 2 + fi + # get device name + if [ "$1" == "dev" ]; then + VTYSH_DEV=$2 + shift 2 + fi + # get distance + if [ "$1" == "metric" ]; then + VTYSH_DISTANCE=$2 + shift 2 + fi + + VTYSH_CMD="ip route $VTYSH_NETADDR $VTYSH_GATEWAY $VTYSH_DEV tag $VTYSH_TAG $VTYSH_DISTANCE $VRF_OPTION" + + # delete route if the command is "del" + if [ "$VTYSH_ACTION" == "del" ] ; then + VTYSH_CMD="no $VTYSH_CMD" + fi + logmsg info "Converted vtysh command: \"$VTYSH_CMD\"" +} + +# delete the same route from kernel before adding new one +function delroute () { + logmsg info "Checking if the route presented in kernel: $@ $VRF_OPTION" + if /usr/sbin/ip route show $@ $VRF_OPTION | grep -qx "$1 " ; then + logmsg info "Deleting IP route: \"/usr/sbin/ip route del $@ $VRF_OPTION\"" + /usr/sbin/ip route del $@ $VRF_OPTION + fi +} + +# try to communicate with vtysh +function vtysh_conf () { + # perform 10 attempts with 1 second delay for retries + for i in {1..10} ; do + if vtysh -c "conf t" -c "$1" ; then + logmsg info "Command was executed successfully via vtysh: \"$1\"" + return 0 + else + logmsg info "Failed to send command to vtysh, retrying in 1 second" + sleep 1 + fi + done + logmsg error "Failed to execute command via vtysh after 10 attempts: \"$1\"" + return 1 +} + +# replace ip command with this wrapper +function ip () { + # pass comand to system `ip` if this is not related to routes change + if [ "$2" != "route" ] ; then + logmsg info "Passing command to /usr/sbin/ip: \"$@\"" + /usr/sbin/ip $@ + else + # if we want to work with routes, try to use FRR first + if frr_alive ; then + delroute ${@:4} + iptovtysh $@ + logmsg info "Sending command to vtysh" + vtysh_conf "$VTYSH_CMD" + else + # add ip route to kernel + logmsg info "Modifying routes in kernel: \"/usr/sbin/ip $@\"" + /usr/sbin/ip $@ $VRF_OPTION + fi + fi +} diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf b/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf new file mode 100644 index 0000000..9a8a53b --- /dev/null +++ b/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf @@ -0,0 +1,32 @@ +# modified make_resolv_conf() for VyOS +# should be used only if vyos-hostsd is running + +if /usr/bin/systemctl -q is-active vyos-hostsd; then + make_resolv_conf() { + hostsd_client="/usr/bin/vyos-hostsd-client" + hostsd_changes= + + if [ -n "$new_domain_name" ]; then + logmsg info "Deleting search domains with tag \"dhcp-$interface\" via vyos-hostsd-client" + $hostsd_client --delete-search-domains --tag "dhcp-$interface" + logmsg info "Adding domain name \"$new_domain_name\" as search domain with tag \"dhcp-$interface\" via vyos-hostsd-client" + $hostsd_client --add-search-domains "$new_domain_name" --tag "dhcp-$interface" + hostsd_changes=y + fi + + if [ -n "$new_domain_name_servers" ]; then + logmsg info "Deleting nameservers with tag \"dhcp-$interface\" via vyos-hostsd-client" + $hostsd_client --delete-name-servers --tag "dhcp-$interface" + logmsg info "Adding nameservers \"$new_domain_name_servers\" with tag \"dhcp-$interface\" via vyos-hostsd-client" + $hostsd_client --add-name-servers $new_domain_name_servers --tag "dhcp-$interface" + hostsd_changes=y + fi + + if [ $hostsd_changes ]; then + logmsg info "Applying changes via vyos-hostsd-client" + $hostsd_client --apply + else + logmsg info "No changes to apply via vyos-hostsd-client" + fi + } +fi diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/05-vyos-mtureplace b/src/etc/dhcp/dhclient-enter-hooks.d/05-vyos-mtureplace new file mode 100644 index 0000000..4a08765 --- /dev/null +++ b/src/etc/dhcp/dhclient-enter-hooks.d/05-vyos-mtureplace @@ -0,0 +1,38 @@ +# replace MTU with value from configuration + +# get MTU value via Python +# as configuration is not available to cli-shell-api at the first boot, we must use vyos.config, which contain workaround for this, instead clean shell +function get_mtu { +python3 - <<PYEND +from vyos.config import Config +import os +import re + +# check if interface is not VLAN and fix name if necessary +interface_name = os.getenv('interface', '') +regex_filter = re.compile('^(?P<interface>\w+)\.(?P<vid>\d+)$') +if regex_filter.search(interface_name): + iface = regex_filter.search(interface_name).group('interface') + vid = regex_filter.search(interface_name).group('vid') + interface_name = "{} vif {}".format(iface, vid) + +# initialize config +config = Config() +if config.exists('interfaces'): + iface_types = config.list_nodes('interfaces') + for iface_type in iface_types: + # check if configuration contain MTU value for interface and return (print) it + if config.exists("interfaces {} {} mtu".format(iface_type, interface_name)): + print(format(config.return_value("interfaces {} {} mtu".format(iface_type, interface_name)))) +PYEND +} + +# check if DHCP server return MTU value +if [ -n "$new_interface_mtu" ]; then + # try to get MTU from config and replace original one + configured_mtu="$(get_mtu)" + if [[ -n $configured_mtu ]] ; then + logmsg info "Replacing MTU value for $interface with preconfigured one: $configured_mtu" + new_interface_mtu="$configured_mtu" + fi +fi diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/99-run-user-hooks b/src/etc/dhcp/dhclient-enter-hooks.d/99-run-user-hooks new file mode 100644 index 0000000..570758b --- /dev/null +++ b/src/etc/dhcp/dhclient-enter-hooks.d/99-run-user-hooks @@ -0,0 +1,5 @@ +#!/bin/bash +DHCP_PRE_HOOKS="/config/scripts/dhcp-client/pre-hooks.d/" +if [ -d "${DHCP_PRE_HOOKS}" ] ; then + run_hookdir "${DHCP_PRE_HOOKS}" +fi diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup new file mode 100644 index 0000000..da1bda1 --- /dev/null +++ b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup @@ -0,0 +1,115 @@ +## +## VyOS cleanup +## +# NOTE: here we use 'ip' wrapper, therefore a route will be actually deleted via /usr/sbin/ip or vtysh, according to the system state +hostsd_client="/usr/bin/vyos-hostsd-client" +hostsd_changes= +# check vyos-hostsd status +/usr/bin/systemctl -q is-active vyos-hostsd +hostsd_status=$? + +if [[ $reason =~ ^(EXPIRE|FAIL|RELEASE|STOP)$ ]]; then + if [[ $hostsd_status -eq 0 ]]; then + # delete search domains and nameservers via vyos-hostsd + logmsg info "Deleting search domains with tag \"dhcp-$interface\" via vyos-hostsd-client" + $hostsd_client --delete-search-domains --tag "dhcp-$interface" + logmsg info "Deleting nameservers with tag \"dhcp-${interface}\" via vyos-hostsd-client" + $hostsd_client --delete-name-servers --tag "dhcp-${interface}" + hostsd_changes=y + fi + + if_metric="$IF_METRIC" + + # try to delete default ip route + for router in $old_routers; do + # check if we are bound to a VRF + local vrf_name=$(basename /sys/class/net/${interface}/upper_* | sed -e 's/upper_//') + if [ "$vrf_name" != "*" ]; then + vrf="vrf $vrf_name" + fi + + logmsg info "Deleting default route: via $router dev ${interface} ${if_metric:+metric $if_metric} ${vrf}" + ip -4 route del default via $router dev ${interface} ${if_metric:+metric $if_metric} ${vrf} + + if_metric=$((if_metric+1)) + done + + # delete rfc3442 routes + if [ -n "$old_rfc3442_classless_static_routes" ]; then + set -- $old_rfc3442_classless_static_routes + while [ $# -gt 0 ]; do + net_length=$1 + via_arg='' + case $net_length in + 32|31|30|29|28|27|26|25) + if [ $# -lt 9 ]; then + return 1 + fi + net_address="${2}.${3}.${4}.${5}" + gateway="${6}.${7}.${8}.${9}" + shift 9 + ;; + 24|23|22|21|20|19|18|17) + if [ $# -lt 8 ]; then + return 1 + fi + net_address="${2}.${3}.${4}.0" + gateway="${5}.${6}.${7}.${8}" + shift 8 + ;; + 16|15|14|13|12|11|10|9) + if [ $# -lt 7 ]; then + return 1 + fi + net_address="${2}.${3}.0.0" + gateway="${4}.${5}.${6}.${7}" + shift 7 + ;; + 8|7|6|5|4|3|2|1) + if [ $# -lt 6 ]; then + return 1 + fi + net_address="${2}.0.0.0" + gateway="${3}.${4}.${5}.${6}" + shift 6 + ;; + 0) # default route + if [ $# -lt 5 ]; then + return 1 + fi + net_address="0.0.0.0" + gateway="${2}.${3}.${4}.${5}" + shift 5 + ;; + *) # error + return 1 + ;; + esac + # take care of link-local routes + if [ "${gateway}" != '0.0.0.0' ]; then + via_arg="via ${gateway}" + fi + # delete route (ip detects host routes automatically) + ip -4 route del "${net_address}/${net_length}" \ + ${via_arg} dev "${interface}" >/dev/null 2>&1 + done + fi +fi + +if [[ $reason =~ ^(EXPIRE6|RELEASE6|STOP6)$ ]]; then + if [[ $hostsd_status -eq 0 ]]; then + # delete search domains and nameservers via vyos-hostsd + logmsg info "Deleting search domains with tag \"dhcpv6-$interface\" via vyos-hostsd-client" + $hostsd_client --delete-search-domains --tag "dhcpv6-$interface" + logmsg info "Deleting nameservers with tag \"dhcpv6-${interface}\" via vyos-hostsd-client" + $hostsd_client --delete-name-servers --tag "dhcpv6-${interface}" + hostsd_changes=y + fi +fi + +if [ $hostsd_changes ]; then + logmsg info "Applying changes via vyos-hostsd-client" + $hostsd_client --apply +else + logmsg info "No changes to apply via vyos-hostsd-client" +fi diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/02-vyos-dhcp-renew-rfc3442 b/src/etc/dhcp/dhclient-exit-hooks.d/02-vyos-dhcp-renew-rfc3442 new file mode 100644 index 0000000..9202fe7 --- /dev/null +++ b/src/etc/dhcp/dhclient-exit-hooks.d/02-vyos-dhcp-renew-rfc3442 @@ -0,0 +1,148 @@ +# support for RFC3442 routes in DHCP RENEW + +function convert_to_cidr () { + cidr="" + set -- $1 + while [ $# -gt 0 ]; do + net_length=$1 + + case $net_length in + 32|31|30|29|28|27|26|25) + if [ $# -lt 9 ]; then + return 1 + fi + net_address="${2}.${3}.${4}.${5}" + gateway="${6}.${7}.${8}.${9}" + shift 9 + ;; + 24|23|22|21|20|19|18|17) + if [ $# -lt 8 ]; then + return 1 + fi + net_address="${2}.${3}.${4}.0" + gateway="${5}.${6}.${7}.${8}" + shift 8 + ;; + 16|15|14|13|12|11|10|9) + if [ $# -lt 7 ]; then + return 1 + fi + net_address="${2}.${3}.0.0" + gateway="${4}.${5}.${6}.${7}" + shift 7 + ;; + 8|7|6|5|4|3|2|1) + if [ $# -lt 6 ]; then + return 1 + fi + net_address="${2}.0.0.0" + gateway="${3}.${4}.${5}.${6}" + shift 6 + ;; + 0) # default route + if [ $# -lt 5 ]; then + return 1 + fi + net_address="0.0.0.0" + gateway="${2}.${3}.${4}.${5}" + shift 5 + ;; + *) # error + return 1 + ;; + esac + + cidr+="${net_address}/${net_length}:${gateway} " + done +} + +# main script starts here + +RUN="yes" + +if [ "$RUN" = "yes" ]; then + convert_to_cidr "$old_rfc3442_classless_static_routes" + old_cidr=$cidr + convert_to_cidr "$new_rfc3442_classless_static_routes" + new_cidr=$cidr + + if [ "$reason" = "RENEW" ]; then + if [ "$new_rfc3442_classless_static_routes" != "$old_rfc3442_classless_static_routes" ]; then + logmsg info "RFC3442 route change detected, old_routes: $old_rfc3442_classless_static_routes" + logmsg info "RFC3442 route change detected, new_routes: $new_rfc3442_classless_static_routes" + if [ -z "$new_rfc3442_classless_static_routes" ]; then + # delete all routes from the old_rfc3442_classless_static_routes + for route in $old_cidr; do + network=$(printf "${route}" | awk -F ":" '{print $1}') + gateway=$(printf "${route}" | awk -F ":" '{print $2}') + # take care of link-local routes + if [ "${gateway}" != '0.0.0.0' ]; then + via_arg="via ${gateway}" + else + via_arg="" + fi + ip -4 route del "${network}" "${via_arg}" dev "${interface}" >/dev/null 2>&1 + done + elif [ -z "$old_rfc3442_classless_static_routes" ]; then + # add all routes from the new_rfc3442_classless_static_routes + for route in $new_cidr; do + network=$(printf "${route}" | awk -F ":" '{print $1}') + gateway=$(printf "${route}" | awk -F ":" '{print $2}') + # take care of link-local routes + if [ "${gateway}" != '0.0.0.0' ]; then + via_arg="via ${gateway}" + else + via_arg="" + fi + ip -4 route add "${network}" "${via_arg}" dev "${interface}" >/dev/null 2>&1 + done + else + # update routes + # delete old + for old_route in $old_cidr; do + match="false" + for new_route in $new_cidr; do + if [[ "$old_route" == "$new_route" ]]; then + match="true" + break + fi + done + if [[ "$match" == "false" ]]; then + # delete old_route + network=$(printf "${old_route}" | awk -F ":" '{print $1}') + gateway=$(printf "${old_route}" | awk -F ":" '{print $2}') + # take care of link-local routes + if [ "${gateway}" != '0.0.0.0' ]; then + via_arg="via ${gateway}" + else + via_arg="" + fi + ip -4 route del "${network}" "${via_arg}" dev "${interface}" >/dev/null 2>&1 + fi + done + # add new + for new_route in $new_cidr; do + match="false" + for old_route in $old_cidr; do + if [[ "$new_route" == "$old_route" ]]; then + match="true" + break + fi + done + if [[ "$match" == "false" ]]; then + # add new_route + network=$(printf "${new_route}" | awk -F ":" '{print $1}') + gateway=$(printf "${new_route}" | awk -F ":" '{print $2}') + # take care of link-local routes + if [ "${gateway}" != '0.0.0.0' ]; then + via_arg="via ${gateway}" + else + via_arg="" + fi + ip -4 route add "${network}" "${via_arg}" dev "${interface}" >/dev/null 2>&1 + fi + done + fi + fi + fi +fi diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/03-vyos-dhclient-hook b/src/etc/dhcp/dhclient-exit-hooks.d/03-vyos-dhclient-hook new file mode 100644 index 0000000..d5e6462 --- /dev/null +++ b/src/etc/dhcp/dhclient-exit-hooks.d/03-vyos-dhclient-hook @@ -0,0 +1,46 @@ +#!/bin/sh + +# Author: Stig Thormodsrud <stig@vyatta.com> +# Date: 2007 +# Description: dhcp client hook + +# **** License **** +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# This code was originally developed by Vyatta, Inc. +# Portions created by Vyatta are Copyright (C) 2006, 2007, 2008 Vyatta, Inc. +# All Rights Reserved. +# **** End License **** + +# To enable this script set the following variable to "yes" +RUN="yes" + +proto="" +if [[ $reason =~ ^(REBOOT6|INIT6|EXPIRE6|RELEASE6|STOP6|INFORM6|BOUND6|REBIND6|DELEGATED6)$ ]]; then + proto="v6" +fi + +if [ "$RUN" = "yes" ]; then + BASE_PATH=$(python3 -c "from vyos.defaults import directories; print(directories['isc_dhclient_dir'])") + mkdir -p ${BASE_PATH} + LOG=${BASE_PATH}/dhclient_"$interface"."$proto"lease + echo `date` > $LOG + + for i in reason interface new_expiry new_dhcp_lease_time medium \ + alias_ip_address new_ip_address new_broadcast_address \ + new_subnet_mask new_domain_name new_network_number \ + new_domain_name_servers new_routers new_static_routes \ + new_dhcp_server_identifier new_dhcp_message_type \ + old_ip_address old_subnet_mask old_domain_name \ + old_domain_name_servers old_routers \ + old_static_routes; do + echo $i=\'${!i}\' >> $LOG + done +fi diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/98-run-user-hooks b/src/etc/dhcp/dhclient-exit-hooks.d/98-run-user-hooks new file mode 100644 index 0000000..910b586 --- /dev/null +++ b/src/etc/dhcp/dhclient-exit-hooks.d/98-run-user-hooks @@ -0,0 +1,5 @@ +#!/bin/bash +DHCP_POST_HOOKS="/config/scripts/dhcp-client/post-hooks.d/" +if [ -d "${DHCP_POST_HOOKS}" ] ; then + run_hookdir "${DHCP_POST_HOOKS}" +fi diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook b/src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook new file mode 100644 index 0000000..57f8030 --- /dev/null +++ b/src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook @@ -0,0 +1,45 @@ +#!/bin/bash +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +DHCP_HOOK_IFLIST="/tmp/ipsec_dhcp_interfaces" + +if ! { [ -f $DHCP_HOOK_IFLIST ] && grep -qw $interface $DHCP_HOOK_IFLIST; }; then + return 0 +fi + +# Re-generate the config on the following events: +# - BOUND: always re-generate +# - RENEW: re-generate if the IP address changed +# - REBIND: re-generate if the IP address changed +if [ "$reason" == "RENEW" ] || [ "$reason" == "REBIND" ]; then + if [ "$old_ip_address" == "$new_ip_address" ]; then + return 0 + fi +elif [ "$reason" != "BOUND" ]; then + return 0 +fi + +# Best effort wait for any active commit to finish +sudo python3 - <<PYEND +from vyos.utils.commit import wait_for_commit_lock + +if __name__ == '__main__': + wait_for_commit_lock() + exit(0) +PYEND + +# Now re-generate the config +sudo /usr/libexec/vyos/conf_mode/vpn_ipsec.py diff --git a/src/etc/ipsec.d/key-pair.template b/src/etc/ipsec.d/key-pair.template new file mode 100644 index 0000000..56be975 --- /dev/null +++ b/src/etc/ipsec.d/key-pair.template @@ -0,0 +1,67 @@ +[ req ] + default_bits = 2048 + default_keyfile = privkey.pem + distinguished_name = req_distinguished_name + string_mask = utf8only + attributes = req_attributes + dirstring_type = nobmp +# SHA-1 is deprecated, so use SHA-2 instead. + default_md = sha256 +# Extension to add when the -x509 option is used. + x509_extensions = v3_ca + +[ req_distinguished_name ] + countryName = Country Name (2 letter code) + countryName_min = 2 + countryName_max = 2 + ST = State Name + localityName = Locality Name (eg, city) + organizationName = Organization Name (eg, company) + organizationalUnitName = Organizational Unit Name (eg, department) + commonName = Common Name (eg, Device hostname) + commonName_max = 64 + emailAddress = Email Address + emailAddress_max = 40 +[ req_attributes ] + challengePassword = A challenge password (optional) + challengePassword_min = 4 + challengePassword_max = 20 +[ v3_ca ] + subjectKeyIdentifier=hash + authorityKeyIdentifier=keyid:always,issuer:always + basicConstraints = critical, CA:true + keyUsage = critical, digitalSignature, cRLSign, keyCertSign +[ v3_intermediate_ca ] +# Extensions for a typical intermediate CA (`man x509v3_config`). + subjectKeyIdentifier = hash + authorityKeyIdentifier = keyid:always,issuer + basicConstraints = critical, CA:true, pathlen:0 + keyUsage = critical, digitalSignature, cRLSign, keyCertSign +[ usr_cert ] +# Extensions for client certificates (`man x509v3_config`). + basicConstraints = CA:FALSE + nsCertType = client, email + nsComment = "OpenSSL Generated Client Certificate" + subjectKeyIdentifier = hash + authorityKeyIdentifier = keyid,issuer + keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment + extendedKeyUsage = clientAuth, emailProtection +[ server_cert ] +# Extensions for server certificates (`man x509v3_config`). + basicConstraints = CA:FALSE + nsCertType = server + nsComment = "OpenSSL Generated Server Certificate" + subjectKeyIdentifier = hash + authorityKeyIdentifier = keyid,issuer:always + keyUsage = critical, digitalSignature, keyEncipherment + extendedKeyUsage = serverAuth +[ crl_ext ] +# Extension for CRLs (`man x509v3_config`). + authorityKeyIdentifier=keyid:always +[ ocsp ] +# Extension for OCSP signing certificates (`man ocsp`). + basicConstraints = CA:FALSE + subjectKeyIdentifier = hash + authorityKeyIdentifier = keyid,issuer + keyUsage = critical, digitalSignature + extendedKeyUsage = critical, OCSPSigning diff --git a/src/etc/ipsec.d/vti-up-down b/src/etc/ipsec.d/vti-up-down new file mode 100644 index 0000000..e1765ae --- /dev/null +++ b/src/etc/ipsec.d/vti-up-down @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Script called up strongswan to bring the VTI interface up/down based on +# the state of the IPSec tunnel. Called as vti_up_down vti_intf_name + +import os +import sys + +from syslog import syslog +from syslog import openlog +from syslog import LOG_PID +from syslog import LOG_INFO + +from vyos.configquery import ConfigTreeQuery +from vyos.configdict import get_interface_dict +from vyos.utils.commit import wait_for_commit_lock +from vyos.utils.process import call +from vyos.utils.vti_updown_db import open_vti_updown_db_for_update + +def supply_interface_dict(interface): + # Lazy-load the running config on first invocation + try: + conf = supply_interface_dict.cached_config + except AttributeError: + conf = ConfigTreeQuery() + supply_interface_dict.cached_config = conf + + _, vti = get_interface_dict(conf.config, ['interfaces', 'vti'], interface) + return vti + +if __name__ == '__main__': + verb = os.getenv('PLUTO_VERB') + connection = os.getenv('PLUTO_CONNECTION') + interface = sys.argv[1] + + if verb.endswith('-v6'): + protocol = 'v6' + else: + protocol = 'v4' + + openlog(ident=f'vti-up-down', logoption=LOG_PID, facility=LOG_INFO) + syslog(f'Interface {interface} {verb} {connection}') + + wait_for_commit_lock() + + if verb in ['up-client', 'up-client-v6', 'up-host', 'up-host-v6']: + with open_vti_updown_db_for_update() as db: + db.add(interface, connection, protocol) + db.commit(supply_interface_dict) + elif verb in ['down-client', 'down-client-v6', 'down-host', 'down-host-v6']: + with open_vti_updown_db_for_update() as db: + db.remove(interface, connection, protocol) + db.commit(supply_interface_dict) diff --git a/src/etc/logrotate.d/conntrackd b/src/etc/logrotate.d/conntrackd new file mode 100644 index 0000000..b0b09de --- /dev/null +++ b/src/etc/logrotate.d/conntrackd @@ -0,0 +1,9 @@ +/var/log/conntrackd-stats.log { + weekly + rotate 2 + missingok + + postrotate + systemctl restart conntrackd.service > /dev/null + endscript +} diff --git a/src/etc/logrotate.d/vyos-atop b/src/etc/logrotate.d/vyos-atop new file mode 100644 index 0000000..0c8359c --- /dev/null +++ b/src/etc/logrotate.d/vyos-atop @@ -0,0 +1,20 @@ +/var/log/atop/atop.log { + daily + dateext + dateformat _%Y-%m-%d_%H-%M-%S + maxsize 10M + missingok + nocompress + nocreate + nomail + rotate 10 + prerotate + # stop the service + systemctl stop atop.service + endscript + postrotate + # start atop service again + systemctl start atop.service + endscript +} + diff --git a/src/etc/logrotate.d/vyos-rsyslog b/src/etc/logrotate.d/vyos-rsyslog new file mode 100644 index 0000000..3c087b9 --- /dev/null +++ b/src/etc/logrotate.d/vyos-rsyslog @@ -0,0 +1,12 @@ +/var/log/messages { + create + missingok + nomail + notifempty + rotate 10 + size 1M + postrotate + # inform rsyslog service about rotation + /usr/lib/rsyslog/rsyslog-rotate + endscript +} diff --git a/src/etc/modprobe.d/ifb.conf b/src/etc/modprobe.d/ifb.conf new file mode 100644 index 0000000..2dcfb6a --- /dev/null +++ b/src/etc/modprobe.d/ifb.conf @@ -0,0 +1 @@ +options ifb numifbs=0 diff --git a/src/etc/modprobe.d/openvpn.conf b/src/etc/modprobe.d/openvpn.conf new file mode 100644 index 0000000..a9259fe --- /dev/null +++ b/src/etc/modprobe.d/openvpn.conf @@ -0,0 +1 @@ +blacklist ovpn-dco-v2 diff --git a/src/etc/netplug/linkup.d/vyos-python-helper b/src/etc/netplug/linkup.d/vyos-python-helper new file mode 100644 index 0000000..9c59c58 --- /dev/null +++ b/src/etc/netplug/linkup.d/vyos-python-helper @@ -0,0 +1,4 @@ +#!/bin/sh +PYTHON3=$(which python3) +# Call the real python script and forward commandline arguments +$PYTHON3 /etc/netplug/vyos-netplug-dhcp-client "${@:1}" diff --git a/src/etc/netplug/netplug b/src/etc/netplug/netplug new file mode 100644 index 0000000..60b65e8 --- /dev/null +++ b/src/etc/netplug/netplug @@ -0,0 +1,41 @@ +#!/bin/sh +# +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +dev="$1" +action="$2" + +case "$action" in +in) + run-parts --arg $dev --arg in /etc/netplug/linkup.d + ;; +out) + run-parts --arg $dev --arg out /etc/netplug/linkdown.d + ;; + +# probe loads and initialises the driver for the interface and brings the +# interface into the "up" state, so that it can generate netlink(7) events. +# This interferes with "admin down" for an interface. Thus, commented out. An +# "admin up" is treated as a "link up" and thus, "link up" action is executed. +# To execute "link down" action on "admin down", run appropriate script in +# /etc/netplug/linkdown.d +#probe) +# ;; + +*) + exit 1 + ;; +esac diff --git a/src/etc/netplug/netplugd.conf b/src/etc/netplug/netplugd.conf new file mode 100644 index 0000000..7da3c67 --- /dev/null +++ b/src/etc/netplug/netplugd.conf @@ -0,0 +1,4 @@ +eth* +br* +bond* +wlan* diff --git a/src/etc/netplug/vyos-netplug-dhcp-client b/src/etc/netplug/vyos-netplug-dhcp-client new file mode 100644 index 0000000..55d15a1 --- /dev/null +++ b/src/etc/netplug/vyos-netplug-dhcp-client @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import sys + +from time import sleep + +from vyos.configquery import ConfigTreeQuery +from vyos.ifconfig import Section +from vyos.utils.boot import boot_configuration_complete +from vyos.utils.commit import commit_in_progress +from vyos.utils.process import call +from vyos import airbag +airbag.enable() + +if len(sys.argv) < 3: + airbag.noteworthy("Must specify both interface and link status!") + sys.exit(1) + +if not boot_configuration_complete(): + airbag.noteworthy("System bootup not yet finished...") + sys.exit(1) + +while commit_in_progress(): + sleep(1) + +interface = sys.argv[1] +in_out = sys.argv[2] +config = ConfigTreeQuery() + +interface_path = ['interfaces'] + Section.get_config_path(interface).split() + +for _, interface_config in config.get_config_dict(interface_path).items(): + # Bail out early if we do not have an IP address configured + if 'address' not in interface_config: + continue + # Bail out early if interface ist administrative down + if 'disable' in interface_config: + continue + systemd_action = 'start' + if in_out == 'out': + systemd_action = 'stop' + # Start/Stop DHCP service + if 'dhcp' in interface_config['address']: + call(f'systemctl {systemd_action} dhclient@{interface}.service') + # Start/Stop DHCPv6 service + if 'dhcpv6' in interface_config['address']: + call(f'systemctl {systemd_action} dhcp6c@{interface}.service') diff --git a/src/etc/opennhrp/opennhrp-script.py b/src/etc/opennhrp/opennhrp-script.py new file mode 100644 index 0000000..f6f6d07 --- /dev/null +++ b/src/etc/opennhrp/opennhrp-script.py @@ -0,0 +1,371 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import re +import sys +import vyos.ipsec + +from json import loads +from pathlib import Path + +from vyos.logger import getLogger +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running + +NHRP_CONFIG: str = '/run/opennhrp/opennhrp.conf' + + +def vici_get_ipsec_uniqueid(conn: str, src_nbma: str, + dst_nbma: str) -> list[str]: + """ Find and return IKE SAs by src nbma and dst nbma + + Args: + conn (str): a connection name + src_nbma (str): an IP address of NBMA source + dst_nbma (str): an IP address of NBMA destination + + Returns: + list: a list of IKE connections that match a criteria + """ + if not conn or not src_nbma or not dst_nbma: + logger.error( + f'Incomplete input data for resolving IKE unique ids: ' + f'conn: {conn}, src_nbma: {src_nbma}, dst_nbma: {dst_nbma}') + return [] + + try: + logger.info( + f'Resolving IKE unique ids for: conn: {conn}, ' + f'src_nbma: {src_nbma}, dst_nbma: {dst_nbma}') + list_ikeid: list[str] = [] + list_sa: list = vyos.ipsec.get_vici_sas_by_name(conn, None) + for sa in list_sa: + if sa[conn]['local-host'].decode('ascii') == src_nbma \ + and sa[conn]['remote-host'].decode('ascii') == dst_nbma: + list_ikeid.append(sa[conn]['uniqueid'].decode('ascii')) + return list_ikeid + except Exception as err: + logger.error(f'Unable to find unique ids for IKE: {err}') + return [] + + +def vici_ike_terminate(list_ikeid: list[str]) -> bool: + """Terminating IKE SAs by list of IKE IDs + + Args: + list_ikeid (list[str]): a list of IKE ids to terminate + + Returns: + bool: result of termination action + """ + if not list: + logger.warning('An empty list for termination was provided') + return False + + try: + vyos.ipsec.terminate_vici_ikeid_list(list_ikeid) + return True + except Exception as err: + logger.error(f'Failed to terminate SA for IKE ids {list_ikeid}: {err}') + return False + + +def parse_type_ipsec(interface: str) -> tuple[str, str]: + """Get DMVPN Type and NHRP Profile from the configuration + + Args: + interface (str): a name of interface + + Returns: + tuple[str, str]: `peer_type` and `profile_name` + """ + if not interface: + logger.error('Cannot find peer type - no input provided') + return '', '' + + config_file: str = Path(NHRP_CONFIG).read_text() + regex: str = rf'^interface {interface} #(?P<peer_type>hub|spoke) ?(?P<profile_name>[^\n]*)$' + match = re.search(regex, config_file, re.M) + if match: + return match.groupdict()['peer_type'], match.groupdict()[ + 'profile_name'] + return '', '' + + +def add_peer_route(nbma_src: str, nbma_dst: str, mtu: str) -> None: + """Add a route to a NBMA peer + + Args: + nbma_src (str): a local IP address + nbma_dst (str): a remote IP address + mtu (str): a MTU for a route + """ + logger.info(f'Adding route from {nbma_src} to {nbma_dst} with MTU {mtu}') + # Find routes to a peer + route_get_cmd: str = f'sudo ip --json route get {nbma_dst} from {nbma_src}' + try: + route_info_data = loads(cmd(route_get_cmd)) + except Exception as err: + logger.error(f'Unable to find a route to {nbma_dst}: {err}') + return + + # Check if an output has an expected format + if not isinstance(route_info_data, list): + logger.error( + f'Garbage returned from the "{route_get_cmd}" ' + f'command: {route_info_data}') + return + + # Add static routes to a peer + for route_item in route_info_data: + route_dev = route_item.get('dev') + route_dst = route_item.get('dst') + route_gateway = route_item.get('gateway') + # Prepare a command to add a route + route_add_cmd = 'sudo ip route add' + if route_dst: + route_add_cmd = f'{route_add_cmd} {route_dst}' + if route_gateway: + route_add_cmd = f'{route_add_cmd} via {route_gateway}' + if route_dev: + route_add_cmd = f'{route_add_cmd} dev {route_dev}' + route_add_cmd = f'{route_add_cmd} proto 42 mtu {mtu}' + # Add a route + try: + cmd(route_add_cmd) + except Exception as err: + logger.error( + f'Unable to add a route using command "{route_add_cmd}": ' + f'{err}') + + +def vici_initiate(conn: str, child_sa: str, src_addr: str, + dest_addr: str) -> bool: + """Initiate IKE SA connection with specific peer + + Args: + conn (str): an IKE connection name + child_sa (str): a child SA profile name + src_addr (str): NBMA local address + dest_addr (str): NBMA address of a peer + + Returns: + bool: a result of initiation command + """ + logger.info( + f'Trying to initiate connection. Name: {conn}, child sa: {child_sa}, ' + f'src_addr: {src_addr}, dst_addr: {dest_addr}') + try: + vyos.ipsec.vici_initiate(conn, child_sa, src_addr, dest_addr) + return True + except Exception as err: + logger.error(f'Unable to initiate connection {err}') + return False + + +def vici_terminate(conn: str, src_addr: str, dest_addr: str) -> None: + """Find and terminate IKE SAs by local NBMA and remote NBMA addresses + + Args: + conn (str): IKE connection name + src_addr (str): NBMA local address + dest_addr (str): NBMA address of a peer + """ + logger.info( + f'Terminating IKE connection {conn} between {src_addr} ' + f'and {dest_addr}') + + ikeid_list: list[str] = vici_get_ipsec_uniqueid(conn, src_addr, dest_addr) + + if not ikeid_list: + logger.warning( + f'No active sessions found for IKE profile {conn}, ' + f'local NBMA {src_addr}, remote NBMA {dest_addr}') + else: + try: + vyos.ipsec.terminate_vici_ikeid_list(ikeid_list) + except Exception as err: + logger.error( + f'Failed to terminate SA for IKE ids {ikeid_list}: {err}') + +def iface_up(interface: str) -> None: + """Proceed tunnel interface UP event + + Args: + interface (str): an interface name + """ + if not interface: + logger.warning('No interface name provided for UP event') + + logger.info(f'Turning up interface {interface}') + try: + cmd(f'sudo ip route flush proto 42 dev {interface}') + cmd(f'sudo ip neigh flush dev {interface}') + except Exception as err: + logger.error( + f'Unable to flush route on interface "{interface}": {err}') + + +def peer_up(dmvpn_type: str, conn: str) -> None: + """Proceed NHRP peer UP event + + Args: + dmvpn_type (str): a type of peer + conn (str): an IKE profile name + """ + logger.info(f'Peer UP event for {dmvpn_type} using IKE profile {conn}') + src_nbma = os.getenv('NHRP_SRCNBMA') + dest_nbma = os.getenv('NHRP_DESTNBMA') + dest_mtu = os.getenv('NHRP_DESTMTU') + + if not src_nbma or not dest_nbma: + logger.error( + f'Can not get NHRP NBMA addresses: local {src_nbma}, ' + f'remote {dest_nbma}') + return + + logger.info(f'NBMA addresses: local {src_nbma}, remote {dest_nbma}') + if dest_mtu: + add_peer_route(src_nbma, dest_nbma, dest_mtu) + if conn and dmvpn_type == 'spoke' and process_named_running('charon'): + vici_terminate(conn, src_nbma, dest_nbma) + vici_initiate(conn, 'dmvpn', src_nbma, dest_nbma) + + +def peer_down(dmvpn_type: str, conn: str) -> None: + """Proceed NHRP peer DOWN event + + Args: + dmvpn_type (str): a type of peer + conn (str): an IKE profile name + """ + logger.info(f'Peer DOWN event for {dmvpn_type} using IKE profile {conn}') + + src_nbma = os.getenv('NHRP_SRCNBMA') + dest_nbma = os.getenv('NHRP_DESTNBMA') + + if not src_nbma or not dest_nbma: + logger.error( + f'Can not get NHRP NBMA addresses: local {src_nbma}, ' + f'remote {dest_nbma}') + return + + logger.info(f'NBMA addresses: local {src_nbma}, remote {dest_nbma}') + if conn and dmvpn_type == 'spoke' and process_named_running('charon'): + vici_terminate(conn, src_nbma, dest_nbma) + try: + cmd(f'sudo ip route del {dest_nbma} src {src_nbma} proto 42') + except Exception as err: + logger.error( + f'Unable to del route from {src_nbma} to {dest_nbma}: {err}') + + +def route_up(interface: str) -> None: + """Proceed NHRP route UP event + + Args: + interface (str): an interface name + """ + logger.info(f'Route UP event for interface {interface}') + + dest_addr = os.getenv('NHRP_DESTADDR') + dest_prefix = os.getenv('NHRP_DESTPREFIX') + next_hop = os.getenv('NHRP_NEXTHOP') + + if not dest_addr or not dest_prefix or not next_hop: + logger.error( + f'Can not get route details: dest_addr {dest_addr}, ' + f'dest_prefix {dest_prefix}, next_hop {next_hop}') + return + + logger.info( + f'Route details: dest_addr {dest_addr}, dest_prefix {dest_prefix}, ' + f'next_hop {next_hop}') + + try: + cmd(f'sudo ip route replace {dest_addr}/{dest_prefix} proto 42 \ + via {next_hop} dev {interface}') + cmd('sudo ip route flush cache') + except Exception as err: + logger.error( + f'Unable replace or flush route to {dest_addr}/{dest_prefix} ' + f'via {next_hop} dev {interface}: {err}') + + +def route_down(interface: str) -> None: + """Proceed NHRP route DOWN event + + Args: + interface (str): an interface name + """ + logger.info(f'Route DOWN event for interface {interface}') + + dest_addr = os.getenv('NHRP_DESTADDR') + dest_prefix = os.getenv('NHRP_DESTPREFIX') + + if not dest_addr or not dest_prefix: + logger.error( + f'Can not get route details: dest_addr {dest_addr}, ' + f'dest_prefix {dest_prefix}') + return + + logger.info( + f'Route details: dest_addr {dest_addr}, dest_prefix {dest_prefix}') + try: + cmd(f'sudo ip route del {dest_addr}/{dest_prefix} proto 42') + cmd('sudo ip route flush cache') + except Exception as err: + logger.error( + f'Unable delete or flush route to {dest_addr}/{dest_prefix}: ' + f'{err}') + + +if __name__ == '__main__': + logger = getLogger('opennhrp-script', syslog=True) + logger.debug( + f'Running script with arguments: {sys.argv}, ' + f'environment: {os.environ}') + + action = sys.argv[1] + interface = os.getenv('NHRP_INTERFACE') + + if not interface: + logger.error('Can not get NHRP interface name') + sys.exit(1) + + dmvpn_type, profile_name = parse_type_ipsec(interface) + if not dmvpn_type: + logger.info(f'Interface {interface} is not NHRP tunnel') + sys.exit() + + dmvpn_conn: str = '' + if profile_name: + dmvpn_conn: str = f'dmvpn-{profile_name}-{interface}' + if action == 'interface-up': + iface_up(interface) + elif action == 'peer-register': + pass + elif action == 'peer-up': + peer_up(dmvpn_type, dmvpn_conn) + elif action == 'peer-down': + peer_down(dmvpn_type, dmvpn_conn) + elif action == 'route-up': + route_up(interface) + elif action == 'route-down': + route_down(interface) + + sys.exit() diff --git a/src/etc/ppp/ip-down.d/98-vyos-pppoe-cleanup-nameservers b/src/etc/ppp/ip-down.d/98-vyos-pppoe-cleanup-nameservers new file mode 100644 index 0000000..5157469 --- /dev/null +++ b/src/etc/ppp/ip-down.d/98-vyos-pppoe-cleanup-nameservers @@ -0,0 +1,14 @@ +#!/bin/bash + +interface=$6 +if [ -z "$interface" ]; then + exit +fi + +if ! /usr/bin/systemctl -q is-active vyos-hostsd; then + exit # vyos-hostsd is not running +fi + +hostsd_client="/usr/bin/vyos-hostsd-client" +$hostsd_client --delete-name-servers --tag "dhcp-$interface" +$hostsd_client --apply diff --git a/src/etc/ppp/ip-up.d/96-vyos-sstpc-callback b/src/etc/ppp/ip-up.d/96-vyos-sstpc-callback new file mode 100644 index 0000000..4e8804f --- /dev/null +++ b/src/etc/ppp/ip-up.d/96-vyos-sstpc-callback @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# This is a Python hook script which is invoked whenever a SSTP client session +# goes "ip-up". It will call into our vyos.ifconfig library and will then +# execute common tasks for the SSTP interface. The reason we have to "hook" this +# is that we can not create a sstpcX interface in advance in linux and then +# connect pppd to this already existing interface. + +from sys import argv +from sys import exit + +from vyos.configquery import ConfigTreeQuery +from vyos.configdict import get_interface_dict +from vyos.ifconfig import SSTPCIf + +# When the ppp link comes up, this script is called with the following +# parameters +# $1 the interface name used by pppd (e.g. ppp3) +# $2 the tty device name +# $3 the tty device speed +# $4 the local IP address for the interface +# $5 the remote IP address +# $6 the parameter specified by the 'ipparam' option to pppd + +if (len(argv) < 7): + exit(1) + +interface = argv[6] + +conf = ConfigTreeQuery() +_, sstpc = get_interface_dict(conf.config, ['interfaces', 'sstpc'], interface) + +# Update the config +p = SSTPCIf(interface) +p.update(sstpc) diff --git a/src/etc/ppp/ip-up.d/98-vyos-pppoe-setup-nameservers b/src/etc/ppp/ip-up.d/98-vyos-pppoe-setup-nameservers new file mode 100644 index 0000000..4affaeb --- /dev/null +++ b/src/etc/ppp/ip-up.d/98-vyos-pppoe-setup-nameservers @@ -0,0 +1,23 @@ +#!/bin/bash + +interface=$6 +if [ -z "$interface" ]; then + exit +fi + +if ! /usr/bin/systemctl -q is-active vyos-hostsd; then + exit # vyos-hostsd is not running +fi + +hostsd_client="/usr/bin/vyos-hostsd-client" + +$hostsd_client --delete-name-servers --tag "dhcp-$interface" + +if [ "$USEPEERDNS" ] && [ -n "$DNS1" ]; then +$hostsd_client --add-name-servers "$DNS1" --tag "dhcp-$interface" +fi +if [ "$USEPEERDNS" ] && [ -n "$DNS2" ]; then +$hostsd_client --add-name-servers "$DNS2" --tag "dhcp-$interface" +fi + +$hostsd_client --apply diff --git a/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback b/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback new file mode 100644 index 0000000..fa1917a --- /dev/null +++ b/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# This is a Python hook script which is invoked whenever a PPPoE session goes +# "ip-up". It will call into our vyos.ifconfig library and will then execute +# common tasks for the PPPoE interface. The reason we have to "hook" this is +# that we can not create a pppoeX interface in advance in linux and then connect +# pppd to this already existing interface. + +from sys import argv +from sys import exit + +from vyos.configquery import ConfigTreeQuery +from vyos.configdict import get_interface_dict +from vyos.ifconfig import PPPoEIf + +# When the ppp link comes up, this script is called with the following +# parameters +# $1 the interface name used by pppd (e.g. ppp3) +# $2 the tty device name +# $3 the tty device speed +# $4 the local IP address for the interface +# $5 the remote IP address +# $6 the parameter specified by the 'ipparam' option to pppd + +if (len(argv) < 7): + exit(1) + +interface = argv[6] + +conf = ConfigTreeQuery() +_, pppoe = get_interface_dict(conf.config, ['interfaces', 'pppoe'], interface) + +# Update the config +p = PPPoEIf(interface) +p.update(pppoe) diff --git a/src/etc/rsyslog.conf b/src/etc/rsyslog.conf new file mode 100644 index 0000000..b3f41ac --- /dev/null +++ b/src/etc/rsyslog.conf @@ -0,0 +1,67 @@ +################# +#### MODULES #### +################# + +$ModLoad imuxsock # provides support for local system logging +$ModLoad imklog # provides kernel logging support (previously done by rklogd) +#$ModLoad immark # provides --MARK-- message capability + +$OmitLocalLogging off +$SystemLogSocketName /run/systemd/journal/syslog + +$KLogPath /proc/kmsg + +########################### +#### GLOBAL DIRECTIVES #### +########################### + +# Use traditional timestamp format. +# To enable high precision timestamps, comment out the following line. +# A modern-style logfile format similar to TraditionalFileFormat, buth with high-precision timestamps and timezone information +#$ActionFileDefaultTemplate RSYSLOG_FileFormat +# The "old style" default log file format with low-precision timestamps +$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat + +# Filter duplicated messages +$RepeatedMsgReduction on + +# +# Set the default permissions for all log files. +# +$FileOwner root +$FileGroup adm +$FileCreateMode 0640 +$DirCreateMode 0755 +$Umask 0022 + +# +# Stop excessive logging of sudo +# +:msg, contains, " pam_unix(sudo:session): session opened for user root(uid=0) by" stop +:msg, contains, "pam_unix(sudo:session): session closed for user root" stop + +# +# Include all config files in /etc/rsyslog.d/ +# +$IncludeConfig /etc/rsyslog.d/*.conf + +# The lines below cause all listed daemons/processes to be logged into +# /var/log/auth.log, then drops the message so it does not also go to the +# regular syslog so that messages are not duplicated + +$outchannel auth_log,/var/log/auth.log +if $programname == 'CRON' or + $programname == 'sudo' or + $programname == 'su' + then :omfile:$auth_log + +if $programname == 'CRON' or + $programname == 'sudo' or + $programname == 'su' + then stop + +############### +#### RULES #### +############### +# Emergencies are sent to everybody logged in. +*.emerg :omusrmsg:*
\ No newline at end of file diff --git a/src/etc/securetty b/src/etc/securetty new file mode 100644 index 0000000..17d8610 --- /dev/null +++ b/src/etc/securetty @@ -0,0 +1,83 @@ +# /etc/securetty: list of terminals on which root is allowed to login. +# See securetty(5) and login(1). +console + +# Standard serial ports +ttyS0 +ttyS1 + +# USB dongles +ttyUSB0 +ttyUSB1 +ttyUSB2 + +# Standard hypervisor virtual console +hvc0 + +# Oldstyle Xen console +xvc0 + +# Standard consoles +tty1 +tty2 +tty3 +tty4 +tty5 +tty6 +tty7 +tty8 +tty9 +tty10 +tty11 +tty12 +tty13 +tty14 +tty15 +tty16 +tty17 +tty18 +tty19 +tty20 +tty21 +tty22 +tty23 +tty24 +tty25 +tty26 +tty27 +tty28 +tty29 +tty30 +tty31 +tty32 +tty33 +tty34 +tty35 +tty36 +tty37 +tty38 +tty39 +tty40 +tty41 +tty42 +tty43 +tty44 +tty45 +tty46 +tty47 +tty48 +tty49 +tty50 +tty51 +tty52 +tty53 +tty54 +tty55 +tty56 +tty57 +tty58 +tty59 +tty60 +tty61 +tty62 +tty63 diff --git a/src/etc/security/capability.conf b/src/etc/security/capability.conf new file mode 100644 index 0000000..0a7235f --- /dev/null +++ b/src/etc/security/capability.conf @@ -0,0 +1,10 @@ +# this is a capability file (used in conjunction with the pam_cap.so module) + +# Special capability for Vyatta admin +all %vyattacfg + +# Vyatta Operator +cap_net_admin,cap_sys_boot,cap_audit_write %vyattaop + +## 'everyone else' gets no inheritable capabilities +none * diff --git a/src/etc/skel/.bashrc b/src/etc/skel/.bashrc new file mode 100644 index 0000000..ba7d500 --- /dev/null +++ b/src/etc/skel/.bashrc @@ -0,0 +1,119 @@ +# ~/.bashrc: executed by bash(1) for non-login shells. +# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc) +# for examples + +# If not running interactively, don't do anything +case $- in + *i*) ;; + *) return;; +esac + +# don't put duplicate lines or lines starting with space in the history. +# See bash(1) for more options +HISTCONTROL=ignoreboth + +# append to the history file, don't overwrite it +shopt -s histappend + +# for setting history length see HISTSIZE and HISTFILESIZE in bash(1) +HISTSIZE=1000 +HISTFILESIZE=2000 + +# check the window size after each command and, if necessary, +# update the values of LINES and COLUMNS. +shopt -s checkwinsize + +# If set, the pattern "**" used in a pathname expansion context will +# match all files and zero or more directories and subdirectories. +#shopt -s globstar + +# make less more friendly for non-text input files, see lesspipe(1) +#[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" + +# set variable identifying the chroot you work in (used in the prompt below) +if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then + debian_chroot=$(cat /etc/debian_chroot) +fi + +# set a fancy prompt (non-color, unless we know we "want" color) +case "$TERM" in + xterm-color) color_prompt=yes;; +esac + +# uncomment for a colored prompt, if the terminal has the capability; turned +# off by default to not distract the user: the focus in a terminal window +# should be on the output of commands, not on the prompt +#force_color_prompt=yes + +if [ -n "$force_color_prompt" ]; then + if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then + # We have color support; assume it's compliant with Ecma-48 + # (ISO/IEC-6429). (Lack of such support is extremely rare, and such + # a case would tend to support setf rather than setaf.) + color_prompt=yes + else + color_prompt= + fi +fi + +if [ "$color_prompt" = yes ]; then + PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\H${VRF:+(vrf:$VRF)}${NETNS:+(ns:$NETNS)}\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' +else + PS1='${debian_chroot:+($debian_chroot)}\u@\H${VRF:+:$VRF}${NETNS:+(ns:$NETNS)}:\w\$ ' +fi +unset color_prompt force_color_prompt + +# If this is an xterm set the title to user@host:dir +case "$TERM" in +xterm*|rxvt*) + PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\H: \w\a\]$PS1" + ;; +*) + ;; +esac + +# enable color support of ls and also add handy aliases +if [ -x /usr/bin/dircolors ]; then + test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" + alias ls='ls --color=auto' + #alias dir='dir --color=auto' + #alias vdir='vdir --color=auto' + + #alias grep='grep --color=auto' + #alias fgrep='fgrep --color=auto' + #alias egrep='egrep --color=auto' +fi + +# colored GCC warnings and errors +#export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01' + +# some more ls aliases +#alias ll='ls -l' +#alias la='ls -A' +#alias l='ls -CF' + +# Alias definitions. +# You may want to put all your additions into a separate file like +# ~/.bash_aliases, instead of adding them here directly. +# See /usr/share/doc/bash-doc/examples in the bash-doc package. + +if [ -f ~/.bash_aliases ]; then + . ~/.bash_aliases +fi + +# enable programmable completion features (you don't need to enable +# this, if it's already enabled in /etc/bash.bashrc and /etc/profile +# sources /etc/bash.bashrc). +if ! shopt -oq posix; then + if [ -f /usr/share/bash-completion/bash_completion ]; then + . /usr/share/bash-completion/bash_completion + elif [ -f /etc/bash_completion ]; then + . /etc/bash_completion + fi +fi +OPAMROOT='/opt/opam'; export OPAMROOT; +OPAM_SWITCH_PREFIX='/opt/opam/4.07.0'; export OPAM_SWITCH_PREFIX; +CAML_LD_LIBRARY_PATH='/opt/opam/4.07.0/lib/stublibs:/opt/opam/4.07.0/lib/ocaml/stublibs:/opt/opam/4.07.0/lib/ocaml'; export CAML_LD_LIBRARY_PATH; +OCAML_TOPLEVEL_PATH='/opt/opam/4.07.0/lib/toplevel'; export OCAML_TOPLEVEL_PATH; +MANPATH=':/opt/opam/4.07.0/man'; export MANPATH; +PATH='/opt/opam/4.07.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'; export PATH; diff --git a/src/etc/skel/.profile b/src/etc/skel/.profile new file mode 100644 index 0000000..c9db459 --- /dev/null +++ b/src/etc/skel/.profile @@ -0,0 +1,22 @@ +# ~/.profile: executed by the command interpreter for login shells. +# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login +# exists. +# see /usr/share/doc/bash/examples/startup-files for examples. +# the files are located in the bash-doc package. + +# the default umask is set in /etc/profile; for setting the umask +# for ssh logins, install and configure the libpam-umask package. +#umask 022 + +# if running bash +if [ -n "$BASH_VERSION" ]; then + # include .bashrc if it exists + if [ -f "$HOME/.bashrc" ]; then + . "$HOME/.bashrc" + fi +fi + +# set PATH so it includes user's private bin if it exists +if [ -d "$HOME/bin" ] ; then + PATH="$HOME/bin:$PATH" +fi diff --git a/src/etc/sudoers.d/vyos b/src/etc/sudoers.d/vyos new file mode 100644 index 0000000..67d7bab --- /dev/null +++ b/src/etc/sudoers.d/vyos @@ -0,0 +1,63 @@ +# +# VyOS modifications to sudo configuration +# +Defaults syslog_goodpri=info +Defaults env_keep+=VYATTA_* + +# +# Command groups allowed for operator users +# +Cmnd_Alias IPTABLES = /sbin/iptables --list -n,\ + /sbin/iptables -L -vn,\ + /sbin/iptables -L * -vn,\ + /sbin/iptables -t * -L *, \ + /sbin/iptables -Z *,\ + /sbin/iptables -Z -t nat, \ + /sbin/iptables -t * -Z * +Cmnd_Alias IP6TABLES = /sbin/ip6tables -t * -Z *, \ + /sbin/ip6tables -t * -L * +Cmnd_Alias CONNTRACK = /usr/sbin/conntrack -L *, \ + /usr/sbin/conntrack -G *, \ + /usr/sbin/conntrack -E * +Cmnd_Alias IPFLUSH = /sbin/ip route flush cache, \ + /sbin/ip route flush cache *,\ + /sbin/ip neigh flush to *, \ + /sbin/ip neigh flush dev *, \ + /sbin/ip -f inet6 route flush cache, \ + /sbin/ip -f inet6 route flush cache *,\ + /sbin/ip -f inet6 neigh flush to *, \ + /sbin/ip -f inet6 neigh flush dev * +Cmnd_Alias ETHTOOL = /sbin/ethtool -p *, \ + /sbin/ethtool -S *, \ + /sbin/ethtool -a *, \ + /sbin/ethtool -c *, \ + /sbin/ethtool -i * +Cmnd_Alias DMIDECODE = /usr/sbin/dmidecode +Cmnd_Alias DISK = /usr/bin/lsof, /sbin/fdisk -l *, /sbin/sfdisk -d * +Cmnd_Alias DATE = /bin/date, /usr/sbin/ntpdate +Cmnd_Alias PPPOE_CMDS = /sbin/pppd, /sbin/poff, /usr/sbin/pppstats +Cmnd_Alias PCAPTURE = /usr/bin/tcpdump +Cmnd_Alias HWINFO = /usr/bin/lspci +Cmnd_Alias FORCE_CLUSTER = /usr/share/heartbeat/hb_takeover, \ + /usr/share/heartbeat/hb_standby +Cmnd_Alias DIAGNOSTICS = /bin/ip vrf exec * /bin/ping *, \ + /bin/ip vrf exec * /bin/traceroute *, \ + /bin/ip vrf exec * /usr/bin/mtr *, \ + /usr/libexec/vyos/op_mode/* +Cmnd_Alias KEA_IP6_ROUTES = /sbin/ip -6 route replace *,\ + /sbin/ip -6 route del * +%operator ALL=NOPASSWD: DATE, IPTABLES, ETHTOOL, IPFLUSH, HWINFO, \ + PPPOE_CMDS, PCAPTURE, /usr/sbin/wanpipemon, \ + DMIDECODE, DISK, CONNTRACK, IP6TABLES, \ + FORCE_CLUSTER, DIAGNOSTICS + +# Allow any user to run files in sudo-users +%users ALL=NOPASSWD: /opt/vyatta/bin/sudo-users/ + +# Allow members of group sudo to execute any command +%sudo ALL=NOPASSWD: ALL + +# Allow any user to query Machine Owner Key status +%sudo ALL=NOPASSWD: /usr/bin/mokutil + +_kea ALL=NOPASSWD: KEA_IP6_ROUTES diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf new file mode 100644 index 0000000..76be41d --- /dev/null +++ b/src/etc/sysctl.d/30-vyos-router.conf @@ -0,0 +1,117 @@ +# +# VyOS specific sysctl settings, see sysctl.conf (5) for information. +# + +# Panic on OOPS +kernel.panic_on_oops=1 + +# Timeout before rebooting on panic +kernel.panic=60 + +# Send all core files to /var/core/core.program.pid.time +kernel.core_pattern=/var/core/core-%e-%p-%t + +# ARP configuration +# arp_filter - allow multiple network interfaces on same subnet +# arp_announce - avoid local addresses no on target's subnet +# arp_ignore - reply only if target IP is local_address on the interface + +# arp_filter defaults to 1 so set all to 0 so vrrp interfaces can override it. +net.ipv4.conf.all.arp_filter=0 + +# https://vyos.dev/T300 +net.ipv4.conf.all.arp_ignore=0 +net.ipv4.conf.all.arp_announce=2 + +# Enable packet forwarding for IPv4 +net.ipv4.ip_forward=1 + +# Enable directed broadcast forwarding feature described in rfc1812#section-5.3.5.2 and rfc2644. +# Note that setting the 'all' entry to 1 doesn't enable directed broadcast forwarding on all interfaces. +# To enable directed broadcast forwarding on an interface, both the 'all' entry and the input interface entry should be set to 1. +net.ipv4.conf.all.bc_forwarding=1 +net.ipv4.conf.default.bc_forwarding=0 + +# if a primary address is removed from an interface promote the +# secondary address if available +net.ipv4.conf.all.promote_secondaries=1 + +# Ignore ICMP broadcasts sent to broadcast/multicast +net.ipv4.icmp_echo_ignore_broadcasts=1 + +# Ignore bogus ICMP errors +net.ipv4.icmp_ignore_bogus_error_responses=1 + +# Send ICMP responses with primary address of exiting interface +net.ipv4.icmp_errors_use_inbound_ifaddr=1 + +# Log packets with impossible addresses to kernel log +net.ipv4.conf.all.log_martians=1 + +# Do not ignore all ICMP ECHO requests by default +net.ipv4.icmp_echo_ignore_all=0 + +# Disable source validation by default +net.ipv4.conf.all.rp_filter=0 +net.ipv4.conf.default.rp_filter=0 + +# Enable tcp syn-cookies by default +net.ipv4.tcp_syncookies=1 + +# Disable accept_redirects by default for any interface +net.ipv4.conf.all.accept_redirects=0 +net.ipv4.conf.default.accept_redirects=0 +net.ipv6.conf.all.accept_redirects=0 +net.ipv6.conf.default.accept_redirects=0 + +# Disable accept_source_route by default +net.ipv4.conf.all.accept_source_route=0 +net.ipv4.conf.default.accept_source_route=0 +net.ipv6.conf.all.accept_source_route=0 +net.ipv6.conf.default.accept_source_route=0 + +# Enable send_redirects by default +net.ipv4.conf.all.send_redirects=1 +net.ipv4.conf.default.send_redirects=1 + +# Increase size of buffer for netlink +net.core.rmem_max=2097152 + +# Remove IPv4 and IPv6 routes from forward information base when link goes down +net.ipv4.conf.all.ignore_routes_with_linkdown=1 +net.ipv4.conf.default.ignore_routes_with_linkdown=1 +net.ipv6.conf.all.ignore_routes_with_linkdown=1 +net.ipv6.conf.default.ignore_routes_with_linkdown=1 + +# Enable packet forwarding for IPv6 +net.ipv6.conf.all.forwarding=1 + +# Increase route table limit +net.ipv6.route.max_size = 262144 + +# Do not forget IPv6 addresses when a link goes down +net.ipv6.conf.default.keep_addr_on_down=1 +net.ipv6.conf.all.keep_addr_on_down=1 +net.ipv6.route.skip_notify_on_dev_down=1 + +# Default value of 20 seems to interfere with larger OSPF and VRRP setups +net.ipv4.igmp_max_memberships = 512 + +# Enable global RFS (Receive Flow Steering) configuration. RFS is inactive +# until explicitly configured at the interface level +net.core.rps_sock_flow_entries = 32768 + +# Congestion control +net.core.default_qdisc=fq_codel +net.ipv4.tcp_congestion_control=bbr + +# Disable IPv6 Segment Routing packets by default +net.ipv6.conf.all.seg6_enabled = 0 +net.ipv6.conf.default.seg6_enabled = 0 + +net.vrf.strict_mode = 1 + +# https://vyos.dev/T6570 +# By default, do not forward traffic from bridge to IPvX layer +net.bridge.bridge-nf-call-iptables = 0 +net.bridge.bridge-nf-call-ip6tables = 0
\ No newline at end of file diff --git a/src/etc/sysctl.d/31-vyos-addr_gen_mode.conf b/src/etc/sysctl.d/31-vyos-addr_gen_mode.conf new file mode 100644 index 0000000..07a0d15 --- /dev/null +++ b/src/etc/sysctl.d/31-vyos-addr_gen_mode.conf @@ -0,0 +1,14 @@ +### Added by vyos-1x ### +# +# addr_gen_mode - INTEGER +# Defines how link-local and autoconf addresses are generated. +# +# 0: generate address based on EUI64 (default) +# 1: do no generate a link-local address, use EUI64 for addresses generated +# from autoconf +# 2: generate stable privacy addresses, using the secret from +# stable_secret (RFC7217) +# 3: generate stable privacy addresses, using a random secret if unset +# +net.ipv6.conf.all.addr_gen_mode = 1 +net.ipv6.conf.default.addr_gen_mode = 1 diff --git a/src/etc/sysctl.d/32-vyos-podman.conf b/src/etc/sysctl.d/32-vyos-podman.conf new file mode 100644 index 0000000..7068bf8 --- /dev/null +++ b/src/etc/sysctl.d/32-vyos-podman.conf @@ -0,0 +1,5 @@ +# Increase inotify watchers as per https://bugzilla.redhat.com/show_bug.cgi?id=1829596 +fs.inotify.max_queued_events = 1048576 +fs.inotify.max_user_instances = 1048576 +fs.inotify.max_user_watches = 1048576 + diff --git a/src/etc/systemd/system-generators/vyos-generator b/src/etc/systemd/system-generators/vyos-generator new file mode 100644 index 0000000..34faab6 --- /dev/null +++ b/src/etc/systemd/system-generators/vyos-generator @@ -0,0 +1,94 @@ +#!/bin/sh +set -f + +LOG="" +DEBUG_LEVEL=1 +LOG_D="/run/vyos-router" +ENABLE="enabled" +DISABLE="disabled" +FOUND="found" +NOTFOUND="notfound" +RUN_ENABLED_FILE="$LOG_D/$ENABLE" +VYOS_SYSTEM_TARGET="/lib/systemd/system/vyos.target" +VYOS_TARGET_NAME="vyos.target" + +debug() { + local lvl="$1" + shift + [ "$lvl" -gt "$DEBUG_LEVEL" ] && return + if [ -z "$LOG" ]; then + local log="$LOG_D/${0##*/}.log" + { [ -d "$LOG_D" ] || mkdir -p "$LOG_D"; } && + { : > "$log"; } >/dev/null 2>&1 && LOG="$log" || + LOG="/dev/kmsg" + fi + echo "$@" >> "$LOG" +} + +default() { + _RET="$ENABLE" +} + +main() { + local normal_d="$1" early_d="$2" late_d="$3" + local target_name="multi-user.target" gen_d="$early_d" + local link_path="$gen_d/${target_name}.wants/${VYOS_TARGET_NAME}" + local ds="$NOTFOUND" + + debug 1 "$0 normal=$normal_d early=$early_d late=$late_d" + debug 2 "$0 $*" + + local search result="error" ret="" + for search in default; do + if $search; then + debug 1 "$search found $_RET" + [ "$_RET" = "$ENABLE" -o "$_RET" = "$DISABLE" ] && + result=$_RET && break + else + ret=$? + debug 0 "search $search returned $ret" + fi + done + + # enable AND ds=found == enable + # enable AND ds=notfound == disable + # disable || <any> == disabled + if [ "$result" = "$ENABLE" ]; then + if [ -e "$link_path" ]; then + debug 1 "already enabled: no change needed" + else + [ -d "${link_path%/*}" ] || mkdir -p "${link_path%/*}" || + debug 0 "failed to make dir $link_path" + if ln -snf "$VYOS_SYSTEM_TARGET" "$link_path"; then + debug 1 "enabled via $link_path -> $VYOS_SYSTEM_TARGET" + else + ret=$? + debug 0 "[$ret] enable failed:" \ + "ln $VYOS_SYSTEM_TARGET $link_path" + fi + fi + : > "$RUN_ENABLED_FILE" + elif [ "$result" = "$DISABLE" ]; then + if [ -f "$link_path" ]; then + if rm -f "$link_path"; then + debug 1 "disabled. removed existing $link_path" + else + ret=$? + debug 0 "[$ret] disable failed, remove $link_path" + fi + else + debug 1 "already disabled: no change needed [no $link_path]" + fi + if [ -e "$RUN_ENABLED_FILE" ]; then + rm -f "$RUN_ENABLED_FILE" + fi + else + debug 0 "unexpected result '$result' 'ds=$ds'" + ret=3 + fi + return $ret +} + +main "$@" + +# vi: ts=4 expandtab diff --git a/src/etc/systemd/system/ModemManager.service.d/override.conf b/src/etc/systemd/system/ModemManager.service.d/override.conf new file mode 100644 index 0000000..07a1846 --- /dev/null +++ b/src/etc/systemd/system/ModemManager.service.d/override.conf @@ -0,0 +1,7 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +ExecStart= +ExecStart=/usr/sbin/ModemManager --filter-policy=strict --log-level=INFO --log-timestamps --log-journal diff --git a/src/etc/systemd/system/atop.service.d/10-override.conf b/src/etc/systemd/system/atop.service.d/10-override.conf new file mode 100644 index 0000000..10df158 --- /dev/null +++ b/src/etc/systemd/system/atop.service.d/10-override.conf @@ -0,0 +1,6 @@ +[Service] +ExecStartPre= +ExecStart= +ExecStart=/bin/sh -c 'exec /usr/bin/atop ${LOGOPTS} -w "${LOGPATH}/atop.log" ${LOGINTERVAL}' +ExecStartPost= + diff --git a/src/etc/systemd/system/certbot.service.d/10-override.conf b/src/etc/systemd/system/certbot.service.d/10-override.conf new file mode 100644 index 0000000..542f77e --- /dev/null +++ b/src/etc/systemd/system/certbot.service.d/10-override.conf @@ -0,0 +1,7 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +ExecStart= +ExecStart=/usr/bin/certbot renew --config-dir /config/auth/letsencrypt --no-random-sleep-on-renew --post-hook "/usr/libexec/vyos/vyos-certbot-renew-pki.sh" diff --git a/src/etc/systemd/system/conntrackd.service.d/override.conf b/src/etc/systemd/system/conntrackd.service.d/override.conf new file mode 100644 index 0000000..eb611e0 --- /dev/null +++ b/src/etc/systemd/system/conntrackd.service.d/override.conf @@ -0,0 +1,8 @@ +[Unit] +After= +After=vyos-router.service +ConditionPathExists=/run/conntrackd/conntrackd.conf + +[Service] +ExecStart= +ExecStart=/usr/sbin/conntrackd -C /run/conntrackd/conntrackd.conf diff --git a/src/etc/systemd/system/conserver-server.service.d/override.conf b/src/etc/systemd/system/conserver-server.service.d/override.conf new file mode 100644 index 0000000..3c753f5 --- /dev/null +++ b/src/etc/systemd/system/conserver-server.service.d/override.conf @@ -0,0 +1,10 @@ +[Unit] +After= +After=vyos-router.service +ConditionPathExists=/run/conserver/conserver.cf + +[Service] +Type=simple +ExecStart= +ExecStart=/usr/sbin/conserver -M localhost -C /run/conserver/conserver.cf +Restart=on-failure diff --git a/src/etc/systemd/system/fastnetmon.service.d/override.conf b/src/etc/systemd/system/fastnetmon.service.d/override.conf new file mode 100644 index 0000000..8416660 --- /dev/null +++ b/src/etc/systemd/system/fastnetmon.service.d/override.conf @@ -0,0 +1,12 @@ +[Unit] +RequiresMountsFor=/run +ConditionPathExists=/run/fastnetmon/fastnetmon.conf +After= +After=vyos-router.service + +[Service] +Type=simple +WorkingDirectory=/run/fastnetmon +PIDFile=/run/fastnetmon.pid +ExecStart= +ExecStart=/usr/sbin/fastnetmon --configuration_file /run/fastnetmon/fastnetmon.conf diff --git a/src/etc/systemd/system/frr.service.d/override.conf b/src/etc/systemd/system/frr.service.d/override.conf new file mode 100644 index 0000000..614b4f7 --- /dev/null +++ b/src/etc/systemd/system/frr.service.d/override.conf @@ -0,0 +1,11 @@ +[Unit] +After=vyos-router.service + +[Service] +LimitNOFILE=4096 +ExecStartPre=/bin/bash -c 'mkdir -p /run/frr/config; \ + echo "log syslog" > /run/frr/config/frr.conf; \ + echo "log facility local7" >> /run/frr/config/frr.conf; \ + chown frr:frr /run/frr/config/frr.conf; \ + chmod 664 /run/frr/config/frr.conf; \ + mount --bind /run/frr/config/frr.conf /etc/frr/frr.conf' diff --git a/src/etc/systemd/system/getty@.service.d/aftervyos.conf b/src/etc/systemd/system/getty@.service.d/aftervyos.conf new file mode 100644 index 0000000..c575390 --- /dev/null +++ b/src/etc/systemd/system/getty@.service.d/aftervyos.conf @@ -0,0 +1,3 @@ +[Service] +ExecStartPre=-/usr/libexec/vyos/init/vyos-config +StandardOutput=journal+console diff --git a/src/etc/systemd/system/hostapd@.service.d/override.conf b/src/etc/systemd/system/hostapd@.service.d/override.conf new file mode 100644 index 0000000..926c07f --- /dev/null +++ b/src/etc/systemd/system/hostapd@.service.d/override.conf @@ -0,0 +1,12 @@ +[Unit] +After= +After=vyos-router.service +ConditionFileNotEmpty= +ConditionFileNotEmpty=/run/hostapd/%i.conf + +[Service] +WorkingDirectory=/run/hostapd +EnvironmentFile= +ExecStart= +ExecStart=/usr/sbin/hostapd -B -P /run/hostapd/%i.pid /run/hostapd/%i.conf +PIDFile=/run/hostapd/%i.pid diff --git a/src/etc/systemd/system/kea-ctrl-agent.service.d/override.conf b/src/etc/systemd/system/kea-ctrl-agent.service.d/override.conf new file mode 100644 index 0000000..0f5bf80 --- /dev/null +++ b/src/etc/systemd/system/kea-ctrl-agent.service.d/override.conf @@ -0,0 +1,9 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +ExecStart= +ExecStart=/usr/sbin/kea-ctrl-agent -c /run/kea/kea-ctrl-agent.conf +AmbientCapabilities=CAP_NET_BIND_SERVICE +CapabilityBoundingSet=CAP_NET_BIND_SERVICE diff --git a/src/etc/systemd/system/kea-dhcp4-server.service.d/override.conf b/src/etc/systemd/system/kea-dhcp4-server.service.d/override.conf new file mode 100644 index 0000000..682e5bb --- /dev/null +++ b/src/etc/systemd/system/kea-dhcp4-server.service.d/override.conf @@ -0,0 +1,7 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +ExecStart= +ExecStart=/usr/sbin/kea-dhcp4 -c /run/kea/kea-dhcp4.conf diff --git a/src/etc/systemd/system/kea-dhcp6-server.service.d/override.conf b/src/etc/systemd/system/kea-dhcp6-server.service.d/override.conf new file mode 100644 index 0000000..cb33fc0 --- /dev/null +++ b/src/etc/systemd/system/kea-dhcp6-server.service.d/override.conf @@ -0,0 +1,7 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +ExecStart= +ExecStart=/usr/sbin/kea-dhcp6 -c /run/kea/kea-dhcp6.conf diff --git a/src/etc/systemd/system/logrotate.timer.d/10-override.conf b/src/etc/systemd/system/logrotate.timer.d/10-override.conf new file mode 100644 index 0000000..f50c2b0 --- /dev/null +++ b/src/etc/systemd/system/logrotate.timer.d/10-override.conf @@ -0,0 +1,2 @@ +[Timer] +OnCalendar=hourly diff --git a/src/etc/systemd/system/nginx.service.d/10-override.conf b/src/etc/systemd/system/nginx.service.d/10-override.conf new file mode 100644 index 0000000..1be5cec --- /dev/null +++ b/src/etc/systemd/system/nginx.service.d/10-override.conf @@ -0,0 +1,3 @@ +[Unit] +After= +After=vyos-router.service diff --git a/src/etc/systemd/system/ocserv.service.d/override.conf b/src/etc/systemd/system/ocserv.service.d/override.conf new file mode 100644 index 0000000..89dbb15 --- /dev/null +++ b/src/etc/systemd/system/ocserv.service.d/override.conf @@ -0,0 +1,14 @@ +[Unit] +RequiresMountsFor=/run +ConditionPathExists=/run/ocserv/ocserv.conf +After= +After=vyos-router.service +After=dbus.service + +[Service] +WorkingDirectory=/run/ocserv +PIDFile= +PIDFile=/run/ocserv/ocserv.pid +ExecStart= +ExecStart=/usr/sbin/ocserv --foreground --pid-file /run/ocserv/ocserv.pid --config /run/ocserv/ocserv.conf + diff --git a/src/etc/systemd/system/openvpn@.service.d/10-override.conf b/src/etc/systemd/system/openvpn@.service.d/10-override.conf new file mode 100644 index 0000000..775a2d7 --- /dev/null +++ b/src/etc/systemd/system/openvpn@.service.d/10-override.conf @@ -0,0 +1,14 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +WorkingDirectory= +WorkingDirectory=/run/openvpn +ExecStart= +ExecStart=/usr/sbin/openvpn --daemon openvpn-%i --config %i.conf --status %i.status 30 --writepid %i.pid +ExecReload=/bin/kill -HUP $MAINPID +User=openvpn +Group=openvpn +AmbientCapabilities=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_OVERRIDE CAP_AUDIT_WRITE +CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_OVERRIDE CAP_AUDIT_WRITE diff --git a/src/etc/systemd/system/radvd.service.d/override.conf b/src/etc/systemd/system/radvd.service.d/override.conf new file mode 100644 index 0000000..812446d --- /dev/null +++ b/src/etc/systemd/system/radvd.service.d/override.conf @@ -0,0 +1,19 @@ +[Unit] +ConditionPathExists= +ConditionPathExists=/run/radvd/radvd.conf +After= +After=vyos-router.service + +[Service] +WorkingDirectory= +WorkingDirectory=/run/radvd +ExecStartPre= +ExecStartPre=/usr/sbin/radvd --logmethod stderr_clean --configtest --config /run/radvd/radvd.conf +ExecStart= +ExecStart=/usr/sbin/radvd --logmethod stderr_clean --config /run/radvd/radvd.conf --pidfile /run/radvd/radvd.pid +ExecReload= +ExecReload=/usr/sbin/radvd --logmethod stderr_clean --configtest --config /run/radvd/radvd.conf +ExecReload=/bin/kill -HUP $MAINPID +PIDFile= +PIDFile=/run/radvd/radvd.pid +Restart=always diff --git a/src/etc/systemd/system/serial-getty@.service.d/aftervyos.conf b/src/etc/systemd/system/serial-getty@.service.d/aftervyos.conf new file mode 100644 index 0000000..8ba4277 --- /dev/null +++ b/src/etc/systemd/system/serial-getty@.service.d/aftervyos.conf @@ -0,0 +1,3 @@ +[Service] +ExecStartPre=-/usr/libexec/vyos/init/vyos-config SERIAL +StandardOutput=journal+console diff --git a/src/etc/systemd/system/ssh@.service.d/vrf-override.conf b/src/etc/systemd/system/ssh@.service.d/vrf-override.conf new file mode 100644 index 0000000..b8952d8 --- /dev/null +++ b/src/etc/systemd/system/ssh@.service.d/vrf-override.conf @@ -0,0 +1,13 @@ +[Unit] +StartLimitIntervalSec=0 +After=vyos-router.service +ConditionPathExists=/run/sshd/sshd_config + +[Service] +EnvironmentFile= +ExecStart= +ExecStart=ip vrf exec %i /usr/sbin/sshd -f /run/sshd/sshd_config +Restart=always +RestartPreventExitStatus= +RestartSec=10 +RuntimeDirectoryPreserve=yes diff --git a/src/etc/systemd/system/suricata.service.d/10-override.conf b/src/etc/systemd/system/suricata.service.d/10-override.conf new file mode 100644 index 0000000..781256c --- /dev/null +++ b/src/etc/systemd/system/suricata.service.d/10-override.conf @@ -0,0 +1,9 @@ +[Service] +ExecStart= +ExecStart=/usr/bin/suricata -D --af-packet -c /run/suricata/suricata.yaml --pidfile /run/suricata/suricata.pid +PIDFile= +PIDFile=/run/suricata/suricata.pid +ExecReload= +ExecReload=/usr/bin/suricatasc -c reload-rules /run/suricata/suricata.socket ; /bin/kill -HUP $MAINPID +ExecStop= +ExecStop=/usr/bin/suricatasc -c shutdown /run/suricata/suricata.socket diff --git a/src/etc/systemd/system/wpa_supplicant-wired@.service.d/override.conf b/src/etc/systemd/system/wpa_supplicant-wired@.service.d/override.conf new file mode 100644 index 0000000..030b89a --- /dev/null +++ b/src/etc/systemd/system/wpa_supplicant-wired@.service.d/override.conf @@ -0,0 +1,11 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +WorkingDirectory= +WorkingDirectory=/run/wpa_supplicant +PIDFile=/run/wpa_supplicant/%I.pid +ExecStart= +ExecStart=/sbin/wpa_supplicant -c/run/wpa_supplicant/%I.conf -Dwired -P/run/wpa_supplicant/%I.pid -i%I +ExecReload=/bin/kill -HUP $MAINPID diff --git a/src/etc/systemd/system/wpa_supplicant@.service.d/override.conf b/src/etc/systemd/system/wpa_supplicant@.service.d/override.conf new file mode 100644 index 0000000..5cffb79 --- /dev/null +++ b/src/etc/systemd/system/wpa_supplicant@.service.d/override.conf @@ -0,0 +1,11 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +WorkingDirectory= +WorkingDirectory=/run/wpa_supplicant +PIDFile=/run/wpa_supplicant/%I.pid +ExecStart= +ExecStart=/sbin/wpa_supplicant -c/run/wpa_supplicant/%I.conf -Dnl80211,wext -P/run/wpa_supplicant/%I.pid -i%I +ExecReload=/bin/kill -HUP $MAINPID diff --git a/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py new file mode 100644 index 0000000..bb7515a --- /dev/null +++ b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 + +import json +import re +import time + +from vyos.utils.process import cmd + + +def get_nft_filter_chains(): + """ + Get list of nft chains for table filter + """ + try: + nft = cmd('/usr/sbin/nft --json list table ip vyos_filter') + except Exception: + return [] + nft = json.loads(nft) + chain_list = [] + + for output in nft['nftables']: + if 'chain' in output: + chain = output['chain']['name'] + chain_list.append(chain) + + return chain_list + + +def get_nftables_details(name): + """ + Get dict, counters packets and bytes for chain + """ + command = f'/usr/sbin/nft list chain ip vyos_filter {name}' + try: + results = cmd(command) + except: + return {} + + # Trick to remove 'NAME_' from chain name in the comment + # It was added to any chain T4218 + # counter packets 0 bytes 0 return comment "FOO default-action accept" + comment_name = name.replace("NAME_", "") + out = {} + for line in results.split('\n'): + comment_search = re.search(rf'{comment_name}[\- ](\d+|default-action)', line) + if not comment_search: + continue + + rule = {} + rule_id = comment_search[1] + counter_search = re.search(r'counter packets (\d+) bytes (\d+)', line) + if counter_search: + rule['packets'] = counter_search[1] + rule['bytes'] = counter_search[2] + + rule['conditions'] = re.sub(r'(\b(counter packets \d+ bytes \d+|drop|reject|return|log)\b|comment "[\w\-]+")', '', line).strip() + out[rule_id] = rule + return out + + +def get_nft_telegraf(name): + """ + Get data for telegraf in influxDB format + """ + for rule, rule_config in get_nftables_details(name).items(): + print(f'nftables,table=vyos_filter,chain={name},' + f'ruleid={rule} ' + f'pkts={rule_config["packets"]}i,' + f'bytes={rule_config["bytes"]}i ' + f'{str(int(time.time()))}000000000') + + +chains = get_nft_filter_chains() + +for chain in chains: + get_nft_telegraf(chain) diff --git a/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py b/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py new file mode 100644 index 0000000..6f14d6a --- /dev/null +++ b/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 + +from vyos.ifconfig import Section +from vyos.ifconfig import Interface + +import time + +def get_interface_addresses(iface, link_local_v6=False): + """ + Get IP and IPv6 addresses from interface in one string + By default don't get IPv6 link-local addresses + If interface doesn't have address, return "-" + """ + addresses = [] + addrs = Interface(iface).get_addr() + + for addr in addrs: + if link_local_v6 == False: + if addr.startswith('fe80::'): + continue + addresses.append(addr) + + if not addresses: + return "-" + + return (" ".join(addresses)) + +def get_interface_description(iface): + """ + Get interface description + If none return "empty" + """ + description = Interface(iface).get_alias() + + if not description: + return "empty" + + return description + +def get_interface_admin_state(iface): + """ + Interface administrative state + up => 0, down => 2 + """ + state = Interface(iface).get_admin_state() + if state == 'up': + admin_state = 0 + if state == 'down': + admin_state = 2 + + return admin_state + +def get_interface_oper_state(iface): + """ + Interface operational state + up => 0, down => 1 + """ + state = Interface(iface).operational.get_state() + if state == 'down': + oper_state = 1 + else: + oper_state = 0 + + return oper_state + +interfaces = Section.interfaces('') + +for iface in interfaces: + print(f'show_interfaces,interface={iface} ' + f'ip_addresses="{get_interface_addresses(iface)}",' + f'state={get_interface_admin_state(iface)}i,' + f'link={get_interface_oper_state(iface)}i,' + f'description="{get_interface_description(iface)}" ' + f'{str(int(time.time()))}000000000') diff --git a/src/etc/telegraf/custom_scripts/vyos_services_input_filter.py b/src/etc/telegraf/custom_scripts/vyos_services_input_filter.py new file mode 100644 index 0000000..00f2f18 --- /dev/null +++ b/src/etc/telegraf/custom_scripts/vyos_services_input_filter.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import time +from vyos.configquery import ConfigTreeQuery +from vyos.utils.process import is_systemd_service_running +from vyos.utils.process import process_named_running + +# Availible services and prouceses +# 1 - service +# 2 - process +services = { + "protocols bgp" : "bgpd", + "protocols ospf" : "ospfd", + "protocols ospfv3" : "ospf6d", + "protocols rip" : "ripd", + "protocols ripng" : "ripngd", + "protocols isis" : "isisd", + "service pppoe" : "accel-ppp@pppoe.service", + "vpn l2tp remote-access" : "accel-ppp@l2tp.service", + "vpn pptp remote-access" : "accel-ppp@pptp.service", + "vpn sstp" : "accel-ppp@sstp.service", + "vpn ipsec" : "charon" +} + +# Configured services +conf_services = { + 'zebra' : 0, + 'staticd' : 0, +} +# Get configured service and create list to check if process running +config = ConfigTreeQuery() +for service in services: + if config.exists(service): + conf_services[services[service]] = 0 + +for conf_service in conf_services: + status = 0 + if ".service" in conf_service: + # Check systemd service + if is_systemd_service_running(conf_service): + status = 1 + else: + # Check process + if process_named_running(conf_service): + status = 1 + print(f'vyos_services,service="{conf_service}" ' + f'status={str(status)}i {str(int(time.time()))}000000000') diff --git a/src/etc/udev/rules.d/42-qemu-usb.rules b/src/etc/udev/rules.d/42-qemu-usb.rules new file mode 100644 index 0000000..a79543d --- /dev/null +++ b/src/etc/udev/rules.d/42-qemu-usb.rules @@ -0,0 +1,14 @@ +# +# Enable autosuspend for qemu emulated usb hid devices. +# +# Note that there are buggy qemu versions which advertise remote +# wakeup support but don't actually implement it correctly. This +# is the reason why we need a match for the serial number here. +# The serial number "42" is used to tag the implementations where +# remote wakeup is working. +# +# Gerd Hoffmann <kraxel@xxxxxxxxxx> + +ACTION=="add", SUBSYSTEM=="usb", ATTR{product}=="QEMU USB Mouse", ATTR{serial}=="42", TEST=="power/control", ATTR{power/control}="auto" +ACTION=="add", SUBSYSTEM=="usb", ATTR{product}=="QEMU USB Tablet", ATTR{serial}=="42", TEST=="power/control", ATTR{power/control}="auto" +ACTION=="add", SUBSYSTEM=="usb", ATTR{product}=="QEMU USB Keyboard", ATTR{serial}=="42", TEST=="power/control", ATTR{power/control}="auto" diff --git a/src/etc/udev/rules.d/62-temporary-interface-rename.rules b/src/etc/udev/rules.d/62-temporary-interface-rename.rules new file mode 100644 index 0000000..4a579dc --- /dev/null +++ b/src/etc/udev/rules.d/62-temporary-interface-rename.rules @@ -0,0 +1 @@ +SUBSYSTEM=="net", ACTION=="add", KERNEL=="eth*", DRIVERS=="?*", NAME="e$env{IFINDEX}" diff --git a/src/etc/udev/rules.d/63-hyperv-vf-net.rules b/src/etc/udev/rules.d/63-hyperv-vf-net.rules new file mode 100644 index 0000000..b4dcb5a --- /dev/null +++ b/src/etc/udev/rules.d/63-hyperv-vf-net.rules @@ -0,0 +1,5 @@ +ATTR{[dmi/id]sys_vendor}!="Microsoft Corporation", GOTO="end_hyperv_nic" + +ACTION=="add", SUBSYSTEM=="net", DRIVERS=="hv_pci", NAME="vf_%k" + +LABEL="end_hyperv_nic" diff --git a/src/etc/udev/rules.d/64-vyos-vmware-net.rules b/src/etc/udev/rules.d/64-vyos-vmware-net.rules new file mode 100644 index 0000000..66a4a06 --- /dev/null +++ b/src/etc/udev/rules.d/64-vyos-vmware-net.rules @@ -0,0 +1,14 @@ +ATTR{[dmi/id]sys_vendor}!="VMware, Inc.", GOTO="end_vmware_nic" + +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet0", ENV{VYOS_IFNAME}="eth0" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet1", ENV{VYOS_IFNAME}="eth1" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet2", ENV{VYOS_IFNAME}="eth2" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet3", ENV{VYOS_IFNAME}="eth3" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet4", ENV{VYOS_IFNAME}="eth4" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet5", ENV{VYOS_IFNAME}="eth5" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet6", ENV{VYOS_IFNAME}="eth6" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet7", ENV{VYOS_IFNAME}="eth7" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet8", ENV{VYOS_IFNAME}="eth8" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet9", ENV{VYOS_IFNAME}="eth9" + +LABEL="end_vmware_nic" diff --git a/src/etc/udev/rules.d/65-vyos-net.rules b/src/etc/udev/rules.d/65-vyos-net.rules new file mode 100644 index 0000000..32ae352 --- /dev/null +++ b/src/etc/udev/rules.d/65-vyos-net.rules @@ -0,0 +1,23 @@ +# These rules use vyos_net_name to persistently name network interfaces +# per "hwid" association in the VyOS configuration file. + +ACTION!="add", GOTO="vyos_net_end" +SUBSYSTEM!="net", GOTO="vyos_net_end" + +# Do name change for ethernet and wireless devices only +KERNEL!="eth*|wlan*|e*", GOTO="vyos_net_end" + +# ignore "secondary" monitor interfaces of mac80211 drivers +KERNEL=="wlan*", ATTRS{type}=="803", GOTO="vyos_net_end" + +# If using VyOS predefined names +ENV{VYOS_IFNAME}!="eth*", GOTO="end_vyos_predef_names" + +DRIVERS=="?*", PROGRAM="vyos_net_name %k $attr{address} $env{VYOS_IFNAME}", NAME="%c", GOTO="vyos_net_end" + +LABEL="end_vyos_predef_names" + +# ignore interfaces without a driver link like bridges and VLANs +DRIVERS=="?*", PROGRAM="vyos_net_name %k $attr{address}", NAME="%c" + +LABEL="vyos_net_end" diff --git a/src/etc/udev/rules.d/90-vyos-serial.rules b/src/etc/udev/rules.d/90-vyos-serial.rules new file mode 100644 index 0000000..30c1d31 --- /dev/null +++ b/src/etc/udev/rules.d/90-vyos-serial.rules @@ -0,0 +1,28 @@ +# do not edit this file, it will be overwritten on update + +ACTION=="remove", GOTO="serial_end" +SUBSYSTEM!="tty", GOTO="serial_end" + +SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}" +SUBSYSTEMS=="pci", IMPORT{builtin}="hwdb --subsystem=pci" +SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb" + +# /dev/serial/by-path/, /dev/serial/by-id/ for USB devices +KERNEL!="ttyUSB[0-9]*", GOTO="serial_end" + +SUBSYSTEMS=="usb-serial", ENV{.ID_PORT}="$attr{port_number}" + +IMPORT{builtin}="path_id", IMPORT{builtin}="usb_id" + +# Change the name of the usb id to a "more" human redable format. +# +# - $env{ID_PATH} usually is a name like: "pci-0000:00:10.0-usb-0:2.3.3.4:1.0-port0" so we strip the "pci-*" +# portion and only use the usb part +# - Transform the USB "speech" to the tree like structure so we start with "usb0" as root-complex 0. +# (tr -d -) does the replacement +# - Replace the first group after ":" to represent the bus relation (sed -e 0,/:/s//b/) indicated by "b" +# - Replace the next group after ":" to represent the port relation (sed -e 0,/:/s//p/) indicated by "p" +ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="", PROGRAM="/bin/sh -c 'echo $env{ID_PATH} | cut -d- -f3- | tr -d - | sed -e 0,/:/s//b/ | sed -e 0,/:/s//p/'", SYMLINK+="serial/by-bus/$result" +ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="?*", PROGRAM="/bin/sh -c 'echo $env{ID_PATH} | cut -d- -f3- | tr -d - | sed -e 0,/:/s//b/ | sed -e 0,/:/s//p/'", SYMLINK+="serial/by-bus/$result" + +LABEL="serial_end" diff --git a/src/etc/udev/rules.d/99-vyos-systemd.rules b/src/etc/udev/rules.d/99-vyos-systemd.rules new file mode 100644 index 0000000..54aea66 --- /dev/null +++ b/src/etc/udev/rules.d/99-vyos-systemd.rules @@ -0,0 +1,79 @@ +# The main reason that we store this file is systemd-udevd interfaces excludes +# /lib/systemd/systemd-sysctl for dynamic interfaces (ppp|ipoe|l2tp etc) + +ACTION=="remove", GOTO="systemd_end" + +SUBSYSTEM=="tty", KERNEL=="tty[a-zA-Z]*|hvc*|xvc*|hvsi*|ttysclp*|sclp_line*|3270/tty[0-9]*", TAG+="systemd" +KERNEL=="vport*", TAG+="systemd" + +SUBSYSTEM=="ptp", TAG+="systemd" + +SUBSYSTEM=="ubi", TAG+="systemd" + +SUBSYSTEM=="block", TAG+="systemd" + +# We can't make any conclusions about suspended DM devices so let's just import previous SYSTEMD_READY state and skip other rules +SUBSYSTEM=="block", ENV{DM_SUSPENDED}=="1", IMPORT{db}="SYSTEMD_READY", GOTO="systemd_end" +SUBSYSTEM=="block", ACTION=="add", ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}=="1", ENV{SYSTEMD_READY}="0" + +# Ignore encrypted devices with no identified superblock on it, since +# we are probably still calling mke2fs or mkswap on it. +SUBSYSTEM=="block", ENV{DM_UUID}=="CRYPT-*", ENV{ID_PART_TABLE_TYPE}=="", ENV{ID_FS_USAGE}=="", ENV{SYSTEMD_READY}="0" + +# Explicitly set SYSTEMD_READY=1 for DM devices that don't have it set yet, so that we always have something to import above +SUBSYSTEM=="block", ENV{DM_UUID}=="?*", ENV{SYSTEMD_READY}=="", ENV{SYSTEMD_READY}="1" + +# add symlink to GPT root disk +SUBSYSTEM=="block", ENV{ID_PART_GPT_AUTO_ROOT}=="1", ENV{ID_FS_TYPE}!="crypto_LUKS", SYMLINK+="gpt-auto-root" +SUBSYSTEM=="block", ENV{ID_PART_GPT_AUTO_ROOT}=="1", ENV{ID_FS_TYPE}=="crypto_LUKS", SYMLINK+="gpt-auto-root-luks" +SUBSYSTEM=="block", ENV{DM_UUID}=="CRYPT-*", ENV{DM_NAME}=="root", SYMLINK+="gpt-auto-root" + +# Ignore raid devices that are not yet assembled and started +SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", KERNEL=="md*", TEST!="md/array_state", ENV{SYSTEMD_READY}="0" +SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", KERNEL=="md*", ATTR{md/array_state}=="|clear|inactive", ENV{SYSTEMD_READY}="0" + +# Ignore loop devices that don't have any file attached +SUBSYSTEM=="block", KERNEL=="loop[0-9]*", ENV{DEVTYPE}=="disk", TEST!="loop/backing_file", ENV{SYSTEMD_READY}="0" + +# Ignore nbd devices until the PID file exists (which signals a connected device) +SUBSYSTEM=="block", KERNEL=="nbd*", ENV{DEVTYPE}=="disk", TEST!="pid", ENV{SYSTEMD_READY}="0" + +# We need a hardware independent way to identify network devices. We +# use the /sys/subsystem/ path for this. Kernel "bus" and "class" names +# should be treated as one namespace, like udev handles it. This is mostly +# just an identification string for systemd, so whether the path actually is +# accessible or not does not matter as long as it is unique and in the +# filesystem namespace. + +SUBSYSTEM=="net", KERNEL!="lo", TAG+="systemd", ENV{SYSTEMD_ALIAS}+="/sys/subsystem/net/devices/$name" +SUBSYSTEM=="bluetooth", TAG+="systemd", ENV{SYSTEMD_ALIAS}+="/sys/subsystem/bluetooth/devices/%k", \ + ENV{SYSTEMD_WANTS}+="bluetooth.target", ENV{SYSTEMD_USER_WANTS}+="bluetooth.target" + +ENV{ID_SMARTCARD_READER}=="?*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="smartcard.target", ENV{SYSTEMD_USER_WANTS}+="smartcard.target" +SUBSYSTEM=="sound", KERNEL=="controlC*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sound.target", ENV{SYSTEMD_USER_WANTS}+="sound.target" + +SUBSYSTEM=="printer", TAG+="systemd", ENV{SYSTEMD_WANTS}+="printer.target", ENV{SYSTEMD_USER_WANTS}+="printer.target" +SUBSYSTEM=="usb", KERNEL=="lp*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="printer.target", ENV{SYSTEMD_USER_WANTS}+="printer.target" +SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{ID_USB_INTERFACES}=="*:0701??:*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="printer.target", ENV{SYSTEMD_USER_WANTS}+="printer.target" + +SUBSYSTEM=="udc", ACTION=="add", TAG+="systemd", ENV{SYSTEMD_WANTS}+="usb-gadget.target" + +# Apply sysctl variables to network devices (and only to those) as they appear. +# T5706. Exclude: lo, dummy*, ppp*, ipoe*, l2tp*, pptp*, sslvpn* and sstp*. +ACTION=="add", SUBSYSTEM=="net", KERNEL!="lo|dummy*|ppp*|ipoe*|l2tp*|pptp*|sslvpn*|sstp*", RUN+="/lib/systemd/systemd-sysctl --prefix=/net/ipv4/conf/$name --prefix=/net/ipv4/neigh/$name --prefix=/net/ipv6/conf/$name --prefix=/net/ipv6/neigh/$name" + +# Pull in backlight save/restore for all backlight devices and +# keyboard backlights +SUBSYSTEM=="backlight", TAG+="systemd", IMPORT{builtin}="path_id", ENV{SYSTEMD_WANTS}+="systemd-backlight@backlight:$name.service" +SUBSYSTEM=="leds", KERNEL=="*kbd_backlight", TAG+="systemd", IMPORT{builtin}="path_id", ENV{SYSTEMD_WANTS}+="systemd-backlight@leds:$name.service" + +# Pull in rfkill save/restore for all rfkill devices +SUBSYSTEM=="rfkill", ENV{SYSTEMD_RFKILL}="1" +SUBSYSTEM=="rfkill", IMPORT{builtin}="path_id" +SUBSYSTEM=="misc", KERNEL=="rfkill", TAG+="systemd", ENV{SYSTEMD_WANTS}+="systemd-rfkill.socket" + +# Asynchronously mount file systems implemented by these modules as soon as they are loaded. +SUBSYSTEM=="module", KERNEL=="fuse", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sys-fs-fuse-connections.mount" +SUBSYSTEM=="module", KERNEL=="configfs", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sys-kernel-config.mount" + +LABEL="systemd_end" diff --git a/src/etc/update-motd.d/99-reboot b/src/etc/update-motd.d/99-reboot new file mode 100644 index 0000000..718be1a --- /dev/null +++ b/src/etc/update-motd.d/99-reboot @@ -0,0 +1,7 @@ +#!/bin/vbash +source /opt/vyatta/etc/functions/script-template +if [ -f /run/systemd/shutdown/scheduled ]; then + echo + run show reboot +fi +exit diff --git a/src/etc/vmware-tools/scripts/resume-vm-default.d/ether-resume.py b/src/etc/vmware-tools/scripts/resume-vm-default.d/ether-resume.py new file mode 100644 index 0000000..7da57bc --- /dev/null +++ b/src/etc/vmware-tools/scripts/resume-vm-default.d/ether-resume.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import syslog + +from vyos import ConfigError +from vyos.config import Config +from vyos.utils.process import run + +def get_config(): + c = Config() + interfaces = dict() + for intf in c.list_effective_nodes('interfaces ethernet'): + # skip interfaces that are disabled + check_disable = f'interfaces ethernet {intf} disable' + if c.exists_effective(check_disable): + continue + + # get addresses configured on the interface + intf_addresses = c.return_effective_values( + f'interfaces ethernet {intf} address') + interfaces[intf] = [addr.strip("'") for addr in intf_addresses] + return interfaces + +def apply(config): + syslog.openlog(ident='ether-resume', logoption=syslog.LOG_PID, + facility=syslog.LOG_INFO) + + for intf, addresses in config.items(): + # bring the interface up + cmd = f'ip link set dev {intf} up' + syslog.syslog(cmd) + run(cmd) + + # add configured addresses to interface + for addr in addresses: + # dhcp is handled by netplug + if addr in ['dhcp', 'dhcpv6']: + continue + cmd = f'ip address add {addr} dev {intf}' + syslog.syslog(cmd) + run(cmd) + +if __name__ == '__main__': + try: + config = get_config() + apply(config) + except ConfigError as e: + print(e) + sys.exit(1) diff --git a/src/etc/vmware-tools/tools.conf b/src/etc/vmware-tools/tools.conf new file mode 100644 index 0000000..da98a4f --- /dev/null +++ b/src/etc/vmware-tools/tools.conf @@ -0,0 +1,2 @@ +[guestinfo] + poll-interval=30 diff --git a/src/helpers/add-system-version.py b/src/helpers/add-system-version.py new file mode 100644 index 0000000..5270ee7 --- /dev/null +++ b/src/helpers/add-system-version.py @@ -0,0 +1,20 @@ +#!/usr/bin/python3 + +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.component_version import add_system_version + +add_system_version() diff --git a/src/helpers/commit-confirm-notify.py b/src/helpers/commit-confirm-notify.py new file mode 100644 index 0000000..8d7626c --- /dev/null +++ b/src/helpers/commit-confirm-notify.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +import os +import sys +import time + +# Minutes before reboot to trigger notification. +intervals = [1, 5, 15, 60] + +def notify(interval): + s = "" if interval == 1 else "s" + time.sleep((minutes - interval) * 60) + message = ('"[commit-confirm] System is going to reboot in ' + f'{interval} minute{s} to rollback the last commit.\n' + 'Confirm your changes to cancel the reboot."') + os.system("wall -n " + message) + +if __name__ == "__main__": + # Must be run as root to call wall(1) without a banner. + if len(sys.argv) != 2 or os.getuid() != 0: + print('This script requires superuser privileges.', file=sys.stderr) + exit(1) + minutes = int(sys.argv[1]) + # Drop the argument from the list so that the notification + # doesn't kick in immediately. + if minutes in intervals: + intervals.remove(minutes) + for interval in sorted(intervals, reverse=True): + if minutes >= interval: + notify(interval) + minutes -= (minutes - interval) + exit(0) diff --git a/src/helpers/config_dependency.py b/src/helpers/config_dependency.py new file mode 100644 index 0000000..817bcc6 --- /dev/null +++ b/src/helpers/config_dependency.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +import os +import sys +import json +from argparse import ArgumentParser +from argparse import ArgumentTypeError +from graphlib import TopologicalSorter, CycleError + +# addon packages will need to specify the dependency directory +data_dir = '/usr/share/vyos/' +dependency_dir = os.path.join(data_dir, 'config-mode-dependencies') + +def dict_merge(source, destination): + from copy import deepcopy + tmp = deepcopy(destination) + + for key, value in source.items(): + if key not in tmp: + tmp[key] = value + elif isinstance(source[key], dict): + tmp[key] = dict_merge(source[key], tmp[key]) + + return tmp + +def read_dependency_dict(dependency_dir: str = dependency_dir) -> dict: + res = {} + for dep_file in os.listdir(dependency_dir): + if not dep_file.endswith('.json'): + continue + path = os.path.join(dependency_dir, dep_file) + with open(path) as f: + d = json.load(f) + if dep_file == 'vyos-1x.json': + res = dict_merge(res, d) + else: + res = dict_merge(d, res) + + return res + +def graph_from_dependency_dict(d: dict) -> dict: + g = {} + for k in list(d): + g[k] = set() + # add the dependencies for every sub-case; should there be cases + # that are mutally exclusive in the future, the graphs will be + # distinguished + for el in list(d[k]): + g[k] |= set(d[k][el]) + + return g + +def is_acyclic(d: dict) -> bool: + g = graph_from_dependency_dict(d) + ts = TopologicalSorter(g) + try: + # get node iterator + order = ts.static_order() + # try iteration + _ = [*order] + except CycleError: + return False + + return True + +def check_dependency_graph(dependency_dir: str = dependency_dir, + supplement: str = None) -> bool: + d = read_dependency_dict(dependency_dir=dependency_dir) + if supplement is not None: + with open(supplement) as f: + d = dict_merge(json.load(f), d) + + return is_acyclic(d) + +def path_exists(s): + if not os.path.exists(s): + raise ArgumentTypeError("Must specify a valid vyos-1x dependency directory") + return s + +def main(): + parser = ArgumentParser(description='generate and save dict from xml defintions') + parser.add_argument('--dependency-dir', type=path_exists, + default=dependency_dir, + help='location of vyos-1x dependency directory') + parser.add_argument('--supplement', type=str, + help='supplemental dependency file') + args = vars(parser.parse_args()) + + if not check_dependency_graph(**args): + print("dependency error: cycle exists") + sys.exit(1) + + print("dependency graph acyclic") + sys.exit(0) + +if __name__ == '__main__': + main() diff --git a/src/helpers/geoip-update.py b/src/helpers/geoip-update.py new file mode 100644 index 0000000..34accf2 --- /dev/null +++ b/src/helpers/geoip-update.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import sys + +from vyos.configquery import ConfigTreeQuery +from vyos.firewall import geoip_update + +def get_config(config=None): + if config: + conf = config + else: + conf = ConfigTreeQuery() + base = ['firewall'] + + if not conf.exists(base): + return None + + return conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("--force", help="Force update", action="store_true") + args = parser.parse_args() + + firewall = get_config() + + if not geoip_update(firewall, force=args.force): + sys.exit(1) diff --git a/src/helpers/priority.py b/src/helpers/priority.py new file mode 100644 index 0000000..0418610 --- /dev/null +++ b/src/helpers/priority.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +import sys +from argparse import ArgumentParser +from tabulate import tabulate + +from vyos.priority import get_priority_data + +if __name__ == '__main__': + parser = ArgumentParser() + parser.add_argument('--legacy-format', action='store_true', + help="format output for comparison with legacy 'priority.pl'") + args = parser.parse_args() + + prio_list = get_priority_data() + if args.legacy_format: + for p in prio_list: + print(f'{p[2]} {"/".join(p[0])}') + sys.exit(0) + + l = [] + for p in prio_list: + l.append((p[2], p[1], p[0])) + headers = ['priority', 'owner', 'path'] + out = tabulate(l, headers, numalign='right') + print(out) diff --git a/src/helpers/read-saved-value.py b/src/helpers/read-saved-value.py new file mode 100644 index 0000000..1463e9f --- /dev/null +++ b/src/helpers/read-saved-value.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +from argparse import ArgumentParser +from vyos.utils.config import read_saved_value + +if __name__ == '__main__': + parser = ArgumentParser() + parser.add_argument('--path', nargs='*') + args = parser.parse_args() + + out = read_saved_value(args.path) if args.path else '' + if isinstance(out, list): + out = ' '.join(out) + print(out) diff --git a/src/helpers/run-config-activation.py b/src/helpers/run-config-activation.py new file mode 100644 index 0000000..5829370 --- /dev/null +++ b/src/helpers/run-config-activation.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import logging +from pathlib import Path +from argparse import ArgumentParser + +from vyos.compose_config import ComposeConfig +from vyos.compose_config import ComposeConfigError +from vyos.defaults import directories + +parser = ArgumentParser() +parser.add_argument('config_file', type=str, + help="configuration file to modify with system-specific settings") +parser.add_argument('--test-script', type=str, + help="test effect of named script") + +args = parser.parse_args() + +checkpoint_file = '/run/vyos-activate-checkpoint' +log_file = Path(directories['config']).joinpath('vyos-activate.log') + +logger = logging.getLogger(__name__) +fh = logging.FileHandler(log_file) +formatter = logging.Formatter('%(message)s') +fh.setFormatter(formatter) +logger.addHandler(fh) + +if 'vyos-activate-debug' in Path('/proc/cmdline').read_text(): + print(f'\nactivate-debug enabled: file {checkpoint_file}_* on error') + debug = checkpoint_file + logger.setLevel(logging.DEBUG) +else: + debug = None + logger.setLevel(logging.INFO) + +def sort_key(s: Path): + s = s.stem + pre, rem = re.match(r'(\d*)(?:-)?(.+)', s).groups() + return int(pre or 0), rem + +def file_ext(file_name: str) -> str: + """Return an identifier from file name for checkpoint file extension. + """ + return Path(file_name).stem + +script_dir = Path(directories['activate']) + +if args.test_script: + script_list = [script_dir.joinpath(args.test_script)] +else: + script_list = sorted(script_dir.glob('*.py'), key=sort_key) + +config_file = args.config_file +config_str = Path(config_file).read_text() + +compose = ComposeConfig(config_str, checkpoint_file=debug) + +for file in script_list: + file = file.as_posix() + logger.info(f'calling {file}') + try: + compose.apply_file(file, func_name='activate') + except ComposeConfigError as e: + if debug: + compose.write(f'{compose.checkpoint_file}_{file_ext(file)}') + logger.error(f'config-activation error in {file}: {e}') + +compose.write(config_file, with_version=True) diff --git a/src/helpers/run-config-migration.py b/src/helpers/run-config-migration.py new file mode 100644 index 0000000..e6ce973 --- /dev/null +++ b/src/helpers/run-config-migration.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys +import time +from argparse import ArgumentParser +from shutil import copyfile + +from vyos.migrate import ConfigMigrate +from vyos.migrate import ConfigMigrateError + +parser = ArgumentParser() +parser.add_argument('config_file', type=str, + help="configuration file to migrate") +parser.add_argument('--test-script', type=str, + help="test named script") +parser.add_argument('--output-file', type=str, + help="write to named output file instead of config file") +parser.add_argument('--force', action='store_true', + help="force run of all migration scripts") + +args = parser.parse_args() + +config_file = args.config_file +out_file = args.output_file +test_script = args.test_script +force = args.force + +if not os.access(config_file, os.R_OK): + print(f"Config file '{config_file}' not readable") + sys.exit(1) + +if out_file is None: + if not os.access(config_file, os.W_OK): + print(f"Config file '{config_file}' not writeable") + sys.exit(1) +else: + try: + open(out_file, 'w').close() + except OSError: + print(f"Output file '{out_file}' not writeable") + sys.exit(1) + +config_migrate = ConfigMigrate(config_file, force=force, output_file=out_file) + +if test_script: + # run_script and exit + config_migrate.run_script(test_script) + sys.exit(0) + +backup = None +if out_file is None: + timestr = time.strftime("%Y%m%d-%H%M%S") + backup = f'{config_file}.{timestr}.pre-migration' + copyfile(config_file, backup) + +try: + config_migrate.run() +except ConfigMigrateError as e: + print(f'Error: {e}') + sys.exit(1) + +if backup is not None and not config_migrate.config_modified: + os.unlink(backup) diff --git a/src/helpers/simple-download.py b/src/helpers/simple-download.py new file mode 100644 index 0000000..501af75 --- /dev/null +++ b/src/helpers/simple-download.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 + +import sys +from argparse import ArgumentParser +from vyos.remote import download + +parser = ArgumentParser() +parser.add_argument('--local-file', help='local file', required=True) +parser.add_argument('--remote-path', help='remote path', required=True) + +args = parser.parse_args() + +try: + download(args.local_file, args.remote_path, + check_space=True, raise_error=True) +except Exception as e: + print(e) + sys.exit(1) + +sys.exit() diff --git a/src/helpers/strip-private.py b/src/helpers/strip-private.py new file mode 100644 index 0000000..cb29069 --- /dev/null +++ b/src/helpers/strip-private.py @@ -0,0 +1,153 @@ +#!/usr/bin/python3 + +# Copyright 2021-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import re +import sys + +from netaddr import IPNetwork, AddrFormatError + +parser = argparse.ArgumentParser(description='strip off private information from VyOS config') + +strictness = parser.add_mutually_exclusive_group() +strictness.add_argument('--loose', action='store_true', help='remove only information specified as arguments') +strictness.add_argument('--strict', action='store_true', help='remove any private information (implies all arguments below). This is the default behavior.') + +parser.add_argument('--mac', action='store_true', help='strip off MAC addresses') +parser.add_argument('--hostname', action='store_true', help='strip off system host and domain names') +parser.add_argument('--username', action='store_true', help='strip off user names') +parser.add_argument('--dhcp', action='store_true', help='strip off DHCP shared network and static mapping names') +parser.add_argument('--domain', action='store_true', help='strip off domain names') +parser.add_argument('--asn', action='store_true', help='strip off BGP ASNs') +parser.add_argument('--snmp', action='store_true', help='strip off SNMP location information') +parser.add_argument('--lldp', action='store_true', help='strip off LLDP location information') + +address_preserval = parser.add_mutually_exclusive_group() +address_preserval.add_argument('--address', action='store_true', help='strip off all IPv4 and IPv6 addresses') +address_preserval.add_argument('--public-address', action='store_true', help='only strip off public IPv4 and IPv6 addresses') +address_preserval.add_argument('--keep-address', action='store_true', help='preserve all IPv4 and IPv6 addresses') + +# Censor the first half of the address. +ipv4_re = re.compile(r'(\d{1,3}\.){2}(\d{1,3}\.\d{1,3})') +ipv4_subst = r'xxx.xxx.\2' + +# Censor all but the first two fields. +ipv6_re = re.compile(r'([0-9a-fA-F]{1,4}\:){2}([0-9a-fA-F:]+)') +ipv6_subst = r'xxxx:xxxx:\2' + +def ip_match(match: re.Match, subst: str) -> str: + """ + Take a Match and a substitution pattern, check if the match contains a valid IP address, strip + information if it is. This routine is intended to be passed to `re.sub' as a replacement pattern. + """ + result = match.group(0) + # Is this a valid IP address? + try: + addr = IPNetwork(result).ip + # No? Then we've got nothing to do with it. + except AddrFormatError: + return result + # Should we strip it? + if args.address or (args.public_address and not addr.is_private()): + return match.expand(subst) + # No? Then we'll leave it as is. + else: + return result + +def strip_address(line: str) -> str: + """ + Strip IPv4 and IPv6 addresses from the given string. + """ + return ipv4_re.sub(lambda match: ip_match(match, ipv4_subst), ipv6_re.sub(lambda match: ip_match(match, ipv6_subst), line)) + +def strip_lines(rules: tuple) -> None: + """ + Read stdin line by line and apply the given stripping rules. + """ + try: + for line in sys.stdin: + if not args.keep_address: + line = strip_address(line) + for (condition, regexp, subst) in rules: + if condition: + line = regexp.sub(subst, line) + print(line, end='') + # stdin can be cut for any reason, such as user interrupt or the pager terminating before the text can be read. + # All we can do is gracefully exit. + except (BrokenPipeError, EOFError, KeyboardInterrupt): + sys.exit(1) + +if __name__ == "__main__": + args = parser.parse_args() + # Strict mode is the default and the absence of loose mode implies presence of strict mode. + if not args.loose: + args.mac = args.domain = args.hostname = args.username = args.dhcp = args.asn = args.snmp = args.lldp = True + if not args.public_address and not args.keep_address: + args.address = True + elif not args.address and not args.public_address: + args.keep_address = True + + # (condition, precompiled regexp, substitution string) + stripping_rules = [ + # Strip passwords + (True, re.compile(r'password \S+'), 'password xxxxxx'), + (True, re.compile(r'cisco-authentication \S+'), 'cisco-authentication xxxxxx'), + # Strip public key information + (True, re.compile(r'public-keys \S+'), 'public-keys xxxx@xxx.xxx'), + (True, re.compile(r'type \'ssh-(rsa|dss)\''), 'type ssh-xxx'), + (True, re.compile(r' key \S+'), ' key xxxxxx'), + # Strip bucket + (True, re.compile(r' bucket \S+'), ' bucket xxxxxx'), + # Strip tokens + (True, re.compile(r' token \S+'), ' token xxxxxx'), + # Strip OpenVPN secrets + (True, re.compile(r'(shared-secret-key-file|ca-cert-file|cert-file|dh-file|key-file|client) (\S+)'), r'\1 xxxxxx'), + # Strip IPSEC secrets + (True, re.compile(r'pre-shared-secret \S+'), 'pre-shared-secret xxxxxx'), + (True, re.compile(r'secret \S+'), 'secret xxxxxx'), + # Strip OSPF md5-key + (True, re.compile(r'md5-key \S+'), 'md5-key xxxxxx'), + # Strip WireGuard private-key + (True, re.compile(r'private-key \S+'), 'private-key xxxxxx'), + + # Strip MAC addresses + (args.mac, re.compile(r'([0-9a-fA-F]{2}\:){5}([0-9a-fA-F]{2}((\:{0,1})){3})'), r'xx:xx:xx:xx:xx:\2'), + + # Strip host-name, domain-name, domain-search and url + (args.hostname, re.compile(r'(host-name|domain-name|domain-search|url) \S+'), r'\1 xxxxxx'), + + # Strip user-names + (args.username, re.compile(r'(user|username|user-id) \S+'), r'\1 xxxxxx'), + # Strip full-name + (args.username, re.compile(r'(full-name) [ -_A-Z a-z]+'), r'\1 xxxxxx'), + + # Strip DHCP static-mapping and shared network names + (args.dhcp, re.compile(r'(shared-network-name|static-mapping) \S+'), r'\1 xxxxxx'), + + # Strip host/domain names + (args.domain, re.compile(r' (peer|remote-host|local-host|server) ([\w-]+\.)+[\w-]+'), r' \1 xxxxx.tld'), + + # Strip BGP ASNs + (args.asn, re.compile(r'(bgp|remote-as) (\d+)'), r'\1 XXXXXX'), + + # Strip LLDP location parameters + (args.lldp, re.compile(r'(altitude|datum|latitude|longitude|ca-value|country-code) (\S+)'), r'\1 xxxxxx'), + + # Strip SNMP location + (args.snmp, re.compile(r'(location) \S+'), r'\1 xxxxxx'), + ] + strip_lines(stripping_rules) diff --git a/src/helpers/vyos-boot-config-loader.py b/src/helpers/vyos-boot-config-loader.py new file mode 100644 index 0000000..42de696 --- /dev/null +++ b/src/helpers/vyos-boot-config-loader.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +import os +import sys +import pwd +import grp +import traceback +from datetime import datetime + +from vyos.defaults import directories, config_status +from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.configtree import ConfigTree +from vyos.utils.process import cmd + +STATUS_FILE = config_status +TRACE_FILE = '/tmp/boot-config-trace' + +CFG_GROUP = 'vyattacfg' + +trace_config = False + +if 'log' in directories: + LOG_DIR = directories['log'] +else: + LOG_DIR = '/var/log/vyatta' + +LOG_FILE = LOG_DIR + '/vyos-boot-config-loader.log' + +try: + with open('/proc/cmdline', 'r') as f: + cmdline = f.read() + if 'vyos-debug' in cmdline: + os.environ['VYOS_DEBUG'] = 'yes' + if 'vyos-config-debug' in cmdline: + os.environ['VYOS_DEBUG'] = 'yes' + trace_config = True +except Exception as e: + print('{0}'.format(e)) + +def write_config_status(status): + try: + with open(STATUS_FILE, 'w') as f: + f.write('{0}\n'.format(status)) + except Exception as e: + print('{0}'.format(e)) + +def trace_to_file(trace_file_name): + try: + with open(trace_file_name, 'w') as trace_file: + traceback.print_exc(file=trace_file) + except Exception as e: + print('{0}'.format(e)) + +def failsafe(config_file_name): + fail_msg = """ + !!!!! + There were errors loading the configuration + Please examine the errors in + {0} + and correct + !!!!! + """.format(TRACE_FILE) + + print(fail_msg, file=sys.stderr) + + users = [x[0] for x in pwd.getpwall()] + if 'vyos' in users: + return + + try: + with open(config_file_name, 'r') as f: + config_file = f.read() + except Exception as e: + print("Catastrophic: no default config file " + "'{0}'".format(config_file_name)) + sys.exit(1) + + config = ConfigTree(config_file) + if not config.exists(['system', 'login', 'user', 'vyos', + 'authentication', 'encrypted-password']): + print("No password entry in default config file;") + print("unable to recover password for user 'vyos'.") + sys.exit(1) + else: + passwd = config.return_value(['system', 'login', 'user', 'vyos', + 'authentication', + 'encrypted-password']) + + cmd(f"useradd --create-home --no-user-group --shell /bin/vbash --password '{passwd}' "\ + "--groups frr,frrvty,vyattacfg,sudo,adm,dip,disk vyos") + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("Must specify boot config file.") + sys.exit(1) + else: + file_name = sys.argv[1] + + # Set user and group options, so that others will be able to commit + # Currently, the only caller does 'sg CFG_GROUP', but that may change + cfg_group = grp.getgrnam(CFG_GROUP) + os.setgid(cfg_group.gr_gid) + + # Need to set file permissions to 775 so that every vyattacfg group + # member has write access to the running config + os.umask(0o002) + + session = ConfigSession(os.getpid(), 'vyos-boot-config-loader') + env = session.get_session_env() + + default_file_name = env['vyatta_sysconfdir'] + '/config.boot.default' + + try: + with open(file_name, 'r') as f: + config_file = f.read() + except Exception: + write_config_status(1) + if trace_config: + failsafe(default_file_name) + trace_to_file(TRACE_FILE) + sys.exit(1) + + try: + time_begin_load = datetime.now() + load_out = session.load_config(file_name) + time_end_load = datetime.now() + time_begin_commit = datetime.now() + commit_out = session.commit() + time_end_commit = datetime.now() + write_config_status(0) + except ConfigSessionError: + # If here, there is no use doing session.discard, as we have no + # recoverable config environment, and will only throw an error + write_config_status(1) + if trace_config: + failsafe(default_file_name) + trace_to_file(TRACE_FILE) + sys.exit(1) + + time_elapsed_load = time_end_load - time_begin_load + time_elapsed_commit = time_end_commit - time_begin_commit + + try: + if not os.path.exists(LOG_DIR): + os.mkdir(LOG_DIR) + with open(LOG_FILE, 'a') as f: + f.write('\n\n') + f.write('{0} Begin config load\n' + ''.format(time_begin_load)) + f.write(load_out) + f.write('{0} End config load\n' + ''.format(time_end_load)) + f.write('Elapsed time for config load: {0}\n' + ''.format(time_elapsed_load)) + f.write('{0} Begin config commit\n' + ''.format(time_begin_commit)) + f.write(commit_out) + f.write('{0} End config commit\n' + ''.format(time_end_commit)) + f.write('Elapsed time for config commit: {0}\n' + ''.format(time_elapsed_commit)) + except Exception as e: + print('{0}'.format(e)) diff --git a/src/helpers/vyos-certbot-renew-pki.sh b/src/helpers/vyos-certbot-renew-pki.sh new file mode 100644 index 0000000..d0b663f --- /dev/null +++ b/src/helpers/vyos-certbot-renew-pki.sh @@ -0,0 +1,3 @@ +#!/bin/sh +source /opt/vyatta/etc/functions/script-template +/usr/libexec/vyos/conf_mode/pki.py certbot_renew diff --git a/src/helpers/vyos-check-wwan.py b/src/helpers/vyos-check-wwan.py new file mode 100644 index 0000000..334f08d --- /dev/null +++ b/src/helpers/vyos-check-wwan.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configquery import VbashOpRun +from vyos.configquery import ConfigTreeQuery + +from vyos.utils.network import is_wwan_connected + +conf = ConfigTreeQuery() +dict = conf.get_config_dict(['interfaces', 'wwan'], key_mangling=('-', '_'), + get_first_key=True) + +for interface, interface_config in dict.items(): + if not is_wwan_connected(interface): + if 'disable' in interface_config: + # do not restart this interface as it's disabled by the user + continue + + op = VbashOpRun() + op.run(['connect', 'interface', interface]) + +exit(0) diff --git a/src/helpers/vyos-config-encrypt.py b/src/helpers/vyos-config-encrypt.py new file mode 100644 index 0000000..84860bd --- /dev/null +++ b/src/helpers/vyos-config-encrypt.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import shutil +import sys + +from argparse import ArgumentParser +from cryptography.fernet import Fernet +from tempfile import NamedTemporaryFile +from tempfile import TemporaryDirectory + +from vyos.tpm import clear_tpm_key +from vyos.tpm import read_tpm_key +from vyos.tpm import write_tpm_key +from vyos.utils.io import ask_input, ask_yes_no +from vyos.utils.process import cmd + +persistpath_cmd = '/opt/vyatta/sbin/vyos-persistpath' +mount_paths = ['/config', '/opt/vyatta/etc/config'] +dm_device = '/dev/mapper/vyos_config' + +def is_opened(): + return os.path.exists(dm_device) + +def get_current_image(): + with open('/proc/cmdline', 'r') as f: + args = f.read().split(" ") + for arg in args: + if 'vyos-union' in arg: + k, v = arg.split("=") + path_split = v.split("/") + return path_split[-1] + return None + +def load_config(key): + if not key: + return + + persist_path = cmd(persistpath_cmd).strip() + image_name = get_current_image() + image_path = os.path.join(persist_path, 'luks', image_name) + + if not os.path.exists(image_path): + raise Exception("Encrypted config volume doesn't exist") + + if is_opened(): + print('Encrypted config volume is already mounted') + return + + with NamedTemporaryFile(dir='/dev/shm', delete=False) as f: + f.write(key) + key_file = f.name + + cmd(f'cryptsetup -q open {image_path} vyos_config --key-file={key_file}') + + for path in mount_paths: + cmd(f'mount /dev/mapper/vyos_config {path}') + cmd(f'chgrp -R vyattacfg {path}') + + os.unlink(key_file) + + return True + +def encrypt_config(key, recovery_key): + if is_opened(): + raise Exception('An encrypted config volume is already mapped') + + # Clear and write key to TPM + try: + clear_tpm_key() + except: + pass + write_tpm_key(key) + + persist_path = cmd(persistpath_cmd).strip() + size = ask_input('Enter size of encrypted config partition (MB): ', numeric_only=True, default=512) + + luks_folder = os.path.join(persist_path, 'luks') + + if not os.path.isdir(luks_folder): + os.mkdir(luks_folder) + + image_name = get_current_image() + image_path = os.path.join(luks_folder, image_name) + + # Create file for encrypted config + cmd(f'fallocate -l {size}M {image_path}') + + # Write TPM key for slot #1 + with NamedTemporaryFile(dir='/dev/shm', delete=False) as f: + f.write(key) + key_file = f.name + + # Format and add main key to volume + cmd(f'cryptsetup -q luksFormat {image_path} {key_file}') + + if recovery_key: + # Write recovery key for slot 2 + with NamedTemporaryFile(dir='/dev/shm', delete=False) as f: + f.write(recovery_key) + recovery_key_file = f.name + + cmd(f'cryptsetup -q luksAddKey {image_path} {recovery_key_file} --key-file={key_file}') + + # Open encrypted volume and format with ext4 + cmd(f'cryptsetup -q open {image_path} vyos_config --key-file={key_file}') + cmd('mkfs.ext4 /dev/mapper/vyos_config') + + with TemporaryDirectory() as d: + cmd(f'mount /dev/mapper/vyos_config {d}') + + # Move /config to encrypted volume + shutil.copytree('/config', d, copy_function=shutil.move, dirs_exist_ok=True) + + cmd(f'umount {d}') + + os.unlink(key_file) + + if recovery_key: + os.unlink(recovery_key_file) + + for path in mount_paths: + cmd(f'mount /dev/mapper/vyos_config {path}') + cmd(f'chgrp vyattacfg {path}') + + return True + +def decrypt_config(key): + if not key: + return + + persist_path = cmd(persistpath_cmd).strip() + image_name = get_current_image() + image_path = os.path.join(persist_path, 'luks', image_name) + + if not os.path.exists(image_path): + raise Exception("Encrypted config volume doesn't exist") + + key_file = None + + if not is_opened(): + with NamedTemporaryFile(dir='/dev/shm', delete=False) as f: + f.write(key) + key_file = f.name + + cmd(f'cryptsetup -q open {image_path} vyos_config --key-file={key_file}') + + # unmount encrypted volume mount points + for path in mount_paths: + if os.path.ismount(path): + cmd(f'umount {path}') + + # If /config is populated, move to /config.old + if len(os.listdir('/config')) > 0: + print('Moving existing /config folder to /config.old') + shutil.move('/config', '/config.old') + + # Temporarily mount encrypted volume and migrate files to /config on rootfs + with TemporaryDirectory() as d: + cmd(f'mount /dev/mapper/vyos_config {d}') + + # Move encrypted volume to /config + shutil.copytree(d, '/config', copy_function=shutil.move, dirs_exist_ok=True) + cmd(f'chgrp -R vyattacfg /config') + + cmd(f'umount {d}') + + # Close encrypted volume + cmd('cryptsetup -q close vyos_config') + + # Remove encrypted volume image file and key + if key_file: + os.unlink(key_file) + os.unlink(image_path) + + try: + clear_tpm_key() + except: + pass + + return True + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("Must specify action.") + sys.exit(1) + + parser = ArgumentParser(description='Config encryption') + parser.add_argument('--disable', help='Disable encryption', action="store_true") + parser.add_argument('--enable', help='Enable encryption', action="store_true") + parser.add_argument('--load', help='Load encrypted config volume', action="store_true") + args = parser.parse_args() + + tpm_exists = os.path.exists('/sys/class/tpm/tpm0') + + key = None + recovery_key = None + need_recovery = False + + question_key_str = 'recovery key' if tpm_exists else 'key' + + if tpm_exists: + if args.enable: + key = Fernet.generate_key() + elif args.disable or args.load: + try: + key = read_tpm_key() + need_recovery = False + except: + print('Failed to read key from TPM, recovery key required') + need_recovery = True + else: + need_recovery = True + + if args.enable and not tpm_exists: + print('WARNING: VyOS will boot into a default config when encrypted without a TPM') + print('You will need to manually login with default credentials and use "encryption load"') + print('to mount the encrypted volume and use "load /config/config.boot"') + + if not ask_yes_no('Are you sure you want to proceed?'): + sys.exit(0) + + if need_recovery or (args.enable and not ask_yes_no(f'Automatically generate a {question_key_str}?', default=True)): + while True: + recovery_key = ask_input(f'Enter {question_key_str}:', default=None).encode() + + if len(recovery_key) >= 32: + break + + print('Invalid key - must be at least 32 characters, try again.') + else: + recovery_key = Fernet.generate_key() + + try: + if args.disable: + decrypt_config(key or recovery_key) + + print('Encrypted config volume has been disabled') + print('Contents have been migrated to /config on rootfs') + elif args.load: + load_config(key or recovery_key) + + print('Encrypted config volume has been mounted') + print('Use "load /config/config.boot" to load configuration') + elif args.enable and tpm_exists: + encrypt_config(key, recovery_key) + + print('Encrypted config volume has been enabled with TPM') + print('Backup the recovery key in a safe place!') + print('Recovery key: ' + recovery_key.decode()) + elif args.enable: + encrypt_config(recovery_key) + + print('Encrypted config volume has been enabled without TPM') + print('Backup the key in a safe place!') + print('Key: ' + recovery_key.decode()) + except Exception as e: + word = 'decrypt' if args.disable or args.load else 'encrypt' + print(f'Failed to {word} config: {e}') diff --git a/src/helpers/vyos-domain-resolver.py b/src/helpers/vyos-domain-resolver.py new file mode 100644 index 0000000..57cfcab --- /dev/null +++ b/src/helpers/vyos-domain-resolver.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json +import time + +from vyos.configdict import dict_merge +from vyos.configquery import ConfigTreeQuery +from vyos.firewall import fqdn_config_parse +from vyos.firewall import fqdn_resolve +from vyos.utils.commit import commit_in_progress +from vyos.utils.dict import dict_search_args +from vyos.utils.process import cmd +from vyos.utils.process import run +from vyos.xml_ref import get_defaults + +base = ['firewall'] +timeout = 300 +cache = False + +domain_state = {} + +ipv4_tables = { + 'ip vyos_mangle', + 'ip vyos_filter', + 'ip vyos_nat', + 'ip raw' +} + +ipv6_tables = { + 'ip6 vyos_mangle', + 'ip6 vyos_filter', + 'ip6 raw' +} + +def get_config(conf): + firewall = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + default_values = get_defaults(base, get_first_key=True) + + firewall = dict_merge(default_values, firewall) + + global timeout, cache + + if 'resolver_interval' in firewall: + timeout = int(firewall['resolver_interval']) + + if 'resolver_cache' in firewall: + cache = True + + fqdn_config_parse(firewall) + + return firewall + +def resolve(domains, ipv6=False): + global domain_state + + ip_list = set() + + for domain in domains: + resolved = fqdn_resolve(domain, ipv6=ipv6) + + if resolved and cache: + domain_state[domain] = resolved + elif not resolved: + if domain not in domain_state: + continue + resolved = domain_state[domain] + + ip_list = ip_list | resolved + return ip_list + +def nft_output(table, set_name, ip_list): + output = [f'flush set {table} {set_name}'] + if ip_list: + ip_str = ','.join(ip_list) + output.append(f'add element {table} {set_name} {{ {ip_str} }}') + return output + +def nft_valid_sets(): + try: + valid_sets = [] + sets_json = cmd('nft --json list sets') + sets_obj = json.loads(sets_json) + + for obj in sets_obj['nftables']: + if 'set' in obj: + family = obj['set']['family'] + table = obj['set']['table'] + name = obj['set']['name'] + valid_sets.append((f'{family} {table}', name)) + + return valid_sets + except: + return [] + +def update(firewall): + conf_lines = [] + count = 0 + + valid_sets = nft_valid_sets() + + domain_groups = dict_search_args(firewall, 'group', 'domain_group') + if domain_groups: + for set_name, domain_config in domain_groups.items(): + if 'address' not in domain_config: + continue + + nft_set_name = f'D_{set_name}' + domains = domain_config['address'] + + ip_list = resolve(domains, ipv6=False) + for table in ipv4_tables: + if (table, nft_set_name) in valid_sets: + conf_lines += nft_output(table, nft_set_name, ip_list) + + ip6_list = resolve(domains, ipv6=True) + for table in ipv6_tables: + if (table, nft_set_name) in valid_sets: + conf_lines += nft_output(table, nft_set_name, ip6_list) + count += 1 + + for set_name, domain in firewall['ip_fqdn'].items(): + table = 'ip vyos_filter' + nft_set_name = f'FQDN_{set_name}' + + ip_list = resolve([domain], ipv6=False) + + if (table, nft_set_name) in valid_sets: + conf_lines += nft_output(table, nft_set_name, ip_list) + count += 1 + + for set_name, domain in firewall['ip6_fqdn'].items(): + table = 'ip6 vyos_filter' + nft_set_name = f'FQDN_{set_name}' + + ip_list = resolve([domain], ipv6=True) + if (table, nft_set_name) in valid_sets: + conf_lines += nft_output(table, nft_set_name, ip_list) + count += 1 + + nft_conf_str = "\n".join(conf_lines) + "\n" + code = run(f'nft --file -', input=nft_conf_str) + + print(f'Updated {count} sets - result: {code}') + +if __name__ == '__main__': + print(f'VyOS domain resolver') + + count = 1 + while commit_in_progress(): + if ( count % 60 == 0 ): + print(f'Commit still in progress after {count}s - waiting') + count += 1 + time.sleep(1) + + conf = ConfigTreeQuery() + firewall = get_config(conf) + + print(f'interval: {timeout}s - cache: {cache}') + + while True: + update(firewall) + time.sleep(timeout) diff --git a/src/helpers/vyos-failover.py b/src/helpers/vyos-failover.py new file mode 100644 index 0000000..3489743 --- /dev/null +++ b/src/helpers/vyos-failover.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import json +import socket +import time + +from vyos.utils.process import rc_cmd +from pathlib import Path +from systemd import journal + + +my_name = Path(__file__).stem + + +def is_route_exists(route, gateway, interface, metric): + """Check if route with expected gateway, dev and metric exists""" + rc, data = rc_cmd(f'ip --json route show protocol failover {route} ' + f'via {gateway} dev {interface} metric {metric}') + if rc == 0: + data = json.loads(data) + if len(data) > 0: + return True + return False + + +def get_best_route_options(route, debug=False): + """ + Return current best route ('gateway, interface, metric) + + % get_best_route_options('203.0.113.1') + ('192.168.0.1', 'eth1', 1) + + % get_best_route_options('203.0.113.254') + (None, None, None) + """ + rc, data = rc_cmd(f'ip --detail --json route show protocol failover {route}') + if rc == 0: + data = json.loads(data) + if len(data) == 0: + print(f'\nRoute {route} for protocol failover was not found') + return None, None, None + # Fake metric 999 by default + # Search route with the lowest metric + best_metric = 999 + for entry in data: + if debug: print('\n', entry) + metric = entry.get('metric') + gateway = entry.get('gateway') + iface = entry.get('dev') + if metric < best_metric: + best_metric = metric + best_gateway = gateway + best_interface = iface + if debug: + print(f'### Best_route exists: {route}, best_gateway: {best_gateway}, ' + f'best_metric: {best_metric}, best_iface: {best_interface}') + return best_gateway, best_interface, best_metric + + +def is_port_open(ip, port): + """ + Check connection to remote host and port + Return True if host alive + + % is_port_open('example.com', 8080) + True + """ + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) + s.settimeout(2) + try: + s.connect((ip, int(port))) + s.shutdown(socket.SHUT_RDWR) + return True + except: + return False + finally: + s.close() + + +def is_target_alive(target_list=None, + iface='', + proto='icmp', + port=None, + debug=False, + policy='any-available') -> bool: + """Check the availability of each target in the target_list using + the specified protocol ICMP, ARP, TCP + + Args: + target_list (list): A list of IP addresses or hostnames to check. + iface (str): The name of the network interface to use for the check. + proto (str): The protocol to use for the check. Options are 'icmp', 'arp', or 'tcp'. + port (int): The port number to use for the TCP check. Only applicable if proto is 'tcp'. + debug (bool): If True, print debug information during the check. + policy (str): The policy to use for the check. Options are 'any-available' or 'all-available'. + + Returns: + bool: True if all targets are reachable according to the policy, False otherwise. + + Example: + % is_target_alive(['192.0.2.1', '192.0.2.5'], 'eth1', proto='arp', policy='all-available') + True + """ + if iface != '': + iface = f'-I {iface}' + + num_reachable_targets = 0 + for target in target_list: + match proto: + case 'icmp': + command = f'/usr/bin/ping -q {target} {iface} -n -c 2 -W 1' + rc, response = rc_cmd(command) + if debug: + print(f' [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]') + if rc == 0: + num_reachable_targets += 1 + if policy == 'any-available': + return True + + case 'arp': + command = f'/usr/bin/arping -b -c 2 -f -w 1 -i 1 {iface} {target}' + rc, response = rc_cmd(command) + if debug: + print(f' [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]') + if rc == 0: + num_reachable_targets += 1 + if policy == 'any-available': + return True + + case _ if proto == 'tcp' and port is not None: + if is_port_open(target, port): + num_reachable_targets += 1 + if policy == 'any-available': + return True + + case _: + return False + + if policy == 'all-available' and num_reachable_targets == len(target_list): + return True + + return False + + +if __name__ == '__main__': + # Parse command arguments and get config + parser = argparse.ArgumentParser() + parser.add_argument('-c', + '--config', + action='store', + help='Path to protocols failover configuration', + required=True, + type=Path) + + args = parser.parse_args() + try: + config_path = Path(args.config) + config = json.loads(config_path.read_text()) + except Exception as err: + print( + f'Configuration file "{config_path}" does not exist or malformed: {err}' + ) + exit(1) + + # Useful debug info to console, use debug = True + # sudo systemctl stop vyos-failover.service + # sudo /usr/libexec/vyos/vyos-failover.py --config /run/vyos-failover.conf + debug = False + + while(True): + + for route, route_config in config.get('route').items(): + + exists_gateway, exists_iface, exists_metric = get_best_route_options(route, debug=debug) + + for next_hop, nexthop_config in route_config.get('next_hop').items(): + conf_iface = nexthop_config.get('interface') + conf_metric = int(nexthop_config.get('metric')) + port = nexthop_config.get('check').get('port') + port_opt = f'port {port}' if port else '' + policy = nexthop_config.get('check').get('policy') + proto = nexthop_config.get('check').get('type') + target = nexthop_config.get('check').get('target') + timeout = nexthop_config.get('check').get('timeout') + onlink = 'onlink' if 'onlink' in nexthop_config else '' + + # Route not found in the current routing table + if not is_route_exists(route, next_hop, conf_iface, conf_metric): + if debug: print(f" [NEW_ROUTE_DETECTED] route: [{route}]") + # Add route if check-target alive + if is_target_alive(target, conf_iface, proto, port, debug=debug, policy=policy): + if debug: print(f' [ ADD ] -- ip route add {route} via {next_hop} dev {conf_iface} ' + f'metric {conf_metric} proto failover\n###') + rc, command = rc_cmd(f'ip route add {route} via {next_hop} dev {conf_iface} ' + f'{onlink} metric {conf_metric} proto failover') + # If something is wrong and gateway not added + # Example: Error: Next-hop has invalid gateway. + if rc !=0: + if debug: print(f'{command} -- return-code [RC: {rc}] {next_hop} dev {conf_iface}') + else: + journal.send(f'ip route add {route} via {next_hop} dev {conf_iface} ' + f'{onlink} metric {conf_metric} proto failover', SYSLOG_IDENTIFIER=my_name) + else: + if debug: print(f' [ TARGET_FAIL ] target checks fails for [{target}], do nothing') + journal.send(f'Check fail for route {route} target {target} proto {proto} ' + f'{port_opt}', SYSLOG_IDENTIFIER=my_name) + + # Route was added, check if the target is alive + # We should delete route if check fails only if route exists in the routing table + if not is_target_alive(target, conf_iface, proto, port, debug=debug, policy=policy) and \ + is_route_exists(route, next_hop, conf_iface, conf_metric): + if debug: + print(f'Nexh_hop {next_hop} fail, target not response') + print(f' [ DEL ] -- ip route del {route} via {next_hop} dev {conf_iface} ' + f'metric {conf_metric} proto failover [DELETE]') + rc_cmd(f'ip route del {route} via {next_hop} dev {conf_iface} metric {conf_metric} proto failover') + journal.send(f'ip route del {route} via {next_hop} dev {conf_iface} ' + f'metric {conf_metric} proto failover', SYSLOG_IDENTIFIER=my_name) + + time.sleep(int(timeout)) diff --git a/src/helpers/vyos-interface-rescan.py b/src/helpers/vyos-interface-rescan.py new file mode 100644 index 0000000..0123572 --- /dev/null +++ b/src/helpers/vyos-interface-rescan.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +import os +import stat +import argparse +import logging +import netaddr + +from vyos.configtree import ConfigTree +from vyos.defaults import directories +from vyos.utils.permission import get_cfg_group_id + +debug = False + +vyos_udev_dir = directories['vyos_udev_dir'] +vyos_log_dir = directories['log'] +log_file = os.path.splitext(os.path.basename(__file__))[0] +vyos_log_file = os.path.join(vyos_log_dir, log_file) + +logger = logging.getLogger(__name__) +handler = logging.FileHandler(vyos_log_file, mode='a') +formatter = logging.Formatter('%(levelname)s: %(message)s') +handler.setFormatter(formatter) +logger.addHandler(handler) + +passlist = { + '02:07:01' : 'Interlan', + '02:60:60' : '3Com', + '02:60:8c' : '3Com', + '02:a0:c9' : 'Intel', + '02:aa:3c' : 'Olivetti', + '02:cf:1f' : 'CMC', + '02:e0:3b' : 'Prominet', + '02:e6:d3' : 'BTI', + '52:54:00' : 'Realtek', + '52:54:4c' : 'Novell 2000', + '52:54:ab' : 'Realtec', + 'e2:0c:0f' : 'Kingston Technologies' +} + +def is_multicast(addr: netaddr.eui.EUI) -> bool: + return bool(addr.words[0] & 0b1) + +def is_locally_administered(addr: netaddr.eui.EUI) -> bool: + return bool(addr.words[0] & 0b10) + +def is_on_passlist(hwid: str) -> bool: + top = hwid.rsplit(':', 3)[0] + if top in list(passlist): + return True + return False + +def is_persistent(hwid: str) -> bool: + addr = netaddr.EUI(hwid) + if is_multicast(addr): + return False + if is_locally_administered(addr) and not is_on_passlist(hwid): + return False + return True + +def get_wireless_physical_device(intf: str) -> str: + if 'wlan' not in intf: + return '' + try: + tmp = os.readlink(f'/sys/class/net/{intf}/phy80211') + except OSError: + logger.critical(f"Failed to read '/sys/class/net/{intf}/phy80211'") + return '' + phy = os.path.basename(tmp) + logger.info(f"wireless phy is {phy}") + return phy + +def get_interface_type(intf: str) -> str: + if 'eth' in intf: + intf_type = 'ethernet' + elif 'wlan' in intf: + intf_type = 'wireless' + else: + logger.critical('Unrecognized interface type!') + intf_type = '' + return intf_type + +def get_new_interfaces() -> dict: + """ Read any new interface data left in /run/udev/vyos by vyos_net_name + """ + interfaces = {} + + for intf in os.listdir(vyos_udev_dir): + path = os.path.join(vyos_udev_dir, intf) + try: + with open(path) as f: + hwid = f.read().rstrip() + except OSError as e: + logger.error(f"OSError {e}") + continue + interfaces[intf] = hwid + + # reverse sort to simplify insertion in config + interfaces = {key: value for key, value in sorted(interfaces.items(), + reverse=True)} + return interfaces + +def filter_interfaces(intfs: dict) -> dict: + """ Ignore no longer existing interfaces or non-persistent mac addresses + """ + filtered = {} + + for intf, hwid in intfs.items(): + if not os.path.isdir(os.path.join('/sys/class/net', intf)): + continue + if not is_persistent(hwid): + continue + filtered[intf] = hwid + + return filtered + +def interface_rescan(config_path: str): + """ Read new data and update config file + """ + interfaces = get_new_interfaces() + + logger.debug(f"interfaces from udev: {interfaces}") + + interfaces = filter_interfaces(interfaces) + + logger.debug(f"filtered interfaces: {interfaces}") + + try: + with open(config_path) as f: + config_file = f.read() + except OSError as e: + logger.critical(f"OSError {e}") + exit(1) + + config = ConfigTree(config_file) + + for intf, hwid in interfaces.items(): + logger.info(f"Writing '{intf}' '{hwid}' to config file") + intf_type = get_interface_type(intf) + if not intf_type: + continue + if not config.exists(['interfaces', intf_type]): + config.set(['interfaces', intf_type]) + config.set_tag(['interfaces', intf_type]) + config.set(['interfaces', intf_type, intf, 'hw-id'], value=hwid) + + if intf_type == 'wireless': + phy = get_wireless_physical_device(intf) + if not phy: + continue + config.set(['interfaces', intf_type, intf, 'physical-device'], + value=phy) + + try: + with open(config_path, 'w') as f: + f.write(config.to_string()) + except OSError as e: + logger.critical(f"OSError {e}") + +def main(): + global debug + + argparser = argparse.ArgumentParser( + formatter_class=argparse.RawTextHelpFormatter) + argparser.add_argument('configfile', type=str) + argparser.add_argument('--debug', action='store_true') + args = argparser.parse_args() + + if args.debug: + debug = True + logger.setLevel(logging.DEBUG) + else: + logger.setLevel(logging.INFO) + + configfile = args.configfile + + # preserve vyattacfg group write access to running config + os.setgid(get_cfg_group_id()) + os.umask(0o002) + + # log file perms are not automatic; this could be cleaner by moving to a + # logging config file + os.chown(vyos_log_file, 0, get_cfg_group_id()) + os.chmod(vyos_log_file, + stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH) + + interface_rescan(configfile) + +if __name__ == '__main__': + main() diff --git a/src/helpers/vyos-load-config.py b/src/helpers/vyos-load-config.py new file mode 100644 index 0000000..16083fd --- /dev/null +++ b/src/helpers/vyos-load-config.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +"""Load config file from within config session. +Config file specified by URI or path (without scheme prefix). +Example: load https://somewhere.net/some.config + or + load /tmp/some.config +""" + +import os +import sys +import gzip +import tempfile +import vyos.defaults +import vyos.remote +from vyos.configsource import ConfigSourceSession, VyOSError +from vyos.migrate import ConfigMigrate +from vyos.migrate import ConfigMigrateError + +class LoadConfig(ConfigSourceSession): + """A subclass for calling 'loadFile'. + This does not belong in configsource.py, and only has a single caller. + """ + def load_config(self, path): + return self._run(['/bin/cli-shell-api','loadFile',path]) + +file_name = sys.argv[1] if len(sys.argv) > 1 else 'config.boot' +configdir = vyos.defaults.directories['config'] +protocols = ['scp', 'sftp', 'http', 'https', 'ftp', 'tftp'] + +def get_local_config(filename): + if os.path.isfile(filename): + fname = filename + elif os.path.isfile(os.path.join(configdir, filename)): + fname = os.path.join(configdir, filename) + else: + sys.exit(f"No such file '{filename}'") + + if fname.endswith('.gz'): + with gzip.open(fname, 'rb') as f: + try: + config_str = f.read().decode() + except OSError as e: + sys.exit(e) + else: + with open(fname, 'r') as f: + try: + config_str = f.read() + except OSError as e: + sys.exit(e) + + return config_str + +if any(file_name.startswith(f'{x}://') for x in protocols): + config_string = vyos.remote.get_remote_config(file_name) + if not config_string: + sys.exit(f"No such config file at '{file_name}'") +else: + config_string = get_local_config(file_name) + +config = LoadConfig() + +print(f"Loading configuration from '{file_name}'") + +with tempfile.NamedTemporaryFile() as fp: + with open(fp.name, 'w') as fd: + fd.write(config_string) + + config_migrate = ConfigMigrate(fp.name) + try: + config_migrate.run() + except ConfigMigrateError as err: + sys.exit(err) + + try: + config.load_config(fp.name) + except VyOSError as err: + sys.exit(err) + +if config.session_changed(): + print("Load complete. Use 'commit' to make changes effective.") +else: + print("No configuration changes to commit.") diff --git a/src/helpers/vyos-merge-config.py b/src/helpers/vyos-merge-config.py new file mode 100644 index 0000000..5ef845a --- /dev/null +++ b/src/helpers/vyos-merge-config.py @@ -0,0 +1,108 @@ +#!/usr/bin/python3 + +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import sys +import tempfile +import vyos.defaults +import vyos.remote + +from vyos.config import Config +from vyos.configtree import ConfigTree +from vyos.migrate import ConfigMigrate +from vyos.migrate import ConfigMigrateError +from vyos.utils.process import cmd +from vyos.utils.process import DEVNULL + +if (len(sys.argv) < 2): + print("Need config file name to merge.") + print("Usage: merge <config file> [config path]") + sys.exit(0) + +file_name = sys.argv[1] + +configdir = vyos.defaults.directories['config'] + +protocols = ['scp', 'sftp', 'http', 'https', 'ftp', 'tftp'] + +if any(x in file_name for x in protocols): + config_file = vyos.remote.get_remote_config(file_name) + if not config_file: + sys.exit("No config file by that name.") +else: + canonical_path = "{0}/{1}".format(configdir, file_name) + first_err = None + try: + with open(canonical_path, 'r') as f: + config_file = f.read() + except Exception as err: + first_err = err + try: + with open(file_name, 'r') as f: + config_file = f.read() + except Exception as err: + print(first_err) + print(err) + sys.exit(1) + +with tempfile.NamedTemporaryFile() as file_to_migrate: + with open(file_to_migrate.name, 'w') as fd: + fd.write(config_file) + + config_migrate = ConfigMigrate(file_to_migrate.name) + try: + config_migrate.run() + except ConfigMigrateError as e: + sys.exit(e) + +merge_config_tree = ConfigTree(config_file) + +effective_config = Config() +effective_config_tree = effective_config._running_config + +effective_cmds = effective_config_tree.to_commands() +merge_cmds = merge_config_tree.to_commands() + +effective_cmd_list = effective_cmds.splitlines() +merge_cmd_list = merge_cmds.splitlines() + +effective_cmd_set = set(effective_cmd_list) +add_cmds = [ cmd for cmd in merge_cmd_list if cmd not in effective_cmd_set ] + +path = None +if (len(sys.argv) > 2): + path = sys.argv[2:] + if (not effective_config_tree.exists(path) and not + merge_config_tree.exists(path)): + print("path {} does not exist in either effective or merge" + " config; will use root.".format(path)) + path = None + else: + path = " ".join(path) + +if path: + add_cmds = [ cmd for cmd in add_cmds if path in cmd ] + +for add in add_cmds: + try: + cmd(f'/opt/vyatta/sbin/my_{add}', shell=True, stderr=DEVNULL) + except OSError as err: + print(err) + +if effective_config.session_changed(): + print("Merge complete. Use 'commit' to make changes effective.") +else: + print("No configuration changes to commit.") diff --git a/src/helpers/vyos-save-config.py b/src/helpers/vyos-save-config.py new file mode 100644 index 0000000..fa2ea0c --- /dev/null +++ b/src/helpers/vyos-save-config.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# +import os +import re +import sys +from tempfile import NamedTemporaryFile +from argparse import ArgumentParser + +from vyos.config import Config +from vyos.remote import urlc +from vyos.component_version import add_system_version +from vyos.defaults import directories + +DEFAULT_CONFIG_PATH = os.path.join(directories['config'], 'config.boot') +remote_save = None + +parser = ArgumentParser(description='Save configuration') +parser.add_argument('file', type=str, nargs='?', help='Save configuration to file') +parser.add_argument('--write-json-file', type=str, help='Save JSON of configuration to file') +args = parser.parse_args() +file = args.file +json_file = args.write_json_file + +if file is not None: + save_file = file +else: + save_file = DEFAULT_CONFIG_PATH + +if re.match(r'\w+:/', save_file): + try: + remote_save = urlc(save_file) + except ValueError as e: + sys.exit(e) + +config = Config() +ct = config.get_config_tree(effective=True) + +# pylint: disable=consider-using-with +write_file = save_file if remote_save is None else NamedTemporaryFile(delete=False).name + +# config_tree is None before boot configuration is complete; +# automated saves should check boot_configuration_complete +config_str = None if ct is None else ct.to_string() +add_system_version(config_str, write_file) + +if json_file is not None and ct is not None: + try: + with open(json_file, 'w') as f: + f.write(ct.to_json()) + except OSError as e: + print(f'failed to write JSON file: {e}') + +if remote_save is not None: + try: + remote_save.upload(write_file) + finally: + os.remove(write_file) diff --git a/src/helpers/vyos-sudo.py b/src/helpers/vyos-sudo.py new file mode 100644 index 0000000..75dd7f2 --- /dev/null +++ b/src/helpers/vyos-sudo.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys + +from vyos.utils.permission import is_admin + + +if __name__ == '__main__': + if len(sys.argv) < 2: + print('Missing command argument') + sys.exit(1) + + if not is_admin(): + print('This account is not authorized to run this command') + sys.exit(1) + + os.execvp('sudo', ['sudo'] + sys.argv[1:]) diff --git a/src/helpers/vyos-vrrp-conntracksync.sh b/src/helpers/vyos-vrrp-conntracksync.sh new file mode 100644 index 0000000..90fa77f --- /dev/null +++ b/src/helpers/vyos-vrrp-conntracksync.sh @@ -0,0 +1,156 @@ +#!/bin/sh +# +# (C) 2008 by Pablo Neira Ayuso <pablo@netfilter.org> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. +# +# Description: +# +# This is the script for primary-backup setups for keepalived +# (http://www.keepalived.org). You may adapt it to make it work with other +# high-availability managers. +# +# Modified by : Mohit Mehta <mohit@vyatta.com> +# Slight modifications were made to this script for running with Vyatta +# The original script came from 0.9.14 debian conntrack-tools package + +CONNTRACKD_BIN=/usr/sbin/conntrackd +CONNTRACKD_LOCK=/var/lock/conntrack.lock +CONNTRACKD_CONFIG=/run/conntrackd/conntrackd.conf +FACILITY=daemon +LEVEL=notice +TAG=conntrack-tools +LOGCMD="logger -t $TAG -p $FACILITY.$LEVEL" +VRRP_GRP="VRRP sync-group [$2]" +FAILOVER_STATE="/var/run/vyatta-conntrackd-failover-state" + +$LOGCMD "vyos-vrrp-conntracksync invoked at `date`" + +if ! systemctl is-active --quiet conntrackd.service; then + echo "conntrackd service not running" + exit 1 +fi + +if [ ! -e $FAILOVER_STATE ]; then + mkdir -p /var/run + touch $FAILOVER_STATE +fi + +case "$1" in + master) + echo MASTER at `date` > $FAILOVER_STATE + $LOGCMD "`uname -n` transitioning to MASTER state for $VRRP_GRP" + # + # commit the external cache into the kernel table + # + $CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -c + if [ $? -eq 1 ] + then + $LOGCMD "ERROR: failed to invoke conntrackd -c" + fi + + # + # commit the expect entries to the kernel + # + $CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -c exp + if [ $? -eq 1 ] + then + $LOGCMD "ERROR: failed to invoke conntrackd -ce exp" + fi + + # + # flush the internal and the external caches + # + $CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -f + if [ $? -eq 1 ] + then + $LOGCMD "ERROR: failed to invoke conntrackd -f" + fi + + # + # resynchronize my internal cache to the kernel table + # + $CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -R + if [ $? -eq 1 ] + then + $LOGCMD "ERROR: failed to invoke conntrackd -R" + fi + + # + # send a bulk update to backups + # + $CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -B + if [ $? -eq 1 ] + then + $LOGCMD "ERROR: failed to invoke conntrackd -B" + fi + ;; + backup) + echo BACKUP at `date` > $FAILOVER_STATE + $LOGCMD "`uname -n` transitioning to BACKUP state for $VRRP_GRP" + # + # is conntrackd running? request some statistics to check it + # + $CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -s + if [ $? -eq 1 ] + then + # + # something's wrong, do we have a lock file? + # + if [ -f $CONNTRACKD_LOCK ] + then + $LOGCMD "WARNING: conntrackd was not cleanly stopped." + $LOGCMD "If you suspect that it has crashed:" + $LOGCMD "1) Enable coredumps" + $LOGCMD "2) Try to reproduce the problem" + $LOGCMD "3) Post the coredump to netfilter-devel@vger.kernel.org" + rm -f $CONNTRACKD_LOCK + fi + $CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -d + if [ $? -eq 1 ] + then + $LOGCMD "ERROR: cannot launch conntrackd" + exit 1 + fi + fi + # + # shorten kernel conntrack timers to remove the zombie entries. + # + $CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -t + if [ $? -eq 1 ] + then + $LOGCMD "ERROR: failed to invoke conntrackd -t" + fi + + # + # request resynchronization with master firewall replica (if any) + # Note: this does nothing in the alarm approach. + # + $CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -n + if [ $? -eq 1 ] + then + $LOGCMD "ERROR: failed to invoke conntrackd -n" + fi + ;; + fault) + echo FAULT at `date` > $FAILOVER_STATE + $LOGCMD "`uname -n` transitioning to FAULT state for $VRRP_GRP" + # + # shorten kernel conntrack timers to remove the zombie entries. + # + $CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -t + if [ $? -eq 1 ] + then + $LOGCMD "ERROR: failed to invoke conntrackd -t" + fi + ;; + *) + echo UNKNOWN at `date` > $FAILOVER_STATE + $LOGCMD "ERROR: `uname -n` unknown state transition for $VRRP_GRP" + echo "Usage: vyos-vrrp-conntracksync.sh {master|backup|fault}" + exit 1 + ;; +esac + +exit 0 diff --git a/src/helpers/vyos_config_sync.py b/src/helpers/vyos_config_sync.py new file mode 100644 index 0000000..9d9aec3 --- /dev/null +++ b/src/helpers/vyos_config_sync.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +import os +import json +import requests +import urllib3 +import logging +from typing import Optional, List, Tuple, Dict, Any + +from vyos.config import Config +from vyos.configtree import ConfigTree +from vyos.configtree import mask_inclusive +from vyos.template import bracketize_ipv6 + + +CONFIG_FILE = '/run/config_sync_conf.conf' + +# Logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) +logger.name = os.path.basename(__file__) + +# API +API_HEADERS = {'Content-Type': 'application/json'} + + +def post_request(url: str, + data: str, + headers: Dict[str, str]) -> requests.Response: + """Sends a POST request to the specified URL + + Args: + url (str): The URL to send the POST request to. + data (Dict[str, Any]): The data to send with the POST request. + headers (Dict[str, str]): The headers to include with the POST request. + + Returns: + requests.Response: The response object representing the server's response to the request + """ + + response = requests.post(url, + data=data, + headers=headers, + verify=False, + timeout=timeout) + return response + + + +def retrieve_config(sections: List[list[str]]) -> Tuple[Dict[str, Any], Dict[str, Any]]: + """Retrieves the configuration from the local server. + + Args: + sections: List[list[str]]: The list of sections of the configuration + to retrieve, given as list of paths. + + Returns: + Tuple[Dict[str, Any],Dict[str,Any]]: The tuple (mask, config) where: + - mask: The tree of paths of sections, as a dictionary. + - config: The subtree of masked config data, as a dictionary. + """ + + mask = ConfigTree('') + for section in sections: + mask.set(section) + mask_dict = json.loads(mask.to_json()) + + config = Config() + config_tree = config.get_config_tree() + masked = mask_inclusive(config_tree, mask) + config_dict = json.loads(masked.to_json()) + + return mask_dict, config_dict + +def set_remote_config( + address: str, + key: str, + op: str, + mask: Dict[str, Any], + config: Dict[str, Any], + port: int) -> Optional[Dict[str, Any]]: + """Loads the VyOS configuration in JSON format to a remote host. + + Args: + address (str): The address of the remote host. + key (str): The key to use for loading the configuration. + op (str): The operation to perform (set or load). + mask (dict): The dict of paths in sections. + config (dict): The dict of masked config data. + port (int): The remote API port + + Returns: + Optional[Dict[str, Any]]: The response from the remote host as a + dictionary, or None if a RequestException occurred. + """ + + headers = {'Content-Type': 'application/json'} + + # Disable the InsecureRequestWarning + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + url = f'https://{address}:{port}/configure-section' + data = json.dumps({ + 'op': op, + 'mask': mask, + 'config': config, + 'key': key + }) + + try: + config = post_request(url, data, headers) + return config.json() + except requests.exceptions.RequestException as e: + print(f"An error occurred: {e}") + logger.error(f"An error occurred: {e}") + return None + + +def is_section_revised(section: List[str]) -> bool: + from vyos.config_mgmt import is_node_revised + return is_node_revised(section) + + +def config_sync(secondary_address: str, + secondary_key: str, + sections: List[list[str]], + mode: str, + secondary_port: int): + """Retrieve a config section from primary router in JSON format and send it to + secondary router + """ + if not any(map(is_section_revised, sections)): + return + + logger.info( + f"Config synchronization: Mode={mode}, Secondary={secondary_address}" + ) + + # Sync sections ("nat", "firewall", etc) + mask_dict, config_dict = retrieve_config(sections) + logger.debug( + f"Retrieved config for sections '{sections}': {config_dict}") + + set_config = set_remote_config(address=secondary_address, + key=secondary_key, + op=mode, + mask=mask_dict, + config=config_dict, + port=secondary_port) + + logger.debug(f"Set config for sections '{sections}': {set_config}") + + +if __name__ == '__main__': + # Read configuration from file + if not os.path.exists(CONFIG_FILE): + logger.error(f"Post-commit: No config file '{CONFIG_FILE}' exists") + exit(0) + + with open(CONFIG_FILE, 'r') as f: + config_data = f.read() + + config = json.loads(config_data) + + mode = config.get('mode') + secondary_address = config.get('secondary', {}).get('address') + secondary_address = bracketize_ipv6(secondary_address) + secondary_key = config.get('secondary', {}).get('key') + secondary_port = int(config.get('secondary', {}).get('port', 443)) + sections = config.get('section') + timeout = int(config.get('secondary', {}).get('timeout')) + + if not all([mode, secondary_address, secondary_key, sections]): + logger.error("Missing required configuration data for config synchronization.") + exit(0) + + # Generate list_sections of sections/subsections + # [ + # ['interfaces', 'pseudo-ethernet'], ['interfaces', 'virtual-ethernet'], ['nat'], ['nat66'] + # ] + list_sections = [] + for section, subsections in sections.items(): + if subsections: + for subsection in subsections: + list_sections.append([section, subsection]) + else: + list_sections.append([section]) + + config_sync(secondary_address, secondary_key, list_sections, mode, secondary_port) diff --git a/src/helpers/vyos_net_name b/src/helpers/vyos_net_name new file mode 100644 index 0000000..f5de182 --- /dev/null +++ b/src/helpers/vyos_net_name @@ -0,0 +1,276 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import re +import time +import logging +import logging.handlers +import tempfile +from pathlib import Path +from sys import argv + +from vyos.configtree import ConfigTree +from vyos.defaults import directories +from vyos.utils.process import cmd +from vyos.utils.boot import boot_configuration_complete +from vyos.utils.locking import Lock +from vyos.migrate import ConfigMigrate + +# Define variables +vyos_udev_dir = directories['vyos_udev_dir'] +config_path = '/opt/vyatta/etc/config/config.boot' + + +def is_available(intfs: dict, intf_name: str) -> bool: + """Check if interface name is already assigned""" + if intf_name in list(intfs.values()): + return False + return True + + +def find_available(intfs: dict, prefix: str) -> str: + """Find lowest indexed iterface name that is not assigned""" + index_list = [ + int(x.replace(prefix, '')) for x in list(intfs.values()) if prefix in x + ] + index_list.sort() + # find 'holes' in list, if any + missing = sorted(set(range(index_list[0], index_list[-1])) - set(index_list)) + if missing: + return f'{prefix}{missing[0]}' + + return f'{prefix}{len(index_list)}' + + +def mod_ifname(ifname: str) -> str: + """Check interface with names eX and return ifname on the next format eth{ifindex} - 2""" + if re.match('^e[0-9]+$', ifname): + intf = ifname.split('e') + if intf[1]: + if int(intf[1]) >= 2: + return 'eth' + str(int(intf[1]) - 2) + else: + return 'eth' + str(intf[1]) + + return ifname + + +def get_biosdevname(ifname: str) -> str: + """Use legacy vyatta-biosdevname to query for name + + This is carried over for compatability only, and will likely be dropped + going forward. + XXX: This throws an error, and likely has for a long time, unnoticed + since vyatta_net_name redirected stderr to /dev/null. + """ + intf = mod_ifname(ifname) + + if 'eth' not in intf: + return intf + if os.path.isdir('/proc/xen'): + return intf + + time.sleep(1) + + try: + biosname = cmd(f'/sbin/biosdevname --policy all_ethN -i {ifname}') + except Exception as e: + logger.error(f'biosdevname error: {e}') + biosname = '' + + return intf if biosname == '' else biosname + + +def leave_rescan_hint(intf_name: str, hwid: str): + """Write interface information reported by udev + + This script is called while the root mount is still read-only. Leave + information in /run/udev: file name, the interface; contents, the + hardware id. + """ + try: + os.mkdir(vyos_udev_dir) + except FileExistsError: + pass + except Exception as e: + logger.critical(f'Error creating rescan hint directory: {e}') + exit(1) + + try: + with open(os.path.join(vyos_udev_dir, intf_name), 'w') as f: + f.write(hwid) + except OSError as e: + logger.critical(f'OSError {e}') + + +def get_configfile_interfaces() -> dict: + """Read existing interfaces from config file""" + interfaces: dict = {} + + if not os.path.isfile(config_path): + # If the case, then we are running off of livecd; return empty + return interfaces + + try: + with open(config_path) as f: + config_file = f.read() + except OSError as e: + logger.critical(f'OSError {e}') + exit(1) + + try: + config = ConfigTree(config_file) + except Exception: + try: + logger.debug('updating component version string syntax') + # this will update the component version string syntax, + # required for updates 1.2 --> 1.3/1.4 + with tempfile.NamedTemporaryFile() as fp: + with open(fp.name, 'w') as fd: + fd.write(config_file) + config_migrate = ConfigMigrate(fp.name) + if config_migrate.syntax_update_needed(): + config_migrate.update_syntax() + config_migrate.write_config() + with open(fp.name) as fd: + config_file = fd.read() + + config = ConfigTree(config_file) + + except Exception as e: + logger.critical(f'ConfigTree error: {e}') + exit(1) + + base = ['interfaces', 'ethernet'] + if config.exists(base): + eth_intfs = config.list_nodes(base) + for intf in eth_intfs: + path = base + [intf, 'hw-id'] + if not config.exists(path): + logger.warning(f"no 'hw-id' entry for {intf}") + continue + hwid = config.return_value(path) + if hwid in list(interfaces): + logger.warning( + f'multiple entries for {hwid}: {interfaces[hwid]}, {intf}' + ) + continue + interfaces[hwid] = intf + + base = ['interfaces', 'wireless'] + if config.exists(base): + wlan_intfs = config.list_nodes(base) + for intf in wlan_intfs: + path = base + [intf, 'hw-id'] + if not config.exists(path): + logger.warning(f"no 'hw-id' entry for {intf}") + continue + hwid = config.return_value(path) + if hwid in list(interfaces): + logger.warning( + f'multiple entries for {hwid}: {interfaces[hwid]}, {intf}' + ) + continue + interfaces[hwid] = intf + + logger.debug(f'config file entries: {interfaces}') + + return interfaces + + +def add_assigned_interfaces(intfs: dict): + """Add interfaces found by previous invocation of udev rule""" + if not os.path.isdir(vyos_udev_dir): + return + + for intf in os.listdir(vyos_udev_dir): + path = os.path.join(vyos_udev_dir, intf) + try: + with open(path) as f: + hwid = f.read().rstrip() + except OSError as e: + logger.error(f'OSError {e}') + continue + intfs[hwid] = intf + + +def on_boot_event(intf_name: str, hwid: str, predefined: str = '') -> str: + """Called on boot by vyos-router: 'coldplug' in vyatta_net_name""" + logger.info(f'lookup {intf_name}, {hwid}') + interfaces = get_configfile_interfaces() + logger.debug(f'config file interfaces are {interfaces}') + + if hwid in list(interfaces): + logger.info(f"use mapping from config file: '{hwid}' -> '{interfaces[hwid]}'") + return interfaces[hwid] + + add_assigned_interfaces(interfaces) + logger.debug(f'adding assigned interfaces: {interfaces}') + + if predefined: + newname = predefined + logger.info(f"predefined interface name for '{intf_name}' is '{newname}'") + else: + newname = get_biosdevname(intf_name) + logger.info(f"biosdevname returned '{newname}' for '{intf_name}'") + + if not is_available(interfaces, newname): + prefix = re.sub(r'\d+$', '', newname) + newname = find_available(interfaces, prefix) + + logger.info(f"new name for '{intf_name}' is '{newname}'") + + leave_rescan_hint(newname, hwid) + + return newname + + +def hotplug_event(): + # Not yet implemented, since interface-rescan will only be run on boot. + pass + + +if __name__ == '__main__': + # Set up logging to syslog + syslog_handler = logging.handlers.SysLogHandler(address='/dev/log') + formatter = logging.Formatter(f'{Path(__file__).name}: %(message)s') + syslog_handler.setFormatter(formatter) + + logger = logging.getLogger() + logger.addHandler(syslog_handler) + logger.setLevel(logging.DEBUG) + + logger.debug(f'Started with arguments: {argv}') + + if len(argv) > 3: + predef_name = argv[3] + else: + predef_name = '' + + lock = Lock('vyos_net_name') + # Wait 60 seconds for other running scripts to finish + lock.acquire(60) + + if not boot_configuration_complete(): + res = on_boot_event(argv[1], argv[2], predefined=predef_name) + logger.debug(f'on boot, returned name is {res}') + print(res) + else: + logger.debug('boot configuration complete') + + lock.release() + logger.debug('Finished') diff --git a/src/init/vyos-config b/src/init/vyos-config new file mode 100644 index 0000000..3564270 --- /dev/null +++ b/src/init/vyos-config @@ -0,0 +1,16 @@ +#!/bin/bash + +while [ ! -f /tmp/vyos-config-status ] +do + sleep 1 +done + +status=$(cat /tmp/vyos-config-status) + +if [ -z "$1" ]; then + if [ $status -ne 0 ]; then + echo "Configuration error" + else + echo "Configuration success" + fi +fi diff --git a/src/init/vyos-router b/src/init/vyos-router new file mode 100644 index 0000000..8825cc1 --- /dev/null +++ b/src/init/vyos-router @@ -0,0 +1,575 @@ +#!/bin/bash +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +. /lib/lsb/init-functions + +: ${vyatta_env:=/etc/default/vyatta} +source $vyatta_env + +declare progname=${0##*/} +declare action=$1; shift + +declare -x BOOTFILE=$vyatta_sysconfdir/config/config.boot +declare -x DEFAULT_BOOTFILE=$vyatta_sysconfdir/config.boot.default + +# If vyos-config= boot option is present, use that file instead +for x in $(cat /proc/cmdline); do + [[ $x = vyos-config=* ]] || continue + VYOS_CONFIG="${x#vyos-config=}" +done + +if [ ! -z "$VYOS_CONFIG" ]; then + if [ -r "$VYOS_CONFIG" ]; then + echo "Config selected manually: $VYOS_CONFIG" + declare -x BOOTFILE="$VYOS_CONFIG" + else + echo "WARNING: Could not read selected config file, using default!" + fi +fi + +declare -a subinit +declare -a all_subinits=( firewall ) + +if [ $# -gt 0 ] ; then + for s in $@ ; do + [ -x ${vyatta_sbindir}/${s}.init ] && subinit[${#subinit}]=$s + done +else + for s in ${all_subinits[@]} ; do + [ -x ${vyatta_sbindir}/${s}.init ] && subinit[${#subinit}]=$s + done +fi + +GROUP=vyattacfg + +# easy way to make empty file without any command +empty() +{ + >$1 +} + +# check if bootup of this portion is disabled +disabled () { + grep -q -w no-vyos-$1 /proc/cmdline +} + +# Load encrypted config volume +mount_encrypted_config() { + persist_path=$(/opt/vyatta/sbin/vyos-persistpath) + if [ $? == 0 ]; then + if [ -e $persist_path/boot ]; then + image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//') + + if [ -z "$image_name" ]; then + return + fi + + if [ ! -f $persist_path/luks/$image_name ]; then + return + fi + + vyos_tpm_key=$(python3 -c 'from vyos.tpm import read_tpm_key; print(read_tpm_key().decode())' 2>/dev/null) + + if [ $? -ne 0 ]; then + echo "ERROR: Failed to fetch encryption key from TPM. Encrypted config volume has not been mounted" + echo "Use 'encryption load' to load volume with recovery key" + echo "or 'encryption disable' to decrypt volume with recovery key" + return + fi + + echo $vyos_tpm_key | tr -d '\r\n' | cryptsetup open $persist_path/luks/$image_name vyos_config --key-file=- + + if [ $? -ne 0 ]; then + echo "ERROR: Failed to decrypt config volume. Encrypted config volume has not been mounted" + echo "Use 'encryption load' to load volume with recovery key" + echo "or 'encryption disable' to decrypt volume with recovery key" + return + fi + + mount /dev/mapper/vyos_config /config + mount /dev/mapper/vyos_config $vyatta_sysconfdir/config + + echo "Mounted encrypted config volume" + fi + fi +} + +unmount_encrypted_config() { + persist_path=$(/opt/vyatta/sbin/vyos-persistpath) + if [ $? == 0 ]; then + if [ -e $persist_path/boot ]; then + image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//') + + if [ -z "$image_name" ]; then + return + fi + + if [ ! -f $persist_path/luks/$image_name ]; then + return + fi + + umount /config + umount $vyatta_sysconfdir/config + + cryptsetup close vyos_config + fi + fi +} + +# if necessary, provide initial config +init_bootfile () { + # define and version default boot config if not present + if [ ! -r $DEFAULT_BOOTFILE ]; then + if [ -f $vyos_data_dir/config.boot.default ]; then + cp $vyos_data_dir/config.boot.default $DEFAULT_BOOTFILE + $vyos_libexec_dir/add-system-version.py >> $DEFAULT_BOOTFILE + fi + fi + if [ ! -r $BOOTFILE ] ; then + if [ -f $DEFAULT_BOOTFILE ]; then + cp $DEFAULT_BOOTFILE $BOOTFILE + else + $vyos_libexec_dir/add-system-version.py > $BOOTFILE + fi + chgrp ${GROUP} $BOOTFILE + chmod 660 $BOOTFILE + fi +} + +# if necessary, migrate initial config +migrate_bootfile () +{ + if [ -x $vyos_libexec_dir/run-config-migration.py ]; then + log_progress_msg migrate + sg ${GROUP} -c "$vyos_libexec_dir/run-config-migration.py $BOOTFILE" + fi +} + +# configure system-specific settings +system_config () +{ + if [ -x $vyos_libexec_dir/run-config-activation.py ]; then + log_progress_msg system + sg ${GROUP} -c "$vyos_libexec_dir/run-config-activation.py $BOOTFILE" + fi +} + +# load the initial config +load_bootfile () +{ + log_progress_msg configure + ( + if [ -f /etc/default/vyatta-load-boot ]; then + # build-specific environment for boot-time config loading + source /etc/default/vyatta-load-boot + fi + if [ -x $vyos_libexec_dir/vyos-boot-config-loader.py ]; then + sg ${GROUP} -c "$vyos_libexec_dir/vyos-boot-config-loader.py $BOOTFILE" + fi + ) +} + +# restore if missing pre-config script +restore_if_missing_preconfig_script () +{ + if [ ! -x ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script ]; then + mkdir -p ${vyatta_sysconfdir}/config/scripts + chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts + chmod 775 ${vyatta_sysconfdir}/config/scripts + cp ${vyos_rootfs_dir}/opt/vyatta/etc/config/scripts/vyos-preconfig-bootup.script ${vyatta_sysconfdir}/config/scripts/ + chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script + chmod 750 ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script + fi +} + +# execute the pre-config script +run_preconfig_script () +{ + if [ -x $vyatta_sysconfdir/config/scripts/vyos-preconfig-bootup.script ]; then + $vyatta_sysconfdir/config/scripts/vyos-preconfig-bootup.script + fi +} + +# restore if missing post-config script +restore_if_missing_postconfig_script () +{ + if [ ! -x ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script ]; then + mkdir -p ${vyatta_sysconfdir}/config/scripts + chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts + chmod 775 ${vyatta_sysconfdir}/config/scripts + cp ${vyos_rootfs_dir}/opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script ${vyatta_sysconfdir}/config/scripts/ + chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script + chmod 750 ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script + fi +} + +# execute the post-config scripts +run_postconfig_scripts () +{ + if [ -x $vyatta_sysconfdir/config/scripts/vyatta-postconfig-bootup.script ]; then + $vyatta_sysconfdir/config/scripts/vyatta-postconfig-bootup.script + fi + if [ -x $vyatta_sysconfdir/config/scripts/vyos-postconfig-bootup.script ]; then + $vyatta_sysconfdir/config/scripts/vyos-postconfig-bootup.script + fi +} + +run_postupgrade_script () +{ + if [ -f $vyatta_sysconfdir/config/.upgraded ]; then + # Run the system script + /usr/libexec/vyos/system/post-upgrade + + # Run user scripts + if [ -d $vyatta_sysconfdir/config/scripts/post-upgrade.d ]; then + run-parts $vyatta_sysconfdir/config/scripts/post-upgrade.d + fi + rm -f $vyatta_sysconfdir/config/.upgraded + fi +} + +# +# On image booted machines, we need to mount /boot from the image-specific +# boot directory so that kernel package installation will put the +# files in the right place. We also have to mount /boot/grub from the +# system-wide grub directory so that tools that edit the grub.cfg +# file will find it in the expected location. +# +bind_mount_boot () +{ + persist_path=$(/opt/vyatta/sbin/vyos-persistpath) + if [ $? == 0 ]; then + if [ -e $persist_path/boot ]; then + image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//') + + if [ -n "$image_name" ]; then + mount --bind $persist_path/boot/$image_name /boot + if [ $? -ne 0 ]; then + echo "Couldn't bind mount /boot" + fi + + if [ ! -d /boot/grub ]; then + mkdir /boot/grub + fi + + mount --bind $persist_path/boot/grub /boot/grub + if [ $? -ne 0 ]; then + echo "Couldn't bind mount /boot/grub" + fi + fi + fi + fi +} + +clear_or_override_config_files () +{ + for conf in snmp/snmpd.conf snmp/snmptrapd.conf snmp/snmp.conf \ + keepalived/keepalived.conf cron.d/vyos-crontab \ + ipvsadm.rules default/ipvsadm resolv.conf + do + if [ -s /etc/$conf ] ; then + empty /etc/$conf + chmod 0644 /etc/$conf + fi + done +} + +update_interface_config () +{ + if [ -d /run/udev/vyos ]; then + $vyos_libexec_dir/vyos-interface-rescan.py $BOOTFILE + fi +} + +cleanup_post_commit_hooks () { + # Remove links from the post-commit hooks directory. + # note that this approach only supports hooks that are "configured", + # i.e., it does not support hooks that need to always be present. + cpostdir=$(cli-shell-api getPostCommitHookDir) + # exclude commit hooks that need to always be present + excluded="00vyos-sync 10vyatta-log-commit.pl 99vyos-user-postcommit-hooks" + if [ -d "$cpostdir" ]; then + for f in $cpostdir/*; do + if [[ ! $excluded =~ $(basename $f) ]]; then + rm -f $cpostdir/$(basename $f) + fi + done + fi +} + +# These are all the default security setting which are later +# overridden when configuration is read. These are the values the +# system defaults. +security_reset () +{ + + # restore NSS cofniguration back to sane system defaults + # will be overwritten later when configuration is loaded + cat <<EOF >/etc/nsswitch.conf +passwd: files +group: files +shadow: files +gshadow: files + +# Per T2678, commenting out myhostname +hosts: files dns #myhostname +networks: files + +protocols: db files +services: db files +ethers: db files +rpc: db files + +netgroup: nis +EOF + + # restore PAM back to virgin state (no radius/tacacs services) + pam-auth-update --disable radius-mandatory radius-optional + rm -f /etc/pam_radius_auth.conf + pam-auth-update --disable tacplus-mandatory tacplus-optional + rm -f /etc/tacplus_nss.conf /etc/tacplus_servers + # and no Google authenticator for 2FA/MFA + pam-auth-update --disable mfa-google-authenticator + + # Certain configuration files are re-generated by the configuration + # subsystem and must reside under /etc and can not easily be moved to /run. + # So on every boot we simply delete any remaining files and let the CLI + # regenearte them. + + # PPPoE + rm -f /etc/ppp/peers/pppoe* /etc/ppp/peers/wlm* + + # IPSec + rm -rf /etc/ipsec.conf /etc/ipsec.secrets + find /etc/swanctl -type f | xargs rm -f + + # limit cleanup + rm -f /etc/security/limits.d/10-vyos.conf + + # iproute2 cleanup + rm -f /etc/iproute2/rt_tables.d/vyos-*.conf + + # Container + rm -f /etc/containers/storage.conf /etc/containers/registries.conf /etc/containers/containers.conf + # Clean all networks and re-create them from our CLI + rm -f /etc/containers/networks/* + + # System Options (SSH/cURL) + rm -f /etc/ssh/ssh_config.d/*vyos*.conf + rm -f /etc/curlrc +} + +# XXX: T3885 - generate persistend DHCPv6 DUID (Type4 - UUID based) +gen_duid () +{ + DUID_FILE="/var/lib/dhcpv6/dhcp6c_duid" + UUID_FILE="/sys/class/dmi/id/product_uuid" + UUID_FILE_ALT="/sys/class/dmi/id/product_serial" + if [ ! -f ${UUID_FILE} ] && [ ! -f ${UUID_FILE_ALT} ]; then + return 1 + fi + + # DUID is based on the BIOS/EFI UUID. We omit additional - characters + if [ -f ${UUID_FILE} ]; then + UUID=$(cat ${UUID_FILE} | tr -d -) + fi + if [ -z ${UUID} ]; then + UUID=$(uuidgen --sha1 --namespace @dns --name $(cat ${UUID_FILE_ALT}) | tr -d -) + fi + # Add DUID type4 (UUID) information + DUID_TYPE="0004" + + # The length-information (as per RFC6355 UUID is 128 bits long) is in big-endian + # format - beware when porting to ARM64. The length field consists out of the + # UUID (128 bit + 16 bits DUID type) resulting in hex 12. + DUID_LEN="0012" + if [ "$(echo -n I | od -to2 | head -n1 | cut -f2 -d" " | cut -c6 )" -eq 1 ]; then + # true on little-endian (x86) systems + DUID_LEN="1200" + fi + + for i in $(echo -n ${DUID_LEN}${DUID_TYPE}${UUID} | sed 's/../& /g'); do + echo -ne "\x$i" + done > ${DUID_FILE} +} + +start () +{ + # reset and clean config files + security_reset || log_failure_msg "security reset failed" + + # some legacy directories migrated over from old rl-system.init + mkdir -p /var/run/vyatta /var/log/vyatta + chgrp vyattacfg /var/run/vyatta /var/log/vyatta + chmod 775 /var/run/vyatta /var/log/vyatta + + log_daemon_msg "Waiting for NICs to settle down" + # On boot time udev migth take a long time to reorder nic's, this will ensure that + # all udev activity is completed and all nics presented at boot-time will have their + # final name before continuing with vyos-router initialization. + SECONDS=0 + udevadm settle + STATUS=$? + log_progress_msg "settled in ${SECONDS}sec." + log_end_msg ${STATUS} + + # mountpoint for bpf maps required by xdp + mount -t bpf none /sys/fs/bpf + + # Clear out Debian APT source config file + empty /etc/apt/sources.list + + # Generate DHCPv6 DUID + gen_duid || log_failure_msg "could not generate DUID" + + # Mount a temporary filesystem for container networks. + # Configuration should be loaded from VyOS cli. + cni_dir="/etc/cni/net.d" + [ ! -d ${cni_dir} ] && mkdir -p ${cni_dir} + mount -t tmpfs none ${cni_dir} + + # Init firewall + nfct helper add rpc inet tcp + nfct helper add rpc inet udp + nfct helper add tns inet tcp + nfct helper add rpc inet6 tcp + nfct helper add rpc inet6 udp + nfct helper add tns inet6 tcp + nft --file /usr/share/vyos/vyos-firewall-init.conf || log_failure_msg "could not initiate firewall rules" + + # As VyOS does not execute commands that are not present in the CLI we call + # the script by hand to have a single source for the login banner and MOTD + ${vyos_conf_scripts_dir}/system_console.py || log_failure_msg "could not reset serial console" + ${vyos_conf_scripts_dir}/system_login_banner.py || log_failure_msg "could not reset motd and issue files" + ${vyos_conf_scripts_dir}/system_option.py || log_failure_msg "could not reset system option files" + ${vyos_conf_scripts_dir}/system_ip.py || log_failure_msg "could not reset system IPv4 options" + ${vyos_conf_scripts_dir}/system_ipv6.py || log_failure_msg "could not reset system IPv6 options" + ${vyos_conf_scripts_dir}/system_conntrack.py || log_failure_msg "could not reset conntrack subsystem" + ${vyos_conf_scripts_dir}/container.py || log_failure_msg "could not reset container subsystem" + + clear_or_override_config_files || log_failure_msg "could not reset config files" + + # enable some debugging before loading the configuration + if grep -q vyos-debug /proc/cmdline; then + log_action_begin_msg "Enable runtime debugging options" + touch /tmp/vyos.container.debug + touch /tmp/vyos.ifconfig.debug + touch /tmp/vyos.frr.debug + touch /tmp/vyos.container.debug + touch /tmp/vyos.smoketest.debug + fi + + log_action_begin_msg "Mounting VyOS Config" + # ensure the vyatta_configdir supports a large number of inodes since + # the config hierarchy is often inode-bound (instead of size). + # impose a minimum and then scale up dynamically with the actual size + # of the system memory. + local tmem=$(sed -n 's/^MemTotal: \+\([0-9]\+\) kB$/\1/p' /proc/meminfo) + local tpages + local tmpfs_opts="nosuid,nodev,mode=775,nr_inodes=0" #automatically allocate inodes + mount -o $tmpfs_opts -t tmpfs none ${vyatta_configdir} \ + && chgrp ${GROUP} ${vyatta_configdir} + log_action_end_msg $? + + mount_encrypted_config + + # T5239: early read of system hostname as this value is read-only once during + # FRR initialisation + tmp=$(${vyos_libexec_dir}/read-saved-value.py --path "system host-name") + hostnamectl set-hostname --static "$tmp" + + ${vyos_conf_scripts_dir}/system_frr.py || log_failure_msg "could not reset FRR config" + # If for any reason FRR was not started by system_frr.py - start it anyways. + # This is a safety net! + systemctl start frr.service + + disabled bootfile || init_bootfile + + cleanup_post_commit_hooks + + log_daemon_msg "Starting VyOS router" + disabled migrate || migrate_bootfile + + restore_if_missing_preconfig_script + + run_preconfig_script + + run_postupgrade_script + + update_interface_config + + disabled system_config || system_config + + for s in ${subinit[@]} ; do + if ! disabled $s; then + log_progress_msg $s + if ! ${vyatta_sbindir}/${s}.init start + then log_failure_msg + exit 1 + fi + fi + done + + bind_mount_boot + + disabled configure || load_bootfile + log_end_msg $? + + telinit q + chmod g-w,o-w / + + restore_if_missing_postconfig_script + + run_postconfig_scripts + tmp=$(${vyos_libexec_dir}/read-saved-value.py --path "protocols rpki cache") + if [[ ! -z "$tmp" ]]; then + vtysh -c "rpki start" + fi +} + +stop() +{ + local -i status=0 + log_daemon_msg "Stopping VyOS router" + for ((i=${#sub_inits[@]} - 1; i >= 0; i--)) ; do + s=${subinit[$i]} + log_progress_msg $s + ${vyatta_sbindir}/${s}.init stop + let status\|=$? + done + log_end_msg $status + log_action_begin_msg "Un-mounting VyOS Config" + umount ${vyatta_configdir} + log_action_end_msg $? + + systemctl stop frr.service + + unmount_encrypted_config +} + +case "$action" in + start) start ;; + stop) stop ;; + restart|force-reload) stop && start ;; + *) log_failure_msg "usage: $progname [ start|stop|restart ] [ subinit ... ]" ; + false ;; +esac + +exit $? + +# Local Variables: +# mode: shell-script +# sh-indentation: 4 +# End: diff --git a/src/migration-scripts/bgp/0-to-1 b/src/migration-scripts/bgp/0-to-1 new file mode 100644 index 0000000..a2f3343 --- /dev/null +++ b/src/migration-scripts/bgp/0-to-1 @@ -0,0 +1,40 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3417: migrate BGP tagNode to node as we can only have one BGP process + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + base = ['protocols', 'bgp'] + + if not config.exists(base) or not config.is_tag(base): + # Nothing to do + return + + # Only one BGP process is supported, thus this operation is savea + asn = config.list_nodes(base) + bgp_base = base + asn + + # We need a temporary copy of the config + tmp_base = ['protocols', 'bgp2'] + config.copy(bgp_base, tmp_base) + + # Now it's save to delete the old configuration + config.delete(base) + + # Rename temporary copy to new final config and set new "local-as" option + config.rename(tmp_base, 'bgp') + config.set(base + ['local-as'], value=asn[0]) diff --git a/src/migration-scripts/bgp/1-to-2 b/src/migration-scripts/bgp/1-to-2 new file mode 100644 index 0000000..c0fc3b0 --- /dev/null +++ b/src/migration-scripts/bgp/1-to-2 @@ -0,0 +1,64 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3741: no-ipv4-unicast is now enabled by default +# T5937: Migrate IPv6 BGP Neighbor Peer Groups + +from vyos.configtree import ConfigTree + +base = ['protocols', 'bgp'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # This is now a default option - simply delete it. + # As it was configured explicitly - we can also bail out early as we need to + # do nothing! + if config.exists(base + ['parameters', 'default', 'no-ipv4-unicast']): + config.delete(base + ['parameters', 'default', 'no-ipv4-unicast']) + + # Check if the "default" node is now empty, if so - remove it + if len(config.list_nodes(base + ['parameters', 'default'])) == 0: + config.delete(base + ['parameters', 'default']) + + # Check if the "default" node is now empty, if so - remove it + if len(config.list_nodes(base + ['parameters'])) == 0: + config.delete(base + ['parameters']) + else: + # As we now install a new default option into BGP we need to migrate all + # existing BGP neighbors and restore the old behavior + if config.exists(base + ['neighbor']): + for neighbor in config.list_nodes(base + ['neighbor']): + peer_group = base + ['neighbor', neighbor, 'peer-group'] + if config.exists(peer_group): + peer_group_name = config.return_value(peer_group) + # peer group enables old behavior for neighbor - bail out + if config.exists(base + ['peer-group', peer_group_name, 'address-family', 'ipv4-unicast']): + continue + + afi_ipv4 = base + ['neighbor', neighbor, 'address-family', 'ipv4-unicast'] + if not config.exists(afi_ipv4): + config.set(afi_ipv4) + + # Migrate IPv6 AFI peer-group + if config.exists(base + ['neighbor']): + for neighbor in config.list_nodes(base + ['neighbor']): + tmp_path = base + ['neighbor', neighbor, 'address-family', 'ipv6-unicast', 'peer-group'] + if config.exists(tmp_path): + peer_group = config.return_value(tmp_path) + config.set(base + ['neighbor', neighbor, 'peer-group'], value=peer_group) + config.delete(tmp_path) diff --git a/src/migration-scripts/bgp/2-to-3 b/src/migration-scripts/bgp/2-to-3 new file mode 100644 index 0000000..d8bc34d --- /dev/null +++ b/src/migration-scripts/bgp/2-to-3 @@ -0,0 +1,30 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T4257: Discussion on changing BGP autonomous system number syntax + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + # Check if BGP is even configured. Then check if local-as exists, then add the system-as, then remove the local-as. This is for global configuration. + if config.exists(['protocols', 'bgp']): + if config.exists(['protocols', 'bgp', 'local-as']): + config.rename(['protocols', 'bgp', 'local-as'], 'system-as') + + # Check if vrf names are configured. Then check if local-as exists inside of a name, then add the system-as, then remove the local-as. This is for vrf configuration. + if config.exists(['vrf', 'name']): + for vrf in config.list_nodes(['vrf', 'name']): + if config.exists(['vrf', f'name {vrf}', 'protocols', 'bgp', 'local-as']): + config.rename(['vrf', f'name {vrf}', 'protocols', 'bgp', 'local-as'], 'system-as') diff --git a/src/migration-scripts/bgp/3-to-4 b/src/migration-scripts/bgp/3-to-4 new file mode 100644 index 0000000..842aef0 --- /dev/null +++ b/src/migration-scripts/bgp/3-to-4 @@ -0,0 +1,43 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5150: Rework CLI definitions to apply route-maps between routing daemons +# and zebra/kernel + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + bgp_base = ['protocols', 'bgp'] + # Check if BGP is configured - if so, migrate the CLI node + if config.exists(bgp_base): + if config.exists(bgp_base + ['route-map']): + tmp = config.return_value(bgp_base + ['route-map']) + + config.set(['system', 'ip', 'protocol', 'bgp', 'route-map'], value=tmp) + config.set_tag(['system', 'ip', 'protocol']) + config.delete(bgp_base + ['route-map']) + + + # Check if vrf names are configured. Check if BGP is configured - if so, migrate + # the CLI node(s) + if config.exists(['vrf', 'name']): + for vrf in config.list_nodes(['vrf', 'name']): + vrf_base = ['vrf', 'name', vrf] + if config.exists(vrf_base + ['protocols', 'bgp', 'route-map']): + tmp = config.return_value(vrf_base + ['protocols', 'bgp', 'route-map']) + + config.set(vrf_base + ['ip', 'protocol', 'bgp', 'route-map'], value=tmp) + config.set_tag(vrf_base + ['ip', 'protocol', 'bgp']) + config.delete(vrf_base + ['protocols', 'bgp', 'route-map']) diff --git a/src/migration-scripts/bgp/4-to-5 b/src/migration-scripts/bgp/4-to-5 new file mode 100644 index 0000000..d779eb1 --- /dev/null +++ b/src/migration-scripts/bgp/4-to-5 @@ -0,0 +1,46 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Delete 'protocols bgp address-family ipv6-unicast route-target vpn +# import/export', if 'protocols bgp address-family ipv6-unicast +# route-target vpn both' exists + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + bgp_base = ['protocols', 'bgp'] + # Delete 'import/export' in default vrf if 'both' exists + if config.exists(bgp_base): + for address_family in ['ipv4-unicast', 'ipv6-unicast']: + rt_path = bgp_base + ['address-family', address_family, 'route-target', + 'vpn'] + if config.exists(rt_path + ['both']): + if config.exists(rt_path + ['import']): + config.delete(rt_path + ['import']) + if config.exists(rt_path + ['export']): + config.delete(rt_path + ['export']) + + # Delete import/export in vrfs if both exists + if config.exists(['vrf', 'name']): + for vrf in config.list_nodes(['vrf', 'name']): + vrf_base = ['vrf', 'name', vrf] + for address_family in ['ipv4-unicast', 'ipv6-unicast']: + rt_path = vrf_base + bgp_base + ['address-family', address_family, + 'route-target', 'vpn'] + if config.exists(rt_path + ['both']): + if config.exists(rt_path + ['import']): + config.delete(rt_path + ['import']) + if config.exists(rt_path + ['export']): + config.delete(rt_path + ['export']) diff --git a/src/migration-scripts/cluster/1-to-2 b/src/migration-scripts/cluster/1-to-2 new file mode 100644 index 0000000..5ca4531 --- /dev/null +++ b/src/migration-scripts/cluster/1-to-2 @@ -0,0 +1,178 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +import re +import sys + +from vyos.configtree import ConfigTree +from vyos.base import MigrationError + +def migrate(config: ConfigTree) -> None: + if not config.exists(['cluster']): + # Cluster is not set -- nothing to do at all + return + + # If at least one cluster group is defined, we have real work to do. + # If there are no groups, we remove the top-level cluster node at the end of this script anyway. + if config.exists(['cluster', 'group']): + # First, gather timer and interface settings to duplicate them in all groups, + # since in the old cluster they are global, but in VRRP they are always per-group + + global_interface = None + if config.exists(['cluster', 'interface']): + global_interface = config.return_value(['cluster', 'interface']) + else: + # Such configs shouldn't exist in practice because interface is a required option. + # But since it's possible to specify interface inside 'service' options, + # we may be able to convert such configs nonetheless. + print("Warning: incorrect cluster config: interface is not defined.", file=sys.stderr) + + # There are three timers: advertise-interval, dead-interval, and monitor-dead-interval + # Only the first one makes sense for the VRRP, we translate it to advertise-interval + advertise_interval = None + if config.exists(['cluster', 'keepalive-interval']): + advertise_interval = config.return_value(['cluster', 'keepalive-interval']) + + if advertise_interval is not None: + # Cluster had all timers in milliseconds, so we need to convert them to seconds + # And ensure they are not shorter than one second + advertise_interval = int(advertise_interval) // 1000 + if advertise_interval < 1: + advertise_interval = 1 + + # Cluster had password as a global option, in VRRP it's per-group + password = None + if config.exists(['cluster', 'pre-shared-secret']): + password = config.return_value(['cluster', 'pre-shared-secret']) + + # Set up the stage for converting cluster groups to VRRP groups + free_vrids = set(range(1,255)) + vrrp_base_path = ['high-availability', 'vrrp', 'group'] + if not config.exists(vrrp_base_path): + # If VRRP is not set up, create a node and set it to 'tag node' + # Setting it to 'tag' is not mandatory but it's better to be consistent + # with configs produced by 'save' + config.set(vrrp_base_path) + config.set_tag(vrrp_base_path) + else: + # If there are VRRP groups already, we need to find the set of unused VRID numbers to avoid conflicts + existing_vrids = set() + for vg in config.list_nodes(vrrp_base_path): + existing_vrids.add(int(config.return_value(vrrp_base_path + [vg, 'vrid']))) + free_vrids = free_vrids.difference(existing_vrids) + + # Now handle cluster groups + groups = config.list_nodes(['cluster', 'group']) + for g in groups: + base_path = ['cluster', 'group', g] + service_names = config.return_values(base_path + ['service']) + + # Cluster used to allow services other than IP addresses, at least nominally + # Whether that ever worked is a big question, but we need to consider that, + # since configs with custom services are definitely impossible to meaningfully migrate now + services = {"ip": [], "other": []} + for s in service_names: + if re.match(r'^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2})(/[a-z]+\d+)?$', s): + services["ip"].append(s) + else: + services["other"].append(s) + + if services["other"]: + err_str = "Cluster config includes non-IP address services and cannot be migrated" + print(err_str, file=sys.stderr) + raise MigrationError(err_str) + + # Cluster allowed virtual IPs for different interfaces within a single group. + # VRRP groups are by definition bound to interfaces, so we cannot migrate such configurations. + # Thus we need to find out if all addresses either leave the interface unspecified + # (in that case the global 'cluster interface' option is used), + # or have the same interface, or have the same interface as the global 'cluster interface'. + + # First, we collect all addresses and check if they have interface specified + # If not, we substitute the global interface option + # or throw an error if it's not in the config. + ips = [] + for ip in services["ip"]: + ip_with_intf = re.match(r'^(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/(?P<intf>[a-z]+\d+)$', ip) + if ip_with_intf: + ips.append({"ip": ip_with_intf.group("ip"), "interface": ip_with_intf.group("intf")}) + else: + if global_interface is not None: + ips.append({"ip": ip, "interface": global_interface}) + else: + err_str = "Cluster group has addresses without interfaces and 'cluster interface' is not specified." + print(f'Error: {err_str}', file=sys.stderr) + raise MigrationError(err_str) + + # Then we check if all addresses are for the same interface. + intfs_set = set(map(lambda i: i["interface"], ips)) + if len(intfs_set) > 1: + err_str = "Cluster group has addresses for different interfaces" + print(f'Error: {err_str}', file=sys.stderr) + raise MigrationError(err_str) + + # If we got this far, the group is migratable. + + # Extract the interface from the set -- we know there's only a single member. + interface = intfs_set.pop() + + addresses = list(map(lambda i: i["ip"], ips)) + vrrp_path = ['high-availability', 'vrrp', 'group', g] + + # If there's already a VRRP group with exactly the same name, + # we probably shouldn't try to make up a unique name, just leave migration to the user... + if config.exists(vrrp_path): + err_str = "VRRP group with the same name already exists" + print(f'Error: {err_str}', file=sys.stderr) + raise MigrationError(err_str) + + config.set(vrrp_path + ['interface'], value=interface) + for a in addresses: + config.set(vrrp_path + ['virtual-address'], value=a, replace=False) + + # Take the next free VRID and assign it to the group + vrid = free_vrids.pop() + config.set(vrrp_path + ['vrid'], value=vrid) + + # Convert the monitor option to VRRP ping health check + if config.exists(base_path + ['monitor']): + monitor_ip = config.return_value(base_path + ['monitor']) + config.set(vrrp_path + ['health-check', 'ping'], value=monitor_ip) + + # Convert "auto-failback" to "no-preempt", if necessary + if config.exists(base_path + ['auto-failback']): + # It's a boolean node that requires "true" or "false" + # so if it exists we still need to check its value + auto_failback = config.return_value(base_path + ['auto-failback']) + if auto_failback == "false": + config.set(vrrp_path + ['no-preempt']) + else: + # It's "true" or we assume it is, which means preemption is desired, + # and in VRRP config it's the default + pass + else: + # The old default for that option is false + config.set(vrrp_path + ['no-preempt']) + + # Inject settings from the global cluster config that have to be per-group in VRRP + if advertise_interval is not None: + config.set(vrrp_path + ['advertise-interval'], value=advertise_interval) + + if password is not None: + config.set(vrrp_path + ['authentication', 'password'], value=password) + config.set(vrrp_path + ['authentication', 'type'], value='plaintext-password') + + # Finally, clean up the old cluster node + config.delete(['cluster']) diff --git a/src/migration-scripts/config-management/0-to-1 b/src/migration-scripts/config-management/0-to-1 new file mode 100644 index 0000000..44c6856 --- /dev/null +++ b/src/migration-scripts/config-management/0-to-1 @@ -0,0 +1,24 @@ +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Add commit-revisions option if it doesn't exist + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + if config.exists(['system', 'config-management', 'commit-revisions']): + # Nothing to do + return + config.set(['system', 'config-management', 'commit-revisions'], value='200') diff --git a/src/migration-scripts/conntrack-sync/1-to-2 b/src/migration-scripts/conntrack-sync/1-to-2 new file mode 100644 index 0000000..3e10e98 --- /dev/null +++ b/src/migration-scripts/conntrack-sync/1-to-2 @@ -0,0 +1,46 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# VyOS 1.2 crux allowed configuring a lower or upper case loglevel. This +# is no longer supported as the input data is validated and will lead to +# an error. If user specifies an upper case logleve, make it lowercase + +from vyos.configtree import ConfigTree + +base = ['service', 'conntrack-sync'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + base_accept_proto = base + ['accept-protocol'] + if config.exists(base_accept_proto): + tmp = config.return_value(base_accept_proto) + config.delete(base_accept_proto) + for protocol in tmp.split(','): + config.set(base_accept_proto, value=protocol, replace=False) + + base_ignore_addr = base + ['ignore-address', 'ipv4'] + if config.exists(base_ignore_addr): + tmp = config.return_values(base_ignore_addr) + config.delete(base_ignore_addr) + for address in tmp: + config.set(base + ['ignore-address'], value=address, replace=False) + + # we no longer support cluster mode + base_cluster = base + ['failover-mechanism', 'cluster'] + if config.exists(base_cluster): + config.delete(base_cluster) diff --git a/src/migration-scripts/conntrack/1-to-2 b/src/migration-scripts/conntrack/1-to-2 new file mode 100644 index 0000000..0a4fb3d --- /dev/null +++ b/src/migration-scripts/conntrack/1-to-2 @@ -0,0 +1,26 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Delete "set system conntrack modules gre" option + +from vyos.configtree import ConfigTree + + +def migrate(config: ConfigTree) -> None: + if not config.exists(['system', 'conntrack', 'modules', 'gre']): + return + + # Delete abandoned node + config.delete(['system', 'conntrack', 'modules', 'gre']) diff --git a/src/migration-scripts/conntrack/2-to-3 b/src/migration-scripts/conntrack/2-to-3 new file mode 100644 index 0000000..5ad4e63 --- /dev/null +++ b/src/migration-scripts/conntrack/2-to-3 @@ -0,0 +1,31 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Conntrack syntax version 3 +# Enables all conntrack modules (previous default behaviour) and omits manually disabled modules. + +from vyos.configtree import ConfigTree + +module_path = ['system', 'conntrack', 'modules'] + +def migrate(config: ConfigTree) -> None: + # Go over all conntrack modules available as of v1.3.0. + for module in ['ftp', 'h323', 'nfs', 'pptp', 'sip', 'sqlnet', 'tftp']: + # 'disable' is being phased out. + if config.exists(module_path + [module, 'disable']): + config.delete(module_path + [module]) + # If it wasn't manually 'disable'd, it was enabled by default. + else: + config.set(module_path + [module]) diff --git a/src/migration-scripts/conntrack/3-to-4 b/src/migration-scripts/conntrack/3-to-4 new file mode 100644 index 0000000..679a260 --- /dev/null +++ b/src/migration-scripts/conntrack/3-to-4 @@ -0,0 +1,30 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Add support for IPv6 conntrack ignore, move existing nodes to `system conntrack ignore ipv4` + +from vyos.configtree import ConfigTree + +base = ['system', 'conntrack'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['ignore', 'rule']): + config.set(base + ['ignore', 'ipv4']) + config.copy(base + ['ignore', 'rule'], base + ['ignore', 'ipv4', 'rule']) + config.delete(base + ['ignore', 'rule']) diff --git a/src/migration-scripts/conntrack/4-to-5 b/src/migration-scripts/conntrack/4-to-5 new file mode 100644 index 0000000..775fe74 --- /dev/null +++ b/src/migration-scripts/conntrack/4-to-5 @@ -0,0 +1,39 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5779: system conntrack timeout custom +# Before: +# Protocols tcp, udp and icmp allowed. When using udp it did not work +# Only ipv4 custom timeout rules +# Now: +# Valid protocols are only tcp or udp. +# Extend functionality to ipv6 and move ipv4 custom rules to new node: +# set system conntrack timeout custom [ipv4 | ipv6] rule <rule> ... + +from vyos.configtree import ConfigTree + +base = ['system', 'conntrack'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['timeout', 'custom', 'rule']): + for rule in config.list_nodes(base + ['timeout', 'custom', 'rule']): + if config.exists(base + ['timeout', 'custom', 'rule', rule, 'protocol', 'tcp']): + config.set(base + ['timeout', 'custom', 'ipv4', 'rule']) + config.copy(base + ['timeout', 'custom', 'rule', rule], base + ['timeout', 'custom', 'ipv4', 'rule', rule]) + config.delete(base + ['timeout', 'custom', 'rule']) diff --git a/src/migration-scripts/container/0-to-1 b/src/migration-scripts/container/0-to-1 new file mode 100644 index 0000000..99102a5 --- /dev/null +++ b/src/migration-scripts/container/0-to-1 @@ -0,0 +1,65 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T4870: change underlaying container filesystem from vfs to overlay + +import os +import shutil + +from vyos.configtree import ConfigTree +from vyos.utils.process import call + +base = ['container', 'name'] + +def migrate(config: ConfigTree) -> None: + # Check if containers exist and we need to perform image manipulation + if config.exists(base): + for container in config.list_nodes(base): + # Stop any given container first + call(f'sudo systemctl stop vyos-container-{container}.service') + # Export container image for later re-import to new filesystem. We store + # the backup on a real disk as a tmpfs (like /tmp) could probably lack + # memory if a host has too many containers stored. + image_name = config.return_value(base + [container, 'image']) + call(f'sudo podman image save --quiet --output /root/{container}.tar --format oci-archive {image_name}') + + # No need to adjust the strage driver online (this is only used for testing and + # debugging on a live system) - it is already overlay2 when the migration script + # is run during system update. But the specified driver in the image is actually + # overwritten by the still present VFS filesystem on disk. Thus podman still + # thinks it uses VFS until we delete the libpod directory under: + # /usr/lib/live/mount/persistence/container/storage + #call('sed -i "s/vfs/overlay2/g" /etc/containers/storage.conf /usr/share/vyos/templates/container/storage.conf.j2') + + base_path = '/usr/lib/live/mount/persistence/container/storage' + for dir in ['libpod', 'vfs', 'vfs-containers', 'vfs-images', 'vfs-layers']: + if os.path.exists(f'{base_path}/{dir}'): + shutil.rmtree(f'{base_path}/{dir}') + + # Now all remaining information about VFS is gone and we operate in overlayfs2 + # filesystem mode. Time to re-import the images. + if config.exists(base): + for container in config.list_nodes(base): + # Export container image for later re-import to new filesystem + image_name = config.return_value(base + [container, 'image']) + image_path = f'/root/{container}.tar' + call(f'sudo podman image load --quiet --input {image_path}') + + # Start any given container first + call(f'sudo systemctl start vyos-container-{container}.service') + + # Delete temporary container image + if os.path.exists(image_path): + os.unlink(image_path) diff --git a/src/migration-scripts/container/1-to-2 b/src/migration-scripts/container/1-to-2 new file mode 100644 index 0000000..c12dd8e --- /dev/null +++ b/src/migration-scripts/container/1-to-2 @@ -0,0 +1,32 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T6208: container: rename "cap-add" CLI node to "capability" + +from vyos.configtree import ConfigTree + +base = ['container', 'name'] + +def migrate(config: ConfigTree) -> None: + + # Check if containers exist and we need to perform image manipulation + if not config.exists(base): + # Nothing to do + return + + for container in config.list_nodes(base): + cap_path = base + [container, 'cap-add'] + if config.exists(cap_path): + config.rename(cap_path, 'capability') diff --git a/src/migration-scripts/dhcp-relay/1-to-2 b/src/migration-scripts/dhcp-relay/1-to-2 new file mode 100644 index 0000000..54cd8d6 --- /dev/null +++ b/src/migration-scripts/dhcp-relay/1-to-2 @@ -0,0 +1,29 @@ +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Delete "set service dhcp-relay relay-options port" option +# Delete "set service dhcpv6-relay listen-port" option + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + if not (config.exists(['service', 'dhcp-relay', 'relay-options', 'port']) or config.exists(['service', 'dhcpv6-relay', 'listen-port'])): + # Nothing to do + return + + # Delete abandoned node + config.delete(['service', 'dhcp-relay', 'relay-options', 'port']) + # Delete abandoned node + config.delete(['service', 'dhcpv6-relay', 'listen-port']) diff --git a/src/migration-scripts/dhcp-server/10-to-11 b/src/migration-scripts/dhcp-server/10-to-11 new file mode 100644 index 0000000..f54a4c7 --- /dev/null +++ b/src/migration-scripts/dhcp-server/10-to-11 @@ -0,0 +1,28 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T6171: rename "service dhcp-server failover" to "service dhcp-server high-availability" + +from vyos.configtree import ConfigTree + +base = ['service', 'dhcp-server'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['failover']): + config.rename(base + ['failover'],'high-availability') diff --git a/src/migration-scripts/dhcp-server/4-to-5 b/src/migration-scripts/dhcp-server/4-to-5 new file mode 100644 index 0000000..a655515 --- /dev/null +++ b/src/migration-scripts/dhcp-server/4-to-5 @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 + +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Removes boolean operator from: +# - "set service dhcp-server shared-network-name <xyz> subnet 172.31.0.0/24 ip-forwarding enable (true|false)" +# - "set service dhcp-server shared-network-name <xyz> authoritative (true|false)" +# - "set service dhcp-server disabled (true|false)" + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + if not config.exists(['service', 'dhcp-server']): + # Nothing to do + return + else: + base = ['service', 'dhcp-server'] + # Make node "set service dhcp-server dynamic-dns-update enable (true|false)" valueless + if config.exists(base + ['dynamic-dns-update']): + bool_val = config.return_value(base + ['dynamic-dns-update', 'enable']) + + # Delete the node with the old syntax + config.delete(base + ['dynamic-dns-update']) + if str(bool_val) == 'true': + # Enable dynamic-dns-update with new syntax + config.set(base + ['dynamic-dns-update'], value=None) + + # Make node "set service dhcp-server disabled (true|false)" valueless + if config.exists(base + ['disabled']): + bool_val = config.return_value(base + ['disabled']) + + # Delete the node with the old syntax + config.delete(base + ['disabled']) + if str(bool_val) == 'true': + # Now disable DHCP server with the new syntax + config.set(base + ['disable'], value=None) + + # Make node "set service dhcp-server hostfile-update (enable|disable) valueless + if config.exists(base + ['hostfile-update']): + bool_val = config.return_value(base + ['hostfile-update']) + + # Delete the node with the old syntax incl. all subnodes + config.delete(base + ['hostfile-update']) + if str(bool_val) == 'enable': + # Enable hostfile update with new syntax + config.set(base + ['hostfile-update'], value=None) + + # Run this for every instance if 'shared-network-name' + for network in config.list_nodes(base + ['shared-network-name']): + base_network = base + ['shared-network-name', network] + # format as tag node to avoid loading problems + config.set_tag(base + ['shared-network-name']) + + # Run this for every specified 'subnet' + for subnet in config.list_nodes(base_network + ['subnet']): + base_subnet = base_network + ['subnet', subnet] + # format as tag node to avoid loading problems + config.set_tag(base_network + ['subnet']) + + # Make node "set service dhcp-server shared-network-name <xyz> subnet 172.31.0.0/24 ip-forwarding enable" valueless + if config.exists(base_subnet + ['ip-forwarding', 'enable']): + bool_val = config.return_value(base_subnet + ['ip-forwarding', 'enable']) + # Delete the node with the old syntax + config.delete(base_subnet + ['ip-forwarding']) + if str(bool_val) == 'true': + # Recreate node with new syntax + config.set(base_subnet + ['ip-forwarding'], value=None) + + # Rename node "set service dhcp-server shared-network-name <xyz> subnet 172.31.0.0/24 start <172.16.0.4> stop <172.16.0.9> + if config.exists(base_subnet + ['start']): + # This is the new "range" id for DHCP lease ranges + r_id = 0 + for range in config.list_nodes(base_subnet + ['start']): + range_start = range + range_stop = config.return_value(base_subnet + ['start', range_start, 'stop']) + + # Delete the node with the old syntax + config.delete(base_subnet + ['start', range_start]) + + # Create the node for the new syntax + # Note: range is a tag node, counter is its child, not a value + config.set(base_subnet + ['range', r_id]) + config.set(base_subnet + ['range', r_id, 'start'], value=range_start) + config.set(base_subnet + ['range', r_id, 'stop'], value=range_stop) + + # format as tag node to avoid loading problems + config.set_tag(base_subnet + ['range']) + + # increment range id for possible next range definition + r_id += 1 + + # Delete the node with the old syntax + config.delete(['service', 'dhcp-server', 'shared-network-name', network, 'subnet', subnet, 'start']) + + + # Make node "set service dhcp-server shared-network-name <xyz> authoritative" valueless + if config.exists(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative']): + authoritative = config.return_value(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative']) + + # Delete the node with the old syntax + config.delete(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative']) + + # Recreate node with new syntax - if required + if authoritative == "enable": + config.set(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative']) diff --git a/src/migration-scripts/dhcp-server/5-to-6 b/src/migration-scripts/dhcp-server/5-to-6 new file mode 100644 index 0000000..9404cd0 --- /dev/null +++ b/src/migration-scripts/dhcp-server/5-to-6 @@ -0,0 +1,69 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T1968: allow multiple static-routes to be configured +# T3838: rename dns-server -> name-server + +from vyos.configtree import ConfigTree + +base = ['service', 'dhcp-server'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base + ['shared-network-name']): + # Nothing to do + return + + # Run this for every instance if 'shared-network-name' + for network in config.list_nodes(base + ['shared-network-name']): + base_network = base + ['shared-network-name', network] + + if not config.exists(base_network + ['subnet']): + continue + + # Run this for every specified 'subnet' + for subnet in config.list_nodes(base_network + ['subnet']): + base_subnet = base_network + ['subnet', subnet] + + # T1968: allow multiple static-routes to be configured + if config.exists(base_subnet + ['static-route']): + prefix = config.return_value(base_subnet + ['static-route', 'destination-subnet']) + router = config.return_value(base_subnet + ['static-route', 'router']) + config.delete(base_subnet + ['static-route']) + + config.set(base_subnet + ['static-route', prefix, 'next-hop'], value=router) + config.set_tag(base_subnet + ['static-route']) + + # T3838: rename dns-server -> name-server + if config.exists(base_subnet + ['dns-server']): + config.rename(base_subnet + ['dns-server'], 'name-server') + + + # T3672: ISC DHCP server only supports one failover peer + if config.exists(base_subnet + ['failover']): + # There can only be one failover configuration, if none is present + # we add the first one + if not config.exists(base + ['failover']): + local = config.return_value(base_subnet + ['failover', 'local-address']) + remote = config.return_value(base_subnet + ['failover', 'peer-address']) + status = config.return_value(base_subnet + ['failover', 'status']) + name = config.return_value(base_subnet + ['failover', 'name']) + + config.set(base + ['failover', 'remote'], value=remote) + config.set(base + ['failover', 'source-address'], value=local) + config.set(base + ['failover', 'status'], value=status) + config.set(base + ['failover', 'name'], value=name) + + config.delete(base_subnet + ['failover']) + config.set(base_subnet + ['enable-failover']) diff --git a/src/migration-scripts/dhcp-server/6-to-7 b/src/migration-scripts/dhcp-server/6-to-7 new file mode 100644 index 0000000..4e6583a --- /dev/null +++ b/src/migration-scripts/dhcp-server/6-to-7 @@ -0,0 +1,58 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T6079: Disable duplicate static mappings + +from vyos.configtree import ConfigTree + +base = ['service', 'dhcp-server'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base + ['shared-network-name']): + # Nothing to do + return + + # Run this for every instance if 'shared-network-name' + for network in config.list_nodes(base + ['shared-network-name']): + base_network = base + ['shared-network-name', network] + + if not config.exists(base_network + ['subnet']): + continue + + for subnet in config.list_nodes(base_network + ['subnet']): + base_subnet = base_network + ['subnet', subnet] + + if config.exists(base_subnet + ['static-mapping']): + used_mac = [] + used_ip = [] + + for mapping in config.list_nodes(base_subnet + ['static-mapping']): + base_mapping = base_subnet + ['static-mapping', mapping] + + if config.exists(base_mapping + ['mac-address']): + mac = config.return_value(base_mapping + ['mac-address']) + + if mac in used_mac: + config.set(base_mapping + ['disable']) + else: + used_mac.append(mac) + + if config.exists(base_mapping + ['ip-address']): + ip = config.return_value(base_mapping + ['ip-address']) + + if ip in used_ip: + config.set(base_subnet + ['static-mapping', mapping, 'disable']) + else: + used_ip.append(ip) diff --git a/src/migration-scripts/dhcp-server/7-to-8 b/src/migration-scripts/dhcp-server/7-to-8 new file mode 100644 index 0000000..7fcb62e --- /dev/null +++ b/src/migration-scripts/dhcp-server/7-to-8 @@ -0,0 +1,69 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3316: Migrate to Kea +# - global-parameters will not function +# - shared-network-parameters will not function +# - subnet-parameters will not function +# - static-mapping-parameters will not function +# - host-decl-name is on by default, option removed +# - ping-check no longer supported +# - failover is default enabled on all subnets that exist on failover servers + +from vyos.configtree import ConfigTree + +base = ['service', 'dhcp-server'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['host-decl-name']): + config.delete(base + ['host-decl-name']) + + if config.exists(base + ['global-parameters']): + config.delete(base + ['global-parameters']) + + if config.exists(base + ['shared-network-name']): + for network in config.list_nodes(base + ['shared-network-name']): + base_network = base + ['shared-network-name', network] + + if config.exists(base_network + ['ping-check']): + config.delete(base_network + ['ping-check']) + + if config.exists(base_network + ['shared-network-parameters']): + config.delete(base_network +['shared-network-parameters']) + + if not config.exists(base_network + ['subnet']): + continue + + # Run this for every specified 'subnet' + for subnet in config.list_nodes(base_network + ['subnet']): + base_subnet = base_network + ['subnet', subnet] + + if config.exists(base_subnet + ['enable-failover']): + config.delete(base_subnet + ['enable-failover']) + + if config.exists(base_subnet + ['ping-check']): + config.delete(base_subnet + ['ping-check']) + + if config.exists(base_subnet + ['subnet-parameters']): + config.delete(base_subnet + ['subnet-parameters']) + + if config.exists(base_subnet + ['static-mapping']): + for mapping in config.list_nodes(base_subnet + ['static-mapping']): + if config.exists(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters']): + config.delete(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters']) diff --git a/src/migration-scripts/dhcp-server/8-to-9 b/src/migration-scripts/dhcp-server/8-to-9 new file mode 100644 index 0000000..5843e9f --- /dev/null +++ b/src/migration-scripts/dhcp-server/8-to-9 @@ -0,0 +1,47 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3316: +# - Adjust hostname to have valid FQDN characters only (underscores aren't allowed anymore) +# - Rename "service dhcp-server shared-network-name ... static-mapping <hostname> mac-address ..." +# to "service dhcp-server shared-network-name ... static-mapping <hostname> mac ..." + +import re +from vyos.configtree import ConfigTree + +base = ['service', 'dhcp-server', 'shared-network-name'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for network in config.list_nodes(base): + # Run this for every specified 'subnet' + if config.exists(base + [network, 'subnet']): + for subnet in config.list_nodes(base + [network, 'subnet']): + base_subnet = base + [network, 'subnet', subnet] + if config.exists(base_subnet + ['static-mapping']): + for hostname in config.list_nodes(base_subnet + ['static-mapping']): + base_mapping = base_subnet + ['static-mapping', hostname] + + # Rename the 'mac-address' node to 'mac' + if config.exists(base_mapping + ['mac-address']): + config.rename(base_mapping + ['mac-address'], 'mac') + + # Adjust hostname to have valid FQDN characters only + new_hostname = re.sub(r'[^a-zA-Z0-9-.]', '-', hostname) + if new_hostname != hostname: + config.rename(base_mapping, new_hostname) diff --git a/src/migration-scripts/dhcp-server/9-to-10 b/src/migration-scripts/dhcp-server/9-to-10 new file mode 100644 index 0000000..eda9755 --- /dev/null +++ b/src/migration-scripts/dhcp-server/9-to-10 @@ -0,0 +1,57 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3316: +# - Migrate dhcp options under new option node +# - Add subnet IDs to existing subnets + +from vyos.configtree import ConfigTree + +base = ['service', 'dhcp-server', 'shared-network-name'] + +option_nodes = ['bootfile-name', 'bootfile-server', 'bootfile-size', 'captive-portal', + 'client-prefix-length', 'default-router', 'domain-name', 'domain-search', + 'name-server', 'ip-forwarding', 'ipv6-only-preferred', 'ntp-server', + 'pop-server', 'server-identifier', 'smtp-server', 'static-route', + 'tftp-server-name', 'time-offset', 'time-server', 'time-zone', + 'vendor-option', 'wins-server', 'wpad-url'] + + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + subnet_id = 1 + + for network in config.list_nodes(base): + for option in option_nodes: + if config.exists(base + [network, option]): + config.set(base + [network, 'option']) + config.copy(base + [network, option], base + [network, 'option', option]) + config.delete(base + [network, option]) + + if config.exists(base + [network, 'subnet']): + for subnet in config.list_nodes(base + [network, 'subnet']): + base_subnet = base + [network, 'subnet', subnet] + + for option in option_nodes: + if config.exists(base_subnet + [option]): + config.set(base_subnet + ['option']) + config.copy(base_subnet + [option], base_subnet + ['option', option]) + config.delete(base_subnet + [option]) + + config.set(base_subnet + ['subnet-id'], value=subnet_id) + subnet_id += 1 diff --git a/src/migration-scripts/dhcpv6-server/0-to-1 b/src/migration-scripts/dhcpv6-server/0-to-1 new file mode 100644 index 0000000..fd9b2d7 --- /dev/null +++ b/src/migration-scripts/dhcpv6-server/0-to-1 @@ -0,0 +1,44 @@ +# Copyright 202-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# combine both sip-server-address and sip-server-name nodes to common sip-server + +from vyos.configtree import ConfigTree + +base = ['service', 'dhcpv6-server', 'shared-network-name'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # we need to run this for every configured network + for network in config.list_nodes(base): + for subnet in config.list_nodes(base + [network, 'subnet']): + sip_server = [] + + # Do we have 'sip-server-address' configured? + if config.exists(base + [network, 'subnet', subnet, 'sip-server-address']): + sip_server += config.return_values(base + [network, 'subnet', subnet, 'sip-server-address']) + config.delete(base + [network, 'subnet', subnet, 'sip-server-address']) + + # Do we have 'sip-server-name' configured? + if config.exists(base + [network, 'subnet', subnet, 'sip-server-name']): + sip_server += config.return_values(base + [network, 'subnet', subnet, 'sip-server-name']) + config.delete(base + [network, 'subnet', subnet, 'sip-server-name']) + + # Write new CLI value for sip-server + for server in sip_server: + config.set(base + [network, 'subnet', subnet, 'sip-server'], value=server, replace=False) diff --git a/src/migration-scripts/dhcpv6-server/1-to-2 b/src/migration-scripts/dhcpv6-server/1-to-2 new file mode 100644 index 0000000..ad30749 --- /dev/null +++ b/src/migration-scripts/dhcpv6-server/1-to-2 @@ -0,0 +1,68 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3316: Migrate to Kea +# - Kea was meant to have support for key "prefix-highest" under PD which would allow an address range +# However this seems to have never been implemented. A conversion to prefix length is needed (where possible). +# Ref: https://lists.isc.org/pipermail/kea-users/2022-November/003686.html +# - Remove prefix temporary value, convert to multi leafNode (https://kea.readthedocs.io/en/kea-2.2.0/arm/dhcp6-srv.html#dhcpv6-server-limitations) + +from vyos.configtree import ConfigTree +from vyos.utils.network import ipv6_prefix_length + +base = ['service', 'dhcpv6-server', 'shared-network-name'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for network in config.list_nodes(base): + if not config.exists(base + [network, 'subnet']): + continue + + for subnet in config.list_nodes(base + [network, 'subnet']): + # Delete temporary value under address-range prefix, convert tagNode to leafNode multi + if config.exists(base + [network, 'subnet', subnet, 'address-range', 'prefix']): + prefix_base = base + [network, 'subnet', subnet, 'address-range', 'prefix'] + prefixes = config.list_nodes(prefix_base) + + config.delete(prefix_base) + + for prefix in prefixes: + config.set(prefix_base, value=prefix, replace=False) + + if config.exists(base + [network, 'subnet', subnet, 'prefix-delegation', 'prefix']): + prefix_base = base + [network, 'subnet', subnet, 'prefix-delegation', 'prefix'] + + config.set(prefix_base) + config.set_tag(prefix_base) + + for start in config.list_nodes(base + [network, 'subnet', subnet, 'prefix-delegation', 'start']): + path = base + [network, 'subnet', subnet, 'prefix-delegation', 'start', start] + + delegated_length = config.return_value(path + ['prefix-length']) + stop = config.return_value(path + ['stop']) + + prefix_length = ipv6_prefix_length(start, stop) + + # This range could not be converted into a simple prefix length and must be skipped + if not prefix_length: + continue + + config.set(prefix_base + [start, 'delegated-length'], value=delegated_length) + config.set(prefix_base + [start, 'prefix-length'], value=prefix_length) + + config.delete(base + [network, 'subnet', subnet, 'prefix-delegation', 'start']) diff --git a/src/migration-scripts/dhcpv6-server/2-to-3 b/src/migration-scripts/dhcpv6-server/2-to-3 new file mode 100644 index 0000000..b44798d --- /dev/null +++ b/src/migration-scripts/dhcpv6-server/2-to-3 @@ -0,0 +1,60 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3316: +# - Adjust hostname to have valid FQDN characters only (underscores aren't allowed anymore) +# - Adjust duid (old identifier) to comply with duid format +# - Rename "service dhcpv6-server shared-network-name ... static-mapping <hostname> identifier ..." +# to "service dhcpv6-server shared-network-name ... static-mapping <hostname> duid ..." +# - Rename "service dhcpv6-server shared-network-name ... static-mapping <hostname> mac-address ..." +# to "service dhcpv6-server shared-network-name ... static-mapping <hostname> mac ..." + +import re +from vyos.configtree import ConfigTree + +base = ['service', 'dhcpv6-server', 'shared-network-name'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for network in config.list_nodes(base): + # Run this for every specified 'subnet' + if config.exists(base + [network, 'subnet']): + for subnet in config.list_nodes(base + [network, 'subnet']): + base_subnet = base + [network, 'subnet', subnet] + if config.exists(base_subnet + ['static-mapping']): + for hostname in config.list_nodes(base_subnet + ['static-mapping']): + base_mapping = base_subnet + ['static-mapping', hostname] + if config.exists(base_mapping + ['identifier']): + + # Adjust duid to comply with duid format (a:3:b:04:... => 0a:03:0b:04:...) + duid = config.return_value(base_mapping + ['identifier']) + new_duid = ':'.join(x.rjust(2,'0') for x in duid.split(':')) + if new_duid != duid: + config.set(base_mapping + ['identifier'], new_duid) + + # Rename the 'identifier' node to 'duid' + config.rename(base_mapping + ['identifier'], 'duid') + + # Rename the 'mac-address' node to 'mac' + if config.exists(base_mapping + ['mac-address']): + config.rename(base_mapping + ['mac-address'], 'mac') + + # Adjust hostname to have valid FQDN characters only + new_hostname = re.sub(r'[^a-zA-Z0-9-.]', '-', hostname) + if new_hostname != hostname: + config.rename(base_mapping, new_hostname) diff --git a/src/migration-scripts/dhcpv6-server/3-to-4 b/src/migration-scripts/dhcpv6-server/3-to-4 new file mode 100644 index 0000000..e38e365 --- /dev/null +++ b/src/migration-scripts/dhcpv6-server/3-to-4 @@ -0,0 +1,72 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3316: +# - Add subnet IDs to existing subnets +# - Move options to option node +# - Migrate address-range to range tagNode + +from vyos.configtree import ConfigTree + +base = ['service', 'dhcpv6-server', 'shared-network-name'] + +option_nodes = ['captive-portal', 'domain-search', 'name-server', + 'nis-domain', 'nis-server', 'nisplus-domain', 'nisplus-server', + 'sip-server', 'sntp-server', 'vendor-option'] + + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + subnet_id = 1 + + for network in config.list_nodes(base): + if config.exists(base + [network, 'subnet']): + for subnet in config.list_nodes(base + [network, 'subnet']): + base_subnet = base + [network, 'subnet', subnet] + + if config.exists(base_subnet + ['address-range']): + config.set(base_subnet + ['range']) + config.set_tag(base_subnet + ['range']) + + range_id = 1 + + if config.exists(base_subnet + ['address-range', 'prefix']): + for prefix in config.return_values(base_subnet + ['address-range', 'prefix']): + config.set(base_subnet + ['range', range_id, 'prefix'], value=prefix) + + range_id += 1 + + if config.exists(base_subnet + ['address-range', 'start']): + for start in config.list_nodes(base_subnet + ['address-range', 'start']): + stop = config.return_value(base_subnet + ['address-range', 'start', start, 'stop']) + + config.set(base_subnet + ['range', range_id, 'start'], value=start) + config.set(base_subnet + ['range', range_id, 'stop'], value=stop) + + range_id += 1 + + config.delete(base_subnet + ['address-range']) + + for option in option_nodes: + if config.exists(base_subnet + [option]): + config.set(base_subnet + ['option']) + config.copy(base_subnet + [option], base_subnet + ['option', option]) + config.delete(base_subnet + [option]) + + config.set(base_subnet + ['subnet-id'], value=subnet_id) + subnet_id += 1 diff --git a/src/migration-scripts/dhcpv6-server/4-to-5 b/src/migration-scripts/dhcpv6-server/4-to-5 new file mode 100644 index 0000000..ad18e1a --- /dev/null +++ b/src/migration-scripts/dhcpv6-server/4-to-5 @@ -0,0 +1,73 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5993: Check if subnet is locally accessible and assign interface to subnet + +from ipaddress import ip_network +from vyos.configtree import ConfigTree + +base = ['service', 'dhcpv6-server', 'shared-network-name'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + def find_subnet_interface(subnet): + subnet_net = ip_network(subnet) + + def check_addr(if_path): + if config.exists(if_path + ['address']): + for addr in config.return_values(if_path + ['address']): + try: + if ip_network(addr, strict=False) == subnet_net: + return True + except: + pass # interface address was probably "dhcp" or other magic string + return None + + for iftype in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', iftype]): + if_base = ['interfaces', iftype, ifname] + + if check_addr(if_base): + return ifname + + if config.exists(if_base + ['vif']): + for vif in config.list_nodes(if_base + ['vif']): + if check_addr(if_base + ['vif', vif]): + return f'{ifname}.{vif}' + + if config.exists(if_base + ['vif-s']): + for vifs in config.list_nodes(if_base + ['vif-s']): + if check_addr(if_base + ['vif-s', vifs]): + return f'{ifname}.{vifs}' + + if config.exists(if_base + ['vif-s', vifs, 'vif-c']): + for vifc in config.list_nodes(if_base + ['vif-s', vifs, 'vif-c']): + if check_addr(if_base + ['vif-s', vifs, 'vif-c', vifc]): + return f'{ifname}.{vifs}.{vifc}' + + return False + + for network in config.list_nodes(base): + if not config.exists(base + [network, 'subnet']): + continue + + for subnet in config.list_nodes(base + [network, 'subnet']): + subnet_interface = find_subnet_interface(subnet) + + if subnet_interface: + config.set(base + [network, 'subnet', subnet, 'interface'], value=subnet_interface) diff --git a/src/migration-scripts/dhcpv6-server/5-to-6 b/src/migration-scripts/dhcpv6-server/5-to-6 new file mode 100644 index 0000000..cad0a35 --- /dev/null +++ b/src/migration-scripts/dhcpv6-server/5-to-6 @@ -0,0 +1,31 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T6648: Rename "common-options" to "option" at shared-network level + +from vyos.configtree import ConfigTree + +base = ['service', 'dhcpv6-server', 'shared-network-name'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for network in config.list_nodes(base): + if not config.exists(base + [network, 'common-options']): + continue + + config.rename(base + [network, 'common-options'], 'option') diff --git a/src/migration-scripts/dns-dynamic/0-to-1 b/src/migration-scripts/dns-dynamic/0-to-1 new file mode 100644 index 0000000..6a91b36 --- /dev/null +++ b/src/migration-scripts/dns-dynamic/0-to-1 @@ -0,0 +1,109 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5144: +# - migrate "service dns dynamic interface ..." +# to "service dns dynamic address ..." +# - migrate "service dns dynamic interface <interface> use-web ..." +# to "service dns dynamic address <address> web-options ..." +# - migrate "service dns dynamic interface <interface> rfc2136 <config> record ..." +# to "service dns dynamic address <address> rfc2136 <config> host-name ..." +# - migrate "service dns dynamic interface <interface> service <config> login ..." +# to "service dns dynamic address <address> service <config> username ..." +# - apply global 'ipv6-enable' to per <config> 'ip-version: ipv6' +# - apply service protocol mapping upfront, they are not 'auto-detected' anymore +# - migrate web-options url to stricter format + +import re +from vyos.configtree import ConfigTree + +service_protocol_mapping = { + 'afraid': 'freedns', + 'changeip': 'changeip', + 'cloudflare': 'cloudflare', + 'dnspark': 'dnspark', + 'dslreports': 'dslreports1', + 'dyndns': 'dyndns2', + 'easydns': 'easydns', + 'namecheap': 'namecheap', + 'noip': 'noip', + 'sitelutions': 'sitelutions', + 'zoneedit': 'zoneedit1' +} + +old_base_path = ['service', 'dns', 'dynamic', 'interface'] +new_base_path = ['service', 'dns', 'dynamic', 'address'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(old_base_path): + # Nothing to do + return + + # Migrate "service dns dynamic interface" + # to "service dns dynamic address" + config.rename(old_base_path, new_base_path[-1]) + + for address in config.list_nodes(new_base_path): + # Migrate "service dns dynamic interface <interface> rfc2136 <config> record" + # to "service dns dynamic address <address> rfc2136 <config> host-name" + if config.exists(new_base_path + [address, 'rfc2136']): + for rfc_cfg in config.list_nodes(new_base_path + [address, 'rfc2136']): + if config.exists(new_base_path + [address, 'rfc2136', rfc_cfg, 'record']): + config.rename(new_base_path + [address, 'rfc2136', rfc_cfg, 'record'], 'host-name') + + # Migrate "service dns dynamic interface <interface> service <config> login" + # to "service dns dynamic address <address> service <config> username" + if config.exists(new_base_path + [address, 'service']): + for svc_cfg in config.list_nodes(new_base_path + [address, 'service']): + if config.exists(new_base_path + [address, 'service', svc_cfg, 'login']): + config.rename(new_base_path + [address, 'service', svc_cfg, 'login'], 'username') + # Apply global 'ipv6-enable' to per <config> 'ip-version: ipv6' + if config.exists(new_base_path + [address, 'ipv6-enable']): + config.set(new_base_path + [address, 'service', svc_cfg, 'ip-version'], 'ipv6') + config.delete(new_base_path + [address, 'ipv6-enable']) + # Apply service protocol mapping upfront, they are not 'auto-detected' anymore + if svc_cfg in service_protocol_mapping: + config.set(new_base_path + [address, 'service', svc_cfg, 'protocol'], + service_protocol_mapping.get(svc_cfg)) + + # If use-web is set, then: + # Move "service dns dynamic address <address> <service|rfc2136> <service> ..." + # to "service dns dynamic address web <service|rfc2136> <service>-<address> ..." + # Move "service dns dynamic address web use-web ..." + # to "service dns dynamic address web web-options ..." + # Note: The config is named <service>-<address> to avoid name conflict with old entries + if config.exists(new_base_path + [address, 'use-web']): + for svc_type in ['rfc2136', 'service']: + if config.exists(new_base_path + [address, svc_type]): + config.set(new_base_path + ['web', svc_type]) + config.set_tag(new_base_path + ['web', svc_type]) + for svc_cfg in config.list_nodes(new_base_path + [address, svc_type]): + config.copy(new_base_path + [address, svc_type, svc_cfg], + new_base_path + ['web', svc_type, f'{svc_cfg}-{address}']) + + # Multiple web-options were not supported, so copy only the first one + # Also, migrate web-options url to stricter format and transition + # checkip.dyndns.org to https://domains.google.com/checkip for better + # TLS support (see: https://github.com/ddclient/ddclient/issues/597) + if not config.exists(new_base_path + ['web', 'web-options']): + config.copy(new_base_path + [address, 'use-web'], new_base_path + ['web', 'web-options']) + if config.exists(new_base_path + ['web', 'web-options', 'url']): + url = config.return_value(new_base_path + ['web', 'web-options', 'url']) + if re.search("^(https?://)?checkip\.dyndns\.org", url): + config.set(new_base_path + ['web', 'web-options', 'url'], 'https://domains.google.com/checkip') + if not url.startswith(('http://', 'https://')): + config.set(new_base_path + ['web', 'web-options', 'url'], f'https://{url}') + + config.delete(new_base_path + [address]) diff --git a/src/migration-scripts/dns-dynamic/1-to-2 b/src/migration-scripts/dns-dynamic/1-to-2 new file mode 100644 index 0000000..5dca9e3 --- /dev/null +++ b/src/migration-scripts/dns-dynamic/1-to-2 @@ -0,0 +1,51 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5708: +# - migrate "service dns dynamic timeout ..." +# to "service dns dynamic interval ..." +# - remove "service dns dynamic address <interface> web-options ..." when <interface> != "web" +# - migrate "service dns dynamic address <interface> service <service> protocol dnsexit" +# to "service dns dynamic address <interface> service <service> protocol dnsexit2" + +from vyos.configtree import ConfigTree + +base_path = ['service', 'dns', 'dynamic'] +timeout_path = base_path + ['timeout'] +address_path = base_path + ['address'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base_path): + # Nothing to do + return + + # Migrate "service dns dynamic timeout ..." + # to "service dns dynamic interval ..." + if config.exists(timeout_path): + config.rename(timeout_path, 'interval') + + # Remove "service dns dynamic address <interface> web-options ..." when <interface> != "web" + for address in config.list_nodes(address_path): + if config.exists(address_path + [address, 'web-options']) and address != 'web': + config.delete(address_path + [address, 'web-options']) + + # Migrate "service dns dynamic address <interface> service <service> protocol dnsexit" + # to "service dns dynamic address <interface> service <service> protocol dnsexit2" + for address in config.list_nodes(address_path): + for svc_cfg in config.list_nodes(address_path + [address, 'service']): + if config.exists(address_path + [address, 'service', svc_cfg, 'protocol']): + protocol = config.return_value(address_path + [address, 'service', svc_cfg, 'protocol']) + if protocol == 'dnsexit': + config.set(address_path + [address, 'service', svc_cfg, 'protocol'], 'dnsexit2') diff --git a/src/migration-scripts/dns-dynamic/2-to-3 b/src/migration-scripts/dns-dynamic/2-to-3 new file mode 100644 index 0000000..9aafc41 --- /dev/null +++ b/src/migration-scripts/dns-dynamic/2-to-3 @@ -0,0 +1,99 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5791: +# - migrate "service dns dynamic address web web-options ..." +# to "service dns dynamic name <service> address web ..." (per service) +# - migrate "service dns dynamic address <address> rfc2136 <service> ..." +# to "service dns dynamic name <service> address <interface> protocol 'nsupdate'" +# - migrate "service dns dynamic address <interface> service <service> ..." +# to "service dns dynamic name <service> address <interface> ..." +# - normalize the all service names to conform with name constraints + +import re +from unicodedata import normalize +from vyos.configtree import ConfigTree + +def normalize_name(name): + """Normalize service names to conform with name constraints. + + This is necessary as part of migration because there were no constraints in + the old name format. + """ + # Normalize unicode characters to ASCII (NFKD) + # Replace all separators with hypens, strip leading and trailing hyphens + name = normalize('NFKD', name).encode('ascii', 'ignore').decode() + name = re.sub(r'(\s|_|\W)+', '-', name).strip('-') + + return name + +base_path = ['service', 'dns', 'dynamic'] +address_path = base_path + ['address'] +name_path = base_path + ['name'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(address_path): + # Nothing to do + return + + # config.copy does not recursively create a path, so initialize the name path as tagged node + if not config.exists(name_path): + config.set(name_path) + config.set_tag(name_path) + + for address in config.list_nodes(address_path): + + address_path_tag = address_path + [address] + + # Move web-option as a configuration in each service instead of top level web-option + if config.exists(address_path_tag + ['web-options']) and address == 'web': + for svc_type in ['service', 'rfc2136']: + if config.exists(address_path_tag + [svc_type]): + for svc_cfg in config.list_nodes(address_path_tag + [svc_type]): + config.copy(address_path_tag + ['web-options'], + address_path_tag + [svc_type, svc_cfg, 'web-options']) + config.delete(address_path_tag + ['web-options']) + + for svc_type in ['service', 'rfc2136']: + if config.exists(address_path_tag + [svc_type]): + # Set protocol to 'nsupdate' for RFC2136 configuration + if svc_type == 'rfc2136': + for rfc_cfg in config.list_nodes(address_path_tag + ['rfc2136']): + config.set(address_path_tag + ['rfc2136', rfc_cfg, 'protocol'], 'nsupdate') + + # Add address as config value in each service before moving the service path + # And then copy the services from 'address <interface> service <service>' + # to 'name (service|rfc2136)-<service>-<address>' + # Note: The new service is named (service|rfc2136)-<service>-<address> + # to avoid name conflict with old entries + for svc_cfg in config.list_nodes(address_path_tag + [svc_type]): + config.set(address_path_tag + [svc_type, svc_cfg, 'address'], address) + config.copy(address_path_tag + [svc_type, svc_cfg], + name_path + ['-'.join([svc_type, svc_cfg, address])]) + + # Finally cleanup the old address path + config.delete(address_path) + + # Normalize the all service names to conform with name constraints + index = 1 + for name in config.list_nodes(name_path): + new_name = normalize_name(name) + if new_name != name: + # Append index if there is still a name conflicts after normalization + # For example, "foo-?(" and "foo-!)" both normalize to "foo-" + if config.exists(name_path + [new_name]): + new_name = f'{new_name}-{index}' + index += 1 + config.rename(name_path + [name], new_name) diff --git a/src/migration-scripts/dns-dynamic/3-to-4 b/src/migration-scripts/dns-dynamic/3-to-4 new file mode 100644 index 0000000..c8e1ffe --- /dev/null +++ b/src/migration-scripts/dns-dynamic/3-to-4 @@ -0,0 +1,57 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5966: +# - migrate "service dns dynamic name <service> address <interface>" +# to "service dns dynamic name <service> address interface <interface>" +# when <interface> != 'web' +# - migrate "service dns dynamic name <service> web-options ..." +# to "service dns dynamic name <service> address web ..." +# when <interface> == 'web' + +from vyos.configtree import ConfigTree + +base_path = ['service', 'dns', 'dynamic', 'name'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base_path): + # Nothing to do + return + + for service in config.list_nodes(base_path): + + service_path = base_path + [service] + + if config.exists(service_path + ['address']): + address = config.return_value(service_path + ['address']) + # 'address' is not a leaf node anymore, delete it first + config.delete(service_path + ['address']) + + # When address is an interface (not 'web'), move it to 'address interface' + if address != 'web': + config.set(service_path + ['address', 'interface'], address) + + else: # address == 'web' + # Relocate optional 'web-options' directly under 'address web' + if config.exists(service_path + ['web-options']): + # config.copy does not recursively create a path, so initialize it + config.set(service_path + ['address']) + config.copy(service_path + ['web-options'], + service_path + ['address', 'web']) + config.delete(service_path + ['web-options']) + + # ensure that valueless 'address web' still exists even if there are no 'web-options' + if not config.exists(service_path + ['address', 'web']): + config.set(service_path + ['address', 'web']) diff --git a/src/migration-scripts/dns-forwarding/0-to-1 b/src/migration-scripts/dns-forwarding/0-to-1 new file mode 100644 index 0000000..264ffb4 --- /dev/null +++ b/src/migration-scripts/dns-forwarding/0-to-1 @@ -0,0 +1,31 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# This migration script will check if there is a allow-from directive configured +# for the dns forwarding service - if not, the node will be created with the old +# default values of 0.0.0.0/0 and ::/0 + +from vyos.configtree import ConfigTree + +base = ['service', 'dns', 'forwarding'] + +def migrate(config: ConfigTree)-> None: + if not config.exists(base): + # Nothing to do + return + + if not config.exists(base + ['allow-from']): + config.set(base + ['allow-from'], value='0.0.0.0/0', replace=False) + config.set(base + ['allow-from'], value='::/0', replace=False) diff --git a/src/migration-scripts/dns-forwarding/1-to-2 b/src/migration-scripts/dns-forwarding/1-to-2 new file mode 100644 index 0000000..15ed1e1 --- /dev/null +++ b/src/migration-scripts/dns-forwarding/1-to-2 @@ -0,0 +1,67 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# This migration script will remove the deprecated 'listen-on' statement +# from the dns forwarding service and will add the corresponding +# listen-address nodes instead. This is required as PowerDNS can only listen +# on interface addresses and not on interface names. + +from ipaddress import ip_interface +from vyos.ifconfig import Interface +from vyos.configtree import ConfigTree + +base = ['service', 'dns', 'forwarding'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base + ['listen-on']): + # Nothing to do + return + + listen_intf = config.return_values(base + ['listen-on']) + # Delete node with abandoned command + config.delete(base + ['listen-on']) + + # retrieve interface addresses for every configured listen-on interface + listen_addr = [] + for intf in listen_intf: + # we need to evaluate the interface section before manipulating the 'intf' variable + section = Interface.section(intf) + if not section: + raise ValueError(f'Invalid interface name {intf}') + + # we need to treat vif and vif-s interfaces differently, + # both "real interfaces" use dots for vlan identifiers - those + # need to be exchanged with vif and vif-s identifiers + if intf.count('.') == 1: + # this is a regular VLAN interface + intf = intf.split('.')[0] + ' vif ' + intf.split('.')[1] + elif intf.count('.') == 2: + # this is a QinQ VLAN interface + intf = intf.split('.')[0] + ' vif-s ' + intf.split('.')[1] + ' vif-c ' + intf.split('.')[2] + + # retrieve corresponding interface addresses in CIDR format + # those need to be converted in pure IP addresses without network information + path = ['interfaces', section, intf, 'address'] + try: + for addr in config.return_values(path): + listen_addr.append( ip_interface(addr).ip ) + except: + # Some interface types do not use "address" option (e.g. OpenVPN) + # and may not even have a fixed address + print("Could not retrieve the address of the interface {} from the config".format(intf)) + print("You will need to update your DNS forwarding configuration manually") + + for addr in listen_addr: + config.set(base + ['listen-address'], value=addr, replace=False) diff --git a/src/migration-scripts/dns-forwarding/2-to-3 b/src/migration-scripts/dns-forwarding/2-to-3 new file mode 100644 index 0000000..729c1f0 --- /dev/null +++ b/src/migration-scripts/dns-forwarding/2-to-3 @@ -0,0 +1,32 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Sets the new options "addnta" and "recursion-desired" for all +# 'dns forwarding domain' as this is usually desired + +from vyos.configtree import ConfigTree + +base = ['service', 'dns', 'forwarding'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['domain']): + for domain in config.list_nodes(base + ['domain']): + domain_base = base + ['domain', domain] + config.set(domain_base + ['addnta']) + config.set(domain_base + ['recursion-desired']) diff --git a/src/migration-scripts/dns-forwarding/3-to-4 b/src/migration-scripts/dns-forwarding/3-to-4 new file mode 100644 index 0000000..b02c0b7 --- /dev/null +++ b/src/migration-scripts/dns-forwarding/3-to-4 @@ -0,0 +1,31 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5115: migrate "service dns forwarding domain example.com server" to +# "service dns forwarding domain example.com name-server" + +from vyos.configtree import ConfigTree + +base = ['service', 'dns', 'forwarding', 'domain'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for domain in config.list_nodes(base): + if config.exists(base + [domain, 'server']): + config.copy(base + [domain, 'server'], base + [domain, 'name-server']) + config.delete(base + [domain, 'server']) diff --git a/src/migration-scripts/firewall/10-to-11 b/src/migration-scripts/firewall/10-to-11 new file mode 100644 index 0000000..70a1709 --- /dev/null +++ b/src/migration-scripts/firewall/10-to-11 @@ -0,0 +1,187 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5160: Firewall re-writing + +# cli changes from: +# set firewall name <name> ... +# set firewall ipv6-name <name> ... +# To +# set firewall ipv4 name <name> +# set firewall ipv6 name <name> + +## Also from 'firewall interface' removed. +## in and out: + # set firewall interface <iface> [in|out] [name | ipv6-name] <name> + # To + # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> [inbound-interface | outboubd-interface] interface-name <iface> + # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> action jump + # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> jump-target <name> +## local: + # set firewall interface <iface> local [name | ipv6-name] <name> + # To + # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> inbound-interface interface-name <iface> + # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> action jump + # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> jump-target <name> + +from vyos.configtree import ConfigTree + +base = ['firewall'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + ### Migration of state policies + if config.exists(base + ['state-policy']): + for state in config.list_nodes(base + ['state-policy']): + action = config.return_value(base + ['state-policy', state, 'action']) + config.set(base + ['global-options', 'state-policy', state, 'action'], value=action) + if config.exists(base + ['state-policy', state, 'log']): + config.set(base + ['global-options', 'state-policy', state, 'log'], value='enable') + config.delete(base + ['state-policy']) + + ## migration of global options: + for option in ['all-ping', 'broadcast-ping', 'config-trap', 'ip-src-route', 'ipv6-receive-redirects', 'ipv6-src-route', 'log-martians', + 'receive-redirects', 'resolver-cache', 'resolver-internal', 'send-redirects', 'source-validation', 'syn-cookies', 'twa-hazards-protection']: + if config.exists(base + [option]): + if option != 'config-trap': + val = config.return_value(base + [option]) + config.set(base + ['global-options', option], value=val) + config.delete(base + [option]) + + ### Migration of firewall name and ipv6-name + ### Also migrate legacy 'accept' behaviour + if config.exists(base + ['name']): + config.set(['firewall', 'ipv4', 'name']) + config.set_tag(['firewall', 'ipv4', 'name']) + + for ipv4name in config.list_nodes(base + ['name']): + config.copy(base + ['name', ipv4name], base + ['ipv4', 'name', ipv4name]) + + if config.exists(base + ['ipv4', 'name', ipv4name, 'default-action']): + action = config.return_value(base + ['ipv4', 'name', ipv4name, 'default-action']) + + if action == 'accept': + config.set(base + ['ipv4', 'name', ipv4name, 'default-action'], value='return') + + if config.exists(base + ['ipv4', 'name', ipv4name, 'rule']): + for rule_id in config.list_nodes(base + ['ipv4', 'name', ipv4name, 'rule']): + action = config.return_value(base + ['ipv4', 'name', ipv4name, 'rule', rule_id, 'action']) + + if action == 'accept': + config.set(base + ['ipv4', 'name', ipv4name, 'rule', rule_id, 'action'], value='return') + + config.delete(base + ['name']) + + if config.exists(base + ['ipv6-name']): + config.set(['firewall', 'ipv6', 'name']) + config.set_tag(['firewall', 'ipv6', 'name']) + + for ipv6name in config.list_nodes(base + ['ipv6-name']): + config.copy(base + ['ipv6-name', ipv6name], base + ['ipv6', 'name', ipv6name]) + + if config.exists(base + ['ipv6', 'name', ipv6name, 'default-action']): + action = config.return_value(base + ['ipv6', 'name', ipv6name, 'default-action']) + + if action == 'accept': + config.set(base + ['ipv6', 'name', ipv6name, 'default-action'], value='return') + + if config.exists(base + ['ipv6', 'name', ipv6name, 'rule']): + for rule_id in config.list_nodes(base + ['ipv6', 'name', ipv6name, 'rule']): + action = config.return_value(base + ['ipv6', 'name', ipv6name, 'rule', rule_id, 'action']) + + if action == 'accept': + config.set(base + ['ipv6', 'name', ipv6name, 'rule', rule_id, 'action'], value='return') + + config.delete(base + ['ipv6-name']) + + ### Migration of firewall interface + if config.exists(base + ['interface']): + fwd_ipv4_rule = 5 + inp_ipv4_rule = 5 + fwd_ipv6_rule = 5 + inp_ipv6_rule = 5 + for direction in ['in', 'out', 'local']: + for iface in config.list_nodes(base + ['interface']): + if config.exists(base + ['interface', iface, direction]): + if config.exists(base + ['interface', iface, direction, 'name']): + target = config.return_value(base + ['interface', iface, direction, 'name']) + if direction == 'in': + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept') + new_base = base + ['ipv4', 'forward', 'filter', 'rule'] + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [fwd_ipv4_rule, 'inbound-interface', 'interface-name'], value=iface) + config.set(new_base + [fwd_ipv4_rule, 'action'], value='jump') + config.set(new_base + [fwd_ipv4_rule, 'jump-target'], value=target) + fwd_ipv4_rule = fwd_ipv4_rule + 5 + elif direction == 'out': + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept') + new_base = base + ['ipv4', 'forward', 'filter', 'rule'] + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [fwd_ipv4_rule, 'outbound-interface', 'interface-name'], value=iface) + config.set(new_base + [fwd_ipv4_rule, 'action'], value='jump') + config.set(new_base + [fwd_ipv4_rule, 'jump-target'], value=target) + fwd_ipv4_rule = fwd_ipv4_rule + 5 + else: + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv4', 'input', 'filter', 'default-action'], value='accept') + new_base = base + ['ipv4', 'input', 'filter', 'rule'] + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [inp_ipv4_rule, 'inbound-interface', 'interface-name'], value=iface) + config.set(new_base + [inp_ipv4_rule, 'action'], value='jump') + config.set(new_base + [inp_ipv4_rule, 'jump-target'], value=target) + inp_ipv4_rule = inp_ipv4_rule + 5 + + if config.exists(base + ['interface', iface, direction, 'ipv6-name']): + target = config.return_value(base + ['interface', iface, direction, 'ipv6-name']) + if direction == 'in': + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept') + new_base = base + ['ipv6', 'forward', 'filter', 'rule'] + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [fwd_ipv6_rule, 'inbound-interface', 'interface-name'], value=iface) + config.set(new_base + [fwd_ipv6_rule, 'action'], value='jump') + config.set(new_base + [fwd_ipv6_rule, 'jump-target'], value=target) + fwd_ipv6_rule = fwd_ipv6_rule + 5 + elif direction == 'out': + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept') + new_base = base + ['ipv6', 'forward', 'filter', 'rule'] + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [fwd_ipv6_rule, 'outbound-interface', 'interface-name'], value=iface) + config.set(new_base + [fwd_ipv6_rule, 'action'], value='jump') + config.set(new_base + [fwd_ipv6_rule, 'jump-target'], value=target) + fwd_ipv6_rule = fwd_ipv6_rule + 5 + else: + new_base = base + ['ipv6', 'input', 'filter', 'rule'] + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv6', 'input', 'filter', 'default-action'], value='accept') + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [inp_ipv6_rule, 'inbound-interface', 'interface-name'], value=iface) + config.set(new_base + [inp_ipv6_rule, 'action'], value='jump') + config.set(new_base + [inp_ipv6_rule, 'jump-target'], value=target) + inp_ipv6_rule = inp_ipv6_rule + 5 + + config.delete(base + ['interface']) diff --git a/src/migration-scripts/firewall/11-to-12 b/src/migration-scripts/firewall/11-to-12 new file mode 100644 index 0000000..80a74cc --- /dev/null +++ b/src/migration-scripts/firewall/11-to-12 @@ -0,0 +1,51 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5681: Firewall re-writing. Simplify cli when mathcing interface +# From + # set firewall ... rule <rule> [inbound-interface | outboubd-interface] interface-name <iface> + # set firewall ... rule <rule> [inbound-interface | outboubd-interface] interface-group <iface_group> +# To + # set firewall ... rule <rule> [inbound-interface | outboubd-interface] name <iface> + # set firewall ... rule <rule> [inbound-interface | outboubd-interface] group <iface_group> + +from vyos.configtree import ConfigTree + +base = ['firewall'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + ## Migration from base chains + #if config.exists(base + ['interface', iface, direction]): + for family in ['ipv4', 'ipv6']: + if config.exists(base + [family]): + for hook in ['forward', 'input', 'output', 'name']: + if config.exists(base + [family, hook]): + for priority in config.list_nodes(base + [family, hook]): + if config.exists(base + [family, hook, priority, 'rule']): + for rule in config.list_nodes(base + [family, hook, priority, 'rule']): + for direction in ['inbound-interface', 'outbound-interface']: + if config.exists(base + [family, hook, priority, 'rule', rule, direction]): + if config.exists(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']): + iface = config.return_value(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']) + config.set(base + [family, hook, priority, 'rule', rule, direction, 'name'], value=iface) + config.delete(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']) + elif config.exists(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']): + group = config.return_value(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']) + config.set(base + [family, hook, priority, 'rule', rule, direction, 'group'], value=group) + config.delete(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']) diff --git a/src/migration-scripts/firewall/12-to-13 b/src/migration-scripts/firewall/12-to-13 new file mode 100644 index 0000000..d7b801c --- /dev/null +++ b/src/migration-scripts/firewall/12-to-13 @@ -0,0 +1,69 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5729: Switch to valueless whenever is possible. +# From + # set firewall ... rule <rule> log enable + # set firewall ... rule <rule> state <state> enable + # set firewall ... rule <rule> log disable + # set firewall ... rule <rule> state <state> disable +# To + # set firewall ... rule <rule> log + # set firewall ... rule <rule> state <state> + # Remove command if log=disable or <state>=disable + +from vyos.configtree import ConfigTree + +base = ['firewall'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # State Policy logs: + if config.exists(base + ['global-options', 'state-policy']): + for state in config.list_nodes(base + ['global-options', 'state-policy']): + if config.exists(base + ['global-options', 'state-policy', state, 'log']): + log_value = config.return_value(base + ['global-options', 'state-policy', state, 'log']) + config.delete(base + ['global-options', 'state-policy', state, 'log']) + if log_value == 'enable': + config.set(base + ['global-options', 'state-policy', state, 'log']) + + for family in ['ipv4', 'ipv6', 'bridge']: + if config.exists(base + [family]): + for hook in ['forward', 'input', 'output', 'name']: + if config.exists(base + [family, hook]): + for priority in config.list_nodes(base + [family, hook]): + if config.exists(base + [family, hook, priority, 'rule']): + for rule in config.list_nodes(base + [family, hook, priority, 'rule']): + # Log + if config.exists(base + [family, hook, priority, 'rule', rule, 'log']): + log_value = config.return_value(base + [family, hook, priority, 'rule', rule, 'log']) + config.delete(base + [family, hook, priority, 'rule', rule, 'log']) + if log_value == 'enable': + config.set(base + [family, hook, priority, 'rule', rule, 'log']) + # State + if config.exists(base + [family, hook, priority, 'rule', rule, 'state']): + flag_enable = 'False' + for state in ['established', 'invalid', 'new', 'related']: + if config.exists(base + [family, hook, priority, 'rule', rule, 'state', state]): + state_value = config.return_value(base + [family, hook, priority, 'rule', rule, 'state', state]) + config.delete(base + [family, hook, priority, 'rule', rule, 'state', state]) + if state_value == 'enable': + config.set(base + [family, hook, priority, 'rule', rule, 'state'], value=state, replace=False) + flag_enable = 'True' + if flag_enable == 'False': + config.delete(base + [family, hook, priority, 'rule', rule, 'state']) diff --git a/src/migration-scripts/firewall/13-to-14 b/src/migration-scripts/firewall/13-to-14 new file mode 100644 index 0000000..723b0ae --- /dev/null +++ b/src/migration-scripts/firewall/13-to-14 @@ -0,0 +1,39 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5834: Rename 'enable-default-log' to 'default-log' +# From + # set firewall ... filter enable-default-log + # set firewall ... name <name> enable-default-log +# To + # set firewall ... filter default-log + # set firewall ... name <name> default-log + +from vyos.configtree import ConfigTree + +base = ['firewall'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for family in ['ipv4', 'ipv6', 'bridge']: + if config.exists(base + [family]): + for hook in ['forward', 'input', 'output', 'name']: + if config.exists(base + [family, hook]): + for priority in config.list_nodes(base + [family, hook]): + if config.exists(base + [family, hook, priority, 'enable-default-log']): + config.rename(base + [family, hook, priority, 'enable-default-log'], 'default-log') diff --git a/src/migration-scripts/firewall/14-to-15 b/src/migration-scripts/firewall/14-to-15 new file mode 100644 index 0000000..e4a2aae --- /dev/null +++ b/src/migration-scripts/firewall/14-to-15 @@ -0,0 +1,25 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5535: Migrate <set system ip disable-directed-broadcast> to <set firewall global-options directed-broadcas [enable|disable] + +from vyos.configtree import ConfigTree + +base = ['firewall'] + +def migrate(config: ConfigTree) -> None: + if config.exists(['system', 'ip', 'disable-directed-broadcast']): + config.set(['firewall', 'global-options', 'directed-broadcast'], value='disable') + config.delete(['system', 'ip', 'disable-directed-broadcast']) diff --git a/src/migration-scripts/firewall/15-to-16 b/src/migration-scripts/firewall/15-to-16 new file mode 100644 index 0000000..8e28bba --- /dev/null +++ b/src/migration-scripts/firewall/15-to-16 @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# T6394: Migrate conntrack timeout options to firewall global-options + # from: set system conntrack timeout .. + # to: set firewall global-options timeout ... + +from vyos.configtree import ConfigTree + +firewall_base = ['firewall', 'global-options'] +conntrack_base = ['system', 'conntrack', 'timeout'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(conntrack_base): + # Nothing to do + return + + for protocol in ['icmp', 'tcp', 'udp', 'other']: + if config.exists(conntrack_base + [protocol]): + if not config.exists(firewall_base + ['timeout']): + config.set(firewall_base + ['timeout']) + + config.copy(conntrack_base + [protocol], firewall_base + ['timeout', protocol]) + config.delete(conntrack_base + [protocol]) diff --git a/src/migration-scripts/firewall/16-to-17 b/src/migration-scripts/firewall/16-to-17 new file mode 100644 index 0000000..ad0706f --- /dev/null +++ b/src/migration-scripts/firewall/16-to-17 @@ -0,0 +1,60 @@ +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# +# T4694: Adding rt ipsec exists/missing match to firewall configs. +# This involves a syntax change for IPsec matches, reflecting that different +# nftables expressions are required depending on whether we're matching a +# decrypted packet or a packet that will be encrypted - it's directional. +# The old rules only matched decrypted packets, those matches are now *-in: + # from: set firewall <family> <chainspec> rule <rule#> ipsec match-ipsec|match-none + # to: set firewall <family> <chainspec> rule <rule#> ipsec match-ipsec-in|match-none-in +# +# The <chainspec> positions this match allowed were: +# name (any custom chains), forward filter, input filter, prerouting raw. +# There are positions where it was possible to set, but it would never commit +# (nftables rejects 'meta ipsec' in output hooks), they are not considered here. +# + +from vyos.configtree import ConfigTree + +firewall_base = ['firewall'] + +def migrate_chain(config: ConfigTree, path: list[str]) -> None: + if not config.exists(path + ['rule']): + return + + for rule_num in config.list_nodes(path + ['rule']): + tmp_path = path + ['rule', rule_num, 'ipsec'] + if config.exists(tmp_path + ['match-ipsec']): + config.delete(tmp_path + ['match-ipsec']) + config.set(tmp_path + ['match-ipsec-in']) + elif config.exists(tmp_path + ['match-none']): + config.delete(tmp_path + ['match-none']) + config.set(tmp_path + ['match-none-in']) + +def migrate(config: ConfigTree) -> None: + if not config.exists(firewall_base): + # Nothing to do + return + + for family in ['ipv4', 'ipv6']: + tmp_path = firewall_base + [family, 'name'] + if config.exists(tmp_path): + for custom_fwname in config.list_nodes(tmp_path): + migrate_chain(config, tmp_path + [custom_fwname]) + + for base_hook in [['forward', 'filter'], ['input', 'filter'], ['prerouting', 'raw']]: + tmp_path = firewall_base + [family] + base_hook + migrate_chain(config, tmp_path) diff --git a/src/migration-scripts/firewall/5-to-6 b/src/migration-scripts/firewall/5-to-6 new file mode 100644 index 0000000..d016847 --- /dev/null +++ b/src/migration-scripts/firewall/5-to-6 @@ -0,0 +1,85 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3090: migrate "firewall options interface <name> adjust-mss" to the +# individual interface. + +from vyos.configtree import ConfigTree +from vyos.ifconfig import Section + +base = ['firewall', 'options', 'interface'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for interface in config.list_nodes(base): + if config.exists(base + [interface, 'disable']): + continue + + if config.exists(base + [interface, 'adjust-mss']): + section = Section.section(interface) + tmp = config.return_value(base + [interface, 'adjust-mss']) + + vlan = interface.split('.') + base_interface_path = ['interfaces', section, vlan[0]] + + if len(vlan) == 1: + # Normal interface, no VLAN + config.set(base_interface_path + ['ip', 'adjust-mss'], value=tmp) + elif len(vlan) == 2: + # Regular VIF or VIF-S interface - we need to check the config + vif = vlan[1] + if config.exists(base_interface_path + ['vif', vif]): + config.set(base_interface_path + ['vif', vif, 'ip', 'adjust-mss'], value=tmp) + elif config.exists(base_interface_path + ['vif-s', vif]): + config.set(base_interface_path + ['vif-s', vif, 'ip', 'adjust-mss'], value=tmp) + elif len(vlan) == 3: + # VIF-S interface with VIF-C subinterface + vif_s = vlan[1] + vif_c = vlan[2] + config.set(base_interface_path + ['vif-s', vif_s, 'vif-c', vif_c, 'ip', 'adjust-mss'], value=tmp) + config.set_tag(base_interface_path + ['vif-s']) + config.set_tag(base_interface_path + ['vif-s', vif_s, 'vif-c']) + + if config.exists(base + [interface, 'adjust-mss6']): + section = Section.section(interface) + tmp = config.return_value(base + [interface, 'adjust-mss6']) + + vlan = interface.split('.') + base_interface_path = ['interfaces', section, vlan[0]] + + if len(vlan) == 1: + # Normal interface, no VLAN + config.set(['interfaces', section, interface, 'ipv6', 'adjust-mss'], value=tmp) + elif len(vlan) == 2: + # Regular VIF or VIF-S interface - we need to check the config + vif = vlan[1] + if config.exists(base_interface_path + ['vif', vif]): + config.set(base_interface_path + ['vif', vif, 'ipv6', 'adjust-mss'], value=tmp) + config.set_tag(base_interface_path + ['vif']) + elif config.exists(base_interface_path + ['vif-s', vif]): + config.set(base_interface_path + ['vif-s', vif, 'ipv6', 'adjust-mss'], value=tmp) + config.set_tag(base_interface_path + ['vif-s']) + elif len(vlan) == 3: + # VIF-S interface with VIF-C subinterface + vif_s = vlan[1] + vif_c = vlan[2] + config.set(base_interface_path + ['vif-s', vif_s, 'vif-c', vif_c, 'ipv6', 'adjust-mss'], value=tmp) + config.set_tag(base_interface_path + ['vif-s']) + config.set_tag(base_interface_path + ['vif-s', vif_s, 'vif-c']) + + config.delete(['firewall', 'options']) diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7 new file mode 100644 index 0000000..1afbc78 --- /dev/null +++ b/src/migration-scripts/firewall/6-to-7 @@ -0,0 +1,304 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T2199: Remove unavailable nodes due to XML/Python implementation using nftables +# monthdays: nftables does not have a monthdays equivalent +# utc: nftables userspace uses localtime and calculates the UTC offset automatically +# icmp/v6: migrate previously available `type-name` to valid type/code +# T4178: Update tcp flags to use multi value node +# T6071: CLI description limit of 256 characters + +import re + +from vyos.configtree import ConfigTree + +max_len_description = 255 + +base = ['firewall'] + +icmp_remove = ['any'] +icmp_translations = { + 'ping': 'echo-request', + 'pong': 'echo-reply', + 'ttl-exceeded': 'time-exceeded', + # Network Unreachable + 'network-unreachable': [3, 0], + 'host-unreachable': [3, 1], + 'protocol-unreachable': [3, 2], + 'port-unreachable': [3, 3], + 'fragmentation-needed': [3, 4], + 'source-route-failed': [3, 5], + 'network-unknown': [3, 6], + 'host-unknown': [3, 7], + 'network-prohibited': [3, 9], + 'host-prohibited': [3, 10], + 'TOS-network-unreachable': [3, 11], + 'TOS-host-unreachable': [3, 12], + 'communication-prohibited': [3, 13], + 'host-precedence-violation': [3, 14], + 'precedence-cutoff': [3, 15], + # Redirect + 'network-redirect': [5, 0], + 'host-redirect': [5, 1], + 'TOS-network-redirect': [5, 2], + 'TOS host-redirect': [5, 3], + # Time Exceeded + 'ttl-zero-during-transit': [11, 0], + 'ttl-zero-during-reassembly': [11, 1], + 'ttl-exceeded': 'time-exceeded', + # Parameter Problem + 'ip-header-bad': [12, 0], + 'required-option-missing': [12, 1] +} + +icmpv6_remove = [] +icmpv6_translations = { + 'ping': 'echo-request', + 'pong': 'echo-reply', + # Destination Unreachable + 'no-route': [1, 0], + 'communication-prohibited': [1, 1], + 'address-unreachble': [1, 3], + 'port-unreachable': [1, 4], + # nd + 'redirect': 'nd-redirect', + 'router-solicitation': 'nd-router-solicit', + 'router-advertisement': 'nd-router-advert', + 'neighbour-solicitation': 'nd-neighbor-solicit', + 'neighbor-solicitation': 'nd-neighbor-solicit', + 'neighbour-advertisement': 'nd-neighbor-advert', + 'neighbor-advertisement': 'nd-neighbor-advert', + # Time Exceeded + 'ttl-zero-during-transit': [3, 0], + 'ttl-zero-during-reassembly': [3, 1], + # Parameter Problem + 'bad-header': [4, 0], + 'unknown-header-type': [4, 1], + 'unknown-option': [4, 2] +} + +v4_groups = ["address-group", "network-group", "port-group"] +v6_groups = ["ipv6-address-group", "ipv6-network-group", "port-group"] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + v4_found = False + v6_found = False + translated_dict = {} + + if config.exists(base + ['group']): + for group_type in config.list_nodes(base + ['group']): + for group_name in config.list_nodes(base + ['group', group_type]): + name_description = base + ['group', group_type, group_name, 'description'] + if config.exists(name_description): + tmp = config.return_value(name_description) + config.set(name_description, value=tmp[:max_len_description]) + if '+' in group_name: + replacement_string = "_" + if group_type in v4_groups and not v4_found: + v4_found = True + if group_type in v6_groups and not v6_found: + v6_found = True + new_group_name = group_name.replace('+', replacement_string) + while config.exists(base + ['group', group_type, new_group_name]): + replacement_string = replacement_string + "_" + new_group_name = group_name.replace('+', replacement_string) + translated_dict[group_name] = new_group_name + config.copy(base + ['group', group_type, group_name], base + ['group', group_type, new_group_name]) + config.delete(base + ['group', group_type, group_name]) + + if config.exists(base + ['name']): + for name in config.list_nodes(base + ['name']): + name_description = base + ['name', name, 'description'] + if config.exists(name_description): + tmp = config.return_value(name_description) + config.set(name_description, value=tmp[:max_len_description]) + + if not config.exists(base + ['name', name, 'rule']): + continue + + for rule in config.list_nodes(base + ['name', name, 'rule']): + rule_description = base + ['name', name, 'rule', rule, 'description'] + if config.exists(rule_description): + tmp = config.return_value(rule_description) + config.set(rule_description, value=tmp[:max_len_description]) + + rule_recent = base + ['name', name, 'rule', rule, 'recent'] + rule_time = base + ['name', name, 'rule', rule, 'time'] + rule_tcp_flags = base + ['name', name, 'rule', rule, 'tcp', 'flags'] + rule_icmp = base + ['name', name, 'rule', rule, 'icmp'] + + if config.exists(rule_time + ['monthdays']): + config.delete(rule_time + ['monthdays']) + + if config.exists(rule_time + ['utc']): + config.delete(rule_time + ['utc']) + + if config.exists(rule_recent + ['time']): + tmp = int(config.return_value(rule_recent + ['time'])) + unit = 'minute' + if tmp > 600: + unit = 'hour' + elif tmp < 10: + unit = 'second' + config.set(rule_recent + ['time'], value=unit) + + if config.exists(rule_tcp_flags): + tmp = config.return_value(rule_tcp_flags) + config.delete(rule_tcp_flags) + for flag in tmp.split(","): + if flag[0] == '!': + config.set(rule_tcp_flags + ['not', flag[1:].lower()]) + else: + config.set(rule_tcp_flags + [flag.lower()]) + + if config.exists(rule_icmp + ['type-name']): + tmp = config.return_value(rule_icmp + ['type-name']) + if tmp in icmp_remove: + config.delete(rule_icmp + ['type-name']) + elif tmp in icmp_translations: + translate = icmp_translations[tmp] + if isinstance(translate, str): + config.set(rule_icmp + ['type-name'], value=translate) + elif isinstance(translate, list): + config.delete(rule_icmp + ['type-name']) + config.set(rule_icmp + ['type'], value=translate[0]) + config.set(rule_icmp + ['code'], value=translate[1]) + + for direction in ['destination', 'source']: + if config.exists(base + ['name', name, 'rule', rule, direction]): + if config.exists(base + ['name', name, 'rule', rule, direction, 'group']) and v4_found: + for group_type in config.list_nodes(base + ['name', name, 'rule', rule, direction, 'group']): + group_name = config.return_value(base + ['name', name, 'rule', rule, direction, 'group', group_type]) + if '+' in group_name: + if group_name[0] == "!": + new_group_name = "!" + translated_dict[group_name[1:]] + else: + new_group_name = translated_dict[group_name] + config.set(base + ['name', name, 'rule', rule, direction, 'group', group_type], value=new_group_name) + + pg_base = base + ['name', name, 'rule', rule, direction, 'group', 'port-group'] + proto_base = base + ['name', name, 'rule', rule, 'protocol'] + if config.exists(pg_base) and not config.exists(proto_base): + config.set(proto_base, value='tcp_udp') + + if '+' in name: + replacement_string = "_" + new_name = name.replace('+', replacement_string) + while config.exists(base + ['name', new_name]): + replacement_string = replacement_string + "_" + new_name = name.replace('+', replacement_string) + config.copy(base + ['name', name], base + ['name', new_name]) + config.delete(base + ['name', name]) + + if config.exists(base + ['ipv6-name']): + for name in config.list_nodes(base + ['ipv6-name']): + name_description = base + ['ipv6-name', name, 'description'] + if config.exists(name_description): + tmp = config.return_value(name_description) + config.set(name_description, value=tmp[:max_len_description]) + + if not config.exists(base + ['ipv6-name', name, 'rule']): + continue + + for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): + rule_description = base + ['ipv6-name', name, 'rule', rule, 'description'] + if config.exists(rule_description): + tmp = config.return_value(rule_description) + config.set(rule_description, value=tmp[:max_len_description]) + + rule_recent = base + ['ipv6-name', name, 'rule', rule, 'recent'] + rule_time = base + ['ipv6-name', name, 'rule', rule, 'time'] + rule_tcp_flags = base + ['ipv6-name', name, 'rule', rule, 'tcp', 'flags'] + rule_icmp = base + ['ipv6-name', name, 'rule', rule, 'icmpv6'] + + if config.exists(rule_time + ['monthdays']): + config.delete(rule_time + ['monthdays']) + + if config.exists(rule_time + ['utc']): + config.delete(rule_time + ['utc']) + + if config.exists(rule_recent + ['time']): + tmp = int(config.return_value(rule_recent + ['time'])) + unit = 'minute' + if tmp > 600: + unit = 'hour' + elif tmp < 10: + unit = 'second' + config.set(rule_recent + ['time'], value=unit) + + if config.exists(rule_tcp_flags): + tmp = config.return_value(rule_tcp_flags) + config.delete(rule_tcp_flags) + for flag in tmp.split(","): + if flag[0] == '!': + config.set(rule_tcp_flags + ['not', flag[1:].lower()]) + else: + config.set(rule_tcp_flags + [flag.lower()]) + + if config.exists(base + ['ipv6-name', name, 'rule', rule, 'protocol']): + tmp = config.return_value(base + ['ipv6-name', name, 'rule', rule, 'protocol']) + if tmp == 'icmpv6': + config.set(base + ['ipv6-name', name, 'rule', rule, 'protocol'], value='ipv6-icmp') + + if config.exists(rule_icmp + ['type']): + tmp = config.return_value(rule_icmp + ['type']) + type_code_match = re.match(r'^(\d+)(?:/(\d+))?$', tmp) + + if type_code_match: + config.set(rule_icmp + ['type'], value=type_code_match[1]) + if type_code_match[2]: + config.set(rule_icmp + ['code'], value=type_code_match[2]) + elif tmp in icmpv6_remove: + config.delete(rule_icmp + ['type']) + elif tmp in icmpv6_translations: + translate = icmpv6_translations[tmp] + if isinstance(translate, str): + config.delete(rule_icmp + ['type']) + config.set(rule_icmp + ['type-name'], value=translate) + elif isinstance(translate, list): + config.set(rule_icmp + ['type'], value=translate[0]) + config.set(rule_icmp + ['code'], value=translate[1]) + else: + config.rename(rule_icmp + ['type'], 'type-name') + + for direction in ['destination', 'source']: + if config.exists(base + ['ipv6-name', name, 'rule', rule, direction]): + if config.exists(base + ['ipv6-name', name, 'rule', rule, direction, 'group']) and v6_found: + for group_type in config.list_nodes(base + ['ipv6-name', name, 'rule', rule, direction, 'group']): + group_name = config.return_value(base + ['ipv6-name', name, 'rule', rule, direction, 'group', group_type]) + if '+' in group_name: + if group_name[0] == "!": + new_group_name = "!" + translated_dict[group_name[1:]] + else: + new_group_name = translated_dict[group_name] + config.set(base + ['ipv6-name', name, 'rule', rule, direction, 'group', group_type], value=new_group_name) + + pg_base = base + ['ipv6-name', name, 'rule', rule, direction, 'group', 'port-group'] + proto_base = base + ['ipv6-name', name, 'rule', rule, 'protocol'] + if config.exists(pg_base) and not config.exists(proto_base): + config.set(proto_base, value='tcp_udp') + + if '+' in name: + replacement_string = "_" + new_name = name.replace('+', replacement_string) + while config.exists(base + ['ipv6-name', new_name]): + replacement_string = replacement_string + "_" + new_name = name.replace('+', replacement_string) + config.copy(base + ['ipv6-name', name], base + ['ipv6-name', new_name]) + config.delete(base + ['ipv6-name', name]) diff --git a/src/migration-scripts/firewall/7-to-8 b/src/migration-scripts/firewall/7-to-8 new file mode 100644 index 0000000..b8bcc52 --- /dev/null +++ b/src/migration-scripts/firewall/7-to-8 @@ -0,0 +1,81 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T2199: Migrate interface firewall nodes to firewall interfaces <ifname> <direction> name/ipv6-name <name> +# T2199: Migrate zone-policy to firewall node + +from vyos.configtree import ConfigTree + +base = ['firewall'] +zone_base = ['zone-policy'] + +def migrate_interface(config, iftype, ifname, vif=None, vifs=None, vifc=None): + if_path = ['interfaces', iftype, ifname] + ifname_full = ifname + + if vif: + if_path += ['vif', vif] + ifname_full = f'{ifname}.{vif}' + elif vifs: + if_path += ['vif-s', vifs] + ifname_full = f'{ifname}.{vifs}' + if vifc: + if_path += ['vif-c', vifc] + ifname_full = f'{ifname}.{vifs}.{vifc}' + + if not config.exists(if_path + ['firewall']): + return + + if not config.exists(['firewall', 'interface']): + config.set(['firewall', 'interface']) + config.set_tag(['firewall', 'interface']) + + config.copy(if_path + ['firewall'], ['firewall', 'interface', ifname_full]) + config.delete(if_path + ['firewall']) + +def migrate(config: ConfigTree) -> None: + if not config.exists(base) and not config.exists(zone_base): + # Nothing to do + return + + for iftype in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', iftype]): + migrate_interface(config, iftype, ifname) + + if config.exists(['interfaces', iftype, ifname, 'vif']): + for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): + migrate_interface(config, iftype, ifname, vif=vif) + + if config.exists(['interfaces', iftype, ifname, 'vif-s']): + for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): + migrate_interface(config, iftype, ifname, vifs=vifs) + + if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + migrate_interface(config, iftype, ifname, vifs=vifs, vifc=vifc) + + if config.exists(zone_base + ['zone']): + config.set(['firewall', 'zone']) + config.set_tag(['firewall', 'zone']) + + for zone in config.list_nodes(zone_base + ['zone']): + if 'interface' in config.list_nodes(zone_base + ['zone', zone]): + for iface in config.return_values(zone_base + ['zone', zone, 'interface']): + if '+' in iface: + config.delete_value(zone_base + ['zone', zone, 'interface'], value=iface) + iface = iface.replace('+', '*') + config.set(zone_base + ['zone', zone, 'interface'], value=iface, replace=False) + config.copy(zone_base + ['zone', zone], ['firewall', 'zone', zone]) + config.delete(zone_base)
\ No newline at end of file diff --git a/src/migration-scripts/firewall/8-to-9 b/src/migration-scripts/firewall/8-to-9 new file mode 100644 index 0000000..3c9e846 --- /dev/null +++ b/src/migration-scripts/firewall/8-to-9 @@ -0,0 +1,68 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T4780: Add firewall interface group +# cli changes from: +# set firewall [name | ipv6-name] <name> rule <number> [inbound-interface | outbound-interface] <interface_name> +# To +# set firewall [name | ipv6-name] <name> rule <number> [inbound-interface | outbound-interface] [interface-name | interface-group] <interface_name | interface_group> + +from vyos.configtree import ConfigTree + +base = ['firewall'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['name']): + for name in config.list_nodes(base + ['name']): + if not config.exists(base + ['name', name, 'rule']): + continue + + for rule in config.list_nodes(base + ['name', name, 'rule']): + rule_iiface = base + ['name', name, 'rule', rule, 'inbound-interface'] + rule_oiface = base + ['name', name, 'rule', rule, 'outbound-interface'] + + if config.exists(rule_iiface): + tmp = config.return_value(rule_iiface) + config.delete(rule_iiface) + config.set(rule_iiface + ['interface-name'], value=tmp) + + if config.exists(rule_oiface): + tmp = config.return_value(rule_oiface) + config.delete(rule_oiface) + config.set(rule_oiface + ['interface-name'], value=tmp) + + + if config.exists(base + ['ipv6-name']): + for name in config.list_nodes(base + ['ipv6-name']): + if not config.exists(base + ['ipv6-name', name, 'rule']): + continue + + for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): + rule_iiface = base + ['ipv6-name', name, 'rule', rule, 'inbound-interface'] + rule_oiface = base + ['ipv6-name', name, 'rule', rule, 'outbound-interface'] + + if config.exists(rule_iiface): + tmp = config.return_value(rule_iiface) + config.delete(rule_iiface) + config.set(rule_iiface + ['interface-name'], value=tmp) + + if config.exists(rule_oiface): + tmp = config.return_value(rule_oiface) + config.delete(rule_oiface) + config.set(rule_oiface + ['interface-name'], value=tmp) diff --git a/src/migration-scripts/firewall/9-to-10 b/src/migration-scripts/firewall/9-to-10 new file mode 100644 index 0000000..306a53a --- /dev/null +++ b/src/migration-scripts/firewall/9-to-10 @@ -0,0 +1,57 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5050: Log options +# cli changes from: +# set firewall [name | ipv6-name] <name> rule <number> log-level <log_level> +# To +# set firewall [name | ipv6-name] <name> rule <number> log-options level <log_level> + +from vyos.configtree import ConfigTree + +base = ['firewall'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['name']): + for name in config.list_nodes(base + ['name']): + if not config.exists(base + ['name', name, 'rule']): + continue + + for rule in config.list_nodes(base + ['name', name, 'rule']): + log_options_base = base + ['name', name, 'rule', rule, 'log-options'] + rule_log_level = base + ['name', name, 'rule', rule, 'log-level'] + + if config.exists(rule_log_level): + tmp = config.return_value(rule_log_level) + config.delete(rule_log_level) + config.set(log_options_base + ['level'], value=tmp) + + if config.exists(base + ['ipv6-name']): + for name in config.list_nodes(base + ['ipv6-name']): + if not config.exists(base + ['ipv6-name', name, 'rule']): + continue + + for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): + log_options_base = base + ['ipv6-name', name, 'rule', rule, 'log-options'] + rule_log_level = base + ['ipv6-name', name, 'rule', rule, 'log-level'] + + if config.exists(rule_log_level): + tmp = config.return_value(rule_log_level) + config.delete(rule_log_level) + config.set(log_options_base + ['level'], value=tmp) diff --git a/src/migration-scripts/flow-accounting/0-to-1 b/src/migration-scripts/flow-accounting/0-to-1 new file mode 100644 index 0000000..77670e3 --- /dev/null +++ b/src/migration-scripts/flow-accounting/0-to-1 @@ -0,0 +1,51 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T4099: flow-accounting: sync "source-ip" and "source-address" between netflow +# and sflow ion CLI +# T4105: flow-accounting: drop "sflow agent-address auto" + +from vyos.configtree import ConfigTree + +base = ['system', 'flow-accounting'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # T4099 + tmp = base + ['netflow', 'source-ip'] + if config.exists(tmp): + config.rename(tmp, 'source-address') + + # T4105 + tmp = base + ['sflow', 'agent-address'] + if config.exists(tmp): + value = config.return_value(tmp) + if value == 'auto': + # delete the "auto" + config.delete(tmp) + + # 1) check if BGP router-id is set + # 2) check if OSPF router-id is set + # 3) check if OSPFv3 router-id is set + router_id = None + for protocol in ['bgp', 'ospf', 'ospfv3']: + if config.exists(['protocols', protocol, 'parameters', 'router-id']): + router_id = config.return_value(['protocols', protocol, 'parameters', 'router-id']) + break + if router_id: + config.set(tmp, value=router_id) diff --git a/src/migration-scripts/https/0-to-1 b/src/migration-scripts/https/0-to-1 new file mode 100644 index 0000000..52fe3f2 --- /dev/null +++ b/src/migration-scripts/https/0-to-1 @@ -0,0 +1,50 @@ +# Copyright 202-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# * Move server block directives under 'virtual-host' tag node, instead of +# relying on 'listen-address' tag node + +from vyos.configtree import ConfigTree + +old_base = ['service', 'https', 'listen-address'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(old_base): + # Nothing to do + return + + new_base = ['service', 'https', 'virtual-host'] + config.set(new_base) + config.set_tag(new_base) + + index = 0 + for addr in config.list_nodes(old_base): + tag_name = f'vhost{index}' + config.set(new_base + [tag_name]) + config.set(new_base + [tag_name, 'listen-address'], value=addr) + + if config.exists(old_base + [addr, 'listen-port']): + port = config.return_value(old_base + [addr, 'listen-port']) + config.set(new_base + [tag_name, 'listen-port'], value=port) + + if config.exists(old_base + [addr, 'server-name']): + names = config.return_values(old_base + [addr, 'server-name']) + for name in names: + config.set(new_base + [tag_name, 'server-name'], value=name, + replace=False) + + index += 1 + + config.delete(old_base) diff --git a/src/migration-scripts/https/1-to-2 b/src/migration-scripts/https/1-to-2 new file mode 100644 index 0000000..dad7ac1 --- /dev/null +++ b/src/migration-scripts/https/1-to-2 @@ -0,0 +1,35 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# * Move 'api virtual-host' list to 'api-restrict virtual-host' so it +# is owned by service_https.py + +from vyos.configtree import ConfigTree + +old_base = ['service', 'https', 'api', 'virtual-host'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(old_base): + # Nothing to do + return + + new_base = ['service', 'https', 'api-restrict', 'virtual-host'] + config.set(new_base) + + names = config.return_values(old_base) + for name in names: + config.set(new_base, value=name, replace=False) + + config.delete(old_base) diff --git a/src/migration-scripts/https/2-to-3 b/src/migration-scripts/https/2-to-3 new file mode 100644 index 0000000..1125cae --- /dev/null +++ b/src/migration-scripts/https/2-to-3 @@ -0,0 +1,66 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# * Migrate system signed certificate to use PKI + +from vyos.configtree import ConfigTree +from vyos.pki import create_certificate +from vyos.pki import create_certificate_request +from vyos.pki import create_private_key +from vyos.pki import encode_certificate +from vyos.pki import encode_private_key + +base = ['service', 'https', 'certificates'] +pki_base = ['pki'] + +def wrapped_pem_to_config_value(pem): + out = [] + for line in pem.strip().split("\n"): + if not line or line.startswith("-----") or line[0] == '#': + continue + out.append(line) + return "".join(out) + +def migrate(config: ConfigTree) -> None: + if not config.exists(base + ['system-generated-certificate']): + return + + if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + + valid_days = 365 + if config.exists(base + ['system-generated-certificate', 'lifetime']): + valid_days = int(config.return_value(base + ['system-generated-certificate', 'lifetime'])) + + key = create_private_key('rsa', 2048) + subject = {'country': 'GB', 'state': 'N/A', 'locality': 'N/A', 'organization': 'VyOS', 'common_name': 'vyos'} + cert_req = create_certificate_request(subject, key, ['vyos']) + cert = create_certificate(cert_req, cert_req, key, valid_days) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['certificate', 'generated_https', 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + + if key: + key_pem = encode_private_key(key) + config.set(pki_base + ['certificate', 'generated_https', 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + + if cert and key: + config.set(base + ['certificate'], value='generated_https') + else: + print('Failed to migrate system-generated-certificate from https service') + + config.delete(base + ['system-generated-certificate']) diff --git a/src/migration-scripts/https/3-to-4 b/src/migration-scripts/https/3-to-4 new file mode 100644 index 0000000..c01236c --- /dev/null +++ b/src/migration-scripts/https/3-to-4 @@ -0,0 +1,34 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T4768 rename node 'gql' to 'graphql'. + +from vyos.configtree import ConfigTree + +old_base = ['service', 'https', 'api', 'gql'] +new_base = ['service', 'https', 'api', 'graphql'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(old_base): + # Nothing to do + return + + config.set(new_base) + + nodes = config.list_nodes(old_base) + for node in nodes: + config.copy(old_base + [node], new_base + [node]) + + config.delete(old_base) diff --git a/src/migration-scripts/https/4-to-5 b/src/migration-scripts/https/4-to-5 new file mode 100644 index 0000000..0f1c790 --- /dev/null +++ b/src/migration-scripts/https/4-to-5 @@ -0,0 +1,43 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5762: http: api: smoketests fail as they can not establish IPv6 connection +# to uvicorn backend server, always make the UNIX domain socket the +# default way of communication + +from vyos.configtree import ConfigTree + +base = ['service', 'https'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # Delete "socket" CLI option - we always use UNIX domain sockets for + # NGINX <-> API server communication + if config.exists(base + ['api', 'socket']): + config.delete(base + ['api', 'socket']) + + # There is no need for an API service port, as UNIX domain sockets + # are used + if config.exists(base + ['api', 'port']): + config.delete(base + ['api', 'port']) + + # rename listen-port -> port ver virtual-host + if config.exists(base + ['virtual-host']): + for vhost in config.list_nodes(base + ['virtual-host']): + if config.exists(base + ['virtual-host', vhost, 'listen-port']): + config.rename(base + ['virtual-host', vhost, 'listen-port'], 'port') diff --git a/src/migration-scripts/https/5-to-6 b/src/migration-scripts/https/5-to-6 new file mode 100644 index 0000000..6ef6976 --- /dev/null +++ b/src/migration-scripts/https/5-to-6 @@ -0,0 +1,89 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5886: Add support for ACME protocol (LetsEncrypt), migrate https certbot +# to new "pki certificate" CLI tree +# T5902: Remove virtual-host + +import os + +from vyos.configtree import ConfigTree +from vyos.defaults import directories +from vyos.utils.process import cmd + +vyos_certbot_dir = directories['certbot'] + +base = ['service', 'https'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['certificates', 'certbot']): + # both domain-name and email must be set on CLI - ensured by previous verify() + domain_names = config.return_values(base + ['certificates', 'certbot', 'domain-name']) + email = config.return_value(base + ['certificates', 'certbot', 'email']) + config.delete(base + ['certificates', 'certbot']) + + # Set default certname based on domain-name + cert_name = 'https-' + domain_names[0].split('.')[0] + # Overwrite certname from previous certbot calls if available + # We can not use python code like os.scandir due to filesystem permissions. + # This must be run as root + certbot_live = f'{vyos_certbot_dir}/live/' # we need the trailing / + if os.path.exists(certbot_live): + tmp = cmd(f'sudo find {certbot_live} -maxdepth 1 -type d') + tmp = tmp.split() # tmp = ['/config/auth/letsencrypt/live', '/config/auth/letsencrypt/live/router.vyos.net'] + tmp.remove(certbot_live) + cert_name = tmp[0].replace(certbot_live, '') + + config.set(['pki', 'certificate', cert_name, 'acme', 'email'], value=email) + config.set_tag(['pki', 'certificate']) + for domain in domain_names: + config.set(['pki', 'certificate', cert_name, 'acme', 'domain-name'], value=domain, replace=False) + + # Update Webserver certificate + config.set(base + ['certificates', 'certificate'], value=cert_name) + + if config.exists(base + ['virtual-host']): + allow_client = [] + listen_port = [] + listen_address = [] + for virtual_host in config.list_nodes(base + ['virtual-host']): + allow_path = base + ['virtual-host', virtual_host, 'allow-client', 'address'] + if config.exists(allow_path): + tmp = config.return_values(allow_path) + allow_client.extend(tmp) + + port_path = base + ['virtual-host', virtual_host, 'port'] + if config.exists(port_path): + tmp = config.return_value(port_path) + listen_port.append(tmp) + + listen_address_path = base + ['virtual-host', virtual_host, 'listen-address'] + if config.exists(listen_address_path): + tmp = config.return_value(listen_address_path) + listen_address.append(tmp) + + config.delete(base + ['virtual-host']) + for client in allow_client: + config.set(base + ['allow-client', 'address'], value=client, replace=False) + + # clear listen-address if "all" were specified + if '*' in listen_address: + listen_address = [] + for address in listen_address: + config.set(base + ['listen-address'], value=address, replace=False) diff --git a/src/migration-scripts/ids/0-to-1 b/src/migration-scripts/ids/0-to-1 new file mode 100644 index 0000000..1b963e8 --- /dev/null +++ b/src/migration-scripts/ids/0-to-1 @@ -0,0 +1,38 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T4557: Migrate threshold and add new threshold types + +from vyos.configtree import ConfigTree + +base = ['service', 'ids', 'ddos-protection'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base + ['threshold']): + # Nothing to do + return + else: + if config.exists(base + ['threshold', 'fps']): + tmp = config.return_value(base + ['threshold', 'fps']) + config.delete(base + ['threshold', 'fps']) + config.set(base + ['threshold', 'general', 'fps'], value=tmp) + if config.exists(base + ['threshold', 'mbps']): + tmp = config.return_value(base + ['threshold', 'mbps']) + config.delete(base + ['threshold', 'mbps']) + config.set(base + ['threshold', 'general', 'mbps'], value=tmp) + if config.exists(base + ['threshold', 'pps']): + tmp = config.return_value(base + ['threshold', 'pps']) + config.delete(base + ['threshold', 'pps']) + config.set(base + ['threshold', 'general', 'pps'], value=tmp) diff --git a/src/migration-scripts/interfaces/0-to-1 b/src/migration-scripts/interfaces/0-to-1 new file mode 100644 index 0000000..7c135e7 --- /dev/null +++ b/src/migration-scripts/interfaces/0-to-1 @@ -0,0 +1,113 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Change syntax of bridge interface +# - move interface based bridge-group to actual bridge (de-nest) +# - make stp and igmp-snooping nodes valueless +# https://vyos.dev/T1556 + +from vyos.configtree import ConfigTree + +def migrate_bridge(config, tree, intf): + # check if bridge-group exists + tree_bridge = tree + ['bridge-group'] + if config.exists(tree_bridge): + bridge = config.return_value(tree_bridge + ['bridge']) + # create new bridge member interface + config.set(base + [bridge, 'member', 'interface', intf]) + # format as tag node to avoid loading problems + config.set_tag(base + [bridge, 'member', 'interface']) + + # cost: migrate if configured + tree_cost = tree + ['bridge-group', 'cost'] + if config.exists(tree_cost): + cost = config.return_value(tree_cost) + # set new node + config.set(base + [bridge, 'member', 'interface', intf, 'cost'], value=cost) + + # priority: migrate if configured + tree_priority = tree + ['bridge-group', 'priority'] + if config.exists(tree_priority): + priority = config.return_value(tree_priority) + # set new node + config.set(base + [bridge, 'member', 'interface', intf, 'priority'], value=priority) + + # Delete the old bridge-group assigned to an interface + config.delete(tree_bridge) + + +def migrate(config: ConfigTree) -> None: + base = ['interfaces', 'bridge'] + + if not config.exists(base): + # Nothing to do + return + + # + # make stp and igmp-snooping nodes valueless + # + for br in config.list_nodes(base): + # STP: check if enabled + if config.exists(base + [br, 'stp']): + stp_val = config.return_value(base + [br, 'stp']) + # STP: delete node with old syntax + config.delete(base + [br, 'stp']) + # STP: set new node - if enabled + if stp_val == "true": + config.set(base + [br, 'stp'], value=None) + + # igmp-snooping: check if enabled + if config.exists(base + [br, 'igmp-snooping', 'querier']): + igmp_val = config.return_value(base + [br, 'igmp-snooping', 'querier']) + # igmp-snooping: delete node with old syntax + config.delete(base + [br, 'igmp-snooping', 'querier']) + # igmp-snooping: set new node - if enabled + if igmp_val == "enable": + config.set(base + [br, 'igmp', 'querier'], value=None) + + # + # move interface based bridge-group to actual bridge (de-nest) + # + bridge_types = ['bonding', 'ethernet', 'l2tpv3', 'openvpn', 'vxlan', 'wireless'] + for type in bridge_types: + if not config.exists(['interfaces', type]): + continue + + for interface in config.list_nodes(['interfaces', type]): + # check if bridge-group exists + bridge_group = ['interfaces', type, interface] + if config.exists(bridge_group + ['bridge-group']): + migrate_bridge(config, bridge_group, interface) + + # We also need to migrate VLAN interfaces + vlan_base = ['interfaces', type, interface, 'vif'] + if config.exists(vlan_base): + for vlan in config.list_nodes(vlan_base): + intf = "{}.{}".format(interface, vlan) + migrate_bridge(config, vlan_base + [vlan], intf) + + # And then we have service VLANs (vif-s) interfaces + vlan_base = ['interfaces', type, interface, 'vif-s'] + if config.exists(vlan_base): + for vif_s in config.list_nodes(vlan_base): + intf = "{}.{}".format(interface, vif_s) + migrate_bridge(config, vlan_base + [vif_s], intf) + + # Every service VLAN can have multiple customer VLANs (vif-c) + vlan_c = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c'] + if config.exists(vlan_c): + for vif_c in config.list_nodes(vlan_c): + intf = "{}.{}.{}".format(interface, vif_s, vif_c) + migrate_bridge(config, vlan_c + [vif_c], intf) diff --git a/src/migration-scripts/interfaces/1-to-2 b/src/migration-scripts/interfaces/1-to-2 new file mode 100644 index 0000000..ebf02b0 --- /dev/null +++ b/src/migration-scripts/interfaces/1-to-2 @@ -0,0 +1,59 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Change syntax of bond interface +# - move interface based bond-group to actual bond (de-nest) +# https://vyos.dev/T1614 + +from vyos.configtree import ConfigTree + +base = ['interfaces', 'bonding'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # + # move interface based bond-group to actual bond (de-nest) + # + for intf in config.list_nodes(['interfaces', 'ethernet']): + # check if bond-group exists + if config.exists(['interfaces', 'ethernet', intf, 'bond-group']): + # get configured bond interface + bond = config.return_value(['interfaces', 'ethernet', intf, 'bond-group']) + # delete old interface asigned (nested) bond group + config.delete(['interfaces', 'ethernet', intf, 'bond-group']) + # create new bond member interface + config.set(base + [bond, 'member', 'interface'], value=intf, replace=False) + + # + # some combinations were allowed in the past from a CLI perspective + # but the kernel overwrote them - remove from CLI to not confuse the users. + # In addition new consitency checks are in place so users can't repeat the + # mistake. One of those nice issues is https://vyos.dev/T532 + for bond in config.list_nodes(base): + if config.exists(base + [bond, 'arp-monitor', 'interval']) and config.exists(base + [bond, 'mode']): + mode = config.return_value(base + [bond, 'mode']) + if mode in ['802.3ad', 'transmit-load-balance', 'adaptive-load-balance']: + intvl = int(config.return_value(base + [bond, 'arp-monitor', 'interval'])) + if intvl > 0: + # this is not allowed and the linux kernel replies with: + # option arp_interval: mode dependency failed, not supported in mode 802.3ad(4) + # option arp_interval: mode dependency failed, not supported in mode balance-alb(6) + # option arp_interval: mode dependency failed, not supported in mode balance-tlb(5) + # + # so we simply disable arp_interval by setting it to 0 and miimon will take care about the link + config.set(base + [bond, 'arp-monitor', 'interval'], value='0') diff --git a/src/migration-scripts/interfaces/10-to-11 b/src/migration-scripts/interfaces/10-to-11 new file mode 100644 index 0000000..8a562f2 --- /dev/null +++ b/src/migration-scripts/interfaces/10-to-11 @@ -0,0 +1,38 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# rename WWAN (wirelessmodem) serial interface from non persistent ttyUSB2 to +# a bus like name, e.g. "usb0b1.3p1.3" + +import os + +from vyos.configtree import ConfigTree + +base = ['interfaces', 'wirelessmodem'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for wwan in config.list_nodes(base): + if config.exists(base + [wwan, 'device']): + device = config.return_value(base + [wwan, 'device']) + + for root, dirs, files in os.walk('/dev/serial/by-bus'): + for file in files: + device_file = os.path.realpath(os.path.join(root, file)) + if os.path.basename(device_file) == device: + config.set(base + [wwan, 'device'], value=file, replace=True) diff --git a/src/migration-scripts/interfaces/11-to-12 b/src/migration-scripts/interfaces/11-to-12 new file mode 100644 index 0000000..132cecb --- /dev/null +++ b/src/migration-scripts/interfaces/11-to-12 @@ -0,0 +1,39 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - rename 'dhcpv6-options prefix-delegation' from single node to a new tag node +# 'dhcpv6-options pd 0' +# - delete 'sla-len' from CLI - value is calculated on demand + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + for type in config.list_nodes(['interfaces']): + for interface in config.list_nodes(['interfaces', type]): + # cache current config tree + base_path = ['interfaces', type, interface, 'dhcpv6-options'] + old_base = base_path + ['prefix-delegation'] + new_base = base_path + ['pd'] + if config.exists(old_base): + config.set(new_base) + config.set_tag(new_base) + config.copy(old_base, new_base + ['0']) + config.delete(old_base) + + for pd in config.list_nodes(new_base): + for tmp in config.list_nodes(new_base + [pd, 'interface']): + sla_config = new_base + [pd, 'interface', tmp, 'sla-len'] + if config.exists(sla_config): + config.delete(sla_config) diff --git a/src/migration-scripts/interfaces/12-to-13 b/src/migration-scripts/interfaces/12-to-13 new file mode 100644 index 0000000..585deb8 --- /dev/null +++ b/src/migration-scripts/interfaces/12-to-13 @@ -0,0 +1,51 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - T2903: Change vif-s ethertype from numeric number to literal +# - 0x88a8 -> 802.1ad +# - 0x8100 -> 802.1q +# - T2905: Change WWAN "ondemand" node to "connect-on-demand" to have identical +# CLI nodes for both types of dialer interfaces + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + # + # T2903 + # + for type in config.list_nodes(['interfaces']): + for interface in config.list_nodes(['interfaces', type]): + if not config.exists(['interfaces', type, interface, 'vif-s']): + continue + + for vif_s in config.list_nodes(['interfaces', type, interface, 'vif-s']): + base_path = ['interfaces', type, interface, 'vif-s', vif_s] + if config.exists(base_path + ['ethertype']): + protocol = '802.1ad' + tmp = config.return_value(base_path + ['ethertype']) + if tmp == '0x8100': + protocol = '802.1q' + + config.set(base_path + ['protocol'], value=protocol) + config.delete(base_path + ['ethertype']) + + # + # T2905 + # + wwan_base = ['interfaces', 'wirelessmodem'] + if config.exists(wwan_base): + for interface in config.list_nodes(wwan_base): + if config.exists(wwan_base + [interface, 'ondemand']): + config.rename(wwan_base + [interface, 'ondemand'], 'connect-on-demand') diff --git a/src/migration-scripts/interfaces/13-to-14 b/src/migration-scripts/interfaces/13-to-14 new file mode 100644 index 0000000..45d8e3b --- /dev/null +++ b/src/migration-scripts/interfaces/13-to-14 @@ -0,0 +1,42 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3043: rename Wireless interface security mode 'both' to 'wpa+wpa2' +# T3043: move "system wifi-regulatory-domain" to indicidual wireless interface + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + base = ['interfaces', 'wireless'] + + if not config.exists(base): + # Nothing to do + return + + country_code = '' + cc_cli = ['system', 'wifi-regulatory-domain'] + if config.exists(cc_cli): + country_code = config.return_value(cc_cli) + config.delete(cc_cli) + + for wifi in config.list_nodes(base): + sec_mode = base + [wifi, 'security', 'wpa', 'mode'] + if config.exists(sec_mode): + mode = config.return_value(sec_mode) + if mode == 'both': + config.set(sec_mode, value='wpa+wpa2', replace=True) + + if country_code: + config.set(base + [wifi, 'country-code'], value=country_code) diff --git a/src/migration-scripts/interfaces/14-to-15 b/src/migration-scripts/interfaces/14-to-15 new file mode 100644 index 0000000..d45d59b --- /dev/null +++ b/src/migration-scripts/interfaces/14-to-15 @@ -0,0 +1,38 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3048: remove smp-affinity node from ethernet and use tuned instead + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + base = ['interfaces', 'ethernet'] + + if not config.exists(base): + # Nothing to do + return + + migrate = False + for interface in config.list_nodes(base): + smp_base = base + [interface, 'smp-affinity'] + # if any one interface had smp-affinity configured manually, we will + # configure "system option performance" + if config.exists(smp_base): + if config.return_value(smp_base) != 'auto': + migrate = True + config.delete(smp_base) + + if migrate: + config.set(['system', 'options', 'performance'], value='throughput') diff --git a/src/migration-scripts/interfaces/15-to-16 b/src/migration-scripts/interfaces/15-to-16 new file mode 100644 index 0000000..c9abdb5 --- /dev/null +++ b/src/migration-scripts/interfaces/15-to-16 @@ -0,0 +1,30 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# remove pppoe "ipv6 enable" option + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + base = ['interfaces', 'pppoe'] + + if not config.exists(base): + # Nothing to do + return + + for interface in config.list_nodes(base): + ipv6_enable = base + [interface, 'ipv6', 'enable'] + if config.exists(ipv6_enable): + config.delete(ipv6_enable) diff --git a/src/migration-scripts/interfaces/16-to-17 b/src/migration-scripts/interfaces/16-to-17 new file mode 100644 index 0000000..7d241ac --- /dev/null +++ b/src/migration-scripts/interfaces/16-to-17 @@ -0,0 +1,33 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Command line migration of port mirroring +# https://vyos.dev/T3089 + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + base = ['interfaces', 'ethernet'] + if not config.exists(base): + # Nothing to do + return + + for interface in config.list_nodes(base): + mirror_old_base = base + [interface, 'mirror'] + if config.exists(mirror_old_base): + intf = config.return_values(mirror_old_base) + if config.exists(mirror_old_base): + config.delete(mirror_old_base) + config.set(mirror_old_base + ['ingress'],intf[0]) diff --git a/src/migration-scripts/interfaces/17-to-18 b/src/migration-scripts/interfaces/17-to-18 new file mode 100644 index 0000000..f45695a --- /dev/null +++ b/src/migration-scripts/interfaces/17-to-18 @@ -0,0 +1,52 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3043: Move "system wifi-regulatory-domain" to indicidual wireless interface. +# Country Code will be migratred from upper to lower case. +# T3140: Relax ethernet interface offload-options + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + # T3140: Cleanup ethernet offload-options, remove on/off value and use + # valueless nodes instead. + eth_base = ['interfaces', 'ethernet'] + if config.exists(eth_base): + for eth in config.list_nodes(eth_base): + offload = eth_base + [eth, 'offload-options'] + if config.exists(offload): + mapping = { + 'generic-receive' : 'gro', + 'generic-segmentation' : 'gso', + 'scatter-gather' : 'sg', + 'tcp-segmentation' : 'tso', + 'udp-fragmentation' : 'ufo', + } + for k, v in mapping.items(): + if config.exists(offload + [k]): + tmp = config.return_value(offload + [k]) + if tmp == 'on': + config.set(eth_base + [eth, 'offload', v]) + + config.delete(offload) + + # T3043: WIFI country-code should be lower-case + wifi_base = ['interfaces', 'wireless'] + if config.exists(wifi_base): + for wifi in config.list_nodes(wifi_base): + ccode = wifi_base + [wifi, 'country-code'] + if config.exists(ccode): + tmp = config.return_value(ccode) + config.set(ccode, value=tmp.lower(), replace=True) diff --git a/src/migration-scripts/interfaces/18-to-19 b/src/migration-scripts/interfaces/18-to-19 new file mode 100644 index 0000000..ae1a07a --- /dev/null +++ b/src/migration-scripts/interfaces/18-to-19 @@ -0,0 +1,86 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os + +from vyos.configtree import ConfigTree + +def replace_nat_interfaces(config, old, new): + if not config.exists(['nat']): + return + for direction in ['destination', 'source']: + conf_direction = ['nat', direction, 'rule'] + if not config.exists(conf_direction): + return + for rule in config.list_nodes(conf_direction): + conf_rule = conf_direction + [rule] + if config.exists(conf_rule + ['inbound-interface']): + tmp = config.return_value(conf_rule + ['inbound-interface']) + if tmp == old: + config.set(conf_rule + ['inbound-interface'], value=new) + if config.exists(conf_rule + ['outbound-interface']): + tmp = config.return_value(conf_rule + ['outbound-interface']) + if tmp == old: + config.set(conf_rule + ['outbound-interface'], value=new) + + +def migrate(config: ConfigTree) -> None: + base = ['interfaces', 'wirelessmodem'] + if not config.exists(base): + # Nothing to do + return + + new_base = ['interfaces', 'wwan'] + config.set(new_base) + config.set_tag(new_base) + for old_interface in config.list_nodes(base): + # convert usb0b1.3p1.2 device identifier and extract 1.3 usb bus id + usb = config.return_value(base + [old_interface, 'device']) + device = usb.split('b')[-1] + busid = device.split('p')[0] + for new_interface in os.listdir('/sys/class/net'): + # we are only interested in interfaces starting with wwan + if not new_interface.startswith('wwan'): + continue + device = os.readlink(f'/sys/class/net/{new_interface}/device') + device = device.split(':')[0] + if busid in device: + config.copy(base + [old_interface], new_base + [new_interface]) + replace_nat_interfaces(config, old_interface, new_interface) + + config.delete(base) + + # Now that we have copied the old wirelessmodem interfaces to wwan + # we can start to migrate also individual config items. + for interface in config.list_nodes(new_base): + # we do no longer need the USB device name + config.delete(new_base + [interface, 'device']) + # set/unset DNS configuration + dns = new_base + [interface, 'no-peer-dns'] + if config.exists(dns): + config.delete(dns) + else: + config.set(['system', 'name-servers-dhcp'], value=interface, replace=False) + + # Backup distance is now handled by DHCP option "default-route-distance" + distance = dns = new_base + [interface, 'backup', 'distance'] + old_default_distance = '10' + if config.exists(distance): + old_default_distance = config.return_value(distance) + config.delete(distance) + config.set(new_base + [interface, 'dhcp-options', 'default-route-distance'], value=old_default_distance) + + # the new wwan interface use regular IP addressing + config.set(new_base + [interface, 'address'], value='dhcp') diff --git a/src/migration-scripts/interfaces/19-to-20 b/src/migration-scripts/interfaces/19-to-20 new file mode 100644 index 0000000..7ee6302 --- /dev/null +++ b/src/migration-scripts/interfaces/19-to-20 @@ -0,0 +1,41 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + for type in ['tunnel', 'l2tpv3']: + base = ['interfaces', type] + if not config.exists(base): + # Nothing to do + continue + + for interface in config.list_nodes(base): + # Migrate "interface tunnel <tunX> encapsulation gre-bridge" to gretap + encap_path = base + [interface, 'encapsulation'] + if type == 'tunnel' and config.exists(encap_path): + tmp = config.return_value(encap_path) + if tmp == 'gre-bridge': + config.set(encap_path, value='gretap') + + # Migrate "interface tunnel|l2tpv3 <interface> local-ip" to source-address + # Migrate "interface tunnel|l2tpv3 <interface> remote-ip" to remote + local_ip_path = base + [interface, 'local-ip'] + if config.exists(local_ip_path): + config.rename(local_ip_path, 'source-address') + + remote_ip_path = base + [interface, 'remote-ip'] + if config.exists(remote_ip_path): + config.rename(remote_ip_path, 'remote') diff --git a/src/migration-scripts/interfaces/2-to-3 b/src/migration-scripts/interfaces/2-to-3 new file mode 100644 index 0000000..695dcbf --- /dev/null +++ b/src/migration-scripts/interfaces/2-to-3 @@ -0,0 +1,40 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Change syntax of openvpn encryption settings +# - move cipher from encryption to encryption cipher +# https://vyos.dev/T1704 + +from vyos.configtree import ConfigTree + +base = ['interfaces', 'openvpn'] + +def migrate(config: ConfigTree) -> None: + + if not config.exists(base): + # Nothing to do + return + # + # move cipher from "encryption" to "encryption cipher" + # + for intf in config.list_nodes(['interfaces', 'openvpn']): + # Check if encryption is set + if config.exists(['interfaces', 'openvpn', intf, 'encryption']): + # Get cipher used + cipher = config.return_value(['interfaces', 'openvpn', intf, 'encryption']) + # Delete old syntax + config.delete(['interfaces', 'openvpn', intf, 'encryption']) + # Add new syntax to config + config.set(['interfaces', 'openvpn', intf, 'encryption', 'cipher'], value=cipher) diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21 new file mode 100644 index 0000000..0b68951 --- /dev/null +++ b/src/migration-scripts/interfaces/20-to-21 @@ -0,0 +1,107 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3619: mirror Linux Kernel defaults for ethernet offloading options into VyOS +# CLI. See https://vyos.dev/T3619#102254 for all the details. +# T3787: Remove deprecated UDP fragmentation offloading option + +from vyos.ethtool import Ethtool +from vyos.configtree import ConfigTree +from vyos.utils.network import interface_exists + +base = ['interfaces', 'ethernet'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + for ifname in config.list_nodes(base): + # Bail out early if interface vanished from system + if not interface_exists(ifname): + continue + + eth = Ethtool(ifname) + + # If GRO is enabled by the Kernel - we reflect this on the CLI. If GRO is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'gro']) + enabled, fixed = eth.get_generic_receive_offload() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'gro']) + elif enabled and not fixed: + config.set(base + [ifname, 'offload', 'gro']) + + # If GSO is enabled by the Kernel - we reflect this on the CLI. If GSO is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'gso']) + enabled, fixed = eth.get_generic_segmentation_offload() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'gso']) + elif enabled and not fixed: + config.set(base + [ifname, 'offload', 'gso']) + + # If LRO is enabled by the Kernel - we reflect this on the CLI. If LRO is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'lro']) + enabled, fixed = eth.get_large_receive_offload() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'lro']) + elif enabled and not fixed: + config.set(base + [ifname, 'offload', 'lro']) + + # If SG is enabled by the Kernel - we reflect this on the CLI. If SG is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'sg']) + enabled, fixed = eth.get_scatter_gather() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'sg']) + elif enabled and not fixed: + config.set(base + [ifname, 'offload', 'sg']) + + # If TSO is enabled by the Kernel - we reflect this on the CLI. If TSO is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'tso']) + enabled, fixed = eth.get_tcp_segmentation_offload() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'tso']) + elif enabled and not fixed: + config.set(base + [ifname, 'offload', 'tso']) + + # Remove deprecated UDP fragmentation offloading option + if config.exists(base + [ifname, 'offload', 'ufo']): + config.delete(base + [ifname, 'offload', 'ufo']) + + # Also while processing the interface configuration, not all adapters support + # changing the speed and duplex settings. If the desired speed and duplex + # values do not work for the NIC driver, we change them back to the default + # value of "auto" - which will be applied if the CLI node is deleted. + speed_path = base + [ifname, 'speed'] + duplex_path = base + [ifname, 'duplex'] + # speed and duplex must always be set at the same time if not set to "auto" + if config.exists(speed_path) and config.exists(duplex_path): + speed = config.return_value(speed_path) + duplex = config.return_value(duplex_path) + if speed != 'auto' and duplex != 'auto': + if not eth.check_speed_duplex(speed, duplex): + config.delete(speed_path) + config.delete(duplex_path) + + # Also while processing the interface configuration, not all adapters support + # changing disabling flow-control - or change this setting. If disabling + # flow-control is not supported by the NIC, we remove the setting from CLI + flow_control_path = base + [ifname, 'disable-flow-control'] + if config.exists(flow_control_path): + if not eth.check_flow_control(): + config.delete(flow_control_path) diff --git a/src/migration-scripts/interfaces/21-to-22 b/src/migration-scripts/interfaces/21-to-22 new file mode 100644 index 0000000..046eb10 --- /dev/null +++ b/src/migration-scripts/interfaces/21-to-22 @@ -0,0 +1,29 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + base = ['interfaces', 'tunnel'] + + if not config.exists(base): + return + + for interface in config.list_nodes(base): + path = base + [interface, 'dhcp-interface'] + if config.exists(path): + tmp = config.return_value(path) + config.delete(path) + config.set(base + [interface, 'source-interface'], value=tmp) diff --git a/src/migration-scripts/interfaces/22-to-23 b/src/migration-scripts/interfaces/22-to-23 new file mode 100644 index 0000000..31f7fa2 --- /dev/null +++ b/src/migration-scripts/interfaces/22-to-23 @@ -0,0 +1,40 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Deletes Wireguard peers if they have the same public key as the router has. + +from vyos.configtree import ConfigTree +from vyos.utils.network import is_wireguard_key_pair + +def migrate(config: ConfigTree) -> None: + base = ['interfaces', 'wireguard'] + if not config.exists(base): + # Nothing to do + return + + for interface in config.list_nodes(base): + if not config.exists(base + [interface, 'private-key']): + continue + private_key = config.return_value(base + [interface, 'private-key']) + interface_base = base + [interface] + if config.exists(interface_base + ['peer']): + for peer in config.list_nodes(interface_base + ['peer']): + peer_base = interface_base + ['peer', peer] + if not config.exists(peer_base + ['public-key']): + continue + peer_public_key = config.return_value(peer_base + ['public-key']) + if not config.exists(peer_base + ['disable']) \ + and is_wireguard_key_pair(private_key, peer_public_key): + config.set(peer_base + ['disable']) diff --git a/src/migration-scripts/interfaces/23-to-24 b/src/migration-scripts/interfaces/23-to-24 new file mode 100644 index 0000000..b72ceee --- /dev/null +++ b/src/migration-scripts/interfaces/23-to-24 @@ -0,0 +1,125 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configtree import ConfigTree + +def migrate_ospf(config, path, interface): + path = path + ['ospf'] + if config.exists(path): + new_base = ['protocols', 'ospf', 'interface'] + config.set(new_base) + config.set_tag(new_base) + config.copy(path, new_base + [interface]) + config.delete(path) + + # if "ip ospf" was the only setting, we can clean out the empty + # ip node afterwards + if len(config.list_nodes(path[:-1])) == 0: + config.delete(path[:-1]) + +def migrate_ospfv3(config, path, interface): + path = path + ['ospfv3'] + if config.exists(path): + new_base = ['protocols', 'ospfv3', 'interface'] + config.set(new_base) + config.set_tag(new_base) + config.copy(path, new_base + [interface]) + config.delete(path) + + # if "ipv6 ospfv3" was the only setting, we can clean out the empty + # ip node afterwards + if len(config.list_nodes(path[:-1])) == 0: + config.delete(path[:-1]) + +def migrate_rip(config, path, interface): + path = path + ['rip'] + if config.exists(path): + new_base = ['protocols', 'rip', 'interface'] + config.set(new_base) + config.set_tag(new_base) + config.copy(path, new_base + [interface]) + config.delete(path) + + # if "ip rip" was the only setting, we can clean out the empty + # ip node afterwards + if len(config.list_nodes(path[:-1])) == 0: + config.delete(path[:-1]) + +def migrate_ripng(config, path, interface): + path = path + ['ripng'] + if config.exists(path): + new_base = ['protocols', 'ripng', 'interface'] + config.set(new_base) + config.set_tag(new_base) + config.copy(path, new_base + [interface]) + config.delete(path) + + # if "ipv6 ripng" was the only setting, we can clean out the empty + # ip node afterwards + if len(config.list_nodes(path[:-1])) == 0: + config.delete(path[:-1]) + +def migrate(config: ConfigTree) -> None: + # + # Migrate "interface ethernet eth0 ip ospf" to "protocols ospf interface eth0" + # + for type in config.list_nodes(['interfaces']): + for interface in config.list_nodes(['interfaces', type]): + ip_base = ['interfaces', type, interface, 'ip'] + ipv6_base = ['interfaces', type, interface, 'ipv6'] + migrate_rip(config, ip_base, interface) + migrate_ripng(config, ipv6_base, interface) + migrate_ospf(config, ip_base, interface) + migrate_ospfv3(config, ipv6_base, interface) + + vif_path = ['interfaces', type, interface, 'vif'] + if config.exists(vif_path): + for vif in config.list_nodes(vif_path): + vif_ip_base = vif_path + [vif, 'ip'] + vif_ipv6_base = vif_path + [vif, 'ipv6'] + ifname = f'{interface}.{vif}' + + migrate_rip(config, vif_ip_base, ifname) + migrate_ripng(config, vif_ipv6_base, ifname) + migrate_ospf(config, vif_ip_base, ifname) + migrate_ospfv3(config, vif_ipv6_base, ifname) + + + vif_s_path = ['interfaces', type, interface, 'vif-s'] + if config.exists(vif_s_path): + for vif_s in config.list_nodes(vif_s_path): + vif_s_ip_base = vif_s_path + [vif_s, 'ip'] + vif_s_ipv6_base = vif_s_path + [vif_s, 'ipv6'] + + # vif-c interfaces MUST be migrated before their parent vif-s + # interface as the migrate_*() functions delete the path! + vif_c_path = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c'] + if config.exists(vif_c_path): + for vif_c in config.list_nodes(vif_c_path): + vif_c_ip_base = vif_c_path + [vif_c, 'ip'] + vif_c_ipv6_base = vif_c_path + [vif_c, 'ipv6'] + ifname = f'{interface}.{vif_s}.{vif_c}' + + migrate_rip(config, vif_c_ip_base, ifname) + migrate_ripng(config, vif_c_ipv6_base, ifname) + migrate_ospf(config, vif_c_ip_base, ifname) + migrate_ospfv3(config, vif_c_ipv6_base, ifname) + + + ifname = f'{interface}.{vif_s}' + migrate_rip(config, vif_s_ip_base, ifname) + migrate_ripng(config, vif_s_ipv6_base, ifname) + migrate_ospf(config, vif_s_ip_base, ifname) + migrate_ospfv3(config, vif_s_ipv6_base, ifname) diff --git a/src/migration-scripts/interfaces/24-to-25 b/src/migration-scripts/interfaces/24-to-25 new file mode 100644 index 0000000..9f8cc80 --- /dev/null +++ b/src/migration-scripts/interfaces/24-to-25 @@ -0,0 +1,41 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# A VTI interface also requires an IPSec configuration - VyOS 1.2 supported +# having a VTI interface in the CLI but no IPSec configuration - drop VTI +# configuration if this is the case for VyOS 1.4 + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + base = ['interfaces', 'vti'] + if not config.exists(base): + # Nothing to do + return + + ipsec_base = ['vpn', 'ipsec', 'site-to-site', 'peer'] + for interface in config.list_nodes(base): + found = False + if config.exists(ipsec_base): + for peer in config.list_nodes(ipsec_base): + if config.exists(ipsec_base + [peer, 'vti', 'bind']): + tmp = config.return_value(ipsec_base + [peer, 'vti', 'bind']) + if tmp == interface: + # Interface was found and we no longer need to search + # for it in our IPSec peers + found = True + break + if not found: + config.delete(base + [interface]) diff --git a/src/migration-scripts/interfaces/25-to-26 b/src/migration-scripts/interfaces/25-to-26 new file mode 100644 index 0000000..7a4032d --- /dev/null +++ b/src/migration-scripts/interfaces/25-to-26 @@ -0,0 +1,368 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Migrate Wireguard to store keys in CLI +# Migrate EAPoL to PKI configuration + +import os + +from vyos.configtree import ConfigTree +from vyos.pki import CERT_BEGIN +from vyos.pki import load_certificate +from vyos.pki import load_crl +from vyos.pki import load_dh_parameters +from vyos.pki import load_private_key +from vyos.pki import encode_certificate +from vyos.pki import encode_dh_parameters +from vyos.pki import encode_private_key +from vyos.pki import verify_crl +from vyos.utils.process import run + +def wrapped_pem_to_config_value(pem): + out = [] + for line in pem.strip().split("\n"): + if not line or line.startswith("-----") or line[0] == '#': + continue + out.append(line) + return "".join(out) + +def read_file_for_pki(config_auth_path): + full_path = os.path.join(AUTH_DIR, config_auth_path) + output = None + + if os.path.isfile(full_path): + if not os.access(full_path, os.R_OK): + run(f'sudo chmod 644 {full_path}') + + with open(full_path, 'r') as f: + output = f.read() + + return output + +AUTH_DIR = '/config/auth' +pki_base = ['pki'] + +def migrate(config: ConfigTree) -> None: + # OpenVPN + base = ['interfaces', 'openvpn'] + + if config.exists(base): + for interface in config.list_nodes(base): + x509_base = base + [interface, 'tls'] + pki_name = f'openvpn_{interface}' + + if config.exists(base + [interface, 'shared-secret-key-file']): + if not config.exists(pki_base + ['openvpn', 'shared-secret']): + config.set(pki_base + ['openvpn', 'shared-secret']) + config.set_tag(pki_base + ['openvpn', 'shared-secret']) + + key_file = config.return_value(base + [interface, 'shared-secret-key-file']) + key = read_file_for_pki(key_file) + key_pki_name = f'{pki_name}_shared' + + if key: + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') + config.set(base + [interface, 'shared-secret-key'], value=key_pki_name) + else: + print(f'Failed to migrate shared-secret-key on openvpn interface {interface}') + + config.delete(base + [interface, 'shared-secret-key-file']) + + if not config.exists(base + [interface, 'tls']): + continue + + if config.exists(base + [interface, 'tls', 'auth-file']): + if not config.exists(pki_base + ['openvpn', 'shared-secret']): + config.set(pki_base + ['openvpn', 'shared-secret']) + config.set_tag(pki_base + ['openvpn', 'shared-secret']) + + key_file = config.return_value(base + [interface, 'tls', 'auth-file']) + key = read_file_for_pki(key_file) + key_pki_name = f'{pki_name}_auth' + + if key: + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') + config.set(base + [interface, 'tls', 'auth-key'], value=key_pki_name) + else: + print(f'Failed to migrate auth-key on openvpn interface {interface}') + + config.delete(base + [interface, 'tls', 'auth-file']) + + if config.exists(base + [interface, 'tls', 'crypt-file']): + if not config.exists(pki_base + ['openvpn', 'shared-secret']): + config.set(pki_base + ['openvpn', 'shared-secret']) + config.set_tag(pki_base + ['openvpn', 'shared-secret']) + + key_file = config.return_value(base + [interface, 'tls', 'crypt-file']) + key = read_file_for_pki(key_file) + key_pki_name = f'{pki_name}_crypt' + + if key: + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') + config.set(base + [interface, 'tls', 'crypt-key'], value=key_pki_name) + else: + print(f'Failed to migrate crypt-key on openvpn interface {interface}') + + config.delete(base + [interface, 'tls', 'crypt-file']) + + ca_certs = {} + + if config.exists(x509_base + ['ca-cert-file']): + if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + cert_file = config.return_value(x509_base + ['ca-cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + certs_str = f.read() + certs_data = certs_str.split(CERT_BEGIN) + index = 1 + for cert_data in certs_data[1:]: + cert = load_certificate(CERT_BEGIN + cert_data, wrap_tags=False) + + if cert: + ca_certs[f'{pki_name}_{index}'] = cert + cert_pem = encode_certificate(cert) + config.set(pki_base + ['ca', f'{pki_name}_{index}', 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['ca-certificate'], value=f'{pki_name}_{index}', replace=False) + else: + print(f'Failed to migrate CA certificate on openvpn interface {interface}') + + index += 1 + else: + print(f'Failed to migrate CA certificate on openvpn interface {interface}') + + config.delete(x509_base + ['ca-cert-file']) + + if config.exists(x509_base + ['crl-file']): + if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + crl_file = config.return_value(x509_base + ['crl-file']) + crl_path = os.path.join(AUTH_DIR, crl_file) + crl = None + crl_ca_name = None + + if os.path.isfile(crl_path): + if not os.access(crl_path, os.R_OK): + run(f'sudo chmod 644 {crl_path}') + + with open(crl_path, 'r') as f: + crl_data = f.read() + crl = load_crl(crl_data, wrap_tags=False) + + for ca_name, ca_cert in ca_certs.items(): + if verify_crl(crl, ca_cert): + crl_ca_name = ca_name + break + + if crl and crl_ca_name: + crl_pem = encode_certificate(crl) + config.set(pki_base + ['ca', crl_ca_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem)) + else: + print(f'Failed to migrate CRL on openvpn interface {interface}') + + config.delete(x509_base + ['crl-file']) + + if config.exists(x509_base + ['cert-file']): + if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + + cert_file = config.return_value(x509_base + ['cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['certificate'], value=pki_name) + else: + print(f'Failed to migrate certificate on openvpn interface {interface}') + + config.delete(x509_base + ['cert-file']) + + if config.exists(x509_base + ['key-file']): + key_file = config.return_value(x509_base + ['key-file']) + key_path = os.path.join(AUTH_DIR, key_file) + key = None + + if os.path.isfile(key_path): + if not os.access(key_path, os.R_OK): + run(f'sudo chmod 644 {key_path}') + + with open(key_path, 'r') as f: + key_data = f.read() + key = load_private_key(key_data, passphrase=None, wrap_tags=False) + + if key: + key_pem = encode_private_key(key, passphrase=None) + config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + else: + print(f'Failed to migrate private key on openvpn interface {interface}') + + config.delete(x509_base + ['key-file']) + + if config.exists(x509_base + ['dh-file']): + if not config.exists(pki_base + ['dh']): + config.set(pki_base + ['dh']) + config.set_tag(pki_base + ['dh']) + + dh_file = config.return_value(x509_base + ['dh-file']) + dh_path = os.path.join(AUTH_DIR, dh_file) + dh = None + + if os.path.isfile(dh_path): + if not os.access(dh_path, os.R_OK): + run(f'sudo chmod 644 {dh_path}') + + with open(dh_path, 'r') as f: + dh_data = f.read() + dh = load_dh_parameters(dh_data, wrap_tags=False) + + if dh: + dh_pem = encode_dh_parameters(dh) + config.set(pki_base + ['dh', pki_name, 'parameters'], value=wrapped_pem_to_config_value(dh_pem)) + config.set(x509_base + ['dh-params'], value=pki_name) + else: + print(f'Failed to migrate DH parameters on openvpn interface {interface}') + + config.delete(x509_base + ['dh-file']) + + # Wireguard + base = ['interfaces', 'wireguard'] + + if config.exists(base): + for interface in config.list_nodes(base): + private_key_path = base + [interface, 'private-key'] + + key_file = 'default' + if config.exists(private_key_path): + key_file = config.return_value(private_key_path) + + full_key_path = f'/config/auth/wireguard/{key_file}/private.key' + + if not os.path.exists(full_key_path): + print(f'Could not find wireguard private key for migration on interface "{interface}"') + continue + + with open(full_key_path, 'r') as f: + key_data = f.read().strip() + config.set(private_key_path, value=key_data) + + for peer in config.list_nodes(base + [interface, 'peer']): + config.rename(base + [interface, 'peer', peer, 'pubkey'], 'public-key') + + # Ethernet EAPoL + base = ['interfaces', 'ethernet'] + + if config.exists(base): + for interface in config.list_nodes(base): + if not config.exists(base + [interface, 'eapol']): + continue + + x509_base = base + [interface, 'eapol'] + pki_name = f'eapol_{interface}' + + if config.exists(x509_base + ['ca-cert-file']): + if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + cert_file = config.return_value(x509_base + ['ca-cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['ca-certificate'], value=pki_name) + else: + print(f'Failed to migrate CA certificate on eapol config for interface {interface}') + + config.delete(x509_base + ['ca-cert-file']) + + if config.exists(x509_base + ['cert-file']): + if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + + cert_file = config.return_value(x509_base + ['cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['certificate'], value=pki_name) + else: + print(f'Failed to migrate certificate on eapol config for interface {interface}') + + config.delete(x509_base + ['cert-file']) + + if config.exists(x509_base + ['key-file']): + key_file = config.return_value(x509_base + ['key-file']) + key_path = os.path.join(AUTH_DIR, key_file) + key = None + + if os.path.isfile(key_path): + if not os.access(key_path, os.R_OK): + run(f'sudo chmod 644 {key_path}') + + with open(key_path, 'r') as f: + key_data = f.read() + key = load_private_key(key_data, passphrase=None, wrap_tags=False) + + if key: + key_pem = encode_private_key(key, passphrase=None) + config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + else: + print(f'Failed to migrate private key on eapol config for interface {interface}') + + config.delete(x509_base + ['key-file']) diff --git a/src/migration-scripts/interfaces/26-to-27 b/src/migration-scripts/interfaces/26-to-27 new file mode 100644 index 0000000..3f58de0 --- /dev/null +++ b/src/migration-scripts/interfaces/26-to-27 @@ -0,0 +1,35 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T4384: pppoe: replace default-route CLI option with common CLI nodes already +# present for DHCP + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + base = ['interfaces', 'pppoe'] + + if not config.exists(base): + return + + for ifname in config.list_nodes(base): + tmp_config = base + [ifname, 'default-route'] + if config.exists(tmp_config): + # Retrieve current config value + value = config.return_value(tmp_config) + # Delete old Config node + config.delete(tmp_config) + if value == 'none': + config.set(base + [ifname, 'no-default-route']) diff --git a/src/migration-scripts/interfaces/27-to-28 b/src/migration-scripts/interfaces/27-to-28 new file mode 100644 index 0000000..eb9363e --- /dev/null +++ b/src/migration-scripts/interfaces/27-to-28 @@ -0,0 +1,29 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T4995: pppoe, wwan, sstpc-client rename "authentication user" CLI node +# to "authentication username" + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + for type in ['pppoe', 'sstpc-client', 'wwam']: + base = ['interfaces', type] + if not config.exists(base): + continue + for interface in config.list_nodes(base): + auth_base = base + [interface, 'authentication', 'user'] + if config.exists(auth_base): + config.rename(auth_base, 'username') diff --git a/src/migration-scripts/interfaces/28-to-29 b/src/migration-scripts/interfaces/28-to-29 new file mode 100644 index 0000000..886d49e --- /dev/null +++ b/src/migration-scripts/interfaces/28-to-29 @@ -0,0 +1,35 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5034: tunnel: rename "multicast enable" CLI node to "enable-multicast" +# valueless node. + +from vyos.configtree import ConfigTree + +base = ['interfaces', 'tunnel'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + for ifname in config.list_nodes(base): + multicast_base = base + [ifname, 'multicast'] + if config.exists(multicast_base): + tmp = config.return_value(multicast_base) + print(tmp) + # Delete old Config node + config.delete(multicast_base) + if tmp == 'enable': + config.set(base + [ifname, 'enable-multicast']) diff --git a/src/migration-scripts/interfaces/29-to-30 b/src/migration-scripts/interfaces/29-to-30 new file mode 100644 index 0000000..7b32d87 --- /dev/null +++ b/src/migration-scripts/interfaces/29-to-30 @@ -0,0 +1,30 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5286: remove XDP support in favour of VPP + +from vyos.configtree import ConfigTree + +supports_xdp = ['bonding', 'ethernet'] + +def migrate(config: ConfigTree) -> None: + for if_type in supports_xdp: + base = ['interfaces', if_type] + if not config.exists(base): + continue + for interface in config.list_nodes(base): + if_base = base + [interface] + if config.exists(if_base + ['xdp']): + config.delete(if_base + ['xdp']) diff --git a/src/migration-scripts/interfaces/3-to-4 b/src/migration-scripts/interfaces/3-to-4 new file mode 100644 index 0000000..4e56200 --- /dev/null +++ b/src/migration-scripts/interfaces/3-to-4 @@ -0,0 +1,93 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Change syntax of wireless interfaces +# Migrate boolean nodes to valueless + +from vyos.configtree import ConfigTree + +base = ['interfaces', 'wireless'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for wifi in config.list_nodes(base): + # as converting a node to bool is always the same, we can script it + to_bool_nodes = ['capabilities ht 40MHz-incapable', + 'capabilities ht auto-powersave', + 'capabilities ht delayed-block-ack', + 'capabilities ht dsss-cck-40', + 'capabilities ht greenfield', + 'capabilities ht ldpc', + 'capabilities ht lsig-protection', + 'capabilities ht stbc tx', + 'capabilities require-ht', + 'capabilities require-vht', + 'capabilities vht antenna-pattern-fixed', + 'capabilities vht ldpc', + 'capabilities vht stbc tx', + 'capabilities vht tx-powersave', + 'capabilities vht vht-cf', + 'expunge-failing-stations', + 'isolate-stations'] + + for node in to_bool_nodes: + if config.exists(base + [wifi, node]): + tmp = config.return_value(base + [wifi, node]) + # delete old node + config.delete(base + [wifi, node]) + # set new node if it was enabled + if tmp == 'true': + # OLD CLI used camel casing in 40MHz-incapable which is + # not supported in the new backend. Convert all to lower-case + config.set(base + [wifi, node.lower()]) + + # Remove debug node + if config.exists(base + [wifi, 'debug']): + config.delete(base + [wifi, 'debug']) + + # RADIUS servers + if config.exists(base + [wifi, 'security', 'wpa', 'radius-server']): + for server in config.list_nodes(base + [wifi, 'security', 'wpa', 'radius-server']): + base_server = base + [wifi, 'security', 'wpa', 'radius-server', server] + + # Migrate RADIUS shared secret + if config.exists(base_server + ['secret']): + key = config.return_value(base_server + ['secret']) + # write new configuration node + config.set(base + [wifi, 'security', 'wpa', 'radius', 'server', server, 'key'], value=key) + # format as tag node + config.set_tag(base + [wifi, 'security', 'wpa', 'radius', 'server']) + + # Migrate RADIUS port + if config.exists(base_server + ['port']): + port = config.return_value(base_server + ['port']) + # write new configuration node + config.set(base + [wifi, 'security', 'wpa', 'radius', 'server', server, 'port'], value=port) + # format as tag node + config.set_tag(base + [wifi, 'security', 'wpa', 'radius', 'server']) + + # Migrate RADIUS accounting + if config.exists(base_server + ['accounting']): + port = config.return_value(base_server + ['accounting']) + # write new configuration node + config.set(base + [wifi, 'security', 'wpa', 'radius', 'server', server, 'accounting']) + # format as tag node + config.set_tag(base + [wifi, 'security', 'wpa', 'radius', 'server']) + + # delete old radius-server nodes + config.delete(base + [wifi, 'security', 'wpa', 'radius-server']) diff --git a/src/migration-scripts/interfaces/30-to-31 b/src/migration-scripts/interfaces/30-to-31 new file mode 100644 index 0000000..7e509dd --- /dev/null +++ b/src/migration-scripts/interfaces/30-to-31 @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5254: Fixed changing ethernet when it is a bond member + +import json +from vyos.configtree import ConfigTree +from vyos.ifconfig import EthernetIf +from vyos.ifconfig import BondIf +from vyos.utils.dict import dict_to_paths_values + +base = ['interfaces', 'bonding'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for bond in config.list_nodes(base): + member_base = base + [bond, 'member', 'interface'] + if config.exists(member_base): + for interface in config.return_values(member_base): + if_base = ['interfaces', 'ethernet', interface] + if config.exists(if_base): + config_ethernet = json.loads(config.get_subtree(if_base).to_json()) + eth_dict_paths = dict_to_paths_values(config_ethernet) + for option_path, option_value in eth_dict_paths.items(): + # If option is allowed for changing then continue + converted_path = option_path.replace('-','_') + if converted_path in EthernetIf.get_bond_member_allowed_options(): + continue + # if option is inherited from bond then continue + if converted_path in BondIf.get_inherit_bond_options(): + continue + option_path_list = option_path.split('.') + config.delete(if_base + option_path_list) + del option_path_list[-1] + # delete empty node from config + while len(option_path_list) > 0: + if config.list_nodes(if_base + option_path_list): + break + config.delete(if_base + option_path_list) + del option_path_list[-1] diff --git a/src/migration-scripts/interfaces/31-to-32 b/src/migration-scripts/interfaces/31-to-32 new file mode 100644 index 0000000..24077ed --- /dev/null +++ b/src/migration-scripts/interfaces/31-to-32 @@ -0,0 +1,37 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5671: change port to IANA assigned default port +# T5759: change default MTU 1450 -> 1500 + +from vyos.configtree import ConfigTree + +base = ['interfaces', 'vxlan'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for vxlan in config.list_nodes(base): + if config.exists(base + [vxlan, 'external']): + config.delete(base + [vxlan, 'external']) + config.set(base + [vxlan, 'parameters', 'external']) + + if not config.exists(base + [vxlan, 'port']): + config.set(base + [vxlan, 'port'], value='8472') + + if not config.exists(base + [vxlan, 'mtu']): + config.set(base + [vxlan, 'mtu'], value='1450') diff --git a/src/migration-scripts/interfaces/32-to-33 b/src/migration-scripts/interfaces/32-to-33 new file mode 100644 index 0000000..c7b1c5b --- /dev/null +++ b/src/migration-scripts/interfaces/32-to-33 @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# T6318: WiFi country-code should be set system-wide instead of per-device + +from vyos.configtree import ConfigTree + +base = ['interfaces', 'wireless'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + installed = False + for interface in config.list_nodes(base): + cc_path = base + [interface, 'country-code'] + if config.exists(cc_path): + tmp = config.return_value(cc_path) + config.delete(cc_path) + + # There can be only ONE wireless country-code per device, everything + # else makes no sense as a WIFI router can not operate in two + # different countries + if not installed: + config.set(['system', 'wireless', 'country-code'], value=tmp) + installed = True diff --git a/src/migration-scripts/interfaces/4-to-5 b/src/migration-scripts/interfaces/4-to-5 new file mode 100644 index 0000000..93fa7c3 --- /dev/null +++ b/src/migration-scripts/interfaces/4-to-5 @@ -0,0 +1,106 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# De-nest PPPoE interfaces +# Migrate boolean nodes to valueless + +from vyos.configtree import ConfigTree + +def migrate_dialer(config, tree, intf): + for pppoe in config.list_nodes(tree): + # assemble string, 0 -> pppoe0 + new_base = ['interfaces', 'pppoe'] + pppoe_base = new_base + ['pppoe' + pppoe] + config.set(new_base) + # format as tag node to avoid loading problems + config.set_tag(new_base) + + # Copy the entire old node to the new one before migrating individual + # parts + config.copy(tree + [pppoe], pppoe_base) + + # Instead of letting the user choose between auto and none + # where auto is default, it makes more sesne to just offer + # an option to disable the default behavior (declutter CLI) + if config.exists(pppoe_base + ['name-server']): + tmp = config.return_value(pppoe_base + ['name-server']) + if tmp == "none": + config.set(pppoe_base + ['no-peer-dns']) + config.delete(pppoe_base + ['name-server']) + + # Migrate user-id and password nodes under an 'authentication' + # node + if config.exists(pppoe_base + ['user-id']): + user = config.return_value(pppoe_base + ['user-id']) + config.set(pppoe_base + ['authentication', 'user'], value=user) + config.delete(pppoe_base + ['user-id']) + + if config.exists(pppoe_base + ['password']): + pwd = config.return_value(pppoe_base + ['password']) + config.set(pppoe_base + ['authentication', 'password'], value=pwd) + config.delete(pppoe_base + ['password']) + + # remove enable-ipv6 node and rather place it under ipv6 node + if config.exists(pppoe_base + ['enable-ipv6']): + config.set(pppoe_base + ['ipv6', 'enable']) + config.delete(pppoe_base + ['enable-ipv6']) + + # Source interface migration + config.set(pppoe_base + ['source-interface'], value=intf) + + # Remove IPv6 router-advert nodes as this makes no sense on a + # client diale rinterface to send RAs back into the network + # https://vyos.dev/T2055 + ipv6_ra = pppoe_base + ['ipv6', 'router-advert'] + if config.exists(ipv6_ra): + config.delete(ipv6_ra) + +def migrate(config: ConfigTree) -> None: + pppoe_links = ['bonding', 'ethernet'] + + for link_type in pppoe_links: + if not config.exists(['interfaces', link_type]): + continue + + for interface in config.list_nodes(['interfaces', link_type]): + # check if PPPoE exists + base_if = ['interfaces', link_type, interface] + pppoe_if = base_if + ['pppoe'] + if config.exists(pppoe_if): + for dialer in config.list_nodes(pppoe_if): + migrate_dialer(config, pppoe_if, interface) + + # Delete old PPPoE interface + config.delete(pppoe_if) + + # bail out early if there are no VLAN interfaces to migrate + if not config.exists(base_if + ['vif']): + continue + + # Migrate PPPoE interfaces attached to a VLAN + for vlan in config.list_nodes(base_if + ['vif']): + vlan_if = base_if + ['vif', vlan] + pppoe_if = vlan_if + ['pppoe'] + if config.exists(pppoe_if): + for dialer in config.list_nodes(pppoe_if): + intf = "{}.{}".format(interface, vlan) + migrate_dialer(config, pppoe_if, intf) + + # Delete old PPPoE interface + config.delete(pppoe_if) + + # Add interface description that this is required for PPPoE + if not config.exists(vlan_if + ['description']): + config.set(vlan_if + ['description'], value='PPPoE link interface') diff --git a/src/migration-scripts/interfaces/5-to-6 b/src/migration-scripts/interfaces/5-to-6 new file mode 100644 index 0000000..44c32ba --- /dev/null +++ b/src/migration-scripts/interfaces/5-to-6 @@ -0,0 +1,114 @@ +# Copyright 202-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Migrate IPv6 router advertisments from a nested interface configuration to +# a denested "service router-advert" + +from vyos.configtree import ConfigTree + +def copy_rtradv(c, old_base, interface): + base = ['service', 'router-advert', 'interface'] + + if c.exists(old_base): + if not c.exists(base): + c.set(base) + c.set_tag(base) + + # take the old node as a whole and copy it to new new path, + # additional migrations will be done afterwards + new_base = base + [interface] + c.copy(old_base, new_base) + c.delete(old_base) + + # cur-hop-limit has been renamed to hop-limit + if c.exists(new_base + ['cur-hop-limit']): + c.rename(new_base + ['cur-hop-limit'], 'hop-limit') + + bool_cleanup = ['managed-flag', 'other-config-flag'] + for bool in bool_cleanup: + if c.exists(new_base + [bool]): + tmp = c.return_value(new_base + [bool]) + c.delete(new_base + [bool]) + if tmp == 'true': + c.set(new_base + [bool]) + + # max/min interval moved to subnode + intervals = ['max-interval', 'min-interval'] + for interval in intervals: + if c.exists(new_base + [interval]): + tmp = c.return_value(new_base + [interval]) + c.delete(new_base + [interval]) + min_max = interval.split('-')[0] + c.set(new_base + ['interval', min_max], value=tmp) + + # cleanup boolean nodes in individual route + route_base = new_base + ['route'] + if c.exists(route_base): + for route in config.list_nodes(route_base): + if c.exists(route_base + [route, 'remove-route']): + tmp = c.return_value(route_base + [route, 'remove-route']) + c.delete(route_base + [route, 'remove-route']) + if tmp == 'false': + c.set(route_base + [route, 'no-remove-route']) + + # cleanup boolean nodes in individual prefix + prefix_base = new_base + ['prefix'] + if c.exists(prefix_base): + for prefix in config.list_nodes(prefix_base): + if c.exists(prefix_base + [prefix, 'autonomous-flag']): + tmp = c.return_value(prefix_base + [prefix, 'autonomous-flag']) + c.delete(prefix_base + [prefix, 'autonomous-flag']) + if tmp == 'false': + c.set(prefix_base + [prefix, 'no-autonomous-flag']) + + if c.exists(prefix_base + [prefix, 'on-link-flag']): + tmp = c.return_value(prefix_base + [prefix, 'on-link-flag']) + c.delete(prefix_base + [prefix, 'on-link-flag']) + if tmp == 'true': + c.set(prefix_base + [prefix, 'on-link-flag']) + + # router advertisement can be individually disabled per interface + # the node has been renamed from send-advert {true | false} to no-send-advert + if c.exists(new_base + ['send-advert']): + tmp = c.return_value(new_base + ['send-advert']) + c.delete(new_base + ['send-advert']) + if tmp == 'false': + c.set(new_base + ['no-send-advert']) + + # link-mtu advertisement was formerly disabled by setting its value to 0 + # ... this makes less sense - if it should not be send, just do not + # configure it + if c.exists(new_base + ['link-mtu']): + tmp = c.return_value(new_base + ['link-mtu']) + if tmp == '0': + c.delete(new_base + ['link-mtu']) + +def migrate(config: ConfigTree) -> None: + # list all individual interface types like dummy, ethernet and so on + for if_type in config.list_nodes(['interfaces']): + base_if_type = ['interfaces', if_type] + + # for every individual interface we need to check if there is an + # ipv6 ra configured ... and also for every VIF (VLAN) interface + for intf in config.list_nodes(base_if_type): + old_base = base_if_type + [intf, 'ipv6', 'router-advert'] + copy_rtradv(config, old_base, intf) + + vif_base = base_if_type + [intf, 'vif'] + if config.exists(vif_base): + for vif in config.list_nodes(vif_base): + old_base = vif_base + [vif, 'ipv6', 'router-advert'] + vlan_name = f'{intf}.{vif}' + copy_rtradv(config, old_base, vlan_name) diff --git a/src/migration-scripts/interfaces/6-to-7 b/src/migration-scripts/interfaces/6-to-7 new file mode 100644 index 0000000..e60121e --- /dev/null +++ b/src/migration-scripts/interfaces/6-to-7 @@ -0,0 +1,45 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Remove network provider name from CLI and rather use provider APN from CLI + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + base = ['interfaces', 'wirelessmodem'] + + if not config.exists(base): + # Nothing to do + return + + # list all individual wwan/wireless modem interfaces + for i in config.list_nodes(base): + iface = base + [i] + + # only three carries have been supported in the past, thus + # this will be fairly simple \o/ - and only one (AT&T) did + # configure an APN + if config.exists(iface + ['network']): + network = config.return_value(iface + ['network']) + if network == "att": + apn = 'isp.cingular' + config.set(iface + ['apn'], value=apn) + + config.delete(iface + ['network']) + + # synchronize DNS configuration with PPPoE interfaces to have a + # uniform CLI experience + if config.exists(iface + ['no-dns']): + config.rename(iface + ['no-dns'], 'no-peer-dns') diff --git a/src/migration-scripts/interfaces/7-to-8 b/src/migration-scripts/interfaces/7-to-8 new file mode 100644 index 0000000..43ae320 --- /dev/null +++ b/src/migration-scripts/interfaces/7-to-8 @@ -0,0 +1,59 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Split WireGuard endpoint into address / port nodes to make use of common +# validators + +import os + +from vyos.configtree import ConfigTree +from vyos.utils.permission import chown +from vyos.utils.permission import chmod_750 + +def migrate_default_keys(): + kdir = r'/config/auth/wireguard' + if os.path.exists(f'{kdir}/private.key') and not os.path.exists(f'{kdir}/default/private.key'): + location = f'{kdir}/default' + if not os.path.exists(location): + os.makedirs(location) + + chown(location, 'root', 'vyattacfg') + chmod_750(location) + os.rename(f'{kdir}/private.key', f'{location}/private.key') + os.rename(f'{kdir}/public.key', f'{location}/public.key') + +def migrate(config: ConfigTree) -> None: + base = ['interfaces', 'wireguard'] + + migrate_default_keys() + + if not config.exists(base): + # Nothing to do + return + + # list all individual wireguard interface isntance + for i in config.list_nodes(base): + iface = base + [i] + for peer in config.list_nodes(iface + ['peer']): + base_peer = iface + ['peer', peer] + if config.exists(base_peer + ['endpoint']): + endpoint = config.return_value(base_peer + ['endpoint']) + address = endpoint.split(':')[0] + port = endpoint.split(':')[1] + # delete old node + config.delete(base_peer + ['endpoint']) + # setup new nodes + config.set(base_peer + ['address'], value=address) + config.set(base_peer + ['port'], value=port) diff --git a/src/migration-scripts/interfaces/8-to-9 b/src/migration-scripts/interfaces/8-to-9 new file mode 100644 index 0000000..bae1b34 --- /dev/null +++ b/src/migration-scripts/interfaces/8-to-9 @@ -0,0 +1,33 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Rename link nodes to source-interface for the following interface types: +# - vxlan +# - pseudo-ethernet + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + for if_type in ['vxlan', 'pseudo-ethernet']: + base = ['interfaces', if_type] + if not config.exists(base): + # Nothing to do + continue + + # list all individual interface isntance + for i in config.list_nodes(base): + iface = base + [i] + if config.exists(iface + ['link']): + config.rename(iface + ['link'], 'source-interface') diff --git a/src/migration-scripts/interfaces/9-to-10 b/src/migration-scripts/interfaces/9-to-10 new file mode 100644 index 0000000..cdfd7d4 --- /dev/null +++ b/src/migration-scripts/interfaces/9-to-10 @@ -0,0 +1,45 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - rename CLI node 'dhcpv6-options delgate' to 'dhcpv6-options prefix-delegation +# interface' +# - rename CLI node 'interface-id' for prefix-delegation to 'address' as it +# represents the local interface IPv6 address assigned by DHCPv6-PD + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + for intf_type in config.list_nodes(['interfaces']): + for intf in config.list_nodes(['interfaces', intf_type]): + # cache current config tree + base_path = ['interfaces', intf_type, intf, 'dhcpv6-options', + 'delegate'] + + if config.exists(base_path): + # cache new config tree + new_path = ['interfaces', intf_type, intf, 'dhcpv6-options', + 'prefix-delegation'] + if not config.exists(new_path): + config.set(new_path) + + # copy to new node + config.copy(base_path, new_path + ['interface']) + + # rename interface-id to address + for interface in config.list_nodes(new_path + ['interface']): + config.rename(new_path + ['interface', interface, 'interface-id'], 'address') + + # delete old noe + config.delete(base_path) diff --git a/src/migration-scripts/ipoe-server/1-to-2 b/src/migration-scripts/ipoe-server/1-to-2 new file mode 100644 index 0000000..034eacb --- /dev/null +++ b/src/migration-scripts/ipoe-server/1-to-2 @@ -0,0 +1,94 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - T4703: merge vlan-id and vlan-range to vlan CLI node +# L2|L3 -> l2|l3 +# mac-address -> mac +# network-mode -> mode + +# - changed cli of all named pools +# - moved gateway-address from pool to global configuration with / netmask +# gateway can exist without pool if radius is used +# and Framed-ip-address is transmited +# - There are several gateway-addresses in ipoe +# - default-pool by migration. +# 1. The first pool that contains next-poll. +# 2. Else, the first pool in the list + +from vyos.configtree import ConfigTree + +base = ['service', 'ipoe-server'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if config.exists(base + ['authentication', 'interface']): + for interface in config.list_nodes(base + ['authentication', 'interface']): + config.rename(base + ['authentication', 'interface', interface, 'mac-address'], 'mac') + + mac_base = base + ['authentication', 'interface', interface, 'mac'] + for mac in config.list_nodes(mac_base): + vlan_config = mac_base + [mac, 'vlan-id'] + if config.exists(vlan_config): + config.rename(vlan_config, 'vlan') + + for interface in config.list_nodes(base + ['interface']): + base_path = base + ['interface', interface] + for vlan in ['vlan-id', 'vlan-range']: + if config.exists(base_path + [vlan]): + for tmp in config.return_values(base_path + [vlan]): + config.set(base_path + ['vlan'], value=tmp, replace=False) + config.delete(base_path + [vlan]) + + if config.exists(base_path + ['network-mode']): + tmp = config.return_value(base_path + ['network-mode']) + config.delete(base_path + ['network-mode']) + # Change L2|L3 to lower case l2|l3 + config.set(base_path + ['mode'], value=tmp.lower()) + + pool_base = base + ['client-ip-pool'] + if config.exists(pool_base): + default_pool = '' + gateway = '' + + #named pool migration + namedpools_base = pool_base + ['name'] + + for pool_name in config.list_nodes(namedpools_base): + pool_path = namedpools_base + [pool_name] + if config.exists(pool_path + ['subnet']): + subnet = config.return_value(pool_path + ['subnet']) + config.set(pool_base + [pool_name, 'range'], value=subnet, replace=False) + # Get netmask from subnet + mask = subnet.split("/")[1] + if config.exists(pool_path + ['next-pool']): + next_pool = config.return_value(pool_path + ['next-pool']) + config.set(pool_base + [pool_name, 'next-pool'], value=next_pool) + if not default_pool: + default_pool = pool_name + if config.exists(pool_path + ['gateway-address']) and mask: + gateway = f'{config.return_value(pool_path + ["gateway-address"])}/{mask}' + config.set(base + ['gateway-address'], value=gateway, replace=False) + + if not default_pool and config.list_nodes(namedpools_base): + default_pool = config.list_nodes(namedpools_base)[0] + + config.delete(namedpools_base) + + if default_pool: + config.set(base + ['default-pool'], value=default_pool) + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/ipoe-server/2-to-3 b/src/migration-scripts/ipoe-server/2-to-3 new file mode 100644 index 0000000..dcd15e5 --- /dev/null +++ b/src/migration-scripts/ipoe-server/2-to-3 @@ -0,0 +1,40 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Migrating to named ipv6 pools + +from vyos.configtree import ConfigTree + +base = ['service', 'ipoe-server'] +pool_base = base + ['client-ipv6-pool'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if not config.exists(pool_base): + return + + ipv6_pool_name = 'ipv6-pool' + config.copy(pool_base, pool_base + [ipv6_pool_name]) + + if config.exists(pool_base + ['prefix']): + config.delete(pool_base + ['prefix']) + config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name) + if config.exists(pool_base + ['delegate']): + config.delete(pool_base + ['delegate']) + + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/ipoe-server/3-to-4 b/src/migration-scripts/ipoe-server/3-to-4 new file mode 100644 index 0000000..3bad975 --- /dev/null +++ b/src/migration-scripts/ipoe-server/3-to-4 @@ -0,0 +1,30 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Add the "vlan-mon" option to the configuration to prevent it +# from disappearing from the configuration file + +from vyos.configtree import ConfigTree + +base = ['service', 'ipoe-server'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + for interface in config.list_nodes(base + ['interface']): + base_path = base + ['interface', interface] + if config.exists(base_path + ['vlan']): + config.set(base_path + ['vlan-mon']) diff --git a/src/migration-scripts/ipsec/10-to-11 b/src/migration-scripts/ipsec/10-to-11 new file mode 100644 index 0000000..6c4ccb5 --- /dev/null +++ b/src/migration-scripts/ipsec/10-to-11 @@ -0,0 +1,63 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T4916: Rewrite IPsec peer authentication and psk migration + +from vyos.configtree import ConfigTree + +base = ['vpn', 'ipsec'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # PEER changes + if config.exists(base + ['site-to-site', 'peer']): + for peer in config.list_nodes(base + ['site-to-site', 'peer']): + peer_base = base + ['site-to-site', 'peer', peer] + + # replace: 'ipsec site-to-site peer <tag> authentication pre-shared-secret xxx' + # => 'ipsec authentication psk <tag> secret xxx' + if config.exists(peer_base + ['authentication', 'pre-shared-secret']): + tmp = config.return_value(peer_base + ['authentication', 'pre-shared-secret']) + config.delete(peer_base + ['authentication', 'pre-shared-secret']) + config.set(base + ['authentication', 'psk', peer, 'secret'], value=tmp) + # format as tag node to avoid loading problems + config.set_tag(base + ['authentication', 'psk']) + + # Get id's from peers for "ipsec auth psk <tag> id xxx" + if config.exists(peer_base + ['authentication', 'local-id']): + local_id = config.return_value(peer_base + ['authentication', 'local-id']) + config.set(base + ['authentication', 'psk', peer, 'id'], value=local_id, replace=False) + if config.exists(peer_base + ['authentication', 'remote-id']): + remote_id = config.return_value(peer_base + ['authentication', 'remote-id']) + config.set(base + ['authentication', 'psk', peer, 'id'], value=remote_id, replace=False) + + if config.exists(peer_base + ['local-address']): + tmp = config.return_value(peer_base + ['local-address']) + config.set(base + ['authentication', 'psk', peer, 'id'], value=tmp, replace=False) + if config.exists(peer_base + ['remote-address']): + tmp = config.return_values(peer_base + ['remote-address']) + if tmp: + for remote_addr in tmp: + if remote_addr == 'any': + remote_addr = '%any' + config.set(base + ['authentication', 'psk', peer, 'id'], value=remote_addr, replace=False) + + # get DHCP peer interface as psk dhcp-interface + if config.exists(peer_base + ['dhcp-interface']): + tmp = config.return_value(peer_base + ['dhcp-interface']) + config.set(base + ['authentication', 'psk', peer, 'dhcp-interface'], value=tmp) diff --git a/src/migration-scripts/ipsec/11-to-12 b/src/migration-scripts/ipsec/11-to-12 new file mode 100644 index 0000000..fc65f18 --- /dev/null +++ b/src/migration-scripts/ipsec/11-to-12 @@ -0,0 +1,31 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Remove legacy ipsec.conf and ipsec.secrets - Not supported with swanctl + +from vyos.configtree import ConfigTree + +base = ['vpn', 'ipsec'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['include-ipsec-conf']): + config.delete(base + ['include-ipsec-conf']) + + if config.exists(base + ['include-ipsec-secrets']): + config.delete(base + ['include-ipsec-secrets']) diff --git a/src/migration-scripts/ipsec/12-to-13 b/src/migration-scripts/ipsec/12-to-13 new file mode 100644 index 0000000..ffe766e --- /dev/null +++ b/src/migration-scripts/ipsec/12-to-13 @@ -0,0 +1,37 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Changed value of dead-peer-detection.action from hold to trap +# Changed value of close-action from hold to trap and from restart to start + +from vyos.configtree import ConfigTree + +base = ['vpn', 'ipsec', 'ike-group'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for ike_group in config.list_nodes(base): + base_dpd_action = base + [ike_group, 'dead-peer-detection', 'action'] + base_close_action = base + [ike_group, 'close-action'] + if config.exists(base_dpd_action) and config.return_value(base_dpd_action) == 'hold': + config.set(base_dpd_action, 'trap', replace=True) + if config.exists(base_close_action): + if config.return_value(base_close_action) == 'hold': + config.set(base_close_action, 'trap', replace=True) + if config.return_value(base_close_action) == 'restart': + config.set(base_close_action, 'start', replace=True) diff --git a/src/migration-scripts/ipsec/4-to-5 b/src/migration-scripts/ipsec/4-to-5 new file mode 100644 index 0000000..a88a543 --- /dev/null +++ b/src/migration-scripts/ipsec/4-to-5 @@ -0,0 +1,28 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# log-modes have changed, keyword all to any + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + if not config.exists(['vpn', 'ipsec', 'logging','log-modes']): + # Nothing to do + return + + lmodes = config.return_values(['vpn', 'ipsec', 'logging','log-modes']) + for mode in lmodes: + if mode == 'all': + config.set(['vpn', 'ipsec', 'logging','log-modes'], value='any', replace=True) diff --git a/src/migration-scripts/ipsec/5-to-6 b/src/migration-scripts/ipsec/5-to-6 new file mode 100644 index 0000000..373428d --- /dev/null +++ b/src/migration-scripts/ipsec/5-to-6 @@ -0,0 +1,73 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Remove deprecated strongSwan options from VyOS CLI +# - vpn ipsec nat-traversal enable +# - vpn ipsec nat-networks allowed-network + +from vyos.configtree import ConfigTree + +base = ['vpn', 'ipsec'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # Delete CLI nodes whose config options got removed by strongSwan + for cli_node in ['nat-traversal', 'nat-networks']: + if config.exists(base + [cli_node]): + config.delete(base + [cli_node]) + + # Remove options only valid in Openswan + if config.exists(base + ['site-to-site', 'peer']): + for peer in config.list_nodes(base + ['site-to-site', 'peer']): + if not config.exists(base + ['site-to-site', 'peer', peer, 'tunnel']): + continue + for tunnel in config.list_nodes(base + ['site-to-site', 'peer', peer, 'tunnel']): + # allow-public-networks - Sets a value in ipsec.conf that was only ever valid in Openswan on kernel 2.6 + nat_networks = base + ['site-to-site', 'peer', peer, 'tunnel', tunnel, 'allow-nat-networks'] + if config.exists(nat_networks): + config.delete(nat_networks) + + # allow-nat-networks - Also sets a value only valid in Openswan + public_networks = base + ['site-to-site', 'peer', peer, 'tunnel', tunnel, 'allow-public-networks'] + if config.exists(public_networks): + config.delete(public_networks) + + # Rename "logging log-level" and "logging log-modes" to something more human friendly + log = base + ['logging'] + if config.exists(log): + config.rename(log, 'log') + log = base + ['log'] + + log_level = log + ['log-level'] + if config.exists(log_level): + config.rename(log_level, 'level') + + log_mode = log + ['log-modes'] + if config.exists(log_mode): + config.rename(log_mode, 'subsystem') + + # Rename "ipsec-interfaces interface" to "interface" + base_interfaces = base + ['ipsec-interfaces', 'interface'] + if config.exists(base_interfaces): + config.copy(base_interfaces, base + ['interface']) + config.delete(base + ['ipsec-interfaces']) + + # Remove deprecated "auto-update" option + tmp = base + ['auto-update'] + if config.exists(tmp): + config.delete(tmp) diff --git a/src/migration-scripts/ipsec/6-to-7 b/src/migration-scripts/ipsec/6-to-7 new file mode 100644 index 0000000..5679477 --- /dev/null +++ b/src/migration-scripts/ipsec/6-to-7 @@ -0,0 +1,155 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Migrate /config/auth certificates and keys into PKI configuration + +import os + +from vyos.configtree import ConfigTree +from vyos.pki import load_certificate +from vyos.pki import load_crl +from vyos.pki import load_private_key +from vyos.pki import encode_certificate +from vyos.pki import encode_private_key +from vyos.utils.process import run + +pki_base = ['pki'] +ipsec_site_base = ['vpn', 'ipsec', 'site-to-site', 'peer'] + +AUTH_DIR = '/config/auth' + +def wrapped_pem_to_config_value(pem): + return "".join(pem.strip().split("\n")[1:-1]) + +def migrate(config: ConfigTree) -> None: + if not config.exists(ipsec_site_base): + return + + migration_needed = False + for peer in config.list_nodes(ipsec_site_base): + if config.exists(ipsec_site_base + [peer, 'authentication', 'x509']): + migration_needed = True + break + + if not migration_needed: + return + + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + + for peer in config.list_nodes(ipsec_site_base): + if not config.exists(ipsec_site_base + [peer, 'authentication', 'x509']): + continue + + peer_x509_base = ipsec_site_base + [peer, 'authentication', 'x509'] + pki_name = 'peer_' + peer.replace(".", "-").replace("@", "") + + if config.exists(peer_x509_base + ['cert-file']): + cert_file = config.return_value(peer_x509_base + ['cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(peer_x509_base + ['certificate'], value=pki_name) + else: + print(f'Failed to migrate certificate on peer "{peer}"') + + config.delete(peer_x509_base + ['cert-file']) + + if config.exists(peer_x509_base + ['ca-cert-file']): + ca_cert_file = config.return_value(peer_x509_base + ['ca-cert-file']) + ca_cert_path = os.path.join(AUTH_DIR, ca_cert_file) + ca_cert = None + + if os.path.isfile(ca_cert_path): + if not os.access(ca_cert_path, os.R_OK): + run(f'sudo chmod 644 {ca_cert_path}') + + with open(ca_cert_path, 'r') as f: + ca_cert_data = f.read() + ca_cert = load_certificate(ca_cert_data, wrap_tags=False) + + if ca_cert: + ca_cert_pem = encode_certificate(ca_cert) + config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(ca_cert_pem)) + config.set(peer_x509_base + ['ca-certificate'], value=pki_name) + else: + print(f'Failed to migrate CA certificate on peer "{peer}"') + + config.delete(peer_x509_base + ['ca-cert-file']) + + if config.exists(peer_x509_base + ['crl-file']): + crl_file = config.return_value(peer_x509_base + ['crl-file']) + crl_path = os.path.join(AUTH_DIR, crl_file) + crl = None + + if os.path.isfile(crl_path): + if not os.access(crl_path, os.R_OK): + run(f'sudo chmod 644 {crl_path}') + + with open(crl_path, 'r') as f: + crl_data = f.read() + crl = load_crl(crl_data, wrap_tags=False) + + if crl: + crl_pem = encode_certificate(crl) + config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem)) + else: + print(f'Failed to migrate CRL on peer "{peer}"') + + config.delete(peer_x509_base + ['crl-file']) + + if config.exists(peer_x509_base + ['key', 'file']): + key_file = config.return_value(peer_x509_base + ['key', 'file']) + key_passphrase = None + + if config.exists(peer_x509_base + ['key', 'password']): + key_passphrase = config.return_value(peer_x509_base + ['key', 'password']) + + key_path = os.path.join(AUTH_DIR, key_file) + key = None + + if os.path.isfile(key_path): + if not os.access(key_path, os.R_OK): + run(f'sudo chmod 644 {key_path}') + + with open(key_path, 'r') as f: + key_data = f.read() + key = load_private_key(key_data, passphrase=key_passphrase, wrap_tags=False) + + if key: + key_pem = encode_private_key(key, passphrase=key_passphrase) + config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + + if key_passphrase: + config.set(pki_base + ['certificate', pki_name, 'private', 'password-protected']) + config.set(peer_x509_base + ['private-key-passphrase'], value=key_passphrase) + else: + print(f'Failed to migrate private key on peer "{peer}"') + + config.delete(peer_x509_base + ['key']) diff --git a/src/migration-scripts/ipsec/7-to-8 b/src/migration-scripts/ipsec/7-to-8 new file mode 100644 index 0000000..481f00d --- /dev/null +++ b/src/migration-scripts/ipsec/7-to-8 @@ -0,0 +1,103 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Migrate rsa keys into PKI configuration + +import base64 +import os +import struct + +from cryptography.hazmat.primitives.asymmetric import rsa + +from vyos.configtree import ConfigTree +from vyos.pki import load_private_key +from vyos.pki import encode_public_key +from vyos.pki import encode_private_key + +pki_base = ['pki'] +ipsec_site_base = ['vpn', 'ipsec', 'site-to-site', 'peer'] +rsa_keys_base = ['vpn', 'rsa-keys'] + +LOCAL_KEY_PATHS = ['/config/auth/', '/config/ipsec.d/rsa-keys/'] + +def migrate_from_vyatta_key(data): + data = base64.b64decode(data[2:]) + length = struct.unpack('B', data[:1])[0] + e = int.from_bytes(data[1:1+length], 'big') + n = int.from_bytes(data[1+length:], 'big') + public_numbers = rsa.RSAPublicNumbers(e, n) + return public_numbers.public_key() + +def wrapped_pem_to_config_value(pem): + return "".join(pem.strip().split("\n")[1:-1]) + +local_key_name = 'localhost' + +def migrate(config: ConfigTree) -> None: + if config.exists(rsa_keys_base): + if not config.exists(pki_base + ['key-pair']): + config.set(pki_base + ['key-pair']) + config.set_tag(pki_base + ['key-pair']) + + if config.exists(rsa_keys_base + ['local-key', 'file']): + local_file = config.return_value(rsa_keys_base + ['local-key', 'file']) + local_path = None + local_key = None + + for path in LOCAL_KEY_PATHS: + full_path = os.path.join(path, local_file) + if os.path.exists(full_path): + local_path = full_path + break + + if local_path: + with open(local_path, 'r') as f: + local_key_data = f.read() + local_key = load_private_key(local_key_data, wrap_tags=False) + + if local_key: + local_key_pem = encode_private_key(local_key) + config.set(pki_base + ['key-pair', local_key_name, 'private', 'key'], value=wrapped_pem_to_config_value(local_key_pem)) + else: + print('Failed to migrate local RSA key') + + if config.exists(rsa_keys_base + ['rsa-key-name']): + for rsa_name in config.list_nodes(rsa_keys_base + ['rsa-key-name']): + if not config.exists(rsa_keys_base + ['rsa-key-name', rsa_name, 'rsa-key']): + continue + + vyatta_key = config.return_value(rsa_keys_base + ['rsa-key-name', rsa_name, 'rsa-key']) + public_key = migrate_from_vyatta_key(vyatta_key) + + if public_key: + public_key_pem = encode_public_key(public_key) + config.set(pki_base + ['key-pair', rsa_name, 'public', 'key'], value=wrapped_pem_to_config_value(public_key_pem)) + else: + print(f'Failed to migrate rsa-key "{rsa_name}"') + + config.delete(rsa_keys_base) + + if config.exists(ipsec_site_base): + for peer in config.list_nodes(ipsec_site_base): + mode = config.return_value(ipsec_site_base + [peer, 'authentication', 'mode']) + + if mode != 'rsa': + continue + + config.set(ipsec_site_base + [peer, 'authentication', 'rsa', 'local-key'], value=local_key_name) + + remote_key_name = config.return_value(ipsec_site_base + [peer, 'authentication', 'rsa-key-name']) + config.set(ipsec_site_base + [peer, 'authentication', 'rsa', 'remote-key'], value=remote_key_name) + config.delete(ipsec_site_base + [peer, 'authentication', 'rsa-key-name']) diff --git a/src/migration-scripts/ipsec/8-to-9 b/src/migration-scripts/ipsec/8-to-9 new file mode 100644 index 0000000..7f32513 --- /dev/null +++ b/src/migration-scripts/ipsec/8-to-9 @@ -0,0 +1,30 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T4288 : close-action is missing in swanctl.conf + +from vyos.configtree import ConfigTree + +base = ['vpn', 'ipsec', 'ike-group'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for ike_group in config.list_nodes(base): + base_closeaction = base + [ike_group, 'close-action'] + if config.exists(base_closeaction) and config.return_value(base_closeaction) == 'clear': + config.set(base_closeaction, 'none', replace=True) diff --git a/src/migration-scripts/ipsec/9-to-10 b/src/migration-scripts/ipsec/9-to-10 new file mode 100644 index 0000000..321a759 --- /dev/null +++ b/src/migration-scripts/ipsec/9-to-10 @@ -0,0 +1,114 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T4118: Change vpn ipsec syntax for IKE ESP and peer +# T4879: IPsec migration script remote-id for peer name eq address + +import re + +from vyos.configtree import ConfigTree + +base = ['vpn', 'ipsec'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # IKE changes, T4118: + if config.exists(base + ['ike-group']): + for ike_group in config.list_nodes(base + ['ike-group']): + # replace 'ipsec ike-group <tag> mobike disable' + # => 'ipsec ike-group <tag> disable-mobike' + mobike = base + ['ike-group', ike_group, 'mobike'] + if config.exists(mobike): + if config.return_value(mobike) == 'disable': + config.set(base + ['ike-group', ike_group, 'disable-mobike']) + config.delete(mobike) + + # replace 'ipsec ike-group <tag> ikev2-reauth yes' + # => 'ipsec ike-group <tag> ikev2-reauth' + reauth = base + ['ike-group', ike_group, 'ikev2-reauth'] + if config.exists(reauth): + if config.return_value(reauth) == 'yes': + config.delete(reauth) + config.set(reauth) + else: + config.delete(reauth) + + # ESP changes + # replace 'ipsec esp-group <tag> compression enable' + # => 'ipsec esp-group <tag> compression' + if config.exists(base + ['esp-group']): + for esp_group in config.list_nodes(base + ['esp-group']): + compression = base + ['esp-group', esp_group, 'compression'] + if config.exists(compression): + if config.return_value(compression) == 'enable': + config.delete(compression) + config.set(compression) + else: + config.delete(compression) + + # PEER changes + if config.exists(base + ['site-to-site', 'peer']): + for peer in config.list_nodes(base + ['site-to-site', 'peer']): + peer_base = base + ['site-to-site', 'peer', peer] + + # replace: 'peer <tag> id x' + # => 'peer <tag> local-id x' + if config.exists(peer_base + ['authentication', 'id']): + config.rename(peer_base + ['authentication', 'id'], 'local-id') + + # For the peer '@foo' set remote-id 'foo' if remote-id is not defined + # For the peer '192.0.2.1' set remote-id '192.0.2.1' if remote-id is not defined + if not config.exists(peer_base + ['authentication', 'remote-id']): + tmp = peer.replace('@', '') if peer.startswith('@') else peer + config.set(peer_base + ['authentication', 'remote-id'], value=tmp) + + # replace: 'peer <tag> force-encapsulation enable' + # => 'peer <tag> force-udp-encapsulation' + force_enc = peer_base + ['force-encapsulation'] + if config.exists(force_enc): + if config.return_value(force_enc) == 'enable': + config.delete(force_enc) + config.set(peer_base + ['force-udp-encapsulation']) + else: + config.delete(force_enc) + + # add option: 'peer <tag> remote-address x.x.x.x' + remote_address = peer + if peer.startswith('@'): + remote_address = 'any' + config.set(peer_base + ['remote-address'], value=remote_address) + # Peer name it is swanctl connection name and shouldn't contain dots or colons + # rename peer: + # peer 192.0.2.1 => peer peer_192-0-2-1 + # peer 2001:db8::2 => peer peer_2001-db8--2 + # peer @foo => peer peer_foo + re_peer_name = re.sub(':|\.', '-', peer) + if re_peer_name.startswith('@'): + re_peer_name = re.sub('@', '', re_peer_name) + new_peer_name = f'peer_{re_peer_name}' + + config.rename(peer_base, new_peer_name) + + # remote-access/road-warrior changes + if config.exists(base + ['remote-access', 'connection']): + for connection in config.list_nodes(base + ['remote-access', 'connection']): + ra_base = base + ['remote-access', 'connection', connection] + # replace: 'remote-access connection <tag> authentication id x' + # => 'remote-access connection <tag> authentication local-id x' + if config.exists(ra_base + ['authentication', 'id']): + config.rename(ra_base + ['authentication', 'id'], 'local-id') diff --git a/src/migration-scripts/isis/0-to-1 b/src/migration-scripts/isis/0-to-1 new file mode 100644 index 0000000..e242885 --- /dev/null +++ b/src/migration-scripts/isis/0-to-1 @@ -0,0 +1,36 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3417: migrate IS-IS tagNode to node as we can only have one IS-IS process + +from vyos.configtree import ConfigTree + +base = ['protocols', 'isis'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # We need a temporary copy of the config + tmp_base = ['protocols', 'isis2'] + config.copy(base, tmp_base) + + # Now it's save to delete the old configuration + config.delete(base) + + # Rename temporary copy to new final config (IS-IS domain key is static and no + # longer required to be set via CLI) + config.rename(tmp_base, 'isis') diff --git a/src/migration-scripts/isis/1-to-2 b/src/migration-scripts/isis/1-to-2 new file mode 100644 index 0000000..0fc92a6 --- /dev/null +++ b/src/migration-scripts/isis/1-to-2 @@ -0,0 +1,27 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T4739 refactor, and remove "on" from segment routing from the configuration + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + # Check if ISIS segment routing is configured. Then check if segment + # routing "on" exists, then delete the "on" as it is no longer needed. + # This is for global configuration. + if config.exists(['protocols', 'isis']): + if config.exists(['protocols', 'isis', 'segment-routing']): + if config.exists(['protocols', 'isis', 'segment-routing', 'enable']): + config.delete(['protocols', 'isis', 'segment-routing', 'enable']) diff --git a/src/migration-scripts/isis/2-to-3 b/src/migration-scripts/isis/2-to-3 new file mode 100644 index 0000000..afb9f23 --- /dev/null +++ b/src/migration-scripts/isis/2-to-3 @@ -0,0 +1,43 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5150: Rework CLI definitions to apply route-maps between routing daemons +# and zebra/kernel + +from vyos.configtree import ConfigTree + +isis_base = ['protocols', 'isis'] + +def migrate(config: ConfigTree) -> None: + # Check if IS-IS is configured - if so, migrate the CLI node + if config.exists(isis_base): + if config.exists(isis_base + ['route-map']): + tmp = config.return_value(isis_base + ['route-map']) + + config.set(['system', 'ip', 'protocol', 'isis', 'route-map'], value=tmp) + config.set_tag(['system', 'ip', 'protocol']) + config.delete(isis_base + ['route-map']) + + # Check if vrf names are configured. Check if IS-IS is configured - if so, + # migrate the CLI node(s) + if config.exists(['vrf', 'name']): + for vrf in config.list_nodes(['vrf', 'name']): + vrf_base = ['vrf', 'name', vrf] + if config.exists(vrf_base + ['protocols', 'isis', 'route-map']): + tmp = config.return_value(vrf_base + ['protocols', 'isis', 'route-map']) + + config.set(vrf_base + ['ip', 'protocol', 'isis', 'route-map'], value=tmp) + config.set_tag(vrf_base + ['ip', 'protocol', 'isis']) + config.delete(vrf_base + ['protocols', 'isis', 'route-map']) diff --git a/src/migration-scripts/l2tp/0-to-1 b/src/migration-scripts/l2tp/0-to-1 new file mode 100644 index 0000000..f0cb6af --- /dev/null +++ b/src/migration-scripts/l2tp/0-to-1 @@ -0,0 +1,56 @@ +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T987: Unclutter L2TP/IPSec RADIUS configuration nodes +# Unclutter L2TP VPN configuiration - move radius-server top level tag +# nodes to a regular node which now also configures the radius source address +# used when querying a radius server + +from vyos.configtree import ConfigTree + +cfg_base = ['vpn', 'l2tp', 'remote-access', 'authentication'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(cfg_base): + # Nothing to do + return + + # Migrate "vpn l2tp authentication radius-source-address" to new + # "vpn l2tp authentication radius source-address" + if config.exists(cfg_base + ['radius-source-address']): + address = config.return_value(cfg_base + ['radius-source-address']) + # delete old configuration node + config.delete(cfg_base + ['radius-source-address']) + # write new configuration node + config.set(cfg_base + ['radius', 'source-address'], value=address) + + # Migrate "vpn l2tp authentication radius-server" tag node to new + # "vpn l2tp authentication radius server" tag node + if config.exists(cfg_base + ['radius-server']): + for server in config.list_nodes(cfg_base + ['radius-server']): + base_server = cfg_base + ['radius-server', server] + key = config.return_value(base_server + ['key']) + + # delete old configuration node + config.delete(base_server) + # write new configuration node + config.set(cfg_base + ['radius', 'server', server, 'key'], value=key) + + # format as tag node + config.set_tag(cfg_base + ['radius', 'server']) + + # delete top level tag node + if config.exists(cfg_base + ['radius-server']): + config.delete(cfg_base + ['radius-server']) diff --git a/src/migration-scripts/l2tp/1-to-2 b/src/migration-scripts/l2tp/1-to-2 new file mode 100644 index 0000000..468d564 --- /dev/null +++ b/src/migration-scripts/l2tp/1-to-2 @@ -0,0 +1,28 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T1858: Delete deprecated outside-nexthop + +from vyos.configtree import ConfigTree + +cfg_base = ['vpn', 'l2tp', 'remote-access'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(cfg_base): + # Nothing to do + return + + if config.exists(cfg_base + ['outside-nexthop']): + config.delete(cfg_base + ['outside-nexthop']) diff --git a/src/migration-scripts/l2tp/2-to-3 b/src/migration-scripts/l2tp/2-to-3 new file mode 100644 index 0000000..00fabb6 --- /dev/null +++ b/src/migration-scripts/l2tp/2-to-3 @@ -0,0 +1,92 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T2264: combine IPv4/IPv6 name-server CLI syntax +# T2264: combine WINS CLI syntax +# T2264: remove RADIUS req-limit node +# T2264: migrate IPv6 prefix node to common CLI style + +from vyos.configtree import ConfigTree + +base = ['vpn', 'l2tp', 'remote-access'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # Migrate IPv4 DNS servers + dns_base = base + ['dns-servers'] + if config.exists(dns_base): + for server in ['server-1', 'server-2']: + if config.exists(dns_base + [server]): + dns = config.return_value(dns_base + [server]) + config.set(base + ['name-server'], value=dns, replace=False) + + config.delete(dns_base) + + # Migrate IPv6 DNS servers + dns_base = base + ['dnsv6-servers'] + if config.exists(dns_base): + for server in config.return_values(dns_base): + config.set(base + ['name-server'], value=server, replace=False) + + config.delete(dns_base) + + # Migrate IPv4 WINS servers + wins_base = base + ['wins-servers'] + if config.exists(wins_base): + for server in ['server-1', 'server-2']: + if config.exists(wins_base + [server]): + wins = config.return_value(wins_base + [server]) + config.set(base + ['wins-server'], value=wins, replace=False) + + config.delete(wins_base) + + + # Remove RADIUS server req-limit node + radius_base = base + ['authentication', 'radius'] + if config.exists(radius_base): + for server in config.list_nodes(radius_base + ['server']): + if config.exists(radius_base + ['server', server, 'req-limit']): + config.delete(radius_base + ['server', server, 'req-limit']) + + # Migrate IPv6 prefixes + ipv6_base = base + ['client-ipv6-pool'] + if config.exists(ipv6_base + ['prefix']): + prefix_old = config.return_values(ipv6_base + ['prefix']) + # delete old prefix CLI nodes + config.delete(ipv6_base + ['prefix']) + # create ned prefix tag node + config.set(ipv6_base + ['prefix']) + config.set_tag(ipv6_base + ['prefix']) + + for p in prefix_old: + prefix = p.split(',')[0] + mask = p.split(',')[1] + config.set(ipv6_base + ['prefix', prefix, 'mask'], value=mask) + + if config.exists(ipv6_base + ['delegate-prefix']): + prefix_old = config.return_values(ipv6_base + ['delegate-prefix']) + # delete old delegate prefix CLI nodes + config.delete(ipv6_base + ['delegate-prefix']) + # create ned delegation tag node + config.set(ipv6_base + ['delegate']) + config.set_tag(ipv6_base + ['delegate']) + + for p in prefix_old: + prefix = p.split(',')[0] + mask = p.split(',')[1] + config.set(ipv6_base + ['delegate', prefix, 'delegate-prefix'], value=mask) diff --git a/src/migration-scripts/l2tp/3-to-4 b/src/migration-scripts/l2tp/3-to-4 new file mode 100644 index 0000000..01c3fa8 --- /dev/null +++ b/src/migration-scripts/l2tp/3-to-4 @@ -0,0 +1,148 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T2816: T3642: Move IPSec/L2TP code into vpn_ipsec.py and update to use PKI. + +import os + +from vyos.configtree import ConfigTree +from vyos.pki import load_certificate +from vyos.pki import load_private_key +from vyos.pki import encode_certificate +from vyos.pki import encode_private_key +from vyos.utils.process import run + +base = ['vpn', 'l2tp', 'remote-access', 'ipsec-settings'] +pki_base = ['pki'] + +AUTH_DIR = '/config/auth' + +def wrapped_pem_to_config_value(pem): + return "".join(pem.strip().split("\n")[1:-1]) + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if not config.exists(base + ['authentication', 'x509']): + return + + x509_base = base + ['authentication', 'x509'] + pki_name = 'l2tp_remote_access' + + if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + + if config.exists(x509_base + ['ca-cert-file']): + cert_file = config.return_value(x509_base + ['ca-cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['ca-certificate'], value=pki_name) + else: + print(f'Failed to migrate CA certificate on l2tp remote-access config') + + config.delete(x509_base + ['ca-cert-file']) + + if config.exists(x509_base + ['crl-file']): + crl_file = config.return_value(x509_base + ['crl-file']) + crl_path = os.path.join(AUTH_DIR, crl_file) + crl = None + + if os.path.isfile(crl_path): + if not os.access(crl_path, os.R_OK): + run(f'sudo chmod 644 {crl_path}') + + with open(crl_path, 'r') as f: + crl_data = f.read() + crl = load_certificate(crl_data, wrap_tags=False) + + if crl: + crl_pem = encode_certificate(crl) + config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem)) + else: + print(f'Failed to migrate CRL on l2tp remote-access config') + + config.delete(x509_base + ['crl-file']) + + if config.exists(x509_base + ['server-cert-file']): + cert_file = config.return_value(x509_base + ['server-cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['certificate'], value=pki_name) + else: + print(f'Failed to migrate certificate on l2tp remote-access config') + + config.delete(x509_base + ['server-cert-file']) + + if config.exists(x509_base + ['server-key-file']): + key_file = config.return_value(x509_base + ['server-key-file']) + key_passphrase = None + + if config.exists(x509_base + ['server-key-password']): + key_passphrase = config.return_value(x509_base + ['server-key-password']) + + key_path = os.path.join(AUTH_DIR, key_file) + key = None + + if os.path.isfile(key_path): + if not os.access(key_path, os.R_OK): + run(f'sudo chmod 644 {key_path}') + + with open(key_path, 'r') as f: + key_data = f.read() + key = load_private_key(key_data, passphrase=key_passphrase, wrap_tags=False) + + if key: + key_pem = encode_private_key(key, passphrase=key_passphrase) + config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + + if key_passphrase: + config.set(pki_base + ['certificate', pki_name, 'private', 'password-protected']) + config.set(x509_base + ['private-key-passphrase'], value=key_passphrase) + else: + print(f'Failed to migrate private key on l2tp remote-access config') + + config.delete(x509_base + ['server-key-file']) + if config.exists(x509_base + ['server-key-password']): + config.delete(x509_base + ['server-key-password']) diff --git a/src/migration-scripts/l2tp/4-to-5 b/src/migration-scripts/l2tp/4-to-5 new file mode 100644 index 0000000..56d451b --- /dev/null +++ b/src/migration-scripts/l2tp/4-to-5 @@ -0,0 +1,68 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - move all pool to named pools +# 'start-stop' migrate to namedpool 'default-range-pool' +# 'subnet' migrate to namedpool 'default-subnet-pool' +# 'default-subnet-pool' is the next pool for 'default-range-pool' + +from vyos.configtree import ConfigTree +from vyos.base import Warning + +base = ['vpn', 'l2tp', 'remote-access'] +pool_base = base + ['client-ip-pool'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if not config.exists(pool_base): + return + + default_pool = '' + range_pool_name = 'default-range-pool' + + if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']): + def is_legalrange(ip1: str, ip2: str, mask: str): + from ipaddress import IPv4Interface + interface1 = IPv4Interface(f'{ip1}/{mask}') + + interface2 = IPv4Interface(f'{ip2}/{mask}') + return interface1.network.network_address == interface2.network.network_address and interface2.ip > interface1.ip + + start_ip = config.return_value(pool_base + ['start']) + stop_ip = config.return_value(pool_base + ['stop']) + if is_legalrange(start_ip, stop_ip,'24'): + ip_range = f'{start_ip}-{stop_ip}' + config.set(pool_base + [range_pool_name, 'range'], value=ip_range, replace=False) + default_pool = range_pool_name + else: + Warning( + f'L2TP client-ip-pool range start-ip:{start_ip} and stop-ip:{stop_ip} can not be migrated.') + + config.delete(pool_base + ['start']) + config.delete(pool_base + ['stop']) + + if config.exists(pool_base + ['subnet']): + for subnet in config.return_values(pool_base + ['subnet']): + config.set(pool_base + [range_pool_name, 'range'], value=subnet, replace=False) + + config.delete(pool_base + ['subnet']) + default_pool = range_pool_name + + if default_pool: + config.set(base + ['default-pool'], value=default_pool) + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/l2tp/5-to-6 b/src/migration-scripts/l2tp/5-to-6 new file mode 100644 index 0000000..cc9f948 --- /dev/null +++ b/src/migration-scripts/l2tp/5-to-6 @@ -0,0 +1,88 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configtree import ConfigTree + +base = ['vpn', 'l2tp', 'remote-access'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + #migrate idle to ppp option lcp-echo-timeout + idle_path = base + ['idle'] + if config.exists(idle_path): + config.set(base + ['ppp-options', 'lcp-echo-timeout'], + value=config.return_value(idle_path)) + config.delete(idle_path) + + #migrate mppe from authentication to ppp-otion + mppe_path = base + ['authentication', 'mppe'] + if config.exists(mppe_path): + config.set(base + ['ppp-options', 'mppe'], + value=config.return_value(mppe_path)) + config.delete(mppe_path) + + #migrate require to protocol + require_path = base + ['authentication', 'require'] + if config.exists(require_path): + protocols = list(config.return_values(require_path)) + for protocol in protocols: + config.set(base + ['authentication', 'protocols'], value=protocol, + replace=False) + config.delete(require_path) + else: + config.set(base + ['authentication', 'protocols'], value='mschap-v2') + + #migrate default gateway if not exist + if not config.exists(base + ['gateway-address']): + config.set(base + ['gateway-address'], value='10.255.255.0') + + #migrate authentication radius timeout + rad_timeout_path = base + ['authentication', 'radius', 'timeout'] + if config.exists(rad_timeout_path): + if int(config.return_value(rad_timeout_path)) > 60: + config.set(rad_timeout_path, value=60) + + #migrate authentication radius acct timeout + rad_acct_timeout_path = base + ['authentication', 'radius', 'acct-timeout'] + if config.exists(rad_acct_timeout_path): + if int(config.return_value(rad_acct_timeout_path)) > 60: + config.set(rad_acct_timeout_path,value=60) + + #migrate authentication radius max-try + rad_max_try_path = base + ['authentication', 'radius', 'max-try'] + if config.exists(rad_max_try_path): + if int(config.return_value(rad_max_try_path)) > 20: + config.set(rad_max_try_path, value=20) + + #migrate dae-server to dynamic-author + dae_path_old = base + ['authentication', 'radius', 'dae-server'] + dae_path_new = base + ['authentication', 'radius', 'dynamic-author'] + + if config.exists(dae_path_old + ['ip-address']): + config.set(dae_path_new + ['server'], + value=config.return_value(dae_path_old + ['ip-address'])) + + if config.exists(dae_path_old + ['port']): + config.set(dae_path_new + ['port'], + value=config.return_value(dae_path_old + ['port'])) + + if config.exists(dae_path_old + ['secret']): + config.set(dae_path_new + ['key'], + value=config.return_value(dae_path_old + ['secret'])) + + if config.exists(dae_path_old): + config.delete(dae_path_old) diff --git a/src/migration-scripts/l2tp/6-to-7 b/src/migration-scripts/l2tp/6-to-7 new file mode 100644 index 0000000..4dba597 --- /dev/null +++ b/src/migration-scripts/l2tp/6-to-7 @@ -0,0 +1,39 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Migrating to named ipv6 pools + +from vyos.configtree import ConfigTree + +base = ['vpn', 'l2tp', 'remote-access'] +pool_base = base + ['client-ipv6-pool'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if not config.exists(pool_base): + return + + ipv6_pool_name = 'ipv6-pool' + config.copy(pool_base, pool_base + [ipv6_pool_name]) + + if config.exists(pool_base + ['prefix']): + config.delete(pool_base + ['prefix']) + config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name) + if config.exists(pool_base + ['delegate']): + config.delete(pool_base + ['delegate']) + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/l2tp/7-to-8 b/src/migration-scripts/l2tp/7-to-8 new file mode 100644 index 0000000..527906f --- /dev/null +++ b/src/migration-scripts/l2tp/7-to-8 @@ -0,0 +1,47 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Migrate from 'ccp-disable' to 'ppp-options.disable-ccp' +# Migration ipv6 options + +from vyos.configtree import ConfigTree + +base = ['vpn', 'l2tp', 'remote-access'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + #CCP migration + if config.exists(base + ['ccp-disable']): + config.delete(base + ['ccp-disable']) + config.set(base + ['ppp-options', 'disable-ccp']) + + #IPV6 options migrations + if config.exists(base + ['ppp-options','ipv6-peer-intf-id']): + intf_peer_id = config.return_value(base + ['ppp-options','ipv6-peer-intf-id']) + if intf_peer_id == 'ipv4': + intf_peer_id = 'ipv4-addr' + config.set(base + ['ppp-options','ipv6-peer-interface-id'], value=intf_peer_id, replace=True) + config.delete(base + ['ppp-options','ipv6-peer-intf-id']) + + if config.exists(base + ['ppp-options','ipv6-intf-id']): + intf_id = config.return_value(base + ['ppp-options','ipv6-intf-id']) + config.set(base + ['ppp-options','ipv6-interface-id'], value=intf_id, replace=True) + config.delete(base + ['ppp-options','ipv6-intf-id']) + + if config.exists(base + ['ppp-options','ipv6-accept-peer-intf-id']): + config.set(base + ['ppp-options','ipv6-accept-peer-interface-id']) + config.delete(base + ['ppp-options','ipv6-accept-peer-intf-id']) diff --git a/src/migration-scripts/l2tp/8-to-9 b/src/migration-scripts/l2tp/8-to-9 new file mode 100644 index 0000000..e6b689e --- /dev/null +++ b/src/migration-scripts/l2tp/8-to-9 @@ -0,0 +1,28 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Deleted 'dhcp-interface' from l2tp + +from vyos.configtree import ConfigTree + +base = ['vpn', 'l2tp', 'remote-access'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + # deleting unused dhcp-interface + if config.exists(base + ['dhcp-interface']): + config.delete(base + ['dhcp-interface']) diff --git a/src/migration-scripts/lldp/0-to-1 b/src/migration-scripts/lldp/0-to-1 new file mode 100644 index 0000000..c16e7e8 --- /dev/null +++ b/src/migration-scripts/lldp/0-to-1 @@ -0,0 +1,31 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Delete "set service lldp interface <interface> location civic-based" option +# as it was broken most of the time anyways + +from vyos.configtree import ConfigTree + +base = ['service', 'lldp', 'interface'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # Delete nodes with abandoned CLI syntax + for interface in config.list_nodes(base): + if config.exists(base + [interface, 'location', 'civic-based']): + config.delete(base + [interface, 'location', 'civic-based']) diff --git a/src/migration-scripts/lldp/1-to-2 b/src/migration-scripts/lldp/1-to-2 new file mode 100644 index 0000000..7f233a7 --- /dev/null +++ b/src/migration-scripts/lldp/1-to-2 @@ -0,0 +1,30 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5855: migrate "set service lldp snmp enable" -> `set service lldp snmp" + +from vyos.configtree import ConfigTree + +base = ['service', 'lldp'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['snmp']): + enabled = config.exists(base + ['snmp', 'enable']) + config.delete(base + ['snmp']) + if enabled: config.set(base + ['snmp']) diff --git a/src/migration-scripts/monitoring/0-to-1 b/src/migration-scripts/monitoring/0-to-1 new file mode 100644 index 0000000..92f8243 --- /dev/null +++ b/src/migration-scripts/monitoring/0-to-1 @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3417: migrate IS-IS tagNode to node as we can only have one IS-IS process + +from vyos.configtree import ConfigTree + +base = ['service', 'monitoring', 'telegraf'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['authentication', 'organization']): + tmp = config.return_value(base + ['authentication', 'organization']) + config.delete(base + ['authentication', 'organization']) + config.set(base + ['influxdb', 'authentication', 'organization'], value=tmp) + + if config.exists(base + ['authentication', 'token']): + tmp = config.return_value(base + ['authentication', 'token']) + config.delete(base + ['authentication', 'token']) + config.set(base + ['influxdb', 'authentication', 'token'], value=tmp) + + if config.exists(base + ['bucket']): + tmp = config.return_value(base + ['bucket']) + config.delete(base + ['bucket']) + config.set(base + ['influxdb', 'bucket'], value=tmp) + + if config.exists(base + ['port']): + tmp = config.return_value(base + ['port']) + config.delete(base + ['port']) + config.set(base + ['influxdb', 'port'], value=tmp) + + if config.exists(base + ['url']): + tmp = config.return_value(base + ['url']) + config.delete(base + ['url']) + config.set(base + ['influxdb', 'url'], value=tmp) diff --git a/src/migration-scripts/nat/4-to-5 b/src/migration-scripts/nat/4-to-5 new file mode 100644 index 0000000..e1919da --- /dev/null +++ b/src/migration-scripts/nat/4-to-5 @@ -0,0 +1,45 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Drop the enable/disable from the nat "log" node. If log node is specified +# it is "enabled" + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + if not config.exists(['nat']): + # Nothing to do + return + + for direction in ['source', 'destination']: + # If a node doesn't exist, we obviously have nothing to do. + if not config.exists(['nat', direction]): + continue + + # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, + # but there are no rules under it. + if not config.list_nodes(['nat', direction]): + continue + + for rule in config.list_nodes(['nat', direction, 'rule']): + base = ['nat', direction, 'rule', rule] + + # Check if the log node exists and if log is enabled, + # migrate it to the new valueless 'log' node + if config.exists(base + ['log']): + tmp = config.return_value(base + ['log']) + config.delete(base + ['log']) + if tmp == 'enable': + config.set(base + ['log']) diff --git a/src/migration-scripts/nat/5-to-6 b/src/migration-scripts/nat/5-to-6 new file mode 100644 index 0000000..a583d4e --- /dev/null +++ b/src/migration-scripts/nat/5-to-6 @@ -0,0 +1,82 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5643: move from 'set nat [source|destination] rule X [inbound-interface|outbound interface] <iface>' +# to +# 'set nat [source|destination] rule X [inbound-interface|outbound interface] interface-name <iface>' + +# T6100: Migration from 1.3.X to 1.4 +# Change IP/netmask to Network/netmask in +# 'set nat [source|destination] rule X [source| destination| translation] address <IP/Netmask| !IP/Netmask>' + +import ipaddress + +from vyos.configtree import ConfigTree + + +def _func_T5643(conf, base_path): + for iface in ['inbound-interface', 'outbound-interface']: + if conf.exists(base_path + [iface]): + tmp = conf.return_value(base_path + [iface]) + if tmp: + conf.delete(base_path + [iface]) + conf.set(base_path + [iface, 'interface-name'], value=tmp) + return + + +def _func_T6100(conf, base_path): + for addr_type in ['source', 'destination', 'translation']: + base_addr_type = base_path + [addr_type] + if not conf.exists(base_addr_type) or not conf.exists( + base_addr_type + ['address']): + continue + + address = conf.return_value(base_addr_type + ['address']) + + if not address or '/' not in address: + continue + + negative = '' + network = address + if '!' in address: + negative = '!' + network = str(address.split(negative)[1]) + + network_ip = ipaddress.ip_network(network, strict=False) + if str(network_ip) != network: + network = f'{negative}{str(network_ip)}' + conf.set(base_addr_type + ['address'], value=network) + return + + +def migrate(config: ConfigTree) -> None: + if not config.exists(['nat']): + # Nothing to do + return + + for direction in ['source', 'destination']: + # If a node doesn't exist, we obviously have nothing to do. + if not config.exists(['nat', direction]): + continue + + # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, + # but there are no rules under it. + if not config.list_nodes(['nat', direction]): + continue + + for rule in config.list_nodes(['nat', direction, 'rule']): + base = ['nat', direction, 'rule', rule] + _func_T5643(config,base) + _func_T6100(config,base) diff --git a/src/migration-scripts/nat/6-to-7 b/src/migration-scripts/nat/6-to-7 new file mode 100644 index 0000000..e9b90fc --- /dev/null +++ b/src/migration-scripts/nat/6-to-7 @@ -0,0 +1,54 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5681: Firewall re-writing. Simplify cli when mathcing interface +# From +# 'set nat [source|destination] rule X [inbound-interface|outbound interface] interface-name <iface>' +# 'set nat [source|destination] rule X [inbound-interface|outbound interface] interface-group <iface_group>' +# to +# 'set nat [source|destination] rule X [inbound-interface|outbound interface] name <iface>' +# 'set nat [source|destination] rule X [inbound-interface|outbound interface] group <iface_group>' +# Also remove command if interface == any + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + if not config.exists(['nat']): + # Nothing to do + return + + for direction in ['source', 'destination']: + # If a node doesn't exist, we obviously have nothing to do. + if not config.exists(['nat', direction]): + continue + + # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, + # but there are no rules under it. + if not config.list_nodes(['nat', direction]): + continue + + for rule in config.list_nodes(['nat', direction, 'rule']): + base = ['nat', direction, 'rule', rule] + for iface in ['inbound-interface','outbound-interface']: + if config.exists(base + [iface]): + if config.exists(base + [iface, 'interface-name']): + tmp = config.return_value(base + [iface, 'interface-name']) + if tmp != 'any': + config.delete(base + [iface, 'interface-name']) + if '+' in tmp: + tmp = tmp.replace('+', '*') + config.set(base + [iface, 'name'], value=tmp) + else: + config.delete(base + [iface]) diff --git a/src/migration-scripts/nat/7-to-8 b/src/migration-scripts/nat/7-to-8 new file mode 100644 index 0000000..9ae389e --- /dev/null +++ b/src/migration-scripts/nat/7-to-8 @@ -0,0 +1,43 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T6345: random - In kernel 5.0 and newer this is the same as fully-random. +# In earlier kernels the port mapping will be randomized using a seeded +# MD5 hash mix using source and destination address and destination port. +# drop fully-random from CLI + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + if not config.exists(['nat']): + # Nothing to do + return + + for direction in ['source', 'destination']: + # If a node doesn't exist, we obviously have nothing to do. + if not config.exists(['nat', direction]): + continue + + # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, + # but there are no rules under it. + if not config.list_nodes(['nat', direction]): + continue + + for rule in config.list_nodes(['nat', direction, 'rule']): + port_mapping = ['nat', direction, 'rule', rule, 'translation', 'options', 'port-mapping'] + if config.exists(port_mapping): + tmp = config.return_value(port_mapping) + if tmp == 'fully-random': + config.set(port_mapping, value='random') diff --git a/src/migration-scripts/nat66/0-to-1 b/src/migration-scripts/nat66/0-to-1 new file mode 100644 index 0000000..b3c6bf4 --- /dev/null +++ b/src/migration-scripts/nat66/0-to-1 @@ -0,0 +1,52 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configtree import ConfigTree + +def merge_npt(config,base,rule): + merge_base = ['nat66','source','rule',rule] + # Configure migration functions + if config.exists(base + ['description']): + tmp = config.return_value(base + ['description']) + config.set(merge_base + ['description'],value=tmp) + + if config.exists(base + ['disable']): + tmp = config.return_value(base + ['disable']) + config.set(merge_base + ['disable'],value=tmp) + + if config.exists(base + ['outbound-interface']): + tmp = config.return_value(base + ['outbound-interface']) + config.set(merge_base + ['outbound-interface'],value=tmp) + + if config.exists(base + ['source','prefix']): + tmp = config.return_value(base + ['source','prefix']) + config.set(merge_base + ['source','prefix'],value=tmp) + + if config.exists(base + ['translation','prefix']): + tmp = config.return_value(base + ['translation','prefix']) + config.set(merge_base + ['translation','address'],value=tmp) + +def migrate(config: ConfigTree) -> None: + if not config.exists(['nat', 'nptv6']): + # Nothing to do + return + + for rule in config.list_nodes(['nat', 'nptv6', 'rule']): + base = ['nat', 'nptv6', 'rule', rule] + # Merge 'nat nptv6' to 'nat66 source' + merge_npt(config,base,rule) + + # Delete the original NPT configuration + config.delete(['nat','nptv6']); diff --git a/src/migration-scripts/nat66/1-to-2 b/src/migration-scripts/nat66/1-to-2 new file mode 100644 index 0000000..f49940a --- /dev/null +++ b/src/migration-scripts/nat66/1-to-2 @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5681: Firewall re-writing. Simplify cli when mathcing interface +# From +# 'set nat66 [source|destination] rule X [inbound-interface|outbound interface] <iface>' +# to +# 'set nat66 [source|destination] rule X [inbound-interface|outbound interface] name <iface>' + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + if not config.exists(['nat66']): + # Nothing to do + return + + for direction in ['source', 'destination']: + # If a node doesn't exist, we obviously have nothing to do. + if not config.exists(['nat66', direction]): + continue + + # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, + # but there are no rules under it. + if not config.list_nodes(['nat66', direction]): + continue + + for rule in config.list_nodes(['nat66', direction, 'rule']): + base = ['nat66', direction, 'rule', rule] + for iface in ['inbound-interface','outbound-interface']: + if config.exists(base + [iface]): + tmp = config.return_value(base + [iface]) + config.delete(base + [iface]) + config.set(base + [iface, 'name'], value=tmp) diff --git a/src/migration-scripts/nat66/2-to-3 b/src/migration-scripts/nat66/2-to-3 new file mode 100644 index 0000000..55d5f4b --- /dev/null +++ b/src/migration-scripts/nat66/2-to-3 @@ -0,0 +1,45 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T2898: add ndp-proxy service + +from vyos.configtree import ConfigTree + +base = ['nat66', 'source'] +new_base = ['service', 'ndp-proxy', 'interface'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for rule in config.list_nodes(base + ['rule']): + base_rule = base + ['rule', rule] + + interface = None + if config.exists(base_rule + ['outbound-interface', 'name']): + interface = config.return_value(base_rule + ['outbound-interface', 'name']) + else: + continue + + prefix_base = base_rule + ['source', 'prefix'] + if config.exists(prefix_base): + prefix = config.return_value(prefix_base) + config.set(new_base + [interface, 'prefix', prefix, 'mode'], value='static') + config.set_tag(new_base) + config.set_tag(new_base + [interface, 'prefix']) + + if config.exists(base_rule + ['disable']): + config.set(new_base + [interface, 'prefix', prefix, 'disable']) diff --git a/src/migration-scripts/ntp/0-to-1 b/src/migration-scripts/ntp/0-to-1 new file mode 100644 index 0000000..01f5a46 --- /dev/null +++ b/src/migration-scripts/ntp/0-to-1 @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Delete "set system ntp server <n> dynamic" option + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + if not config.exists(['system', 'ntp', 'server']): + # Nothing to do + return + + # Delete abandoned leaf node if found inside tag node for + # "set system ntp server <n> dynamic" + base = ['system', 'ntp', 'server'] + for server in config.list_nodes(base): + if config.exists(base + [server, 'dynamic']): + config.delete(base + [server, 'dynamic']) diff --git a/src/migration-scripts/ntp/1-to-2 b/src/migration-scripts/ntp/1-to-2 new file mode 100644 index 0000000..fd7b082 --- /dev/null +++ b/src/migration-scripts/ntp/1-to-2 @@ -0,0 +1,53 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3008: move from ntpd to chrony and migrate "system ntp" to "service ntp" + +from vyos.configtree import ConfigTree + +base_path = ['system', 'ntp'] +new_base_path = ['service', 'ntp'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base_path): + # Nothing to do + return + + # config.copy does not recursively create a path, so create ['service'] if + # it doesn't yet exist, such as for config.boot.default + if not config.exists(['service']): + config.set(['service']) + + # copy "system ntp" to "service ntp" + config.copy(base_path, new_base_path) + config.delete(base_path) + + # chrony does not support the preempt option, drop it + for server in config.list_nodes(new_base_path + ['server']): + server_base = new_base_path + ['server', server] + if config.exists(server_base + ['preempt']): + config.delete(server_base + ['preempt']) + + # Rename "allow-clients" -> "allow-client" + if config.exists(new_base_path + ['allow-clients']): + config.rename(new_base_path + ['allow-clients'], 'allow-client') + + # By default VyOS 1.3 allowed NTP queries for all networks - in chrony we + # explicitly disable this behavior and clients need to be specified using the + # allow-client CLI option. In order to be fully backwards compatible, we specify + # 0.0.0.0/0 and ::/0 as allow networks if not specified otherwise explicitly. + if not config.exists(new_base_path + ['allow-client']): + config.set(new_base_path + ['allow-client', 'address'], value='0.0.0.0/0', replace=False) + config.set(new_base_path + ['allow-client', 'address'], value='::/0', replace=False) diff --git a/src/migration-scripts/ntp/2-to-3 b/src/migration-scripts/ntp/2-to-3 new file mode 100644 index 0000000..bbda903 --- /dev/null +++ b/src/migration-scripts/ntp/2-to-3 @@ -0,0 +1,43 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5154: allow only one ip address per family for parameter 'listen-address' +# Allow only one interface for parameter 'interface' +# If more than one are specified, remove such entries + +from vyos.configtree import ConfigTree +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 + +base_path = ['service', 'ntp'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base_path): + # Nothing to do + return + + if config.exists(base_path + ['listen-address']) and (len([addr for addr in config.return_values(base_path + ['listen-address']) if is_ipv4(addr)]) > 1): + for addr in config.return_values(base_path + ['listen-address']): + if is_ipv4(addr): + config.delete_value(base_path + ['listen-address'], addr) + + if config.exists(base_path + ['listen-address']) and (len([addr for addr in config.return_values(base_path + ['listen-address']) if is_ipv6(addr)]) > 1): + for addr in config.return_values(base_path + ['listen-address']): + if is_ipv6(addr): + config.delete_value(base_path + ['listen-address'], addr) + + if config.exists(base_path + ['interface']): + if len(config.return_values(base_path + ['interface'])) > 1: + config.delete(base_path + ['interface']) diff --git a/src/migration-scripts/openconnect/0-to-1 b/src/migration-scripts/openconnect/0-to-1 new file mode 100644 index 0000000..aa5a97e --- /dev/null +++ b/src/migration-scripts/openconnect/0-to-1 @@ -0,0 +1,116 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - Update SSL to use PKI configuration + +import os + +from vyos.configtree import ConfigTree +from vyos.pki import load_certificate +from vyos.pki import load_private_key +from vyos.pki import encode_certificate +from vyos.pki import encode_private_key +from vyos.utils.process import run + +base = ['vpn', 'openconnect'] +pki_base = ['pki'] + +AUTH_DIR = '/config/auth' + +def wrapped_pem_to_config_value(pem): + return "".join(pem.strip().split("\n")[1:-1]) + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if not config.exists(base + ['ssl']): + return + + x509_base = base + ['ssl'] + pki_name = 'openconnect' + + if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + + if config.exists(x509_base + ['ca-cert-file']): + cert_file = config.return_value(x509_base + ['ca-cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['ca-certificate'], value=pki_name) + else: + print(f'Failed to migrate CA certificate on openconnect config') + + config.delete(x509_base + ['ca-cert-file']) + + if config.exists(x509_base + ['cert-file']): + cert_file = config.return_value(x509_base + ['cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['certificate'], value=pki_name) + else: + print(f'Failed to migrate certificate on openconnect config') + + config.delete(x509_base + ['cert-file']) + + if config.exists(x509_base + ['key-file']): + key_file = config.return_value(x509_base + ['key-file']) + key_path = os.path.join(AUTH_DIR, key_file) + key = None + + if os.path.isfile(key_path): + if not os.access(key_path, os.R_OK): + run(f'sudo chmod 644 {key_path}') + + with open(key_path, 'r') as f: + key_data = f.read() + key = load_private_key(key_data, passphrase=None, wrap_tags=False) + + if key: + key_pem = encode_private_key(key, passphrase=None) + config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + else: + print(f'Failed to migrate private key on openconnect config') + + config.delete(x509_base + ['key-file']) diff --git a/src/migration-scripts/openconnect/1-to-2 b/src/migration-scripts/openconnect/1-to-2 new file mode 100644 index 0000000..4f74b44 --- /dev/null +++ b/src/migration-scripts/openconnect/1-to-2 @@ -0,0 +1,35 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Delete depricated outside-nexthop address + +from vyos.configtree import ConfigTree + +cfg_base = ['vpn', 'openconnect'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(cfg_base): + # Nothing to do + return + + if config.exists(cfg_base + ['authentication', 'mode']): + if config.return_value(cfg_base + ['authentication', 'mode']) == 'radius': + # if "mode value radius", change to "mode + valueless node radius" + config.delete_value(cfg_base + ['authentication','mode'], 'radius') + config.set(cfg_base + ['authentication', 'mode', 'radius'], value=None) + elif config.return_value(cfg_base + ['authentication', 'mode']) == 'local': + # if "mode local", change to "mode + node local value password" + config.delete_value(cfg_base + ['authentication', 'mode'], 'local') + config.set(cfg_base + ['authentication', 'mode', 'local'], value='password') diff --git a/src/migration-scripts/openconnect/2-to-3 b/src/migration-scripts/openconnect/2-to-3 new file mode 100644 index 0000000..00e13ec --- /dev/null +++ b/src/migration-scripts/openconnect/2-to-3 @@ -0,0 +1,30 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T4982: Retain prior default TLS version (v1.0) when upgrading installations with existing openconnect configurations + +from vyos.configtree import ConfigTree + +cfg_base = ['vpn', 'openconnect'] + +def migrate(config: ConfigTree) -> None: + # bail out early if service is unconfigured + if not config.exists(cfg_base): + return + + # new default is TLS 1.2 - set explicit old default value of TLS 1.0 for upgraded configurations to keep compatibility + tls_min_path = cfg_base + ['tls-version-min'] + if not config.exists(tls_min_path): + config.set(tls_min_path, value='1.0') diff --git a/src/migration-scripts/openvpn/0-to-1 b/src/migration-scripts/openvpn/0-to-1 new file mode 100644 index 0000000..e5db731 --- /dev/null +++ b/src/migration-scripts/openvpn/0-to-1 @@ -0,0 +1,43 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Removes outdated ciphers (DES and Blowfish) from OpenVPN configs + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + if not config.exists(['interfaces', 'openvpn']): + # Nothing to do + return + + ovpn_intfs = config.list_nodes(['interfaces', 'openvpn']) + for i in ovpn_intfs: + # Remove DES and Blowfish from 'encryption cipher' + cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'cipher'] + if config.exists(cipher_path): + cipher = config.return_value(cipher_path) + if cipher in ['des', 'bf128', 'bf256']: + config.delete(cipher_path) + + ncp_cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'ncp-ciphers'] + if config.exists(ncp_cipher_path): + ncp_ciphers = config.return_values(['interfaces', 'openvpn', i, 'encryption', 'ncp-ciphers']) + if 'des' in ncp_ciphers: + config.delete_value(['interfaces', 'openvpn', i, 'encryption', 'ncp-ciphers'], 'des') + + # Clean up the encryption subtree if the migration procedure left it empty + if config.exists(['interfaces', 'openvpn', i, 'encryption']) and \ + (config.list_nodes(['interfaces', 'openvpn', i, 'encryption']) == []): + config.delete(['interfaces', 'openvpn', i, 'encryption']) diff --git a/src/migration-scripts/openvpn/1-to-2 b/src/migration-scripts/openvpn/1-to-2 new file mode 100644 index 0000000..2baa730 --- /dev/null +++ b/src/migration-scripts/openvpn/1-to-2 @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Removes --cipher option (deprecated) from OpenVPN configs +# and moves it to --data-ciphers for server and client modes + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + ovpn_intfs = config.list_nodes(['interfaces', 'openvpn'], path_must_exist=False) + for i in ovpn_intfs: + # Remove 'encryption cipher' and add this value to 'encryption ncp-ciphers' + # for server and client mode. + # Site-to-site mode still can use --cipher option + cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'cipher'] + ncp_cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'ncp-ciphers'] + if config.exists(cipher_path): + if config.exists(['interfaces', 'openvpn', i, 'shared-secret-key']): + continue + cipher = config.return_value(cipher_path) + config.delete(cipher_path) + if cipher == 'none': + if not config.exists(ncp_cipher_path): + config.delete(['interfaces', 'openvpn', i, 'encryption']) + continue + + ncp_ciphers = [] + if config.exists(ncp_cipher_path): + ncp_ciphers = config.return_values(ncp_cipher_path) + config.delete(ncp_cipher_path) + + # need to add the deleted cipher at the first place in the list + if cipher in ncp_ciphers: + ncp_ciphers.remove(cipher) + ncp_ciphers.insert(0, cipher) + + for c in ncp_ciphers: + config.set(ncp_cipher_path, value=c, replace=False) diff --git a/src/migration-scripts/openvpn/2-to-3 b/src/migration-scripts/openvpn/2-to-3 new file mode 100644 index 0000000..4e6b3c8 --- /dev/null +++ b/src/migration-scripts/openvpn/2-to-3 @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Adds an explicit old default for 'server topology' +# to keep old configs working as before even though the default has changed. + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + ovpn_intfs = config.list_nodes(['interfaces', 'openvpn'], path_must_exist=False) + for i in ovpn_intfs: + mode = config.return_value(['interfaces', 'openvpn', i, 'mode']) + if mode != 'server': + # If it's a client or a site-to-site OpenVPN interface, + # the topology setting is not applicable + # and will cause commit errors on load, + # so we must not change such interfaces. + continue + else: + # The default OpenVPN server topology was changed from net30 to subnet + # because net30 is deprecated and causes problems with Windows clients. + # We add 'net30' to old configs if topology is not set there + # to ensure that if anyone relies on net30, their configs work as before. + topology_path = ['interfaces', 'openvpn', i, 'server', 'topology'] + if not config.exists(topology_path): + config.set(topology_path, value='net30', replace=False) diff --git a/src/migration-scripts/openvpn/3-to-4 b/src/migration-scripts/openvpn/3-to-4 new file mode 100644 index 0000000..0529491 --- /dev/null +++ b/src/migration-scripts/openvpn/3-to-4 @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. +# Renames ncp-ciphers option to data-ciphers + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + ovpn_intfs = config.list_nodes(['interfaces', 'openvpn'], path_must_exist=False) + for i in ovpn_intfs: + #Rename 'encryption ncp-ciphers' with 'encryption data-ciphers' + ncp_cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'ncp-ciphers'] + if config.exists(ncp_cipher_path): + config.rename(ncp_cipher_path, 'data-ciphers') diff --git a/src/migration-scripts/ospf/0-to-1 b/src/migration-scripts/ospf/0-to-1 new file mode 100644 index 0000000..a1f8109 --- /dev/null +++ b/src/migration-scripts/ospf/0-to-1 @@ -0,0 +1,66 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3753: upgrade to FRR8 and move CLI options to better fit with the new FRR CLI + +from vyos.configtree import ConfigTree + +def ospf_passive_migration(config, ospf_base): + if config.exists(ospf_base): + if config.exists(ospf_base + ['passive-interface']): + default = False + for interface in config.return_values(ospf_base + ['passive-interface']): + if interface == 'default': + default = True + continue + config.set(ospf_base + ['interface', interface, 'passive']) + config.set_tag(ospf_base + ['interface']) + + config.delete(ospf_base + ['passive-interface']) + if default: + config.set(ospf_base + ['passive-interface'], value='default') + + if config.exists(ospf_base + ['passive-interface-exclude']): + for interface in config.return_values(ospf_base + ['passive-interface-exclude']): + config.set(ospf_base + ['interface', interface, 'passive', 'disable']) + config.set_tag(ospf_base + ['interface']) + config.delete(ospf_base + ['passive-interface-exclude']) + +ospfv3_base = ['protocols', 'ospfv3'] + +def migrate(config: ConfigTree) -> None: + if config.exists(ospfv3_base): + area_base = ospfv3_base + ['area'] + if config.exists(area_base): + for area in config.list_nodes(area_base): + if not config.exists(area_base + [area, 'interface']): + continue + + for interface in config.return_values(area_base + [area, 'interface']): + config.set(ospfv3_base + ['interface', interface, 'area'], value=area) + config.set_tag(ospfv3_base + ['interface']) + + config.delete(area_base + [area, 'interface']) + + # Migrate OSPF syntax in default VRF + ospf_base = ['protocols', 'ospf'] + ospf_passive_migration(config, ospf_base) + + vrf_base = ['vrf', 'name'] + if config.exists(vrf_base): + for vrf in config.list_nodes(vrf_base): + vrf_ospf_base = vrf_base + [vrf, 'protocols', 'ospf'] + if config.exists(vrf_ospf_base): + ospf_passive_migration(config, vrf_ospf_base) diff --git a/src/migration-scripts/ospf/1-to-2 b/src/migration-scripts/ospf/1-to-2 new file mode 100644 index 0000000..5368d8d --- /dev/null +++ b/src/migration-scripts/ospf/1-to-2 @@ -0,0 +1,60 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5150: Rework CLI definitions to apply route-maps between routing daemons +# and zebra/kernel + +from vyos.configtree import ConfigTree + +ospf_base = ['protocols', 'ospf'] + +def migrate(config: ConfigTree) -> None: + # Check if OSPF is configured - if so, migrate the CLI node + if config.exists(ospf_base): + if config.exists(ospf_base + ['route-map']): + tmp = config.return_value(ospf_base + ['route-map']) + + config.set(['system', 'ip', 'protocol', 'ospf', 'route-map'], value=tmp) + config.set_tag(['system', 'ip', 'protocol']) + config.delete(ospf_base + ['route-map']) + + ospfv3_base = ['protocols', 'ospfv3'] + # Check if OSPFv3 is configured - if so, migrate the CLI node + if config.exists(ospfv3_base): + if config.exists(ospfv3_base + ['route-map']): + tmp = config.return_value(ospfv3_base + ['route-map']) + + config.set(['system', 'ipv6', 'protocol', 'ospfv3', 'route-map'], value=tmp) + config.set_tag(['system', 'ipv6', 'protocol']) + config.delete(ospfv3_base + ['route-map']) + + # Check if vrf names are configured. Check if OSPF/OSPFv3 is configured - if so, + # migrate the CLI node(s) + if config.exists(['vrf', 'name']): + for vrf in config.list_nodes(['vrf', 'name']): + vrf_base = ['vrf', 'name', vrf] + if config.exists(vrf_base + ['protocols', 'ospf', 'route-map']): + tmp = config.return_value(vrf_base + ['protocols', 'ospf', 'route-map']) + + config.set(vrf_base + ['ip', 'protocol', 'ospf', 'route-map'], value=tmp) + config.set_tag(vrf_base + ['ip', 'protocol', 'ospf']) + config.delete(vrf_base + ['protocols', 'ospf', 'route-map']) + + if config.exists(vrf_base + ['protocols', 'ospfv3', 'route-map']): + tmp = config.return_value(vrf_base + ['protocols', 'ospfv3', 'route-map']) + + config.set(vrf_base + ['ipv6', 'protocol', 'ospfv3', 'route-map'], value=tmp) + config.set_tag(vrf_base + ['ipv6', 'protocol', 'ospfv6']) + config.delete(vrf_base + ['protocols', 'ospfv3', 'route-map']) diff --git a/src/migration-scripts/pim/0-to-1 b/src/migration-scripts/pim/0-to-1 new file mode 100644 index 0000000..ce24b23 --- /dev/null +++ b/src/migration-scripts/pim/0-to-1 @@ -0,0 +1,54 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5736: igmp: migrate "protocols igmp" to "protocols pim" + +from vyos.configtree import ConfigTree + +base = ['protocols', 'igmp'] +pim_base = ['protocols', 'pim'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for interface in config.list_nodes(base + ['interface']): + base_igmp_iface = base + ['interface', interface] + pim_base_iface = pim_base + ['interface', interface] + + # Create IGMP note under PIM interface + if not config.exists(pim_base_iface + ['igmp']): + config.set(pim_base_iface + ['igmp']) + + if config.exists(base_igmp_iface + ['join']): + config.copy(base_igmp_iface + ['join'], pim_base_iface + ['igmp', 'join']) + config.set_tag(pim_base_iface + ['igmp', 'join']) + + new_join_base = pim_base_iface + ['igmp', 'join'] + for address in config.list_nodes(new_join_base): + if config.exists(new_join_base + [address, 'source']): + config.rename(new_join_base + [address, 'source'], 'source-address') + + if config.exists(base_igmp_iface + ['query-interval']): + config.copy(base_igmp_iface + ['query-interval'], pim_base_iface + ['igmp', 'query-interval']) + + if config.exists(base_igmp_iface + ['query-max-response-time']): + config.copy(base_igmp_iface + ['query-max-response-time'], pim_base_iface + ['igmp', 'query-max-response-time']) + + if config.exists(base_igmp_iface + ['version']): + config.copy(base_igmp_iface + ['version'], pim_base_iface + ['igmp', 'version']) + + config.delete(base) diff --git a/src/migration-scripts/policy/0-to-1 b/src/migration-scripts/policy/0-to-1 new file mode 100644 index 0000000..837946c --- /dev/null +++ b/src/migration-scripts/policy/0-to-1 @@ -0,0 +1,43 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3631: route-map: migrate "set extcommunity-rt" and "set extcommunity-soo" +# to "set extcommunity rt|soo" to match FRR syntax + +from vyos.configtree import ConfigTree + +base = ['policy', 'route-map'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for route_map in config.list_nodes(base): + if not config.exists(base + [route_map, 'rule']): + continue + for rule in config.list_nodes(base + [route_map, 'rule']): + base_rule = base + [route_map, 'rule', rule] + + if config.exists(base_rule + ['set', 'extcommunity-rt']): + tmp = config.return_value(base_rule + ['set', 'extcommunity-rt']) + config.delete(base_rule + ['set', 'extcommunity-rt']) + config.set(base_rule + ['set', 'extcommunity', 'rt'], value=tmp) + + + if config.exists(base_rule + ['set', 'extcommunity-soo']): + tmp = config.return_value(base_rule + ['set', 'extcommunity-soo']) + config.delete(base_rule + ['set', 'extcommunity-soo']) + config.set(base_rule + ['set', 'extcommunity', 'soo'], value=tmp) diff --git a/src/migration-scripts/policy/1-to-2 b/src/migration-scripts/policy/1-to-2 new file mode 100644 index 0000000..ba3e48d --- /dev/null +++ b/src/migration-scripts/policy/1-to-2 @@ -0,0 +1,67 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T4170: rename "policy ipv6-route" to "policy route6" to match common +# IPv4/IPv6 schema +# T4178: Update tcp flags to use multi value node + +from vyos.configtree import ConfigTree + +base = ['policy'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['ipv6-route']): + config.rename(base + ['ipv6-route'],'route6') + config.set_tag(['policy', 'route6']) + + for route in ['route', 'route6']: + if config.exists(base + [route]): + for name in config.list_nodes(base + [route]): + if config.exists(base + [route, name, 'rule']): + for rule in config.list_nodes(base + [route, name, 'rule']): + rule_tcp_flags = base + [route, name, 'rule', rule, 'tcp', 'flags'] + + if config.exists(rule_tcp_flags): + tmp = config.return_value(rule_tcp_flags) + config.delete(rule_tcp_flags) + for flag in tmp.split(","): + for flag in tmp.split(","): + if flag[0] == '!': + config.set(rule_tcp_flags + ['not', flag[1:].lower()]) + else: + config.set(rule_tcp_flags + [flag.lower()]) + + if config.exists(['interfaces']): + def if_policy_rename(config, path): + if config.exists(path + ['policy', 'ipv6-route']): + config.rename(path + ['policy', 'ipv6-route'], 'route6') + + for if_type in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', if_type]): + if_path = ['interfaces', if_type, ifname] + if_policy_rename(config, if_path) + + for vif_type in ['vif', 'vif-s']: + if config.exists(if_path + [vif_type]): + for vifname in config.list_nodes(if_path + [vif_type]): + if_policy_rename(config, if_path + [vif_type, vifname]) + + if config.exists(if_path + [vif_type, vifname, 'vif-c']): + for vifcname in config.list_nodes(if_path + [vif_type, vifname, 'vif-c']): + if_policy_rename(config, if_path + [vif_type, vifname, 'vif-c', vifcname]) diff --git a/src/migration-scripts/policy/2-to-3 b/src/migration-scripts/policy/2-to-3 new file mode 100644 index 0000000..399a553 --- /dev/null +++ b/src/migration-scripts/policy/2-to-3 @@ -0,0 +1,38 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3976: change cli +# from: set policy route-map FOO rule 10 match ipv6 nexthop 'h:h:h:h:h:h:h:h' +# to: set policy route-map FOO rule 10 match ipv6 nexthop address 'h:h:h:h:h:h:h:h' + +from vyos.configtree import ConfigTree + +base = ['policy', 'route-map'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for route_map in config.list_nodes(base): + if not config.exists(base + [route_map, 'rule']): + continue + for rule in config.list_nodes(base + [route_map, 'rule']): + base_rule = base + [route_map, 'rule', rule] + + if config.exists(base_rule + ['match', 'ipv6', 'nexthop']): + tmp = config.return_value(base_rule + ['match', 'ipv6', 'nexthop']) + config.delete(base_rule + ['match', 'ipv6', 'nexthop']) + config.set(base_rule + ['match', 'ipv6', 'nexthop', 'address'], value=tmp) diff --git a/src/migration-scripts/policy/3-to-4 b/src/migration-scripts/policy/3-to-4 new file mode 100644 index 0000000..5d4959d --- /dev/null +++ b/src/migration-scripts/policy/3-to-4 @@ -0,0 +1,143 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T4660: change cli +# from: set policy route-map FOO rule 10 set community 'TEXT' +# Multiple value +# to: set policy route-map FOO rule 10 set community replace <community> +# Multiple value +# to: set policy route-map FOO rule 10 set community add <community> +# to: set policy route-map FOO rule 10 set community none +# +# from: set policy route-map FOO rule 10 set large-community 'TEXT' +# Multiple value +# to: set policy route-map FOO rule 10 set large-community replace <community> +# Multiple value +# to: set policy route-map FOO rule 10 set large-community add <community> +# to: set policy route-map FOO rule 10 set large-community none +# +# from: set policy route-map FOO rule 10 set extecommunity [rt|soo] 'TEXT' +# Multiple value +# to: set policy route-map FOO rule 10 set extcommunity [rt|soo] <community> + +from vyos.configtree import ConfigTree + + +# Migration function for large and regular communities +def community_migrate(config: ConfigTree, rule: list[str]) -> bool: + """ + + :param config: configuration object + :type config: ConfigTree + :param rule: Path to variable + :type rule: list[str] + :return: True if additive presents in community string + :rtype: bool + """ + community_list = list((config.return_value(rule)).split(" ")) + config.delete(rule) + if 'none' in community_list: + config.set(rule + ['none']) + return False + else: + community_action: str = 'replace' + if 'additive' in community_list: + community_action = 'add' + community_list.remove('additive') + for community in community_list: + config.set(rule + [community_action], value=community, + replace=False) + if community_action == 'replace': + return False + else: + return True + + +# Migration function for extcommunities +def extcommunity_migrate(config: ConfigTree, rule: list[str]) -> None: + """ + + :param config: configuration object + :type config: ConfigTree + :param rule: Path to variable + :type rule: list[str] + """ + # if config.exists(rule + ['bandwidth']): + # bandwidth: str = config.return_value(rule + ['bandwidth']) + # config.delete(rule + ['bandwidth']) + # config.set(rule + ['bandwidth'], value=bandwidth) + + if config.exists(rule + ['rt']): + community_list = list((config.return_value(rule + ['rt'])).split(" ")) + config.delete(rule + ['rt']) + for community in community_list: + config.set(rule + ['rt'], value=community, replace=False) + + if config.exists(rule + ['soo']): + community_list = list((config.return_value(rule + ['soo'])).split(" ")) + config.delete(rule + ['soo']) + for community in community_list: + config.set(rule + ['soo'], value=community, replace=False) + + +base: list[str] = ['policy', 'route-map'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for route_map in config.list_nodes(base): + if not config.exists(base + [route_map, 'rule']): + continue + for rule in config.list_nodes(base + [route_map, 'rule']): + base_rule: list[str] = base + [route_map, 'rule', rule, 'set'] + + # IF additive presents in coummunity then comm-list is redundant + isAdditive: bool = True + #### Change Set community ######## + if config.exists(base_rule + ['community']): + isAdditive = community_migrate(config, + base_rule + ['community']) + + #### Change Set community-list delete migrate ######## + if config.exists(base_rule + ['comm-list', 'comm-list']): + if isAdditive: + tmp = config.return_value( + base_rule + ['comm-list', 'comm-list']) + config.delete(base_rule + ['comm-list']) + config.set(base_rule + ['community', 'delete'], value=tmp) + else: + config.delete(base_rule + ['comm-list']) + + isAdditive = False + #### Change Set large-community ######## + if config.exists(base_rule + ['large-community']): + isAdditive = community_migrate(config, + base_rule + ['large-community']) + + #### Change Set large-community delete by List ######## + if config.exists(base_rule + ['large-comm-list-delete']): + if isAdditive: + tmp = config.return_value( + base_rule + ['large-comm-list-delete']) + config.delete(base_rule + ['large-comm-list-delete']) + config.set(base_rule + ['large-community', 'delete'], + value=tmp) + else: + config.delete(base_rule + ['large-comm-list-delete']) + + #### Change Set extcommunity ######## + extcommunity_migrate(config, base_rule + ['extcommunity']) diff --git a/src/migration-scripts/policy/4-to-5 b/src/migration-scripts/policy/4-to-5 new file mode 100644 index 0000000..0ecfdfd --- /dev/null +++ b/src/migration-scripts/policy/4-to-5 @@ -0,0 +1,106 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T2199: Migrate interface policy nodes to policy route <name> interface <ifname> + +from vyos.configtree import ConfigTree + +base4 = ['policy', 'route'] +base6 = ['policy', 'route6'] + +def delete_orphaned_interface_policy(config, iftype, ifname, vif=None, vifs=None, vifc=None): + """Delete unexpected policy on interfaces in cases when + policy does not exist but inreface has a policy configuration + Example T5941: + set interfaces bonding bond0 vif 995 policy + """ + if_path = ['interfaces', iftype, ifname] + + if vif: + if_path += ['vif', vif] + elif vifs: + if_path += ['vif-s', vifs] + if vifc: + if_path += ['vif-c', vifc] + + if not config.exists(if_path + ['policy']): + return + + config.delete(if_path + ['policy']) + +def migrate_interface(config, iftype, ifname, vif=None, vifs=None, vifc=None): + if_path = ['interfaces', iftype, ifname] + ifname_full = ifname + + if vif: + if_path += ['vif', vif] + ifname_full = f'{ifname}.{vif}' + elif vifs: + if_path += ['vif-s', vifs] + ifname_full = f'{ifname}.{vifs}' + if vifc: + if_path += ['vif-c', vifc] + ifname_full = f'{ifname}.{vifs}.{vifc}' + + if not config.exists(if_path + ['policy']): + return + + if config.exists(if_path + ['policy', 'route']): + route_name = config.return_value(if_path + ['policy', 'route']) + config.set(base4 + [route_name, 'interface'], value=ifname_full, replace=False) + + if config.exists(if_path + ['policy', 'route6']): + route_name = config.return_value(if_path + ['policy', 'route6']) + config.set(base6 + [route_name, 'interface'], value=ifname_full, replace=False) + + config.delete(if_path + ['policy']) + +def migrate(config: ConfigTree) -> None: + if not config.exists(base4) and not config.exists(base6): + # Delete orphaned nodes on interfaces T5941 + for iftype in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', iftype]): + delete_orphaned_interface_policy(config, iftype, ifname) + + if config.exists(['interfaces', iftype, ifname, 'vif']): + for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): + delete_orphaned_interface_policy(config, iftype, ifname, vif=vif) + + if config.exists(['interfaces', iftype, ifname, 'vif-s']): + for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): + delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs) + + if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs, vifc=vifc) + + # Nothing to do + return + + for iftype in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', iftype]): + migrate_interface(config, iftype, ifname) + + if config.exists(['interfaces', iftype, ifname, 'vif']): + for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): + migrate_interface(config, iftype, ifname, vif=vif) + + if config.exists(['interfaces', iftype, ifname, 'vif-s']): + for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): + migrate_interface(config, iftype, ifname, vifs=vifs) + + if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + migrate_interface(config, iftype, ifname, vifs=vifs, vifc=vifc) diff --git a/src/migration-scripts/policy/5-to-6 b/src/migration-scripts/policy/5-to-6 new file mode 100644 index 0000000..acba0b4 --- /dev/null +++ b/src/migration-scripts/policy/5-to-6 @@ -0,0 +1,42 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5165: Migrate policy local-route rule <tag> destination|source + +from vyos.configtree import ConfigTree + +base4 = ['policy', 'local-route'] +base6 = ['policy', 'local-route6'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base4) and not config.exists(base6): + # Nothing to do + return + + # replace 'policy local-route{v6} rule <tag> destination|source <x.x.x.x>' + # => 'policy local-route{v6} rule <tag> destination|source address <x.x.x.x>' + for base in [base4, base6]: + if config.exists(base + ['rule']): + for rule in config.list_nodes(base + ['rule']): + dst_path = base + ['rule', rule, 'destination'] + src_path = base + ['rule', rule, 'source'] + # Destination + if config.exists(dst_path): + for dst_addr in config.return_values(dst_path): + config.set(dst_path + ['address'], value=dst_addr, replace=False) + # Source + if config.exists(src_path): + for src_addr in config.return_values(src_path): + config.set(src_path + ['address'], value=src_addr, replace=False) diff --git a/src/migration-scripts/policy/6-to-7 b/src/migration-scripts/policy/6-to-7 new file mode 100644 index 0000000..69aa703 --- /dev/null +++ b/src/migration-scripts/policy/6-to-7 @@ -0,0 +1,56 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5729: Switch to valueless whenever is possible. +# From + # set policy [route | route6] ... rule <rule> log enable + # set policy [route | route6] ... rule <rule> log disable +# To + # set policy [route | route6] ... rule <rule> log + # Remove command if log=disable + +from vyos.configtree import ConfigTree + +base = ['policy'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for family in ['route', 'route6']: + if config.exists(base + [family]): + + for policy_name in config.list_nodes(base + [family]): + if config.exists(base + [family, policy_name, 'rule']): + for rule in config.list_nodes(base + [family, policy_name, 'rule']): + # Log + if config.exists(base + [family, policy_name, 'rule', rule, 'log']): + log_value = config.return_value(base + [family, policy_name, 'rule', rule, 'log']) + config.delete(base + [family, policy_name, 'rule', rule, 'log']) + if log_value == 'enable': + config.set(base + [family, policy_name, 'rule', rule, 'log']) + # State + if config.exists(base + [family, policy_name, 'rule', rule, 'state']): + flag_enable = 'False' + for state in ['established', 'invalid', 'new', 'related']: + if config.exists(base + [family, policy_name, 'rule', rule, 'state', state]): + state_value = config.return_value(base + [family, policy_name, 'rule', rule, 'state', state]) + config.delete(base + [family, policy_name, 'rule', rule, 'state', state]) + if state_value == 'enable': + config.set(base + [family, policy_name, 'rule', rule, 'state'], value=state, replace=False) + flag_enable = 'True' + if flag_enable == 'False': + config.delete(base + [family, policy_name, 'rule', rule, 'state']) diff --git a/src/migration-scripts/policy/7-to-8 b/src/migration-scripts/policy/7-to-8 new file mode 100644 index 0000000..a887f37 --- /dev/null +++ b/src/migration-scripts/policy/7-to-8 @@ -0,0 +1,36 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5834: Rename 'enable-default-log' to 'default-log' +# From + # set policy [route | route 6] <route> enable-default-log +# To + # set policy [route | route 6] <route> default-log + +from vyos.configtree import ConfigTree + +base = ['policy'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for family in ['route', 'route6']: + if config.exists(base + [family]): + + for policy_name in config.list_nodes(base + [family]): + if config.exists(base + [family, policy_name, 'enable-default-log']): + config.rename(base + [family, policy_name, 'enable-default-log'], 'default-log') diff --git a/src/migration-scripts/pppoe-server/0-to-1 b/src/migration-scripts/pppoe-server/0-to-1 new file mode 100644 index 0000000..8c9a24f --- /dev/null +++ b/src/migration-scripts/pppoe-server/0-to-1 @@ -0,0 +1,33 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Convert "service pppoe-server authentication radius-server node key" +# to: "service pppoe-server authentication radius-server node secret" + +from vyos.configtree import ConfigTree + +base = ['service', 'pppoe-server', 'authentication', 'radius-server'] + +def migrate(ctree: ConfigTree) -> None: + if not ctree.exists(base): + # Nothing to do + return + + nodes = ctree.list_nodes(base) + for node in nodes: + if ctree.exists(base + [node, 'key']): + val = ctree.return_value(base + [node, 'key']) + ctree.set(base + [node, 'secret'], value=val, replace=False) + ctree.delete(base + [node, 'key']) diff --git a/src/migration-scripts/pppoe-server/1-to-2 b/src/migration-scripts/pppoe-server/1-to-2 new file mode 100644 index 0000000..c9c968b --- /dev/null +++ b/src/migration-scripts/pppoe-server/1-to-2 @@ -0,0 +1,41 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# change mppe node to a leaf node with value prefer + +from vyos.configtree import ConfigTree + +base = ['service', 'pppoe-server'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + mppe_base = base + ['ppp-options', 'mppe'] + if config.exists(mppe_base): + # get current values + tmp = config.list_nodes(mppe_base) + # drop node(s) first ... + config.delete(mppe_base) + + print(tmp) + # set new value based on preference + if 'require' in tmp: + config.set(mppe_base, value='require') + elif 'prefer' in tmp: + config.set(mppe_base, value='prefer') + elif 'deny' in tmp: + config.set(mppe_base, value='deny') diff --git a/src/migration-scripts/pppoe-server/10-to-11 b/src/migration-scripts/pppoe-server/10-to-11 new file mode 100644 index 0000000..6bc138b --- /dev/null +++ b/src/migration-scripts/pppoe-server/10-to-11 @@ -0,0 +1,30 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Add the "vlan-mon" option to the configuration to prevent it +# from disappearing from the configuration file + +from vyos.configtree import ConfigTree + +base = ['service', 'pppoe-server'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + for interface in config.list_nodes(base + ['interface']): + base_path = base + ['interface', interface] + if config.exists(base_path + ['vlan']): + config.set(base_path + ['vlan-mon']) diff --git a/src/migration-scripts/pppoe-server/2-to-3 b/src/migration-scripts/pppoe-server/2-to-3 new file mode 100644 index 0000000..160cffd --- /dev/null +++ b/src/migration-scripts/pppoe-server/2-to-3 @@ -0,0 +1,31 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Convert "service pppoe-server interface ethX" to: "service pppoe-server interface ethX {}" + +from vyos.configtree import ConfigTree + +cbase = ['service', 'pppoe-server','interface'] + +def migrate(ctree: ConfigTree) -> None: + if not ctree.exists(cbase): + return + + nics = ctree.return_values(cbase) + # convert leafNode to a tagNode + ctree.set(cbase) + ctree.set_tag(cbase) + for nic in nics: + ctree.set(cbase + [nic]) diff --git a/src/migration-scripts/pppoe-server/3-to-4 b/src/migration-scripts/pppoe-server/3-to-4 new file mode 100644 index 0000000..29dd622 --- /dev/null +++ b/src/migration-scripts/pppoe-server/3-to-4 @@ -0,0 +1,121 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - remove primary/secondary identifier from nameserver + +from vyos.configtree import ConfigTree + +base = ['service', 'pppoe-server'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # Migrate IPv4 DNS servers + dns_base = base + ['dns-servers'] + if config.exists(dns_base): + for server in ['server-1', 'server-2']: + if config.exists(dns_base + [server]): + dns = config.return_value(dns_base + [server]) + config.set(base + ['name-server'], value=dns, replace=False) + + config.delete(dns_base) + + # Migrate IPv6 DNS servers + dns_base = base + ['dnsv6-servers'] + if config.exists(dns_base): + for server in ['server-1', 'server-2', 'server-3']: + if config.exists(dns_base + [server]): + dns = config.return_value(dns_base + [server]) + config.set(base + ['name-server'], value=dns, replace=False) + + config.delete(dns_base) + + # Migrate IPv4 WINS servers + wins_base = base + ['wins-servers'] + if config.exists(wins_base): + for server in ['server-1', 'server-2']: + if config.exists(wins_base + [server]): + wins = config.return_value(wins_base + [server]) + config.set(base + ['wins-server'], value=wins, replace=False) + + config.delete(wins_base) + + # Migrate radius-settings node to RADIUS and use this as base for the + # later migration of the RADIUS servers - this will save a lot of code + radius_settings = base + ['authentication', 'radius-settings'] + if config.exists(radius_settings): + config.rename(radius_settings, 'radius') + + # Migrate RADIUS dynamic author / change of authorisation server + dae_old = base + ['authentication', 'radius', 'dae-server'] + if config.exists(dae_old): + config.rename(dae_old, 'dynamic-author') + dae_new = base + ['authentication', 'radius', 'dynamic-author'] + + if config.exists(dae_new + ['ip-address']): + config.rename(dae_new + ['ip-address'], 'server') + + if config.exists(dae_new + ['secret']): + config.rename(dae_new + ['secret'], 'key') + + # Migrate RADIUS server + radius_server = base + ['authentication', 'radius-server'] + if config.exists(radius_server): + new_base = base + ['authentication', 'radius', 'server'] + config.set(new_base) + config.set_tag(new_base) + for server in config.list_nodes(radius_server): + old_base = radius_server + [server] + config.copy(old_base, new_base + [server]) + + # migrate key + if config.exists(new_base + [server, 'secret']): + config.rename(new_base + [server, 'secret'], 'key') + + # remove old req-limit node + if config.exists(new_base + [server, 'req-limit']): + config.delete(new_base + [server, 'req-limit']) + + config.delete(radius_server) + + # Migrate IPv6 prefixes + ipv6_base = base + ['client-ipv6-pool'] + if config.exists(ipv6_base + ['prefix']): + prefix_old = config.return_values(ipv6_base + ['prefix']) + # delete old prefix CLI nodes + config.delete(ipv6_base + ['prefix']) + # create ned prefix tag node + config.set(ipv6_base + ['prefix']) + config.set_tag(ipv6_base + ['prefix']) + + for p in prefix_old: + prefix = p.split(',')[0] + mask = p.split(',')[1] + config.set(ipv6_base + ['prefix', prefix, 'mask'], value=mask) + + if config.exists(ipv6_base + ['delegate-prefix']): + prefix_old = config.return_values(ipv6_base + ['delegate-prefix']) + # delete old delegate prefix CLI nodes + config.delete(ipv6_base + ['delegate-prefix']) + # create ned delegation tag node + config.set(ipv6_base + ['delegate']) + config.set_tag(ipv6_base + ['delegate']) + + for p in prefix_old: + prefix = p.split(',')[0] + mask = p.split(',')[1] + config.set(ipv6_base + ['delegate', prefix, 'delegation-prefix'], value=mask) diff --git a/src/migration-scripts/pppoe-server/4-to-5 b/src/migration-scripts/pppoe-server/4-to-5 new file mode 100644 index 0000000..03fbfb2 --- /dev/null +++ b/src/migration-scripts/pppoe-server/4-to-5 @@ -0,0 +1,30 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - rename local-ip to gateway-address + +from vyos.configtree import ConfigTree + +base_path = ['service', 'pppoe-server'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base_path): + # Nothing to do + return + + config_gw = base_path + ['local-ip'] + if config.exists(config_gw): + config.rename(config_gw, 'gateway-address') + config.delete(config_gw) diff --git a/src/migration-scripts/pppoe-server/5-to-6 b/src/migration-scripts/pppoe-server/5-to-6 new file mode 100644 index 0000000..13de8f8 --- /dev/null +++ b/src/migration-scripts/pppoe-server/5-to-6 @@ -0,0 +1,33 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - T4703: merge vlan-id and vlan-range to vlan CLI node + +from vyos.configtree import ConfigTree + +base_path = ['service', 'pppoe-server', 'interface'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base_path): + # Nothing to do + return + + for interface in config.list_nodes(base_path): + for vlan in ['vlan-id', 'vlan-range']: + if config.exists(base_path + [interface, vlan]): + print(interface, vlan) + for tmp in config.return_values(base_path + [interface, vlan]): + config.set(base_path + [interface, 'vlan'], value=tmp, replace=False) + config.delete(base_path + [interface, vlan]) diff --git a/src/migration-scripts/pppoe-server/6-to-7 b/src/migration-scripts/pppoe-server/6-to-7 new file mode 100644 index 0000000..79745a0 --- /dev/null +++ b/src/migration-scripts/pppoe-server/6-to-7 @@ -0,0 +1,99 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - move all pool to named pools +# 'start-stop' migrate to namedpool 'default-range-pool' +# 'subnet' migrate to namedpool 'default-subnet-pool' +# 'default-subnet-pool' is the next pool for 'default-range-pool' +# - There is only one gateway-address, take the first which is configured +# - default-pool by migration. +# 1. If authentication mode = 'local' then it is first named pool. +# If there are not named pools, namedless pool will be default. +# 2. If authentication mode = 'radius' then namedless pool will be default + +from vyos.configtree import ConfigTree +from vyos.base import Warning + +base = ['service', 'pppoe-server'] +pool_base = base + ['client-ip-pool'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if not config.exists(pool_base): + return + + default_pool = '' + range_pool_name = 'default-range-pool' + + #Default nameless pools migrations + if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']): + def is_legalrange(ip1: str, ip2: str, mask: str): + from ipaddress import IPv4Interface + interface1 = IPv4Interface(f'{ip1}/{mask}') + interface2 = IPv4Interface(f'{ip2}/{mask}') + return interface1.network.network_address == interface2.network.network_address and interface2.ip > interface1.ip + + start_ip = config.return_value(pool_base + ['start']) + stop_ip = config.return_value(pool_base + ['stop']) + if is_legalrange(start_ip, stop_ip, '24'): + ip_range = f'{start_ip}-{stop_ip}' + config.set(pool_base + [range_pool_name, 'range'], value=ip_range, replace=False) + default_pool = range_pool_name + else: + Warning( + f'PPPoE client-ip-pool range start-ip:{start_ip} and stop-ip:{stop_ip} can not be migrated.') + config.delete(pool_base + ['start']) + config.delete(pool_base + ['stop']) + + if config.exists(pool_base + ['subnet']): + default_pool = range_pool_name + for subnet in config.return_values(pool_base + ['subnet']): + config.set(pool_base + [range_pool_name, 'range'], value=subnet, replace=False) + config.delete(pool_base + ['subnet']) + + gateway = '' + if config.exists(base + ['gateway-address']): + gateway = config.return_value(base + ['gateway-address']) + + #named pool migration + namedpools_base = pool_base + ['name'] + if config.exists(namedpools_base): + if config.exists(base + ['authentication', 'mode']): + if config.return_value(base + ['authentication', 'mode']) == 'local': + if config.list_nodes(namedpools_base): + default_pool = config.list_nodes(namedpools_base)[0] + + for pool_name in config.list_nodes(namedpools_base): + pool_path = namedpools_base + [pool_name] + if config.exists(pool_path + ['subnet']): + subnet = config.return_value(pool_path + ['subnet']) + config.set(pool_base + [pool_name, 'range'], value=subnet, replace=False) + if config.exists(pool_path + ['next-pool']): + next_pool = config.return_value(pool_path + ['next-pool']) + config.set(pool_base + [pool_name, 'next-pool'], value=next_pool) + if not gateway: + if config.exists(pool_path + ['gateway-address']): + gateway = config.return_value(pool_path + ['gateway-address']) + + config.delete(namedpools_base) + + if gateway: + config.set(base + ['gateway-address'], value=gateway) + if default_pool: + config.set(base + ['default-pool'], value=default_pool) + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/pppoe-server/7-to-8 b/src/migration-scripts/pppoe-server/7-to-8 new file mode 100644 index 0000000..90e4fa0 --- /dev/null +++ b/src/migration-scripts/pppoe-server/7-to-8 @@ -0,0 +1,40 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Migrating to named ipv6 pools + +from vyos.configtree import ConfigTree + +base = ['service', 'pppoe-server'] +pool_base = base + ['client-ipv6-pool'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if not config.exists(pool_base): + return + + ipv6_pool_name = 'ipv6-pool' + config.copy(pool_base, pool_base + [ipv6_pool_name]) + + if config.exists(pool_base + ['prefix']): + config.delete(pool_base + ['prefix']) + config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name) + if config.exists(pool_base + ['delegate']): + config.delete(pool_base + ['delegate']) + + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/pppoe-server/8-to-9 b/src/migration-scripts/pppoe-server/8-to-9 new file mode 100644 index 0000000..e7e0aaa --- /dev/null +++ b/src/migration-scripts/pppoe-server/8-to-9 @@ -0,0 +1,48 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Change from 'ccp' to 'disable-ccp' in ppp-option section +# Migration ipv6 options + +from vyos.configtree import ConfigTree + +base = ['service', 'pppoe-server'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + #CCP migration + if config.exists(base + ['ppp-options', 'ccp']): + config.delete(base + ['ppp-options', 'ccp']) + else: + config.set(base + ['ppp-options', 'disable-ccp']) + + #IPV6 options migrations + if config.exists(base + ['ppp-options','ipv6-peer-intf-id']): + intf_peer_id = config.return_value(base + ['ppp-options','ipv6-peer-intf-id']) + if intf_peer_id == 'ipv4': + intf_peer_id = 'ipv4-addr' + config.set(base + ['ppp-options','ipv6-peer-interface-id'], value=intf_peer_id, replace=True) + config.delete(base + ['ppp-options','ipv6-peer-intf-id']) + + if config.exists(base + ['ppp-options','ipv6-intf-id']): + intf_id = config.return_value(base + ['ppp-options','ipv6-intf-id']) + config.set(base + ['ppp-options','ipv6-interface-id'], value=intf_id, replace=True) + config.delete(base + ['ppp-options','ipv6-intf-id']) + + if config.exists(base + ['ppp-options','ipv6-accept-peer-intf-id']): + config.set(base + ['ppp-options','ipv6-accept-peer-interface-id']) + config.delete(base + ['ppp-options','ipv6-accept-peer-intf-id']) diff --git a/src/migration-scripts/pppoe-server/9-to-10 b/src/migration-scripts/pppoe-server/9-to-10 new file mode 100644 index 0000000..d3475e8 --- /dev/null +++ b/src/migration-scripts/pppoe-server/9-to-10 @@ -0,0 +1,38 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Migration of pado-delay options + +from vyos.configtree import ConfigTree + +base = ['service', 'pppoe-server', 'pado-delay'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + pado_delay = {} + for delay in config.list_nodes(base): + sessions = config.return_value(base + [delay, 'sessions']) + pado_delay[delay] = sessions + + # need to define delay for latest sessions + sorted_delays = dict(sorted(pado_delay.items(), key=lambda k_v: int(k_v[1]))) + last_delay = list(sorted_delays)[-1] + + # Rename last delay -> disable + tmp = base + [last_delay] + if config.exists(tmp): + config.rename(tmp, 'disable') diff --git a/src/migration-scripts/pptp/0-to-1 b/src/migration-scripts/pptp/0-to-1 new file mode 100644 index 0000000..dd0b6f5 --- /dev/null +++ b/src/migration-scripts/pptp/0-to-1 @@ -0,0 +1,54 @@ +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Unclutter PPTP VPN configuiration - move radius-server top level tag +# nodes to a regular node which now also configures the radius source address +# used when querying a radius server + +from vyos.configtree import ConfigTree + +cfg_base = ['vpn', 'pptp', 'remote-access', 'authentication'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(cfg_base): + # Nothing to do + return + + # Migrate "vpn pptp authentication radius-source-address" to new + # "vpn pptp authentication radius source-address" + if config.exists(cfg_base + ['radius-source-address']): + address = config.return_value(cfg_base + ['radius-source-address']) + # delete old configuration node + config.delete(cfg_base + ['radius-source-address']) + # write new configuration node + config.set(cfg_base + ['radius', 'source-address'], value=address) + + # Migrate "vpn pptp authentication radius-server" tag node to new + # "vpn pptp authentication radius server" tag node + for server in config.list_nodes(cfg_base + ['radius-server']): + base_server = cfg_base + ['radius-server', server] + key = config.return_value(base_server + ['key']) + + # delete old configuration node + config.delete(base_server) + # write new configuration node + config.set(cfg_base + ['radius', 'server', server, 'key'], value=key) + + # format as tag node + config.set_tag(cfg_base + ['radius', 'server']) + + # delete top level tag node + if config.exists(cfg_base + ['radius-server']): + config.delete(cfg_base + ['radius-server']) diff --git a/src/migration-scripts/pptp/1-to-2 b/src/migration-scripts/pptp/1-to-2 new file mode 100644 index 0000000..1e76011 --- /dev/null +++ b/src/migration-scripts/pptp/1-to-2 @@ -0,0 +1,53 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - migrate dns-servers node to common name-servers +# - remove radios req-limit node + +from vyos.configtree import ConfigTree + +base = ['vpn', 'pptp', 'remote-access'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # Migrate IPv4 DNS servers + dns_base = base + ['dns-servers'] + if config.exists(dns_base): + for server in ['server-1', 'server-2']: + if config.exists(dns_base + [server]): + dns = config.return_value(dns_base + [server]) + config.set(base + ['name-server'], value=dns, replace=False) + + config.delete(dns_base) + + # Migrate IPv4 WINS servers + wins_base = base + ['wins-servers'] + if config.exists(wins_base): + for server in ['server-1', 'server-2']: + if config.exists(wins_base + [server]): + wins = config.return_value(wins_base + [server]) + config.set(base + ['wins-server'], value=wins, replace=False) + + config.delete(wins_base) + + # Remove RADIUS server req-limit node + radius_base = base + ['authentication', 'radius'] + if config.exists(radius_base): + for server in config.list_nodes(radius_base + ['server']): + if config.exists(radius_base + ['server', server, 'req-limit']): + config.delete(radius_base + ['server', server, 'req-limit']) diff --git a/src/migration-scripts/pptp/2-to-3 b/src/migration-scripts/pptp/2-to-3 new file mode 100644 index 0000000..8b0d6d8 --- /dev/null +++ b/src/migration-scripts/pptp/2-to-3 @@ -0,0 +1,55 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - move all pool to named pools +# 'start-stop' migrate to namedpool 'default-range-pool' +# 'default-subnet-pool' is the next pool for 'default-range-pool' + +from vyos.configtree import ConfigTree +from vyos.base import Warning + +base = ['vpn', 'pptp', 'remote-access'] +pool_base = base + ['client-ip-pool'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if not config.exists(pool_base): + return + + range_pool_name = 'default-range-pool' + + if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']): + def is_legalrange(ip1: str, ip2: str, mask: str): + from ipaddress import IPv4Interface + interface1 = IPv4Interface(f'{ip1}/{mask}') + interface2 = IPv4Interface(f'{ip2}/{mask}') + return interface1.network.network_address == interface2.network.network_address and interface2.ip > interface1.ip + + start_ip = config.return_value(pool_base + ['start']) + stop_ip = config.return_value(pool_base + ['stop']) + if is_legalrange(start_ip, stop_ip, '24'): + ip_range = f'{start_ip}-{stop_ip}' + config.set(pool_base + [range_pool_name, 'range'], value=ip_range, replace=False) + config.set(base + ['default-pool'], value=range_pool_name) + else: + Warning( + f'PPTP client-ip-pool range start-ip:{start_ip} and stop-ip:{stop_ip} can not be migrated.') + + config.delete(pool_base + ['start']) + config.delete(pool_base + ['stop']) + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/pptp/3-to-4 b/src/migration-scripts/pptp/3-to-4 new file mode 100644 index 0000000..2dabd84 --- /dev/null +++ b/src/migration-scripts/pptp/3-to-4 @@ -0,0 +1,29 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - Move 'mppe' from 'authentication' node to 'ppp-options' + +from vyos.configtree import ConfigTree + +base = ['vpn', 'pptp', 'remote-access'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if config.exists(base + ['authentication','mppe']): + mppe = config.return_value(base + ['authentication','mppe']) + config.set(base + ['ppp-options', 'mppe'], value=mppe, replace=True) + config.delete(base + ['authentication','mppe']) diff --git a/src/migration-scripts/pptp/4-to-5 b/src/migration-scripts/pptp/4-to-5 new file mode 100644 index 0000000..c906f58 --- /dev/null +++ b/src/migration-scripts/pptp/4-to-5 @@ -0,0 +1,43 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - Move 'require' from 'protocols' in 'authentication' node +# - Migrate to new default values in radius timeout and acct-timeout + +from vyos.configtree import ConfigTree + +base = ['vpn', 'pptp', 'remote-access'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + #migrate require to protocols + require_path = base + ['authentication', 'require'] + if config.exists(require_path): + protocols = list(config.return_values(require_path)) + for protocol in protocols: + config.set(base + ['authentication', 'protocols'], value=protocol, + replace=False) + config.delete(require_path) + else: + config.set(base + ['authentication', 'protocols'], value='mschap-v2') + + radius_path = base + ['authentication', 'radius'] + if config.exists(radius_path): + if not config.exists(radius_path + ['timeout']): + config.set(radius_path + ['timeout'], value=3) + if not config.exists(radius_path + ['acct-timeout']): + config.set(radius_path + ['acct-timeout'], value=3) diff --git a/src/migration-scripts/qos/1-to-2 b/src/migration-scripts/qos/1-to-2 new file mode 100644 index 0000000..c43d8fa --- /dev/null +++ b/src/migration-scripts/qos/1-to-2 @@ -0,0 +1,168 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.base import Warning +from vyos.configtree import ConfigTree +from vyos.utils.file import read_file + +def bandwidth_percent_to_val(interface, percent) -> int: + speed = read_file(f'/sys/class/net/{interface}/speed') + if not speed.isnumeric(): + Warning('Interface speed cannot be determined (assuming 10 Mbit/s)') + speed = 10 + speed = int(speed) *1000000 # convert to MBit/s + return speed * int(percent) // 100 # integer division + + +def delete_orphaned_interface_policy(config, iftype, ifname, vif=None, vifs=None, vifc=None): + """Delete unexpected traffic-policy on interfaces in cases when + policy does not exist but inreface has a policy configuration + Example T5941: + set interfaces bonding bond0 vif 995 traffic-policy + """ + if_path = ['interfaces', iftype, ifname] + + if vif: + if_path += ['vif', vif] + elif vifs: + if_path += ['vif-s', vifs] + if vifc: + if_path += ['vif-c', vifc] + + if not config.exists(if_path + ['traffic-policy']): + return + + config.delete(if_path + ['traffic-policy']) + + +def migrate(config: ConfigTree) -> None: + base = ['traffic-policy'] + + if not config.exists(base): + # Delete orphaned nodes on interfaces T5941 + for iftype in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', iftype]): + delete_orphaned_interface_policy(config, iftype, ifname) + + if config.exists(['interfaces', iftype, ifname, 'vif']): + for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): + delete_orphaned_interface_policy(config, iftype, ifname, vif=vif) + + if config.exists(['interfaces', iftype, ifname, 'vif-s']): + for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): + delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs) + + if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs, vifc=vifc) + + # Nothing to do + return + + iface_config = {} + + if config.exists(['interfaces']): + def get_qos(config, interface, interface_base): + if config.exists(interface_base): + tmp = { interface : {} } + if config.exists(interface_base + ['in']): + tmp[interface]['ingress'] = config.return_value(interface_base + ['in']) + if config.exists(interface_base + ['out']): + tmp[interface]['egress'] = config.return_value(interface_base + ['out']) + config.delete(interface_base) + return tmp + return None + + # Migrate "interface ethernet eth0 traffic-policy in|out" to "qos interface eth0 ingress|egress" + for type in config.list_nodes(['interfaces']): + for interface in config.list_nodes(['interfaces', type]): + interface_base = ['interfaces', type, interface, 'traffic-policy'] + tmp = get_qos(config, interface, interface_base) + if tmp: iface_config.update(tmp) + + vif_path = ['interfaces', type, interface, 'vif'] + if config.exists(vif_path): + for vif in config.list_nodes(vif_path): + vif_interface_base = vif_path + [vif, 'traffic-policy'] + ifname = f'{interface}.{vif}' + tmp = get_qos(config, ifname, vif_interface_base) + if tmp: iface_config.update(tmp) + + vif_s_path = ['interfaces', type, interface, 'vif-s'] + if config.exists(vif_s_path): + for vif_s in config.list_nodes(vif_s_path): + vif_s_interface_base = vif_s_path + [vif_s, 'traffic-policy'] + ifname = f'{interface}.{vif_s}' + tmp = get_qos(config, ifname, vif_s_interface_base) + if tmp: iface_config.update(tmp) + + # vif-c interfaces MUST be migrated before their parent vif-s + # interface as the migrate_*() functions delete the path! + vif_c_path = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c'] + if config.exists(vif_c_path): + for vif_c in config.list_nodes(vif_c_path): + vif_c_interface_base = vif_c_path + [vif_c, 'traffic-policy'] + ifname = f'{interface}.{vif_s}.{vif_c}' + tmp = get_qos(config, ifname, vif_s_interface_base) + if tmp: iface_config.update(tmp) + + + # Now we have the information which interface uses which QoS policy. + # Interface binding will be moved to the qos CLi tree + config.set(['qos']) + config.copy(base, ['qos', 'policy']) + config.delete(base) + + # Now map the interface policy binding to the new CLI syntax + if len(iface_config): + config.set(['qos', 'interface']) + config.set_tag(['qos', 'interface']) + + for interface, interface_config in iface_config.items(): + config.set(['qos', 'interface', interface]) + config.set_tag(['qos', 'interface', interface]) + if 'ingress' in interface_config: + config.set(['qos', 'interface', interface, 'ingress'], value=interface_config['ingress']) + if 'egress' in interface_config: + config.set(['qos', 'interface', interface, 'egress'], value=interface_config['egress']) + + # Remove "burst" CLI node from network emulator + netem_base = ['qos', 'policy', 'network-emulator'] + if config.exists(netem_base): + for policy_name in config.list_nodes(netem_base): + if config.exists(netem_base + [policy_name, 'burst']): + config.delete(netem_base + [policy_name, 'burst']) + + # Change bandwidth unit MBit -> mbit as tc only supports mbit + base = ['qos', 'policy'] + if config.exists(base): + for policy_type in config.list_nodes(base): + for policy in config.list_nodes(base + [policy_type]): + policy_base = base + [policy_type, policy] + if config.exists(policy_base + ['bandwidth']): + tmp = config.return_value(policy_base + ['bandwidth']) + config.set(policy_base + ['bandwidth'], value=tmp.lower()) + + if config.exists(policy_base + ['class']): + for cls in config.list_nodes(policy_base + ['class']): + cls_base = policy_base + ['class', cls] + if config.exists(cls_base + ['bandwidth']): + tmp = config.return_value(cls_base + ['bandwidth']) + config.set(cls_base + ['bandwidth'], value=tmp.lower()) + + if config.exists(policy_base + ['default', 'bandwidth']): + if config.exists(policy_base + ['default', 'bandwidth']): + tmp = config.return_value(policy_base + ['default', 'bandwidth']) + config.set(policy_base + ['default', 'bandwidth'], value=tmp.lower()) diff --git a/src/migration-scripts/quagga/10-to-11 b/src/migration-scripts/quagga/10-to-11 new file mode 100644 index 0000000..15dbbb1 --- /dev/null +++ b/src/migration-scripts/quagga/10-to-11 @@ -0,0 +1,31 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5150: Rework CLI definitions to apply route-maps between routing daemons +# and zebra/kernel + +from vyos.configtree import ConfigTree + +static_base = ['protocols', 'static'] + +def migrate(config: ConfigTree) -> None: + # Check if static routes are configured - if so, migrate the CLI node + if config.exists(static_base): + if config.exists(static_base + ['route-map']): + tmp = config.return_value(static_base + ['route-map']) + + config.set(['system', 'ip', 'protocol', 'static', 'route-map'], value=tmp) + config.set_tag(['system', 'ip', 'protocol']) + config.delete(static_base + ['route-map']) diff --git a/src/migration-scripts/quagga/2-to-3 b/src/migration-scripts/quagga/2-to-3 new file mode 100644 index 0000000..d62c387 --- /dev/null +++ b/src/migration-scripts/quagga/2-to-3 @@ -0,0 +1,181 @@ +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configtree import ConfigTree + + +def migrate_neighbor(config, neighbor_path, neighbor): + if config.exists(neighbor_path): + neighbors = config.list_nodes(neighbor_path) + for neighbor in neighbors: + # Move the valueless options: as-override, next-hop-self, route-reflector-client, route-server-client, + # remove-private-as + for valueless_option in ['as-override', 'nexthop-self', 'route-reflector-client', 'route-server-client', + 'remove-private-as']: + if config.exists(neighbor_path + [neighbor, valueless_option]): + config.set(neighbor_path + [neighbor] + af_path + [valueless_option]) + config.delete(neighbor_path + [neighbor, valueless_option]) + + # Move filter options: distribute-list, filter-list, prefix-list, and route-map + # They share the same syntax inside so we can group them + for filter_type in ['distribute-list', 'filter-list', 'prefix-list', 'route-map']: + if config.exists(neighbor_path + [neighbor, filter_type]): + for filter_dir in ['import', 'export']: + if config.exists(neighbor_path + [neighbor, filter_type, filter_dir]): + filter_name = config.return_value(neighbor_path + [neighbor, filter_type, filter_dir]) + config.set(neighbor_path + [neighbor] + af_path + [filter_type, filter_dir], value=filter_name) + config.delete(neighbor_path + [neighbor, filter_type]) + + # Move simple leaf node options: maximum-prefix, unsuppress-map, weight + for leaf_option in ['maximum-prefix', 'unsuppress-map', 'weight']: + if config.exists(neighbor_path + [neighbor, leaf_option]): + if config.exists(neighbor_path + [neighbor, leaf_option]): + leaf_opt_value = config.return_value(neighbor_path + [neighbor, leaf_option]) + config.set(neighbor_path + [neighbor] + af_path + [leaf_option], value=leaf_opt_value) + config.delete(neighbor_path + [neighbor, leaf_option]) + + # The rest is special cases, for better or worse + + # Move allowas-in + if config.exists(neighbor_path + [neighbor, 'allowas-in']): + if config.exists(neighbor_path + [neighbor, 'allowas-in', 'number']): + allowas_in = config.return_value(neighbor_path + [neighbor, 'allowas-in', 'number']) + config.set(neighbor_path + [neighbor] + af_path + ['allowas-in', 'number'], value=allowas_in) + config.delete(neighbor_path + [neighbor, 'allowas-in']) + + # Move attribute-unchanged options + if config.exists(neighbor_path + [neighbor, 'attribute-unchanged']): + for attr in ['as-path', 'med', 'next-hop']: + if config.exists(neighbor_path + [neighbor, 'attribute-unchanged', attr]): + config.set(neighbor_path + [neighbor] + af_path + ['attribute-unchanged', attr]) + config.delete(neighbor_path + [neighbor, 'attribute-unchanged', attr]) + config.delete(neighbor_path + [neighbor, 'attribute-unchanged']) + + # Move capability options + if config.exists(neighbor_path + [neighbor, 'capability']): + # "capability dynamic" is a peer-global option, we only migrate ORF + if config.exists(neighbor_path + [neighbor, 'capability', 'orf']): + if config.exists(neighbor_path + [neighbor, 'capability', 'orf', 'prefix-list']): + for orf in ['send', 'receive']: + if config.exists(neighbor_path + [neighbor, 'capability', 'orf', 'prefix-list', orf]): + config.set(neighbor_path + [neighbor] + af_path + ['capability', 'orf', 'prefix-list', orf]) + config.delete(neighbor_path + [neighbor, 'capability', 'orf', 'prefix-list', orf]) + config.delete(neighbor_path + [neighbor, 'capability', 'orf', 'prefix-list']) + config.delete(neighbor_path + [neighbor, 'capability', 'orf']) + + # Move default-originate + if config.exists(neighbor_path + [neighbor, 'default-originate']): + if config.exists(neighbor_path + [neighbor, 'default-originate', 'route-map']): + route_map = config.return_value(neighbor_path + [neighbor, 'default-originate', 'route-map']) + config.set(neighbor_path + [neighbor] + af_path + ['default-originate', 'route-map'], value=route_map) + else: + # Empty default-originate node is meaningful so we re-create it + config.set(neighbor_path + [neighbor] + af_path + ['default-originate']) + config.delete(neighbor_path + [neighbor, 'default-originate']) + + # Move soft-reconfiguration + if config.exists(neighbor_path + [neighbor, 'soft-reconfiguration']): + if config.exists(neighbor_path + [neighbor, 'soft-reconfiguration', 'inbound']): + config.set(neighbor_path + [neighbor] + af_path + ['soft-reconfiguration', 'inbound']) + # Empty soft-reconfiguration is meaningless, so we just remove it + config.delete(neighbor_path + [neighbor, 'soft-reconfiguration']) + + # Move disable-send-community + if config.exists(neighbor_path + [neighbor, 'disable-send-community']): + for comm_type in ['standard', 'extended']: + if config.exists(neighbor_path + [neighbor, 'disable-send-community', comm_type]): + config.set(neighbor_path + [neighbor] + af_path + ['disable-send-community', comm_type]) + config.delete(neighbor_path + [neighbor, 'disable-send-community', comm_type]) + config.delete(neighbor_path + [neighbor, 'disable-send-community']) + + +def migrate(config: ConfigTree) -> None: + if not config.exists(['protocols', 'bgp']): + # Nothing to do + return + + # Just to avoid writing it so many times + af_path = ['address-family', 'ipv4-unicast'] + + # Check if BGP is actually configured and obtain the ASN + asn_list = config.list_nodes(['protocols', 'bgp']) + if asn_list: + # There's always just one BGP node, if any + asn = asn_list[0] + bgp_path = ['protocols', 'bgp', asn] + else: + # There's actually no BGP, just its empty shell + return + + ## Move global IPv4-specific BGP options to "address-family ipv4-unicast" + + # Move networks + network_path = ['protocols', 'bgp', asn, 'network'] + if config.exists(network_path): + config.set(bgp_path + af_path + ['network']) + config.set_tag(bgp_path + af_path + ['network']) + + networks = config.list_nodes(network_path) + for network in networks: + config.set(bgp_path + af_path + ['network', network]) + if config.exists(network_path + [network, 'route-map']): + route_map = config.return_value(network_path + [network, 'route-map']) + config.set(bgp_path + af_path + ['network', network, 'route-map'], value=route_map) + config.delete(network_path) + + # Move aggregate-address statements + aggregate_path = ['protocols', 'bgp', asn, 'aggregate-address'] + if config.exists(aggregate_path): + config.set(bgp_path + af_path + ['aggregate-address']) + config.set_tag(bgp_path + af_path + ['aggregate-address']) + + aggregates = config.list_nodes(aggregate_path) + for aggregate in aggregates: + config.set(bgp_path + af_path + ['aggregate-address', aggregate]) + if config.exists(aggregate_path + [aggregate, 'as-set']): + config.set(bgp_path + af_path + ['aggregate-address', aggregate, 'as-set']) + if config.exists(aggregate_path + [aggregate, 'summary-only']): + config.set(bgp_path + af_path + ['aggregate-address', aggregate, 'summary-only']) + config.delete(aggregate_path) + + ## Migrate neighbor options + neighbor_path = ['protocols', 'bgp', asn, 'neighbor'] + if config.exists(neighbor_path): + neighbors = config.list_nodes(neighbor_path) + for neighbor in neighbors: + migrate_neighbor(config, neighbor_path, neighbor) + + peer_group_path = ['protocols', 'bgp', asn, 'peer-group'] + if config.exists(peer_group_path): + peer_groups = config.list_nodes(peer_group_path) + for peer_group in peer_groups: + migrate_neighbor(config, peer_group_path, peer_group) + + ## Migrate redistribute statements + redistribute_path = ['protocols', 'bgp', asn, 'redistribute'] + if config.exists(redistribute_path): + config.set(bgp_path + af_path + ['redistribute']) + + redistributes = config.list_nodes(redistribute_path) + for redistribute in redistributes: + config.set(bgp_path + af_path + ['redistribute', redistribute]) + if config.exists(redistribute_path + [redistribute, 'metric']): + redist_metric = config.return_value(redistribute_path + [redistribute, 'metric']) + config.set(bgp_path + af_path + ['redistribute', redistribute, 'metric'], value=redist_metric) + if config.exists(redistribute_path + [redistribute, 'route-map']): + redist_route_map = config.return_value(redistribute_path + [redistribute, 'route-map']) + config.set(bgp_path + af_path + ['redistribute', redistribute, 'route-map'], value=redist_route_map) + + config.delete(redistribute_path) diff --git a/src/migration-scripts/quagga/3-to-4 b/src/migration-scripts/quagga/3-to-4 new file mode 100644 index 0000000..81cf139 --- /dev/null +++ b/src/migration-scripts/quagga/3-to-4 @@ -0,0 +1,52 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Between 1.2.3 and 1.2.4, FRR added per-neighbor enforce-first-as option. +# Unfortunately they also removed the global enforce-first-as option, +# which broke all old configs that used to have it. +# +# To emulate the effect of the original option, we insert it in every neighbor +# if the config used to have the original global option + +from vyos.configtree import ConfigTree + + +def migrate(config: ConfigTree) -> None: + if not config.exists(['protocols', 'bgp']): + # Nothing to do + return + + # Check if BGP is actually configured and obtain the ASN + asn_list = config.list_nodes(['protocols', 'bgp']) + if asn_list: + # There's always just one BGP node, if any + asn = asn_list[0] + else: + # There's actually no BGP, just its empty shell + return + + # Check if BGP enforce-first-as option is set + enforce_first_as_path = ['protocols', 'bgp', asn, 'parameters', 'enforce-first-as'] + if config.exists(enforce_first_as_path): + # Delete the obsolete option + config.delete(enforce_first_as_path) + + # Now insert it in every peer + peers = config.list_nodes(['protocols', 'bgp', asn, 'neighbor']) + for p in peers: + config.set(['protocols', 'bgp', asn, 'neighbor', p, 'enforce-first-as']) + else: + # Do nothing + return diff --git a/src/migration-scripts/quagga/4-to-5 b/src/migration-scripts/quagga/4-to-5 new file mode 100644 index 0000000..27b9954 --- /dev/null +++ b/src/migration-scripts/quagga/4-to-5 @@ -0,0 +1,40 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configtree import ConfigTree + + +def migrate(config: ConfigTree) -> None: + if not config.exists(['protocols', 'bgp']): + # Nothing to do + return + + # Check if BGP is actually configured and obtain the ASN + asn_list = config.list_nodes(['protocols', 'bgp']) + if asn_list: + # There's always just one BGP node, if any + asn = asn_list[0] + else: + # There's actually no BGP, just its empty shell + return + + # Check if BGP scan-time parameter exist + scan_time_param = ['protocols', 'bgp', asn, 'parameters', 'scan-time'] + if config.exists(scan_time_param): + # Delete BGP scan-time parameter + config.delete(scan_time_param) + else: + # Do nothing + return diff --git a/src/migration-scripts/quagga/5-to-6 b/src/migration-scripts/quagga/5-to-6 new file mode 100644 index 0000000..08fd070 --- /dev/null +++ b/src/migration-scripts/quagga/5-to-6 @@ -0,0 +1,40 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configtree import ConfigTree + + +def migrate(config: ConfigTree) -> None: + if not config.exists(['protocols', 'bgp']): + # Nothing to do + return + + # Check if BGP is actually configured and obtain the ASN + asn_list = config.list_nodes(['protocols', 'bgp']) + if asn_list: + # There's always just one BGP node, if any + asn = asn_list[0] + else: + # There's actually no BGP, just its empty shell + return + + # Check if BGP parameter disable-network-import-check exists + param = ['protocols', 'bgp', asn, 'parameters', 'disable-network-import-check'] + if config.exists(param): + # Delete parameter + config.delete(param) + else: + # Do nothing + return diff --git a/src/migration-scripts/quagga/6-to-7 b/src/migration-scripts/quagga/6-to-7 new file mode 100644 index 0000000..095baac --- /dev/null +++ b/src/migration-scripts/quagga/6-to-7 @@ -0,0 +1,97 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - T3037, BGP address-family ipv6-unicast capability dynamic does not exist in +# FRR, there is only a base, per neighbor dynamic capability, migrate config + +from vyos.configtree import ConfigTree +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 + +base = ['protocols', 'bgp'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # Check if BGP is actually configured and obtain the ASN + asn_list = config.list_nodes(base) + if asn_list: + # There's always just one BGP node, if any + bgp_base = base + [asn_list[0]] + + for neighbor_type in ['neighbor', 'peer-group']: + if not config.exists(bgp_base + [neighbor_type]): + continue + for neighbor in config.list_nodes(bgp_base + [neighbor_type]): + # T2844 - add IPv4 AFI disable-send-community support + send_comm_path = bgp_base + [neighbor_type, neighbor, 'disable-send-community'] + if config.exists(send_comm_path): + new_base = bgp_base + [neighbor_type, neighbor, 'address-family', 'ipv4-unicast'] + config.set(new_base) + config.copy(send_comm_path, new_base + ['disable-send-community']) + config.delete(send_comm_path) + + cap_dynamic = False + peer_group = None + for afi in ['ipv4-unicast', 'ipv6-unicast']: + afi_path = bgp_base + [neighbor_type, neighbor, 'address-family', afi] + # Exit loop early if AFI does not exist + if not config.exists(afi_path): + continue + + cap_path = afi_path + ['capability', 'dynamic'] + if config.exists(cap_path): + cap_dynamic = True + config.delete(cap_path) + + # We have now successfully migrated the address-family + # specific dynamic capability to the neighbor/peer-group + # level. If this has been the only option under the + # address-family nodes, we can clean them up by checking if + # no other nodes are left under that tree and if so, delete + # the parent. + # + # We walk from the most inner node to the most outer one. + cleanup = -1 + while len(config.list_nodes(cap_path[:cleanup])) == 0: + config.delete(cap_path[:cleanup]) + cleanup -= 1 + + peer_group_path = afi_path + ['peer-group'] + if config.exists(peer_group_path): + if ((is_ipv4(neighbor) and afi == 'ipv4-unicast') or + (is_ipv6(neighbor) and afi == 'ipv6-unicast')): + peer_group = config.return_value(peer_group_path) + + config.delete(peer_group_path) + + # We have now successfully migrated the address-family + # specific peer-group to the neighbor level. If this has + # been the only option under the address-family nodes, we + # can clean them up by checking if no other nodes are left + # under that tree and if so, delete the parent. + # + # We walk from the most inner node to the most outer one. + cleanup = -1 + while len(config.list_nodes(peer_group_path[:cleanup])) == 0: + config.delete(peer_group_path[:cleanup]) + cleanup -= 1 + + if cap_dynamic: + config.set(bgp_base + [neighbor_type, neighbor, 'capability', 'dynamic']) + if peer_group: + config.set(bgp_base + [neighbor_type, neighbor, 'peer-group'], value=peer_group) diff --git a/src/migration-scripts/quagga/7-to-8 b/src/migration-scripts/quagga/7-to-8 new file mode 100644 index 0000000..d9de26d --- /dev/null +++ b/src/migration-scripts/quagga/7-to-8 @@ -0,0 +1,42 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - T3391: Migrate "maximum-paths" setting from "protocols bgp asn maximum-paths" +# under the IPv4 address-family tree. Reason is we currently have no way in +# configuring this for IPv6 address-family. This mimics the FRR configuration. + +from vyos.configtree import ConfigTree + +base = ['protocols', 'bgp'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # Check if BGP is actually configured and obtain the ASN + asn_list = config.list_nodes(base) + if asn_list: + # There's always just one BGP node, if any + bgp_base = base + [asn_list[0]] + + maximum_paths = bgp_base + ['maximum-paths'] + if config.exists(maximum_paths): + for bgp_type in ['ebgp', 'ibgp']: + if config.exists(maximum_paths + [bgp_type]): + new_base = bgp_base + ['address-family', 'ipv4-unicast', 'maximum-paths'] + config.set(new_base) + config.copy(maximum_paths + [bgp_type], new_base + [bgp_type]) + config.delete(maximum_paths) diff --git a/src/migration-scripts/quagga/8-to-9 b/src/migration-scripts/quagga/8-to-9 new file mode 100644 index 0000000..eece6c1 --- /dev/null +++ b/src/migration-scripts/quagga/8-to-9 @@ -0,0 +1,117 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - T2450: drop interface-route and interface-route6 from "protocols static" + +from vyos.configtree import ConfigTree + +def migrate_interface_route(config, base, path, route_route6): + """ Generic migration function which can be called on every instance of + interface-route, beeing it ipv4, ipv6 or nested under the "static table" nodes. + + What we do? + - Drop 'interface-route' or 'interface-route6' and migrate the route unter the + 'route' or 'route6' tag node. + """ + if config.exists(base + path): + for route in config.list_nodes(base + path): + interface = config.list_nodes(base + path + [route, 'next-hop-interface']) + + tmp = base + path + [route, 'next-hop-interface'] + for interface in config.list_nodes(tmp): + new_base = base + [route_route6, route, 'interface'] + config.set(new_base) + config.set_tag(base + [route_route6]) + config.set_tag(new_base) + config.copy(tmp + [interface], new_base + [interface]) + + config.delete(base + path) + +def migrate_route(config, base, path, route_route6): + """ Generic migration function which can be called on every instance of + route, beeing it ipv4, ipv6 or even nested under the static table nodes. + + What we do? + - for consistency reasons rename next-hop-interface to interface + - for consistency reasons rename next-hop-vrf to vrf + """ + if config.exists(base + path): + for route in config.list_nodes(base + path): + next_hop = base + path + [route, 'next-hop'] + if config.exists(next_hop): + for gateway in config.list_nodes(next_hop): + # IPv4 routes calls it next-hop-interface, rename this to + # interface instead so it's consitent with IPv6 + interface_path = next_hop + [gateway, 'next-hop-interface'] + if config.exists(interface_path): + config.rename(interface_path, 'interface') + + # When VRFs got introduced, I (c-po) named it next-hop-vrf, + # we can also call it vrf which is simply shorter. + vrf_path = next_hop + [gateway, 'next-hop-vrf'] + if config.exists(vrf_path): + config.rename(vrf_path, 'vrf') + + next_hop = base + path + [route, 'interface'] + if config.exists(next_hop): + for interface in config.list_nodes(next_hop): + # IPv4 routes calls it next-hop-interface, rename this to + # interface instead so it's consitent with IPv6 + interface_path = next_hop + [interface, 'next-hop-interface'] + if config.exists(interface_path): + config.rename(interface_path, 'interface') + + # When VRFs got introduced, I (c-po) named it next-hop-vrf, + # we can also call it vrf which is simply shorter. + vrf_path = next_hop + [interface, 'next-hop-vrf'] + if config.exists(vrf_path): + config.rename(vrf_path, 'vrf') + + +base = ['protocols', 'static'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # Migrate interface-route into route + migrate_interface_route(config, base, ['interface-route'], 'route') + + # Migrate interface-route6 into route6 + migrate_interface_route(config, base, ['interface-route6'], 'route6') + + # Cleanup nodes inside route + migrate_route(config, base, ['route'], 'route') + + # Cleanup nodes inside route6 + migrate_route(config, base, ['route6'], 'route6') + + # + # PBR table cleanup + table_path = base + ['table'] + if config.exists(table_path): + for table in config.list_nodes(table_path): + # Migrate interface-route into route + migrate_interface_route(config, table_path + [table], ['interface-route'], 'route') + + # Migrate interface-route6 into route6 + migrate_interface_route(config, table_path + [table], ['interface-route6'], 'route6') + + # Cleanup nodes inside route + migrate_route(config, table_path + [table], ['route'], 'route') + + # Cleanup nodes inside route6 + migrate_route(config, table_path + [table], ['route6'], 'route6') diff --git a/src/migration-scripts/quagga/9-to-10 b/src/migration-scripts/quagga/9-to-10 new file mode 100644 index 0000000..4ac1f0b --- /dev/null +++ b/src/migration-scripts/quagga/9-to-10 @@ -0,0 +1,42 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# re-organize route-map as-path + +from vyos.configtree import ConfigTree + +base = ['policy', 'route-map'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for route_map in config.list_nodes(base): + # Bail out Early + if not config.exists(base + [route_map, 'rule']): + continue + + for rule in config.list_nodes(base + [route_map, 'rule']): + rule_base = base + [route_map, 'rule', rule] + if config.exists(rule_base + ['set', 'as-path-exclude']): + tmp = config.return_value(rule_base + ['set', 'as-path-exclude']) + config.delete(rule_base + ['set', 'as-path-exclude']) + config.set(rule_base + ['set', 'as-path', 'exclude'], value=tmp) + + if config.exists(rule_base + ['set', 'as-path-prepend']): + tmp = config.return_value(rule_base + ['set', 'as-path-prepend']) + config.delete(rule_base + ['set', 'as-path-prepend']) + config.set(rule_base + ['set', 'as-path', 'prepend'], value=tmp) diff --git a/src/migration-scripts/reverse-proxy/0-to-1 b/src/migration-scripts/reverse-proxy/0-to-1 new file mode 100644 index 0000000..b495474 --- /dev/null +++ b/src/migration-scripts/reverse-proxy/0-to-1 @@ -0,0 +1,31 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T6409: Remove unused 'backend bk-example parameters' node + +from vyos.configtree import ConfigTree + +base = ['load-balancing', 'reverse-proxy', 'backend'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # we need to run this for every configured network + for backend in config.list_nodes(base): + param_node = base + [backend, 'parameters'] + if config.exists(param_node): + config.delete(param_node) diff --git a/src/migration-scripts/rip/0-to-1 b/src/migration-scripts/rip/0-to-1 new file mode 100644 index 0000000..6d41bcf --- /dev/null +++ b/src/migration-scripts/rip/0-to-1 @@ -0,0 +1,31 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5150: Rework CLI definitions to apply route-maps between routing daemons +# and zebra/kernel + +from vyos.configtree import ConfigTree + +ripng_base = ['protocols', 'ripng'] + +def migrate(config: ConfigTree) -> None: + # Check if RIPng is configured - if so, migrate the CLI node + if config.exists(ripng_base): + if config.exists(ripng_base + ['route-map']): + tmp = config.return_value(ripng_base + ['route-map']) + + config.set(['system', 'ipv6', 'protocol', 'ripng', 'route-map'], value=tmp) + config.set_tag(['system', 'ipv6', 'protocol']) + config.delete(ripng_base + ['route-map']) diff --git a/src/migration-scripts/rpki/0-to-1 b/src/migration-scripts/rpki/0-to-1 new file mode 100644 index 0000000..b6e781f --- /dev/null +++ b/src/migration-scripts/rpki/0-to-1 @@ -0,0 +1,44 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configtree import ConfigTree + +base = ['protocols', 'rpki'] + +def migrate(config: ConfigTree) -> None: + # Nothing to do + if not config.exists(base): + return + + if config.exists(base + ['cache']): + preference = 1 + for cache in config.list_nodes(base + ['cache']): + address_node = base + ['cache', cache, 'address'] + if config.exists(address_node): + address = config.return_value(address_node) + # We do not longer support the address leafNode, RPKI cache server + # IP address is now used from the tagNode + config.delete(address_node) + # VyOS 1.2 had no per instance preference, setting new defaults + config.set(base + ['cache', cache, 'preference'], value=preference) + # Increase preference for the next caching peer - actually VyOS 1.2 + # supported only one but better save then sorry (T3253) + preference += 1 + + # T3293: If the RPKI cache name equals the configured address, + # renaming is not possible, as rename expects the new path to not + # exist. + if not config.exists(base + ['cache', address]): + config.rename(base + ['cache', cache], address) diff --git a/src/migration-scripts/rpki/1-to-2 b/src/migration-scripts/rpki/1-to-2 new file mode 100644 index 0000000..855236d --- /dev/null +++ b/src/migration-scripts/rpki/1-to-2 @@ -0,0 +1,53 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T6011: rpki: known-hosts-file is no longer supported bxy FRR CLI, +# remove VyOS CLI node + +from vyos.configtree import ConfigTree +from vyos.pki import OPENSSH_KEY_BEGIN +from vyos.pki import OPENSSH_KEY_END +from vyos.utils.file import read_file + +base = ['protocols', 'rpki'] + +def migrate(config: ConfigTree) -> None: + # Nothing to do + if not config.exists(base): + return + + if config.exists(base + ['cache']): + for cache in config.list_nodes(base + ['cache']): + ssh_node = base + ['cache', cache, 'ssh'] + if config.exists(ssh_node + ['known-hosts-file']): + config.delete(ssh_node + ['known-hosts-file']) + + if config.exists(base + ['cache', cache, 'ssh']): + private_key_node = base + ['cache', cache, 'ssh', 'private-key-file'] + private_key_file = config.return_value(private_key_node) + private_key = read_file(private_key_file).replace(OPENSSH_KEY_BEGIN, '').replace(OPENSSH_KEY_END, '').replace('\n','') + + public_key_node = base + ['cache', cache, 'ssh', 'public-key-file'] + public_key_file = config.return_value(public_key_node) + public_key = read_file(public_key_file).split() + + config.set(['pki', 'openssh', f'rpki-{cache}', 'private', 'key'], value=private_key) + config.set(['pki', 'openssh', f'rpki-{cache}', 'public', 'key'], value=public_key[1]) + config.set(['pki', 'openssh', f'rpki-{cache}', 'public', 'type'], value=public_key[0]) + config.set_tag(['pki', 'openssh']) + config.set(ssh_node + ['key'], value=f'rpki-{cache}') + + config.delete(private_key_node) + config.delete(public_key_node) diff --git a/src/migration-scripts/salt/0-to-1 b/src/migration-scripts/salt/0-to-1 new file mode 100644 index 0000000..3990a88 --- /dev/null +++ b/src/migration-scripts/salt/0-to-1 @@ -0,0 +1,38 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Delete log_file, log_level and user nodes +# rename hash_type to hash +# rename mine_interval to interval + +from vyos.configtree import ConfigTree + +base = ['service', 'salt-minion'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # delete nodes which are now populated with sane defaults + for node in ['log_file', 'log_level', 'user']: + if config.exists(base + [node]): + config.delete(base + [node]) + + if config.exists(base + ['hash_type']): + config.rename(base + ['hash_type'], 'hash') + + if config.exists(base + ['mine_interval']): + config.rename(base + ['mine_interval'], 'interval') diff --git a/src/migration-scripts/snmp/0-to-1 b/src/migration-scripts/snmp/0-to-1 new file mode 100644 index 0000000..03b190c --- /dev/null +++ b/src/migration-scripts/snmp/0-to-1 @@ -0,0 +1,38 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configtree import ConfigTree + +config_base = ['service', 'snmp', 'v3'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(config_base): + # Nothing to do + return + + # we no longer support a per trap target engine ID (https://vyos.dev/T818) + if config.exists(config_base + ['v3', 'trap-target']): + for target in config.list_nodes(config_base + ['v3', 'trap-target']): + config.delete(config_base + ['v3', 'trap-target', target, 'engineid']) + + # we no longer support a per user engine ID (https://vyos.dev/T818) + if config.exists(config_base + ['v3', 'user']): + for user in config.list_nodes(config_base + ['v3', 'user']): + config.delete(config_base + ['v3', 'user', user, 'engineid']) + + # we drop TSM support as there seem to be no users and this code is untested + # https://vyos.dev/T1769 + if config.exists(config_base + ['v3', 'tsm']): + config.delete(config_base + ['v3', 'tsm']) diff --git a/src/migration-scripts/snmp/1-to-2 b/src/migration-scripts/snmp/1-to-2 new file mode 100644 index 0000000..0120f8a --- /dev/null +++ b/src/migration-scripts/snmp/1-to-2 @@ -0,0 +1,70 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configtree import ConfigTree + +# We no longer support hashed values prefixed with '0x' to unclutter +# CLI and also calculate the hases in advance instead of retrieving +# them after service startup - which was always a bad idea +prefix = '0x' + +def migrate_keys(config, path): + # authentication: rename node 'encrypted-key' -> 'encrypted-password' + config_path_auth = path + ['auth', 'encrypted-key'] + if config.exists(config_path_auth): + config.rename(config_path_auth, 'encrypted-password') + config_path_auth = path + ['auth', 'encrypted-password'] + + # remove leading '0x' from string if present + tmp = config.return_value(config_path_auth) + if tmp.startswith(prefix): + tmp = tmp.replace(prefix, '') + config.set(config_path_auth, value=tmp) + + # privacy: rename node 'encrypted-key' -> 'encrypted-password' + config_path_priv = path + ['privacy', 'encrypted-key'] + if config.exists(config_path_priv): + config.rename(config_path_priv, 'encrypted-password') + config_path_priv = path + ['privacy', 'encrypted-password'] + + # remove leading '0x' from string if present + tmp = config.return_value(config_path_priv) + if tmp.startswith(prefix): + tmp = tmp.replace(prefix, '') + config.set(config_path_priv, value=tmp) + +def migrate(config: ConfigTree) -> None: + config_base = ['service', 'snmp', 'v3'] + + if not config.exists(config_base): + # Nothing to do + return + + config_engineid = config_base + ['engineid'] + if config.exists(config_engineid): + tmp = config.return_value(config_engineid) + if tmp.startswith(prefix): + tmp = tmp.replace(prefix, '') + config.set(config_engineid, value=tmp) + + config_user = config_base + ['user'] + if config.exists(config_user): + for user in config.list_nodes(config_user): + migrate_keys(config, config_user + [user]) + + config_trap = config_base + ['trap-target'] + if config.exists(config_trap): + for trap in config.list_nodes(config_trap): + migrate_keys(config, config_trap + [trap]) diff --git a/src/migration-scripts/snmp/2-to-3 b/src/migration-scripts/snmp/2-to-3 new file mode 100644 index 0000000..6d828b6 --- /dev/null +++ b/src/migration-scripts/snmp/2-to-3 @@ -0,0 +1,33 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T4857: Implement FRR SNMP recomendations +# cli changes from: +# set service snmp oid-enable route-table +# To +# set service snmp oid-enable ip-forward + +from vyos.configtree import ConfigTree + +base = ['service snmp'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['oid-enable']): + config.delete(base + ['oid-enable']) + config.set(base + ['oid-enable'], 'ip-forward') diff --git a/src/migration-scripts/ssh/0-to-1 b/src/migration-scripts/ssh/0-to-1 new file mode 100644 index 0000000..65b68f5 --- /dev/null +++ b/src/migration-scripts/ssh/0-to-1 @@ -0,0 +1,26 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Delete "service ssh allow-root" option + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + if not config.exists(['service', 'ssh', 'allow-root']): + # Nothing to do + return + + # Delete node with abandoned command + config.delete(['service', 'ssh', 'allow-root']) diff --git a/src/migration-scripts/ssh/1-to-2 b/src/migration-scripts/ssh/1-to-2 new file mode 100644 index 0000000..b601db3 --- /dev/null +++ b/src/migration-scripts/ssh/1-to-2 @@ -0,0 +1,63 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# VyOS 1.2 crux allowed configuring a lower or upper case loglevel. This +# is no longer supported as the input data is validated and will lead to +# an error. If user specifies an upper case logleve, make it lowercase + +from vyos.configtree import ConfigTree + +base = ['service', 'ssh'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + path_loglevel = base + ['loglevel'] + if config.exists(path_loglevel): + # red in configured loglevel and convert it to lower case + tmp = config.return_value(path_loglevel).lower() + # VyOS 1.2 had no proper value validation on the CLI thus the + # user could use any arbitrary values - sanitize them + if tmp not in ['quiet', 'fatal', 'error', 'info', 'verbose']: + tmp = 'info' + config.set(path_loglevel, value=tmp) + + # T4273: migrate ssh cipher list to multi node + path_ciphers = base + ['ciphers'] + if config.exists(path_ciphers): + tmp = [] + # get curtrent cipher list - comma delimited + for cipher in config.return_values(path_ciphers): + tmp.extend(cipher.split(',')) + # delete old cipher suite representation + config.delete(path_ciphers) + + for cipher in tmp: + config.set(path_ciphers, value=cipher, replace=False) + + # T4273: migrate ssh key-exchange list to multi node + path_kex = base + ['key-exchange'] + if config.exists(path_kex): + tmp = [] + # get curtrent cipher list - comma delimited + for kex in config.return_values(path_kex): + tmp.extend(kex.split(',')) + # delete old cipher suite representation + config.delete(path_kex) + + for kex in tmp: + config.set(path_kex, value=kex, replace=False) diff --git a/src/migration-scripts/sstp/0-to-1 b/src/migration-scripts/sstp/0-to-1 new file mode 100644 index 0000000..1bd7d6c --- /dev/null +++ b/src/migration-scripts/sstp/0-to-1 @@ -0,0 +1,109 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - migrate from "service sstp-server" to "vpn sstp" +# - remove primary/secondary identifier from nameserver +# - migrate RADIUS configuration to a more uniform syntax accross the system +# - authentication radius-server x.x.x.x to authentication radius server x.x.x.x +# - authentication radius-settings to authentication radius +# - do not migrate radius server req-limit, use default of unlimited +# - migrate SSL certificate path + +from vyos.configtree import ConfigTree + +old_base = ['service', 'sstp-server'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(old_base): + # Nothing to do + return + + # ensure new base path exists + if not config.exists(['vpn']): + config.set(['vpn']) + + new_base = ['vpn', 'sstp'] + # copy entire tree + config.copy(old_base, new_base) + config.delete(old_base) + + # migrate DNS servers + dns_base = new_base + ['network-settings', 'dns-server'] + if config.exists(dns_base): + if config.exists(dns_base + ['primary-dns']): + dns = config.return_value(dns_base + ['primary-dns']) + config.set(new_base + ['network-settings', 'name-server'], value=dns, replace=False) + + if config.exists(dns_base + ['secondary-dns']): + dns = config.return_value(dns_base + ['secondary-dns']) + config.set(new_base + ['network-settings', 'name-server'], value=dns, replace=False) + + config.delete(dns_base) + + + # migrate radius options - copy subtree + # thus must happen before migration of the individual RADIUS servers + old_options = new_base + ['authentication', 'radius-settings'] + if config.exists(old_options): + new_options = new_base + ['authentication', 'radius'] + config.copy(old_options, new_options) + config.delete(old_options) + + # migrate radius dynamic author / change of authorisation server + dae_old = new_base + ['authentication', 'radius', 'dae-server'] + if config.exists(dae_old): + config.rename(dae_old, 'dynamic-author') + dae_new = new_base + ['authentication', 'radius', 'dynamic-author'] + + if config.exists(dae_new + ['ip-address']): + config.rename(dae_new + ['ip-address'], 'server') + + if config.exists(dae_new + ['secret']): + config.rename(dae_new + ['secret'], 'key') + + + # migrate radius server + radius_server = new_base + ['authentication', 'radius-server'] + if config.exists(radius_server): + for server in config.list_nodes(radius_server): + base = radius_server + [server] + new = new_base + ['authentication', 'radius', 'server', server] + + # convert secret to key + if config.exists(base + ['secret']): + tmp = config.return_value(base + ['secret']) + config.set(new + ['key'], value=tmp) + + if config.exists(base + ['fail-time']): + tmp = config.return_value(base + ['fail-time']) + config.set(new + ['fail-time'], value=tmp) + + config.set_tag(new_base + ['authentication', 'radius', 'server']) + config.delete(radius_server) + + # migrate SSL certificates + old_ssl = new_base + ['sstp-settings'] + new_ssl = new_base + ['ssl'] + config.copy(old_ssl + ['ssl-certs'], new_ssl) + config.delete(old_ssl) + + if config.exists(new_ssl + ['ca']): + config.rename(new_ssl + ['ca'], 'ca-cert-file') + + if config.exists(new_ssl + ['server-cert']): + config.rename(new_ssl + ['server-cert'], 'cert-file') + + if config.exists(new_ssl + ['server-key']): + config.rename(new_ssl + ['server-key'], 'key-file') diff --git a/src/migration-scripts/sstp/1-to-2 b/src/migration-scripts/sstp/1-to-2 new file mode 100644 index 0000000..2349e3c --- /dev/null +++ b/src/migration-scripts/sstp/1-to-2 @@ -0,0 +1,93 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - migrate relative path SSL certificate to absolute path, as certs are only +# allowed to stored in /config/user-data/sstp/ this is pretty straight +# forward move. Delete certificates from source directory + +import os + +from shutil import copy2 +from stat import S_IRUSR, S_IWUSR, S_IRGRP, S_IROTH +from vyos.configtree import ConfigTree + +base_path = ['vpn', 'sstp', 'ssl'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base_path): + # Nothing to do + return + + cert_path_old ='/config/user-data/sstp/' + cert_path_new ='/config/auth/sstp/' + + if not os.path.isdir(cert_path_new): + os.mkdir(cert_path_new) + + # + # migrate ca-cert-file to new path + if config.exists(base_path + ['ca-cert-file']): + tmp = config.return_value(base_path + ['ca-cert-file']) + cert_old = cert_path_old + tmp + cert_new = cert_path_new + tmp + + if os.path.isfile(cert_old): + # adjust file permissions on source file, + # permissions will be copied by copy2() + os.chmod(cert_old, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) + copy2(cert_old, cert_path_new) + # delete old certificate file + os.unlink(cert_old) + + config.set(base_path + ['ca-cert-file'], value=cert_new, replace=True) + + # + # migrate cert-file to new path + if config.exists(base_path + ['cert-file']): + tmp = config.return_value(base_path + ['cert-file']) + cert_old = cert_path_old + tmp + cert_new = cert_path_new + tmp + + if os.path.isfile(cert_old): + # adjust file permissions on source file, + # permissions will be copied by copy2() + os.chmod(cert_old, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) + copy2(cert_old, cert_path_new) + # delete old certificate file + os.unlink(cert_old) + + config.set(base_path + ['cert-file'], value=cert_new, replace=True) + + # + # migrate key-file to new path + if config.exists(base_path + ['key-file']): + tmp = config.return_value(base_path + ['key-file']) + cert_old = cert_path_old + tmp + cert_new = cert_path_new + tmp + + if os.path.isfile(cert_old): + # adjust file permissions on source file, + # permissions will be copied by copy2() + os.chmod(cert_old, S_IRUSR | S_IWUSR) + copy2(cert_old, cert_path_new) + # delete old certificate file + os.unlink(cert_old) + + config.set(base_path + ['key-file'], value=cert_new, replace=True) + + # + # check if old certificate directory exists but is empty + if os.path.isdir(cert_path_old) and not os.listdir(cert_path_old): + os.rmdir(cert_path_old) diff --git a/src/migration-scripts/sstp/2-to-3 b/src/migration-scripts/sstp/2-to-3 new file mode 100644 index 0000000..4255a89 --- /dev/null +++ b/src/migration-scripts/sstp/2-to-3 @@ -0,0 +1,59 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - Rename SSTP ppp-settings node to ppp-options to make use of a common +# Jinja Template to render Accel-PPP services + +from vyos.configtree import ConfigTree + +base_path = ['vpn', 'sstp'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base_path): + # Nothing to do + return + + if config.exists(base_path + ['ppp-settings']): + config.rename(base_path + ['ppp-settings'], 'ppp-options') + + config_ns = base_path + ['network-settings', 'name-server'] + if config.exists(config_ns): + config.copy(config_ns, base_path + ['name-server']) + config.delete(config_ns) + + config_mtu = base_path + ['network-settings', 'mtu'] + if config.exists(config_mtu): + config.copy(config_mtu, base_path + ['mtu']) + config.delete(config_mtu) + + config_gw = base_path + ['network-settings', 'client-ip-settings', 'gateway-address'] + if config.exists(config_gw): + config.copy(config_gw, base_path + ['gateway-address']) + config.delete(config_gw) + + config_client_ip = base_path + ['network-settings', 'client-ip-settings'] + if config.exists(config_client_ip): + config.copy(config_client_ip, base_path + ['client-ip-pool']) + config.delete(config_client_ip) + + config_client_ipv6 = base_path + ['network-settings', 'client-ipv6-pool'] + if config.exists(config_client_ipv6): + config.copy(config_client_ipv6, base_path + ['client-ipv6-pool']) + config.delete(config_client_ipv6) + + # all nodes now have been migrated out of network-settings - delete node + config_nw_settings = base_path + ['network-settings'] + if config.exists(config_nw_settings): + config.delete(config_nw_settings) diff --git a/src/migration-scripts/sstp/3-to-4 b/src/migration-scripts/sstp/3-to-4 new file mode 100644 index 0000000..fd10985 --- /dev/null +++ b/src/migration-scripts/sstp/3-to-4 @@ -0,0 +1,116 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - Update SSL to use PKI configuration + +import os + +from vyos.configtree import ConfigTree +from vyos.pki import load_certificate +from vyos.pki import load_private_key +from vyos.pki import encode_certificate +from vyos.pki import encode_private_key +from vyos.utils.process import run + +base = ['vpn', 'sstp'] +pki_base = ['pki'] + +AUTH_DIR = '/config/auth' + +def wrapped_pem_to_config_value(pem): + return "".join(pem.strip().split("\n")[1:-1]) + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if not config.exists(base + ['ssl']): + return + + x509_base = base + ['ssl'] + pki_name = 'sstp' + + if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + + if config.exists(x509_base + ['ca-cert-file']): + cert_file = config.return_value(x509_base + ['ca-cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['ca-certificate'], value=pki_name) + else: + print(f'Failed to migrate CA certificate on sstp config') + + config.delete(x509_base + ['ca-cert-file']) + + if config.exists(x509_base + ['cert-file']): + cert_file = config.return_value(x509_base + ['cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['certificate'], value=pki_name) + else: + print(f'Failed to migrate certificate on sstp config') + + config.delete(x509_base + ['cert-file']) + + if config.exists(x509_base + ['key-file']): + key_file = config.return_value(x509_base + ['key-file']) + key_path = os.path.join(AUTH_DIR, key_file) + key = None + + if os.path.isfile(key_path): + if not os.access(key_path, os.R_OK): + run(f'sudo chmod 644 {key_path}') + + with open(key_path, 'r') as f: + key_data = f.read() + key = load_private_key(key_data, passphrase=None, wrap_tags=False) + + if key: + key_pem = encode_private_key(key, passphrase=None) + config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + else: + print(f'Failed to migrate private key on sstp config') + + config.delete(x509_base + ['key-file']) diff --git a/src/migration-scripts/sstp/4-to-5 b/src/migration-scripts/sstp/4-to-5 new file mode 100644 index 0000000..254e828 --- /dev/null +++ b/src/migration-scripts/sstp/4-to-5 @@ -0,0 +1,41 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - move all pool to named pools +# 'subnet' migrate to namedpool 'default-subnet-pool' +# 'default-subnet-pool' is the next pool for 'default-range-pool' + +from vyos.configtree import ConfigTree + +base = ['vpn', 'sstp'] +pool_base = base + ['client-ip-pool'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if not config.exists(pool_base): + return + + range_pool_name = 'default-range-pool' + + if config.exists(pool_base + ['subnet']): + default_pool = range_pool_name + for subnet in config.return_values(pool_base + ['subnet']): + config.set(pool_base + [range_pool_name, 'range'], value=subnet, replace=False) + config.delete(pool_base + ['subnet']) + config.set(base + ['default-pool'], value=default_pool) + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/sstp/5-to-6 b/src/migration-scripts/sstp/5-to-6 new file mode 100644 index 0000000..fc3cc29 --- /dev/null +++ b/src/migration-scripts/sstp/5-to-6 @@ -0,0 +1,40 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Migrating to named ipv6 pools + +from vyos.configtree import ConfigTree + +base = ['vpn', 'sstp'] +pool_base = base + ['client-ipv6-pool'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if not config.exists(pool_base): + return + + ipv6_pool_name = 'ipv6-pool' + config.copy(pool_base, pool_base + [ipv6_pool_name]) + + if config.exists(pool_base + ['prefix']): + config.delete(pool_base + ['prefix']) + config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name) + if config.exists(pool_base + ['delegate']): + config.delete(pool_base + ['delegate']) + + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/system/10-to-11 b/src/migration-scripts/system/10-to-11 new file mode 100644 index 0000000..76d7f23 --- /dev/null +++ b/src/migration-scripts/system/10-to-11 @@ -0,0 +1,32 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Operator accounts have been deprecated due to a security issue. Those accounts +# will be converted to regular admin accounts. + +from vyos.configtree import ConfigTree + +base_level = ['system', 'login', 'user'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base_level): + # Nothing to do, which shouldn't happen anyway + # only if you wipe the config and reboot. + return + + for user in config.list_nodes(base_level): + if config.exists(base_level + [user, 'level']): + if config.return_value(base_level + [user, 'level']) == 'operator': + config.set(base_level + [user, 'level'], value="admin", replace=True) diff --git a/src/migration-scripts/system/11-to-12 b/src/migration-scripts/system/11-to-12 new file mode 100644 index 0000000..71c359b --- /dev/null +++ b/src/migration-scripts/system/11-to-12 @@ -0,0 +1,69 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Unclutter RADIUS configuration +# +# Move radius-server top level tag nodes to a regular node which allows us +# to specify additional general features for the RADIUS client. + +from vyos.configtree import ConfigTree + +cfg_base = ['system', 'login'] + +def migrate(config: ConfigTree) -> None: + if not (config.exists(cfg_base + ['radius-server']) or config.exists(cfg_base + ['radius-source-address'])): + # Nothing to do + return + + # + # Migrate "system login radius-source-address" to "system login radius" + # + if config.exists(cfg_base + ['radius-source-address']): + address = config.return_value(cfg_base + ['radius-source-address']) + # delete old configuration node + config.delete(cfg_base + ['radius-source-address']) + # write new configuration node + config.set(cfg_base + ['radius', 'source-address'], value=address) + + # + # Migrate "system login radius-server" tag node to new + # "system login radius server" tag node and also rename the "secret" node to "key" + # + if config.exists(cfg_base + ['radius-server']): + for server in config.list_nodes(cfg_base + ['radius-server']): + base_server = cfg_base + ['radius-server', server] + # "key" node is mandatory + key = config.return_value(base_server + ['secret']) + config.set(cfg_base + ['radius', 'server', server, 'key'], value=key) + + # "port" is optional + if config.exists(base_server + ['port']): + port = config.return_value(base_server + ['port']) + config.set(cfg_base + ['radius', 'server', server, 'port'], value=port) + + # "timeout is optional" + if config.exists(base_server + ['timeout']): + timeout = config.return_value(base_server + ['timeout']) + config.set(cfg_base + ['radius', 'server', server, 'timeout'], value=timeout) + + # format as tag node + config.set_tag(cfg_base + ['radius', 'server']) + + # delete old configuration node + config.delete(base_server) + + # delete top level tag node + if config.exists(cfg_base + ['radius-server']): + config.delete(cfg_base + ['radius-server']) diff --git a/src/migration-scripts/system/12-to-13 b/src/migration-scripts/system/12-to-13 new file mode 100644 index 0000000..014edba --- /dev/null +++ b/src/migration-scripts/system/12-to-13 @@ -0,0 +1,44 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# converts 'set system syslog host <address>:<port>' +# to 'set system syslog host <address> port <port>' + +import re + +from vyos.configtree import ConfigTree + +cbase = ['system', 'syslog', 'host'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(cbase): + return + + for host in config.list_nodes(cbase): + if re.search(':[0-9]{1,5}$',host): + h = re.search('^[a-zA-Z\-0-9\.]+', host).group(0) + p = re.sub(':', '', re.search(':[0-9]+$', host).group(0)) + config.set(cbase + [h]) + config.set(cbase + [h, 'port'], value=p) + for fac in config.list_nodes(cbase + [host, 'facility']): + config.set(cbase + [h, 'facility', fac]) + config.set_tag(cbase + [h, 'facility']) + if config.exists(cbase + [host, 'facility', fac, 'protocol']): + proto = config.return_value(cbase + [host, 'facility', fac, 'protocol']) + config.set(cbase + [h, 'facility', fac, 'protocol'], value=proto) + if config.exists(cbase + [host, 'facility', fac, 'level']): + lvl = config.return_value(cbase + [host, 'facility', fac, 'level']) + config.set(cbase + [h, 'facility', fac, 'level'], value=lvl) + config.delete(cbase + [host]) diff --git a/src/migration-scripts/system/13-to-14 b/src/migration-scripts/system/13-to-14 new file mode 100644 index 0000000..fbbecbc --- /dev/null +++ b/src/migration-scripts/system/13-to-14 @@ -0,0 +1,67 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Fixup non existent time-zones. Some systems have time-zone set to: Los* +# (Los_Angeles), Den* (Denver), New* (New_York) ... but those are no real IANA +# assigned time zones. In the past they have been silently remapped. +# +# Time to clean it up! +# +# Migrate all configured timezones to real IANA assigned timezones! + +import re + +from vyos.configtree import ConfigTree +from vyos.utils.process import cmd + + +tz_base = ['system', 'time-zone'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(tz_base): + # Nothing to do + return + + tz = config.return_value(tz_base) + + # retrieve all valid timezones + try: + tz_datas = cmd('timedatectl list-timezones') + except OSError: + tz_datas = '' + tz_data = tz_datas.split('\n') + + if re.match(r'[Ll][Oo][Ss].+', tz): + tz = 'America/Los_Angeles' + elif re.match(r'[Dd][Ee][Nn].+', tz): + tz = 'America/Denver' + elif re.match(r'[Hh][Oo][Nn][Oo].+', tz): + tz = 'Pacific/Honolulu' + elif re.match(r'[Nn][Ee][Ww].+', tz): + tz = 'America/New_York' + elif re.match(r'[Cc][Hh][Ii][Cc]*.+', tz): + tz = 'America/Chicago' + elif re.match(r'[Aa][Nn][Cc].+', tz): + tz = 'America/Anchorage' + elif re.match(r'[Pp][Hh][Oo].+', tz): + tz = 'America/Phoenix' + elif re.match(r'GMT(.+)?', tz): + tz = 'Etc/' + tz + elif tz not in tz_data: + # assign default UTC timezone + tz = 'UTC' + + # replace timezone data is required + config.set(tz_base, value=tz) diff --git a/src/migration-scripts/system/14-to-15 b/src/migration-scripts/system/14-to-15 new file mode 100644 index 0000000..2818094 --- /dev/null +++ b/src/migration-scripts/system/14-to-15 @@ -0,0 +1,37 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Delete 'system ipv6 blacklist' option as the IPv6 module can no longer be +# blacklisted as it is required by e.g. WireGuard and thus will always be +# loaded. + +import os + +ipv6_blacklist_file = '/etc/modprobe.d/vyatta_blacklist_ipv6.conf' + +from vyos.configtree import ConfigTree + +ip_base = ['system', 'ipv6'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(ip_base): + # Nothing to do + return + + # delete 'system ipv6 blacklist' node + if config.exists(ip_base + ['blacklist']): + config.delete(ip_base + ['blacklist']) + if os.path.isfile(ipv6_blacklist_file): + os.unlink(ipv6_blacklist_file) diff --git a/src/migration-scripts/system/15-to-16 b/src/migration-scripts/system/15-to-16 new file mode 100644 index 0000000..7db0429 --- /dev/null +++ b/src/migration-scripts/system/15-to-16 @@ -0,0 +1,32 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Make 'system options reboot-on-panic' valueless + +from vyos.configtree import ConfigTree + +base = ['system', 'options'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['reboot-on-panic']): + reboot = config.return_value(base + ['reboot-on-panic']) + config.delete(base + ['reboot-on-panic']) + # create new valueless node if action was true + if reboot == "true": + config.set(base + ['reboot-on-panic']) diff --git a/src/migration-scripts/system/16-to-17 b/src/migration-scripts/system/16-to-17 new file mode 100644 index 0000000..9fb86af --- /dev/null +++ b/src/migration-scripts/system/16-to-17 @@ -0,0 +1,36 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# * remove "system login user <user> group" node, Why should be add a user to a +# 3rd party group when the system is fully managed by CLI? +# * remove "system login user <user> level" node +# This is the only privilege level left and also the default, what is the +# sense in keeping this orphaned node? + +from vyos.configtree import ConfigTree + +base = ['system', 'login', 'user'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for user in config.list_nodes(base): + if config.exists(base + [user, 'group']): + config.delete(base + [user, 'group']) + + if config.exists(base + [user, 'level']): + config.delete(base + [user, 'level']) diff --git a/src/migration-scripts/system/17-to-18 b/src/migration-scripts/system/17-to-18 new file mode 100644 index 0000000..323ef4e --- /dev/null +++ b/src/migration-scripts/system/17-to-18 @@ -0,0 +1,59 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# remove "system console netconsole" +# remove "system console device <device> modem" + +import os + +from vyos.configtree import ConfigTree + +base = ['system', 'console'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # remove "system console netconsole" (T2561) + if config.exists(base + ['netconsole']): + config.delete(base + ['netconsole']) + + if config.exists(base + ['device']): + for device in config.list_nodes(base + ['device']): + dev_path = base + ['device', device] + # remove "system console device <device> modem" (T2570) + if config.exists(dev_path + ['modem']): + config.delete(dev_path + ['modem']) + + # Only continue on USB based serial consoles + if not 'ttyUSB' in device: + continue + + # A serial console has been configured but it does no longer + # exist on the system - cleanup + if not os.path.exists(f'/dev/{device}'): + config.delete(dev_path) + continue + + # migrate from ttyUSB device to new device in /dev/serial/by-bus + for root, dirs, files in os.walk('/dev/serial/by-bus'): + for usb_device in files: + device_file = os.path.realpath(os.path.join(root, usb_device)) + # migrate to new USB device names (T2529) + if os.path.basename(device_file) == device: + config.copy(dev_path, base + ['device', usb_device]) + # Delete old USB node from config + config.delete(dev_path) diff --git a/src/migration-scripts/system/18-to-19 b/src/migration-scripts/system/18-to-19 new file mode 100644 index 0000000..5d9788d --- /dev/null +++ b/src/migration-scripts/system/18-to-19 @@ -0,0 +1,81 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# migrate disable-dhcp-nameservers (boolean) to name-servers-dhcp <interface> +# if disable-dhcp-nameservers is set, just remove it +# else retrieve all interface names that have configured dhcp(v6) address and +# add them to the new name-servers-dhcp node + +from vyos.ifconfig import Interface +from vyos.configtree import ConfigTree + +base = ['system'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['disable-dhcp-nameservers']): + config.delete(base + ['disable-dhcp-nameservers']) + else: + dhcp_interfaces = [] + + # go through all interfaces searching for 'address dhcp(v6)?' + for sect in Interface.sections(): + sect_base = ['interfaces', sect] + + if not config.exists(sect_base): + continue + + for intf in config.list_nodes(sect_base): + intf_base = sect_base + [intf] + + # try without vlans + if config.exists(intf_base + ['address']): + for addr in config.return_values(intf_base + ['address']): + if addr in ['dhcp', 'dhcpv6']: + dhcp_interfaces.append(intf) + + # try vif + if config.exists(intf_base + ['vif']): + for vif in config.list_nodes(intf_base + ['vif']): + vif_base = intf_base + ['vif', vif] + if config.exists(vif_base + ['address']): + for addr in config.return_values(vif_base + ['address']): + if addr in ['dhcp', 'dhcpv6']: + dhcp_interfaces.append(f'{intf}.{vif}') + + # try vif-s + if config.exists(intf_base + ['vif-s']): + for vif_s in config.list_nodes(intf_base + ['vif-s']): + vif_s_base = intf_base + ['vif-s', vif_s] + if config.exists(vif_s_base + ['address']): + for addr in config.return_values(vif_s_base + ['address']): + if addr in ['dhcp', 'dhcpv6']: + dhcp_interfaces.append(f'{intf}.{vif_s}') + + # try vif-c + if config.exists(intf_base + ['vif-c']): + for vif_c in config.list_nodes(vif_s_base + ['vif-c']): + vif_c_base = vif_s_base + ['vif-c', vif_c] + if config.exists(vif_c_base + ['address']): + for addr in config.return_values(vif_c_base + ['address']): + if addr in ['dhcp', 'dhcpv6']: + dhcp_interfaces.append(f'{intf}.{vif_s}.{vif_c}') + + # set new config nodes + for intf in dhcp_interfaces: + config.set(base + ['name-servers-dhcp'], value=intf, replace=False) diff --git a/src/migration-scripts/system/19-to-20 b/src/migration-scripts/system/19-to-20 new file mode 100644 index 0000000..cb84e11 --- /dev/null +++ b/src/migration-scripts/system/19-to-20 @@ -0,0 +1,44 @@ +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3048: remove smp-affinity node from ethernet and use tuned instead + +from vyos.configtree import ConfigTree + +base = ['system', 'options'] +base_new = ['system', 'option'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base_new): + for node in config.list_nodes(base): + config.copy(base + [node], base_new + [node]) + else: + config.copy(base, base_new) + + config.delete(base) + + # Rename "system option beep-if-fully-booted" -> "system option startup-beep" + base_beep = base_new + ['beep-if-fully-booted'] + if config.exists(base_beep): + config.rename(base_beep, 'startup-beep') + + # Rename "system option ctrl-alt-del-action" -> "system option ctrl-alt-delete" + base_ctrl_alt_del = base_new + ['ctrl-alt-del-action'] + if config.exists(base_ctrl_alt_del): + config.rename(base_ctrl_alt_del, 'ctrl-alt-delete') diff --git a/src/migration-scripts/system/20-to-21 b/src/migration-scripts/system/20-to-21 new file mode 100644 index 0000000..71c283d --- /dev/null +++ b/src/migration-scripts/system/20-to-21 @@ -0,0 +1,30 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3795: merge "system name-servers-dhcp" into "system name-server" + +from vyos.configtree import ConfigTree + +base = ['system', 'name-servers-dhcp'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for interface in config.return_values(base): + config.set(['system', 'name-server'], value=interface, replace=False) + + config.delete(base) diff --git a/src/migration-scripts/system/21-to-22 b/src/migration-scripts/system/21-to-22 new file mode 100644 index 0000000..0e68a68 --- /dev/null +++ b/src/migration-scripts/system/21-to-22 @@ -0,0 +1,38 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configtree import ConfigTree + +base = ['system', 'sysctl'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for all_custom in ['all', 'custom']: + if config.exists(base + [all_custom]): + for key in config.list_nodes(base + [all_custom]): + tmp = config.return_value(base + [all_custom, key, 'value']) + config.set(base + ['parameter', key, 'value'], value=tmp) + config.set_tag(base + ['parameter']) + config.delete(base + [all_custom]) + + for ipv4_param in ['net.ipv4.igmp_max_memberships', 'net.ipv4.ipfrag_time']: + if config.exists(base + [ipv4_param]): + tmp = config.return_value(base + [ipv4_param]) + config.set(base + ['parameter', ipv4_param, 'value'], value=tmp) + config.set_tag(base + ['parameter']) + config.delete(base + [ipv4_param]) diff --git a/src/migration-scripts/system/22-to-23 b/src/migration-scripts/system/22-to-23 new file mode 100644 index 0000000..e49094e --- /dev/null +++ b/src/migration-scripts/system/22-to-23 @@ -0,0 +1,31 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configtree import ConfigTree + +base = ['system', 'ipv6'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # T4346: drop support to disbale IPv6 address family within the OS Kernel + if config.exists(base + ['disable']): + config.delete(base + ['disable']) + # IPv6 address family disable was the only CLI option set - we can cleanup + # the entire tree + if len(config.list_nodes(base)) == 0: + config.delete(base) diff --git a/src/migration-scripts/system/23-to-24 b/src/migration-scripts/system/23-to-24 new file mode 100644 index 0000000..feb62bc --- /dev/null +++ b/src/migration-scripts/system/23-to-24 @@ -0,0 +1,71 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from ipaddress import ip_interface +from ipaddress import ip_address + +from vyos.configtree import ConfigTree +from vyos.template import is_ipv4 + +base = ['protocols', 'static', 'arp'] +tmp_base = ['protocols', 'static', 'arp-tmp'] + +def fixup_cli(config, path, interface, host): + if config.exists(path + ['address']): + for address in config.return_values(path + ['address']): + tmp = ip_interface(address) + # ARP is only available for IPv4 ;-) + if not is_ipv4(tmp): + continue + if ip_address(host) in tmp.network.hosts(): + mac = config.return_value(tmp_base + [host, 'hwaddr']) + iface_path = ['protocols', 'static', 'arp', 'interface'] + config.set(iface_path + [interface, 'address', host, 'mac'], value=mac) + config.set_tag(iface_path) + config.set_tag(iface_path + [interface, 'address']) + continue + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # We need a temporary copy of the config tree as the original one needs to be + # deleted first due to a change iun thge tagNode structure. + config.copy(base, tmp_base) + config.delete(base) + + for host in config.list_nodes(tmp_base): + for type in config.list_nodes(['interfaces']): + for interface in config.list_nodes(['interfaces', type]): + if_base = ['interfaces', type, interface] + fixup_cli(config, if_base, interface, host) + + if config.exists(if_base + ['vif']): + for vif in config.list_nodes(if_base + ['vif']): + vif_base = ['interfaces', type, interface, 'vif', vif] + fixup_cli(config, vif_base, f'{interface}.{vif}', host) + + if config.exists(if_base + ['vif-s']): + for vif_s in config.list_nodes(if_base + ['vif-s']): + vif_s_base = ['interfaces', type, interface, 'vif-s', vif_s] + fixup_cli(config, vif_s_base, f'{interface}.{vif_s}', host) + + if config.exists(if_base + ['vif-s', vif_s, 'vif-c']): + for vif_c in config.list_nodes(if_base + ['vif-s', vif_s, 'vif-c']): + vif_c_base = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c', vif_c] + fixup_cli(config, vif_c_base, f'{interface}.{vif_s}.{vif_c}', host) + + config.delete(tmp_base) diff --git a/src/migration-scripts/system/24-to-25 b/src/migration-scripts/system/24-to-25 new file mode 100644 index 0000000..bdb8990 --- /dev/null +++ b/src/migration-scripts/system/24-to-25 @@ -0,0 +1,35 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Migrate system syslog global archive to system logs logrotate messages + +from vyos.configtree import ConfigTree + +base = ['system', 'syslog', 'global', 'archive'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if config.exists(base + ['file']): + tmp = config.return_value(base + ['file']) + config.set(['system', 'logs', 'logrotate', 'messages', 'rotate'], value=tmp) + + if config.exists(base + ['size']): + tmp = config.return_value(base + ['size']) + tmp = max(round(int(tmp) / 1024), 1) # kb -> mb + config.set(['system', 'logs', 'logrotate', 'messages', 'max-size'], value=tmp) + + config.delete(base) diff --git a/src/migration-scripts/system/25-to-26 b/src/migration-scripts/system/25-to-26 new file mode 100644 index 0000000..8832f48 --- /dev/null +++ b/src/migration-scripts/system/25-to-26 @@ -0,0 +1,65 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# syslog: migrate deprecated CLI options +# - protocols -> local7 +# - security -> auth + +from vyos.configtree import ConfigTree + +base = ['system', 'syslog'] + +def rename_facilities(config, base_tree, facility, facility_new) -> None: + if config.exists(base + [base_tree, 'facility', facility]): + # do not overwrite already existing replacement facility + if not config.exists(base + [base_tree, 'facility', facility_new]): + config.rename(base + [base_tree, 'facility', facility], facility_new) + else: + # delete old duplicate facility config + config.delete(base + [base_tree, 'facility', facility]) + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + # + # Rename protocols and securityy facility to common ones + # + replace = { + 'protocols' : 'local7', + 'security' : 'auth' + } + for facility, facility_new in replace.items(): + rename_facilities(config, 'console', facility, facility_new) + rename_facilities(config, 'global', facility, facility_new) + + if config.exists(base + ['host']): + for host in config.list_nodes(base + ['host']): + rename_facilities(config, f'host {host}', facility, facility_new) + + # + # It makes no sense to configure udp/tcp transport per individual facility + # + if config.exists(base + ['host']): + for host in config.list_nodes(base + ['host']): + protocol = None + for facility in config.list_nodes(base + ['host', host, 'facility']): + tmp_path = base + ['host', host, 'facility', facility, 'protocol'] + if config.exists(tmp_path): + # We can only change the first one + if protocol == None: + protocol = config.return_value(tmp_path) + config.set(base + ['host', host, 'protocol'], value=protocol) + config.delete(tmp_path) diff --git a/src/migration-scripts/system/26-to-27 b/src/migration-scripts/system/26-to-27 new file mode 100644 index 0000000..499e16e --- /dev/null +++ b/src/migration-scripts/system/26-to-27 @@ -0,0 +1,30 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5877: migrate 'system domain-search domain' to 'system domain-search' + +from vyos.configtree import ConfigTree + +base = ['system', 'domain-search'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if config.exists(base + ['domain']): + entries = config.return_values(base + ['domain']) + config.delete(base + ['domain']) + for entry in entries: + config.set(base, value=entry, replace=False) diff --git a/src/migration-scripts/system/6-to-7 b/src/migration-scripts/system/6-to-7 new file mode 100644 index 0000000..e91ccc4 --- /dev/null +++ b/src/migration-scripts/system/6-to-7 @@ -0,0 +1,36 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Change smp_affinity to smp-affinity + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + intf_types = config.list_nodes(["interfaces"]) + + for intf_type in intf_types: + intf_type_path = ["interfaces", intf_type] + intfs = config.list_nodes(intf_type_path) + + for intf in intfs: + intf_path = intf_type_path + [intf] + if not config.exists(intf_path + ["smp_affinity"]): + # Nothing to do. + continue + else: + # Rename the node. + old_smp_affinity_path = intf_path + ["smp_affinity"] + config.rename(old_smp_affinity_path, "smp-affinity") + update_required = True diff --git a/src/migration-scripts/system/7-to-8 b/src/migration-scripts/system/7-to-8 new file mode 100644 index 0000000..64dd4dc --- /dev/null +++ b/src/migration-scripts/system/7-to-8 @@ -0,0 +1,39 @@ +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Converts "system gateway-address" option to "protocols static route 0.0.0.0/0 next-hop $gw" + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + if not config.exists(['system', 'gateway-address']): + # Nothing to do + return + + # Save the address + gw = config.return_value(['system', 'gateway-address']) + + # Create the node for the new syntax + # Note: next-hop is a tag node, gateway address is its child, not a value + config.set(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', gw]) + + # Delete the node with the old syntax + config.delete(['system', 'gateway-address']) + + # Now, the interesting part. Both route and next-hop are supposed to be tag nodes, + # which you can verify with "cli-shell-api isTag $configPath". + # They must be formatted as such to load correctly. + config.set_tag(['protocols', 'static', 'route']) + config.set_tag(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop']) diff --git a/src/migration-scripts/system/8-to-9 b/src/migration-scripts/system/8-to-9 new file mode 100644 index 0000000..ea5f7af --- /dev/null +++ b/src/migration-scripts/system/8-to-9 @@ -0,0 +1,26 @@ +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Deletes "system package" option as it is deprecated + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + if not config.exists(['system', 'package']): + # Nothing to do + return + + # Delete the node with the old syntax + config.delete(['system', 'package']) diff --git a/src/migration-scripts/vrf/0-to-1 b/src/migration-scripts/vrf/0-to-1 new file mode 100644 index 0000000..70abae2 --- /dev/null +++ b/src/migration-scripts/vrf/0-to-1 @@ -0,0 +1,113 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - T2450: drop interface-route and interface-route6 from "protocols vrf" + +from vyos.configtree import ConfigTree + +base = ['protocols', 'vrf'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for vrf in config.list_nodes(base): + static_base = base + [vrf, 'static'] + if not config.exists(static_base): + continue + + # + # Migrate interface-route into route + # + interface_route_path = static_base + ['interface-route'] + if config.exists(interface_route_path): + for route in config.list_nodes(interface_route_path): + interface = config.list_nodes(interface_route_path + [route, 'next-hop-interface']) + + tmp = interface_route_path + [route, 'next-hop-interface'] + for interface in config.list_nodes(tmp): + new_base = static_base + ['route', route, 'interface'] + config.set(new_base) + config.set_tag(new_base) + config.copy(tmp + [interface], new_base + [interface]) + + config.delete(interface_route_path) + + # + # Migrate interface-route6 into route6 + # + interface_route_path = static_base + ['interface-route6'] + if config.exists(interface_route_path): + for route in config.list_nodes(interface_route_path): + interface = config.list_nodes(interface_route_path + [route, 'next-hop-interface']) + + tmp = interface_route_path + [route, 'next-hop-interface'] + for interface in config.list_nodes(tmp): + new_base = static_base + ['route6', route, 'interface'] + config.set(new_base) + config.set_tag(new_base) + config.copy(tmp + [interface], new_base + [interface]) + + config.delete(interface_route_path) + + # + # Cleanup nodes inside route + # + route_path = static_base + ['route'] + if config.exists(route_path): + for route in config.list_nodes(route_path): + next_hop = route_path + [route, 'next-hop'] + if config.exists(next_hop): + for gateway in config.list_nodes(next_hop): + interface_path = next_hop + [gateway, 'next-hop-interface'] + if config.exists(interface_path): + config.rename(interface_path, 'interface') + vrf_path = next_hop + [gateway, 'next-hop-vrf'] + if config.exists(vrf_path): + config.rename(vrf_path, 'vrf') + + next_hop = route_path + [route, 'interface'] + if config.exists(next_hop): + for interface in config.list_nodes(next_hop): + interface_path = next_hop + [interface, 'next-hop-interface'] + if config.exists(interface_path): + config.rename(interface_path, 'interface') + vrf_path = next_hop + [interface, 'next-hop-vrf'] + if config.exists(vrf_path): + config.rename(vrf_path, 'vrf') + + # + # Cleanup nodes inside route6 + # + route_path = static_base + ['route6'] + if config.exists(route_path): + for route in config.list_nodes(route_path): + next_hop = route_path + [route, 'next-hop'] + if config.exists(next_hop): + for gateway in config.list_nodes(next_hop): + vrf_path = next_hop + [gateway, 'next-hop-vrf'] + if config.exists(vrf_path): + config.rename(vrf_path, 'vrf') + + next_hop = route_path + [route, 'interface'] + if config.exists(next_hop): + for interface in config.list_nodes(next_hop): + interface_path = next_hop + [interface, 'next-hop-interface'] + if config.exists(interface_path): + config.rename(interface_path, 'interface') + vrf_path = next_hop + [interface, 'next-hop-vrf'] + if config.exists(vrf_path): + config.rename(vrf_path, 'vrf') diff --git a/src/migration-scripts/vrf/1-to-2 b/src/migration-scripts/vrf/1-to-2 new file mode 100644 index 0000000..557a9ec --- /dev/null +++ b/src/migration-scripts/vrf/1-to-2 @@ -0,0 +1,43 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# - T3344: migrate routing options from "protocols vrf" to "vrf <name> protocols" + +from vyos.configtree import ConfigTree + +base = ['protocols', 'vrf'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + vrf_base = ['vrf', 'name'] + config.set(vrf_base) + config.set_tag(vrf_base) + + # Copy all existing static routes to the new base node under "vrf name <name> protocols static" + for vrf in config.list_nodes(base): + static_base = base + [vrf, 'static'] + if not config.exists(static_base): + continue + + new_static_base = vrf_base + [vrf, 'protocols'] + config.set(new_static_base) + config.copy(static_base, new_static_base + ['static']) + config.set_tag(new_static_base + ['static', 'route']) + + # Now delete the old configuration + config.delete(base) diff --git a/src/migration-scripts/vrf/2-to-3 b/src/migration-scripts/vrf/2-to-3 new file mode 100644 index 0000000..acacffb --- /dev/null +++ b/src/migration-scripts/vrf/2-to-3 @@ -0,0 +1,125 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Since connection tracking zones are int16, VRFs tables maximum value must +# be limited to 65535 +# Also, interface names in nftables cannot start from numbers, +# so VRF name should not start from a number + +from random import randrange +from random import choice +from string import ascii_lowercase +from vyos.configtree import ConfigTree +import re + + +# Helper function to find all config items with a VRF name +def _search_vrfs(config_commands, vrf_name): + vrf_values = [] + # Regex to find path of config command with old VRF + regex_filter = re.compile(rf'^set (?P<cmd_path>[^\']+vrf) \'{vrf_name}\'$') + # Check each command for VRF value + for config_command in config_commands: + search_result = regex_filter.search(config_command) + if search_result: + # Append VRF command to a list + vrf_values.append(search_result.group('cmd_path').split()) + if vrf_values: + return vrf_values + else: + return None + + +# Helper function to find all config items with a table number +def _search_tables(config_commands, table_num): + table_items = {'table_tags': [], 'table_values': []} + # Regex to find values and nodes with a table number + regex_tags = re.compile(rf'^set (?P<cmd_path>[^\']+table {table_num}) ?.*$') + regex_values = re.compile( + rf'^set (?P<cmd_path>[^\']+table) \'{table_num}\'$') + for config_command in config_commands: + # Search for tag nodes + search_result = regex_tags.search(config_command) + if search_result: + # Append table node path to a tag nodes list + cmd_path = search_result.group('cmd_path').split() + if cmd_path not in table_items['table_tags']: + table_items['table_tags'].append(cmd_path) + # Search for value nodes + search_result = regex_values.search(config_command) + if search_result: + # Append table node path to a value nodes list + table_items['table_values'].append( + search_result.group('cmd_path').split()) + return table_items + + +base = ['vrf', 'name'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # Get a list of all currently used VRFs and tables + vrfs_current = {} + for vrf in config.list_nodes(base): + vrfs_current[vrf] = int(config.return_value(base + [vrf, 'table'])) + + # Check VRF names and table numbers + name_regex = re.compile(r'^\d.*$') + for vrf_name, vrf_table in vrfs_current.items(): + # Check table number + if vrf_table > 65535: + # Find new unused table number + vrfs_current[vrf_name] = None + while not vrfs_current[vrf_name]: + table_random = randrange(100, 65535) + if table_random not in vrfs_current.values(): + vrfs_current[vrf_name] = table_random + # Update number to a new one + config.set(['vrf', 'name', vrf_name, 'table'], + vrfs_current[vrf_name], + replace=True) + # Check config items with old table number and replace to new one + config_commands = config.to_commands().split('\n') + table_config_lines = _search_tables(config_commands, vrf_table) + # Rename table nodes + if table_config_lines.get('table_tags'): + for table_config_path in table_config_lines.get('table_tags'): + config.rename(table_config_path, f'{vrfs_current[vrf_name]}') + # Replace table values + if table_config_lines.get('table_values'): + for table_config_path in table_config_lines.get('table_values'): + config.set(table_config_path, + f'{vrfs_current[vrf_name]}', + replace=True) + + # Check VRF name + if name_regex.match(vrf_name): + vrf_name_new = None + while not vrf_name_new: + vrf_name_rand = f'{choice(ascii_lowercase)}{vrf_name}'[:15] + if vrf_name_rand not in vrfs_current: + vrf_name_new = vrf_name_rand + # Update VRF name to a new one + config.rename(['vrf', 'name', vrf_name], vrf_name_new) + # Check config items with old VRF name and replace to new one + config_commands = config.to_commands().split('\n') + vrf_config_lines = _search_vrfs(config_commands, vrf_name) + # Rename VRF to a new name + if vrf_config_lines: + for vrf_value_path in vrf_config_lines: + config.set(vrf_value_path, vrf_name_new, replace=True) diff --git a/src/migration-scripts/vrrp/1-to-2 b/src/migration-scripts/vrrp/1-to-2 new file mode 100644 index 0000000..8639a75 --- /dev/null +++ b/src/migration-scripts/vrrp/1-to-2 @@ -0,0 +1,250 @@ +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +import re + +from vyos.configtree import ConfigTree + + +# Convert the old VRRP syntax to the new syntax + +# The old approach was to put VRRP groups inside interfaces, +# as in "interfaces ethernet eth0 vrrp vrrp-group 10 ...". +# It was supported only under ethernet and bonding and their +# respective vif, vif-s, and vif-c subinterfaces + +def get_vrrp_group(path): + group = {"preempt": True, "rfc_compatibility": False, "disable": False} + + if config.exists(path + ["advertise-interval"]): + group["advertise_interval"] = config.return_value(path + ["advertise-interval"]) + + if config.exists(path + ["description"]): + group["description"] = config.return_value(path + ["description"]) + + if config.exists(path + ["disable"]): + group["disable"] = True + + if config.exists(path + ["hello-source-address"]): + group["hello_source"] = config.return_value(path + ["hello-source-address"]) + + # 1.1.8 didn't have it, but earlier 1.2.0 did, we don't want to break + # configs of early adopters! + if config.exists(path + ["peer-address"]): + group["peer_address"] = config.return_value(path + ["peer-address"]) + + if config.exists(path + ["preempt"]): + preempt = config.return_value(path + ["preempt"]) + if preempt == "false": + group["preempt"] = False + + if config.exists(path + ["rfc3768-compatibility"]): + group["rfc_compatibility"] = True + + if config.exists(path + ["preempt-delay"]): + group["preempt_delay"] = config.return_value(path + ["preempt-delay"]) + + if config.exists(path + ["priority"]): + group["priority"] = config.return_value(path + ["priority"]) + + if config.exists(path + ["sync-group"]): + group["sync_group"] = config.return_value(path + ["sync-group"]) + + if config.exists(path + ["authentication", "type"]): + group["auth_type"] = config.return_value(path + ["authentication", "type"]) + + if config.exists(path + ["authentication", "password"]): + group["auth_password"] = config.return_value(path + ["authentication", "password"]) + + if config.exists(path + ["virtual-address"]): + group["virtual_addresses"] = config.return_values(path + ["virtual-address"]) + + if config.exists(path + ["run-transition-scripts"]): + if config.exists(path + ["run-transition-scripts", "master"]): + group["master_script"] = config.return_value(path + ["run-transition-scripts", "master"]) + if config.exists(path + ["run-transition-scripts", "backup"]): + group["backup_script"] = config.return_value(path + ["run-transition-scripts", "backup"]) + if config.exists(path + ["run-transition-scripts", "fault"]): + group["fault_script"] = config.return_value(path + ["run-transition-scripts", "fault"]) + + # Also not present in 1.1.8, but supported by earlier 1.2.0 + if config.exists(path + ["health-check"]): + if config.exists(path + ["health-check", "interval"]): + group["health_check_interval"] = config.return_value(path + ["health-check", "interval"]) + if config.exists(path + ["health-check", "failure-count"]): + group["health_check_count"] = config.return_value(path + ["health-check", "failure-count"]) + if config.exists(path + ["health-check", "script"]): + group["health_check_script"] = config.return_value(path + ["health-check", "script"]) + + return group + +# Since VRRP is all over the place, there's no way to just check a path and exit early +# if it doesn't exist, we have to walk all interfaces and collect VRRP settings from them. +# Only if no data is collected from any interface we can conclude that VRRP is not configured +# and exit. + +def migrate(config: ConfigTree) -> None: + groups = [] + base_paths = [] + + if config.exists(["interfaces", "ethernet"]): + base_paths.append("ethernet") + if config.exists(["interfaces", "bonding"]): + base_paths.append("bonding") + + for bp in base_paths: + parent_path = ["interfaces", bp] + + parent_intfs = config.list_nodes(parent_path) + + for pi in parent_intfs: + # Extract VRRP groups from the parent interface + vg_path =[pi, "vrrp", "vrrp-group"] + if config.exists(parent_path + vg_path): + pgroups = config.list_nodes(parent_path + vg_path) + for pg in pgroups: + g = get_vrrp_group(parent_path + vg_path + [pg]) + g["interface"] = pi + g["vrid"] = pg + groups.append(g) + + # Delete the VRRP subtree + # If left in place, configs will not load correctly + config.delete(parent_path + [pi, "vrrp"]) + + # Extract VRRP groups from 802.1q VLAN interfaces + if config.exists(parent_path + [pi, "vif"]): + vifs = config.list_nodes(parent_path + [pi, "vif"]) + for vif in vifs: + vif_vg_path = [pi, "vif", vif, "vrrp", "vrrp-group"] + if config.exists(parent_path + vif_vg_path): + vifgroups = config.list_nodes(parent_path + vif_vg_path) + for vif_group in vifgroups: + g = get_vrrp_group(parent_path + vif_vg_path + [vif_group]) + g["interface"] = "{0}.{1}".format(pi, vif) + g["vrid"] = vif_group + groups.append(g) + + config.delete(parent_path + [pi, "vif", vif, "vrrp"]) + + # Extract VRRP groups from 802.3ad QinQ service VLAN interfaces + if config.exists(parent_path + [pi, "vif-s"]): + vif_ss = config.list_nodes(parent_path + [pi, "vif-s"]) + for vif_s in vif_ss: + vifs_vg_path = [pi, "vif-s", vif_s, "vrrp", "vrrp-group"] + if config.exists(parent_path + vifs_vg_path): + vifsgroups = config.list_nodes(parent_path + vifs_vg_path) + for vifs_group in vifsgroups: + g = get_vrrp_group(parent_path + vifs_vg_path + [vifs_group]) + g["interface"] = "{0}.{1}".format(pi, vif_s) + g["vrid"] = vifs_group + groups.append(g) + + config.delete(parent_path + [pi, "vif-s", vif_s, "vrrp"]) + + # Extract VRRP groups from QinQ client VLAN interfaces nested in the vif-s + if config.exists(parent_path + [pi, "vif-s", vif_s, "vif-c"]): + vif_cs = config.list_nodes(parent_path + [pi, "vif-s", vif_s, "vif-c"]) + for vif_c in vif_cs: + vifc_vg_path = [pi, "vif-s", vif_s, "vif-c", vif_c, "vrrp", "vrrp-group"] + vifcgroups = config.list_nodes(parent_path + vifc_vg_path) + for vifc_group in vifcgroups: + g = get_vrrp_group(parent_path + vifc_vg_path + [vifc_group]) + g["interface"] = "{0}.{1}.{2}".format(pi, vif_s, vif_c) + g["vrid"] = vifc_group + groups.append(g) + + config.delete(parent_path + [pi, "vif-s", vif_s, "vif-c", vif_c, "vrrp"]) + + # If nothing was collected before this point, it means the config has no VRRP setup + if not groups: + return + + # Otherwise, there is VRRP to convert + + # Now convert the collected groups to the new syntax + base_group_path = ["high-availability", "vrrp", "group"] + sync_path = ["high-availability", "vrrp", "sync-group"] + + for g in groups: + group_name = "{0}-{1}".format(g["interface"], g["vrid"]) + group_path = base_group_path + [group_name] + + config.set(group_path + ["interface"], value=g["interface"]) + config.set(group_path + ["vrid"], value=g["vrid"]) + + if "advertise_interval" in g: + config.set(group_path + ["advertise-interval"], value=g["advertise_interval"]) + + if "priority" in g: + config.set(group_path + ["priority"], value=g["priority"]) + + if not g["preempt"]: + config.set(group_path + ["no-preempt"], value=None) + + if "preempt_delay" in g: + config.set(group_path + ["preempt-delay"], value=g["preempt_delay"]) + + if g["rfc_compatibility"]: + config.set(group_path + ["rfc3768-compatibility"], value=None) + + if g["disable"]: + config.set(group_path + ["disable"], value=None) + + if "hello_source" in g: + config.set(group_path + ["hello-source-address"], value=g["hello_source"]) + + if "peer_address" in g: + config.set(group_path + ["peer-address"], value=g["peer_address"]) + + if "auth_password" in g: + config.set(group_path + ["authentication", "password"], value=g["auth_password"]) + if "auth_type" in g: + config.set(group_path + ["authentication", "type"], value=g["auth_type"]) + + if "master_script" in g: + config.set(group_path + ["transition-script", "master"], value=g["master_script"]) + if "backup_script" in g: + config.set(group_path + ["transition-script", "backup"], value=g["backup_script"]) + if "fault_script" in g: + config.set(group_path + ["transition-script", "fault"], value=g["fault_script"]) + + if "health_check_interval" in g: + config.set(group_path + ["health-check", "interval"], value=g["health_check_interval"]) + if "health_check_count" in g: + config.set(group_path + ["health-check", "failure-count"], value=g["health_check_count"]) + if "health_check_script" in g: + config.set(group_path + ["health-check", "script"], value=g["health_check_script"]) + + # Not that it should ever be absent... + if "virtual_addresses" in g: + # The new CLI disallows addresses without prefix length + # Pre-rewrite configs didn't support IPv6 VRRP, but handle it anyway + for va in g["virtual_addresses"]: + if not re.search(r'/', va): + if re.search(r':', va): + va = "{0}/128".format(va) + else: + va = "{0}/32".format(va) + config.set(group_path + ["virtual-address"], value=va, replace=False) + + # Sync group + if "sync_group" in g: + config.set(sync_path + [g["sync_group"], "member"], value=group_name, replace=False) + + # Set the tag flag + config.set_tag(base_group_path) + if config.exists(sync_path): + config.set_tag(sync_path) diff --git a/src/migration-scripts/vrrp/2-to-3 b/src/migration-scripts/vrrp/2-to-3 new file mode 100644 index 0000000..468918f --- /dev/null +++ b/src/migration-scripts/vrrp/2-to-3 @@ -0,0 +1,44 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T3847: vrrp config cleanup + +from vyos.configtree import ConfigTree + +base = ['high-availability', 'vrrp'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['group']): + for group in config.list_nodes(base + ['group']): + group_base = base + ['group', group] + + # Deprecated option + tmp = group_base + ['transition-script', 'mode-force'] + if config.exists(tmp): + config.delete(tmp) + + # Rename virtual-address -> address + tmp = group_base + ['virtual-address'] + if config.exists(tmp): + config.rename(tmp, 'address') + + # Rename virtual-address-excluded -> excluded-address + tmp = group_base + ['virtual-address-excluded'] + if config.exists(tmp): + config.rename(tmp, 'excluded-address') diff --git a/src/migration-scripts/vrrp/3-to-4 b/src/migration-scripts/vrrp/3-to-4 new file mode 100644 index 0000000..9f05cf7 --- /dev/null +++ b/src/migration-scripts/vrrp/3-to-4 @@ -0,0 +1,32 @@ +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configtree import ConfigTree + +base = ['high-availability', 'virtual-server'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base): + for vs in config.list_nodes(base): + vs_base = base + [vs] + + # If the fwmark is used, the address is not required + if not config.exists(vs_base + ['fwmark']): + # add option: 'virtual-server <tag> address x.x.x.x' + config.set(vs_base + ['address'], value=vs) diff --git a/src/migration-scripts/webproxy/1-to-2 b/src/migration-scripts/webproxy/1-to-2 new file mode 100644 index 0000000..5a48474 --- /dev/null +++ b/src/migration-scripts/webproxy/1-to-2 @@ -0,0 +1,33 @@ +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# migrate old style `webproxy proxy-bypass 1.2.3.4/24` +# to new style `webproxy whitelist destination-address 1.2.3.4/24` + +from vyos.configtree import ConfigTree + +cfg_webproxy_base = ['service', 'webproxy'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(cfg_webproxy_base + ['proxy-bypass']): + # Nothing to do + return + + bypass_addresses = config.return_values(cfg_webproxy_base + ['proxy-bypass']) + # delete old configuration node + config.delete(cfg_webproxy_base + ['proxy-bypass']) + for bypass_address in bypass_addresses: + # add data to new configuration node + config.set(cfg_webproxy_base + ['whitelist', 'destination-address'], value=bypass_address, replace=False) diff --git a/src/op_mode/accelppp.py b/src/op_mode/accelppp.py new file mode 100644 index 0000000..67ce786 --- /dev/null +++ b/src/op_mode/accelppp.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import sys + +import vyos.accel_ppp +import vyos.opmode + +from vyos.configquery import ConfigTreeQuery +from vyos.utils.process import rc_cmd + + +accel_dict = { + 'ipoe': { + 'port': 2002, + 'path': 'service ipoe-server', + 'base_path': 'service ipoe-server' + }, + 'pppoe': { + 'port': 2001, + 'path': 'service pppoe-server', + 'base_path': 'service pppoe-server' + }, + 'pptp': { + 'port': 2003, + 'path': 'vpn pptp', + 'base_path': 'vpn pptp' + }, + 'l2tp': { + 'port': 2004, + 'path': 'vpn l2tp', + 'base_path': 'vpn l2tp remote-access' + }, + 'sstp': { + 'port': 2005, + 'path': 'vpn sstp', + 'base_path': 'vpn sstp' + } +} + + +def _get_config_settings(protocol): + '''Get config dict from VyOS configuration''' + conf = ConfigTreeQuery() + base_path = accel_dict[protocol]['base_path'] + data = conf.get_config_dict(base_path, + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + if conf.exists(f'{base_path} authentication local-users'): + # Delete sensitive data + del data['authentication']['local_users'] + return {'config_option': data} + + +def _get_raw_statistics(accel_output, pattern, protocol): + return { + **vyos.accel_ppp.get_server_statistics(accel_output, pattern, sep=':'), + **_get_config_settings(protocol) + } + + +def _get_raw_sessions(port): + cmd_options = 'show sessions ifname,username,ip,ip6,ip6-dp,type,rate-limit,' \ + 'state,uptime-raw,calling-sid,called-sid,sid,comp,rx-bytes-raw,' \ + 'tx-bytes-raw,rx-pkts,tx-pkts' + output = vyos.accel_ppp.accel_cmd(port, cmd_options) + parsed_data: list[dict[str, str]] = vyos.accel_ppp.accel_out_parse( + output.splitlines()) + return parsed_data + + +def _verify(func): + """Decorator checks if accel-ppp protocol + ipoe/pppoe/pptp/l2tp/sstp is configured + + for example: + service ipoe-server + vpn sstp + """ + from functools import wraps + + @wraps(func) + def _wrapper(*args, **kwargs): + config = ConfigTreeQuery() + protocol_list = accel_dict.keys() + protocol = kwargs.get('protocol') + # unknown or incorrect protocol query + if protocol not in protocol_list: + unconf_message = f'unknown protocol "{protocol}"' + raise vyos.opmode.UnconfiguredSubsystem(unconf_message) + # Check if config does not exist + config_protocol_path = accel_dict[protocol]['path'] + if not config.exists(config_protocol_path): + unconf_message = f'"{config_protocol_path}" is not configured' + raise vyos.opmode.UnconfiguredSubsystem(unconf_message) + return func(*args, **kwargs) + + return _wrapper + + +@_verify +def show_statistics(raw: bool, protocol: str): + """show accel-cmd statistics + CPU utilization and amount of sessions + + protocol: ipoe/pppoe/ppptp/l2tp/sstp + """ + pattern = f'{protocol}:' + port = accel_dict[protocol]['port'] + rc, output = rc_cmd(f'/usr/bin/accel-cmd -p {port} show stat') + + if raw: + return _get_raw_statistics(output, pattern, protocol) + + return output + + +@_verify +def show_sessions(raw: bool, protocol: str): + """show accel-cmd sessions + + protocol: ipoe/pppoe/ppptp/l2tp/sstp + """ + port = accel_dict[protocol]['port'] + if raw: + return _get_raw_sessions(port) + + return vyos.accel_ppp.accel_cmd(port, + 'show sessions ifname,username,ip,ip6,ip6-dp,' + 'calling-sid,rate-limit,state,uptime,rx-bytes,tx-bytes') + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/bgp.py b/src/op_mode/bgp.py new file mode 100644 index 0000000..096113c --- /dev/null +++ b/src/op_mode/bgp.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Purpose: +# Displays BGP neighbors and tables information. + +import re +import sys +import typing + +from jinja2 import Template + +import vyos.opmode + +frr_command_template = Template(""" +show bgp + +{## VRF and family modifiers that may precede any options ##} + +{% if vrf %} + vrf {{vrf}} +{% endif %} + +{% if family == "inet" %} + ipv4 +{% elif family == "inet6" %} + ipv6 +{% elif family == "l2vpn" %} + l2vpn evpn +{% endif %} + +{% if family_modifier == "unicast" %} + unicast +{% elif family_modifier == "multicast" %} + multicast +{% elif family_modifier == "flowspec" %} + flowspec +{% elif family_modifier == "vpn" %} + vpn +{% endif %} + +{## Mutually exclusive query parameters ##} + +{# Network prefix #} +{% if prefix %} + {{prefix}} + + {% if longer_prefixes %} + longer-prefixes + {% elif best_path %} + bestpath + {% endif %} +{% endif %} + +{# Regex #} +{% if regex %} + regex {{regex}} +{% endif %} + +{## Raw modifier ##} + +{% if raw %} + json +{% endif %} +""") + +ArgFamily = typing.Literal['inet', 'inet6', 'l2vpn'] +ArgFamilyModifier = typing.Literal['unicast', 'labeled_unicast', 'multicast', 'vpn', 'flowspec'] + +def show_summary(raw: bool): + from vyos.utils.process import cmd + + if raw: + from json import loads + + output = cmd(f"vtysh -c 'show bgp summary json'").strip() + + # FRR 8.5 correctly returns an empty object when BGP is not running, + # we don't need to do anything special here + return loads(output) + else: + output = cmd(f"vtysh -c 'show bgp summary'") + return output + +def show_neighbors(raw: bool): + from vyos.utils.process import cmd + from vyos.utils.dict import dict_to_list + + if raw: + from json import loads + + output = cmd(f"vtysh -c 'show bgp neighbors json'").strip() + d = loads(output) + return dict_to_list(d, save_key_to="neighbor") + else: + output = cmd(f"vtysh -c 'show bgp neighbors'") + return output + +def show(raw: bool, + family: ArgFamily, + family_modifier: ArgFamilyModifier, + prefix: typing.Optional[str], + longer_prefixes: typing.Optional[bool], + best_path: typing.Optional[bool], + regex: typing.Optional[str], + vrf: typing.Optional[str]): + from vyos.utils.dict import dict_to_list + + if (longer_prefixes or best_path) and (prefix is None): + raise ValueError("longer_prefixes and best_path can only be used when prefix is given") + elif (family == "l2vpn") and (family_modifier is not None): + raise ValueError("l2vpn family does not accept any modifiers") + else: + kwargs = dict(locals()) + + frr_command = frr_command_template.render(kwargs) + frr_command = re.sub(r'\s+', ' ', frr_command) + + from vyos.utils.process import cmd + output = cmd(f"vtysh -c '{frr_command}'") + + if raw: + from json import loads + d = loads(output) + if not ("routes" in d): + raise vyos.opmode.InternalError("FRR returned a BGP table with no routes field") + d = d["routes"] + routes = dict_to_list(d, save_key_to="route_key") + return routes + else: + return output + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/bonding.py b/src/op_mode/bonding.py new file mode 100644 index 0000000..07bccbd --- /dev/null +++ b/src/op_mode/bonding.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2016-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# This script will parse 'sudo cat /proc/net/bonding/<interface name>' and return table output for lacp related info + +import subprocess +import re +import sys +import typing +from tabulate import tabulate + +import vyos.opmode +from vyos.configquery import ConfigTreeQuery + +def list_to_dict(data, headers, basekey): + data_list = {basekey: []} + + for row in data: + row_dict = {headers[i]: row[i] for i in range(len(headers))} + data_list[basekey].append(row_dict) + + return data_list + +def show_lacp_neighbors(raw: bool, interface: typing.Optional[str]): + headers = ["Interface", "Member", "Local ID", "Remote ID"] + data = subprocess.run(f"cat /proc/net/bonding/{interface}", stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True, text=False).stdout.decode('utf-8') + if 'Bonding Mode: IEEE 802.3ad Dynamic link aggregation' not in data: + raise vyos.opmode.DataUnavailable(f"{interface} is not present or not configured with mode 802.3ad") + + pattern = re.compile( + r"Slave Interface: (?P<member>\w+\d+).*?" + r"system mac address: (?P<local_id>[0-9a-f:]+).*?" + r"details partner lacp pdu:.*?" + r"system mac address: (?P<remote_id>[0-9a-f:]+)", + re.DOTALL + ) + + interfaces = [] + + for match in re.finditer(pattern, data): + member = match.group("member") + local_id = match.group("local_id") + remote_id = match.group("remote_id") + interfaces.append([interface, member, local_id, remote_id]) + + if raw: + return list_to_dict(interfaces, headers, 'lacp') + else: + return tabulate(interfaces, headers) + +def show_lacp_detail(raw: bool, interface: typing.Optional[str]): + headers = ["Interface", "Members", "Mode", "Rate", "System-MAC", "Hash"] + query = ConfigTreeQuery() + + if interface: + intList = [interface] + else: + intList = query.list_nodes(['interfaces', 'bonding']) + + bondList = [] + + for interface in intList: + data = subprocess.run(f"cat /proc/net/bonding/{interface}", stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True, text=False).stdout.decode('utf-8') + if 'Bonding Mode: IEEE 802.3ad Dynamic link aggregation' not in data: + continue + + mode_active = "active" if "LACP active: on" in data else "passive" + lacp_rate = re.search(r"LACP rate: (\w+)", data).group(1) if re.search(r"LACP rate: (\w+)", data) else "N/A" + hash_policy = re.search(r"Transmit Hash Policy: (.+?) \(\d+\)", data).group(1) if re.search(r"Transmit Hash Policy: (.+?) \(\d+\)", data) else "N/A" + system_mac = re.search(r"System MAC address: ([0-9a-f:]+)", data).group(1) if re.search(r"System MAC address: ([0-9a-f:]+)", data) else "N/A" + if raw: + members = re.findall(r"Slave Interface: ([a-zA-Z0-9:_-]+)", data) + else: + members = ",".join(set(re.findall(r"Slave Interface: ([a-zA-Z0-9:_-]+)", data))) + + bondList.append([interface, members, mode_active, lacp_rate, system_mac, hash_policy]) + + if raw: + return list_to_dict(bondList, headers, 'lacp') + else: + return tabulate(bondList, headers) + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/bridge.py b/src/op_mode/bridge.py new file mode 100644 index 0000000..e80b1c2 --- /dev/null +++ b/src/op_mode/bridge.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import jmespath +import json +import sys +import typing + +from tabulate import tabulate + +from vyos.utils.process import cmd +from vyos.utils.process import rc_cmd +from vyos.utils.process import call + +import vyos.opmode + +def _get_json_data(): + """ + Get bridge data format JSON + """ + return cmd(f'bridge --json link show') + + +def _get_raw_data_summary(): + """Get interested rules + :returns dict + """ + data = _get_json_data() + data_dict = json.loads(data) + return data_dict + + +def _get_raw_data_vlan(tunnel:bool=False): + """ + :returns dict + """ + show = 'show' + if tunnel: + show = 'tunnel' + json_data = cmd(f'bridge --json --compressvlans vlan {show}') + data_dict = json.loads(json_data) + return data_dict + +def _get_raw_data_vni() -> dict: + """ + :returns dict + """ + json_data = cmd(f'bridge --json vni show') + data_dict = json.loads(json_data) + return data_dict + +def _get_raw_data_fdb(bridge): + """Get MAC-address for the bridge brX + :returns list + """ + code, json_data = rc_cmd(f'bridge --json fdb show br {bridge}') + # From iproute2 fdb.c, fdb_show() will only exit(-1) in case of + # non-existent bridge device; raise error. + if code == 255: + raise vyos.opmode.UnconfiguredObject(f"bridge {bridge} does not exist in the system") + data_dict = json.loads(json_data) + return data_dict + + +def _get_raw_data_mdb(bridge): + """Get MAC-address multicast gorup for the bridge brX + :return list + """ + json_data = cmd(f'bridge --json mdb show br {bridge}') + data_dict = json.loads(json_data) + return data_dict + + +def _get_bridge_members(bridge: str) -> list: + """ + Get list of interface bridge members + :param bridge: str + :default: ['n/a'] + :return: list + """ + data = _get_raw_data_summary() + members = jmespath.search(f'[?master == `{bridge}`].ifname', data) + return [member for member in members] if members else ['n/a'] + + +def _get_member_options(bridge: str): + data = _get_raw_data_summary() + options = jmespath.search(f'[?master == `{bridge}`]', data) + return options + + +def _get_formatted_output_summary(data): + data_entries = '' + bridges = set(jmespath.search('[*].master', data)) + for bridge in bridges: + member_options = _get_member_options(bridge) + member_entries = [] + for option in member_options: + interface = option.get('ifname') + ifindex = option.get('ifindex') + state = option.get('state') + mtu = option.get('mtu') + flags = ','.join(option.get('flags')).lower() + prio = option.get('priority') + member_entries.append([interface, state, mtu, flags, prio]) + member_headers = ["Member", "State", "MTU", "Flags", "Prio"] + output_members = tabulate(member_entries, member_headers, numalign="left") + output_bridge = f"""Bridge interface {bridge}: +{output_members} + +""" + data_entries += output_bridge + output = data_entries + return output + + +def _get_formatted_output_vlan(data): + data_entries = [] + for entry in data: + interface = entry.get('ifname') + vlans = entry.get('vlans') + for vlan_entry in vlans: + vlan = vlan_entry.get('vlan') + if vlan_entry.get('vlanEnd'): + vlan_end = vlan_entry.get('vlanEnd') + vlan = f'{vlan}-{vlan_end}' + flags_raw = vlan_entry.get('flags') + flags = ', '.join(flags_raw if isinstance(flags_raw,list) else "").lower() + data_entries.append([interface, vlan, flags]) + + headers = ["Interface", "VLAN", "Flags"] + output = tabulate(data_entries, headers) + return output + +def _get_formatted_output_vlan_tunnel(data): + data_entries = [] + for entry in data: + interface = entry.get('ifname') + first = True + for tunnel_entry in entry.get('tunnels'): + vlan = tunnel_entry.get('vlan') + vni = tunnel_entry.get('tunid') + if first: + data_entries.append([interface, vlan, vni]) + first = False + else: + # Group by VXLAN interface only - no need to repeat + # VXLAN interface name for every VLAN <-> VNI mapping + # + # Interface VLAN VNI + # ----------- ------ ----- + # vxlan0 100 100 + # 200 200 + data_entries.append(['', vlan, vni]) + + headers = ["Interface", "VLAN", "VNI"] + output = tabulate(data_entries, headers) + return output + +def _get_formatted_output_vni(data): + data_entries = [] + for entry in data: + interface = entry.get('ifname') + vlans = entry.get('vnis') + for vlan_entry in vlans: + vlan = vlan_entry.get('vni') + if vlan_entry.get('vniEnd'): + vlan_end = vlan_entry.get('vniEnd') + vlan = f'{vlan}-{vlan_end}' + data_entries.append([interface, vlan]) + + headers = ["Interface", "VNI"] + output = tabulate(data_entries, headers) + return output + +def _get_formatted_output_fdb(data): + data_entries = [] + for entry in data: + interface = entry.get('ifname') + mac = entry.get('mac') + state = entry.get('state') + flags = ','.join(entry['flags']) + data_entries.append([interface, mac, state, flags]) + + headers = ["Interface", "Mac address", "State", "Flags"] + output = tabulate(data_entries, headers, numalign="left") + return output + + +def _get_formatted_output_mdb(data): + data_entries = [] + for entry in data: + for mdb_entry in entry['mdb']: + interface = mdb_entry.get('port') + group = mdb_entry.get('grp') + state = mdb_entry.get('state') + flags = ','.join(mdb_entry.get('flags')) + data_entries.append([interface, group, state, flags]) + headers = ["Interface", "Group", "State", "Flags"] + output = tabulate(data_entries, headers) + return output + +def _get_bridge_detail(iface): + """Get interface detail statistics""" + return call(f'vtysh -c "show interface {iface}"') + +def _get_bridge_detail_nexthop_group(iface): + """Get interface detail nexthop_group statistics""" + return call(f'vtysh -c "show interface {iface} nexthop-group"') + +def _get_bridge_detail_nexthop_group_raw(iface): + out = cmd(f'vtysh -c "show interface {iface} nexthop-group"') + return out + +def _get_bridge_detail_raw(iface): + """Get interface detail json statistics""" + data = cmd(f'vtysh -c "show interface {iface} json"') + data_dict = json.loads(data) + return data_dict + +def show(raw: bool): + bridge_data = _get_raw_data_summary() + if raw: + return bridge_data + else: + return _get_formatted_output_summary(bridge_data) + + +def show_vlan(raw: bool, tunnel: typing.Optional[bool]): + bridge_vlan = _get_raw_data_vlan(tunnel) + if raw: + return bridge_vlan + else: + if tunnel: + return _get_formatted_output_vlan_tunnel(bridge_vlan) + else: + return _get_formatted_output_vlan(bridge_vlan) + +def show_vni(raw: bool): + bridge_vni = _get_raw_data_vni() + if raw: + return bridge_vni + else: + return _get_formatted_output_vni(bridge_vni) + +def show_fdb(raw: bool, interface: str): + fdb_data = _get_raw_data_fdb(interface) + if raw: + return fdb_data + else: + return _get_formatted_output_fdb(fdb_data) + + +def show_mdb(raw: bool, interface: str): + mdb_data = _get_raw_data_mdb(interface) + if raw: + return mdb_data + else: + return _get_formatted_output_mdb(mdb_data) + +def show_detail(raw: bool, nexthop_group: typing.Optional[bool], interface: str): + if raw: + if nexthop_group: + return _get_bridge_detail_nexthop_group_raw(interface) + else: + return _get_bridge_detail_raw(interface) + else: + if nexthop_group: + return _get_bridge_detail_nexthop_group(interface) + else: + return _get_bridge_detail(interface) + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/cgnat.py b/src/op_mode/cgnat.py new file mode 100644 index 0000000..9ad8f92 --- /dev/null +++ b/src/op_mode/cgnat.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json +import sys +import typing + +from tabulate import tabulate + +import vyos.opmode + +from vyos.configquery import ConfigTreeQuery +from vyos.utils.process import cmd + +CGNAT_TABLE = 'cgnat' + + +def _get_raw_data(external_address: str = '', internal_address: str = '') -> list[dict]: + """Get CGNAT dictionary and filter by external or internal address if provided.""" + cmd_output = cmd(f'nft --json list table ip {CGNAT_TABLE}') + data = json.loads(cmd_output) + + elements = data['nftables'][2]['map']['elem'] + allocations = [] + for elem in elements: + internal = elem[0] # internal + external = elem[1]['concat'][0] # external + start_port = elem[1]['concat'][1]['range'][0] + end_port = elem[1]['concat'][1]['range'][1] + port_range = f'{start_port}-{end_port}' + + if (internal_address and internal != internal_address) or ( + external_address and external != external_address + ): + continue + + allocations.append( + { + 'internal_address': internal, + 'external_address': external, + 'port_range': port_range, + } + ) + + return allocations + + +def _get_formatted_output(allocations: list[dict]) -> str: + # Convert the list of dictionaries to a list of tuples for tabulate + headers = ['Internal IP', 'External IP', 'Port range'] + data = [ + (alloc['internal_address'], alloc['external_address'], alloc['port_range']) + for alloc in allocations + ] + output = tabulate(data, headers, numalign="left") + return output + + +def show_allocation( + raw: bool, + external_address: typing.Optional[str], + internal_address: typing.Optional[str], +) -> str: + config = ConfigTreeQuery() + if not config.exists('nat cgnat'): + raise vyos.opmode.UnconfiguredSubsystem('CGNAT is not configured') + + if raw: + return _get_raw_data(external_address, internal_address) + + else: + raw_data = _get_raw_data(external_address, internal_address) + return _get_formatted_output(raw_data) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/clear_conntrack.py b/src/op_mode/clear_conntrack.py new file mode 100644 index 0000000..fec7cf1 --- /dev/null +++ b/src/op_mode/clear_conntrack.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys + +from vyos.utils.io import ask_yes_no +from vyos.utils.process import cmd +from vyos.utils.process import DEVNULL + +if not ask_yes_no("This will clear all currently tracked and expected connections. Continue?"): + sys.exit(1) +else: + cmd('/usr/sbin/conntrack -F', stderr=DEVNULL) + cmd('/usr/sbin/conntrack -F expect', stderr=DEVNULL) diff --git a/src/op_mode/config_mgmt.py b/src/op_mode/config_mgmt.py new file mode 100644 index 0000000..66de26d --- /dev/null +++ b/src/op_mode/config_mgmt.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import typing + +import vyos.opmode +from vyos.config_mgmt import ConfigMgmt + +def show_commit_diff(raw: bool, rev: int, rev2: typing.Optional[int], + commands: bool): + config_mgmt = ConfigMgmt() + config_diff = config_mgmt.show_commit_diff(rev, rev2, commands) + + if raw: + rev2 = (rev+1) if rev2 is None else rev2 + if commands: + d = {f'config_command_diff_{rev2}_{rev}': config_diff} + else: + d = {f'config_file_diff_{rev2}_{rev}': config_diff} + return d + + return config_diff + +def show_commit_file(raw: bool, rev: int): + config_mgmt = ConfigMgmt() + config_file = config_mgmt.show_commit_file(rev) + + if raw: + d = {f'config_revision_{rev}': config_file} + return d + + return config_file + +def show_commit_log(raw: bool): + config_mgmt = ConfigMgmt() + + msg = '' + if config_mgmt.max_revisions == 0: + msg = ('commit-revisions is not configured;\n' + 'commit log is empty or stale:\n\n') + + data = config_mgmt.get_raw_log_data() + if raw: + return data + + out = config_mgmt.format_log_data(data) + out = msg + out + + return out + +def show_commit_log_brief(raw: bool): + # used internally for completion help for 'rollback' + # option 'raw' will return same as 'show_commit_log' + config_mgmt = ConfigMgmt() + + data = config_mgmt.get_raw_log_data() + if raw: + return data + + out = config_mgmt.format_log_data_brief(data) + + return out + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/connect_disconnect.py b/src/op_mode/connect_disconnect.py new file mode 100644 index 0000000..8903f91 --- /dev/null +++ b/src/op_mode/connect_disconnect.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import argparse + +from psutil import process_iter + +from vyos.configquery import ConfigTreeQuery +from vyos.utils.process import call +from vyos.utils.commit import commit_in_progress +from vyos.utils.network import is_wwan_connected +from vyos.utils.process import DEVNULL + +def check_ppp_interface(interface): + if not os.path.isfile(f'/etc/ppp/peers/{interface}'): + print(f'Interface {interface} does not exist!') + exit(1) + +def check_ppp_running(interface): + """ Check if PPP process is running in the interface in question """ + for p in process_iter(): + if "pppd" in p.name(): + if interface in p.cmdline(): + return True + + return False + +def connect(interface): + """ Connect dialer interface """ + + if interface.startswith('pppoe') or interface.startswith('sstpc'): + check_ppp_interface(interface) + # Check if interface is already dialed + if os.path.isdir(f'/sys/class/net/{interface}'): + print(f'Interface {interface}: already connected!') + elif check_ppp_running(interface): + print(f'Interface {interface}: connection is being established!') + else: + print(f'Interface {interface}: connecting...') + call(f'systemctl restart ppp@{interface}.service') + elif interface.startswith('wwan'): + if is_wwan_connected(interface): + print(f'Interface {interface}: already connected!') + else: + call(f'VYOS_TAGNODE_VALUE={interface} /usr/libexec/vyos/conf_mode/interfaces_wwan.py') + else: + print(f'Unknown interface {interface}, cannot connect. Aborting!') + + # Reaply QoS configuration + config = ConfigTreeQuery() + if config.exists(f'qos interface {interface}'): + count = 1 + while commit_in_progress(): + if ( count % 60 == 0 ): + print(f'Commit still in progress after {count}s - waiting') + count += 1 + time.sleep(1) + call('/usr/libexec/vyos/conf_mode/qos.py') + +def disconnect(interface): + """ Disconnect dialer interface """ + + if interface.startswith('pppoe') or interface.startswith('sstpc'): + check_ppp_interface(interface) + + # Check if interface is already down + if not check_ppp_running(interface): + print(f'Interface {interface}: connection is already down') + else: + print(f'Interface {interface}: disconnecting...') + call(f'systemctl stop ppp@{interface}.service') + elif interface.startswith('wwan'): + if not is_wwan_connected(interface): + print(f'Interface {interface}: connection is already down') + else: + modem = interface.lstrip('wwan') + call(f'mmcli --modem {modem} --simple-disconnect', stdout=DEVNULL) + else: + print(f'Unknown interface {interface}, cannot disconnect. Aborting!') + +def main(): + parser = argparse.ArgumentParser() + group = parser.add_mutually_exclusive_group() + group.add_argument("--connect", help="Bring up a connection-oriented network interface", action="store_true") + group.add_argument("--disconnect", help="Take down connection-oriented network interface", action="store_true") + parser.add_argument("--interface", help="Interface name", action="store", required=True) + args = parser.parse_args() + + if args.connect or args.disconnect: + if args.disconnect: + disconnect(args.interface) + + if args.connect: + if commit_in_progress(): + print('Cannot connect while a commit is in progress') + exit(1) + connect(args.interface) + + else: + parser.print_help() + + exit(0) + +if __name__ == '__main__': + main() diff --git a/src/op_mode/conntrack.py b/src/op_mode/conntrack.py new file mode 100644 index 0000000..c379c3e --- /dev/null +++ b/src/op_mode/conntrack.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import typing +import xmltodict + +from tabulate import tabulate +from vyos.utils.process import cmd + +import vyos.opmode + +ArgFamily = typing.Literal['inet', 'inet6'] + +def _get_xml_data(family): + """ + Get conntrack XML output + """ + return cmd(f'sudo conntrack --dump --family {family} --output xml') + + +def _xml_to_dict(xml): + """ + Convert XML to dictionary + Return: dictionary + """ + parse = xmltodict.parse(xml, attr_prefix='') + # If only one conntrack entry we must change dict + if 'meta' in parse['conntrack']['flow']: + return dict(conntrack={'flow': [parse['conntrack']['flow']]}) + return parse + + +def _get_raw_data(family): + """ + Return: dictionary + """ + xml = _get_xml_data(family) + if len(xml) == 0: + output = {'conntrack': + { + 'error': True, + 'reason': 'entries not found' + } + } + return output + return _xml_to_dict(xml) + + +def _get_raw_statistics(): + entries = [] + data = cmd('sudo conntrack --stats') + data = data.replace(' \t', '').split('\n') + for entry in data: + entries.append(entry.split()) + return entries + + +def get_formatted_statistics(entries): + headers = [ + "CPU", + "Found", + "Invalid", + "Insert", + "Insert fail", + "Drop", + "Early drop", + "Errors", + "Search restart", + "", + "", + ] + # Process each entry to extract and format the values after '=' + processed_entries = [ + [value.split('=')[-1] for value in entry] + for entry in entries + ] + output = tabulate(processed_entries, headers, numalign="left") + return output + + +def get_formatted_output(dict_data): + """ + :param xml: + :return: formatted output + """ + data_entries = [] + if 'error' in dict_data['conntrack']: + return 'Entries not found' + for entry in dict_data['conntrack']['flow']: + orig_src, orig_dst, orig_sport, orig_dport = {}, {}, {}, {} + reply_src, reply_dst, reply_sport, reply_dport = {}, {}, {}, {} + proto = {} + for meta in entry['meta']: + direction = meta['direction'] + if direction in ['original']: + if 'layer3' in meta: + orig_src = meta['layer3']['src'] + orig_dst = meta['layer3']['dst'] + if 'layer4' in meta: + if meta.get('layer4').get('sport'): + orig_sport = meta['layer4']['sport'] + if meta.get('layer4').get('dport'): + orig_dport = meta['layer4']['dport'] + proto = meta['layer4']['protoname'] + if direction in ['reply']: + if 'layer3' in meta: + reply_src = meta['layer3']['src'] + reply_dst = meta['layer3']['dst'] + if 'layer4' in meta: + if meta.get('layer4').get('sport'): + reply_sport = meta['layer4']['sport'] + if meta.get('layer4').get('dport'): + reply_dport = meta['layer4']['dport'] + proto = meta['layer4']['protoname'] + if direction == 'independent': + conn_id = meta['id'] + # T6138 flowtable offload conntrack entries without 'timeout' + timeout = meta.get('timeout', 'n/a') + orig_src = f'{orig_src}:{orig_sport}' if orig_sport else orig_src + orig_dst = f'{orig_dst}:{orig_dport}' if orig_dport else orig_dst + reply_src = f'{reply_src}:{reply_sport}' if reply_sport else reply_src + reply_dst = f'{reply_dst}:{reply_dport}' if reply_dport else reply_dst + state = meta['state'] if 'state' in meta else '' + mark = meta['mark'] if 'mark' in meta else '' + zone = meta['zone'] if 'zone' in meta else '' + data_entries.append( + [conn_id, orig_src, orig_dst, reply_src, reply_dst, proto, state, timeout, mark, zone]) + headers = ["Id", "Original src", "Original dst", "Reply src", "Reply dst", "Protocol", "State", "Timeout", "Mark", + "Zone"] + output = tabulate(data_entries, headers, numalign="left") + return output + + +def show(raw: bool, family: ArgFamily): + family = 'ipv6' if family == 'inet6' else 'ipv4' + conntrack_data = _get_raw_data(family) + if raw: + return conntrack_data + else: + return get_formatted_output(conntrack_data) + + +def show_statistics(raw: bool): + conntrack_statistics = _get_raw_statistics() + if raw: + return conntrack_statistics + else: + return get_formatted_statistics(conntrack_statistics) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/conntrack_sync.py b/src/op_mode/conntrack_sync.py new file mode 100644 index 0000000..f3b09b4 --- /dev/null +++ b/src/op_mode/conntrack_sync.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys +import syslog +import xmltodict + +from tabulate import tabulate + +import vyos.opmode + +from vyos.configquery import CliShellApiConfigQuery +from vyos.configquery import ConfigTreeQuery +from vyos.utils.commit import commit_in_progress +from vyos.utils.process import call +from vyos.utils.process import cmd +from vyos.utils.process import run + +conntrackd_bin = '/usr/sbin/conntrackd' +conntrackd_config = '/run/conntrackd/conntrackd.conf' +failover_state_file = '/var/run/vyatta-conntrackd-failover-state' + +def is_configured(): + """ Check if conntrack-sync service is configured """ + config = CliShellApiConfigQuery() + if not config.exists(['service', 'conntrack-sync']): + raise vyos.opmode.UnconfiguredSubsystem("conntrack-sync is not configured!") + +def send_bulk_update(): + """ send bulk update of internal-cache to other systems """ + tmp = run(f'{conntrackd_bin} -C {conntrackd_config} -B') + if tmp > 0: + raise vyos.opmode.Error('Failed to send bulk update to other conntrack-sync systems') + +def request_sync(): + """ request resynchronization with other systems """ + tmp = run(f'{conntrackd_bin} -C {conntrackd_config} -n') + if tmp > 0: + raise vyos.opmode.Error('Failed to request resynchronization of external cache') + +def flush_cache(direction): + """ flush conntrackd cache (internal or external) """ + if direction not in ['internal', 'external']: + raise ValueError() + tmp = run(f'{conntrackd_bin} -C {conntrackd_config} -f {direction}') + if tmp > 0: + raise vyos.opmode.Error('Failed to clear {direction} cache') + +def get_formatted_output(data): + data_entries = [] + for parsed in data: + for meta in parsed.get('flow', {}).get('meta', []): + direction = meta['@direction'] + if direction == 'original': + src = meta['layer3']['src'] + dst = meta['layer3']['dst'] + sport = meta['layer4'].get('sport') + dport = meta['layer4'].get('dport') + protocol = meta['layer4'].get('@protoname') + orig_src = f'{src}:{sport}' if sport else src + orig_dst = f'{dst}:{dport}' if dport else dst + + data_entries.append([orig_src, orig_dst, protocol]) + + headers = ["Source", "Destination", "Protocol"] + output = tabulate(data_entries, headers, tablefmt="simple") + return output + +def from_xml(raw, xml): + out = [] + for line in xml.splitlines(): + if line == '\n': + continue + parsed = xmltodict.parse(line) + out.append(parsed) + + if raw: + return out + else: + return get_formatted_output(out) + +def restart(): + is_configured() + if commit_in_progress(): + raise vyos.opmode.CommitInProgress('Cannot restart conntrackd while a commit is in progress') + + syslog.syslog('Restarting conntrack sync service...') + cmd('systemctl restart conntrackd.service') + # request resynchronization with other systems + request_sync() + # send bulk update of internal-cache to other systems + send_bulk_update() + +def reset_external_cache(): + is_configured() + syslog.syslog('Resetting external cache of conntrack sync service...') + + # flush the external cache + flush_cache('external') + # request resynchronization with other systems + request_sync() + +def reset_internal_cache(): + is_configured() + syslog.syslog('Resetting internal cache of conntrack sync service...') + # flush the internal cache + flush_cache('internal') + + # request resynchronization of internal cache with kernel conntrack table + tmp = run(f'{conntrackd_bin} -C {conntrackd_config} -R') + if tmp > 0: + print('ERROR: failed to resynchronize internal cache with kernel conntrack table') + + # send bulk update of internal-cache to other systems + send_bulk_update() + +def _show_cache(raw, opts): + is_configured() + out = cmd(f'{conntrackd_bin} -C {conntrackd_config} {opts} -x') + return from_xml(raw, out) + +def show_external_cache(raw: bool): + opts = '-e ct' + return _show_cache(raw, opts) + +def show_external_expect(raw: bool): + opts = '-e expect' + return _show_cache(raw, opts) + +def show_internal_cache(raw: bool): + opts = '-i ct' + return _show_cache(raw, opts) + +def show_internal_expect(raw: bool): + opts = '-i expect' + return _show_cache(raw, opts) + +def show_statistics(raw: bool): + if raw: + raise vyos.opmode.UnsupportedOperation("Machine-readable conntrack-sync statistics are not available yet") + else: + is_configured() + config = ConfigTreeQuery() + print('\nMain Table Statistics:\n') + call(f'{conntrackd_bin} -C {conntrackd_config} -s') + print() + if config.exists(['service', 'conntrack-sync', 'expect-sync']): + print('\nExpect Table Statistics:\n') + call(f'{conntrackd_bin} -C {conntrackd_config} -s exp') + print() + +def show_status(raw: bool): + is_configured() + config = ConfigTreeQuery() + ct_sync_intf = config.list_nodes(['service', 'conntrack-sync', 'interface']) + ct_sync_intf = ', '.join(ct_sync_intf) + failover_state = "no transition yet!" + expect_sync_protocols = [] + + if config.exists(['service', 'conntrack-sync', 'failover-mechanism', 'vrrp']): + failover_mechanism = "vrrp" + vrrp_sync_grp = config.value(['service', 'conntrack-sync', 'failover-mechanism', 'vrrp', 'sync-group']) + + if os.path.isfile(failover_state_file): + with open(failover_state_file, "r") as f: + failover_state = f.readline() + + if config.exists(['service', 'conntrack-sync', 'expect-sync']): + expect_sync_protocols = config.values(['service', 'conntrack-sync', 'expect-sync']) + if 'all' in expect_sync_protocols: + expect_sync_protocols = ["ftp", "sip", "h323", "nfs", "sqlnet"] + + if raw: + status_data = { + "sync_interface": ct_sync_intf, + "failover_mechanism": failover_mechanism, + "sync_group": vrrp_sync_grp, + "last_transition": failover_state, + "sync_protocols": expect_sync_protocols + } + + return status_data + else: + if expect_sync_protocols: + expect_sync_protocols = ', '.join(expect_sync_protocols) + else: + expect_sync_protocols = "disabled" + show_status = (f'\nsync-interface : {ct_sync_intf}\n' + f'failover-mechanism : {failover_mechanism} [sync-group {vrrp_sync_grp}]\n' + f'last state transition : {failover_state}\n' + f'ExpectationSync : {expect_sync_protocols}') + + return show_status + +if __name__ == '__main__': + syslog.openlog(ident='conntrack-tools', logoption=syslog.LOG_PID, facility=syslog.LOG_INFO) + + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/container.py b/src/op_mode/container.py new file mode 100644 index 0000000..05f65df --- /dev/null +++ b/src/op_mode/container.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json +import sys + +from vyos.utils.process import cmd +from vyos.utils.process import rc_cmd +import vyos.opmode + +def _get_json_data(command: str) -> list: + """ + Get container command format JSON + """ + return cmd(f'{command} --format json') + +def _get_raw_data(command: str) -> list: + json_data = _get_json_data(command) + data = json.loads(json_data) + return data + +def add_image(name: str): + """ Pull image from container registry. If registry authentication + is defined within VyOS CLI, credentials are used to login befroe pull """ + from vyos.configquery import ConfigTreeQuery + + conf = ConfigTreeQuery() + container = conf.get_config_dict(['container', 'registry']) + + do_logout = False + if 'registry' in container: + for registry, registry_config in container['registry'].items(): + if 'disable' in registry_config: + continue + if 'authentication' in registry_config: + do_logout = True + if {'username', 'password'} <= set(registry_config['authentication']): + username = registry_config['authentication']['username'] + password = registry_config['authentication']['password'] + cmd = f'podman login --username {username} --password {password} {registry}' + rc, out = rc_cmd(cmd) + if rc != 0: raise vyos.opmode.InternalError(out) + + rc, output = rc_cmd(f'podman image pull {name}') + if rc != 0: + raise vyos.opmode.InternalError(output) + + if do_logout: + rc_cmd('podman logout --all') + +def delete_image(name: str): + from vyos.utils.process import rc_cmd + + if name == 'all': + # gather list of all images and pass them to the removal list + name = cmd('sudo podman image ls --quiet') + # If there are no container images left, we can not delete them all + if not name: return + # replace newline with whitespace + name = name.replace('\n', ' ') + rc, output = rc_cmd(f'podman image rm {name}') + if rc != 0: + raise vyos.opmode.InternalError(output) + +def show_container(raw: bool): + command = 'podman ps --all' + container_data = _get_raw_data(command) + if raw: + return container_data + else: + return cmd(command) + +def show_image(raw: bool): + command = 'podman image ls' + container_data = _get_raw_data('podman image ls') + if raw: + return container_data + else: + return cmd(command) + +def show_network(raw: bool): + command = 'podman network ls' + container_data = _get_raw_data(command) + if raw: + return container_data + else: + return cmd(command) + +def restart(name: str): + from vyos.utils.process import rc_cmd + + rc, output = rc_cmd(f'systemctl restart vyos-container-{name}.service') + if rc != 0: + print(output) + return None + print(f'Container "{name}" restarted!') + return output + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/cpu.py b/src/op_mode/cpu.py new file mode 100644 index 0000000..1a0f739 --- /dev/null +++ b/src/op_mode/cpu.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2016-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys + +import vyos.opmode +from vyos.utils.cpu import get_cpus +from vyos.utils.cpu import get_core_count + +from jinja2 import Template + +cpu_template = Template(""" +{% for cpu in cpus %} +{% if 'physical id' in cpu %}CPU socket: {{cpu['physical id']}}{% endif %} +{% if 'vendor_id' in cpu %}CPU Vendor: {{cpu['vendor_id']}}{% endif %} +{% if 'model name' in cpu %}Model: {{cpu['model name']}}{% endif %} +{% if 'cpu cores' in cpu %}Cores: {{cpu['cpu cores']}}{% endif %} +{% if 'cpu MHz' in cpu %}Current MHz: {{cpu['cpu MHz']}}{% endif %} +{% endfor %} +""") + +cpu_summary_template = Template(""" +Physical CPU cores: {{count}} +CPU model(s): {{models | join(", ")}} +""") + +def _get_raw_data(): + return get_cpus() + +def _format_cpus(cpu_data): + env = {'cpus': cpu_data} + return cpu_template.render(env).strip() + +def _get_summary_data(): + count = get_core_count() + cpu_data = get_cpus() + models = [c['model name'] for c in cpu_data] + env = {'count': count, "models": models} + + return env + +def _format_cpu_summary(summary_data): + return cpu_summary_template.render(summary_data).strip() + +def show(raw: bool): + cpu_data = _get_raw_data() + + if raw: + return cpu_data + else: + return _format_cpus(cpu_data) + +def show_summary(raw: bool): + cpu_summary_data = _get_summary_data() + + if raw: + return cpu_summary_data + else: + return _format_cpu_summary(cpu_summary_data) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py new file mode 100644 index 0000000..e5455c8 --- /dev/null +++ b/src/op_mode/dhcp.py @@ -0,0 +1,530 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys +import typing + +from datetime import datetime +from glob import glob +from ipaddress import ip_address +from tabulate import tabulate + +import vyos.opmode + +from vyos.base import Warning +from vyos.configquery import ConfigTreeQuery + +from vyos.kea import kea_get_active_config +from vyos.kea import kea_get_leases +from vyos.kea import kea_get_pool_from_subnet_id +from vyos.kea import kea_delete_lease +from vyos.utils.process import is_systemd_service_running +from vyos.utils.process import call + +time_string = "%a %b %d %H:%M:%S %Z %Y" + +config = ConfigTreeQuery() +lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup'] +sort_valid_inet = ['end', 'mac', 'hostname', 'ip', 'pool', 'remaining', 'start', 'state'] +sort_valid_inet6 = ['end', 'duid', 'ip', 'last_communication', 'pool', 'remaining', 'state', 'type'] +mapping_sort_valid = ['mac', 'ip', 'pool', 'duid'] + +ArgFamily = typing.Literal['inet', 'inet6'] +ArgState = typing.Literal['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup'] +ArgOrigin = typing.Literal['local', 'remote'] + +def _utc_to_local(utc_dt): + return datetime.fromtimestamp((datetime.fromtimestamp(utc_dt) - datetime(1970, 1, 1)).total_seconds()) + + +def _format_hex_string(in_str): + out_str = "" + # if input is divisible by 2, add : every 2 chars + if len(in_str) > 0 and len(in_str) % 2 == 0: + out_str = ':'.join(a+b for a,b in zip(in_str[::2], in_str[1::2])) + else: + out_str = in_str + + return out_str + + +def _find_list_of_dict_index(lst, key='ip', value='') -> int: + """ + Find the index entry of list of dict matching the dict value + Exampe: + % lst = [{'ip': '192.0.2.1'}, {'ip': '192.0.2.2'}] + % _find_list_of_dict_index(lst, key='ip', value='192.0.2.2') + % 1 + """ + idx = next((index for (index, d) in enumerate(lst) if d[key] == value), None) + return idx + + +def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[], origin=None) -> list: + """ + Get DHCP server leases + :return list + """ + inet_suffix = '6' if family == 'inet6' else '4' + try: + leases = kea_get_leases(inet_suffix) + except: + raise vyos.opmode.DataUnavailable('Cannot fetch DHCP server lease information') + + if pool is None: + pool = _get_dhcp_pools(family=family) + else: + pool = [pool] + + try: + active_config = kea_get_active_config(inet_suffix) + except: + raise vyos.opmode.DataUnavailable('Cannot fetch DHCP server configuration') + + data = [] + for lease in leases: + lifetime = lease['valid-lft'] + expiry = (lease['cltt'] + lifetime) + + lease['start_timestamp'] = datetime.utcfromtimestamp(expiry - lifetime) + lease['expire_timestamp'] = datetime.utcfromtimestamp(expiry) if expiry else None + + data_lease = {} + data_lease['ip'] = lease['ip-address'] + lease_state_long = {0: 'active', 1: 'rejected', 2: 'expired'} + data_lease['state'] = lease_state_long[lease['state']] + data_lease['pool'] = kea_get_pool_from_subnet_id(active_config, inet_suffix, lease['subnet-id']) if active_config else '-' + data_lease['end'] = lease['expire_timestamp'].timestamp() if lease['expire_timestamp'] else None + data_lease['origin'] = 'local' # TODO: Determine remote in HA + + if family == 'inet': + data_lease['mac'] = lease['hw-address'] + data_lease['start'] = lease['start_timestamp'].timestamp() + data_lease['hostname'] = lease['hostname'] + + if family == 'inet6': + data_lease['last_communication'] = lease['start_timestamp'].timestamp() + data_lease['duid'] = _format_hex_string(lease['duid']) + data_lease['type'] = lease['type'] + + if lease['type'] == 'IA_PD': + prefix_len = lease['prefix-len'] + data_lease['ip'] += f'/{prefix_len}' + + data_lease['remaining'] = '-' + + if lease['valid-lft'] > 0: + data_lease['remaining'] = lease['expire_timestamp'] - datetime.utcnow() + + if data_lease['remaining'].days >= 0: + # substraction gives us a timedelta object which can't be formatted with strftime + # so we use str(), split gets rid of the microseconds + data_lease['remaining'] = str(data_lease["remaining"]).split('.')[0] + + # Do not add old leases + if data_lease['remaining'] != '' and data_lease['pool'] in pool and data_lease['state'] != 'free': + if not state or state == 'all' or data_lease['state'] in state: + data.append(data_lease) + + # deduplicate + checked = [] + for entry in data: + addr = entry.get('ip') + if addr not in checked: + checked.append(addr) + else: + idx = _find_list_of_dict_index(data, key='ip', value=addr) + data.pop(idx) + + if sorted: + if sorted == 'ip': + data.sort(key = lambda x:ip_address(x['ip'])) + else: + data.sort(key = lambda x:x[sorted]) + return data + + +def _get_formatted_server_leases(raw_data, family='inet'): + data_entries = [] + if family == 'inet': + for lease in raw_data: + ipaddr = lease.get('ip') + hw_addr = lease.get('mac') + state = lease.get('state') + start = lease.get('start') + start = _utc_to_local(start).strftime('%Y/%m/%d %H:%M:%S') + end = lease.get('end') + end = _utc_to_local(end).strftime('%Y/%m/%d %H:%M:%S') if end else '-' + remain = lease.get('remaining') + pool = lease.get('pool') + hostname = lease.get('hostname') + origin = lease.get('origin') + data_entries.append([ipaddr, hw_addr, state, start, end, remain, pool, hostname, origin]) + + headers = ['IP Address', 'MAC address', 'State', 'Lease start', 'Lease expiration', 'Remaining', 'Pool', + 'Hostname', 'Origin'] + + if family == 'inet6': + for lease in raw_data: + ipaddr = lease.get('ip') + state = lease.get('state') + start = lease.get('last_communication') + start = _utc_to_local(start).strftime('%Y/%m/%d %H:%M:%S') + end = lease.get('end') + end = _utc_to_local(end).strftime('%Y/%m/%d %H:%M:%S') + remain = lease.get('remaining') + lease_type = lease.get('type') + pool = lease.get('pool') + host_identifier = lease.get('duid') + data_entries.append([ipaddr, state, start, end, remain, lease_type, pool, host_identifier]) + + headers = ['IPv6 address', 'State', 'Last communication', 'Lease expiration', 'Remaining', 'Type', 'Pool', + 'DUID'] + + output = tabulate(data_entries, headers, numalign='left') + return output + + +def _get_dhcp_pools(family='inet') -> list: + v = 'v6' if family == 'inet6' else '' + pools = config.list_nodes(f'service dhcp{v}-server shared-network-name') + return pools + + +def _get_pool_size(pool, family='inet'): + v = 'v6' if family == 'inet6' else '' + base = f'service dhcp{v}-server shared-network-name {pool}' + size = 0 + subnets = config.list_nodes(f'{base} subnet') + for subnet in subnets: + ranges = config.list_nodes(f'{base} subnet {subnet} range') + for range in ranges: + if family == 'inet6': + start = config.value(f'{base} subnet {subnet} range {range} start') + stop = config.value(f'{base} subnet {subnet} range {range} stop') + else: + start = config.value(f'{base} subnet {subnet} range {range} start') + stop = config.value(f'{base} subnet {subnet} range {range} stop') + # Add +1 because both range boundaries are inclusive + size += int(ip_address(stop)) - int(ip_address(start)) + 1 + return size + + +def _get_raw_pool_statistics(family='inet', pool=None): + if pool is None: + pool = _get_dhcp_pools(family=family) + else: + pool = [pool] + + v = 'v6' if family == 'inet6' else '' + stats = [] + for p in pool: + subnet = config.list_nodes(f'service dhcp{v}-server shared-network-name {p} subnet') + size = _get_pool_size(family=family, pool=p) + leases = len(_get_raw_server_leases(family=family, pool=p)) + use_percentage = round(leases / size * 100) if size != 0 else 0 + pool_stats = {'pool': p, 'size': size, 'leases': leases, + 'available': (size - leases), 'use_percentage': use_percentage, 'subnet': subnet} + stats.append(pool_stats) + return stats + + +def _get_formatted_pool_statistics(pool_data, family='inet'): + data_entries = [] + for entry in pool_data: + pool = entry.get('pool') + size = entry.get('size') + leases = entry.get('leases') + available = entry.get('available') + use_percentage = entry.get('use_percentage') + use_percentage = f'{use_percentage}%' + data_entries.append([pool, size, leases, available, use_percentage]) + + headers = ['Pool', 'Size','Leases', 'Available', 'Usage'] + output = tabulate(data_entries, headers, numalign='left') + return output + +def _get_raw_server_static_mappings(family='inet', pool=None, sorted=None): + if pool is None: + pool = _get_dhcp_pools(family=family) + else: + pool = [pool] + + v = 'v6' if family == 'inet6' else '' + mappings = [] + for p in pool: + pool_config = config.get_config_dict(['service', f'dhcp{v}-server', 'shared-network-name', p], + get_first_key=True) + if 'subnet' in pool_config: + for subnet, subnet_config in pool_config['subnet'].items(): + if 'static-mapping' in subnet_config: + for name, mapping_config in subnet_config['static-mapping'].items(): + mapping = {'pool': p, 'subnet': subnet, 'name': name} + mapping.update(mapping_config) + mappings.append(mapping) + + if sorted: + if sorted == 'ip': + data.sort(key = lambda x:ip_address(x['ip-address'])) + else: + data.sort(key = lambda x:x[sorted]) + return mappings + +def _get_formatted_server_static_mappings(raw_data, family='inet'): + data_entries = [] + for entry in raw_data: + pool = entry.get('pool') + subnet = entry.get('subnet') + name = entry.get('name') + ip_addr = entry.get('ip-address', 'N/A') + mac_addr = entry.get('mac', 'N/A') + duid = entry.get('duid', 'N/A') + description = entry.get('description', 'N/A') + data_entries.append([pool, subnet, name, ip_addr, mac_addr, duid, description]) + + headers = ['Pool', 'Subnet', 'Name', 'IP Address', 'MAC Address', 'DUID', 'Description'] + output = tabulate(data_entries, headers, numalign='left') + return output + +def _verify(func): + """Decorator checks if DHCP(v6) config exists""" + from functools import wraps + + @wraps(func) + def _wrapper(*args, **kwargs): + config = ConfigTreeQuery() + family = kwargs.get('family') + v = 'v6' if family == 'inet6' else '' + unconf_message = f'DHCP{v} server is not configured' + # Check if config does not exist + if not config.exists(f'service dhcp{v}-server'): + raise vyos.opmode.UnconfiguredSubsystem(unconf_message) + return func(*args, **kwargs) + return _wrapper + +def _verify_client(func): + """Decorator checks if interface is configured as DHCP client""" + from functools import wraps + from vyos.ifconfig import Section + + @wraps(func) + def _wrapper(*args, **kwargs): + config = ConfigTreeQuery() + family = kwargs.get('family') + v = 'v6' if family == 'inet6' else '' + interface = kwargs.get('interface') + interface_path = Section.get_config_path(interface) + unconf_message = f'DHCP{v} client not configured on interface {interface}!' + + # Check if config does not exist + if not config.exists(f'interfaces {interface_path} address dhcp{v}'): + raise vyos.opmode.UnconfiguredObject(unconf_message) + return func(*args, **kwargs) + return _wrapper + +@_verify +def show_pool_statistics(raw: bool, family: ArgFamily, pool: typing.Optional[str]): + pool_data = _get_raw_pool_statistics(family=family, pool=pool) + if raw: + return pool_data + else: + return _get_formatted_pool_statistics(pool_data, family=family) + + +@_verify +def show_server_leases(raw: bool, family: ArgFamily, pool: typing.Optional[str], + sorted: typing.Optional[str], state: typing.Optional[ArgState], + origin: typing.Optional[ArgOrigin] ): + # if dhcp server is down, inactive leases may still be shown as active, so warn the user. + v = '6' if family == 'inet6' else '4' + if not is_systemd_service_running(f'kea-dhcp{v}-server.service'): + Warning('DHCP server is configured but not started. Data may be stale.') + + v = 'v6' if family == 'inet6' else '' + if pool and pool not in _get_dhcp_pools(family=family): + raise vyos.opmode.IncorrectValue(f'DHCP{v} pool "{pool}" does not exist!') + + if state and state not in lease_valid_states: + raise vyos.opmode.IncorrectValue(f'DHCP{v} state "{state}" is invalid!') + + sort_valid = sort_valid_inet6 if family == 'inet6' else sort_valid_inet + if sorted and sorted not in sort_valid: + raise vyos.opmode.IncorrectValue(f'DHCP{v} sort "{sorted}" is invalid!') + + lease_data = _get_raw_server_leases(family=family, pool=pool, sorted=sorted, state=state, origin=origin) + if raw: + return lease_data + else: + return _get_formatted_server_leases(lease_data, family=family) + +@_verify +def show_server_static_mappings(raw: bool, family: ArgFamily, pool: typing.Optional[str], + sorted: typing.Optional[str]): + v = 'v6' if family == 'inet6' else '' + if pool and pool not in _get_dhcp_pools(family=family): + raise vyos.opmode.IncorrectValue(f'DHCP{v} pool "{pool}" does not exist!') + + if sorted and sorted not in mapping_sort_valid: + raise vyos.opmode.IncorrectValue(f'DHCP{v} sort "{sorted}" is invalid!') + + static_mappings = _get_raw_server_static_mappings(family=family, pool=pool, sorted=sorted) + if raw: + return static_mappings + else: + return _get_formatted_server_static_mappings(static_mappings, family=family) + +def _lease_valid(inet, address): + leases = kea_get_leases(inet) + for lease in leases: + if address == lease['ip-address']: + return True + return False + +@_verify +def clear_dhcp_server_lease(family: ArgFamily, address: str): + v = 'v6' if family == 'inet6' else '' + inet = '6' if family == 'inet6' else '4' + + if not _lease_valid(inet, address): + print(f'Lease not found on DHCP{v} server') + return None + + if not kea_delete_lease(inet, address): + print(f'Failed to clear lease for "{address}"') + return None + + print(f'Lease "{address}" has been cleared') + +def _get_raw_client_leases(family='inet', interface=None): + from time import mktime + from datetime import datetime + from vyos.defaults import directories + from vyos.utils.network import get_interface_vrf + + lease_dir = directories['isc_dhclient_dir'] + lease_files = [] + lease_data = [] + + if interface: + tmp = f'{lease_dir}/dhclient_{interface}.lease' + if os.path.exists(tmp): + lease_files.append(tmp) + else: + # All DHCP leases + lease_files = glob(f'{lease_dir}/dhclient_*.lease') + + for lease in lease_files: + tmp = {} + with open(lease, 'r') as f: + for line in f.readlines(): + line = line.rstrip() + if 'last_update' not in tmp: + # ISC dhcp client contains least_update timestamp in human readable + # format this makes less sense for an API and also the expiry + # timestamp is provided in UNIX time. Convert string (e.g. Sun Jul + # 30 18:13:44 CEST 2023) to UNIX time (1690733624) + tmp.update({'last_update' : int(mktime(datetime.strptime(line, time_string).timetuple()))}) + continue + + k, v = line.split('=') + tmp.update({k : v.replace("'", "")}) + + if 'interface' in tmp: + vrf = get_interface_vrf(tmp['interface']) + if vrf: tmp.update({'vrf' : vrf}) + + lease_data.append(tmp) + + return lease_data + +def _get_formatted_client_leases(lease_data, family): + from time import localtime + from time import strftime + + from vyos.utils.network import is_intf_addr_assigned + + data_entries = [] + for lease in lease_data: + if not lease.get('new_ip_address'): + continue + data_entries.append(["Interface", lease['interface']]) + if 'new_ip_address' in lease: + tmp = '[Active]' if is_intf_addr_assigned(lease['interface'], lease['new_ip_address']) else '[Inactive]' + data_entries.append(["IP address", lease['new_ip_address'], tmp]) + if 'new_subnet_mask' in lease: + data_entries.append(["Subnet Mask", lease['new_subnet_mask']]) + if 'new_domain_name' in lease: + data_entries.append(["Domain Name", lease['new_domain_name']]) + if 'new_routers' in lease: + data_entries.append(["Router", lease['new_routers']]) + if 'new_domain_name_servers' in lease: + data_entries.append(["Name Server", lease['new_domain_name_servers']]) + if 'new_dhcp_server_identifier' in lease: + data_entries.append(["DHCP Server", lease['new_dhcp_server_identifier']]) + if 'new_dhcp_lease_time' in lease: + data_entries.append(["DHCP Server", lease['new_dhcp_lease_time']]) + if 'vrf' in lease: + data_entries.append(["VRF", lease['vrf']]) + if 'last_update' in lease: + tmp = strftime(time_string, localtime(int(lease['last_update']))) + data_entries.append(["Last Update", tmp]) + if 'new_expiry' in lease: + tmp = strftime(time_string, localtime(int(lease['new_expiry']))) + data_entries.append(["Expiry", tmp]) + + # Add empty marker + data_entries.append(['']) + + output = tabulate(data_entries, tablefmt='plain') + + return output + +def show_client_leases(raw: bool, family: ArgFamily, interface: typing.Optional[str]): + lease_data = _get_raw_client_leases(family=family, interface=interface) + if raw: + return lease_data + else: + return _get_formatted_client_leases(lease_data, family=family) + +@_verify_client +def renew_client_lease(raw: bool, family: ArgFamily, interface: str): + if not raw: + v = 'v6' if family == 'inet6' else '' + print(f'Restarting DHCP{v} client on interface {interface}...') + if family == 'inet6': + call(f'systemctl restart dhcp6c@{interface}.service') + else: + call(f'systemctl restart dhclient@{interface}.service') + +@_verify_client +def release_client_lease(raw: bool, family: ArgFamily, interface: str): + if not raw: + v = 'v6' if family == 'inet6' else '' + print(f'Release DHCP{v} client on interface {interface}...') + if family == 'inet6': + call(f'systemctl stop dhcp6c@{interface}.service') + else: + call(f'systemctl stop dhclient@{interface}.service') + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/dns.py b/src/op_mode/dns.py new file mode 100644 index 0000000..16c462f --- /dev/null +++ b/src/op_mode/dns.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import os +import sys +import time +import typing +import vyos.opmode + +from tabulate import tabulate +from vyos.configquery import ConfigTreeQuery +from vyos.utils.process import cmd, rc_cmd +from vyos.template import is_ipv4, is_ipv6 + +_dynamic_cache_file = r'/run/ddclient/ddclient.cache' + +_dynamic_status_columns = { + 'host': 'Hostname', + 'ipv4': 'IPv4 address', + 'status-ipv4': 'IPv4 status', + 'ipv6': 'IPv6 address', + 'status-ipv6': 'IPv6 status', + 'mtime': 'Last update', +} + +_forwarding_statistics_columns = { + 'cache-entries': 'Cache entries', + 'max-cache-entries': 'Max cache entries', + 'cache-size': 'Cache size', +} + +def _forwarding_data_to_dict(data, sep="\t") -> dict: + """ + Return dictionary from plain text + separated by tab + + cache-entries 73 + cache-hits 0 + uptime 2148 + user-msec 172 + + { + 'cache-entries': '73', + 'cache-hits': '0', + 'uptime': '2148', + 'user-msec': '172' + } + """ + dictionary = {} + mylist = [line for line in data.split('\n')] + + for line in mylist: + if sep in line: + key, value = line.split(sep) + dictionary[key] = value + return dictionary + +def _get_dynamic_host_records_raw() -> dict: + + data = [] + + if os.path.isfile(_dynamic_cache_file): # A ddclient status file might not always exist + with open(_dynamic_cache_file, 'r') as f: + for line in f: + if line.startswith('#'): + continue + + props = {} + # ddclient cache rows have properties in 'key=value' format separated by comma + # we pick up the ones we are interested in + for kvraw in line.split(' ')[0].split(','): + k, v = kvraw.split('=') + if k in list(_dynamic_status_columns.keys()) + ['ip', 'status']: # ip and status are legacy keys + props[k] = v + + # Extract IPv4 and IPv6 address and status from legacy keys + # Dual-stack isn't supported in legacy format, 'ip' and 'status' are for one of IPv4 or IPv6 + if 'ip' in props: + if is_ipv4(props['ip']): + props['ipv4'] = props['ip'] + props['status-ipv4'] = props['status'] + elif is_ipv6(props['ip']): + props['ipv6'] = props['ip'] + props['status-ipv6'] = props['status'] + del props['ip'] + + # Convert mtime to human readable format + if 'mtime' in props: + props['mtime'] = time.strftime( + "%Y-%m-%d %H:%M:%S", time.localtime(int(props['mtime'], base=10))) + + data.append(props) + + return data + +def _get_dynamic_host_records_formatted(data): + data_entries = [] + for entry in data: + data_entries.append([entry.get(key) for key in _dynamic_status_columns.keys()]) + header = _dynamic_status_columns.values() + output = tabulate(data_entries, header, numalign='left') + return output + +def _get_forwarding_statistics_raw() -> dict: + command = cmd('rec_control get-all') + data = _forwarding_data_to_dict(command) + data['cache-size'] = "{0:.2f} kbytes".format( int( + cmd('rec_control get cache-bytes')) / 1024 ) + return data + +def _get_forwarding_statistics_formatted(data): + data_entries = [] + data_entries.append([data.get(key) for key in _forwarding_statistics_columns.keys()]) + header = _forwarding_statistics_columns.values() + output = tabulate(data_entries, header, numalign='left') + return output + +def _verify(target): + """Decorator checks if config for DNS related service exists""" + from functools import wraps + + if target not in ['dynamic', 'forwarding']: + raise ValueError('Invalid target') + + def _verify_target(func): + @wraps(func) + def _wrapper(*args, **kwargs): + config = ConfigTreeQuery() + if not config.exists(f'service dns {target}'): + _prefix = f'Dynamic DNS' if target == 'dynamic' else 'DNS Forwarding' + raise vyos.opmode.UnconfiguredSubsystem(f'{_prefix} is not configured') + return func(*args, **kwargs) + return _wrapper + return _verify_target + +@_verify('dynamic') +def show_dynamic_status(raw: bool): + host_data = _get_dynamic_host_records_raw() + if raw: + return host_data + else: + return _get_dynamic_host_records_formatted(host_data) + +@_verify('dynamic') +def reset_dynamic(): + """ + Reset Dynamic DNS cache + """ + if os.path.exists(_dynamic_cache_file): + os.remove(_dynamic_cache_file) + rc, output = rc_cmd('systemctl restart ddclient.service') + if rc != 0: + print(output) + return None + print(f'Dynamic DNS state reset!') + +@_verify('forwarding') +def show_forwarding_statistics(raw: bool): + dns_data = _get_forwarding_statistics_raw() + if raw: + return dns_data + else: + return _get_forwarding_statistics_formatted(dns_data) + +@_verify('forwarding') +def reset_forwarding(all: bool, domain: typing.Optional[str]): + """ + Reset DNS Forwarding cache + + :param all (bool): reset cache all domains + :param domain (str): reset cache for specified domain + """ + if all: + rc, output = rc_cmd('rec_control wipe-cache ".$"') + if rc != 0: + print(output) + return None + print('DNS Forwarding cache reset for all domains!') + return output + elif domain: + rc, output = rc_cmd(f'rec_control wipe-cache "{domain}$"') + if rc != 0: + print(output) + return None + print(f'DNS Forwarding cache reset for domain "{domain}"!') + return output + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/evpn.py b/src/op_mode/evpn.py new file mode 100644 index 0000000..cae4ab9 --- /dev/null +++ b/src/op_mode/evpn.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2016-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# This script is a helper to run VTYSH commands for "show evpn", allowing for the --raw flag to output JSON + +import sys +import typing +import json + +import vyos.opmode +from vyos.utils.process import cmd + +def show_evpn(raw: bool, command: typing.Optional[str]): + if raw: + command = f"{command} json" + evpnDict = {} + try: + evpnDict['evpn'] = json.loads(cmd(f"vtysh -c '{command}'")) + except: + raise vyos.opmode.DataUnavailable(f"\"{command.replace(' json', '')}\" is invalid or has no JSON option") + + return evpnDict + else: + return cmd(f"vtysh -c '{command}'") + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/execute_bandwidth_test.sh b/src/op_mode/execute_bandwidth_test.sh new file mode 100644 index 0000000..a6ad0b4 --- /dev/null +++ b/src/op_mode/execute_bandwidth_test.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +if ipaddrcheck --is-ipv6 $1; then + # Set address family to IPv6 when an IPv6 address was specified + OPT="-V" +elif [[ $(dig $1 AAAA +short | grep -v '\.$' | wc -l) -gt 0 ]]; then + # CNAME is also part of the dig answer thus we must remove any + # CNAME response and only shot the AAAA response(s), this is done + # by grep -v '\.$' + + # Set address family to IPv6 when FQDN has at least one AAAA record + OPT="-V" +else + # It's not IPv6, no option needed + OPT="" +fi + +/usr/bin/iperf $OPT -c $1 $2 + diff --git a/src/op_mode/execute_port-scan.py b/src/op_mode/execute_port-scan.py new file mode 100644 index 0000000..bf17d03 --- /dev/null +++ b/src/op_mode/execute_port-scan.py @@ -0,0 +1,155 @@ +#! /usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys + +from vyos.utils.process import call + + +options = { + 'port': { + 'cmd': '{command} -p {value}', + 'type': '<1-65535> <list>', + 'help': 'Scan specified ports.' + }, + 'tcp': { + 'cmd': '{command} -sT', + 'type': 'noarg', + 'help': 'Use TCP scan.' + }, + 'udp': { + 'cmd': '{command} -sU', + 'type': 'noarg', + 'help': 'Use UDP scan.' + }, + 'skip-ping': { + 'cmd': '{command} -Pn', + 'type': 'noarg', + 'help': 'Skip the Nmap discovery stage altogether.' + }, + 'ipv6': { + 'cmd': '{command} -6', + 'type': 'noarg', + 'help': 'Enable IPv6 scanning.' + }, +} + +nmap = 'sudo /usr/bin/nmap' + + +class List(list): + def first(self): + return self.pop(0) if self else '' + + def last(self): + return self.pop() if self else '' + + def prepend(self, value): + self.insert(0, value) + + +def completion_failure(option: str) -> None: + """ + Shows failure message after TAB when option is wrong + :param option: failure option + :type str: + """ + sys.stderr.write('\n\n Invalid option: {}\n\n'.format(option)) + sys.stdout.write('<nocomps>') + sys.exit(1) + + +def expansion_failure(option, completions): + reason = 'Ambiguous' if completions else 'Invalid' + sys.stderr.write( + '\n\n {} command: {} [{}]\n\n'.format(reason, ' '.join(sys.argv), + option)) + if completions: + sys.stderr.write(' Possible completions:\n ') + sys.stderr.write('\n '.join(completions)) + sys.stderr.write('\n') + sys.stdout.write('<nocomps>') + sys.exit(1) + + +def complete(prefix): + return [o for o in options if o.startswith(prefix)] + + +def convert(command, args): + while args: + shortname = args.first() + longnames = complete(shortname) + if len(longnames) != 1: + expansion_failure(shortname, longnames) + longname = longnames[0] + if options[longname]['type'] == 'noarg': + command = options[longname]['cmd'].format( + command=command, value='') + elif not args: + sys.exit(f'port-scan: missing argument for {longname} option') + else: + command = options[longname]['cmd'].format( + command=command, value=args.first()) + return command + + +if __name__ == '__main__': + args = List(sys.argv[1:]) + host = args.first() + + if host == '--get-options-nested': + args.first() # pop execute + args.first() # pop port-scan + args.first() # pop host + args.first() # pop <host> + usedoptionslist = [] + while args: + option = args.first() # pop option + matched = complete(option) # get option parameters + usedoptionslist.append(option) # list of used options + # Select options + if not args: + # remove from Possible completions used options + for o in usedoptionslist: + if o in matched: + matched.remove(o) + if not matched: + sys.stdout.write('<nocomps>') + else: + sys.stdout.write(' '.join(matched)) + sys.exit(0) + + if len(matched) > 1: + sys.stdout.write(' '.join(matched)) + sys.exit(0) + # If option doesn't have value + if matched: + if options[matched[0]]['type'] == 'noarg': + continue + else: + # Unexpected option + completion_failure(option) + + value = args.first() # pop option's value + if not args: + matched = complete(option) + helplines = options[matched[0]]['type'] + sys.stdout.write(helplines) + sys.exit(0) + + command = convert(nmap, args) + call(f'{command} -T4 {host}') diff --git a/src/op_mode/file.py b/src/op_mode/file.py new file mode 100644 index 0000000..bf13bed --- /dev/null +++ b/src/op_mode/file.py @@ -0,0 +1,383 @@ +#!/usr/bin/python3 + +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import contextlib +import datetime +import grp +import os +import pwd +import shutil +import sys +import tempfile + +from vyos.remote import download +from vyos.remote import upload +from vyos.utils.io import ask_yes_no +from vyos.utils.io import print_error +from vyos.utils.process import cmd +from vyos.utils.process import run + + +parser = argparse.ArgumentParser(description='view, copy or remove files and directories', + formatter_class=argparse.RawDescriptionHelpFormatter) +parser.epilog = """ +TYPE is one of 'remote', 'image' and 'local'. +A local path is <path> or ~/<path>. +A remote path is <scheme>://<urn>. +An image path is <image>:<path>. + +Clone operation is between images only. +Copy operation does not support directories from remote locations. +Delete operation does not support remote paths. +""" +operations = parser.add_mutually_exclusive_group(required=True) +operations.add_argument('--show', nargs=1, help='show the contents of file PATH of type TYPE', + metavar=('PATH')) +operations.add_argument('--copy', nargs=2, help='copy SRC to DEST', + metavar=('SRC', 'DEST')) +operations.add_argument('--delete', nargs=1, help='delete file PATH', + metavar=('PATH')) +operations.add_argument('--clone', help='clone config from running image to IMG', + metavar='IMG') +operations.add_argument('--clone-from', nargs=2, help='clone config from image SRC to image DEST', + metavar=('SRC', 'DEST')) + +## Helper procedures +def fix_terminal() -> None: + """ + Reset terminal after potential breakage caused by abrupt exits. + """ + run('stty sane') + +def get_types(arg: str) -> tuple[str, str]: + """ + Determine whether the argument shows a local, image or remote path. + """ + schemes = ['http', 'https', 'ftp', 'ftps', 'sftp', 'ssh', 'scp', 'tftp'] + s = arg.split("://", 1) + if len(s) != 2: + return 'local', arg + elif s[0] in schemes: + return 'remote', arg + else: + return 'image', arg + +def zealous_copy(source: str, destination: str) -> None: + # Even shutil.copy2() doesn't preserve ownership across copies. + # So we need to resort to this. + stats = os.stat(source) + shutil.copy2(source, destination) + os.chown(destination, stats.st_uid, stats.st_gid) + +def get_file_type(path: str) -> str: + return cmd(['file', '-sb', path]) + +def print_header(string: str) -> None: + print('#' * 10, string, '#' * 10) + +def octal_to_symbolic(octal: str) -> str: + perms = ['---', '--x', '-w-', '-wx', 'r--', 'r-x', 'rw-', 'rwx'] + result = "" + # We discard all but the last three digits because we're only + # interested in the permission bits. + for i in octal[-3:]: + result += perms[int(i)] + return result + +def get_user_and_group(stats: os.stat_result) -> tuple[str, str]: + try: + user = pwd.getpwuid(stats.st_uid).pw_name + except (KeyError, PermissionError): + user = str(stats.st_uid) + try: + group = grp.getgrgid(stats.st_gid).gr_name + except (KeyError, PermissionError): + group = str(stats.st_gid) + return user, group + +def print_file_info(path: str) -> None: + stats = os.stat(path) + username, groupname = get_user_and_group(stats) + mtime = datetime.datetime.fromtimestamp(stats.st_mtime).strftime("%F %X") + print_header('FILE INFO') + print(f'Path:\t\t{path}') + # File type is determined through `file(1)`. + print(f'Type:\t\t{get_file_type(path)}') + # Owner user and group + print(f'Owner:\t\t{username}:{groupname}') + # Permissions are converted from raw int to octal string to symbolic string. + print(f'Permissions:\t{octal_to_symbolic(oct(stats.st_mode))}') + # Last date of modification + print(f'Modified:\t{mtime}') + +def print_file_data(path: str) -> None: + print_header('FILE DATA') + file_type = get_file_type(path) + # Human-readable files are streamed line-by-line. + if 'text' in file_type: + with open(path, 'r') as f: + for line in f: + print(line, end='') + # tcpdump files go to TShark. + elif 'pcap' in file_type or os.path.splitext(path)[1] == '.pcap': + print(cmd(['sudo', 'tshark', '-r', path])) + # All other binaries get hexdumped. + else: + print(cmd(['hexdump', '-C', path])) + +def parse_image_path(image_path: str) -> str: + """ + my-image:/foo/bar -> /lib/live/mount/persistence/boot/my-image/rw/foo/bar + """ + image_name, path = image_path.split('://', 1) + if image_name == 'running': + image_root = '/' + elif image_name == 'disk-install': + image_root = '/lib/live/mount/persistence/' + else: + image_root = os.path.join('/lib/live/mount/persistence/boot', image_name, 'rw') + if not os.path.isdir(image_root): + print_error(f'Image {image_name} not found.') + sys.exit(1) + return os.path.join(image_root, path) + + +## Show procedures +def show_locally(path: str) -> None: + """ + Display the contents of a local file or directory. + """ + location = os.path.realpath(os.path.expanduser(path)) + # Temporarily redirect stdout to a throwaway file for `less(1)` to read. + # The output could be potentially too hefty for an in-memory StringIO. + temp = tempfile.NamedTemporaryFile('w', delete=False) + try: + with contextlib.redirect_stdout(temp): + # Just a directory. Call `ls(1)` and bail. + if os.path.isdir(location): + print_header('DIRECTORY LISTING') + print('Path:\t', location) + print(cmd(['ls', '-hlFGL', '--group-directories-first', location])) + elif os.path.isfile(location): + print_file_info(location) + print() + print_file_data(location) + else: + print_error(f'File or directory {path} not found.') + sys.exit(1) + sys.stdout.flush() + # Call `less(1)` and wait for it to terminate before going forward. + cmd(['/usr/bin/less', '-X', temp.name], stdout=sys.stdout) + # The stream to the temporary file could break for any reason. + # It's much less fragile than if we streamed directly to the process stdin. + # But anything could still happen and we don't want to scare the user. + except (BrokenPipeError, EOFError, KeyboardInterrupt, OSError): + fix_terminal() + sys.exit(1) + finally: + os.remove(temp.name) + +def show(type: str, path: str) -> None: + if type == 'remote': + temp = tempfile.NamedTemporaryFile(delete=False) + download(temp.name, path) + show_locally(temp.name) + os.remove(temp.name) + elif type == 'image': + show_locally(parse_image_path(path)) + elif type == 'local': + show_locally(path) + else: + print_error(f'Unknown target for showing: {type}') + print_error('Valid types are "remote", "image" and "local".') + sys.exit(1) + + +## Copying procedures +def copy(source_type: str, source_path: str, + destination_type: str, destination_path: str) -> None: + """ + Copy a file or directory locally, remotely or to and from an image. + Directory uploads and downloads not supported. + """ + source = '' + try: + # Download to a temporary file and use that as the source. + if source_type == 'remote': + source = tempfile.NamedTemporaryFile(delete=False).name + download(source, source_path) + # Prepend the image root to the path. + elif source_type == 'image': + source = parse_image_path(source_path) + elif source_type == 'local': + source = source_path + else: + print_error(f'Unknown source type: {source_type}') + print_error(f'Valid source types are "remote", "image" and "local".') + sys.exit(1) + + # Directly upload the file. + if destination_type == 'remote': + if os.path.isdir(source): + print_error(f'Cannot upload {source}. Directory uploads not supported.') + sys.exit(1) + upload(source, destination_path) + # No need to duplicate local copy operations for image copying. + elif destination_type == 'image': + copy('local', source, 'local', parse_image_path(destination_path)) + # Try to preserve metadata when copying. + elif destination_type == 'local': + if os.path.isdir(destination_path): + destination_path = os.path.join(destination_path, os.path.basename(source)) + if os.path.isdir(source): + shutil.copytree(source, destination_path, copy_function=zealous_copy) + else: + zealous_copy(source, destination_path) + else: + print_error(f'Unknown destination type: {source_type}') + print_error(f'Valid destination types are "remote", "image" and "local".') + sys.exit(1) + except OSError: + import traceback + # We can't check for every single user error (eg copying a directory to a file) + # so we just let a curtailed stack trace provide a descriptive error. + print_error(f'Failed to copy {source_path} to {destination_path}.') + traceback.print_exception(*sys.exc_info()[:2], None) + sys.exit(1) + else: + # To prevent a duplicate message. + if destination_type != 'image': + print('Copy successful.') + finally: + # Clean up temporary file. + if source_type == 'remote': + os.remove(source) + + +## Deletion procedures +def delete_locally(path: str) -> None: + """ + Remove a local file or directory. + """ + try: + if os.path.isdir(path): + if (ask_yes_no(f'Do you want to remove {path} with all its contents?')): + shutil.rmtree(path) + print(f'Directory {path} removed.') + else: + print('Operation aborted.') + elif os.path.isfile(path): + if (ask_yes_no(f'Do you want to remove {path}?')): + os.remove(path) + print(f'File {path} removed.') + else: + print('Operation aborted.') + else: + raise OSError(f'File or directory {path} not found.') + except OSError: + import traceback + print_error(f'Failed to delete {path}.') + traceback.print_exception(*sys.exc_info()[:2], None) + sys.exit(1) + +def delete(type: str, path: str) -> None: + if type == 'local': + delete_locally(path) + elif type == 'image': + delete_locally(parse_image_path(path)) + else: + print_error(f'Unknown target for deletion: {type}') + print_error('Valid types are "image" and "local".') + sys.exit(1) + + +## Cloning procedures +def clone(source: str, destination: str) -> None: + if os.geteuid(): + print_error('Only the superuser can run this command.') + sys.exit(1) + if destination == 'running' or destination == 'disk-install': + print_error(f'Cannot clone config to {destination}.') + sys.exit(1) + # If `source` is None, then we're going to copy from the running image. + if source is None or source == 'running': + source_path = '/config' + # For the warning message only. + source = 'the current' + else: + source_path = parse_image_path(source + ':/config') + destination_path = parse_image_path(destination + ':/config') + backup_path = destination_path + '.preclone' + + if not os.path.isdir(source_path): + print_error(f'Source image {source} does not exist.') + sys.exit(1) + if not os.path.isdir(destination_path): + print_error(f'Destination image {destination} does not exist.') + sys.exit(1) + print(f'WARNING: This operation will erase /config data in image {destination}.') + print(f'/config data in {source} image will be copied over in its place.') + print(f'The existing /config data in {destination} image will be backed up to /config.preclone.') + + if ask_yes_no('Are you sure you want to continue?'): + try: + if os.path.isdir(backup_path): + print('Removing previous backup...') + shutil.rmtree(backup_path) + print('Making new backup...') + shutil.move(destination_path, backup_path) + except: + print('Something went wrong during the backup process!') + print('Cowardly refusing to proceed with cloning.') + raise + # Copy new config from image. + try: + shutil.copytree(source_path, destination_path, copy_function=zealous_copy) + except: + print('Cloning failed! Reverting to backup!') + # Delete leftover files from the botched cloning. + shutil.rmtree(destination_path, ignore_errors=True) + # Restore backup before bailing out. + shutil.copytree(backup_path, destination_path, copy_function=zealous_copy) + raise + else: + print(f'Successfully cloned config from {source} to {destination}.') + finally: + shutil.rmtree(backup_path) + else: + print('Operation aborted.') + +if __name__ == '__main__': + args = parser.parse_args() + try: + if args.show: + show(*get_types(args.show[0])) + elif args.copy: + copy(*get_types(args.copy[0]), + *get_types(args.copy[1])) + elif args.delete: + delete(*get_types(args.delete[0])) + elif args.clone_from: + clone(*args.clone_from) + elif args.clone: + # Pass None as source image to copy from local image. + clone(None, args.clone) + except KeyboardInterrupt: + print_error('Operation cancelled by user.') + sys.exit(1) + sys.exit(0) diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py new file mode 100644 index 0000000..c197ca4 --- /dev/null +++ b/src/op_mode/firewall.py @@ -0,0 +1,728 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import ipaddress +import json +import re +import tabulate +import textwrap + +from vyos.config import Config +from vyos.utils.process import cmd +from vyos.utils.dict import dict_search_args + +def get_config_node(conf, node=None, family=None, hook=None, priority=None): + if node == 'nat': + if family == 'ipv6': + config_path = ['nat66'] + else: + config_path = ['nat'] + + elif node == 'policy': + config_path = ['policy'] + else: + config_path = ['firewall'] + if family: + config_path += [family] + if hook: + config_path += [hook] + if priority: + config_path += [priority] + + node_config = conf.get_config_dict(config_path, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + return node_config + +def get_nftables_details(family, hook, priority): + if family == 'ipv6': + suffix = 'ip6' + name_prefix = 'NAME6_' + aux='IPV6_' + elif family == 'ipv4': + suffix = 'ip' + name_prefix = 'NAME_' + aux='' + else: + suffix = 'bridge' + name_prefix = 'NAME_' + aux='' + + if hook == 'name' or hook == 'ipv6-name': + command = f'nft list chain {suffix} vyos_filter {name_prefix}{priority}' + else: + up_hook = hook.upper() + command = f'nft list chain {suffix} vyos_filter VYOS_{aux}{up_hook}_{priority}' + + try: + results = cmd(command) + except: + return {} + + out = {} + for line in results.split('\n'): + comment_search = re.search(rf'{priority}[\- ](\d+|default-action)', line) + if not comment_search: + continue + + rule = {} + rule_id = comment_search[1] + counter_search = re.search(r'counter packets (\d+) bytes (\d+)', line) + if counter_search: + rule['packets'] = counter_search[1] + rule['bytes'] = counter_search[2] + + rule['conditions'] = re.sub(r'(\b(counter packets \d+ bytes \d+|drop|reject|return|log)\b|comment "[\w\-]+")', '', line).strip() + out[rule_id] = rule + return out + +def get_nftables_state_details(family): + if family == 'ipv6': + suffix = 'ip6' + name_suffix = 'POLICY6' + elif family == 'ipv4': + suffix = 'ip' + name_suffix = 'POLICY' + else: + # no state policy for bridge + return {} + + command = f'nft list chain {suffix} vyos_filter VYOS_STATE_{name_suffix}' + try: + results = cmd(command) + except: + return {} + + out = {} + for line in results.split('\n'): + rule = {} + for state in ['established', 'related', 'invalid']: + if state in line: + counter_search = re.search(r'counter packets (\d+) bytes (\d+)', line) + if counter_search: + rule['packets'] = counter_search[1] + rule['bytes'] = counter_search[2] + rule['conditions'] = re.sub(r'(\b(counter packets \d+ bytes \d+|drop|reject|return|log)\b|comment "[\w\-]+")', '', line).strip() + out[state] = rule + return out + +def get_nftables_group_members(family, table, name): + prefix = 'ip6' if family == 'ipv6' else 'ip' + out = [] + + try: + results_str = cmd(f'nft -j list set {prefix} {table} {name}') + results = json.loads(results_str) + except: + return out + + if 'nftables' not in results: + return out + + for obj in results['nftables']: + if 'set' not in obj: + continue + + set_obj = obj['set'] + + if 'elem' in set_obj: + for elem in set_obj['elem']: + if isinstance(elem, str): + out.append(elem) + elif isinstance(elem, dict) and 'elem' in elem: + out.append(elem['elem']) + + return out + +def output_firewall_vertical(rules, headers, adjust=True): + for rule in rules: + adjusted_rule = rule + [""] * (len(headers) - len(rule)) if adjust else rule # account for different header length, like default-action + transformed_rule = [[header, textwrap.fill(adjusted_rule[i].replace('\n', ' '), 65)] for i, header in enumerate(headers) if i < len(adjusted_rule)] # create key-pair list from headers and rules lists; wrap at 100 char + + print(tabulate.tabulate(transformed_rule, tablefmt="presto")) + print() + +def output_firewall_name(family, hook, priority, firewall_conf, single_rule_id=None): + print(f'\n---------------------------------\n{family} Firewall "{hook} {priority}"\n') + + details = get_nftables_details(family, hook, priority) + rows = [] + + if 'rule' in firewall_conf: + for rule_id, rule_conf in firewall_conf['rule'].items(): + if single_rule_id and rule_id != single_rule_id: + continue + + if 'disable' in rule_conf: + continue + + row = [rule_id, textwrap.fill(rule_conf.get('description') or '', 50), rule_conf['action'], rule_conf['protocol'] if 'protocol' in rule_conf else 'all'] + if rule_id in details: + rule_details = details[rule_id] + row.append(rule_details.get('packets', 0)) + row.append(rule_details.get('bytes', 0)) + row.append(rule_details['conditions']) + rows.append(row) + + if hook in ['input', 'forward', 'output']: + def_action = firewall_conf['default_action'] if 'default_action' in firewall_conf else 'accept' + else: + def_action = firewall_conf['default_action'] if 'default_action' in firewall_conf else 'drop' + row = ['default', '', def_action, 'all'] + rule_details = details['default-action'] + row.append(rule_details.get('packets', 0)) + row.append(rule_details.get('bytes', 0)) + + rows.append(row) + + if rows: + if args.rule: + rows.pop() + + if args.detail: + header = ['Rule', 'Description', 'Action', 'Protocol', 'Packets', 'Bytes', 'Conditions'] + output_firewall_vertical(rows, header) + else: + header = ['Rule', 'Action', 'Protocol', 'Packets', 'Bytes', 'Conditions'] + for i in rows: + rows[rows.index(i)].pop(1) + print(tabulate.tabulate(rows, header) + '\n') + +def output_firewall_state_policy(family): + if family == 'bridge': + return {} + print(f'\n---------------------------------\n{family} State Policy\n') + + details = get_nftables_state_details(family) + rows = [] + + for state, state_conf in details.items(): + row = [state, state_conf['conditions']] + row.append(state_conf.get('packets', 0)) + row.append(state_conf.get('bytes', 0)) + row.append(state_conf.get('conditions')) + rows.append(row) + + if rows: + if args.rule: + rows.pop() + + if args.detail: + header = ['State', 'Conditions', 'Packets', 'Bytes'] + output_firewall_vertical(rows, header) + else: + header = ['State', 'Packets', 'Bytes', 'Conditions'] + for i in rows: + rows[rows.index(i)].pop(1) + print(tabulate.tabulate(rows, header) + '\n') + +def output_firewall_name_statistics(family, hook, prior, prior_conf, single_rule_id=None): + print(f'\n---------------------------------\n{family} Firewall "{hook} {prior}"\n') + + details = get_nftables_details(family, hook, prior) + rows = [] + + if 'rule' in prior_conf: + for rule_id, rule_conf in prior_conf['rule'].items(): + if single_rule_id and rule_id != single_rule_id: + continue + + if 'disable' in rule_conf: + continue + + # Get source + source_addr = dict_search_args(rule_conf, 'source', 'address') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'group', 'address_group') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'group', 'network_group') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'group', 'domain_group') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'fqdn') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'geoip', 'country_code') + if source_addr: + source_addr = str(source_addr)[1:-1].replace('\'','') + if 'inverse_match' in dict_search_args(rule_conf, 'source', 'geoip'): + source_addr = 'NOT ' + str(source_addr) + if not source_addr: + source_addr = 'any' + + # Get destination + dest_addr = dict_search_args(rule_conf, 'destination', 'address') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'group', 'address_group') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'group', 'network_group') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'group', 'domain_group') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'fqdn') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'geoip', 'country_code') + if dest_addr: + dest_addr = str(dest_addr)[1:-1].replace('\'','') + if 'inverse_match' in dict_search_args(rule_conf, 'destination', 'geoip'): + dest_addr = 'NOT ' + str(dest_addr) + if not dest_addr: + dest_addr = 'any' + + # Get inbound interface + iiface = dict_search_args(rule_conf, 'inbound_interface', 'name') + if not iiface: + iiface = dict_search_args(rule_conf, 'inbound_interface', 'group') + if not iiface: + iiface = 'any' + + # Get outbound interface + oiface = dict_search_args(rule_conf, 'outbound_interface', 'name') + if not oiface: + oiface = dict_search_args(rule_conf, 'outbound_interface', 'group') + if not oiface: + oiface = 'any' + + row = [rule_id, textwrap.fill(rule_conf.get('description') or '', 50)] + if rule_id in details: + rule_details = details[rule_id] + row.append(rule_details.get('packets', 0)) + row.append(rule_details.get('bytes', 0)) + else: + row.append('0') + row.append('0') + row.append(rule_conf['action']) + row.append(source_addr) + row.append(dest_addr) + row.append(iiface) + row.append(oiface) + rows.append(row) + + + if hook in ['input', 'forward', 'output']: + row = ['default', ''] + rule_details = details['default-action'] + row.append(rule_details.get('packets', 0)) + row.append(rule_details.get('bytes', 0)) + if 'default_action' in prior_conf: + row.append(prior_conf['default_action']) + else: + row.append('accept') + row.append('any') + row.append('any') + row.append('any') + row.append('any') + rows.append(row) + + elif 'default_action' in prior_conf and not single_rule_id: + row = ['default', ''] + if 'default-action' in details: + rule_details = details['default-action'] + row.append(rule_details.get('packets', 0)) + row.append(rule_details.get('bytes', 0)) + else: + row.append('0') + row.append('0') + row.append(prior_conf['default_action']) + row.append('any') # Source + row.append('any') # Dest + row.append('any') # inbound-interface + row.append('any') # outbound-interface + rows.append(row) + + if rows: + if args.detail: + header = ['Rule', 'Description', 'Packets', 'Bytes', 'Action', 'Source', 'Destination', 'Inbound-Interface', 'Outbound-interface'] + output_firewall_vertical(rows, header) + else: + header = ['Rule', 'Packets', 'Bytes', 'Action', 'Source', 'Destination', 'Inbound-Interface', 'Outbound-interface'] + for i in rows: + rows[rows.index(i)].pop(1) + print(tabulate.tabulate(rows, header) + '\n') + +def show_firewall(): + print('Rulesets Information') + + conf = Config() + firewall = get_config_node(conf) + + if not firewall: + return + + for family in ['ipv4', 'ipv6', 'bridge']: + if 'global_options' in firewall: + if 'state_policy' in firewall['global_options']: + output_firewall_state_policy(family) + + if family in firewall: + for hook, hook_conf in firewall[family].items(): + for prior, prior_conf in firewall[family][hook].items(): + output_firewall_name(family, hook, prior, prior_conf) + +def show_firewall_family(family): + print(f'Rulesets {family} Information') + + conf = Config() + firewall = get_config_node(conf) + + if not firewall: + return + + if 'global_options' in firewall: + if 'state_policy' in firewall['global_options']: + output_firewall_state_policy(family) + + if family in firewall: + for hook, hook_conf in firewall[family].items(): + for prior, prior_conf in firewall[family][hook].items(): + output_firewall_name(family, hook, prior, prior_conf) + +def show_firewall_name(family, hook, priority): + print('Ruleset Information') + + conf = Config() + firewall = get_config_node(conf, 'firewall', family, hook, priority) + if firewall: + output_firewall_name(family, hook, priority, firewall) + +def show_firewall_rule(family, hook, priority, rule_id): + print('Rule Information') + + conf = Config() + firewall = get_config_node(conf, 'firewall', family, hook, priority) + if firewall: + output_firewall_name(family, hook, priority, firewall, rule_id) + +def show_firewall_group(name=None): + conf = Config() + firewall = get_config_node(conf, node='firewall') + + if 'group' not in firewall: + return + + nat = get_config_node(conf, node='nat') + policy = get_config_node(conf, node='policy') + + def find_references(group_type, group_name): + out = [] + family = [] + if group_type in ['address_group', 'network_group']: + family = ['ipv4'] + elif group_type == 'ipv6_address_group': + family = ['ipv6'] + group_type = 'address_group' + elif group_type == 'ipv6_network_group': + family = ['ipv6'] + group_type = 'network_group' + else: + family = ['ipv4', 'ipv6', 'bridge'] + + for item in family: + # Look references in firewall + for name_type in ['name', 'ipv6_name', 'forward', 'input', 'output']: + if item in firewall: + if name_type not in firewall[item]: + continue + for priority, priority_conf in firewall[item][name_type].items(): + if priority not in firewall[item][name_type]: + continue + if 'rule' not in priority_conf: + continue + for rule_id, rule_conf in priority_conf['rule'].items(): + source_group = dict_search_args(rule_conf, 'source', 'group', group_type) + dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type) + in_interface = dict_search_args(rule_conf, 'inbound_interface', 'group') + out_interface = dict_search_args(rule_conf, 'outbound_interface', 'group') + dyn_group_source = dict_search_args(rule_conf, 'add_address_to_group', 'source_address', group_type) + dyn_group_dst = dict_search_args(rule_conf, 'add_address_to_group', 'destination_address', group_type) + if source_group: + if source_group[0] == "!": + source_group = source_group[1:] + if group_name == source_group: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') + if dest_group: + if dest_group[0] == "!": + dest_group = dest_group[1:] + if group_name == dest_group: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') + if in_interface: + if in_interface[0] == "!": + in_interface = in_interface[1:] + if group_name == in_interface: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') + if out_interface: + if out_interface[0] == "!": + out_interface = out_interface[1:] + if group_name == out_interface: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') + + if dyn_group_source: + if group_name == dyn_group_source: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') + if dyn_group_dst: + if group_name == dyn_group_dst: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') + + + # Look references in route | route6 + for name_type in ['route', 'route6']: + if name_type not in policy: + continue + if name_type == 'route' and item == 'ipv6': + continue + elif name_type == 'route6' and item == 'ipv4': + continue + else: + for policy_name, policy_conf in policy[name_type].items(): + if 'rule' not in policy_conf: + continue + for rule_id, rule_conf in policy_conf['rule'].items(): + source_group = dict_search_args(rule_conf, 'source', 'group', group_type) + dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type) + in_interface = dict_search_args(rule_conf, 'inbound_interface', 'group') + out_interface = dict_search_args(rule_conf, 'outbound_interface', 'group') + if source_group: + if source_group[0] == "!": + source_group = source_group[1:] + if group_name == source_group: + out.append(f'{name_type}-{policy_name}-{rule_id}') + if dest_group: + if dest_group[0] == "!": + dest_group = dest_group[1:] + if group_name == dest_group: + out.append(f'{name_type}-{policy_name}-{rule_id}') + if in_interface: + if in_interface[0] == "!": + in_interface = in_interface[1:] + if group_name == in_interface: + out.append(f'{name_type}-{policy_name}-{rule_id}') + if out_interface: + if out_interface[0] == "!": + out_interface = out_interface[1:] + if group_name == out_interface: + out.append(f'{name_type}-{policy_name}-{rule_id}') + + ## Look references in nat table + for direction in ['source', 'destination']: + if direction in nat: + if 'rule' not in nat[direction]: + continue + for rule_id, rule_conf in nat[direction]['rule'].items(): + source_group = dict_search_args(rule_conf, 'source', 'group', group_type) + dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type) + in_interface = dict_search_args(rule_conf, 'inbound_interface', 'group') + out_interface = dict_search_args(rule_conf, 'outbound_interface', 'group') + if source_group: + if source_group[0] == "!": + source_group = source_group[1:] + if group_name == source_group: + out.append(f'nat-{direction}-{rule_id}') + if dest_group: + if dest_group[0] == "!": + dest_group = dest_group[1:] + if group_name == dest_group: + out.append(f'nat-{direction}-{rule_id}') + if in_interface: + if in_interface[0] == "!": + in_interface = in_interface[1:] + if group_name == in_interface: + out.append(f'nat-{direction}-{rule_id}') + if out_interface: + if out_interface[0] == "!": + out_interface = out_interface[1:] + if group_name == out_interface: + out.append(f'nat-{direction}-{rule_id}') + + return out + + rows = [] + header_tail = [] + + for group_type, group_type_conf in firewall['group'].items(): + ## + if group_type != 'dynamic_group': + + for group_name, group_conf in group_type_conf.items(): + if name and name != group_name: + continue + + references = find_references(group_type, group_name) + row = [group_name, textwrap.fill(group_conf.get('description') or '', 50), group_type, '\n'.join(references) or 'N/D'] + if 'address' in group_conf: + row.append("\n".join(sorted(group_conf['address']))) + elif 'network' in group_conf: + row.append("\n".join(sorted(group_conf['network'], key=ipaddress.ip_network))) + elif 'mac_address' in group_conf: + row.append("\n".join(sorted(group_conf['mac_address']))) + elif 'port' in group_conf: + row.append("\n".join(sorted(group_conf['port']))) + elif 'interface' in group_conf: + row.append("\n".join(sorted(group_conf['interface']))) + else: + row.append('N/D') + rows.append(row) + + else: + if not args.detail: + header_tail = ['Timeout', 'Expires'] + + for dynamic_type in ['address_group', 'ipv6_address_group']: + family = 'ipv4' if dynamic_type == 'address_group' else 'ipv6' + prefix = 'DA_' if dynamic_type == 'address_group' else 'DA6_' + if dynamic_type in firewall['group']['dynamic_group']: + for dynamic_name, dynamic_conf in firewall['group']['dynamic_group'][dynamic_type].items(): + references = find_references(dynamic_type, dynamic_name) + row = [dynamic_name, textwrap.fill(dynamic_conf.get('description') or '', 50), dynamic_type + '(dynamic)', '\n'.join(references) or 'N/D'] + + members = get_nftables_group_members(family, 'vyos_filter', f'{prefix}{dynamic_name}') + + if not members: + if args.detail: + row.append('N/D') + else: + row += ["N/D"] * 3 + rows.append(row) + continue + + for idx, member in enumerate(members): + if isinstance(member, str): + # Only member, and no timeout: + val = member + timeout = "N/D" + expires = "N/D" + else: + val = member.get('val', 'N/D') + timeout = str(member.get('timeout', 'N/D')) + expires = str(member.get('expires', 'N/D')) + + if args.detail: + row.append(f'{val} (timeout: {timeout}, expires: {expires})') + continue + + if idx > 0: + row = [""] * 4 + + row += [val, timeout, expires] + rows.append(row) + + if args.detail: + header_tail += [""] * (len(members) - 1) + rows.append(row) + + if rows: + print('Firewall Groups\n') + if args.detail: + header = ['Name', 'Description', 'Type', 'References', 'Members'] + header_tail + output_firewall_vertical(rows, header, adjust=False) + else: + header = ['Name', 'Type', 'References', 'Members'] + header_tail + for i in rows: + rows[rows.index(i)].pop(1) + print(tabulate.tabulate(rows, header)) + +def show_summary(): + print('Ruleset Summary') + + conf = Config() + firewall = get_config_node(conf) + + if not firewall: + return + + header = ['Ruleset Hook', 'Ruleset Priority', 'Description', 'References'] + v4_out = [] + v6_out = [] + br_out = [] + + if 'ipv4' in firewall: + for hook, hook_conf in firewall['ipv4'].items(): + for prior, prior_conf in firewall['ipv4'][hook].items(): + description = prior_conf.get('description', '') + v4_out.append([hook, prior, description]) + + if 'ipv6' in firewall: + for hook, hook_conf in firewall['ipv6'].items(): + for prior, prior_conf in firewall['ipv6'][hook].items(): + description = prior_conf.get('description', '') + v6_out.append([hook, prior, description]) + + if 'bridge' in firewall: + for hook, hook_conf in firewall['bridge'].items(): + for prior, prior_conf in firewall['bridge'][hook].items(): + description = prior_conf.get('description', '') + br_out.append([hook, prior, description]) + + if v6_out: + print('\nIPv6 Ruleset:\n') + print(tabulate.tabulate(v6_out, header) + '\n') + + if v4_out: + print('\nIPv4 Ruleset:\n') + print(tabulate.tabulate(v4_out, header) + '\n') + + if br_out: + print('\nBridge Ruleset:\n') + print(tabulate.tabulate(br_out, header) + '\n') + + show_firewall_group() + +def show_statistics(): + print('Rulesets Statistics') + + conf = Config() + firewall = get_config_node(conf) + + if not firewall: + return + + for family in ['ipv4', 'ipv6', 'bridge']: + if 'global_options' in firewall: + if 'state_policy' in firewall['global_options']: + output_firewall_state_policy(family) + + if family in firewall: + for hook, hook_conf in firewall[family].items(): + for prior, prior_conf in firewall[family][hook].items(): + output_firewall_name_statistics(family, hook,prior, prior_conf) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--action', help='Action', required=False) + parser.add_argument('--name', help='Firewall name', required=False, action='store', nargs='?', default='') + parser.add_argument('--family', help='IP family', required=False, action='store', nargs='?', default='') + parser.add_argument('--hook', help='Firewall hook', required=False, action='store', nargs='?', default='') + parser.add_argument('--priority', help='Firewall priority', required=False, action='store', nargs='?', default='') + parser.add_argument('--rule', help='Firewall Rule ID', required=False) + parser.add_argument('--ipv6', help='IPv6 toggle', action='store_true') + parser.add_argument('--detail', help='Firewall view select', required=False) + + args = parser.parse_args() + + if args.action == 'show': + if not args.rule: + show_firewall_name(args.family, args.hook, args.priority) + else: + show_firewall_rule(args.family, args.hook, args.priority, args.rule) + elif args.action == 'show_all': + show_firewall() + elif args.action == 'show_family': + show_firewall_family(args.family) + elif args.action == 'show_group': + show_firewall_group(args.name) + elif args.action == 'show_statistics': + show_statistics() + elif args.action == 'show_summary': + show_summary() diff --git a/src/op_mode/flow_accounting_op.py b/src/op_mode/flow_accounting_op.py new file mode 100644 index 0000000..497ccaf --- /dev/null +++ b/src/op_mode/flow_accounting_op.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import argparse +import re +import ipaddress +import os.path + +from tabulate import tabulate +from json import loads +from vyos.utils.commit import commit_in_progress +from vyos.utils.process import cmd +from vyos.utils.process import run +from vyos.logger import syslog + +# some default values +uacctd_pidfile = '/var/run/uacctd.pid' +uacctd_pipefile = '/tmp/uacctd.pipe' + +def parse_port(port): + try: + port_num = int(port) + if (port_num >= 0) and (port_num <= 65535): + return port_num + else: + raise ValueError("out of the 0-65535 range".format(port)) + except ValueError as e: + raise ValueError("Incorrect port number \'{0}\': {1}".format(port, e)) + +def parse_ports(arg): + if re.match(r'^\d+$', arg): + # Single port + port = parse_port(arg) + return {"type": "single", "value": port} + elif re.match(r'^\d+\-\d+$', arg): + # Port range + ports = arg.split("-") + ports = list(map(parse_port, ports)) + if ports[0] > ports[1]: + raise ValueError("Malformed port range \'{0}\': lower end is greater than the higher".format(arg)) + else: + return {"type": "range", "value": (ports[0], ports[1])} + elif re.match(r'^\d+,.*\d$', arg): + # Port list + ports = re.split(r',+', arg) # This allows duplicate commad like '1,,2,3,4' + ports = list(map(parse_port, ports)) + return {"type": "list", "value": ports} + else: + raise ValueError("Malformed port spec \'{0}\'".format(arg)) + +# check if host argument have correct format +def check_host(host): + # define regex for checking + if not ipaddress.ip_address(host): + raise ValueError("Invalid host \'{}\', must be a valid IP or IPv6 address".format(host)) + +# check if flow-accounting running +def _uacctd_running(): + command = 'systemctl status uacctd.service > /dev/null' + return run(command) == 0 + + +# get list of interfaces +def _get_ifaces_dict(): + # run command to get ifaces list + out = cmd('/bin/ip link show') + + # read output + ifaces_out = out.splitlines() + + # make a dictionary with interfaces and indexes + ifaces_dict = {} + regex_filter = re.compile(r'^(?P<iface_index>\d+):\ (?P<iface_name>[\w\d\.]+)[:@].*$') + for iface_line in ifaces_out: + if regex_filter.search(iface_line): + ifaces_dict[int(regex_filter.search(iface_line).group('iface_index'))] = regex_filter.search(iface_line).group('iface_name') + + # return dictioanry + return ifaces_dict + + +# get list of flows +def _get_flows_list(): + # run command to get flows list + out = cmd(f'/usr/bin/pmacct -s -O json -T flows -p {uacctd_pipefile}', + message='Failed to get flows list') + + # read output + flows_out = out.splitlines() + + # make a list with flows + flows_list = [] + for flow_line in flows_out: + try: + flows_list.append(loads(flow_line)) + except Exception as err: + syslog.error('Unable to read flow info: {}'.format(err)) + + # return list of flows + return flows_list + + +# filter and format flows +def _flows_filter(flows, ifaces): + # predefine filtered flows list + flows_filtered = [] + + # add interface names to flows + for flow in flows: + if flow['iface_in'] in ifaces: + flow['iface_in_name'] = ifaces[flow['iface_in']] + else: + flow['iface_in_name'] = 'unknown' + + # iterate through flows list + for flow in flows: + # filter by interface + if cmd_args.interface: + if flow['iface_in_name'] != cmd_args.interface: + continue + # filter by host + if cmd_args.host: + if flow['ip_src'] != cmd_args.host and flow['ip_dst'] != cmd_args.host: + continue + # filter by ports + if cmd_args.ports: + if cmd_args.ports['type'] == 'single': + if flow['port_src'] != cmd_args.ports['value'] and flow['port_dst'] != cmd_args.ports['value']: + continue + else: + if flow['port_src'] not in cmd_args.ports['value'] and flow['port_dst'] not in cmd_args.ports['value']: + continue + # add filtered flows to new list + flows_filtered.append(flow) + + # stop adding if we already reached top count + if cmd_args.top: + if len(flows_filtered) == cmd_args.top: + break + + # return filtered flows + return flows_filtered + + +# print flow table +def _flows_table_print(flows): + # define headers and body + table_headers = ['IN_IFACE', 'SRC_MAC', 'DST_MAC', 'SRC_IP', 'DST_IP', 'SRC_PORT', 'DST_PORT', 'PROTOCOL', 'TOS', 'PACKETS', 'FLOWS', 'BYTES'] + table_body = [] + # convert flows to list + for flow in flows: + table_line = [ + flow.get('iface_in_name'), + flow.get('mac_src'), + flow.get('mac_dst'), + flow.get('ip_src'), + flow.get('ip_dst'), + flow.get('port_src'), + flow.get('port_dst'), + flow.get('ip_proto'), + flow.get('tos'), + flow.get('packets'), + flow.get('flows'), + flow.get('bytes') + ] + table_body.append(table_line) + # configure and fill table + table = tabulate(table_body, table_headers, tablefmt="simple") + + # print formatted table + try: + print(table) + except IOError: + sys.exit(0) + except KeyboardInterrupt: + sys.exit(0) + + +# check if in-memory table is active +def _check_imt(): + if not os.path.exists(uacctd_pipefile): + print("In-memory table is not available") + sys.exit(1) + + +# define program arguments +cmd_args_parser = argparse.ArgumentParser(description='show flow-accounting') +cmd_args_parser.add_argument('--action', choices=['show', 'clear', 'restart'], required=True, help='command to flow-accounting daemon') +cmd_args_parser.add_argument('--filter', choices=['interface', 'host', 'ports', 'top'], required=False, nargs='*', help='filter flows to display') +cmd_args_parser.add_argument('--interface', required=False, help='interface name for output filtration') +cmd_args_parser.add_argument('--host', type=str, required=False, help='host address for output filtering') +cmd_args_parser.add_argument('--ports', type=str, required=False, help='port number, range or list for output filtering') +cmd_args_parser.add_argument('--top', type=int, required=False, help='top records for output filtering') +# parse arguments +cmd_args = cmd_args_parser.parse_args() + +try: + if cmd_args.host: + check_host(cmd_args.host) + + if cmd_args.ports: + cmd_args.ports = parse_ports(cmd_args.ports) +except ValueError as e: + print(e) + sys.exit(1) + +# main logic +# do nothing if uacctd daemon is not running +if not _uacctd_running(): + print("flow-accounting is not active") + sys.exit(1) + +# restart pmacct daemon +if cmd_args.action == 'restart': + if commit_in_progress(): + print('Cannot restart flow-accounting while a commit is in progress') + exit(1) + # run command to restart flow-accounting + cmd('systemctl restart uacctd.service', + message='Failed to restart flow-accounting') + +# clear in-memory collected flows +if cmd_args.action == 'clear': + _check_imt() + # run command to clear flows + cmd(f'/usr/bin/pmacct -e -p {uacctd_pipefile}', + message='Failed to clear flows') + +# show table with flows +if cmd_args.action == 'show': + _check_imt() + # get interfaces index and names + ifaces_dict = _get_ifaces_dict() + # get flows + flows_list = _get_flows_list() + + # filter and format flows + tabledata = _flows_filter(flows_list, ifaces_dict) + + # print flows + _flows_table_print(tabledata) + +sys.exit(0) diff --git a/src/op_mode/force_mtu_host.sh b/src/op_mode/force_mtu_host.sh new file mode 100644 index 0000000..c72fc24 --- /dev/null +++ b/src/op_mode/force_mtu_host.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +target=$1 +interface=$2 + +# IPv4 header 20 byte + TCP header 20 byte +ipv4_overhead=40 + +# IPv6 headter 40 byte + TCP header 20 byte +ipv6_overhead=60 + +# If no arguments +if [[ $# -eq 0 ]] ; then + echo "Target host not defined" + exit 1 +fi + +# If one argument, it's ip address. If 2, the second arg "interface" +if [[ $# -eq 1 ]] ; then + mtu=$(sudo nmap -T4 --script path-mtu -F $target | grep "PMTU" | awk {'print $NF'}) +elif [[ $# -eq 2 ]]; then + mtu=$(sudo nmap -T4 -e $interface --script path-mtu -F $target | grep "PMTU" | awk {'print $NF'}) +fi + +tcpv4_mss=$(($mtu-$ipv4_overhead)) +tcpv6_mss=$(($mtu-$ipv6_overhead)) + +echo " +Recommended maximum values (or less) for target $target: +--- +MTU: $mtu +TCP-MSS: $tcpv4_mss +TCP-MSS_IPv6: $tcpv6_mss +" + diff --git a/src/op_mode/force_root-partition-auto-resize.sh b/src/op_mode/force_root-partition-auto-resize.sh new file mode 100644 index 0000000..b39e875 --- /dev/null +++ b/src/op_mode/force_root-partition-auto-resize.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# ROOT_PART_DEV – root partition device path +# ROOT_PART_NAME – root partition device name +# ROOT_DEV_NAME – disk device name +# ROOT_DEV – disk device path +# ROOT_PART_NUM – number of root partition on disk +# ROOT_DEV_SIZE – disk total size in 512 bytes sectors +# ROOT_PART_SIZE – root partition total size in 512 bytes sectors +# ROOT_PART_START – number of 512 bytes sector where root partition starts +# AVAILABLE_EXTENSION_SIZE – calculation available disk space after root partition in 512 bytes sectors +ROOT_PART_DEV=$(findmnt /usr/lib/live/mount/persistence -o source -n) +ROOT_PART_NAME=$(echo "$ROOT_PART_DEV" | cut -d "/" -f 3) +ROOT_DEV_NAME=$(echo /sys/block/*/"${ROOT_PART_NAME}" | cut -d "/" -f 4) +ROOT_DEV="/dev/${ROOT_DEV_NAME}" +ROOT_PART_NUM=$(cat "/sys/block/${ROOT_DEV_NAME}/${ROOT_PART_NAME}/partition") +ROOT_DEV_SIZE=$(cat "/sys/block/${ROOT_DEV_NAME}/size") +ROOT_PART_SIZE=$(cat "/sys/block/${ROOT_DEV_NAME}/${ROOT_PART_NAME}/size") +ROOT_PART_START=$(cat "/sys/block/${ROOT_DEV_NAME}/${ROOT_PART_NAME}/start") +AVAILABLE_EXTENSION_SIZE=$((ROOT_DEV_SIZE - ROOT_PART_START - ROOT_PART_SIZE - 8)) + +# +# Check if device have space for root partition growing up. +# +if [ $AVAILABLE_EXTENSION_SIZE -lt 1 ]; then + echo "There is no available space for root partition extension" + exit 0; +fi + +# +# Resize the partition and grow the filesystem. +# +# "print" and "Fix" directives were added to fix GPT table if it corrupted after virtual drive extension. +# If GPT table is corrupted we'll get Fix/Ignore dialogue after "print" command. +# "Fix" will be the answer for this dialogue. +# If GPT table is fine and no auto-fix dialogue appeared the directive "Fix" simply will print parted utility help info. +parted -m ${ROOT_DEV} ---pretend-input-tty > /dev/null 2>&1 <<EOF +print +Fix +resizepart +${ROOT_PART_NUM} +Yes +100% +EOF +partprobe > /dev/null 2>&1 +resize2fs ${ROOT_PART_DEV} > /dev/null 2>&1 diff --git a/src/op_mode/format_disk.py b/src/op_mode/format_disk.py new file mode 100644 index 0000000..dc3c963 --- /dev/null +++ b/src/op_mode/format_disk.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import os +import re + +from datetime import datetime + +from vyos.utils.io import ask_yes_no +from vyos.utils.process import call +from vyos.utils.process import cmd +from vyos.utils.process import DEVNULL +from vyos.utils.disk import device_from_id + +def list_disks(): + disks = set() + with open('/proc/partitions') as partitions_file: + for line in partitions_file: + fields = line.strip().split() + if len(fields) == 4 and fields[3].isalpha() and fields[3] != 'name': + disks.add(fields[3]) + return disks + + +def is_busy(disk: str): + """Check if given disk device is busy by re-reading it's partition table""" + return call(f'blockdev --rereadpt /dev/{disk}', stderr=DEVNULL) != 0 + + +def backup_partitions(disk: str): + """Save sfdisk partitions output to a backup file""" + + device_path = f'/dev/{disk}' + backup_ts = datetime.now().strftime('%Y%m%d-%H%M') + backup_file = f'/var/tmp/backup_{disk}.{backup_ts}' + call(f'sfdisk -d {device_path} > {backup_file}') + print(f'Partition table backup saved to {backup_file}') + + +def list_partitions(disk: str): + """List partition numbers of a given disk""" + + parts = set() + part_num_expr = re.compile(disk + '([0-9]+)') + with open('/proc/partitions') as partitions_file: + for line in partitions_file: + fields = line.strip().split() + if len(fields) == 4 and fields[3] != 'name' and part_num_expr.match(fields[3]): + part_idx = part_num_expr.match(fields[3]).group(1) + parts.add(int(part_idx)) + return parts + + +def delete_partition(disk: str, partition_idx: int): + cmd(f'parted /dev/{disk} rm {partition_idx}') + + +def format_disk_like(target: str, proto: str): + cmd(f'sfdisk -d /dev/{proto} | sfdisk --force /dev/{target}') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + group = parser.add_argument_group() + group.add_argument('-t', '--target', type=str, required=True, help='Target device to format') + group.add_argument('-p', '--proto', type=str, required=True, help='Prototype device to use as reference') + parser.add_argument('--by-id', action='store_true', help='Specify device by disk id') + args = parser.parse_args() + target = args.target + proto = args.proto + if args.by_id: + target = device_from_id(target) + proto = device_from_id(proto) + + target_disk = target + eligible_target_disks = list_disks() + + proto_disk = proto + eligible_proto_disks = eligible_target_disks.copy() + eligible_proto_disks.remove(target_disk) + + if proto_disk == target_disk: + print('The two disk drives must be different.') + exit(1) + + if not os.path.exists(f'/dev/{proto_disk}'): + print(f'Device /dev/{proto_disk} does not exist') + exit(1) + + if not os.path.exists('/dev/' + target_disk): + print(f'Device /dev/{target_disk} does not exist') + exit(1) + + if target_disk not in eligible_target_disks: + print(f'Device {target_disk} can not be formatted') + exit(1) + + if proto_disk not in eligible_proto_disks: + print(f'Device {proto_disk} can not be used as a prototype for {target_disk}') + exit(1) + + if is_busy(target_disk): + print(f'Disk device {target_disk} is busy, unable to format') + exit(1) + + print(f'\nThis will re-format disk {target_disk} so that it has the same disk' + f'\npartion sizes and offsets as {proto_disk}. This will not copy' + f'\ndata from {proto_disk} to {target_disk}. But this will erase all' + f'\ndata on {target_disk}.\n') + + if not ask_yes_no('Do you wish to proceed?'): + print(f'Disk drive {target_disk} will not be re-formated') + exit(0) + + print(f'Re-formating disk drive {target_disk}...') + + print('Making backup copy of partitions...') + backup_partitions(target_disk) + + print('Deleting old partitions...') + for p in list_partitions(target_disk): + delete_partition(disk=target_disk, partition_idx=p) + + print(f'Creating new partitions on {target_disk} based on {proto_disk}...') + format_disk_like(target=target_disk, proto=proto_disk) + print('Done!') diff --git a/src/op_mode/generate_interfaces_debug_archive.py b/src/op_mode/generate_interfaces_debug_archive.py new file mode 100644 index 0000000..3059aad --- /dev/null +++ b/src/op_mode/generate_interfaces_debug_archive.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from datetime import datetime +from pathlib import Path +from shutil import rmtree +from socket import gethostname +from sys import exit +from tarfile import open as tar_open +from vyos.utils.process import rc_cmd +import os + +# define a list of commands that needs to be executed + +CMD_LIST: list[str] = [ + "journalctl -b -n 500", + "journalctl -b -k -n 500", + "ip -s l", + "cat /proc/interrupts", + "cat /proc/softirqs", + "top -b -d 1 -n 2 -1", + "netstat -l", + "cat /proc/net/dev", + "cat /proc/net/softnet_stat", + "cat /proc/net/icmp", + "cat /proc/net/udp", + "cat /proc/net/tcp", + "cat /proc/net/netstat", + "sysctl net", + "timeout 10 tcpdump -c 500 -eni any port not 22" +] + +CMD_INTERFACES_LIST: list[str] = [ + "ethtool -i ", + "ethtool -S ", + "ethtool -g ", + "ethtool -c ", + "ethtool -a ", + "ethtool -k ", + "ethtool -i ", + "ethtool --phy-statistics " +] + +# get intefaces info +interfaces_list = os.popen('ls /sys/class/net/').read().split() + +# modify CMD_INTERFACES_LIST for all interfaces +CMD_INTERFACES_LIST_MOD=[] +for command_interface in interfaces_list: + for command_interfacev2 in CMD_INTERFACES_LIST: + CMD_INTERFACES_LIST_MOD.append (f'{command_interfacev2}{command_interface}') + +# execute a command and save the output to a file + +def save_stdout(command: str, file: Path) -> None: + rc, stdout = rc_cmd(command) + body: str = f'''### {command} ### +Command: {command} +Exit code: {rc} +Stdout: +{stdout} + +''' + with file.open(mode='a') as f: + f.write(body) + +# get local host name +hostname: str = gethostname() +# get current time +time_now: str = datetime.now().isoformat(timespec='seconds') + +# define a temporary directory for logs and collected data +tmp_dir: Path = Path(f'/tmp/drops-debug_{time_now}') +# set file paths +drops_file: Path = Path(f'{tmp_dir}/drops.txt') +interfaces_file: Path = Path(f'{tmp_dir}/interfaces.txt') +archive_file: str = f'/tmp/packet-drops-debug_{time_now}.tar.bz2' + +# create files +tmp_dir.mkdir() +drops_file.touch() +interfaces_file.touch() + +try: + # execute all commands + for command in CMD_LIST: + save_stdout(command, drops_file) + for command_interface in CMD_INTERFACES_LIST_MOD: + save_stdout(command_interface, interfaces_file) + + # create an archive + with tar_open(name=archive_file, mode='x:bz2') as tar_file: + tar_file.add(tmp_dir) + + # inform user about success + print(f'Debug file is generated and located in {archive_file}') +except Exception as err: + print(f'Error during generating a debug file: {err}') +finally: + # cleanup + rmtree(tmp_dir) + exit() diff --git a/src/op_mode/generate_ipsec_debug_archive.py b/src/op_mode/generate_ipsec_debug_archive.py new file mode 100644 index 0000000..ca2eeb5 --- /dev/null +++ b/src/op_mode/generate_ipsec_debug_archive.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from datetime import datetime +from pathlib import Path +from shutil import rmtree +from socket import gethostname +from sys import exit +from tarfile import open as tar_open +from vyos.utils.process import rc_cmd + +# define a list of commands that needs to be executed +CMD_LIST: list[str] = [ + 'swanctl -L', + 'swanctl -l', + 'swanctl -P', + 'ip x sa show', + 'ip x policy show', + 'ip tunnel show', + 'ip address', + 'ip rule show', + 'ip route | head -100', + 'ip route show table 220' +] +JOURNALCTL_CMD: str = 'journalctl --no-hostname --boot --unit strongswan.service' + +# execute a command and save the output to a file +def save_stdout(command: str, file: Path) -> None: + rc, stdout = rc_cmd(command) + body: str = f'''### {command} ### +Command: {command} +Exit code: {rc} +Stdout: +{stdout} + +''' + with file.open(mode='a') as f: + f.write(body) + + +# get local host name +hostname: str = gethostname() +# get current time +time_now: str = datetime.now().isoformat(timespec='seconds') + +# define a temporary directory for logs and collected data +tmp_dir: Path = Path(f'/tmp/ipsec_debug_{time_now}') +# set file paths +ipsec_status_file: Path = Path(f'{tmp_dir}/ipsec_status.txt') +journalctl_charon_file: Path = Path(f'{tmp_dir}/journalctl_charon.txt') +archive_file: str = f'/tmp/ipsec_debug_{time_now}.tar.bz2' + +# create files +tmp_dir.mkdir() +ipsec_status_file.touch() +journalctl_charon_file.touch() + +try: + # execute all commands + for command in CMD_LIST: + save_stdout(command, ipsec_status_file) + save_stdout(JOURNALCTL_CMD, journalctl_charon_file) + + # create an archive + with tar_open(name=archive_file, mode='x:bz2') as tar_file: + tar_file.add(tmp_dir) + + # inform user about success + print(f'Debug file is generated and located in {archive_file}') +except Exception as err: + print(f'Error during generating a debug file: {err}') +finally: + # cleanup + rmtree(tmp_dir) + exit() diff --git a/src/op_mode/generate_openconnect_otp_key.py b/src/op_mode/generate_openconnect_otp_key.py new file mode 100644 index 0000000..99b67d2 --- /dev/null +++ b/src/op_mode/generate_openconnect_otp_key.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import os + +from vyos.utils.process import popen +from secrets import token_hex +from base64 import b32encode + +if os.geteuid() != 0: + exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.") + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("-u", "--username", type=str, help='Username used for authentication', required=True) + parser.add_argument("-i", "--interval", type=str, help='Duration of single time interval', default="30", required=False) + parser.add_argument("-d", "--digits", type=str, help='The number of digits in the one-time password', default="6", required=False) + args = parser.parse_args() + + hostname = os.uname()[1] + username = args.username + digits = args.digits + period = args.interval + + # check variables: + if int(digits) < 6 or int(digits) > 8: + print("") + quit("The number of digits in the one-time password must be between '6' and '8'") + + if int(period) < 5 or int(period) > 86400: + print("") + quit("Time token interval must be between '5' and '86400' seconds") + + # generate OTP key, URL & QR: + key_hex = token_hex(20) + key_base32 = b32encode(bytes.fromhex(key_hex)).decode() + + otp_url=''.join(["otpauth://totp/",username,"@",hostname,"?secret=",key_base32,"&digits=",digits,"&period=",period]) + qrcode,err = popen('qrencode -t ansiutf8', input=otp_url) + + print("# You can share it with the user, he just needs to scan the QR in his OTP app") + print("# username: ", username) + print("# OTP KEY: ", key_base32) + print("# OTP URL: ", otp_url) + print(qrcode) + print('# To add this OTP key to configuration, run the following commands:') + print(f"set vpn openconnect authentication local-users username {username} otp key '{key_hex}'") + if period != "30": + print(f"set vpn openconnect authentication local-users username {username} otp interval '{period}'") + if digits != "6": + print(f"set vpn openconnect authentication local-users username {username} otp otp-length '{digits}'") diff --git a/src/op_mode/generate_ovpn_client_file.py b/src/op_mode/generate_ovpn_client_file.py new file mode 100644 index 0000000..1d2f106 --- /dev/null +++ b/src/op_mode/generate_ovpn_client_file.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse + +from jinja2 import Template +from textwrap import fill + +from vyos.config import Config +from vyos.ifconfig import Section + +client_config = """ + +client +nobind +remote {{ local_host if local_host else 'x.x.x.x' }} {{ port }} +remote-cert-tls server +proto {{ 'tcp-client' if protocol == 'tcp-passive' else 'udp' }} +dev {{ device_type }} +dev-type {{ device_type }} +persist-key +persist-tun +verb 3 + +# Encryption options +{# Define the encryption map #} +{% set encryption_map = { + 'des': 'DES-CBC', + '3des': 'DES-EDE3-CBC', + 'bf128': 'BF-CBC', + 'bf256': 'BF-CBC', + 'aes128gcm': 'AES-128-GCM', + 'aes128': 'AES-128-CBC', + 'aes192gcm': 'AES-192-GCM', + 'aes192': 'AES-192-CBC', + 'aes256gcm': 'AES-256-GCM', + 'aes256': 'AES-256-CBC' +} %} + +{% if encryption is defined and encryption is not none %} +{% if encryption.data_ciphers is defined and encryption.data_ciphers is not none %} +cipher {% for algo in encryption.data_ciphers %} +{{ encryption_map[algo] if algo in encryption_map.keys() else algo }}{% if not loop.last %}:{% endif %} +{% endfor %} + +data-ciphers {% for algo in encryption.data_ciphers %} +{{ encryption_map[algo] if algo in encryption_map.keys() else algo }}{% if not loop.last %}:{% endif %} +{% endfor %} +{% endif %} +{% endif %} + +{% if hash is defined and hash is not none %} +auth {{ hash }} +{% endif %} +{{ 'comp-lzo' if use_lzo_compression is defined else '' }} + +<ca> +-----BEGIN CERTIFICATE----- +{{ ca }} +-----END CERTIFICATE----- + +</ca> + +<cert> +-----BEGIN CERTIFICATE----- +{{ cert }} +-----END CERTIFICATE----- + +</cert> + +<key> +-----BEGIN PRIVATE KEY----- +{{ key }} +-----END PRIVATE KEY----- + +</key> + +""" + +config = Config() +base = ['interfaces', 'openvpn'] + +if not config.exists(base): + print('OpenVPN not configured') + exit(0) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument( + "-i", + "--interface", + type=str, + help='OpenVPN interface the client is connecting to', + required=True, + ) + parser.add_argument( + "-a", "--ca", type=str, help='OpenVPN CA cerificate', required=True + ) + parser.add_argument( + "-c", "--cert", type=str, help='OpenVPN client cerificate', required=True + ) + parser.add_argument( + "-k", "--key", type=str, help='OpenVPN client cerificate key', action="store" + ) + args = parser.parse_args() + + interface = args.interface + ca = args.ca + cert = args.cert + key = args.key + if not key: + key = args.cert + + if interface not in Section.interfaces('openvpn'): + exit(f'OpenVPN interface "{interface}" does not exist!') + + if not config.exists(['pki', 'ca', ca, 'certificate']): + exit(f'OpenVPN CA certificate "{ca}" does not exist!') + + if not config.exists(['pki', 'certificate', cert, 'certificate']): + exit(f'OpenVPN certificate "{cert}" does not exist!') + + if not config.exists(['pki', 'certificate', cert, 'private', 'key']): + exit(f'OpenVPN certificate key "{key}" does not exist!') + + config = config.get_config_dict( + base + [interface], + key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True, + with_pki=True, + ) + + ca = config['pki']['ca'][ca]['certificate'] + ca = fill(ca, width=64) + cert = config['pki']['certificate'][cert]['certificate'] + cert = fill(cert, width=64) + key = config['pki']['certificate'][key]['private']['key'] + key = fill(key, width=64) + + config['ca'] = ca + config['cert'] = cert + config['key'] = key + config['port'] = '1194' if 'local_port' not in config else config['local_port'] + + client = Template(client_config, trim_blocks=True).render(config) + print(client) diff --git a/src/op_mode/generate_public_key_command.py b/src/op_mode/generate_public_key_command.py new file mode 100644 index 0000000..8ba55c9 --- /dev/null +++ b/src/op_mode/generate_public_key_command.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys +import urllib.parse + +import vyos.remote +from vyos.template import generate_uuid4 + + +def get_key(path) -> list: + """Get public keys from a local file or remote URL + + Args: + path: Path to the public keys file + + Returns: list of public keys split by new line + + """ + url = urllib.parse.urlparse(path) + if url.scheme == 'file' or url.scheme == '': + with open(os.path.expanduser(path), 'r') as f: + key_string = f.read() + else: + key_string = vyos.remote.get_remote_config(path) + return key_string.split('\n') + + +if __name__ == "__main__": + first_loop = True + + for k in get_key(sys.argv[2]): + k = k.split() + # Skip empty list entry + if k == []: + continue + + try: + username = sys.argv[1] + # Github keys don't have identifier for example 'vyos@localhost' + # 'ssh-rsa AAAA... vyos@localhost' + # Generate uuid4 identifier + identifier = f'github@{generate_uuid4("")}' if sys.argv[2].startswith('https://github.com') else k[2] + algorithm, key = k[0], k[1] + except Exception as e: + print("Failed to retrieve the public key: {}".format(e)) + sys.exit(1) + + if first_loop: + print('# To add this key as an embedded key, run the following commands:') + print('configure') + print(f'set system login user {username} authentication public-keys {identifier} key {key}') + print(f'set system login user {username} authentication public-keys {identifier} type {algorithm}') + + first_loop = False diff --git a/src/op_mode/generate_service_rule-resequence.py b/src/op_mode/generate_service_rule-resequence.py new file mode 100644 index 0000000..9333d63 --- /dev/null +++ b/src/op_mode/generate_service_rule-resequence.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import argparse +from vyos.configquery import ConfigTreeQuery + + +def convert_to_set_commands(config_dict, parent_key=''): + """ + Converts a configuration dictionary into a list of set commands. + + Args: + config_dict (dict): The configuration dictionary. + parent_key (str): The parent key for nested dictionaries. + + Returns: + list: A list of set commands. + """ + commands = [] + for key, value in config_dict.items(): + current_key = parent_key + key if parent_key else key + + if isinstance(value, dict): + if not value: + commands.append(f"set {current_key}") + else: + commands.extend( + convert_to_set_commands(value, f"{current_key} ")) + + elif isinstance(value, list): + for item in value: + commands.append(f"set {current_key} '{item}'") + + elif isinstance(value, str): + commands.append(f"set {current_key} '{value}'") + + return commands + + +def change_rule_numbers(config_dict, start, step): + """ + Changes rule numbers in the configuration dictionary. + + Args: + config_dict (dict): The configuration dictionary. + start (int): The starting rule number. + step (int): The step to increment the rule numbers. + + Returns: + None + """ + if 'rule' in config_dict: + rule_dict = config_dict['rule'] + updated_rule_dict = {} + rule_num = start + for rule_key in sorted(rule_dict.keys()): + updated_rule_dict[str(rule_num)] = rule_dict[rule_key] + rule_num += step + config_dict['rule'] = updated_rule_dict + + for key in config_dict: + if isinstance(config_dict[key], dict): + change_rule_numbers(config_dict[key], start, step) + + +def convert_rule_keys_to_int(config_dict, prev_key=None): + """ + Converts rule keys in the configuration dictionary to integers. + + Args: + config_dict (dict or list): The configuration dictionary or list. + + Returns: + dict or list: The modified dictionary or list. + """ + if isinstance(config_dict, dict): + new_dict = {} + for key, value in config_dict.items(): + # Convert key to integer if possible + new_key = int(key) if key.isdigit() and prev_key == 'rule' else key + + # Recur for nested dictionaries + if isinstance(value, dict): + new_value = convert_rule_keys_to_int(value, key) + else: + new_value = value + + new_dict[new_key] = new_value + + return new_dict + elif isinstance(config_dict, list): + return [convert_rule_keys_to_int(item) for item in config_dict] + else: + return config_dict + + +if __name__ == "__main__": + # Parse command-line arguments + parser = argparse.ArgumentParser(description='Convert dictionary to set commands with rule number modifications.') + parser.add_argument('--service', type=str, help='Name of service') + parser.add_argument('--start', type=int, default=100, help='Start rule number (default: 100)') + parser.add_argument('--step', type=int, default=10, help='Step for rule numbers (default: 10)') + args = parser.parse_args() + + config = ConfigTreeQuery() + if not config.exists(args.service): + print(f'{args.service} is not configured') + exit(1) + + config_dict = config.get_config_dict(args.service) + + if 'firewall' in config_dict: + # Remove global-options, group and flowtable as they don't need sequencing + for item in ['global-options', 'group', 'flowtable']: + if item in config_dict['firewall']: + del config_dict['firewall'][item] + + # Convert rule keys to integers, rule "10" -> rule 10 + # This is necessary for sorting the rules + config_dict = convert_rule_keys_to_int(config_dict) + + # Apply rule number modifications + change_rule_numbers(config_dict, start=args.start, step=args.step) + + # Convert to 'set' commands + set_commands = convert_to_set_commands(config_dict) + + print() + for command in set_commands: + print(command) + print() diff --git a/src/op_mode/generate_ssh_server_key.py b/src/op_mode/generate_ssh_server_key.py new file mode 100644 index 0000000..d6063c4 --- /dev/null +++ b/src/op_mode/generate_ssh_server_key.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit +from vyos.utils.io import ask_yes_no +from vyos.utils.process import cmd +from vyos.utils.commit import commit_in_progress + +if not ask_yes_no('Do you really want to remove the existing SSH host keys?'): + exit(0) + +if commit_in_progress(): + print('Cannot restart SSH while a commit is in progress') + exit(1) + +cmd('rm -v /etc/ssh/ssh_host_*') +cmd('dpkg-reconfigure openssh-server') +cmd('systemctl restart ssh.service') diff --git a/src/op_mode/generate_system_login_user.py b/src/op_mode/generate_system_login_user.py new file mode 100644 index 0000000..1b328ea --- /dev/null +++ b/src/op_mode/generate_system_login_user.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import os + +from vyos.utils.process import popen +from secrets import token_hex +from base64 import b32encode + +if os.geteuid() != 0: + exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.") + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("-u", "--username", type=str, help='Username used for authentication', required=True) + parser.add_argument("-l", "--rate_limit", type=str, help='Limit number of logins (rate-limit) per rate-time (default: 3)', default="3", required=False) + parser.add_argument("-t", "--rate_time", type=str, help='Limit number of logins (rate-limit) per rate-time (default: 30)', default="30", required=False) + parser.add_argument("-w", "--window_size", type=str, help='Set window of concurrently valid codes (default: 3)', default="3", required=False) + parser.add_argument("-i", "--interval", type=str, help='Duration of single time interval', default="30", required=False) + parser.add_argument("-d", "--digits", type=str, help='The number of digits in the one-time password', default="6", required=False) + args = parser.parse_args() + + hostname = os.uname()[1] + username = args.username + rate_limit = args.rate_limit + rate_time = args.rate_time + window_size = args.window_size + digits = args.digits + period = args.interval + + # check variables: + if int(rate_limit) < 1 or int(rate_limit) > 10: + print("") + quit("Number of logins (rate-limit) must be between '1' and '10'") + + if int(rate_time) < 15 or int(rate_time) > 600: + print("") + quit("The rate-time must be between '15' and '600' seconds") + + if int(window_size) < 1 or int(window_size) > 21: + print("") + quit("Window of concurrently valid codes must be between '1' and '21' seconds") + + # generate OTP key, URL & QR: + key_hex = token_hex(20) + key_base32 = b32encode(bytes.fromhex(key_hex)).decode() + + otp_url=''.join(["otpauth://totp/",username,"@",hostname,"?secret=",key_base32,"&digits=",digits,"&period=",period]) + qrcode,err = popen('qrencode -t ansiutf8', input=otp_url) + + print("# You can share it with the user, he just needs to scan the QR in his OTP app") + print("# username: ", username) + print("# OTP KEY: ", key_base32) + print("# OTP URL: ", otp_url) + print(qrcode) + print('# To add this OTP key to configuration, run the following commands:') + print(f"set system login user {username} authentication otp key '{key_base32}'") + if rate_limit != "3": + print(f"set system login user {username} authentication otp rate-limit '{rate_limit}'") + if rate_time != "30": + print(f"set system login user {username} authentication otp rate-time '{rate_time}'") + if window_size != "3": + print(f"set system login user {username} authentication otp window-size '{window_size}'") diff --git a/src/op_mode/generate_tech-support_archive.py b/src/op_mode/generate_tech-support_archive.py new file mode 100644 index 0000000..41b53cd --- /dev/null +++ b/src/op_mode/generate_tech-support_archive.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import os +import argparse +import glob +from datetime import datetime +from pathlib import Path +from shutil import rmtree + +from socket import gethostname +from sys import exit +from tarfile import open as tar_open +from vyos.utils.process import rc_cmd +from vyos.remote import upload + +def op(cmd: str) -> str: + """Returns a command with the VyOS operational mode wrapper.""" + return f'/opt/vyatta/bin/vyatta-op-cmd-wrapper {cmd}' + +def save_stdout(command: str, file: Path) -> None: + rc, stdout = rc_cmd(command) + body: str = f'''### {command} ### +Command: {command} +Exit code: {rc} +Stdout: +{stdout} + +''' + with file.open(mode='a') as f: + f.write(body) +def __rotate_logs(path: str, log_pattern:str): + files_list = glob.glob(f'{path}/{log_pattern}') + if len(files_list) > 5: + oldest_file = min(files_list, key=os.path.getctime) + os.remove(oldest_file) + + +def __generate_archived_files(location_path: str) -> None: + """ + Generate arhives of main directories + :param location_path: path to temporary directory + :type location_path: str + """ + # Dictionary arhive_name:directory_to_arhive + archive_dict = { + 'etc': '/etc', + 'home': '/home', + 'var-log': '/var/log', + 'root': '/root', + 'tmp': '/tmp', + 'core-dump': '/var/core', + 'config': '/opt/vyatta/etc/config' + } + # Dictionary arhive_name:excluding pattern + archive_excludes = { + # Old location of archives + 'config': 'tech-support-archive', + # New locations of arhives + 'tmp': 'tech-support-archive' + } + for archive_name, path in archive_dict.items(): + archive_file: str = f'{location_path}/{archive_name}.tar.gz' + with tar_open(name=archive_file, mode='x:gz') as tar_file: + if archive_name in archive_excludes: + tar_file.add(path, filter=lambda x: None if str(archive_excludes[archive_name]) in str(x.name) else x) + else: + tar_file.add(path) + + +def __generate_main_archive_file(archive_file: str, tmp_dir_path: str) -> None: + """ + Generate main arhive file + :param archive_file: name of arhive file + :type archive_file: str + :param tmp_dir_path: path to arhive memeber + :type tmp_dir_path: str + """ + with tar_open(name=archive_file, mode='x:gz') as tar_file: + tar_file.add(tmp_dir_path, arcname=os.path.basename(tmp_dir_path)) + + +if __name__ == '__main__': + defualt_tmp_dir = '/tmp' + parser = argparse.ArgumentParser() + parser.add_argument("path", nargs='?', default=defualt_tmp_dir) + args = parser.parse_args() + location_path = args.path[:-1] if args.path[-1] == '/' else args.path + + hostname: str = gethostname() + time_now: str = datetime.now().isoformat(timespec='seconds').replace(":", "-") + + remote = False + tmp_path = '' + tmp_dir_path = '' + if 'ftp://' in args.path or 'scp://' in args.path: + remote = True + tmp_path = defualt_tmp_dir + else: + tmp_path = location_path + archive_pattern = f'_tech-support-archive_' + archive_file_name = f'{hostname}{archive_pattern}{time_now}.tar.gz' + + # Log rotation in tmp directory + if tmp_path == defualt_tmp_dir: + __rotate_logs(tmp_path, f'*{archive_pattern}*') + + # Temporary directory creation + tmp_dir_path = f'{tmp_path}/drops-debug_{time_now}' + tmp_dir: Path = Path(tmp_dir_path) + tmp_dir.mkdir(parents=True) + + report_file: Path = Path(f'{tmp_dir_path}/show_tech-support_report.txt') + report_file.touch() + try: + + save_stdout(op('show tech-support report'), report_file) + # Generate included archives + __generate_archived_files(tmp_dir_path) + + # Generate main archive + __generate_main_archive_file(f'{tmp_path}/{archive_file_name}', tmp_dir_path) + # Delete temporary directory + rmtree(tmp_dir) + # Upload to remote site if it is scpecified + if remote: + upload(f'{tmp_path}/{archive_file_name}', args.path) + print(f'Debug file is generated and located in {location_path}/{archive_file_name}') + except Exception as err: + print(f'Error during generating a debug file: {err}') + # cleanup + if tmp_dir.exists(): + rmtree(tmp_dir) + finally: + # cleanup + exit() diff --git a/src/op_mode/igmp-proxy.py b/src/op_mode/igmp-proxy.py new file mode 100644 index 0000000..709e259 --- /dev/null +++ b/src/op_mode/igmp-proxy.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# File: show_igmpproxy.py +# Purpose: +# Display istatistics from IPv4 IGMP proxy. +# Used by the "run show ip multicast" command tree. + +import ipaddress +import json +import socket +import sys +import tabulate + +import vyos.config +import vyos.opmode + +from vyos.utils.convert import bytes_to_human +from vyos.utils.io import print_error +from vyos.utils.process import process_named_running + +def _is_configured(): + """Check if IGMP proxy is configured""" + return vyos.config.Config().exists_effective('protocols igmp-proxy') + +def _kernel_to_ip(addr): + """ + Convert any given address from Linux kernel to a proper, IPv4 address + using the correct host byte order. + """ + # Convert from hex 'FE000A0A' to decimal '4261415434' + addr = int(addr, 16) + # Kernel ABI _always_ uses network byte order. + addr = socket.ntohl(addr) + return str(ipaddress.IPv4Address(addr)) + +def _process_mr_vif(): + """Read rows from /proc/net/ip_mr_vif into dicts.""" + result = [] + with open('/proc/net/ip_mr_vif', 'r') as f: + next(f) + for line in f: + result.append({ + 'Interface': line.split()[1], + 'PktsIn' : int(line.split()[3]), + 'PktsOut' : int(line.split()[5]), + 'BytesIn' : int(line.split()[2]), + 'BytesOut' : int(line.split()[4]), + 'Local' : _kernel_to_ip(line.split()[7]), + }) + return result + +def show_interface(raw: bool): + if data := _process_mr_vif(): + if raw: + # Make the interface name the key for each row. + table = {} + for v in data: + table[v.pop('Interface')] = v + return json.loads(json.dumps(table)) + # Make byte values human-readable for the table. + arr = [] + for x in data: + arr.append({k: bytes_to_human(v) if k.startswith('Bytes') \ + else v for k, v in x.items()}) + return tabulate.tabulate(arr, headers='keys') + + +if not _is_configured(): + print_error('IGMP proxy is not configured.') + sys.exit(0) +if not process_named_running('igmpproxy'): + print_error('IGMP proxy is not running.') + sys.exit(0) + + +if __name__ == "__main__": + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print_error(e) + sys.exit(1) diff --git a/src/op_mode/ikev2_profile_generator.py b/src/op_mode/ikev2_profile_generator.py new file mode 100644 index 0000000..cf2bc6d --- /dev/null +++ b/src/op_mode/ikev2_profile_generator.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse + +from sys import exit +from socket import getfqdn +from cryptography.x509.oid import NameOID + +from vyos.configquery import ConfigTreeQuery +from vyos.config import config_dict_mangle_acme +from vyos.pki import CERT_BEGIN +from vyos.pki import CERT_END +from vyos.pki import find_chain +from vyos.pki import encode_certificate +from vyos.pki import load_certificate +from vyos.template import render_to_string +from vyos.utils.io import ask_input + +# Apple profiles only support one IKE/ESP encryption cipher and hash, whereas +# VyOS comes with a multitude of different proposals for a connection. +# +# We take all available proposals from the VyOS CLI and ask the user which one +# he would like to get enabled in his profile - thus there is limited possibility +# to select a proposal that is not supported on the connection profile. +# +# IOS supports IKE-SA encryption algorithms: +# - DES +# - 3DES +# - AES-128 +# - AES-256 +# - AES-128-GCM +# - AES-256-GCM +# - ChaCha20Poly1305 +# +vyos2apple_cipher = { + '3des' : '3DES', + 'aes128' : 'AES-128', + 'aes256' : 'AES-256', + 'aes128gcm128' : 'AES-128-GCM', + 'aes256gcm128' : 'AES-256-GCM', + 'chacha20poly1305' : 'ChaCha20Poly1305', +} + +# Windows supports IKE-SA encryption algorithms: +# - DES3 +# - AES128 +# - AES192 +# - AES256 +# - GCMAES128 +# - GCMAES192 +# - GCMAES256 +# +vyos2windows_cipher = { + '3des' : 'DES3', + 'aes128' : 'AES128', + 'aes192' : 'AES192', + 'aes256' : 'AES256', + 'aes128gcm128' : 'GCMAES128', + 'aes192gcm128' : 'GCMAES192', + 'aes256gcm128' : 'GCMAES256', +} + +# IOS supports IKE-SA integrity algorithms: +# - SHA1-96 +# - SHA1-160 +# - SHA2-256 +# - SHA2-384 +# - SHA2-512 +# +vyos2apple_integrity = { + 'sha1' : 'SHA1-96', + 'sha1_160' : 'SHA1-160', + 'sha256' : 'SHA2-256', + 'sha384' : 'SHA2-384', + 'sha512' : 'SHA2-512', +} + +# Windows supports IKE-SA integrity algorithms: +# - SHA1-96 +# - SHA1-160 +# - SHA2-256 +# - SHA2-384 +# - SHA2-512 +# +vyos2windows_integrity = { + 'sha1' : 'SHA196', + 'sha256' : 'SHA256', + 'aes128gmac' : 'GCMAES128', + 'aes192gmac' : 'GCMAES192', + 'aes256gmac' : 'GCMAES256', +} + +# IOS 14.2 and later do no support dh-group 1,2 and 5. Supported DH groups would +# be: 14, 15, 16, 17, 18, 19, 20, 21, 31, 32 +vyos2apple_dh_group = { + '14' : '14', + '15' : '15', + '16' : '16', + '17' : '17', + '18' : '18', + '19' : '19', + '20' : '20', + '21' : '21', + '31' : '31', + '32' : '32' +} + +# Newer versions of Windows support groups 19 and 20, albeit under a different naming convention +vyos2windows_dh_group = { + '1' : 'Group1', + '2' : 'Group2', + '14' : 'Group14', + '19' : 'ECP256', + '20' : 'ECP384', + '24' : 'Group24' +} + +# For PFS, Windows also has its own inconsistent naming scheme for each group +vyos2windows_pfs_group = { + '1' : 'PFS1', + '2' : 'PFS2', + '14' : 'PFS2048', + '19' : 'ECP256', + '20' : 'ECP384', + '24' : 'PFS24' +} + +parser = argparse.ArgumentParser() +parser.add_argument('--os', const='all', nargs='?', choices=['ios', 'windows'], help='Operating system used for config generation', required=True) +parser.add_argument("--connection", action="store", help='IPsec IKEv2 remote-access connection name from CLI', required=True) +parser.add_argument("--remote", action="store", help='VPN connection remote-address where the client will connect to', required=True) +parser.add_argument("--profile", action="store", help='IKEv2 profile name used in the profile list on the device') +parser.add_argument("--name", action="store", help='VPN connection name as seen in the VPN application later') +args = parser.parse_args() + +ipsec_base = ['vpn', 'ipsec'] +config_base = ipsec_base + ['remote-access', 'connection'] +pki_base = ['pki'] +conf = ConfigTreeQuery() +if not conf.exists(config_base): + exit('IPsec remote-access is not configured!') +if not conf.exists(pki_base): + exit('PKI is not configured!') + +profile_name = 'VyOS IKEv2 Profile' +if args.profile: + profile_name = args.profile + +vpn_name = 'VyOS IKEv2 VPN' +if args.name: + vpn_name = args.name + +conn_base = config_base + [args.connection] +if not conf.exists(conn_base): + exit(f'IPsec remote-access connection "{args.connection}" does not exist!') + +data = conf.get_config_dict(conn_base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + +data['profile_name'] = profile_name +data['vpn_name'] = vpn_name +data['remote'] = args.remote +# This is a reverse-DNS style unique identifier used to detect duplicate profiles +tmp = getfqdn().split('.') +tmp = reversed(tmp) +data['rfqdn'] = '.'.join(tmp) + +if args.os == 'ios': + pki = conf.get_config_dict(pki_base, get_first_key=True) + if 'certificate' in pki: + for certificate in pki['certificate']: + pki['certificate'][certificate] = config_dict_mangle_acme(certificate, pki['certificate'][certificate]) + + cert_name = data['authentication']['x509']['certificate'] + + + cert_data = load_certificate(pki['certificate'][cert_name]['certificate']) + data['cert_common_name'] = cert_data.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value + data['ca_common_name'] = cert_data.issuer.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value + data['ca_certificates'] = [] + + loaded_ca_certs = {load_certificate(c['certificate']) + for c in pki['ca'].values()} if 'ca' in pki else {} + + for ca_name in data['authentication']['x509']['ca_certificate']: + loaded_ca_cert = load_certificate(pki['ca'][ca_name]['certificate']) + ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + for ca in ca_full_chain: + tmp = { + 'ca_name' : ca.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value, + 'ca_chain' : encode_certificate(ca).replace(CERT_BEGIN, '').replace(CERT_END, '').replace('\n', ''), + } + data['ca_certificates'].append(tmp) + + # Remove duplicate list entries for CA certificates, as they are added by their common name + # https://stackoverflow.com/a/9427216 + data['ca_certificates'] = [dict(t) for t in {tuple(d.items()) for d in data['ca_certificates']}] + +esp_group = conf.get_config_dict(ipsec_base + ['esp-group', data['esp_group']], + key_mangling=('-', '_'), get_first_key=True) +ike_proposal = conf.get_config_dict(ipsec_base + ['ike-group', data['ike_group'], 'proposal'], + key_mangling=('-', '_'), get_first_key=True) + +# This script works only for Apple iOS/iPadOS and Windows. Both operating systems +# have different limitations thus we load the limitations based on the operating +# system used. + +vyos2client_cipher = vyos2apple_cipher if args.os == 'ios' else vyos2windows_cipher; +vyos2client_integrity = vyos2apple_integrity if args.os == 'ios' else vyos2windows_integrity; +vyos2client_dh_group = vyos2apple_dh_group if args.os == 'ios' else vyos2windows_dh_group + +def transform_pfs(pfs, ike_dh_group): + pfs_enabled = (pfs != 'disable') + if pfs == 'enable': + pfs_dh_group = ike_dh_group + elif pfs.startswith('dh-group'): + pfs_dh_group = pfs.removeprefix('dh-group') + + if args.os == 'ios': + if pfs_enabled: + if pfs_dh_group not in set(vyos2apple_dh_group): + exit(f'The PFS group configured for "{args.connection}" is not supported by the client!') + return pfs_dh_group + else: + return None + else: + if pfs_enabled: + if pfs_dh_group not in set(vyos2windows_pfs_group): + exit(f'The PFS group configured for "{args.connection}" is not supported by the client!') + return vyos2windows_pfs_group[ pfs_dh_group ] + else: + return 'None' + +# Create a dictionary containing client conform IKE settings +ike = {} +count = 1 +for _, proposal in ike_proposal.items(): + if {'dh_group', 'encryption', 'hash'} <= set(proposal): + if (proposal['encryption'] in set(vyos2client_cipher) and + proposal['hash'] in set(vyos2client_integrity) and + proposal['dh_group'] in set(vyos2client_dh_group)): + + # We 're-code' from the VyOS IPsec proposals to the Apple naming scheme + proposal['encryption'] = vyos2client_cipher[ proposal['encryption'] ] + proposal['hash'] = vyos2client_integrity[ proposal['hash'] ] + # DH group will need to be transformed later after we calculate PFS group + + ike.update( { str(count) : proposal } ) + count += 1 + +# Create a dictionary containing client conform ESP settings +esp = {} +count = 1 +for _, proposal in esp_group['proposal'].items(): + if {'encryption', 'hash'} <= set(proposal): + if proposal['encryption'] in set(vyos2client_cipher) and proposal['hash'] in set(vyos2client_integrity): + # We 're-code' from the VyOS IPsec proposals to the Apple naming scheme + proposal['encryption'] = vyos2client_cipher[ proposal['encryption'] ] + proposal['hash'] = vyos2client_integrity[ proposal['hash'] ] + # Copy PFS setting from the group, if present (we will need to + # transform this later once the IKE group is selected) + proposal['pfs'] = esp_group.get('pfs', 'enable') + + esp.update( { str(count) : proposal } ) + count += 1 +try: + if len(ike) > 1: + # Propare the input questions for the user + tmp = '\n' + for number, options in ike.items(): + tmp += f'({number}) Encryption {options["encryption"]}, Integrity {options["hash"]}, DH group {options["dh_group"]}\n' + tmp += '\nSelect one of the above IKE groups: ' + data['ike_encryption'] = ike[ ask_input(tmp, valid_responses=list(ike)) ] + elif len(ike) == 1: + data['ike_encryption'] = ike['1'] + else: + exit(f'None of the configured IKE proposals for "{args.connection}" are supported by the client!') + + if len(esp) > 1: + tmp = '\n' + for number, options in esp.items(): + tmp += f'({number}) Encryption {options["encryption"]}, Integrity {options["hash"]}\n' + tmp += '\nSelect one of the above ESP groups: ' + data['esp_encryption'] = esp[ ask_input(tmp, valid_responses=list(esp)) ] + elif len(esp) == 1: + data['esp_encryption'] = esp['1'] + else: + exit(f'None of the configured ESP proposals for "{args.connection}" are supported by the client!') + +except KeyboardInterrupt: + exit("Interrupted") + +# Transform the DH and PFS groups now that all selections are known +data['esp_encryption']['pfs'] = transform_pfs(data['esp_encryption']['pfs'], data['ike_encryption']['dh_group']) +data['ike_encryption']['dh_group'] = vyos2client_dh_group[ data['ike_encryption']['dh_group'] ] + +print('\n\n==== <snip> ====') +if args.os == 'ios': + print(render_to_string('ipsec/ios_profile.j2', data)) + print('==== </snip> ====\n') + print('Save the XML from above to a new file named "vyos.mobileconfig" and E-Mail it to your phone.') +elif args.os == 'windows': + print(render_to_string('ipsec/windows_profile.j2', data)) + print('==== </snip> ====\n') diff --git a/src/op_mode/image_info.py b/src/op_mode/image_info.py new file mode 100644 index 0000000..56aefcd --- /dev/null +++ b/src/op_mode/image_info.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This file is part of VyOS. +# +# VyOS is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# VyOS is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# VyOS. If not, see <https://www.gnu.org/licenses/>. + +import sys +from typing import Union + +from tabulate import tabulate + +from vyos import opmode +from vyos.system import disk +from vyos.system import grub +from vyos.system import image +from vyos.utils.convert import bytes_to_human + + +def _format_show_images_summary(images_summary: image.BootDetails) -> str: + headers: list[str] = ['Name', 'Default boot', 'Running'] + table_data: list[list[str]] = list() + for image_item in images_summary.get('images_available', []): + name: str = image_item + if images_summary.get('image_default') == name: + default: str = 'Yes' + else: + default: str = '' + + if images_summary.get('image_running') == name: + running: str = 'Yes' + else: + running: str = '' + + table_data.append([name, default, running]) + tabulated: str = tabulate(table_data, headers) + + return tabulated + + +def _format_show_images_details( + images_details: list[image.ImageDetails]) -> str: + headers: list[str] = [ + 'Name', 'Version', 'Storage Read-Only', 'Storage Read-Write', + 'Storage Total' + ] + table_data: list[list[Union[str, int]]] = list() + for image_item in images_details: + name: str = image_item.get('name') + version: str = image_item.get('version') + disk_ro: str = bytes_to_human(image_item.get('disk_ro'), + precision=1, int_below_exponent=30) + disk_rw: str = bytes_to_human(image_item.get('disk_rw'), + precision=1, int_below_exponent=30) + disk_total: str = bytes_to_human(image_item.get('disk_total'), + precision=1, int_below_exponent=30) + table_data.append([name, version, disk_ro, disk_rw, disk_total]) + tabulated: str = tabulate(table_data, headers, + colalign=('left', 'left', 'right', 'right', 'right')) + + return tabulated + + +def show_images_summary(raw: bool) -> Union[image.BootDetails, str]: + images_available: list[str] = grub.version_list() + root_dir: str = disk.find_persistence() + boot_vars: dict = grub.vars_read(f'{root_dir}/{image.CFG_VYOS_VARS}') + + images_summary: image.BootDetails = dict() + + images_summary['image_default'] = image.get_default_image() + images_summary['image_running'] = image.get_running_image() + images_summary['images_available'] = images_available + images_summary['console_type'] = boot_vars.get('console_type') + images_summary['console_num'] = boot_vars.get('console_num') + + if raw: + return images_summary + else: + return _format_show_images_summary(images_summary) + + +def show_images_details(raw: bool) -> Union[list[image.ImageDetails], str]: + images_details = image.get_images_details() + + if raw: + return images_details + else: + return _format_show_images_details(images_details) + + +if __name__ == '__main__': + try: + res = opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py new file mode 100644 index 0000000..bdc16de --- /dev/null +++ b/src/op_mode/image_installer.py @@ -0,0 +1,1056 @@ +#!/usr/bin/env python3 +# +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This file is part of VyOS. +# +# VyOS is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# VyOS is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# VyOS. If not, see <https://www.gnu.org/licenses/>. + +from argparse import ArgumentParser, Namespace +from pathlib import Path +from shutil import copy, chown, rmtree, copytree +from glob import glob +from sys import exit +from os import environ +from os import readlink +from os import getpid, getppid +from typing import Union +from urllib.parse import urlparse +from passlib.hosts import linux_context +from errno import ENOSPC + +from psutil import disk_partitions + +from vyos.configtree import ConfigTree +from vyos.configquery import ConfigTreeQuery +from vyos.remote import download +from vyos.system import disk, grub, image, compat, raid, SYSTEM_CFG_VER +from vyos.template import render +from vyos.utils.io import ask_input, ask_yes_no, select_entry +from vyos.utils.file import chmod_2775 +from vyos.utils.process import cmd, run +from vyos.version import get_remote_version, get_version_data + +# define text messages +MSG_ERR_NOT_LIVE: str = 'The system is already installed. Please use "add system image" instead.' +MSG_ERR_LIVE: str = 'The system is in live-boot mode. Please use "install image" instead.' +MSG_ERR_NO_DISK: str = 'No suitable disk was found. There must be at least one disk of 2GB or greater size.' +MSG_ERR_IMPROPER_IMAGE: str = 'Missing sha256sum.txt.\nEither this image is corrupted, or of era 1.2.x (md5sum) and would downgrade image tools;\ndisallowed in either case.' +MSG_ERR_ARCHITECTURE_MISMATCH: str = 'Upgrading to a different image architecture will break your system.' +MSG_INFO_INSTALL_WELCOME: str = 'Welcome to VyOS installation!\nThis command will install VyOS to your permanent storage.' +MSG_INFO_INSTALL_EXIT: str = 'Exiting from VyOS installation' +MSG_INFO_INSTALL_SUCCESS: str = 'The image installed successfully; please reboot now.' +MSG_INFO_INSTALL_DISKS_LIST: str = 'The following disks were found:' +MSG_INFO_INSTALL_DISK_SELECT: str = 'Which one should be used for installation?' +MSG_INFO_INSTALL_RAID_CONFIGURE: str = 'Would you like to configure RAID-1 mirroring?' +MSG_INFO_INSTALL_RAID_FOUND_DISKS: str = 'Would you like to configure RAID-1 mirroring on them?' +MSG_INFO_INSTALL_RAID_CHOOSE_DISKS: str = 'Would you like to choose two disks for RAID-1 mirroring?' +MSG_INFO_INSTALL_DISK_CONFIRM: str = 'Installation will delete all data on the drive. Continue?' +MSG_INFO_INSTALL_RAID_CONFIRM: str = 'Installation will delete all data on both drives. Continue?' +MSG_INFO_INSTALL_PARTITONING: str = 'Creating partition table...' +MSG_INPUT_CONFIG_FOUND: str = 'An active configuration was found. Would you like to copy it to the new image?' +MSG_INPUT_CONFIG_CHOICE: str = 'The following config files are available for boot:' +MSG_INPUT_CONFIG_CHOOSE: str = 'Which file would you like as boot config?' +MSG_INPUT_IMAGE_NAME: str = 'What would you like to name this image?' +MSG_INPUT_IMAGE_DEFAULT: str = 'Would you like to set the new image as the default one for boot?' +MSG_INPUT_PASSWORD: str = 'Please enter a password for the "vyos" user:' +MSG_INPUT_PASSWORD_CONFIRM: str = 'Please confirm password for the "vyos" user:' +MSG_INPUT_ROOT_SIZE_ALL: str = 'Would you like to use all the free space on the drive?' +MSG_INPUT_ROOT_SIZE_SET: str = 'Please specify the size (in GB) of the root partition (min is 1.5 GB)?' +MSG_INPUT_CONSOLE_TYPE: str = 'What console should be used by default? (K: KVM, S: Serial)?' +MSG_INPUT_COPY_DATA: str = 'Would you like to copy data to the new image?' +MSG_INPUT_CHOOSE_COPY_DATA: str = 'From which image would you like to save config information?' +MSG_INPUT_COPY_ENC_DATA: str = 'Would you like to copy the encrypted config to the new image?' +MSG_INPUT_CHOOSE_COPY_ENC_DATA: str = 'From which image would you like to copy the encrypted config?' +MSG_WARN_ISO_SIGN_INVALID: str = 'Signature is not valid. Do you want to continue with installation?' +MSG_WARN_ISO_SIGN_UNAVAL: str = 'Signature is not available. Do you want to continue with installation?' +MSG_WARN_ROOT_SIZE_TOOBIG: str = 'The size is too big. Try again.' +MSG_WARN_ROOT_SIZE_TOOSMALL: str = 'The size is too small. Try again' +MSG_WARN_IMAGE_NAME_WRONG: str = 'The suggested name is unsupported!\n'\ +'It must be between 1 and 64 characters long and contains only the next characters: .+-_ a-z A-Z 0-9' +MSG_WARN_PASSWORD_CONFIRM: str = 'The entered values did not match. Try again' +MSG_WARN_FLAVOR_MISMATCH: str = 'The running image flavor is "{0}". The new image flavor is "{1}".\n' \ +'Installing a different image flavor may cause functionality degradation or break your system.\n' \ +'Do you want to continue with installation?' +CONST_MIN_DISK_SIZE: int = 2147483648 # 2 GB +CONST_MIN_ROOT_SIZE: int = 1610612736 # 1.5 GB +# a reserved space: 2MB for header, 1 MB for BIOS partition, 256 MB for EFI +CONST_RESERVED_SPACE: int = (2 + 1 + 256) * 1024**2 + +# define directories and paths +DIR_INSTALLATION: str = '/mnt/installation' +DIR_ROOTFS_SRC: str = f'{DIR_INSTALLATION}/root_src' +DIR_ROOTFS_DST: str = f'{DIR_INSTALLATION}/root_dst' +DIR_ISO_MOUNT: str = f'{DIR_INSTALLATION}/iso_src' +DIR_DST_ROOT: str = f'{DIR_INSTALLATION}/disk_dst' +DIR_KERNEL_SRC: str = '/boot/' +FILE_ROOTFS_SRC: str = '/usr/lib/live/mount/medium/live/filesystem.squashfs' +ISO_DOWNLOAD_PATH: str = '/tmp/vyos_installation.iso' + +external_download_script = '/usr/libexec/vyos/simple-download.py' + +# default boot variables +DEFAULT_BOOT_VARS: dict[str, str] = { + 'timeout': '5', + 'console_type': 'tty', + 'console_num': '0', + 'console_speed': '115200', + 'bootmode': 'normal' +} + + +def bytes_to_gb(size: int) -> float: + """Convert Bytes to GBytes, rounded to 1 decimal number + + Args: + size (int): input size in bytes + + Returns: + float: size in GB + """ + return round(size / 1024**3, 1) + + +def gb_to_bytes(size: float) -> int: + """Convert GBytes to Bytes + + Args: + size (float): input size in GBytes + + Returns: + int: size in bytes + """ + return int(size * 1024**3) + + +def find_disks() -> dict[str, int]: + """Find a target disk for installation + + Returns: + dict[str, int]: a list of available disks by name and size + """ + # check for available disks + print('Probing disks') + disks_available: dict[str, int] = disk.disks_size() + for disk_name, disk_size in disks_available.copy().items(): + if disk_size < CONST_MIN_DISK_SIZE: + del disks_available[disk_name] + if not disks_available: + print(MSG_ERR_NO_DISK) + exit(MSG_INFO_INSTALL_EXIT) + + num_disks: int = len(disks_available) + print(f'{num_disks} disk(s) found') + + return disks_available + + +def ask_root_size(available_space: int) -> int: + """Define a size of root partition + + Args: + available_space (int): available space in bytes for a root partition + + Returns: + int: defined size + """ + if ask_yes_no(MSG_INPUT_ROOT_SIZE_ALL, default=True): + return available_space + + while True: + root_size_gb: str = ask_input(MSG_INPUT_ROOT_SIZE_SET) + root_size_kbytes: int = (gb_to_bytes(float(root_size_gb))) // 1024 + + if root_size_kbytes > available_space: + print(MSG_WARN_ROOT_SIZE_TOOBIG) + continue + if root_size_kbytes < CONST_MIN_ROOT_SIZE / 1024: + print(MSG_WARN_ROOT_SIZE_TOOSMALL) + continue + + return root_size_kbytes + +def create_partitions(target_disk: str, target_size: int, + prompt: bool = True) -> None: + """Create partitions on a target disk + + Args: + target_disk (str): a target disk + target_size (int): size of disk in bytes + """ + # define target rootfs size in KB (smallest unit acceptable by sgdisk) + available_size: int = (target_size - CONST_RESERVED_SPACE) // 1024 + if prompt: + rootfs_size: int = ask_root_size(available_size) + else: + rootfs_size: int = available_size + + print(MSG_INFO_INSTALL_PARTITONING) + raid.clear() + disk.disk_cleanup(target_disk) + disk_details: disk.DiskDetails = disk.parttable_create(target_disk, + rootfs_size) + + return disk_details + + +def search_format_selection(image: tuple[str, str]) -> str: + """Format a string for selection of image + + Args: + image (tuple[str, str]): a tuple of image name and drive + + Returns: + str: formatted string + """ + return f'{image[0]} on {image[1]}' + + +def search_previous_installation(disks: list[str]) -> None: + """Search disks for previous installation config and SSH keys + + Args: + disks (list[str]): a list of available disks + """ + mnt_config = '/mnt/config' + mnt_encrypted_config = '/mnt/encrypted_config' + mnt_ssh = '/mnt/ssh' + mnt_tmp = '/mnt/tmp' + rmtree(Path(mnt_config), ignore_errors=True) + rmtree(Path(mnt_ssh), ignore_errors=True) + Path(mnt_tmp).mkdir(exist_ok=True) + Path(mnt_encrypted_config).unlink(missing_ok=True) + + print('Searching for data from previous installations') + image_data = [] + encrypted_configs = [] + for disk_name in disks: + for partition in disk.partition_list(disk_name): + if disk.partition_mount(partition, mnt_tmp): + if Path(mnt_tmp + '/boot').exists(): + for path in Path(mnt_tmp + '/boot').iterdir(): + if path.joinpath('rw/config/.vyatta_config').exists(): + image_data.append((path.name, partition)) + if Path(mnt_tmp + '/luks').exists(): + for path in Path(mnt_tmp + '/luks').iterdir(): + encrypted_configs.append((path.name, partition)) + + disk.partition_umount(partition) + + image_name = None + image_drive = None + encrypted = False + + if len(image_data) > 0: + if len(image_data) == 1: + print('Found data from previous installation:') + print(f'\t{" on ".join(image_data[0])}') + if ask_yes_no(MSG_INPUT_COPY_DATA, default=True): + image_name, image_drive = image_data[0] + + elif len(image_data) > 1: + print('Found data from previous installations') + if ask_yes_no(MSG_INPUT_COPY_DATA, default=True): + image_name, image_drive = select_entry(image_data, + 'Available versions:', + MSG_INPUT_CHOOSE_COPY_DATA, + search_format_selection) + elif len(encrypted_configs) > 0: + if len(encrypted_configs) == 1: + print('Found encrypted config from previous installation:') + print(f'\t{" on ".join(encrypted_configs[0])}') + if ask_yes_no(MSG_INPUT_COPY_ENC_DATA, default=True): + image_name, image_drive = encrypted_configs[0] + encrypted = True + + elif len(encrypted_configs) > 1: + print('Found encrypted configs from previous installations') + if ask_yes_no(MSG_INPUT_COPY_ENC_DATA, default=True): + image_name, image_drive = select_entry(encrypted_configs, + 'Available versions:', + MSG_INPUT_CHOOSE_COPY_ENC_DATA, + search_format_selection) + encrypted = True + + else: + print('No previous installation found') + return + + if not image_name: + return + + disk.partition_mount(image_drive, mnt_tmp) + + if not encrypted: + copytree(f'{mnt_tmp}/boot/{image_name}/rw/config', mnt_config) + else: + copy(f'{mnt_tmp}/luks/{image_name}', mnt_encrypted_config) + + Path(mnt_ssh).mkdir() + host_keys: list[str] = glob(f'{mnt_tmp}/boot/{image_name}/rw/etc/ssh/ssh_host*') + for host_key in host_keys: + copy(host_key, mnt_ssh) + + disk.partition_umount(image_drive) + +def copy_preserve_owner(src: str, dst: str, *, follow_symlinks=True): + if not Path(src).is_file(): + return + if Path(dst).is_dir(): + dst = Path(dst).joinpath(Path(src).name) + st = Path(src).stat() + copy(src, dst, follow_symlinks=follow_symlinks) + chown(dst, user=st.st_uid) + + +def copy_previous_installation_data(target_dir: str) -> None: + if Path('/mnt/config').exists(): + copytree('/mnt/config', f'{target_dir}/opt/vyatta/etc/config', + dirs_exist_ok=True) + if Path('/mnt/ssh').exists(): + copytree('/mnt/ssh', f'{target_dir}/etc/ssh', + dirs_exist_ok=True) + + +def copy_previous_encrypted_config(target_dir: str, image_name: str) -> None: + if Path('/mnt/encrypted_config').exists(): + Path(target_dir).mkdir(exist_ok=True) + copy('/mnt/encrypted_config', Path(target_dir).joinpath(image_name)) + + +def ask_single_disk(disks_available: dict[str, int]) -> str: + """Ask user to select a disk for installation + + Args: + disks_available (dict[str, int]): a list of available disks + """ + print(MSG_INFO_INSTALL_DISKS_LIST) + default_disk: str = list(disks_available)[0] + for disk_name, disk_size in disks_available.items(): + disk_size_human: str = bytes_to_gb(disk_size) + print(f'Drive: {disk_name} ({disk_size_human} GB)') + disk_selected: str = ask_input(MSG_INFO_INSTALL_DISK_SELECT, + default=default_disk, + valid_responses=list(disks_available)) + + # create partitions + if not ask_yes_no(MSG_INFO_INSTALL_DISK_CONFIRM): + print(MSG_INFO_INSTALL_EXIT) + exit() + + search_previous_installation(list(disks_available)) + + disk_details: disk.DiskDetails = create_partitions(disk_selected, + disks_available[disk_selected]) + + disk.filesystem_create(disk_details.partition['efi'], 'efi') + disk.filesystem_create(disk_details.partition['root'], 'ext4') + + return disk_details + + +def check_raid_install(disks_available: dict[str, int]) -> Union[str, None]: + """Ask user to select disks for RAID installation + + Args: + disks_available (dict[str, int]): a list of available disks + """ + if len(disks_available) < 2: + return None + + if not ask_yes_no(MSG_INFO_INSTALL_RAID_CONFIGURE, default=True): + return None + + def format_selection(disk_name: str) -> str: + return f'{disk_name}\t({bytes_to_gb(disks_available[disk_name])} GB)' + + disk0, disk1 = list(disks_available)[0], list(disks_available)[1] + disks_selected: dict[str, int] = { disk0: disks_available[disk0], + disk1: disks_available[disk1] } + + target_size: int = min(disks_selected[disk0], disks_selected[disk1]) + + print(MSG_INFO_INSTALL_DISKS_LIST) + for disk_name, disk_size in disks_selected.items(): + disk_size_human: str = bytes_to_gb(disk_size) + print(f'\t{disk_name} ({disk_size_human} GB)') + if not ask_yes_no(MSG_INFO_INSTALL_RAID_FOUND_DISKS, default=True): + if not ask_yes_no(MSG_INFO_INSTALL_RAID_CHOOSE_DISKS, default=True): + return None + else: + disks_selected = {} + disk0 = select_entry(list(disks_available), 'Disks available:', + 'Select first disk:', format_selection) + + disks_selected[disk0] = disks_available[disk0] + del disks_available[disk0] + disk1 = select_entry(list(disks_available), 'Remaining disks:', + 'Select second disk:', format_selection) + disks_selected[disk1] = disks_available[disk1] + + target_size: int = min(disks_selected[disk0], + disks_selected[disk1]) + + # create partitions + if not ask_yes_no(MSG_INFO_INSTALL_RAID_CONFIRM): + print(MSG_INFO_INSTALL_EXIT) + exit() + + search_previous_installation(list(disks_available)) + + disks: list[disk.DiskDetails] = [] + for disk_selected in list(disks_selected): + print(f'Creating partitions on {disk_selected}') + disk_details = create_partitions(disk_selected, target_size, + prompt=False) + disk.filesystem_create(disk_details.partition['efi'], 'efi') + + disks.append(disk_details) + + print('Creating RAID array') + members = [disk.partition['root'] for disk in disks] + raid_details: raid.RaidDetails = raid.raid_create(members) + # raid init stuff + print('Updating initramfs') + raid.update_initramfs() + # end init + print('Creating filesystem on RAID array') + disk.filesystem_create(raid_details.name, 'ext4') + + return raid_details + + +def prepare_tmp_disr() -> None: + """Create temporary directories for installation + """ + print('Creating temporary directories') + for dir in [DIR_ROOTFS_SRC, DIR_ROOTFS_DST, DIR_DST_ROOT]: + dirpath = Path(dir) + dirpath.mkdir(mode=0o755, parents=True) + + +def setup_grub(root_dir: str) -> None: + """Install GRUB configurations + + Args: + root_dir (str): a path to the root of target filesystem + """ + print('Installing GRUB configuration files') + grub_cfg_main = f'{root_dir}/{grub.GRUB_DIR_MAIN}/grub.cfg' + grub_cfg_vars = f'{root_dir}/{grub.CFG_VYOS_VARS}' + grub_cfg_modules = f'{root_dir}/{grub.CFG_VYOS_MODULES}' + grub_cfg_menu = f'{root_dir}/{grub.CFG_VYOS_MENU}' + grub_cfg_options = f'{root_dir}/{grub.CFG_VYOS_OPTIONS}' + + # create new files + render(grub_cfg_main, grub.TMPL_GRUB_MAIN, {}) + grub.common_write(root_dir) + grub.vars_write(grub_cfg_vars, DEFAULT_BOOT_VARS) + grub.modules_write(grub_cfg_modules, []) + grub.write_cfg_ver(1, root_dir) + render(grub_cfg_menu, grub.TMPL_GRUB_MENU, {}) + render(grub_cfg_options, grub.TMPL_GRUB_OPTS, {}) + + +def configure_authentication(config_file: str, password: str) -> None: + """Write encrypted password to config file + + Args: + config_file (str): path of target config file + password (str): plaintext password + + N.B. this can not be deferred by simply setting the plaintext password + and relying on the config mode script to process at boot, as the config + will not automatically be saved in that case, thus leaving the + plaintext exposed + """ + encrypted_password = linux_context.hash(password) + + with open(config_file) as f: + config_string = f.read() + + config = ConfigTree(config_string) + config.set([ + 'system', 'login', 'user', 'vyos', 'authentication', + 'encrypted-password' + ], + value=encrypted_password, + replace=True) + config.set_tag(['system', 'login', 'user']) + + with open(config_file, 'w') as f: + f.write(config.to_string()) + +def validate_signature(file_path: str, sign_type: str) -> None: + """Validate a file by signature and delete a signature file + + Args: + file_path (str): a path to file + sign_type (str): a signature type + """ + print('Validating signature') + signature_valid: bool = False + # validate with minisig + if sign_type == 'minisig': + pub_key_list = glob('/usr/share/vyos/keys/*.minisign.pub') + for pubkey in pub_key_list: + if run(f'minisign -V -q -p {pubkey} -m {file_path} -x {file_path}.minisig' + ) == 0: + signature_valid = True + break + Path(f'{file_path}.minisig').unlink() + # validate with GPG + if sign_type == 'asc': + if run(f'gpg --verify ${file_path}.asc ${file_path}') == 0: + signature_valid = True + Path(f'{file_path}.asc').unlink() + + # warn or pass + if not signature_valid: + if not ask_yes_no(MSG_WARN_ISO_SIGN_INVALID, default=False): + exit(MSG_INFO_INSTALL_EXIT) + else: + print('Signature is valid') + +def download_file(local_file: str, remote_path: str, vrf: str, + username: str, password: str, + progressbar: bool = False, check_space: bool = False): + environ['REMOTE_USERNAME'] = username + environ['REMOTE_PASSWORD'] = password + if vrf is None: + download(local_file, remote_path, progressbar=progressbar, + check_space=check_space, raise_error=True) + else: + vrf_cmd = f'REMOTE_USERNAME={username} REMOTE_PASSWORD={password} \ + ip vrf exec {vrf} {external_download_script} \ + --local-file {local_file} --remote-path {remote_path}' + cmd(vrf_cmd) + +def image_fetch(image_path: str, vrf: str = None, + username: str = '', password: str = '', + no_prompt: bool = False) -> Path: + """Fetch an ISO image + + Args: + image_path (str): a path, remote or local + + Returns: + Path: a path to a local file + """ + # Latest version gets url from configured "system update-check url" + if image_path == 'latest': + config = ConfigTreeQuery() + if config.exists('system update-check url'): + configured_url_version = config.value('system update-check url') + remote_url_list = get_remote_version(configured_url_version) + image_path = remote_url_list[0].get('url') + + try: + # check a type of path + if urlparse(image_path).scheme: + # download an image + download_file(ISO_DOWNLOAD_PATH, image_path, vrf, + username, password, + progressbar=True, check_space=True) + + # download a signature + sign_file = (False, '') + for sign_type in ['minisig', 'asc']: + try: + download_file(f'{ISO_DOWNLOAD_PATH}.{sign_type}', + f'{image_path}.{sign_type}', vrf, + username, password) + sign_file = (True, sign_type) + break + except Exception: + print(f'{sign_type} signature is not available') + # validate a signature if it is available + if sign_file[0]: + validate_signature(ISO_DOWNLOAD_PATH, sign_file[1]) + else: + if (not no_prompt and + not ask_yes_no(MSG_WARN_ISO_SIGN_UNAVAL, default=False)): + cleanup() + exit(MSG_INFO_INSTALL_EXIT) + + return Path(ISO_DOWNLOAD_PATH) + else: + local_path: Path = Path(image_path) + if local_path.is_file(): + return local_path + else: + raise FileNotFoundError + except Exception as e: + print(f'The image cannot be fetched from: {image_path} {e}') + exit(1) + + +def migrate_config() -> bool: + """Check for active config and ask user for migration + + Returns: + bool: user's decision + """ + active_config_path: Path = Path('/opt/vyatta/etc/config/config.boot') + if active_config_path.exists(): + if ask_yes_no(MSG_INPUT_CONFIG_FOUND, default=True): + return True + return False + + +def copy_ssh_host_keys() -> bool: + """Ask user to copy SSH host keys + + Returns: + bool: user's decision + """ + if ask_yes_no('Would you like to copy SSH host keys?', default=True): + return True + return False + + +def console_hint() -> str: + pid = getppid() if 'SUDO_USER' in environ else getpid() + try: + path = readlink(f'/proc/{pid}/fd/1') + except OSError: + path = '/dev/tty' + + name = Path(path).name + if name == 'ttyS0': + return 'S' + else: + return 'K' + + +def cleanup(mounts: list[str] = [], remove_items: list[str] = []) -> None: + """Clean up after installation + + Args: + mounts (list[str], optional): List of mounts to unmount. + Defaults to []. + remove_items (list[str], optional): List of files or directories + to remove. Defaults to []. + """ + print('Cleaning up') + # clean up installation directory by default + mounts_all = disk_partitions(all=True) + for mounted_device in mounts_all: + if mounted_device.mountpoint.startswith(DIR_INSTALLATION) and not ( + mounted_device.device in mounts or + mounted_device.mountpoint in mounts): + mounts.append(mounted_device.mountpoint) + # add installation dir to cleanup list + if DIR_INSTALLATION not in remove_items: + remove_items.append(DIR_INSTALLATION) + # also delete an ISO file + if Path(ISO_DOWNLOAD_PATH).exists( + ) and ISO_DOWNLOAD_PATH not in remove_items: + remove_items.append(ISO_DOWNLOAD_PATH) + + if mounts: + print('Unmounting target filesystems') + for mountpoint in mounts: + disk.partition_umount(mountpoint) + for mountpoint in mounts: + disk.wait_for_umount(mountpoint) + if remove_items: + print('Removing temporary files') + for remove_item in remove_items: + if Path(remove_item).exists(): + if Path(remove_item).is_file(): + Path(remove_item).unlink() + if Path(remove_item).is_dir(): + rmtree(remove_item, ignore_errors=True) + + +def cleanup_raid(details: raid.RaidDetails) -> None: + efiparts = [] + for raid_disk in details.disks: + efiparts.append(raid_disk.partition['efi']) + cleanup([details.name, *efiparts], + ['/mnt/installation']) + + +def is_raid_install(install_object: Union[disk.DiskDetails, raid.RaidDetails]) -> bool: + """Check if installation target is a RAID array + + Args: + install_object (Union[disk.DiskDetails, raid.RaidDetails]): a target disk + + Returns: + bool: True if it is a RAID array + """ + if isinstance(install_object, raid.RaidDetails): + return True + return False + + +def validate_compatibility(iso_path: str) -> None: + """Check architecture and flavor compatibility with the running image + + Args: + iso_path (str): a path to the mounted ISO image + """ + old_data = get_version_data() + old_flavor = old_data.get('flavor', '') + old_architecture = old_data.get('architecture') or cmd('dpkg --print-architecture') + + new_data = get_version_data(f'{iso_path}/version.json') + new_flavor = new_data.get('flavor', '') + new_architecture = new_data.get('architecture', '') + + if not old_architecture == new_architecture: + print(MSG_ERR_ARCHITECTURE_MISMATCH) + cleanup() + exit(MSG_INFO_INSTALL_EXIT) + + if not old_flavor == new_flavor: + if not ask_yes_no(MSG_WARN_FLAVOR_MISMATCH.format(old_flavor, new_flavor), default=False): + cleanup() + exit(MSG_INFO_INSTALL_EXIT) + + +def install_image() -> None: + """Install an image to a disk + """ + if not image.is_live_boot(): + exit(MSG_ERR_NOT_LIVE) + + print(MSG_INFO_INSTALL_WELCOME) + if not ask_yes_no('Would you like to continue?'): + print(MSG_INFO_INSTALL_EXIT) + exit() + + # configure image name + running_image_name: str = image.get_running_image() + while True: + image_name: str = ask_input(MSG_INPUT_IMAGE_NAME, + running_image_name) + if image.validate_name(image_name): + break + print(MSG_WARN_IMAGE_NAME_WRONG) + + # ask for password + while True: + user_password: str = ask_input(MSG_INPUT_PASSWORD, no_echo=True, + non_empty=True) + confirm: str = ask_input(MSG_INPUT_PASSWORD_CONFIRM, no_echo=True, + non_empty=True) + if user_password == confirm: + break + print(MSG_WARN_PASSWORD_CONFIRM) + + # ask for default console + console_type: str = ask_input(MSG_INPUT_CONSOLE_TYPE, + default=console_hint(), + valid_responses=['K', 'S']) + console_dict: dict[str, str] = {'K': 'tty', 'S': 'ttyS'} + + config_boot_list = ['/opt/vyatta/etc/config/config.boot', + '/opt/vyatta/etc/config.boot.default'] + default_config = config_boot_list[0] + + disks: dict[str, int] = find_disks() + + install_target: Union[disk.DiskDetails, raid.RaidDetails, None] = None + try: + install_target = check_raid_install(disks) + if install_target is None: + install_target = ask_single_disk(disks) + + # if previous install was selected in search_previous_installation, + # directory /mnt/config was prepared for copy below; if not, prompt: + if not Path('/mnt/config').exists(): + default_config: str = select_entry(config_boot_list, + MSG_INPUT_CONFIG_CHOICE, + MSG_INPUT_CONFIG_CHOOSE, + default_entry=1) # select_entry indexes from 1 + + # create directories for installation media + prepare_tmp_disr() + + # mount target filesystem and create required dirs inside + print('Mounting new partitions') + if is_raid_install(install_target): + disk.partition_mount(install_target.name, DIR_DST_ROOT) + Path(f'{DIR_DST_ROOT}/boot/efi').mkdir(parents=True) + else: + disk.partition_mount(install_target.partition['root'], DIR_DST_ROOT) + Path(f'{DIR_DST_ROOT}/boot/efi').mkdir(parents=True) + disk.partition_mount(install_target.partition['efi'], f'{DIR_DST_ROOT}/boot/efi') + + # a config dir. It is the deepest one, so the comand will + # create all the rest in a single step + print('Creating a configuration file') + target_config_dir: str = f'{DIR_DST_ROOT}/boot/{image_name}/rw/opt/vyatta/etc/config/' + Path(target_config_dir).mkdir(parents=True) + chown(target_config_dir, group='vyattacfg') + chmod_2775(target_config_dir) + # copy config + copy(default_config, f'{target_config_dir}/config.boot') + configure_authentication(f'{target_config_dir}/config.boot', + user_password) + Path(f'{target_config_dir}/.vyatta_config').touch() + + # create a persistence.conf + Path(f'{DIR_DST_ROOT}/persistence.conf').write_text('/ union\n') + + # copy system image and kernel files + print('Copying system image files') + for file in Path(DIR_KERNEL_SRC).iterdir(): + if file.is_file(): + copy(file, f'{DIR_DST_ROOT}/boot/{image_name}/') + copy(FILE_ROOTFS_SRC, + f'{DIR_DST_ROOT}/boot/{image_name}/{image_name}.squashfs') + + # copy saved config data and SSH keys + # owner restored on copy of config data by chmod_2775, above + copy_previous_installation_data(f'{DIR_DST_ROOT}/boot/{image_name}/rw') + + # copy saved encrypted config volume + copy_previous_encrypted_config(f'{DIR_DST_ROOT}/luks', image_name) + + if is_raid_install(install_target): + write_dir: str = f'{DIR_DST_ROOT}/boot/{image_name}/rw' + raid.update_default(write_dir) + + setup_grub(DIR_DST_ROOT) + # add information about version + grub.create_structure() + grub.version_add(image_name, DIR_DST_ROOT) + grub.set_default(image_name, DIR_DST_ROOT) + grub.set_console_type(console_dict[console_type], DIR_DST_ROOT) + + if is_raid_install(install_target): + # add RAID specific modules + grub.modules_write(f'{DIR_DST_ROOT}/{grub.CFG_VYOS_MODULES}', + ['part_msdos', 'part_gpt', 'diskfilter', + 'ext2','mdraid1x']) + # install GRUB + if is_raid_install(install_target): + print('Installing GRUB to the drives') + l = install_target.disks + for disk_target in l: + disk.partition_mount(disk_target.partition['efi'], f'{DIR_DST_ROOT}/boot/efi') + grub.install(disk_target.name, f'{DIR_DST_ROOT}/boot/', + f'{DIR_DST_ROOT}/boot/efi', + id=f'VyOS (RAID disk {l.index(disk_target) + 1})') + disk.partition_umount(disk_target.partition['efi']) + else: + print('Installing GRUB to the drive') + grub.install(install_target.name, f'{DIR_DST_ROOT}/boot/', + f'{DIR_DST_ROOT}/boot/efi') + + # sort inodes (to make GRUB read config files in alphabetical order) + grub.sort_inodes(f'{DIR_DST_ROOT}/{grub.GRUB_DIR_VYOS}') + grub.sort_inodes(f'{DIR_DST_ROOT}/{grub.GRUB_DIR_VYOS_VERS}') + + # umount filesystems and remove temporary files + if is_raid_install(install_target): + cleanup([install_target.name], + ['/mnt/installation']) + else: + cleanup([install_target.partition['efi'], + install_target.partition['root']], + ['/mnt/installation']) + + # we are done + print(MSG_INFO_INSTALL_SUCCESS) + exit() + + except Exception as err: + print(f'Unable to install VyOS: {err}') + # unmount filesystems and clenup + try: + if install_target is not None: + if is_raid_install(install_target): + cleanup_raid(install_target) + else: + cleanup([install_target.partition['efi'], + install_target.partition['root']], + ['/mnt/installation']) + except Exception as err: + print(f'Cleanup failed: {err}') + + exit(1) + + +@compat.grub_cfg_update +def add_image(image_path: str, vrf: str = None, username: str = '', + password: str = '', no_prompt: bool = False) -> None: + """Add a new image + + Args: + image_path (str): a path to an ISO image + """ + if image.is_live_boot(): + exit(MSG_ERR_LIVE) + + # fetch an image + iso_path: Path = image_fetch(image_path, vrf, username, password, no_prompt) + try: + # mount an ISO + Path(DIR_ISO_MOUNT).mkdir(mode=0o755, parents=True) + disk.partition_mount(iso_path, DIR_ISO_MOUNT, 'iso9660') + + print('Validating image compatibility') + validate_compatibility(DIR_ISO_MOUNT) + + # check sums + print('Validating image checksums') + if not Path(DIR_ISO_MOUNT).joinpath('sha256sum.txt').exists(): + cleanup() + exit(MSG_ERR_IMPROPER_IMAGE) + if run(f'cd {DIR_ISO_MOUNT} && sha256sum --status -c sha256sum.txt'): + cleanup() + exit('Image checksum verification failed.') + + # mount rootfs (to get a system version) + Path(DIR_ROOTFS_SRC).mkdir(mode=0o755, parents=True) + disk.partition_mount(f'{DIR_ISO_MOUNT}/live/filesystem.squashfs', + DIR_ROOTFS_SRC, 'squashfs') + + cfg_ver: str = image.get_image_tools_version(DIR_ROOTFS_SRC) + version_name: str = image.get_image_version(DIR_ROOTFS_SRC) + + disk.partition_umount(f'{DIR_ISO_MOUNT}/live/filesystem.squashfs') + + if cfg_ver < SYSTEM_CFG_VER: + raise compat.DowngradingImageTools( + f'Adding image would downgrade image tools to v.{cfg_ver}; disallowed') + + if not no_prompt: + while True: + image_name: str = ask_input(MSG_INPUT_IMAGE_NAME, version_name) + if image.validate_name(image_name): + break + print(MSG_WARN_IMAGE_NAME_WRONG) + set_as_default: bool = ask_yes_no(MSG_INPUT_IMAGE_DEFAULT, default=True) + else: + image_name: str = version_name + set_as_default: bool = True + + # find target directory + root_dir: str = disk.find_persistence() + + # a config dir. It is the deepest one, so the comand will + # create all the rest in a single step + target_config_dir: str = f'{root_dir}/boot/{image_name}/rw/opt/vyatta/etc/config/' + # copy config + if no_prompt or migrate_config(): + print('Copying configuration directory') + # copytree preserves perms but not ownership: + Path(target_config_dir).mkdir(parents=True) + chown(target_config_dir, group='vyattacfg') + chmod_2775(target_config_dir) + copytree('/opt/vyatta/etc/config/', target_config_dir, + copy_function=copy_preserve_owner, dirs_exist_ok=True) + else: + Path(target_config_dir).mkdir(parents=True) + chown(target_config_dir, group='vyattacfg') + chmod_2775(target_config_dir) + Path(f'{target_config_dir}/.vyatta_config').touch() + + target_ssh_dir: str = f'{root_dir}/boot/{image_name}/rw/etc/ssh/' + if no_prompt or copy_ssh_host_keys(): + print('Copying SSH host keys') + Path(target_ssh_dir).mkdir(parents=True) + host_keys: list[str] = glob('/etc/ssh/ssh_host*') + for host_key in host_keys: + copy(host_key, target_ssh_dir) + + # copy system image and kernel files + print('Copying system image files') + for file in Path(f'{DIR_ISO_MOUNT}/live').iterdir(): + if file.is_file() and (file.match('initrd*') or + file.match('vmlinuz*')): + copy(file, f'{root_dir}/boot/{image_name}/') + copy(f'{DIR_ISO_MOUNT}/live/filesystem.squashfs', + f'{root_dir}/boot/{image_name}/{image_name}.squashfs') + + # unmount an ISO and cleanup + cleanup([str(iso_path)]) + + # add information about version + grub.version_add(image_name, root_dir) + if set_as_default: + grub.set_default(image_name, root_dir) + + except OSError as e: + # if no space error, remove image dir and cleanup + if e.errno == ENOSPC: + cleanup(mounts=[str(iso_path)], + remove_items=[f'{root_dir}/boot/{image_name}']) + else: + # unmount an ISO and cleanup + cleanup([str(iso_path)]) + exit(f'Error: {e}') + + except Exception as err: + # unmount an ISO and cleanup + cleanup([str(iso_path)]) + exit(f'Error: {err}') + + +def parse_arguments() -> Namespace: + """Parse arguments + + Returns: + Namespace: a namespace with parsed arguments + """ + parser: ArgumentParser = ArgumentParser( + description='Install new system images') + parser.add_argument('--action', + choices=['install', 'add'], + required=True, + help='action to perform with an image') + parser.add_argument('--vrf', + help='vrf name for image download') + parser.add_argument('--no-prompt', action='store_true', + help='perform action non-interactively') + parser.add_argument('--username', default='', + help='username for image download') + parser.add_argument('--password', default='', + help='password for image download') + parser.add_argument('--image-path', + help='a path (HTTP or local file) to an image that needs to be installed' + ) + # parser.add_argument('--image_new_name', help='a new name for image') + args: Namespace = parser.parse_args() + # Validate arguments + if args.action == 'add' and not args.image_path: + exit('A path to image is required for add action') + + return args + + +if __name__ == '__main__': + try: + args: Namespace = parse_arguments() + if args.action == 'install': + install_image() + if args.action == 'add': + add_image(args.image_path, args.vrf, + args.username, args.password, args.no_prompt) + + exit() + + except KeyboardInterrupt: + print('Stopped by Ctrl+C') + cleanup() + exit() + + except Exception as err: + exit(f'{err}') diff --git a/src/op_mode/image_manager.py b/src/op_mode/image_manager.py new file mode 100644 index 0000000..fb4286d --- /dev/null +++ b/src/op_mode/image_manager.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 +# +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This file is part of VyOS. +# +# VyOS is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# VyOS is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# VyOS. If not, see <https://www.gnu.org/licenses/>. + +from argparse import ArgumentParser, Namespace +from pathlib import Path +from shutil import rmtree +from sys import exit +from typing import Optional, Literal, TypeAlias, get_args + +from vyos.system import disk, grub, image, compat +from vyos.utils.io import ask_yes_no, select_entry + +SET_IMAGE_LIST_MSG: str = 'The following images are available:' +SET_IMAGE_PROMPT_MSG: str = 'Select an image to set as default:' +DELETE_IMAGE_LIST_MSG: str = 'The following images are installed:' +DELETE_IMAGE_PROMPT_MSG: str = 'Select an image to delete:' +MSG_DELETE_IMAGE_RUNNING: str = 'Currently running image cannot be deleted; reboot into another image first' +MSG_DELETE_IMAGE_DEFAULT: str = 'Default image cannot be deleted; set another image as default first' + +ConsoleType: TypeAlias = Literal['tty', 'ttyS'] + +def annotate_list(images_list: list[str]) -> list[str]: + """Annotate list of images with additional info + + Args: + images_list (list[str]): a list of image names + + Returns: + dict[str, str]: a dict of annotations indexed by image name + """ + running = image.get_running_image() + default = image.get_default_image() + annotated = {} + for image_name in images_list: + annotated[image_name] = f'{image_name}' + if running in images_list: + annotated[running] = annotated[running] + ' (running)' + if default in images_list: + annotated[default] = annotated[default] + ' (default boot)' + return annotated + +def define_format(images): + annotated = annotate_list(images) + def format_selection(image_name): + return annotated[image_name] + return format_selection + +@compat.grub_cfg_update +def delete_image(image_name: Optional[str] = None, + no_prompt: bool = False) -> None: + """Remove installed image files and boot entry + + Args: + image_name (str): a name of image to delete + """ + available_images: list[str] = grub.version_list() + format_selection = define_format(available_images) + if image_name is None: + if no_prompt: + exit('An image name is required for delete action') + else: + image_name = select_entry(available_images, + DELETE_IMAGE_LIST_MSG, + DELETE_IMAGE_PROMPT_MSG, + format_selection) + if image_name == image.get_running_image(): + exit(MSG_DELETE_IMAGE_RUNNING) + if image_name == image.get_default_image(): + exit(MSG_DELETE_IMAGE_DEFAULT) + if image_name not in available_images: + exit(f'The image "{image_name}" cannot be found') + persistence_storage: str = disk.find_persistence() + if not persistence_storage: + exit('Persistence storage cannot be found') + + if (not no_prompt and + not ask_yes_no(f'Do you really want to delete the image {image_name}?', + default=False)): + exit() + + # remove files and menu entry + version_path: Path = Path(f'{persistence_storage}/boot/{image_name}') + try: + rmtree(version_path) + grub.version_del(image_name, persistence_storage) + print(f'The image "{image_name}" was successfully deleted') + except Exception as err: + exit(f'Unable to remove the image "{image_name}": {err}') + + # remove LUKS volume if it exists + luks_path: Path = Path(f'{persistence_storage}/luks/{image_name}') + if luks_path.is_file(): + try: + luks_path.unlink() + print(f'The encrypted config for "{image_name}" was successfully deleted') + except Exception as err: + exit(f'Unable to remove the encrypted config for "{image_name}": {err}') + + +@compat.grub_cfg_update +def set_image(image_name: Optional[str] = None, + prompt: bool = True) -> None: + """Set default boot image + + Args: + image_name (str): an image name + """ + available_images: list[str] = grub.version_list() + format_selection = define_format(available_images) + if image_name is None: + if not prompt: + exit('An image name is required for set action') + else: + image_name = select_entry(available_images, + SET_IMAGE_LIST_MSG, + SET_IMAGE_PROMPT_MSG, + format_selection) + if image_name == image.get_default_image(): + exit(f'The image "{image_name}" already configured as default') + if image_name not in available_images: + exit(f'The image "{image_name}" cannot be found') + persistence_storage: str = disk.find_persistence() + if not persistence_storage: + exit('Persistence storage cannot be found') + + # set default boot image + try: + grub.set_default(image_name, persistence_storage) + print(f'The image "{image_name}" is now default boot image') + except Exception as err: + exit(f'Unable to set default image "{image_name}": {err}') + + +@compat.grub_cfg_update +def rename_image(name_old: str, name_new: str) -> None: + """Rename installed image + + Args: + name_old (str): old name + name_new (str): new name + """ + if name_old == image.get_running_image(): + exit('Currently running image cannot be renamed') + available_images: list[str] = grub.version_list() + if name_old not in available_images: + exit(f'The image "{name_old}" cannot be found') + if name_new in available_images: + exit(f'The image "{name_new}" already exists') + if not image.validate_name(name_new): + exit(f'The image name "{name_new}" is not allowed') + + persistence_storage: str = disk.find_persistence() + if not persistence_storage: + exit('Persistence storage cannot be found') + + if not ask_yes_no( + f'Do you really want to rename the image {name_old} ' + f'to the {name_new}?', + default=False): + exit() + + try: + # replace default boot item + if name_old == image.get_default_image(): + grub.set_default(name_new, persistence_storage) + + # rename files and dirs + old_path: Path = Path(f'{persistence_storage}/boot/{name_old}') + new_path: Path = Path(f'{persistence_storage}/boot/{name_new}') + old_path.rename(new_path) + + # replace boot item + grub.version_del(name_old, persistence_storage) + grub.version_add(name_new, persistence_storage) + + print(f'The image "{name_old}" was renamed to "{name_new}"') + except Exception as err: + exit(f'Unable to rename image "{name_old}" to "{name_new}": {err}') + + # rename LUKS volume if it exists + old_luks_path: Path = Path(f'{persistence_storage}/luks/{name_old}') + if old_luks_path.is_file(): + try: + new_luks_path: Path = Path(f'{persistence_storage}/luks/{name_new}') + old_luks_path.rename(new_luks_path) + print(f'The encrypted config for "{name_old}" was successfully renamed to "{name_new}"') + except Exception as err: + exit(f'Unable to rename the encrypted config for "{name_old}" to "{name_new}": {err}') + + +@compat.grub_cfg_update +def set_console_type(console_type: ConsoleType) -> None: + console_choice = get_args(ConsoleType) + if console_type not in console_choice: + exit(f'console type \'{console_type}\' not available') + + grub.set_console_type(console_type) + + +def list_images() -> None: + """Print list of available images for CLI hints""" + images_list: list[str] = grub.version_list() + for image_name in images_list: + print(image_name) + + +def list_console_types() -> None: + """Print list of console types for CLI hints""" + console_types: list[str] = list(get_args(ConsoleType)) + for console_type in console_types: + print(console_type) + + +def parse_arguments() -> Namespace: + """Parse arguments + + Returns: + Namespace: a namespace with parsed arguments + """ + parser: ArgumentParser = ArgumentParser(description='Manage system images') + parser.add_argument('--action', + choices=['delete', 'set', 'set_console_type', + 'rename', 'list', 'list_console_types'], + required=True, + help='action to perform with an image') + parser.add_argument('--no-prompt', action='store_true', + help='perform action non-interactively') + parser.add_argument( + '--image-name', + help= + 'a name of an image to add, delete, install, rename, or set as default') + parser.add_argument('--image-new-name', help='a new name for image') + parser.add_argument('--console-type', help='console type for boot') + args: Namespace = parser.parse_args() + # Validate arguments + if args.action == 'rename' and (not args.image_name or + not args.image_new_name): + exit('Both old and new image names are required for rename action') + + return args + + +if __name__ == '__main__': + try: + args: Namespace = parse_arguments() + if args.action == 'delete': + delete_image(args.image_name, args.no_prompt) + if args.action == 'set': + set_image(args.image_name) + if args.action == 'set_console_type': + set_console_type(args.console_type) + if args.action == 'rename': + rename_image(args.image_name, args.image_new_name) + if args.action == 'list': + list_images() + if args.action == 'list_console_types': + list_console_types() + + exit() + + except KeyboardInterrupt: + print('Stopped by Ctrl+C') + exit() + + except Exception as err: + exit(f'{err}') diff --git a/src/op_mode/interfaces.py b/src/op_mode/interfaces.py new file mode 100644 index 0000000..e7afc4c --- /dev/null +++ b/src/op_mode/interfaces.py @@ -0,0 +1,511 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import os +import re +import sys +import glob +import json +import typing +from datetime import datetime +from tabulate import tabulate + +import vyos.opmode +from vyos.ifconfig import Section +from vyos.ifconfig import Interface +from vyos.ifconfig import VRRP +from vyos.utils.process import cmd +from vyos.utils.process import rc_cmd +from vyos.utils.process import call + +def catch_broken_pipe(func): + def wrapped(*args, **kwargs): + try: + func(*args, **kwargs) + except (BrokenPipeError, KeyboardInterrupt): + # Flush output to /dev/null and bail out. + os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno()) + return wrapped + +# The original implementation of filtered_interfaces has signature: +# (ifnames: list, iftypes: typing.Union[str, list], vif: bool, vrrp: bool) -> intf: Interface: +# Arg types allowed in CLI (ifnames: str, iftypes: str) were manually +# re-typed from argparse args. +# We include the function in a general form, however op-mode standard +# functions will restrict to the CLI-allowed arg types, wrapped in Optional. +def filtered_interfaces(ifnames: typing.Union[str, list], + iftypes: typing.Union[str, list], + vif: bool, vrrp: bool) -> Interface: + """ + get all interfaces from the OS and return them; ifnames can be used to + filter which interfaces should be considered + + ifnames: a list of interface names to consider, empty do not filter + + return an instance of the Interface class + """ + if isinstance(ifnames, str): + ifnames = [ifnames] if ifnames else [] + if isinstance(iftypes, list): + for iftype in iftypes: + yield from filtered_interfaces(ifnames, iftype, vif, vrrp) + + for ifname in Section.interfaces(iftypes): + # Bail out early if interface name not part of our search list + if ifnames and ifname not in ifnames: + continue + + # As we are only "reading" from the interface - we must use the + # generic base class which exposes all the data via a common API + interface = Interface(ifname, create=False, debug=False) + + # VLAN interfaces have a '.' in their name by convention + if vif and not '.' in ifname: + continue + + if vrrp: + vrrp_interfaces = VRRP.active_interfaces() + if ifname not in vrrp_interfaces: + continue + + yield interface + +def _split_text(text, used=0): + """ + take a string and attempt to split it to fit with the width of the screen + + text: the string to split + used: number of characted already used in the screen + """ + no_tty = call('tty -s') + + returned = cmd('stty size') if not no_tty else '' + returned = returned.split() + if len(returned) == 2: + _, columns = tuple(int(_) for _ in returned) + else: + _, columns = (40, 80) + + desc_len = columns - used + + line = '' + for word in text.split(): + if len(line) + len(word) < desc_len: + line = f'{line} {word}' + continue + if line: + yield line[1:] + else: + line = f'{line} {word}' + + yield line[1:] + +def _get_counter_val(prev, now): + """ + attempt to correct a counter if it wrapped, copied from perl + + prev: previous counter + now: the current counter + """ + # This function has to deal with both 32 and 64 bit counters + if prev == 0: + return now + + # device is using 64 bit values assume they never wrap + value = now - prev + if (now >> 32) != 0: + return value + + # The counter has rolled. If the counter has rolled + # multiple times since the prev value, then this math + # is meaningless. + if value < 0: + value = (4294967296 - prev) + now + + return value + +def _pppoe(ifname): + out = cmd('ps -C pppd -f') + if ifname in out: + return 'C' + if ifname in [_.split('/')[-1] for _ in glob.glob('/etc/ppp/peers/pppoe*')]: + return 'D' + return '' + +def _find_intf_by_ifname(intf_l: list, name: str): + for d in intf_l: + if d['ifname'] == name: + return d + return {} + +# lifted out of operational.py to separate formatting from data +def _format_stats(stats, indent=4): + stat_names = { + 'rx': ['bytes', 'packets', 'errors', 'dropped', 'overrun', 'mcast'], + 'tx': ['bytes', 'packets', 'errors', 'dropped', 'carrier', 'collisions'], + } + + stats_dir = { + 'rx': ['rx_bytes', 'rx_packets', 'rx_errors', 'rx_dropped', 'rx_over_errors', 'multicast'], + 'tx': ['tx_bytes', 'tx_packets', 'tx_errors', 'tx_dropped', 'tx_carrier_errors', 'collisions'], + } + tabs = [] + for rtx in list(stats_dir): + tabs.append([f'{rtx.upper()}:', ] + stat_names[rtx]) + tabs.append(['', ] + [stats[_] for _ in stats_dir[rtx]]) + + s = tabulate( + tabs, + stralign="right", + numalign="right", + tablefmt="plain" + ) + + p = ' '*indent + return f'{p}' + s.replace('\n', f'\n{p}') + +def _get_raw_data(ifname: typing.Optional[str], + iftype: typing.Optional[str], + vif: bool, vrrp: bool) -> list: + if ifname is None: + ifname = '' + if iftype is None: + iftype = '' + ret =[] + for interface in filtered_interfaces(ifname, iftype, vif, vrrp): + res_intf = {} + cache = interface.operational.load_counters() + + out = cmd(f'ip -json addr show {interface.ifname}') + res_intf_l = json.loads(out) + res_intf = res_intf_l[0] + + if res_intf['link_type'] == 'tunnel6': + # Note that 'ip -6 tun show {interface.ifname}' is not json + # aware, so find in list + out = cmd('ip -json -6 tun show') + tunnel = json.loads(out) + res_intf['tunnel6'] = _find_intf_by_ifname(tunnel, + interface.ifname) + if 'ip6_tnl_f_use_orig_tclass' in res_intf['tunnel6']: + res_intf['tunnel6']['tclass'] = 'inherit' + del res_intf['tunnel6']['ip6_tnl_f_use_orig_tclass'] + + res_intf['counters_last_clear'] = int(cache.get('timestamp', 0)) + + res_intf['description'] = interface.get_alias() + + stats = interface.operational.get_stats() + for k in list(stats): + stats[k] = _get_counter_val(cache[k], stats[k]) + + res_intf['stats'] = stats + + ret.append(res_intf) + + # find pppoe interfaces that are in a transitional/dead state + if ifname.startswith('pppoe') and not _find_intf_by_ifname(ret, ifname): + pppoe_intf = {} + pppoe_intf['unhandled'] = None + pppoe_intf['ifname'] = ifname + pppoe_intf['state'] = _pppoe(ifname) + ret.append(pppoe_intf) + + return ret + +def _get_summary_data(ifname: typing.Optional[str], + iftype: typing.Optional[str], + vif: bool, vrrp: bool) -> list: + if ifname is None: + ifname = '' + if iftype is None: + iftype = '' + ret = [] + + def is_interface_has_mac(interface_name): + interface_no_mac = ('tun', 'wg') + return not any(interface_name.startswith(prefix) for prefix in interface_no_mac) + + for interface in filtered_interfaces(ifname, iftype, vif, vrrp): + res_intf = {} + + res_intf['ifname'] = interface.ifname + res_intf['oper_state'] = interface.operational.get_state() + res_intf['admin_state'] = interface.get_admin_state() + res_intf['addr'] = [_ for _ in interface.get_addr() if not _.startswith('fe80::')] + res_intf['description'] = interface.get_alias() + res_intf['mtu'] = interface.get_mtu() + res_intf['mac'] = interface.get_mac() if is_interface_has_mac(interface.ifname) else 'n/a' + res_intf['vrf'] = interface.get_vrf() + + ret.append(res_intf) + + # find pppoe interfaces that are in a transitional/dead state + if ifname.startswith('pppoe') and not _find_intf_by_ifname(ret, ifname): + pppoe_intf = {} + pppoe_intf['unhandled'] = None + pppoe_intf['ifname'] = ifname + pppoe_intf['state'] = _pppoe(ifname) + ret.append(pppoe_intf) + + return ret + +def _get_counter_data(ifname: typing.Optional[str], + iftype: typing.Optional[str], + vif: bool, vrrp: bool) -> list: + if ifname is None: + ifname = '' + if iftype is None: + iftype = '' + ret = [] + for interface in filtered_interfaces(ifname, iftype, vif, vrrp): + res_intf = {} + + oper = interface.operational.get_state() + + if oper not in ('up','unknown'): + continue + + stats = interface.operational.get_stats() + cache = interface.operational.load_counters() + res_intf['ifname'] = interface.ifname + res_intf['rx_packets'] = _get_counter_val(cache['rx_packets'], stats['rx_packets']) + res_intf['rx_bytes'] = _get_counter_val(cache['rx_bytes'], stats['rx_bytes']) + res_intf['tx_packets'] = _get_counter_val(cache['tx_packets'], stats['tx_packets']) + res_intf['tx_bytes'] = _get_counter_val(cache['tx_bytes'], stats['tx_bytes']) + res_intf['rx_dropped'] = _get_counter_val(cache['rx_dropped'], stats['rx_dropped']) + res_intf['tx_dropped'] = _get_counter_val(cache['tx_dropped'], stats['tx_dropped']) + res_intf['rx_over_errors'] = _get_counter_val(cache['rx_over_errors'], stats['rx_over_errors']) + res_intf['tx_carrier_errors'] = _get_counter_val(cache['tx_carrier_errors'], stats['tx_carrier_errors']) + + ret.append(res_intf) + + return ret + +@catch_broken_pipe +def _format_show_data(data: list): + unhandled = [] + for intf in data: + if 'unhandled' in intf: + unhandled.append(intf) + continue + # instead of reformatting data, call non-json output: + rc, out = rc_cmd(f"ip addr show {intf['ifname']}") + if rc != 0: + continue + out = re.sub('^\d+:\s+','',out) + # add additional data already collected + if 'tunnel6' in intf: + t6_d = intf['tunnel6'] + t6_str = 'encaplimit %s hoplimit %s tclass %s flowlabel %s (flowinfo %s)' % ( + t6_d.get('encap_limit', ''), t6_d.get('hoplimit', ''), + t6_d.get('tclass', ''), t6_d.get('flowlabel', ''), + t6_d.get('flowinfo', '')) + out = re.sub('(\n\s+)(link/tunnel6)', f'\g<1>{t6_str}\g<1>\g<2>', out) + print(out) + ts = intf.get('counters_last_clear', 0) + if ts: + when = datetime.fromtimestamp(ts).strftime("%a %b %d %R:%S %Z %Y") + print(f' Last clear: {when}') + description = intf.get('description', '') + if description: + print(f' Description: {description}') + + stats = intf.get('stats', {}) + if stats: + print() + print(_format_stats(stats)) + + for intf in unhandled: + string = { + 'C': 'Coming up', + 'D': 'Link down' + }[intf['state']] + print(f"{intf['ifname']}: {string}") + + return 0 + +@catch_broken_pipe +def _format_show_summary(data): + format1 = '%-16s %-33s %-4s %s' + format2 = '%-16s %s' + + print('Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down') + print(format1 % ("Interface", "IP Address", "S/L", "Description")) + print(format1 % ("---------", "----------", "---", "-----------")) + + unhandled = [] + for intf in data: + if 'unhandled' in intf: + unhandled.append(intf) + continue + ifname = [intf['ifname'],] + oper = ['u',] if intf['oper_state'] in ('up', 'unknown') else ['D',] + admin = ['u',] if intf['admin_state'] in ('up', 'unknown') else ['A',] + addrs = intf['addr'] or ['-',] + descs = list(_split_text(intf['description'], 0)) + + while ifname or oper or admin or addrs or descs: + i = ifname.pop(0) if ifname else '' + a = addrs.pop(0) if addrs else '' + d = descs.pop(0) if descs else '' + s = [admin.pop(0)] if admin else [] + l = [oper.pop(0)] if oper else [] + if len(a) < 33: + print(format1 % (i, a, '/'.join(s+l), d)) + else: + print(format2 % (i, a)) + print(format1 % ('', '', '/'.join(s+l), d)) + + for intf in unhandled: + string = { + 'C': 'u/D', + 'D': 'A/D' + }[intf['state']] + print(format1 % (ifname, '', string, '')) + + return 0 + +@catch_broken_pipe +def _format_show_summary_extended(data): + headers = ["Interface", "IP Address", "MAC", "VRF", "MTU", "S/L", "Description"] + table_data = [] + + print('Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down') + + for intf in data: + if 'unhandled' in intf: + continue + + ifname = intf['ifname'] + oper_state = 'u' if intf['oper_state'] in ('up', 'unknown') else 'D' + admin_state = 'u' if intf['admin_state'] in ('up', 'unknown') else 'A' + addrs = intf['addr'] or ['-'] + description = '\n'.join(_split_text(intf['description'], 0)) + mac = intf['mac'] if intf['mac'] else 'n/a' + mtu = intf['mtu'] if intf['mtu'] else 'n/a' + vrf = intf['vrf'] if intf['vrf'] else 'default' + + ip_addresses = '\n'.join(ip for ip in addrs) + + # Create a row for the table + row = [ + ifname, + ip_addresses, + mac, + vrf, + mtu, + f"{admin_state}/{oper_state}", + description, + ] + + # Append the row to the table data + table_data.append(row) + + for intf in data: + if 'unhandled' in intf: + string = {'C': 'u/D', 'D': 'A/D'}[intf['state']] + table_data.append([intf['ifname'], '', '', '', '', string, '']) + + print(tabulate(table_data, headers)) + + return 0 + +@catch_broken_pipe +def _format_show_counters(data: list): + data_entries = [] + for entry in data: + interface = entry.get('ifname') + rx_packets = entry.get('rx_packets') + rx_bytes = entry.get('rx_bytes') + tx_packets = entry.get('tx_packets') + tx_bytes = entry.get('tx_bytes') + rx_dropped = entry.get('rx_dropped') + tx_dropped = entry.get('tx_dropped') + rx_errors = entry.get('rx_over_errors') + tx_errors = entry.get('tx_carrier_errors') + data_entries.append([interface, rx_packets, rx_bytes, tx_packets, tx_bytes, rx_dropped, tx_dropped, rx_errors, tx_errors]) + + headers = ['Interface', 'Rx Packets', 'Rx Bytes', 'Tx Packets', 'Tx Bytes', 'Rx Dropped', 'Tx Dropped', 'Rx Errors', 'Tx Errors'] + output = tabulate(data_entries, headers, numalign="left") + print (output) + return output + + +def _show_raw(data: list, intf_name: str): + if intf_name is not None and len(data) <= 1: + try: + return data[0] + except IndexError: + raise vyos.opmode.UnconfiguredObject( + f"Interface {intf_name} does not exist") + else: + return data + + +def show(raw: bool, intf_name: typing.Optional[str], + intf_type: typing.Optional[str], + vif: bool, vrrp: bool): + data = _get_raw_data(intf_name, intf_type, vif, vrrp) + if raw: + return _show_raw(data, intf_name) + return _format_show_data(data) + +def show_summary(raw: bool, intf_name: typing.Optional[str], + intf_type: typing.Optional[str], + vif: bool, vrrp: bool): + data = _get_summary_data(intf_name, intf_type, vif, vrrp) + if raw: + return _show_raw(data, intf_name) + return _format_show_summary(data) + +def show_summary_extended(raw: bool, intf_name: typing.Optional[str], + intf_type: typing.Optional[str], + vif: bool, vrrp: bool): + data = _get_summary_data(intf_name, intf_type, vif, vrrp) + if raw: + return _show_raw(data, intf_name) + return _format_show_summary_extended(data) + +def show_counters(raw: bool, intf_name: typing.Optional[str], + intf_type: typing.Optional[str], + vif: bool, vrrp: bool): + data = _get_counter_data(intf_name, intf_type, vif, vrrp) + if raw: + return _show_raw(data, intf_name) + return _format_show_counters(data) + +def clear_counters(intf_name: typing.Optional[str], + intf_type: typing.Optional[str], + vif: bool, vrrp: bool): + for interface in filtered_interfaces(intf_name, intf_type, vif, vrrp): + interface.operational.clear_counters() + +def reset_counters(intf_name: typing.Optional[str], + intf_type: typing.Optional[str], + vif: bool, vrrp: bool): + for interface in filtered_interfaces(intf_name, intf_type, vif, vrrp): + interface.operational.reset_counters() + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/interfaces_wireguard.py b/src/op_mode/interfaces_wireguard.py new file mode 100644 index 0000000..627af05 --- /dev/null +++ b/src/op_mode/interfaces_wireguard.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import vyos.opmode + +from vyos.ifconfig import WireGuardIf +from vyos.configquery import ConfigTreeQuery + + +def _verify(func): + """Decorator checks if WireGuard interface config exists""" + from functools import wraps + + @wraps(func) + def _wrapper(*args, **kwargs): + config = ConfigTreeQuery() + interface = kwargs.get('intf_name') + if not config.exists(['interfaces', 'wireguard', interface]): + unconf_message = f'WireGuard interface {interface} is not configured' + raise vyos.opmode.UnconfiguredSubsystem(unconf_message) + return func(*args, **kwargs) + + return _wrapper + + +@_verify +def show_summary(raw: bool, intf_name: str): + intf = WireGuardIf(intf_name, create=False, debug=False) + return intf.operational.show_interface() + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/interfaces_wireless.py b/src/op_mode/interfaces_wireless.py new file mode 100644 index 0000000..bf6e462 --- /dev/null +++ b/src/op_mode/interfaces_wireless.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import sys +import vyos.opmode + +from copy import deepcopy +from tabulate import tabulate +from vyos.utils.process import popen +from vyos.configquery import ConfigTreeQuery + +def _verify(func): + """Decorator checks if Wireless LAN config exists""" + from functools import wraps + + @wraps(func) + def _wrapper(*args, **kwargs): + config = ConfigTreeQuery() + if not config.exists(['interfaces', 'wireless']): + unconf_message = 'No Wireless interfaces configured' + raise vyos.opmode.UnconfiguredSubsystem(unconf_message) + return func(*args, **kwargs) + return _wrapper + +def _get_raw_info_data(): + output_data = [] + + config = ConfigTreeQuery() + raw = config.get_config_dict(['interfaces', 'wireless'], effective=True, + get_first_key=True, key_mangling=('-', '_')) + for interface, interface_config in raw.items(): + tmp = {'name' : interface} + + if 'type' in interface_config: + tmp.update({'type' : interface_config['type']}) + else: + tmp.update({'type' : '-'}) + + if 'ssid' in interface_config: + tmp.update({'ssid' : interface_config['ssid']}) + else: + tmp.update({'ssid' : '-'}) + + if 'channel' in interface_config: + tmp.update({'channel' : interface_config['channel']}) + else: + tmp.update({'channel' : '-'}) + + output_data.append(tmp) + + return output_data + +def _get_formatted_info_output(raw_data): + output=[] + for ssid in raw_data: + output.append([ssid['name'], ssid['type'], ssid['ssid'], ssid['channel']]) + + headers = ["Interface", "Type", "SSID", "Channel"] + print(tabulate(output, headers, numalign="left")) + +def _get_raw_scan_data(intf_name): + # XXX: This ignores errors + tmp, _ = popen(f'iw dev {intf_name} scan ap-force') + networks = [] + data = { + 'ssid': '', + 'mac': '', + 'channel': '', + 'signal': '' + } + re_mac = re.compile(r'([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})') + for line in tmp.splitlines(): + if line.startswith('BSS '): + ssid = deepcopy(data) + ssid['mac'] = re.search(re_mac, line).group() + + elif line.lstrip().startswith('SSID: '): + # SSID can be " SSID: WLAN-57 6405", thus strip all leading whitespaces + ssid['ssid'] = line.lstrip().split(':')[-1].lstrip() + + elif line.lstrip().startswith('signal: '): + # Siganl can be " signal: -67.00 dBm", thus strip all leading whitespaces + ssid['signal'] = line.lstrip().split(':')[-1].split()[0] + + elif line.lstrip().startswith('DS Parameter set: channel'): + # Channel can be " DS Parameter set: channel 6" , thus + # strip all leading whitespaces + ssid['channel'] = line.lstrip().split(':')[-1].split()[-1] + networks.append(ssid) + continue + + return networks + +def _format_scan_data(raw_data): + output=[] + for ssid in raw_data: + output.append([ssid['mac'], ssid['ssid'], ssid['channel'], ssid['signal']]) + headers = ["Address", "SSID", "Channel", "Signal (dbm)"] + return tabulate(output, headers, numalign="left") + +def _get_raw_station_data(intf_name): + # XXX: This ignores errors + tmp, _ = popen(f'iw dev {intf_name} station dump') + clients = [] + data = { + 'mac': '', + 'signal': '', + 'rx_bytes': '', + 'rx_packets': '', + 'tx_bytes': '', + 'tx_packets': '' + } + re_mac = re.compile(r'([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})') + for line in tmp.splitlines(): + if line.startswith('Station'): + client = deepcopy(data) + client['mac'] = re.search(re_mac, line).group() + + elif line.lstrip().startswith('signal avg:'): + client['signal'] = line.lstrip().split(':')[-1].lstrip().split()[0] + + elif line.lstrip().startswith('rx bytes:'): + client['rx_bytes'] = line.lstrip().split(':')[-1].lstrip() + + elif line.lstrip().startswith('rx packets:'): + client['rx_packets'] = line.lstrip().split(':')[-1].lstrip() + + elif line.lstrip().startswith('tx bytes:'): + client['tx_bytes'] = line.lstrip().split(':')[-1].lstrip() + + elif line.lstrip().startswith('tx packets:'): + client['tx_packets'] = line.lstrip().split(':')[-1].lstrip() + clients.append(client) + continue + + return clients + +def _format_station_data(raw_data): + output=[] + for ssid in raw_data: + output.append([ssid['mac'], ssid['signal'], ssid['rx_bytes'], ssid['rx_packets'], ssid['tx_bytes'], ssid['tx_packets']]) + headers = ["Station", "Signal", "RX bytes", "RX packets", "TX bytes", "TX packets"] + return tabulate(output, headers, numalign="left") + +@_verify +def show_info(raw: bool): + info_data = _get_raw_info_data() + if raw: + return info_data + return _get_formatted_info_output(info_data) + +def show_scan(raw: bool, intf_name: str): + data = _get_raw_scan_data(intf_name) + if raw: + return data + return _format_scan_data(data) + +@_verify +def show_stations(raw: bool, intf_name: str): + data = _get_raw_station_data(intf_name) + if raw: + return data + return _format_station_data(data) + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/ipoe-control.py b/src/op_mode/ipoe-control.py new file mode 100644 index 0000000..b7d6a0c --- /dev/null +++ b/src/op_mode/ipoe-control.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import argparse + +from vyos.config import Config +from vyos.utils.process import popen +from vyos.utils.process import run + +cmd_dict = { + 'cmd_base' : '/usr/bin/accel-cmd -p 2002 ', + 'selector' : ['if', 'username', 'sid'], + 'actions' : { + 'show_sessions' : 'show sessions', + 'show_stat' : 'show stat', + 'terminate' : 'teminate' + } +} + +def is_ipoe_configured(): + if not Config().exists_effective('service ipoe-server'): + print("Service IPoE is not configured") + sys.exit(1) + +def main(): + #parese args + parser = argparse.ArgumentParser() + parser.add_argument('--action', help='Control action', required=True) + parser.add_argument('--selector', help='Selector username|ifname|sid', required=False) + parser.add_argument('--target', help='Target must contain username|ifname|sid', required=False) + args = parser.parse_args() + + + # Check is IPoE configured + is_ipoe_configured() + + if args.action == "restart": + run(cmd_dict['cmd_base'] + "restart") + sys.exit(0) + + if args.action in cmd_dict['actions']: + if args.selector in cmd_dict['selector'] and args.target: + run(cmd_dict['cmd_base'] + "{0} {1} {2}".format(args.action, args.selector, args.target)) + else: + if args.action == "show_sessions": + ses_pattern = " ifname,username,calling-sid,ip,ip6,ip6-dp,rate-limit,type,comp,state,uptime" + else: + ses_pattern = "" + output, err = popen(cmd_dict['cmd_base'] + cmd_dict['actions'][args.action] + ses_pattern, decode='utf-8') + if not err: + print(output) + else: + print("IPoE server is not running") + +if __name__ == '__main__': + main() diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py new file mode 100644 index 0000000..02ba126 --- /dev/null +++ b/src/op_mode/ipsec.py @@ -0,0 +1,1053 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import re +import sys +import typing + +from hurry import filesize +from re import split as re_split +from tabulate import tabulate + +from vyos.utils.convert import convert_data +from vyos.utils.convert import seconds_to_human +from vyos.utils.process import cmd +from vyos.configquery import ConfigTreeQuery +from vyos.base import Warning + +import vyos.opmode +import vyos.ipsec + + +def _convert(text): + return int(text) if text.isdigit() else text.lower() + + +def _alphanum_key(key): + return [_convert(c) for c in re_split('([0-9]+)', str(key))] + + +def _get_raw_data_sas(): + try: + get_sas = vyos.ipsec.get_vici_sas() + sas = convert_data(get_sas) + return sas + except vyos.ipsec.ViciInitiateError as err: + raise vyos.opmode.UnconfiguredSubsystem(err) + + +def _get_output_swanctl_sas_from_list(ra_output_list: list) -> str: + """ + Template for output for VICI + Inserts \n after each IKE SA + :param ra_output_list: IKE SAs list + :type ra_output_list: list + :return: formatted string + :rtype: str + """ + output = '' + for sa_val in ra_output_list: + for sa in sa_val.values(): + swanctl_output: str = cmd(f'sudo swanctl -l --ike-id {sa["uniqueid"]}') + output = f'{output}{swanctl_output}\n\n' + return output + + +def _get_formatted_output_sas(sas): + sa_data = [] + for sa in sas: + for parent_sa in sa.values(): + # create an item for each child-sa + for child_sa in parent_sa.get('child-sas', {}).values(): + # prepare a list for output data + sa_out_name = sa_out_state = sa_out_uptime = sa_out_bytes = ( + sa_out_packets + ) = sa_out_remote_addr = sa_out_remote_id = sa_out_proposal = 'N/A' + + # collect raw data + sa_name = child_sa.get('name') + sa_state = child_sa.get('state') + sa_uptime = child_sa.get('install-time') + sa_bytes_in = child_sa.get('bytes-in') + sa_bytes_out = child_sa.get('bytes-out') + sa_packets_in = child_sa.get('packets-in') + sa_packets_out = child_sa.get('packets-out') + sa_remote_addr = parent_sa.get('remote-host') + sa_remote_id = parent_sa.get('remote-id') + sa_proposal_encr_alg = child_sa.get('encr-alg') + sa_proposal_integ_alg = child_sa.get('integ-alg') + sa_proposal_encr_keysize = child_sa.get('encr-keysize') + sa_proposal_dh_group = child_sa.get('dh-group') + + # format data to display + if sa_name: + sa_out_name = sa_name + if sa_state: + if sa_state == 'INSTALLED': + sa_out_state = 'up' + else: + sa_out_state = 'down' + if sa_uptime: + sa_out_uptime = seconds_to_human(sa_uptime) + if sa_bytes_in and sa_bytes_out: + bytes_in = filesize.size(int(sa_bytes_in)) + bytes_out = filesize.size(int(sa_bytes_out)) + sa_out_bytes = f'{bytes_in}/{bytes_out}' + if sa_packets_in and sa_packets_out: + packets_in = filesize.size(int(sa_packets_in), system=filesize.si) + packets_out = filesize.size(int(sa_packets_out), system=filesize.si) + packets_str = f'{packets_in}/{packets_out}' + sa_out_packets = re.sub(r'B', r'', packets_str) + if sa_remote_addr: + sa_out_remote_addr = sa_remote_addr + if sa_remote_id: + sa_out_remote_id = sa_remote_id + # format proposal + if sa_proposal_encr_alg: + sa_out_proposal = sa_proposal_encr_alg + if sa_proposal_encr_keysize: + sa_proposal_encr_keysize_str = sa_proposal_encr_keysize + sa_out_proposal = ( + f'{sa_out_proposal}_{sa_proposal_encr_keysize_str}' + ) + if sa_proposal_integ_alg: + sa_proposal_integ_alg_str = sa_proposal_integ_alg + sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_integ_alg_str}' + if sa_proposal_dh_group: + sa_proposal_dh_group_str = sa_proposal_dh_group + sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_dh_group_str}' + + # add a new item to output data + sa_data.append( + [ + sa_out_name, + sa_out_state, + sa_out_uptime, + sa_out_bytes, + sa_out_packets, + sa_out_remote_addr, + sa_out_remote_id, + sa_out_proposal, + ] + ) + + headers = [ + 'Connection', + 'State', + 'Uptime', + 'Bytes In/Out', + 'Packets In/Out', + 'Remote address', + 'Remote ID', + 'Proposal', + ] + sa_data = sorted(sa_data, key=_alphanum_key) + output = tabulate(sa_data, headers) + return output + + +# Connections block + + +def _get_convert_data_connections(): + try: + get_connections = vyos.ipsec.get_vici_connections() + connections = convert_data(get_connections) + return connections + except vyos.ipsec.ViciInitiateError as err: + raise vyos.opmode.UnconfiguredSubsystem(err) + + +def _get_parent_sa_proposal(connection_name: str, data: list) -> dict: + """Get parent SA proposals by connection name + if connections not in the 'down' state + + Args: + connection_name (str): Connection name + data (list): List of current SAs from vici + + Returns: + str: Parent SA connection proposal + AES_CBC/256/HMAC_SHA2_256_128/MODP_1024 + """ + if not data: + return {} + for sa in data: + # check if parent SA exist + if connection_name not in sa.keys(): + continue + if 'encr-alg' in sa[connection_name]: + encr_alg = sa.get(connection_name, '').get('encr-alg') + cipher = encr_alg.split('_')[0] + mode = encr_alg.split('_')[1] + encr_keysize = sa.get(connection_name, '').get('encr-keysize') + integ_alg = sa.get(connection_name, '').get('integ-alg') + # prf_alg = sa.get(connection_name, '').get('prf-alg') + dh_group = sa.get(connection_name, '').get('dh-group') + proposal = { + 'cipher': cipher, + 'mode': mode, + 'key_size': encr_keysize, + 'hash': integ_alg, + 'dh': dh_group, + } + return proposal + return {} + + +def _get_parent_sa_state(connection_name: str, data: list) -> str: + """Get parent SA state by connection name + + Args: + connection_name (str): Connection name + data (list): List of current SAs from vici + + Returns: + Parent SA connection state + """ + ike_state = 'down' + if not data: + return ike_state + for sa in data: + # check if parent SA exist + for connection, connection_conf in sa.items(): + if connection_name != connection: + continue + if connection_conf['state'].lower() == 'established': + ike_state = 'up' + return ike_state + + +def _get_child_sa_state(connection_name: str, tunnel_name: str, data: list) -> str: + """Get child SA state by connection and tunnel name + + Args: + connection_name (str): Connection name + tunnel_name (str): Tunnel name + data (list): List of current SAs from vici + + Returns: + str: `up` if child SA state is 'installed' otherwise `down` + """ + child_sa = 'down' + if not data: + return child_sa + for sa in data: + # check if parent SA exist + if connection_name not in sa.keys(): + continue + child_sas = sa[connection_name]['child-sas'] + # Get all child SA states + # there can be multiple SAs per tunnel + child_sa_states = [ + v['state'] for k, v in child_sas.items() if v['name'] == tunnel_name + ] + return 'up' if 'INSTALLED' in child_sa_states else child_sa + + +def _get_child_sa_info(connection_name: str, tunnel_name: str, data: list) -> dict: + """Get child SA installed info by connection and tunnel name + + Args: + connection_name (str): Connection name + tunnel_name (str): Tunnel name + data (list): List of current SAs from vici + + Returns: + dict: Info of the child SA in the dictionary format + """ + for sa in data: + # check if parent SA exist + if connection_name not in sa.keys(): + continue + child_sas = sa[connection_name]['child-sas'] + # Get all child SA data + # Skip temp SA name (first key), get only SA values as dict + # {'OFFICE-B-tunnel-0-46': {'name': 'OFFICE-B-tunnel-0'}...} + # i.e get all data after 'OFFICE-B-tunnel-0-46' + child_sa_info = [ + v + for k, v in child_sas.items() + if 'name' in v and v['name'] == tunnel_name and v['state'] == 'INSTALLED' + ] + return child_sa_info[-1] if child_sa_info else {} + + +def _get_child_sa_proposal(child_sa_data: dict) -> dict: + if child_sa_data and 'encr-alg' in child_sa_data: + encr_alg = child_sa_data.get('encr-alg') + cipher = encr_alg.split('_')[0] + mode = encr_alg.split('_')[1] + key_size = child_sa_data.get('encr-keysize') + integ_alg = child_sa_data.get('integ-alg') + dh_group = child_sa_data.get('dh-group') + proposal = { + 'cipher': cipher, + 'mode': mode, + 'key_size': key_size, + 'hash': integ_alg, + 'dh': dh_group, + } + return proposal + return {} + + +def _get_raw_data_connections(list_connections: list, list_sas: list) -> list: + """Get configured VPN IKE connections and IPsec states + + Args: + list_connections (list): List of configured connections from vici + list_sas (list): List of current SAs from vici + + Returns: + list: List and status of IKE/IPsec connections/tunnels + """ + base_dict = [] + for connections in list_connections: + base_list = {} + for connection, conn_conf in connections.items(): + base_list['ike_connection_name'] = connection + base_list['ike_connection_state'] = _get_parent_sa_state( + connection, list_sas + ) + base_list['ike_remote_address'] = conn_conf['remote_addrs'] + base_list['ike_proposal'] = _get_parent_sa_proposal(connection, list_sas) + base_list['local_id'] = conn_conf.get('local-1', '').get('id') + base_list['remote_id'] = conn_conf.get('remote-1', '').get('id') + base_list['version'] = conn_conf.get('version', 'IKE') + base_list['children'] = [] + children = conn_conf['children'] + for tunnel, tun_options in children.items(): + state = _get_child_sa_state(connection, tunnel, list_sas) + local_ts = tun_options.get('local-ts') + remote_ts = tun_options.get('remote-ts') + dpd_action = tun_options.get('dpd_action') + close_action = tun_options.get('close_action') + sa_info = _get_child_sa_info(connection, tunnel, list_sas) + esp_proposal = _get_child_sa_proposal(sa_info) + base_list['children'].append( + { + 'name': tunnel, + 'state': state, + 'local_ts': local_ts, + 'remote_ts': remote_ts, + 'dpd_action': dpd_action, + 'close_action': close_action, + 'sa': sa_info, + 'esp_proposal': esp_proposal, + } + ) + base_dict.append(base_list) + return base_dict + + +def _get_raw_connections_summary(list_conn, list_sas): + import jmespath + + data = _get_raw_data_connections(list_conn, list_sas) + match = '[*].children[]' + child = jmespath.search(match, data) + tunnels_down = len([k for k in child if k['state'] == 'down']) + tunnels_up = len([k for k in child if k['state'] == 'up']) + tun_dict = { + 'tunnels': child, + 'total': len(child), + 'down': tunnels_down, + 'up': tunnels_up, + } + return tun_dict + + +def _get_formatted_output_conections(data): + from tabulate import tabulate + + connections = [] + for entry in data: + ike_name = entry['ike_connection_name'] + ike_state = entry['ike_connection_state'] + conn_type = entry.get('version', 'IKE') + remote_addrs = ','.join(entry['ike_remote_address']) + local_ts, remote_ts = '-', '-' + local_id = entry['local_id'] + remote_id = entry['remote_id'] + proposal = '-' + if entry.get('ike_proposal'): + proposal = ( + f'{entry["ike_proposal"]["cipher"]}_' + f'{entry["ike_proposal"]["mode"]}/' + f'{entry["ike_proposal"]["key_size"]}/' + f'{entry["ike_proposal"]["hash"]}/' + f'{entry["ike_proposal"]["dh"]}' + ) + connections.append( + [ + ike_name, + ike_state, + conn_type, + remote_addrs, + local_ts, + remote_ts, + local_id, + remote_id, + proposal, + ] + ) + for tun in entry['children']: + tun_name = tun.get('name') + tun_state = tun.get('state') + conn_type = 'IPsec' + local_ts = '\n'.join(tun.get('local_ts')) + remote_ts = '\n'.join(tun.get('remote_ts')) + proposal = '-' + if tun.get('esp_proposal'): + proposal = ( + f'{tun["esp_proposal"]["cipher"]}_' + f'{tun["esp_proposal"]["mode"]}/' + f'{tun["esp_proposal"]["key_size"]}/' + f'{tun["esp_proposal"]["hash"]}/' + f'{tun["esp_proposal"]["dh"]}' + ) + connections.append( + [ + tun_name, + tun_state, + conn_type, + remote_addrs, + local_ts, + remote_ts, + local_id, + remote_id, + proposal, + ] + ) + connection_headers = [ + 'Connection', + 'State', + 'Type', + 'Remote address', + 'Local TS', + 'Remote TS', + 'Local id', + 'Remote id', + 'Proposal', + ] + output = tabulate(connections, connection_headers, numalign='left') + return output + + +# Connections block end + + +def _get_childsa_id_list(ike_sas: list) -> list: + """ + Generate list of CHILD SA ids based on list of OrderingDict + wich is returned by vici + :param ike_sas: list of IKE SAs generated by vici + :type ike_sas: list + :return: list of IKE SAs ids + :rtype: list + """ + list_childsa_id: list = [] + for ike in ike_sas: + for ike_sa in ike.values(): + for child_sa in ike_sa['child-sas'].values(): + list_childsa_id.append(child_sa['uniqueid'].decode('ascii')) + return list_childsa_id + + +def _get_con_childsa_name_list( + ike_sas: list, filter_dict: typing.Optional[dict] = None +) -> list: + """ + Generate list of CHILD SA ids based on list of OrderingDict + wich is returned by vici + :param ike_sas: list of IKE SAs connections generated by vici + :type ike_sas: list + :param filter_dict: dict of filter options + :type filter_dict: dict + :return: list of IKE SAs name + :rtype: list + """ + list_childsa_name: list = [] + for ike in ike_sas: + for ike_name, ike_values in ike.items(): + for sa, sa_values in ike_values['children'].items(): + if filter_dict: + if filter_dict.items() <= sa_values.items(): + list_childsa_name.append(sa) + else: + list_childsa_name.append(sa) + return list_childsa_name + + +def _get_all_sitetosite_peers_name_list() -> list: + """ + Return site-to-site peers configuration + :return: site-to-site peers configuration + :rtype: list + """ + conf: ConfigTreeQuery = ConfigTreeQuery() + config_path = ['vpn', 'ipsec', 'site-to-site', 'peer'] + peers_config = conf.get_config_dict( + config_path, + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + ) + peers_list: list = [] + for name in peers_config: + peers_list.append(name) + return peers_list + + +def _get_tunnel_sw_format(peer: str, tunnel: str) -> str: + """ + Convert tunnel to Strongwan format of CHILD_SA + :param peer: Peer name (IKE_SA) + :type peer: str + :param tunnel: tunnel number (CHILD_SA) + :type tunnel: str + :return: Converted tunnel name (CHILD_SA) + :rtype: str + """ + tunnel_sw = None + if tunnel: + if tunnel.isnumeric(): + tunnel_sw = f'{peer}-tunnel-{tunnel}' + elif tunnel == 'vti': + tunnel_sw = f'{peer}-vti' + return tunnel_sw + + +def _initiate_peer_with_childsas( + peer: str, tunnel: typing.Optional[str] = None +) -> None: + """ + Initiate IPSEC peer SAs by vici. + If tunnel is None it initiates all peers tunnels + :param peer: Peer name (IKE_SA) + :type peer: str + :param tunnel: tunnel number (CHILD_SA) + :type tunnel: str + """ + tunnel_sw = _get_tunnel_sw_format(peer, tunnel) + try: + con_list: list = vyos.ipsec.get_vici_connection_by_name(peer) + if not con_list: + raise vyos.opmode.IncorrectValue( + f"Peer's {peer} SA(s) not loaded. Initiation was failed" + ) + childsa_name_list: list = _get_con_childsa_name_list(con_list) + + if not tunnel_sw: + vyos.ipsec.vici_initiate_all_child_sa_by_ike(peer, childsa_name_list) + print(f'Peer {peer} initiate result: success') + return + + if tunnel_sw in childsa_name_list: + vyos.ipsec.vici_initiate_all_child_sa_by_ike(peer, [tunnel_sw]) + print(f'Peer {peer} tunnel {tunnel} initiate result: success') + return + + raise vyos.opmode.IncorrectValue(f'Peer {peer} SA {tunnel} not found, aborting') + + except vyos.ipsec.ViciInitiateError as err: + raise vyos.opmode.UnconfiguredSubsystem(err) + except vyos.ipsec.ViciCommandError as err: + raise vyos.opmode.IncorrectValue(err) + + +def _terminate_peer(peer: str, tunnel: typing.Optional[str] = None) -> None: + """ + Terminate IPSEC peer SAs by vici. + If tunnel is None it terminates all peers tunnels + :param peer: Peer name (IKE_SA) + :type peer: str + :param tunnel: tunnel number (CHILD_SA) + :type tunnel: str + """ + # Convert tunnel to Strongwan format of CHILD_SA + tunnel_sw = _get_tunnel_sw_format(peer, tunnel) + try: + sa_list: list = vyos.ipsec.get_vici_sas_by_name(peer, tunnel_sw) + if sa_list: + if tunnel: + childsa_id_list: list = _get_childsa_id_list(sa_list) + if childsa_id_list: + vyos.ipsec.terminate_vici_by_name(peer, tunnel_sw) + print(f'Peer {peer} tunnel {tunnel} terminate result: success') + else: + Warning( + f'Peer {peer} tunnel {tunnel} SA is not initiated. Nothing to terminate' + ) + else: + vyos.ipsec.terminate_vici_by_name(peer, tunnel_sw) + print(f'Peer {peer} terminate result: success') + else: + Warning(f"Peer's {peer} SAs are not initiated. Nothing to terminate") + + except vyos.ipsec.ViciInitiateError as err: + raise vyos.opmode.UnconfiguredSubsystem(err) + except vyos.ipsec.ViciCommandError as err: + raise vyos.opmode.IncorrectValue(err) + + +def reset_peer(peer: str, tunnel: typing.Optional[str] = None) -> None: + """ + Reset IPSEC peer SAs. + If tunnel is None it resets all peers tunnels + :param peer: Peer name (IKE_SA) + :type peer: str + :param tunnel: tunnel number (CHILD_SA) + :type tunnel: str + """ + _terminate_peer(peer, tunnel) + peer_config = _get_sitetosite_peer_config(peer) + # initiate SAs only if 'connection-type=initiate' + if ( + 'connection_type' in peer_config + and peer_config['connection_type'] == 'initiate' + ): + _initiate_peer_with_childsas(peer, tunnel) + + +def reset_all_peers() -> None: + sitetosite_list = _get_all_sitetosite_peers_name_list() + if sitetosite_list: + for peer_name in sitetosite_list: + try: + reset_peer(peer_name) + except vyos.opmode.IncorrectValue as err: + print(err) + print('Peers reset result: success') + else: + raise vyos.opmode.UnconfiguredSubsystem( + 'VPN IPSec site-to-site is not configured, aborting' + ) + + +def _get_ra_session_list_by_username(username: typing.Optional[str] = None): + """ + Return list of remote-access IKE_SAs uniqueids + :param username: + :type username: + :return: + :rtype: + """ + list_sa_id = [] + sa_list = _get_raw_data_sas() + for sa_val in sa_list: + for sa in sa_val.values(): + if 'remote-eap-id' in sa: + if username: + if username == sa['remote-eap-id']: + list_sa_id.append(sa['uniqueid']) + else: + list_sa_id.append(sa['uniqueid']) + return list_sa_id + + +def reset_ra(username: typing.Optional[str] = None): + # Reset remote-access ipsec sessions + if username: + list_sa_id = _get_ra_session_list_by_username(username) + else: + list_sa_id = _get_ra_session_list_by_username() + if list_sa_id: + vyos.ipsec.terminate_vici_ikeid_list(list_sa_id) + + +def reset_profile_dst(profile: str, tunnel: str, nbma_dst: str): + if profile and tunnel and nbma_dst: + ike_sa_name = f'dmvpn-{profile}-{tunnel}' + try: + # Get IKE SAs + sa_list = convert_data(vyos.ipsec.get_vici_sas_by_name(ike_sa_name, None)) + if not sa_list: + raise vyos.opmode.IncorrectValue( + f'SA(s) for profile {profile} tunnel {tunnel} not found, aborting' + ) + sa_nbma_list = list( + [ + x + for x in sa_list + if ike_sa_name in x and x[ike_sa_name]['remote-host'] == nbma_dst + ] + ) + if not sa_nbma_list: + raise vyos.opmode.IncorrectValue( + f'SA(s) for profile {profile} tunnel {tunnel} remote-host {nbma_dst} not found, aborting' + ) + # terminate IKE SAs + vyos.ipsec.terminate_vici_ikeid_list( + list( + [ + x[ike_sa_name]['uniqueid'] + for x in sa_nbma_list + if ike_sa_name in x + ] + ) + ) + # initiate IKE SAs + for ike in sa_nbma_list: + if ike_sa_name in ike: + vyos.ipsec.vici_initiate( + ike_sa_name, + 'dmvpn', + ike[ike_sa_name]['local-host'], + ike[ike_sa_name]['remote-host'], + ) + print( + f'Profile {profile} tunnel {tunnel} remote-host {nbma_dst} reset result: success' + ) + except vyos.ipsec.ViciInitiateError as err: + raise vyos.opmode.UnconfiguredSubsystem(err) + except vyos.ipsec.ViciCommandError as err: + raise vyos.opmode.IncorrectValue(err) + + +def reset_profile_all(profile: str, tunnel: str): + if profile and tunnel: + ike_sa_name = f'dmvpn-{profile}-{tunnel}' + try: + # Get IKE SAs + sa_list: list = convert_data( + vyos.ipsec.get_vici_sas_by_name(ike_sa_name, None) + ) + if not sa_list: + raise vyos.opmode.IncorrectValue( + f'SA(s) for profile {profile} tunnel {tunnel} not found, aborting' + ) + # terminate IKE SAs + vyos.ipsec.terminate_vici_by_name(ike_sa_name, None) + # initiate IKE SAs + for ike in sa_list: + if ike_sa_name in ike: + vyos.ipsec.vici_initiate( + ike_sa_name, + 'dmvpn', + ike[ike_sa_name]['local-host'], + ike[ike_sa_name]['remote-host'], + ) + print( + f'Profile {profile} tunnel {tunnel} remote-host {ike[ike_sa_name]["remote-host"]} reset result: success' + ) + print(f'Profile {profile} tunnel {tunnel} reset result: success') + except vyos.ipsec.ViciInitiateError as err: + raise vyos.opmode.UnconfiguredSubsystem(err) + except vyos.ipsec.ViciCommandError as err: + raise vyos.opmode.IncorrectValue(err) + + +def show_sa(raw: bool): + sa_data = _get_raw_data_sas() + if raw: + return sa_data + return _get_formatted_output_sas(sa_data) + + +def _get_output_sas_detail(ra_output_list: list) -> str: + """ + Formate all IKE SAs detail output + :param ra_output_list: IKE SAs list + :type ra_output_list: list + :return: formatted RA IKE SAs detail output + :rtype: str + """ + return _get_output_swanctl_sas_from_list(ra_output_list) + + +def show_sa_detail(raw: bool): + sa_data = _get_raw_data_sas() + if raw: + return sa_data + return _get_output_sas_detail(sa_data) + + +def show_connections(raw: bool): + list_conns = _get_convert_data_connections() + list_sas = _get_raw_data_sas() + if raw: + return _get_raw_data_connections(list_conns, list_sas) + + connections = _get_raw_data_connections(list_conns, list_sas) + return _get_formatted_output_conections(connections) + + +def show_connections_summary(raw: bool): + list_conns = _get_convert_data_connections() + list_sas = _get_raw_data_sas() + if raw: + return _get_raw_connections_summary(list_conns, list_sas) + + +def _get_ra_sessions(username: typing.Optional[str] = None) -> list: + """ + Return list of remote-access IKE_SAs from VICI by username. + If username unspecified, return all remote-access IKE_SAs + :param username: Username of RA connection + :type username: str + :return: list of ra remote-access IKE_SAs + :rtype: list + """ + list_sa = [] + sa_list = _get_raw_data_sas() + for conn in sa_list: + for sa in conn.values(): + if 'remote-eap-id' in sa: + if username: + if username == sa['remote-eap-id']: + list_sa.append(conn) + else: + list_sa.append(conn) + return list_sa + + +def _filter_ikesas(list_sa: list, filter_key: str, filter_value: str) -> list: + """ + Filter IKE SAs by specifice key + :param list_sa: list of IKE SAs + :type list_sa: list + :param filter_key: Filter Key + :type filter_key: str + :param filter_value: Filter Value + :type filter_value: str + :return: Filtered list of IKE SAs + :rtype: list + """ + filtered_sa_list = [] + for conn in list_sa: + for sa in conn.values(): + if sa[filter_key] and sa[filter_key] == filter_value: + filtered_sa_list.append(conn) + return filtered_sa_list + + +def _get_last_installed_childsa(sa: dict) -> str: + """ + Return name of last installed active Child SA + :param sa: Dictionary with Child SAs + :type sa: dict + :return: Name of the Last installed active Child SA + :rtype: str + """ + child_sa_name = None + child_sa_id = 0 + for sa_name, child_sa in sa['child-sas'].items(): + if child_sa['state'] == 'INSTALLED': + if child_sa_id == 0 or int(child_sa['uniqueid']) > child_sa_id: + child_sa_id = int(child_sa['uniqueid']) + child_sa_name = sa_name + return child_sa_name + + +def _get_formatted_ike_proposal(sa: dict) -> str: + """ + Return IKE proposal string in format + EncrALG-EncrKeySize/PFR/HASH/DH-GROUP + :param sa: IKE SA + :type sa: dict + :return: IKE proposal string + :rtype: str + """ + proposal = '' + proposal = f'{proposal}{sa["encr-alg"]}' if 'encr-alg' in sa else proposal + proposal = f'{proposal}-{sa["encr-keysize"]}' if 'encr-keysize' in sa else proposal + proposal = f'{proposal}/{sa["prf-alg"]}' if 'prf-alg' in sa else proposal + proposal = f'{proposal}/{sa["integ-alg"]}' if 'integ-alg' in sa else proposal + proposal = f'{proposal}/{sa["dh-group"]}' if 'dh-group' in sa else proposal + return proposal + + +def _get_formatted_ipsec_proposal(sa: dict) -> str: + """ + Return IPSec proposal string in format + Protocol: EncrALG-EncrKeySize/HASH/PFS + :param sa: Child SA + :type sa: dict + :return: IPSec proposal string + :rtype: str + """ + proposal = '' + proposal = f'{proposal}{sa["protocol"]}' if 'protocol' in sa else proposal + proposal = f'{proposal}:{sa["encr-alg"]}' if 'encr-alg' in sa else proposal + proposal = f'{proposal}-{sa["encr-keysize"]}' if 'encr-keysize' in sa else proposal + proposal = f'{proposal}/{sa["integ-alg"]}' if 'integ-alg' in sa else proposal + proposal = f'{proposal}/{sa["dh-group"]}' if 'dh-group' in sa else proposal + return proposal + + +def _get_output_ra_sas_detail(ra_output_list: list) -> str: + """ + Formate RA IKE SAs detail output + :param ra_output_list: IKE SAs list + :type ra_output_list: list + :return: formatted RA IKE SAs detail output + :rtype: str + """ + return _get_output_swanctl_sas_from_list(ra_output_list) + + +def _get_formatted_output_ra_summary(ra_output_list: list): + sa_data = [] + for conn in ra_output_list: + for sa in conn.values(): + sa_id = sa['uniqueid'] if 'uniqueid' in sa else '' + sa_username = sa['remote-eap-id'] if 'remote-eap-id' in sa else '' + sa_protocol = f'IKEv{sa["version"]}' if 'version' in sa else '' + sa_remotehost = sa['remote-host'] if 'remote-host' in sa else '' + sa_remoteid = sa['remote-id'] if 'remote-id' in sa else '' + sa_ike_proposal = _get_formatted_ike_proposal(sa) + sa_tunnel_ip = sa['remote-vips'][0] + child_sa_key = _get_last_installed_childsa(sa) + if child_sa_key: + child_sa = sa['child-sas'][child_sa_key] + sa_ipsec_proposal = _get_formatted_ipsec_proposal(child_sa) + sa_state = 'UP' + sa_uptime = seconds_to_human(sa['established']) + else: + sa_ipsec_proposal = '' + sa_state = 'DOWN' + sa_uptime = '' + sa_data.append( + [ + sa_id, + sa_username, + sa_protocol, + sa_state, + sa_uptime, + sa_tunnel_ip, + sa_remotehost, + sa_remoteid, + sa_ike_proposal, + sa_ipsec_proposal, + ] + ) + + headers = [ + 'Connection ID', + 'Username', + 'Protocol', + 'State', + 'Uptime', + 'Tunnel IP', + 'Remote Host', + 'Remote ID', + 'IKE Proposal', + 'IPSec Proposal', + ] + sa_data = sorted(sa_data, key=_alphanum_key) + output = tabulate(sa_data, headers) + return output + + +def show_ra_detail( + raw: bool, + username: typing.Optional[str] = None, + conn_id: typing.Optional[str] = None, +): + list_sa: list = _get_ra_sessions() + if username: + list_sa = _filter_ikesas(list_sa, 'remote-eap-id', username) + elif conn_id: + list_sa = _filter_ikesas(list_sa, 'uniqueid', conn_id) + if not list_sa: + raise vyos.opmode.IncorrectValue('No active connections found, aborting') + if raw: + return list_sa + return _get_output_ra_sas_detail(list_sa) + + +def show_ra_summary(raw: bool): + list_sa: list = _get_ra_sessions() + if not list_sa: + raise vyos.opmode.IncorrectValue('No active connections found, aborting') + if raw: + return list_sa + return _get_formatted_output_ra_summary(list_sa) + + +# PSK block +def _get_raw_psk(): + conf: ConfigTreeQuery = ConfigTreeQuery() + config_path = ['vpn', 'ipsec', 'authentication', 'psk'] + psk_config = conf.get_config_dict( + config_path, + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + ) + + psk_list = [] + for psk, psk_data in psk_config.items(): + psk_data['psk'] = psk + psk_list.append(psk_data) + + return psk_list + + +def _get_formatted_psk(psk_list): + headers = ['PSK', 'Id', 'Secret'] + formatted_data = [] + + for psk_data in psk_list: + formatted_data.append( + [psk_data['psk'], '\n'.join(psk_data['id']), psk_data['secret']] + ) + + return tabulate(formatted_data, headers=headers) + + +def show_psk(raw: bool): + config = ConfigTreeQuery() + if not config.exists('vpn ipsec authentication psk'): + raise vyos.opmode.UnconfiguredSubsystem( + 'VPN ipsec psk authentication is not configured' + ) + + psk = _get_raw_psk() + if raw: + return psk + return _get_formatted_psk(psk) + + +# PSK block end + + +def _get_sitetosite_peer_config(peer: str): + """ + Return site-to-site peers configuration + :return: site-to-site peers configuration + :rtype: list + """ + conf: ConfigTreeQuery = ConfigTreeQuery() + config_path = ['vpn', 'ipsec', 'site-to-site', 'peer', peer] + peers_config = conf.get_config_dict( + config_path, + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + ) + return peers_config + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/kernel_modules.py b/src/op_mode/kernel_modules.py new file mode 100644 index 0000000..e381a1d --- /dev/null +++ b/src/op_mode/kernel_modules.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Purpose: +# Provides commands for retrieving information about kernel modules. + +import sys +import typing + +import vyos.opmode + + +lsmod_tmpl = """ +{% for m in modules -%} +Module: {{m.name}} + +{% if m.holders -%} +Holders: {{m.holders | join(", ")}} +{%- endif %} + +{% if m.drivers -%} +Drivers: {{m.drivers | join(", ")}} +{%- endif %} + +{% for k in m.fields -%} +{{k}}: {{m["fields"][k]}} +{% endfor %} +{% if m.parameters %} + +Parameters: + +{% for p in m.parameters -%} +{{p}}: {{m["parameters"][p]}} +{% endfor -%} +{% endif -%} + +------------- + +{% endfor %} +""" + +def _get_raw_data(module=None): + from vyos.utils.kernel import get_module_data, lsmod + + if module: + return [get_module_data(module)] + else: + return lsmod() + +def show(raw: bool, module: typing.Optional[str]): + from jinja2 import Template + + data = _get_raw_data(module=module) + + if raw: + return data + else: + t = Template(lsmod_tmpl) + output = t.render({"modules": data}) + return output + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/lldp.py b/src/op_mode/lldp.py new file mode 100644 index 0000000..fac622b --- /dev/null +++ b/src/op_mode/lldp.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import jmespath +import json +import sys +import typing + +from tabulate import tabulate + +from vyos.configquery import ConfigTreeQuery +from vyos.utils.process import cmd +from vyos.utils.dict import dict_search + +import vyos.opmode +unconf_message = 'LLDP is not configured' +capability_codes = """Capability Codes: R - Router, B - Bridge, W - Wlan r - Repeater, S - Station + D - Docsis, T - Telephone, O - Other + +""" + +def _verify(func): + """Decorator checks if LLDP config exists""" + from functools import wraps + + @wraps(func) + def _wrapper(*args, **kwargs): + config = ConfigTreeQuery() + if not config.exists(['service', 'lldp']): + raise vyos.opmode.UnconfiguredSubsystem(unconf_message) + return func(*args, **kwargs) + return _wrapper + +def _get_raw_data(interface=None, detail=False): + """ + If interface name is not set - get all interfaces + """ + tmp = 'lldpcli -f json show neighbors' + if detail: + tmp += f' details' + if interface: + tmp += f' ports {interface}' + output = cmd(tmp) + data = json.loads(output) + if not data: + return [] + return data + +def _get_formatted_output(raw_data): + data_entries = [] + tmp = dict_search('lldp.interface', raw_data) + if not tmp: + return None + # One can not always ensure that "interface" is of type list, add safeguard. + # E.G. Juniper Networks, Inc. ex2300-c-12t only has a dict, not a list of dicts + if isinstance(tmp, dict): + tmp = [tmp] + for neighbor in tmp: + for local_if, values in neighbor.items(): + tmp = [] + + # Device field + if 'chassis' in values: + tmp.append(next(iter(values['chassis']))) + else: + tmp.append('') + + # Local Port field + tmp.append(local_if) + + # Protocol field + tmp.append(values['via']) + + # Capabilities + cap = '' + capabilities = jmespath.search('chassis.[*][0][0].capability', values) + # One can not always ensure that "capability" is of type list, add + # safeguard. E.G. Unify US-24-250W only has a dict, not a list of dicts + if isinstance(capabilities, dict): + capabilities = [capabilities] + if capabilities: + for capability in capabilities: + if capability['enabled']: + if capability['type'] == 'Router': + cap += 'R' + if capability['type'] == 'Bridge': + cap += 'B' + if capability['type'] == 'Wlan': + cap += 'W' + if capability['type'] == 'Station': + cap += 'S' + if capability['type'] == 'Repeater': + cap += 'r' + if capability['type'] == 'Telephone': + cap += 'T' + if capability['type'] == 'Docsis': + cap += 'D' + if capability['type'] == 'Other': + cap += 'O' + tmp.append(cap) + + # Remote software platform + platform = jmespath.search('chassis.[*][0][0].descr', values) + if platform: + tmp.append(platform[:37]) + else: + tmp.append('') + + # Remote interface + interface = None + if jmespath.search('port.id.type', values) == 'ifname': + # Remote peer has explicitly returned the interface name as the PortID + interface = jmespath.search('port.id.value', values) + if not interface: + interface = jmespath.search('port.descr', values) + if not interface: + interface = jmespath.search('port.id.value', values) + if not interface: + interface = 'Unknown' + tmp.append(interface) + + # Add individual neighbor to output list + data_entries.append(tmp) + + headers = ["Device", "Local Port", "Protocol", "Capability", "Platform", "Remote Port"] + output = tabulate(data_entries, headers, numalign="left") + return capability_codes + output + +@_verify +def show_neighbors(raw: bool, interface: typing.Optional[str], detail: typing.Optional[bool]): + if raw or not detail: + lldp_data = _get_raw_data(interface=interface, detail=detail) + if raw: + return lldp_data + else: + return _get_formatted_output(lldp_data) + else: # non-raw, detail + tmp = 'lldpcli -f text show neighbors details' + if interface: + tmp += f' ports {interface}' + return cmd(tmp) + +if __name__ == "__main__": + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/log.py b/src/op_mode/log.py new file mode 100644 index 0000000..797ba5a --- /dev/null +++ b/src/op_mode/log.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json +import re +import sys +import typing + +from jinja2 import Template + +from vyos.utils.process import rc_cmd + +import vyos.opmode + +journalctl_command_template = Template(""" +--no-hostname +--quiet + +{% if boot %} + --boot +{% endif %} + +{% if count %} + --lines={{ count }} +{% endif %} + +{% if reverse %} + --reverse +{% endif %} + +{% if since %} + --since={{ since }} +{% endif %} + +{% if unit %} + --unit={{ unit }} +{% endif %} + +{% if utc %} + --utc +{% endif %} + +{% if raw %} +{# By default show 100 only lines for raw option if count does not set #} +{# Protection from parsing the full log by default #} +{% if not boot %} + --lines={{ '' ~ count if count else '100' }} +{% endif %} + --no-pager + --output=json +{% endif %} +""") + + +def show(raw: bool, + boot: typing.Optional[bool], + count: typing.Optional[int], + facility: typing.Optional[str], + reverse: typing.Optional[bool], + utc: typing.Optional[bool], + unit: typing.Optional[str]): + kwargs = dict(locals()) + + journalctl_options = journalctl_command_template.render(kwargs) + journalctl_options = re.sub(r'\s+', ' ', journalctl_options) + rc, output = rc_cmd(f'journalctl {journalctl_options}') + if raw: + # Each 'journalctl --output json' line is a separate JSON object + # So we should return list of dict + return [json.loads(line) for line in output.split('\n')] + return output + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/maya_date.py b/src/op_mode/maya_date.py new file mode 100644 index 0000000..847b543 --- /dev/null +++ b/src/op_mode/maya_date.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2013, 2018 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys + +class MayaDate(object): + """ Converts number of days since UNIX epoch + to the Maya calendar date. + + Ancient Maya people used three independent calendars for + different purposes. + + The long count calendar is for recording historical events. + It represents the number of days passed + since some date in the past the Maya believed is the day + our world was created. + + Tzolkin calendar is for religious purposes, it has + two independent cycles of 13 and 20 days, where 13 day + cycle days are numbered, and 20 day cycle days are named. + + Haab calendar is for agriculture and daily life, it's a + 365 day calendar with 18 months 20 days each, and 5 + nameless days. + + The smallest unit of the long count calendar is one day (kin). + """ + + """ The long count calendar uses five different base 18 or base 20 + cycles. Modern scholars write long count calendar dates in a dot separated format + from longest to shortest cycle, + <baktun>.<katun>.<tun>.<winal>.<kin> + for example, "13.0.0.9.2". + + Classic version actually used by the ancient Maya wraps around + every 13th baktun, but modern historians often use longer cycles + such as piktun = 20 baktun. + + """ + kin = 1 + winal = 20 # 20 kin + tun = 360 # 18 winal + katun = 7200 # 20 tun + baktun = 144000 # 20 katun + + """ Tzolk'in date is composed of two independent cycles. + Dates repeat every 260 days, 13 Ajaw is considered the end + of tzolk'in. + + Every day of the 20 day cycle has unique name, we number + them from zero so it's easier to map the remainder to day: + """ + tzolkin_days = { 0: "Imix'", + 1: "Ik'", + 2: "Ak'b'al", + 3: "K'an", + 4: "Chikchan", + 5: "Kimi", + 6: "Manik'", + 7: "Lamat", + 8: "Muluk", + 9: "Ok", + 10: "Chuwen", + 11: "Eb'", + 12: "B'en", + 13: "Ix", + 14: "Men", + 15: "Kib'", + 16: "Kab'an", + 17: "Etz'nab'", + 18: "Kawak", + 19: "Ajaw" } + + """ As said above, haab (year) has 19 months. Only 18 are + true months of 20 days each, the remaining 5 days called "wayeb" + do not really belong to any month, but we think of them as a pseudo-month + for convenience. + + Also, note that days of the month are actually numbered from 0, not from 1, + it's not for technical reasons. + """ + haab_months = { 0: "Pop", + 1: "Wo'", + 2: "Sip", + 3: "Sotz'", + 4: "Sek", + 5: "Xul", + 6: "Yaxk'in'", + 7: "Mol", + 8: "Ch'en", + 9: "Yax", + 10: "Sak'", + 11: "Keh", + 12: "Mak", + 13: "K'ank'in", + 14: "Muwan'", + 15: "Pax", + 16: "K'ayab", + 17: "Kumk'u", + 18: "Wayeb'" } + + """ Now we need to map the beginning of UNIX epoch + (Jan 1 1970 00:00 UTC) to the beginning of the long count + calendar (0.0.0.0.0, 4 Ajaw, 8 Kumk'u). + + The problem with mapping the long count calendar to + any other is that its start date is not known exactly. + + The most widely accepted hypothesis suggests it was + August 11, 3114 BC gregorian date. In this case UNIX epoch + starts on 12.17.16.7.5, 13 Chikchan, 3 K'ank'in + + It's known as Goodman-Martinez-Thompson (GMT) correlation + constant. + """ + start_days = 1856305 + + """ Seconds in day, for conversion from timestamp """ + seconds_in_day = 60 * 60 * 24 + + def __init__(self, timestamp): + if timestamp is None: + self.days = self.start_days + else: + self.days = self.start_days + (int(timestamp) // self.seconds_in_day) + + def long_count_date(self): + """ Returns long count date string """ + days = self.days + + cur_baktun = days // self.baktun + days = days % self.baktun + + cur_katun = days // self.katun + days = days % self.katun + + cur_tun = days // self.tun + days = days % self.tun + + cur_winal = days // self.winal + days = days % self.winal + + cur_kin = days + + longcount_string = "{0}.{1}.{2}.{3}.{4}".format( cur_baktun, + cur_katun, + cur_tun, + cur_winal, + cur_kin ) + return(longcount_string) + + def tzolkin_date(self): + """ Returns tzolkin date string """ + days = self.days + + """ The start date is not the beginning of both cycles, + it's 4 Ajaw. So we need to add 4 to the 13 days cycle day, + and substract 1 from the 20 day cycle to get correct result. + """ + tzolkin_13 = (days + 4) % 13 + tzolkin_20 = (days - 1) % 20 + + tzolkin_string = "{0} {1}".format(tzolkin_13, self.tzolkin_days[tzolkin_20]) + + return(tzolkin_string) + + def haab_date(self): + """ Returns haab date string. + + The time start on 8 Kumk'u rather than 0 Pop, which is + 17 days before the new haab, so we need to substract 17 + from the current date to get correct result. + """ + days = self.days + + haab_day = (days - 17) % 365 + haab_month = haab_day // 20 + haab_day_of_month = haab_day % 20 + + haab_string = "{0} {1}".format(haab_day_of_month, self.haab_months[haab_month]) + + return(haab_string) + + def date(self): + return("{0}, {1}, {2}".format( self.long_count_date(), self.tzolkin_date(), self.haab_date() )) + +if __name__ == '__main__': + try: + timestamp = sys.argv[1] + except: + print("Please specify timestamp in the argument") + sys.exit(1) + + maya_date = MayaDate(timestamp) + print(maya_date.date()) diff --git a/src/op_mode/memory.py b/src/op_mode/memory.py new file mode 100644 index 0000000..eb53003 --- /dev/null +++ b/src/op_mode/memory.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import sys + +import vyos.opmode + + +def _get_raw_data(): + from re import search as re_search + + def find_value(keyword, mem_data): + regex = keyword + ':\s+(\d+)' + res = re_search(regex, mem_data).group(1) + return int(res) + + with open("/proc/meminfo", "r") as f: + mem_data = f.read() + + total = find_value('MemTotal', mem_data) + available = find_value('MemAvailable', mem_data) + buffers = find_value('Buffers', mem_data) + cached = find_value('Cached', mem_data) + + used = total - available + + mem_data = { + "total": total, + "free": available, + "used": used, + "buffers": buffers, + "cached": cached + } + + for key in mem_data: + # The Linux kernel exposes memory values in kilobytes, + # so we need to normalize them + mem_data[key] = mem_data[key] * 1024 + + return mem_data + +def _get_formatted_output(mem): + from vyos.utils.convert import bytes_to_human + + # For human-readable outputs, we convert bytes to more convenient units + # (100M, 1.3G...) + for key in mem: + mem[key] = bytes_to_human(mem[key]) + + out = "Total: {}\n".format(mem["total"]) + out += "Free: {}\n".format(mem["free"]) + out += "Used: {}".format(mem["used"]) + + return out + +def show(raw: bool): + ram_data = _get_raw_data() + + if raw: + return ram_data + else: + return _get_formatted_output(ram_data) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) + diff --git a/src/op_mode/mtr.py b/src/op_mode/mtr.py new file mode 100644 index 0000000..de139f2 --- /dev/null +++ b/src/op_mode/mtr.py @@ -0,0 +1,306 @@ +#! /usr/bin/env python3 + +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import socket +import ipaddress + +from vyos.utils.network import interface_list +from vyos.utils.network import vrf_list +from vyos.utils.process import call + +options = { + 'report': { + 'mtr': '{command} --report', + 'type': 'noarg', + 'help': 'This option puts mtr into report mode. When in this mode, mtr will run for the number of cycles specified by the -c option, and then print statistics and exit.' + }, + 'report-wide': { + 'mtr': '{command} --report-wide', + 'type': 'noarg', + 'help': 'This option puts mtr into wide report mode. When in this mode, mtr will not cut hostnames in the report.' + }, + 'raw': { + 'mtr': '{command} --raw', + 'type': 'noarg', + 'help': 'Use the raw output format. This format is better suited for archival of the measurement results.' + }, + 'json': { + 'mtr': '{command} --json', + 'type': 'noarg', + 'help': 'Use this option to tell mtr to use the JSON output format.' + }, + 'split': { + 'mtr': '{command} --split', + 'type': 'noarg', + 'help': 'Use this option to set mtr to spit out a format that is suitable for a split-user interface.' + }, + 'no-dns': { + 'mtr': '{command} --no-dns', + 'type': 'noarg', + 'help': 'Use this option to force mtr to display numeric IP numbers and not try to resolve the host names.' + }, + 'show-ips': { + 'mtr': '{command} --show-ips {value}', + 'type': '<num>', + 'help': 'Use this option to tell mtr to display both the host names and numeric IP numbers.' + }, + 'ipinfo': { + 'mtr': '{command} --ipinfo {value}', + 'type': '<num>', + 'help': 'Displays information about each IP hop.' + }, + 'aslookup': { + 'mtr': '{command} --aslookup', + 'type': 'noarg', + 'help': 'Displays the Autonomous System (AS) number alongside each hop. Equivalent to --ipinfo 0.' + }, + 'interval': { + 'mtr': '{command} --interval {value}', + 'type': '<num>', + 'help': 'Use this option to specify the positive number of seconds between ICMP ECHO requests. The default value for this parameter is one second. The root user may choose values between zero and one.' + }, + 'report-cycles': { + 'mtr': '{command} --report-cycles {value}', + 'type': '<num>', + 'help': 'Use this option to set the number of pings sent to determine both the machines on the network and the reliability of those machines. Each cycle lasts one second.' + }, + 'psize': { + 'mtr': '{command} --psize {value}', + 'type': '<num>', + 'help': 'This option sets the packet size used for probing. It is in bytes, inclusive IP and ICMP headers. If set to a negative number, every iteration will use a different, random packet size up to that number.' + }, + 'bitpattern': { + 'mtr': '{command} --bitpattern {value}', + 'type': '<num>', + 'help': 'Specifies bit pattern to use in payload. Should be within range 0 - 255. If NUM is greater than 255, a random pattern is used.' + }, + 'gracetime': { + 'mtr': '{command} --gracetime {value}', + 'type': '<num>', + 'help': 'Use this option to specify the positive number of seconds to wait for responses after the final request. The default value is five seconds.' + }, + 'tos': { + 'mtr': '{command} --tos {value}', + 'type': '<tos>', + 'help': 'Specifies value for type of service field in IP header. Should be within range 0 - 255.' + }, + 'mpls': { + 'mtr': '{command} --mpls {value}', + 'type': 'noarg', + 'help': 'Use this option to tell mtr to display information from ICMP extensions for MPLS (RFC 4950) that are encoded in the response packets.' + }, + 'interface': { + 'mtr': '{command} --interface {value}', + 'type': '<interface>', + 'helpfunction': interface_list, + 'help': 'Use the network interface with a specific name for sending network probes. This can be useful when you have multiple network interfaces with routes to your destination, for example both wired Ethernet and WiFi, and wish to test a particular interface.' + }, + 'address': { + 'mtr': '{command} --address {value}', + 'type': '<x.x.x.x> <h:h:h:h:h:h:h:h>', + 'help': 'Use this option to bind the outgoing socket to ADDRESS, so that all packets will be sent with ADDRESS as source address.' + }, + 'first-ttl': { + 'mtr': '{command} --first-ttl {value}', + 'type': '<num>', + 'help': 'Specifies with what TTL to start. Defaults to 1.' + }, + 'max-ttl': { + 'mtr': '{command} --max-ttl {value}', + 'type': '<num>', + 'help': 'Specifies the maximum number of hops or max time-to-live value mtr will probe. Default is 30.' + }, + 'max-unknown': { + 'mtr': '{command} --max-unknown {value}', + 'type': '<num>', + 'help': 'Specifies the maximum unknown host. Default is 5.' + }, + 'udp': { + 'mtr': '{command} --udp', + 'type': 'noarg', + 'help': 'Use UDP datagrams instead of ICMP ECHO.' + }, + 'tcp': { + 'mtr': '{command} --tcp', + 'type': 'noarg', + 'help': ' Use TCP SYN packets instead of ICMP ECHO. PACKETSIZE is ignored, since SYN packets can not contain data.' + }, + 'sctp': { + 'mtr': '{command} --sctp', + 'type': 'noarg', + 'help': 'Use Stream Control Transmission Protocol packets instead of ICMP ECHO.' + }, + 'port': { + 'mtr': '{command} --port {value}', + 'type': '<port>', + 'help': 'The target port number for TCP/SCTP/UDP traces.' + }, + 'localport': { + 'mtr': '{command} --localport {value}', + 'type': '<port>', + 'help': 'The source port number for UDP traces.' + }, + 'timeout': { + 'mtr': '{command} --timeout {value}', + 'type': '<num>', + 'help': ' The number of seconds to keep probe sockets open before giving up on the connection.' + }, + 'mark': { + 'mtr': '{command} --mark {value}', + 'type': '<num>', + 'help': ' Set the mark for each packet sent through this socket similar to the netfilter MARK target but socket-based. MARK is 32 unsigned integer.' + }, + 'vrf': { + 'mtr': 'sudo ip vrf exec {value} {command}', + 'type': '<vrf>', + 'help': 'Use specified VRF table', + 'helpfunction': vrf_list, + 'dflt': 'default' + } + } + +mtr = { + 4: '/bin/mtr -4', + 6: '/bin/mtr -6', +} + +class List(list): + def first(self): + return self.pop(0) if self else '' + + def last(self): + return self.pop() if self else '' + + def prepend(self, value): + self.insert(0, value) + + +def completion_failure(option: str) -> None: + """ + Shows failure message after TAB when option is wrong + :param option: failure option + :type str: + """ + sys.stderr.write('\n\n Invalid option: {}\n\n'.format(option)) + sys.stdout.write('<nocomps>') + sys.exit(1) + + +def expension_failure(option, completions): + reason = 'Ambiguous' if completions else 'Invalid' + sys.stderr.write( + '\n\n {} command: {} [{}]\n\n'.format(reason, ' '.join(sys.argv), + option)) + if completions: + sys.stderr.write(' Possible completions:\n ') + sys.stderr.write('\n '.join(completions)) + sys.stderr.write('\n') + sys.stdout.write('<nocomps>') + sys.exit(1) + + +def complete(prefix): + return [o for o in options if o.startswith(prefix)] + + +def convert(command, args): + while args: + shortname = args.first() + longnames = complete(shortname) + if len(longnames) != 1: + expension_failure(shortname, longnames) + longname = longnames[0] + if options[longname]['type'] == 'noarg': + command = options[longname]['mtr'].format( + command=command, value='') + elif not args: + sys.exit(f'mtr: missing argument for {longname} option') + else: + command = options[longname]['mtr'].format( + command=command, value=args.first()) + return command + + +if __name__ == '__main__': + args = List(sys.argv[1:]) + host = args.first() + + if not host: + sys.exit("mtr: Missing host") + + + if host == '--get-options' or host == '--get-options-nested': + if host == '--get-options-nested': + args.first() # pop monitor + args.first() # pop mtr | traceroute + args.first() # pop IP + usedoptionslist = [] + while args: + option = args.first() # pop option + matched = complete(option) # get option parameters + usedoptionslist.append(option) # list of used options + # Select options + if not args: + # remove from Possible completions used options + for o in usedoptionslist: + if o in matched: + matched.remove(o) + sys.stdout.write(' '.join(matched)) + sys.exit(0) + + if len(matched) > 1: + sys.stdout.write(' '.join(matched)) + sys.exit(0) + # If option doesn't have value + if matched: + if options[matched[0]]['type'] == 'noarg': + continue + else: + # Unexpected option + completion_failure(option) + + value = args.first() # pop option's value + if not args: + matched = complete(option) + helplines = options[matched[0]]['type'] + # Run helpfunction to get list of possible values + if 'helpfunction' in options[matched[0]]: + result = options[matched[0]]['helpfunction']() + if result: + helplines = '\n' + ' '.join(result) + sys.stdout.write(helplines) + sys.exit(0) + + for name, option in options.items(): + if 'dflt' in option and name not in args: + args.append(name) + args.append(option['dflt']) + + try: + ip = socket.gethostbyname(host) + except UnicodeError: + sys.exit(f'mtr: Unknown host: {host}') + except socket.gaierror: + ip = host + + try: + version = ipaddress.ip_address(ip).version + except ValueError: + sys.exit(f'mtr: Unknown host: {host}') + + command = convert(mtr[version], args) + call(f'{command} --curses --displaymode 0 {host}') diff --git a/src/op_mode/multicast.py b/src/op_mode/multicast.py new file mode 100644 index 0000000..0666f8a --- /dev/null +++ b/src/op_mode/multicast.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json +import sys +import typing + +from tabulate import tabulate +from vyos.utils.process import cmd + +import vyos.opmode + +ArgFamily = typing.Literal['inet', 'inet6'] + +def _get_raw_data(family, interface=None): + tmp = 'ip -4' + if family == 'inet6': + tmp = 'ip -6' + tmp = f'{tmp} -j maddr show' + if interface: + tmp = f'{tmp} dev {interface}' + output = cmd(tmp) + data = json.loads(output) + if not data: + return [] + return data + +def _get_formatted_output(raw_data): + data_entries = [] + + # sort result by interface name + for interface in sorted(raw_data, key=lambda x: x['ifname']): + for address in interface['maddr']: + tmp = [] + tmp.append(interface['ifname']) + tmp.append(address['family']) + tmp.append(address['address']) + + data_entries.append(tmp) + + headers = ["Interface", "Family", "Address"] + output = tabulate(data_entries, headers, numalign="left") + return output + +def show_group(raw: bool, family: ArgFamily, interface: typing.Optional[str]): + multicast_data = _get_raw_data(family=family, interface=interface) + if raw: + return multicast_data + else: + return _get_formatted_output(multicast_data) + +if __name__ == "__main__": + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py new file mode 100644 index 0000000..c6cf477 --- /dev/null +++ b/src/op_mode/nat.py @@ -0,0 +1,365 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import jmespath +import json +import sys +import xmltodict +import typing + +from tabulate import tabulate + +import vyos.opmode + +from vyos.configquery import ConfigTreeQuery +from vyos.utils.process import cmd +from vyos.utils.dict import dict_search + +ArgDirection = typing.Literal['source', 'destination'] +ArgFamily = typing.Literal['inet', 'inet6'] + + +def _get_xml_translation(direction, family, address=None): + """ + Get conntrack XML output --src-nat|--dst-nat + """ + if direction == 'source': + opt = '--src-nat' + if direction == 'destination': + opt = '--dst-nat' + tmp = f'conntrack --dump --family {family} {opt} --output xml' + if address: + tmp += f' --src {address}' + return cmd(tmp) + + +def _xml_to_dict(xml): + """ + Convert XML to dictionary + Return: dictionary + """ + parse = xmltodict.parse(xml, attr_prefix='') + # If only one conntrack entry we must change dict + if 'meta' in parse['conntrack']['flow']: + return dict(conntrack={'flow': [parse['conntrack']['flow']]}) + return parse + + +def _get_json_data(direction, family): + """ + Get NAT format JSON + """ + if direction == 'source': + chain = 'POSTROUTING' + if direction == 'destination': + chain = 'PREROUTING' + family = 'ip6' if family == 'inet6' else 'ip' + return cmd(f'nft --json list chain {family} vyos_nat {chain}') + + +def _get_raw_data_rules(direction, family): + """Get interested rules + :returns dict + """ + data = _get_json_data(direction, family) + data_dict = json.loads(data) + rules = [] + for rule in data_dict['nftables']: + if 'rule' in rule and 'comment' in rule['rule']: + rules.append(rule) + return rules + + +def _get_raw_translation(direction, family, address=None): + """ + Return: dictionary + """ + xml = _get_xml_translation(direction, family, address) + if len(xml) == 0: + output = {'conntrack': + { + 'error': True, + 'reason': 'entries not found' + } + } + return output + return _xml_to_dict(xml) + + +def _get_formatted_output_rules(data, direction, family): + + + def _get_ports_for_output(rules): + """ + Return: string of configured ports + """ + ports = [] + if 'set' in rules: + for index, port in enumerate(rules['set']): + if 'range' in str(rules['set'][index]): + output = rules['set'][index]['range'] + output = '-'.join(map(str, output)) + else: + output = str(port) + ports.append(output) + # When NAT rule contains port range or single port + # JSON will not contain keyword 'set' + elif 'range' in rules: + output = rules['range'] + output = '-'.join(map(str, output)) + ports.append(output) + else: + output = rules['right'] + ports.append(str(output)) + result = ','.join(ports) + # Handle case where ports in NAT rule are negated + if rules['op'] == '!=': + result = '!' + result + return(result) + + # Add default values before loop + sport, dport, proto = 'any', 'any', 'any' + saddr = '::/0' if family == 'inet6' else '0.0.0.0/0' + daddr = '::/0' if family == 'inet6' else '0.0.0.0/0' + + data_entries = [] + for rule in data: + if 'comment' in rule['rule']: + comment = rule.get('rule').get('comment') + rule_number = comment.split('-')[-1] + rule_number = rule_number.split(' ')[0] + if 'expr' in rule['rule']: + interface = rule.get('rule').get('expr')[0].get('match').get('right') \ + if jmespath.search('rule.expr[*].match.left.meta', rule) else 'any' + for index, match in enumerate(jmespath.search('rule.expr[*].match', rule)): + if 'payload' in match['left']: + # Handle NAT rule containing comma-seperated list of ports + if (isinstance(match['right'], dict) and + ('prefix' in match['right'] or 'set' in match['right'] or + 'range' in match['right'])): + # Merge dict src/dst l3_l4 parameters + my_dict = {**match['left']['payload'], **match['right']} + my_dict['op'] = match['op'] + op = '!' if my_dict.get('op') == '!=' else '' + proto = my_dict.get('protocol').upper() + if my_dict['field'] == 'saddr': + saddr = f'{op}{my_dict["prefix"]["addr"]}/{my_dict["prefix"]["len"]}' + elif my_dict['field'] == 'daddr': + daddr = f'{op}{my_dict["prefix"]["addr"]}/{my_dict["prefix"]["len"]}' + elif my_dict['field'] == 'sport': + sport = _get_ports_for_output(my_dict) + elif my_dict['field'] == 'dport': + dport = _get_ports_for_output(my_dict) + # Handle NAT rule containing a single port + else: + field = jmespath.search('left.payload.field', match) + if field == 'saddr': + saddr = match.get('right') + elif field == 'daddr': + daddr = match.get('right') + elif field == 'sport': + sport = _get_ports_for_output(match) + elif field == 'dport': + dport = _get_ports_for_output(match) + else: + saddr = '::/0' if family == 'inet6' else '0.0.0.0/0' + daddr = '::/0' if family == 'inet6' else '0.0.0.0/0' + sport = 'any' + dport = 'any' + proto = 'any' + + source = f'''{saddr} +sport {sport}''' + destination = f'''{daddr} +dport {dport}''' + + if jmespath.search('left.payload.field', match) == 'protocol': + field_proto = match.get('right').upper() + + for expr in rule.get('rule').get('expr'): + if 'snat' in expr: + translation = dict_search('snat.addr', expr) + if expr['snat'] and 'port' in expr['snat']: + if jmespath.search('snat.port.range', expr): + port = dict_search('snat.port.range', expr) + port = '-'.join(map(str, port)) + else: + port = expr['snat']['port'] + translation = f'''{translation} +port {port}''' + + elif 'masquerade' in expr: + translation = 'masquerade' + if expr['masquerade'] and 'port' in expr['masquerade']: + if jmespath.search('masquerade.port.range', expr): + port = dict_search('masquerade.port.range', expr) + port = '-'.join(map(str, port)) + else: + port = expr['masquerade']['port'] + + translation = f'''{translation} +port {port}''' + elif 'dnat' in expr: + translation = dict_search('dnat.addr', expr) + if expr['dnat'] and 'port' in expr['dnat']: + if jmespath.search('dnat.port.range', expr): + port = dict_search('dnat.port.range', expr) + port = '-'.join(map(str, port)) + else: + port = expr['dnat']['port'] + translation = f'''{translation} +port {port}''' + else: + translation = 'exclude' + # Overwrite match loop 'proto' if specified filed 'protocol' exist + if 'protocol' in jmespath.search('rule.expr[*].match.left.payload.field', rule): + proto = jmespath.search('rule.expr[0].match.right', rule).upper() + + data_entries.append([rule_number, source, destination, proto, interface, translation]) + + interface_header = 'Out-Int' if direction == 'source' else 'In-Int' + headers = ["Rule", "Source", "Destination", "Proto", interface_header, "Translation"] + output = tabulate(data_entries, headers, numalign="left") + return output + + +def _get_formatted_output_statistics(data, direction): + data_entries = [] + for rule in data: + if 'comment' in rule['rule']: + comment = rule.get('rule').get('comment') + rule_number = comment.split('-')[-1] + rule_number = rule_number.split(' ')[0] + if 'expr' in rule['rule']: + interface = rule.get('rule').get('expr')[0].get('match').get('right') \ + if jmespath.search('rule.expr[*].match.left.meta', rule) else 'any' + packets = jmespath.search('rule.expr[*].counter.packets | [0]', rule) + _bytes = jmespath.search('rule.expr[*].counter.bytes | [0]', rule) + data_entries.append([rule_number, packets, _bytes, interface]) + headers = ["Rule", "Packets", "Bytes", "Interface"] + output = tabulate(data_entries, headers, numalign="left") + return output + + +def _get_formatted_translation(dict_data, nat_direction, family, verbose): + data_entries = [] + if 'error' in dict_data['conntrack']: + return 'Entries not found' + for entry in dict_data['conntrack']['flow']: + orig_src, orig_dst, orig_sport, orig_dport = {}, {}, {}, {} + reply_src, reply_dst, reply_sport, reply_dport = {}, {}, {}, {} + proto = {} + for meta in entry['meta']: + direction = meta['direction'] + if direction in ['original']: + if 'layer3' in meta: + orig_src = meta['layer3']['src'] + orig_dst = meta['layer3']['dst'] + if 'layer4' in meta: + if meta.get('layer4').get('sport'): + orig_sport = meta['layer4']['sport'] + if meta.get('layer4').get('dport'): + orig_dport = meta['layer4']['dport'] + proto = meta['layer4']['protoname'] + if direction in ['reply']: + if 'layer3' in meta: + reply_src = meta['layer3']['src'] + reply_dst = meta['layer3']['dst'] + if 'layer4' in meta: + if meta.get('layer4').get('sport'): + reply_sport = meta['layer4']['sport'] + if meta.get('layer4').get('dport'): + reply_dport = meta['layer4']['dport'] + proto = meta['layer4']['protoname'] + if direction == 'independent': + conn_id = meta['id'] + timeout = meta.get('timeout', 'n/a') + orig_src = f'{orig_src}:{orig_sport}' if orig_sport else orig_src + orig_dst = f'{orig_dst}:{orig_dport}' if orig_dport else orig_dst + reply_src = f'{reply_src}:{reply_sport}' if reply_sport else reply_src + reply_dst = f'{reply_dst}:{reply_dport}' if reply_dport else reply_dst + state = meta['state'] if 'state' in meta else '' + mark = meta.get('mark', '') + zone = meta['zone'] if 'zone' in meta else '' + if nat_direction == 'source': + tmp = [orig_src, reply_dst, proto, timeout, mark, zone] + data_entries.append(tmp) + elif nat_direction == 'destination': + tmp = [orig_dst, reply_src, proto, timeout, mark, zone] + data_entries.append(tmp) + + headers = ["Pre-NAT", "Post-NAT", "Proto", "Timeout", "Mark", "Zone"] + output = tabulate(data_entries, headers, numalign="left") + return output + + +def _verify(func): + """Decorator checks if NAT config exists""" + from functools import wraps + + @wraps(func) + def _wrapper(*args, **kwargs): + config = ConfigTreeQuery() + base = 'nat66' if 'inet6' in sys.argv[1:] else 'nat' + if not config.exists(base): + raise vyos.opmode.UnconfiguredSubsystem(f'{base.upper()} is not configured') + return func(*args, **kwargs) + return _wrapper + + +@_verify +def show_rules(raw: bool, direction: ArgDirection, family: ArgFamily): + nat_rules = _get_raw_data_rules(direction, family) + if raw: + return nat_rules + else: + return _get_formatted_output_rules(nat_rules, direction, family) + + +@_verify +def show_statistics(raw: bool, direction: ArgDirection, family: ArgFamily): + nat_statistics = _get_raw_data_rules(direction, family) + if raw: + return nat_statistics + else: + return _get_formatted_output_statistics(nat_statistics, direction) + + +@_verify +def show_translations(raw: bool, direction: ArgDirection, + family: ArgFamily, + address: typing.Optional[str], + verbose: typing.Optional[bool]): + family = 'ipv6' if family == 'inet6' else 'ipv4' + nat_translation = _get_raw_translation(direction, + family=family, + address=address) + + if raw: + return nat_translation + else: + return _get_formatted_translation(nat_translation, direction, family, + verbose) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/neighbor.py b/src/op_mode/neighbor.py new file mode 100644 index 0000000..8b3c45c --- /dev/null +++ b/src/op_mode/neighbor.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Sample output of `ip --json neigh list`: +# +# [ +# { +# "dst": "192.168.1.1", +# "dev": "eth0", # Missing if `dev ...` option is used +# "lladdr": "00:aa:bb:cc:dd:ee", # May be missing for failed entries +# "state": [ +# "REACHABLE" +# ] +# }, +# ] + +import sys +import typing + +import vyos.opmode +from vyos.utils.network import interface_exists + +ArgFamily = typing.Literal['inet', 'inet6'] +ArgState = typing.Literal['reachable', 'stale', 'failed', 'permanent'] + +def get_raw_data(family, interface=None, state=None): + from json import loads + from vyos.utils.process import cmd + + if interface: + if not interface_exists(interface): + raise ValueError(f"Interface '{interface}' does not exist in the system") + interface = f"dev {interface}" + else: + interface = "" + + if state: + state = f"nud {state}" + else: + state = "" + + neigh_cmd = f"ip --family {family} --json neighbor list {interface} {state}" + + data = loads(cmd(neigh_cmd)) + + return data + +def format_neighbors(neighs, interface=None): + from tabulate import tabulate + + def entry_to_list(e, intf=None): + dst = e["dst"] + + # State is always a list in the iproute2 output + state = ", ".join(e["state"]) + + # Link layer address is absent from e.g. FAILED entries + if "lladdr" in e: + lladdr = e["lladdr"] + else: + lladdr = None + + # Device field is absent from outputs of `ip neigh list dev ...` + if "dev" in e: + dev = e["dev"] + elif interface: + dev = interface + else: + raise ValueError("interface is not defined") + + return [dst, dev, lladdr, state] + + neighs = map(entry_to_list, neighs) + + headers = ["Address", "Interface", "Link layer address", "State"] + return tabulate(neighs, headers) + +def show(raw: bool, family: ArgFamily, interface: typing.Optional[str], + state: typing.Optional[ArgState]): + """ Display neighbor table contents """ + data = get_raw_data(family, interface, state=state) + + if raw: + return data + else: + return format_neighbors(data, interface) + +def reset(family: ArgFamily, interface: typing.Optional[str], address: typing.Optional[str]): + from vyos.utils.process import run + + if address and interface: + raise ValueError("interface and address parameters are mutually exclusive") + elif address: + run(f"""ip --family {family} neighbor flush to {address}""") + elif interface: + run(f"""ip --family {family} neighbor flush dev {interface}""") + else: + # Flush an entire neighbor table + run(f"""ip --family {family} neighbor flush""") + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) + diff --git a/src/op_mode/nhrp.py b/src/op_mode/nhrp.py new file mode 100644 index 0000000..e66f330 --- /dev/null +++ b/src/op_mode/nhrp.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import tabulate +import vyos.opmode + +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.dict import colon_separated_to_dict + + +def _get_formatted_output(output_dict: dict) -> str: + """ + Create formatted table for CLI output + :param output_dict: dictionary for API + :type output_dict: dict + :return: tabulate string + :rtype: str + """ + print(f"Status: {output_dict['Status']}") + output: str = tabulate.tabulate(output_dict['routes'], headers='keys', + numalign="left") + return output + + +def _get_formatted_dict(output_string: str) -> dict: + """ + Format string returned from CMD to API list + :param output_string: String received by CMD + :type output_string: str + :return: dictionary for API + :rtype: dict + """ + formatted_dict: dict = { + 'Status': '', + 'routes': [] + } + output_list: list = output_string.split('\n\n') + for list_a in output_list: + output_dict = colon_separated_to_dict(list_a, True) + if 'Status' in output_dict: + formatted_dict['Status'] = output_dict['Status'] + else: + formatted_dict['routes'].append(output_dict) + return formatted_dict + + +def show_interface(raw: bool): + """ + Command 'show nhrp interface' + :param raw: if API + :type raw: bool + """ + if not process_named_running('opennhrp'): + raise vyos.opmode.UnconfiguredSubsystem('OpenNHRP is not running.') + interface_string: str = cmd('sudo opennhrpctl interface show') + interface_dict: dict = _get_formatted_dict(interface_string) + if raw: + return interface_dict + else: + return _get_formatted_output(interface_dict) + + +def show_tunnel(raw: bool): + """ + Command 'show nhrp tunnel' + :param raw: if API + :type raw: bool + """ + if not process_named_running('opennhrp'): + raise vyos.opmode.UnconfiguredSubsystem('OpenNHRP is not running.') + tunnel_string: str = cmd('sudo opennhrpctl show') + tunnel_dict: list = _get_formatted_dict(tunnel_string) + if raw: + return tunnel_dict + else: + return _get_formatted_output(tunnel_dict) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/ntp.py b/src/op_mode/ntp.py new file mode 100644 index 0000000..6ec0fed --- /dev/null +++ b/src/op_mode/ntp.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import csv +import sys +from itertools import chain + +import vyos.opmode +from vyos.configquery import ConfigTreeQuery +from vyos.utils.process import cmd + +def _get_raw_data(command: str) -> dict: + # Returns returns chronyc output as a dictionary + + # Initialize dictionary keys to align with output of + # chrony -c. From some commands, its -c switch outputs + # more parameters, make sure to include them all below. + # See to chronyc(1) for definition of key variables + match command: + case "chronyc -c activity": + keys: list = [ + 'sources_online', + 'sources_offline', + 'sources_doing_burst_return_online', + 'sources_doing_burst_return_offline', + 'sources_with_unknown_address' + ] + + case "chronyc -c sources": + keys: list = [ + 'm', + 's', + 'name_ip_address', + 'stratum', + 'poll', + 'reach', + 'last_rx', + 'last_sample_adj_offset', + 'last_sample_mes_offset', + 'last_sample_est_error' + ] + + case "chronyc -c sourcestats": + keys: list = [ + 'name_ip_address', + 'np', + 'nr', + 'span', + 'frequency', + 'freq_skew', + 'offset', + 'std_dev' + ] + + case "chronyc -c tracking": + keys: list = [ + 'ref_id', + 'ref_id_name', + 'stratum', + 'ref_time', + 'system_time', + 'last_offset', + 'rms_offset', + 'frequency', + 'residual_freq', + 'skew', + 'root_delay', + 'root_dispersion', + 'update_interval', + 'leap_status' + ] + + case _: + raise ValueError(f"Raw mode: of {command} is not implemented") + + # Get -c option command line output, splitlines, + # and save comma-separated values as a flat list + output = cmd(command).splitlines() + values = csv.reader(output) + values = list(chain.from_iterable(values)) + + # Divide values into chunks of size keys and transpose + if len(values) > len(keys): + values = _chunk_list(values,keys) + values = zip(*values) + + return dict(zip(keys, values)) + +def _chunk_list(in_list, n): + # Yields successive n-sized chunks from in_list + for i in range(0, len(in_list), len(n)): + yield in_list[i:i + len(n)] + +def _is_configured(): + # Check if ntp is configured + config = ConfigTreeQuery() + if not config.exists("service ntp"): + raise vyos.opmode.UnconfiguredSubsystem("NTP service is not enabled.") + +def _extend_command_vrf(): + config = ConfigTreeQuery() + if config.exists('service ntp vrf'): + vrf = config.value('service ntp vrf') + return f'ip vrf exec {vrf} ' + return '' + + +def show_activity(raw: bool): + _is_configured() + command = f'chronyc' + + if raw: + command += f" -c activity" + return _get_raw_data(command) + else: + command = _extend_command_vrf() + command + command += f" activity" + return cmd(command) + +def show_sources(raw: bool): + _is_configured() + command = f'chronyc' + + if raw: + command += f" -c sources" + return _get_raw_data(command) + else: + command = _extend_command_vrf() + command + command += f" sources -v" + return cmd(command) + +def show_tracking(raw: bool): + _is_configured() + command = f'chronyc' + + if raw: + command += f" -c tracking" + return _get_raw_data(command) + else: + command = _extend_command_vrf() + command + command += f" tracking" + return cmd(command) + +def show_sourcestats(raw: bool): + _is_configured() + command = f'chronyc' + + if raw: + command += f" -c sourcestats" + return _get_raw_data(command) + else: + command = _extend_command_vrf() + command + command += f" sourcestats -v" + return cmd(command) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/openconnect-control.py b/src/op_mode/openconnect-control.py new file mode 100644 index 0000000..b70d4fa --- /dev/null +++ b/src/op_mode/openconnect-control.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import argparse +import json + +from tabulate import tabulate + +from vyos.config import Config +from vyos.utils.process import popen +from vyos.utils.process import run +from vyos.utils.process import DEVNULL + +occtl = '/usr/bin/occtl' +occtl_socket = '/run/ocserv/occtl.socket' + +def show_sessions(): + out, code = popen("sudo {0} -j -s {1} show users".format(occtl, occtl_socket),stderr=DEVNULL) + if code: + sys.exit('Cannot get openconnect users information') + else: + headers = ["interface", "username", "ip", "remote IP", "RX", "TX", "state", "uptime"] + sessions = json.loads(out) + ses_list = [] + for ses in sessions: + ses_list.append([ses["Device"], ses["Username"], ses["IPv4"], ses["Remote IP"], ses["_RX"], ses["_TX"], ses["State"], ses["_Connected at"]]) + if len(ses_list) > 0: + print(tabulate(ses_list, headers)) + else: + print("No active openconnect sessions") + +def is_ocserv_configured(): + if not Config().exists_effective('vpn openconnect'): + print("vpn openconnect server is not configured") + sys.exit(1) + +def main(): + #parese args + parser = argparse.ArgumentParser() + parser.add_argument('--action', help='Control action', required=True) + parser.add_argument('--selector', help='Selector username|ifname|sid', required=False) + parser.add_argument('--target', help='Target must contain username|ifname|sid', required=False) + args = parser.parse_args() + + + # Check is Openconnect server configured + is_ocserv_configured() + + if args.action == "restart": + run("sudo systemctl restart ocserv.service") + sys.exit(0) + elif args.action == "show_sessions": + show_sessions() + +if __name__ == '__main__': + main() diff --git a/src/op_mode/openconnect.py b/src/op_mode/openconnect.py new file mode 100644 index 0000000..62c683e --- /dev/null +++ b/src/op_mode/openconnect.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import json + +from tabulate import tabulate +from vyos.configquery import ConfigTreeQuery +from vyos.utils.process import rc_cmd + +import vyos.opmode + + +occtl = '/usr/bin/occtl' +occtl_socket = '/run/ocserv/occtl.socket' + + +def _get_raw_data_sessions(): + rc, out = rc_cmd(f'sudo {occtl} --json --socket-file {occtl_socket} show users') + if rc != 0: + raise vyos.opmode.DataUnavailable(out) + + sessions = json.loads(out) + return sessions + + +def _get_formatted_sessions(data): + headers = ["Interface", "Username", "IP", "Remote IP", "RX", "TX", "State", "Uptime"] + ses_list = [] + for ses in data: + ses_list.append([ + ses.get("Device", '(none)'), ses.get("Username", '(none)'), + ses.get("IPv4", '(none)'), ses.get("Remote IP", '(none)'), + ses.get("_RX", '(none)'), ses.get("_TX", '(none)'), + ses.get("State", '(none)'), ses.get("_Connected at", '(none)') + ]) + if len(ses_list) > 0: + output = tabulate(ses_list, headers) + else: + output = 'No active openconnect sessions' + return output + + +def show_sessions(raw: bool): + config = ConfigTreeQuery() + if not config.exists('vpn openconnect'): + raise vyos.opmode.UnconfiguredSubsystem('Openconnect is not configured') + + openconnect_data = _get_raw_data_sessions() + if raw: + return openconnect_data + return _get_formatted_sessions(openconnect_data) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/openvpn.py b/src/op_mode/openvpn.py new file mode 100644 index 0000000..0928739 --- /dev/null +++ b/src/op_mode/openvpn.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +import json +import os +import sys +import typing +from tabulate import tabulate + +import vyos.opmode +from vyos.utils.convert import bytes_to_human +from vyos.utils.commit import commit_in_progress +from vyos.utils.process import call +from vyos.utils.process import rc_cmd +from vyos.config import Config + +ArgMode = typing.Literal['client', 'server', 'site_to_site'] + +def _get_tunnel_address(peer_host, peer_port, status_file): + peer = peer_host + ':' + peer_port + lst = [] + + with open(status_file, 'r') as f: + lines = f.readlines() + for line in lines: + if peer in line: + lst.append(line) + + # filter out subnet entries if iroute: + # in the case that one sets, say: + # [ ..., 'vtun10', 'server', 'client', 'client1', 'subnet','10.10.2.0/25'] + # the status file will have an entry: + # 10.10.2.0/25,client1,... + lst = [l for l in lst[1:] if '/' not in l.split(',')[0]] + + if lst: + tunnel_ip = lst[0].split(',')[0] + + return tunnel_ip + + return 'n/a' + +def _get_interface_status(mode: str, interface: str) -> dict: + status_file = f'/run/openvpn/{interface}.status' + + data: dict = { + 'mode': mode, + 'intf': interface, + 'local_host': '', + 'local_port': '', + 'date': '', + 'clients': [], + } + + if not os.path.exists(status_file): + return data + + with open(status_file, 'r') as f: + lines = f.readlines() + for line_no, line in enumerate(lines): + # remove trailing newline character first + line = line.rstrip('\n') + + # check first line header + if line_no == 0: + if mode == 'server': + if not line == 'OpenVPN CLIENT LIST': + raise vyos.opmode.InternalError('Expected "OpenVPN CLIENT LIST"') + else: + if not line == 'OpenVPN STATISTICS': + raise vyos.opmode.InternalError('Expected "OpenVPN STATISTICS"') + + continue + + # second line informs us when the status file has been last updated + if line_no == 1: + data['date'] = line.lstrip('Updated,').rstrip('\n') + continue + + if mode == 'server': + # for line_no > 1, lines appear as follows: + # + # Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since + # client1,172.18.202.10:55904,2880587,2882653,Fri Aug 23 16:25:48 2019 + # client3,172.18.204.10:41328,2850832,2869729,Fri Aug 23 16:25:43 2019 + # client2,172.18.203.10:48987,2856153,2871022,Fri Aug 23 16:25:45 2019 + # ... + # ROUTING TABLE + # ... + if line_no >= 3: + # indicator that there are no more clients + if line == 'ROUTING TABLE': + break + # otherwise, get client data + remote = (line.split(',')[1]).rsplit(':', maxsplit=1) + + client = { + 'name': line.split(',')[0], + 'remote_host': remote[0], + 'remote_port': remote[1], + 'tunnel': 'N/A', + 'rx_bytes': bytes_to_human(int(line.split(',')[2]), + precision=1), + 'tx_bytes': bytes_to_human(int(line.split(',')[3]), + precision=1), + 'online_since': line.split(',')[4] + } + client['tunnel'] = _get_tunnel_address(client['remote_host'], + client['remote_port'], + status_file) + data['clients'].append(client) + continue + else: # mode == 'client' or mode == 'site-to-site' + if line_no == 2: + client = { + 'name': 'N/A', + 'remote_host': 'N/A', + 'remote_port': 'N/A', + 'tunnel': 'N/A', + 'rx_bytes': bytes_to_human(int(line.split(',')[1]), + precision=1), + 'tx_bytes': '', + 'online_since': 'N/A' + } + continue + + if line_no == 3: + client['tx_bytes'] = bytes_to_human(int(line.split(',')[1]), + precision=1) + data['clients'].append(client) + break + + return data + + +def _get_interface_state(iface): + rc, out = rc_cmd(f'ip --json link show dev {iface}') + try: + data = json.loads(out) + except: + return 'DOWN' + return data[0].get('operstate', 'DOWN') + + +def _get_interface_description(iface): + rc, out = rc_cmd(f'ip --json link show dev {iface}') + try: + data = json.loads(out) + except: + return '' + return data[0].get('ifalias', '') + + +def _get_raw_data(mode: str) -> list: + data: list = [] + conf = Config() + conf_dict = conf.get_config_dict(['interfaces', 'openvpn'], + get_first_key=True) + if not conf_dict: + return data + + interfaces = [x for x in list(conf_dict) if + conf_dict[x]['mode'].replace('-', '_') == mode] + for intf in interfaces: + d = _get_interface_status(mode, intf) + d['state'] = _get_interface_state(intf) + d['description'] = _get_interface_description(intf) + d['local_host'] = conf_dict[intf].get('local-host', '') + d['local_port'] = conf_dict[intf].get('local-port', '') + if conf.exists(f'interfaces openvpn {intf} server client'): + d['configured_clients'] = conf.list_nodes(f'interfaces openvpn {intf} server client') + if mode in ['client', 'site_to_site']: + for client in d['clients']: + if 'shared-secret-key-file' in list(conf_dict[intf]): + client['name'] = 'None (PSK)' + client['remote_host'] = conf_dict[intf].get('remote-host', [''])[0] + client['remote_port'] = conf_dict[intf].get('remote-port', '1194') + data.append(d) + + return data + +def _format_openvpn(data: list) -> str: + if not data: + out = 'No OpenVPN interfaces configured' + return out + + headers = ['Client CN', 'Remote Host', 'Tunnel IP', 'Local Host', + 'TX bytes', 'RX bytes', 'Connected Since'] + + out = '' + for d in data: + data_out = [] + intf = d['intf'] + l_host = d['local_host'] + l_port = d['local_port'] + out += f'\nOpenVPN status on {intf}\n\n' + for client in d['clients']: + r_host = client['remote_host'] + r_port = client['remote_port'] + + name = client['name'] + remote = r_host + ':' + r_port if r_host and r_port else 'N/A' + tunnel = client['tunnel'] + local = l_host + ':' + l_port if l_host and l_port else 'N/A' + tx_bytes = client['tx_bytes'] + rx_bytes = client['rx_bytes'] + online_since = client['online_since'] + data_out.append([name, remote, tunnel, local, tx_bytes, + rx_bytes, online_since]) + + out += tabulate(data_out, headers) + out += "\n" + + return out + +def show(raw: bool, mode: ArgMode) -> typing.Union[list,str]: + openvpn_data = _get_raw_data(mode) + + if raw: + return openvpn_data + + return _format_openvpn(openvpn_data) + +def reset(interface: str): + if os.path.isfile(f'/run/openvpn/{interface}.conf'): + if commit_in_progress(): + raise vyos.opmode.CommitInProgress('Retry OpenVPN reset: commit in progress.') + call(f'systemctl restart openvpn@{interface}.service') + else: + raise vyos.opmode.IncorrectValue(f'OpenVPN interface "{interface}" does not exist!') + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/otp.py b/src/op_mode/otp.py new file mode 100644 index 0000000..a4ab9b2 --- /dev/null +++ b/src/op_mode/otp.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 + +# Copyright 2017, 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + + +import sys +import os +import vyos.opmode +from jinja2 import Template +from vyos.config import Config +from vyos.utils.process import popen + + +users_otp_template = Template(""" +{% if info == "full" %} +# You can share it with the user, he just needs to scan the QR in his OTP app +# username: {{username}} +# OTP KEY: {{key_base32}} +# OTP URL: {{otp_url}} +{{qrcode}} +# To add this OTP key to configuration, run the following commands: +set system login user {{username}} authentication otp key '{{key_base32}}' +{% if rate_limit != "3" %} +set system login user {{username}} authentication otp rate-limit '{{rate_limit}}' +{% endif %} +{% if rate_time != "30" %} +set system login user {{username}} authentication otp rate-time '{{rate_time}}' +{% endif %} +{% if window_size != "3" %} +set system login user {{username}} authentication otp window-size '{{window_size}}' +{% endif %} +{% elif info == "key-b32" %} +# OTP key in Base32 for system user {{username}}: +{{key_base32}} +{% elif info == "qrcode" %} +# QR code for system user '{{username}}' +{{qrcode}} +{% elif info == "uri" %} +# URI for system user '{{username}}' +{{otp_url}} +{% endif %} +""", trim_blocks=True, lstrip_blocks=True) + + +def _check_uname_otp(username:str): + """ + Check if "username" exists and have an OTP key + """ + config = Config() + base_key = ['system', 'login', 'user', username, 'authentication', 'otp', 'key'] + if not config.exists(base_key): + return None + return True + +def _get_login_otp(username: str, info:str): + """ + Retrieve user settings from configuration and set some defaults + """ + config = Config() + base = ['system', 'login', 'user', username] + if not config.exists(base): + return None + user_otp = config.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + result = user_otp['authentication']['otp'] + # Filling in the system and default options + result['info'] = info + result['hostname'] = os.uname()[1] + result['username'] = username + result['key_base32'] = result['key'] + result['otp_length'] = '6' + result['interval'] = '30' + result['token_type'] = 'hotp-time' + if result['token_type'] == 'hotp-time': + token_type_acrn = 'totp' + result['otp_url'] = ''.join(["otpauth://",token_type_acrn,"/",username,"@",\ + result['hostname'],"?secret=",result['key_base32'],"&digits=",\ + result['otp_length'],"&period=",result['interval']]) + result['qrcode'],_ = popen('qrencode -t ansiutf8', input=result['otp_url']) + return result + +def show_login(raw: bool, username: str, info:str): + ''' + Display OTP parameters for <username> + ''' + check_otp = _check_uname_otp(username) + if check_otp: + user_otp_params = _get_login_otp(username, info) + else: + print(f'There is no such user ("{username}") with an OTP key configured') + print('You can use the following command to generate a key for a user:\n') + print(f'generate system login username {username} otp-key hotp-time') + sys.exit(0) + if raw: + return user_otp_params + return users_otp_template.render(user_otp_params) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/ping.py b/src/op_mode/ping.py new file mode 100644 index 0000000..583d879 --- /dev/null +++ b/src/op_mode/ping.py @@ -0,0 +1,282 @@ +#! /usr/bin/env python3 + +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import socket +import ipaddress + +from vyos.utils.network import interface_list +from vyos.utils.network import vrf_list +from vyos.utils.process import call + +options = { + 'audible': { + 'ping': '{command} -a', + 'type': 'noarg', + 'help': 'Make a noise on ping' + }, + 'adaptive': { + 'ping': '{command} -A', + 'type': 'noarg', + 'help': 'Adativly set interpacket interval' + }, + 'allow-broadcast': { + 'ping': '{command} -b', + 'type': 'noarg', + 'help': 'Ping broadcast address' + }, + 'bypass-route': { + 'ping': '{command} -r', + 'type': 'noarg', + 'help': 'Bypass normal routing tables' + }, + 'count': { + 'ping': '{command} -c {value}', + 'type': '<requests>', + 'help': 'Number of requests to send' + }, + 'deadline': { + 'ping': '{command} -w {value}', + 'type': '<seconds>', + 'help': 'Number of seconds before ping exits' + }, + 'do-not-fragment': { + 'ping': '{command} -M do', + 'type': 'noarg', + 'help': 'Set DF-bit flag to 1 for no fragmentation' + }, + 'flood': { + 'ping': 'sudo {command} -f', + 'type': 'noarg', + 'help': 'Send 100 requests per second' + }, + 'interface': { + 'ping': '{command} -I {value}', + 'type': '<interface>', + 'helpfunction': interface_list, + 'help': 'Source interface' + }, + 'interval': { + 'ping': '{command} -i {value}', + 'type': '<seconds>', + 'help': 'Number of seconds to wait between requests' + }, + 'ipv4': { + 'ping': '{command} -4', + 'type': 'noarg', + 'help': 'Use IPv4 only' + }, + 'ipv6': { + 'ping': '{command} -6', + 'type': 'noarg', + 'help': 'Use IPv6 only' + }, + 'mark': { + 'ping': '{command} -m {value}', + 'type': '<fwmark>', + 'help': 'Mark request for special processing' + }, + 'numeric': { + 'ping': '{command} -n', + 'type': 'noarg', + 'help': 'Do not resolve DNS names' + }, + 'no-loopback': { + 'ping': '{command} -L', + 'type': 'noarg', + 'help': 'Supress loopback of multicast pings' + }, + 'pattern': { + 'ping': '{command} -p {value}', + 'type': '<pattern>', + 'help': 'Pattern to fill out the packet' + }, + 'timestamp': { + 'ping': '{command} -D', + 'type': 'noarg', + 'help': 'Print timestamp of output' + }, + 'tos': { + 'ping': '{command} -Q {value}', + 'type': '<tos>', + 'help': 'Mark packets with specified TOS' + }, + 'quiet': { + 'ping': '{command} -q', + 'type': 'noarg', + 'help': 'Only print summary lines' + }, + 'record-route': { + 'ping': '{command} -R', + 'type': 'noarg', + 'help': 'Record route the packet takes' + }, + 'size': { + 'ping': '{command} -s {value}', + 'type': '<bytes>', + 'help': 'Number of bytes to send' + }, + 'source-address': { + 'ping': '{command} -I {value}', + 'type': '<x.x.x.x> <h:h:h:h:h:h:h:h>', + }, + 'ttl': { + 'ping': '{command} -t {value}', + 'type': '<ttl>', + 'help': 'Maximum packet lifetime' + }, + 'vrf': { + 'ping': 'sudo ip vrf exec {value} {command}', + 'type': '<vrf>', + 'help': 'Use specified VRF table', + 'helpfunction': vrf_list, + 'dflt': 'default', + }, + 'verbose': { + 'ping': '{command} -v', + 'type': 'noarg', + 'help': 'Verbose output'} +} + +ping = { + 4: '/bin/ping', + 6: '/bin/ping6', +} + + +class List(list): + def first(self): + return self.pop(0) if self else '' + + def last(self): + return self.pop() if self else '' + + def prepend(self, value): + self.insert(0, value) + + +def completion_failure(option: str) -> None: + """ + Shows failure message after TAB when option is wrong + :param option: failure option + :type str: + """ + sys.stderr.write('\n\n Invalid option: {}\n\n'.format(option)) + sys.stdout.write('<nocomps>') + sys.exit(1) + + +def expension_failure(option, completions): + reason = 'Ambiguous' if completions else 'Invalid' + sys.stderr.write( + '\n\n {} command: {} [{}]\n\n'.format(reason, ' '.join(sys.argv), + option)) + if completions: + sys.stderr.write(' Possible completions:\n ') + sys.stderr.write('\n '.join(completions)) + sys.stderr.write('\n') + sys.stdout.write('<nocomps>') + sys.exit(1) + + +def complete(prefix): + return [o for o in options if o.startswith(prefix)] + + +def convert(command, args): + while args: + shortname = args.first() + longnames = complete(shortname) + if len(longnames) != 1: + expension_failure(shortname, longnames) + longname = longnames[0] + if options[longname]['type'] == 'noarg': + command = options[longname]['ping'].format( + command=command, value='') + elif not args: + sys.exit(f'ping: missing argument for {longname} option') + else: + command = options[longname]['ping'].format( + command=command, value=args.first()) + return command + + +if __name__ == '__main__': + args = List(sys.argv[1:]) + host = args.first() + + if not host: + sys.exit("ping: Missing host") + + if host == '--get-options': + args.first() # pop ping + args.first() # pop IP + usedoptionslist = [] + while args: + option = args.first() # pop option + matched = complete(option) # get option parameters + usedoptionslist.append(option) # list of used options + # Select options + if not args: + # remove from Possible completions used options + for o in usedoptionslist: + if o in matched: + matched.remove(o) + sys.stdout.write(' '.join(matched)) + sys.exit(0) + + if len(matched) > 1: + sys.stdout.write(' '.join(matched)) + sys.exit(0) + # If option doesn't have value + if matched: + if options[matched[0]]['type'] == 'noarg': + continue + else: + # Unexpected option + completion_failure(option) + + value = args.first() # pop option's value + if not args: + matched = complete(option) + helplines = options[matched[0]]['type'] + # Run helpfunction to get list of possible values + if 'helpfunction' in options[matched[0]]: + result = options[matched[0]]['helpfunction']() + if result: + helplines = '\n' + ' '.join(result) + sys.stdout.write(helplines) + sys.exit(0) + + for name, option in options.items(): + if 'dflt' in option and name not in args: + args.append(name) + args.append(option['dflt']) + + try: + ip = socket.gethostbyname(host) + except UnicodeError: + sys.exit(f'ping: Unknown host: {host}') + except socket.gaierror: + ip = host + + try: + version = ipaddress.ip_address(ip).version + except ValueError: + sys.exit(f'ping: Unknown host: {host}') + + command = convert(ping[version], args) + call(f'{command} {host}') diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py new file mode 100644 index 0000000..ab613e5 --- /dev/null +++ b/src/op_mode/pki.py @@ -0,0 +1,1111 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import ipaddress +import os +import re +import sys +import tabulate + +from cryptography import x509 +from cryptography.x509.oid import ExtendedKeyUsageOID + +from vyos.config import Config +from vyos.config import config_dict_mangle_acme +from vyos.pki import encode_certificate, encode_public_key, encode_private_key, encode_dh_parameters +from vyos.pki import get_certificate_fingerprint +from vyos.pki import create_certificate, create_certificate_request, create_certificate_revocation_list +from vyos.pki import create_private_key +from vyos.pki import create_dh_parameters +from vyos.pki import load_certificate, load_certificate_request, load_private_key +from vyos.pki import load_crl, load_dh_parameters, load_public_key +from vyos.pki import verify_certificate +from vyos.utils.io import ask_input +from vyos.utils.io import ask_yes_no +from vyos.utils.misc import install_into_config +from vyos.utils.process import cmd + +CERT_REQ_END = '-----END CERTIFICATE REQUEST-----' +auth_dir = '/config/auth' + +# Helper Functions +conf = Config() +def get_default_values(): + # Fetch default x509 values + base = ['pki', 'x509', 'default'] + x509_defaults = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + return x509_defaults + +def get_config_ca_certificate(name=None): + # Fetch ca certificates from config + base = ['pki', 'ca'] + if not conf.exists(base): + return False + + if name: + base = base + [name] + if not conf.exists(base + ['private', 'key']) or not conf.exists(base + ['certificate']): + return False + + return conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + +def get_config_certificate(name=None): + # Get certificates from config + base = ['pki', 'certificate'] + if not conf.exists(base): + return False + + if name: + base = base + [name] + if not conf.exists(base + ['private', 'key']) or not conf.exists(base + ['certificate']): + return False + + pki = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + if pki: + for certificate in pki: + pki[certificate] = config_dict_mangle_acme(certificate, pki[certificate]) + + return pki + +def get_certificate_ca(cert, ca_certs): + # Find CA certificate for given certificate + if not ca_certs: + return None + + for ca_name, ca_dict in ca_certs.items(): + if 'certificate' not in ca_dict: + continue + + ca_cert = load_certificate(ca_dict['certificate']) + + if not ca_cert: + continue + + if verify_certificate(cert, ca_cert): + return ca_name + return None + +def get_config_revoked_certificates(): + # Fetch revoked certificates from config + ca_base = ['pki', 'ca'] + cert_base = ['pki', 'certificate'] + + certs = [] + + if conf.exists(ca_base): + ca_certificates = conf.get_config_dict(ca_base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + certs.extend(ca_certificates.values()) + + if conf.exists(cert_base): + certificates = conf.get_config_dict(cert_base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + certs.extend(certificates.values()) + + return [cert_dict for cert_dict in certs if 'revoke' in cert_dict] + +def get_revoked_by_serial_numbers(serial_numbers=[]): + # Return serial numbers of revoked certificates + certs_out = [] + certs = get_config_certificate() + ca_certs = get_config_ca_certificate() + if certs: + for cert_name, cert_dict in certs.items(): + if 'certificate' not in cert_dict: + continue + + cert = load_certificate(cert_dict['certificate']) + if cert.serial_number in serial_numbers: + certs_out.append(cert_name) + if ca_certs: + for cert_name, cert_dict in ca_certs.items(): + if 'certificate' not in cert_dict: + continue + + cert = load_certificate(cert_dict['certificate']) + if cert.serial_number in serial_numbers: + certs_out.append(cert_name) + return certs_out + +def install_certificate(name, cert='', private_key=None, key_type=None, key_passphrase=None, is_ca=False): + # Show/install conf commands for certificate + prefix = 'ca' if is_ca else 'certificate' + + base = f"pki {prefix} {name}" + config_paths = [] + if cert: + cert_pem = "".join(encode_certificate(cert).strip().split("\n")[1:-1]) + config_paths.append(f"{base} certificate '{cert_pem}'") + + if private_key: + key_pem = "".join(encode_private_key(private_key, passphrase=key_passphrase).strip().split("\n")[1:-1]) + config_paths.append(f"{base} private key '{key_pem}'") + if key_passphrase: + config_paths.append(f"{base} private password-protected") + + install_into_config(conf, config_paths) + +def install_crl(ca_name, crl): + # Show/install conf commands for crl + crl_pem = "".join(encode_certificate(crl).strip().split("\n")[1:-1]) + install_into_config(conf, [f"pki ca {ca_name} crl '{crl_pem}'"]) + +def install_dh_parameters(name, params): + # Show/install conf commands for dh params + dh_pem = "".join(encode_dh_parameters(params).strip().split("\n")[1:-1]) + install_into_config(conf, [f"pki dh {name} parameters '{dh_pem}'"]) + +def install_ssh_key(name, public_key, private_key, passphrase=None): + # Show/install conf commands for ssh key + key_openssh = encode_public_key(public_key, encoding='OpenSSH', key_format='OpenSSH') + username = os.getlogin() + type_key_split = key_openssh.split(" ") + + base = f"system login user {username} authentication public-keys {name}" + install_into_config(conf, [ + f"{base} key '{type_key_split[1]}'", + f"{base} type '{type_key_split[0]}'" + ]) + print(encode_private_key(private_key, encoding='PEM', key_format='OpenSSH', passphrase=passphrase)) + +def install_keypair(name, key_type, private_key=None, public_key=None, passphrase=None, prompt=True): + # Show/install conf commands for key-pair + + config_paths = [] + + if public_key: + install_public_key = not prompt or ask_yes_no('Do you want to install the public key?', default=True) + public_key_pem = encode_public_key(public_key) + + if install_public_key: + install_public_pem = "".join(public_key_pem.strip().split("\n")[1:-1]) + config_paths.append(f"pki key-pair {name} public key '{install_public_pem}'") + else: + print("Public key:") + print(public_key_pem) + + if private_key: + install_private_key = not prompt or ask_yes_no('Do you want to install the private key?', default=True) + private_key_pem = encode_private_key(private_key, passphrase=passphrase) + + if install_private_key: + install_private_pem = "".join(private_key_pem.strip().split("\n")[1:-1]) + config_paths.append(f"pki key-pair {name} private key '{install_private_pem}'") + if passphrase: + config_paths.append(f"pki key-pair {name} private password-protected") + else: + print("Private key:") + print(private_key_pem) + + install_into_config(conf, config_paths) + +def install_openvpn_key(name, key_data, key_version='1'): + config_paths = [ + f"pki openvpn shared-secret {name} key '{key_data}'", + f"pki openvpn shared-secret {name} version '{key_version}'" + ] + install_into_config(conf, config_paths) + +def install_wireguard_key(interface, private_key, public_key): + # Show conf commands for installing wireguard key pairs + from vyos.ifconfig import Section + if Section.section(interface) != 'wireguard': + print(f'"{interface}" is not a WireGuard interface name!') + exit(1) + + # Check if we are running in a config session - if yes, we can directly write to the CLI + install_into_config(conf, [f"interfaces wireguard {interface} private-key '{private_key}'"]) + + print(f"Corresponding public-key to use on peer system is: '{public_key}'") + +def install_wireguard_psk(interface, peer, psk): + from vyos.ifconfig import Section + if Section.section(interface) != 'wireguard': + print(f'"{interface}" is not a WireGuard interface name!') + exit(1) + + # Check if we are running in a config session - if yes, we can directly write to the CLI + install_into_config(conf, [f"interfaces wireguard {interface} peer {peer} preshared-key '{psk}'"]) + +def ask_passphrase(): + passphrase = None + print("Note: If you plan to use the generated key on this router, do not encrypt the private key.") + if ask_yes_no('Do you want to encrypt the private key with a passphrase?'): + passphrase = ask_input('Enter passphrase:') + return passphrase + +def write_file(filename, contents): + full_path = os.path.join(auth_dir, filename) + directory = os.path.dirname(full_path) + + if not os.path.exists(directory): + print('Failed to write file: directory does not exist') + return False + + if os.path.exists(full_path) and not ask_yes_no('Do you want to overwrite the existing file?'): + return False + + with open(full_path, 'w') as f: + f.write(contents) + + print(f'File written to {full_path}') + +# Generation functions + +def generate_private_key(): + key_type = ask_input('Enter private key type: [rsa, dsa, ec]', default='rsa', valid_responses=['rsa', 'dsa', 'ec']) + + size_valid = [] + size_default = 0 + + if key_type in ['rsa', 'dsa']: + size_default = 2048 + size_valid = [512, 1024, 2048, 4096] + elif key_type == 'ec': + size_default = 256 + size_valid = [224, 256, 384, 521] + + size = ask_input('Enter private key bits:', default=size_default, numeric_only=True, valid_responses=size_valid) + + return create_private_key(key_type, size), key_type + +def parse_san_string(san_string): + if not san_string: + return None + + output = [] + san_split = san_string.strip().split(",") + + for pair_str in san_split: + tag, value = pair_str.strip().split(":", 1) + if tag == 'ipv4': + output.append(ipaddress.IPv4Address(value)) + elif tag == 'ipv6': + output.append(ipaddress.IPv6Address(value)) + elif tag == 'dns' or tag == 'rfc822': + output.append(value) + return output + +def generate_certificate_request(private_key=None, key_type=None, return_request=False, name=None, install=False, file=False, ask_san=True): + if not private_key: + private_key, key_type = generate_private_key() + + default_values = get_default_values() + subject = {} + while True: + country = ask_input('Enter country code:', default=default_values['country']) + if len(country) != 2: + print("Country name must be a 2 character country code") + continue + subject['country'] = country + break + subject['state'] = ask_input('Enter state:', default=default_values['state']) + subject['locality'] = ask_input('Enter locality:', default=default_values['locality']) + subject['organization'] = ask_input('Enter organization name:', default=default_values['organization']) + subject['common_name'] = ask_input('Enter common name:', default='vyos.io') + subject_alt_names = None + + if ask_san and ask_yes_no('Do you want to configure Subject Alternative Names?'): + print("Enter alternative names in a comma separate list, example: ipv4:1.1.1.1,ipv6:fe80::1,dns:vyos.net,rfc822:user@vyos.net") + san_string = ask_input('Enter Subject Alternative Names:') + subject_alt_names = parse_san_string(san_string) + + cert_req = create_certificate_request(subject, private_key, subject_alt_names) + + if return_request: + return cert_req + + passphrase = ask_passphrase() + + if not install and not file: + print(encode_certificate(cert_req)) + print(encode_private_key(private_key, passphrase=passphrase)) + return None + + if install: + print("Certificate request:") + print(encode_certificate(cert_req) + "\n") + install_certificate(name, private_key=private_key, key_type=key_type, key_passphrase=passphrase, is_ca=False) + + if file: + write_file(f'{name}.csr', encode_certificate(cert_req)) + write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase)) + +def generate_certificate(cert_req, ca_cert, ca_private_key, is_ca=False, is_sub_ca=False): + valid_days = ask_input('Enter how many days certificate will be valid:', default='365' if not is_ca else '1825', numeric_only=True) + cert_type = None + if not is_ca: + cert_type = ask_input('Enter certificate type: (client, server)', default='server', valid_responses=['client', 'server']) + return create_certificate(cert_req, ca_cert, ca_private_key, valid_days, cert_type, is_ca, is_sub_ca) + +def generate_ca_certificate(name, install=False, file=False): + private_key, key_type = generate_private_key() + cert_req = generate_certificate_request(private_key, key_type, return_request=True, ask_san=False) + cert = generate_certificate(cert_req, cert_req, private_key, is_ca=True) + passphrase = ask_passphrase() + + if not install and not file: + print(encode_certificate(cert)) + print(encode_private_key(private_key, passphrase=passphrase)) + return None + + if install: + install_certificate(name, cert, private_key, key_type, key_passphrase=passphrase, is_ca=True) + + if file: + write_file(f'{name}.pem', encode_certificate(cert)) + write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase)) + +def generate_ca_certificate_sign(name, ca_name, install=False, file=False): + ca_dict = get_config_ca_certificate(ca_name) + + if not ca_dict: + print(f"CA certificate or private key for '{ca_name}' not found") + return None + + ca_cert = load_certificate(ca_dict['certificate']) + + if not ca_cert: + print("Failed to load signing CA certificate, aborting") + return None + + ca_private = ca_dict['private'] + ca_private_passphrase = None + if 'password_protected' in ca_private: + ca_private_passphrase = ask_input('Enter signing CA private key passphrase:') + ca_private_key = load_private_key(ca_private['key'], passphrase=ca_private_passphrase) + + if not ca_private_key: + print("Failed to load signing CA private key, aborting") + return None + + private_key = None + key_type = None + + cert_req = None + if not ask_yes_no('Do you already have a certificate request?'): + private_key, key_type = generate_private_key() + cert_req = generate_certificate_request(private_key, key_type, return_request=True, ask_san=False) + else: + print("Paste certificate request and press enter:") + lines = [] + curr_line = '' + while True: + curr_line = input().strip() + if not curr_line or curr_line == CERT_REQ_END: + break + lines.append(curr_line) + + if not lines: + print("Aborted") + return None + + wrap = lines[0].find('-----') < 0 # Only base64 pasted, add the CSR tags for parsing + cert_req = load_certificate_request("\n".join(lines), wrap) + + if not cert_req: + print("Invalid certificate request") + return None + + cert = generate_certificate(cert_req, ca_cert, ca_private_key, is_ca=True, is_sub_ca=True) + + passphrase = None + if private_key is not None: + passphrase = ask_passphrase() + + if not install and not file: + print(encode_certificate(cert)) + if private_key is not None: + print(encode_private_key(private_key, passphrase=passphrase)) + return None + + if install: + install_certificate(name, cert, private_key, key_type, key_passphrase=passphrase, is_ca=True) + + if file: + write_file(f'{name}.pem', encode_certificate(cert)) + if private_key is not None: + write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase)) + +def generate_certificate_sign(name, ca_name, install=False, file=False): + ca_dict = get_config_ca_certificate(ca_name) + + if not ca_dict: + print(f"CA certificate or private key for '{ca_name}' not found") + return None + + ca_cert = load_certificate(ca_dict['certificate']) + + if not ca_cert: + print("Failed to load CA certificate, aborting") + return None + + ca_private = ca_dict['private'] + ca_private_passphrase = None + if 'password_protected' in ca_private: + ca_private_passphrase = ask_input('Enter CA private key passphrase:') + ca_private_key = load_private_key(ca_private['key'], passphrase=ca_private_passphrase) + + if not ca_private_key: + print("Failed to load CA private key, aborting") + return None + + private_key = None + key_type = None + + cert_req = None + if not ask_yes_no('Do you already have a certificate request?'): + private_key, key_type = generate_private_key() + cert_req = generate_certificate_request(private_key, key_type, return_request=True) + else: + print("Paste certificate request and press enter:") + lines = [] + curr_line = '' + while True: + curr_line = input().strip() + if not curr_line or curr_line == CERT_REQ_END: + break + lines.append(curr_line) + + if not lines: + print("Aborted") + return None + + wrap = lines[0].find('-----') < 0 # Only base64 pasted, add the CSR tags for parsing + cert_req = load_certificate_request("\n".join(lines), wrap) + + if not cert_req: + print("Invalid certificate request") + return None + + cert = generate_certificate(cert_req, ca_cert, ca_private_key, is_ca=False) + + passphrase = None + if private_key is not None: + passphrase = ask_passphrase() + + if not install and not file: + print(encode_certificate(cert)) + if private_key is not None: + print(encode_private_key(private_key, passphrase=passphrase)) + return None + + if install: + install_certificate(name, cert, private_key, key_type, key_passphrase=passphrase, is_ca=False) + + if file: + write_file(f'{name}.pem', encode_certificate(cert)) + if private_key is not None: + write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase)) + +def generate_certificate_selfsign(name, install=False, file=False): + private_key, key_type = generate_private_key() + cert_req = generate_certificate_request(private_key, key_type, return_request=True) + cert = generate_certificate(cert_req, cert_req, private_key, is_ca=False) + passphrase = ask_passphrase() + + if not install and not file: + print(encode_certificate(cert)) + print(encode_private_key(private_key, passphrase=passphrase)) + return None + + if install: + install_certificate(name, cert, private_key=private_key, key_type=key_type, key_passphrase=passphrase, is_ca=False) + + if file: + write_file(f'{name}.pem', encode_certificate(cert)) + write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase)) + +def generate_certificate_revocation_list(ca_name, install=False, file=False): + ca_dict = get_config_ca_certificate(ca_name) + + if not ca_dict: + print(f"CA certificate or private key for '{ca_name}' not found") + return None + + ca_cert = load_certificate(ca_dict['certificate']) + + if not ca_cert: + print("Failed to load CA certificate, aborting") + return None + + ca_private = ca_dict['private'] + ca_private_passphrase = None + if 'password_protected' in ca_private: + ca_private_passphrase = ask_input('Enter CA private key passphrase:') + ca_private_key = load_private_key(ca_private['key'], passphrase=ca_private_passphrase) + + if not ca_private_key: + print("Failed to load CA private key, aborting") + return None + + revoked_certs = get_config_revoked_certificates() + to_revoke = [] + + for cert_dict in revoked_certs: + if 'certificate' not in cert_dict: + continue + + cert_data = cert_dict['certificate'] + + try: + cert = load_certificate(cert_data) + + if cert.issuer == ca_cert.subject: + to_revoke.append(cert.serial_number) + except ValueError: + continue + + if not to_revoke: + print("No revoked certificates to add to the CRL") + return None + + crl = create_certificate_revocation_list(ca_cert, ca_private_key, to_revoke) + + if not crl: + print("Failed to create CRL") + return None + + if not install and not file: + print(encode_certificate(crl)) + return None + + if install: + install_crl(ca_name, crl) + + if file: + write_file(f'{name}.crl', encode_certificate(crl)) + +def generate_ssh_keypair(name, install=False, file=False): + private_key, key_type = generate_private_key() + public_key = private_key.public_key() + passphrase = ask_passphrase() + + if not install and not file: + print(encode_public_key(public_key, encoding='OpenSSH', key_format='OpenSSH')) + print("") + print(encode_private_key(private_key, encoding='PEM', key_format='OpenSSH', passphrase=passphrase)) + return None + + if install: + install_ssh_key(name, public_key, private_key, passphrase) + + if file: + write_file(f'{name}.pem', encode_public_key(public_key, encoding='OpenSSH', key_format='OpenSSH')) + write_file(f'{name}.key', encode_private_key(private_key, encoding='PEM', key_format='OpenSSH', passphrase=passphrase)) + +def generate_dh_parameters(name, install=False, file=False): + bits = ask_input('Enter DH parameters key size:', default=2048, numeric_only=True) + + print("Generating parameters...") + + dh_params = create_dh_parameters(bits) + if not dh_params: + print("Failed to create DH parameters") + return None + + if not install and not file: + print("DH Parameters:") + print(encode_dh_parameters(dh_params)) + + if install: + install_dh_parameters(name, dh_params) + + if file: + write_file(f'{name}.pem', encode_dh_parameters(dh_params)) + +def generate_keypair(name, install=False, file=False): + private_key, key_type = generate_private_key() + public_key = private_key.public_key() + passphrase = ask_passphrase() + + if not install and not file: + print(encode_public_key(public_key)) + print("") + print(encode_private_key(private_key, passphrase=passphrase)) + return None + + if install: + install_keypair(name, key_type, private_key, public_key, passphrase) + + if file: + write_file(f'{name}.pem', encode_public_key(public_key)) + write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase)) + +def generate_openvpn_key(name, install=False, file=False): + result = cmd('openvpn --genkey secret /dev/stdout | grep -o "^[^#]*"') + + if not result: + print("Failed to generate OpenVPN key") + return None + + if not install and not file: + print(result) + return None + + if install: + key_lines = result.split("\n") + key_data = "".join(key_lines[1:-1]) # Remove wrapper tags and line endings + key_version = '1' + + version_search = re.search(r'BEGIN OpenVPN Static key V(\d+)', result) # Future-proofing (hopefully) + if version_search: + key_version = version_search[1] + + install_openvpn_key(name, key_data, key_version) + + if file: + write_file(f'{name}.key', result) + +def generate_wireguard_key(interface=None, install=False): + private_key = cmd('wg genkey') + public_key = cmd('wg pubkey', input=private_key) + + if interface and install: + install_wireguard_key(interface, private_key, public_key) + else: + print(f'Private key: {private_key}') + print(f'Public key: {public_key}', end='\n\n') + +def generate_wireguard_psk(interface=None, peer=None, install=False): + psk = cmd('wg genpsk') + if interface and peer and install: + install_wireguard_psk(interface, peer, psk) + else: + print(f'Pre-shared key: {psk}') + +# Import functions +def import_ca_certificate(name, path=None, key_path=None, no_prompt=False, passphrase=None): + if path: + if not os.path.exists(path): + print(f'File not found: {path}') + return + + cert = None + + with open(path) as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if not cert: + print(f'Invalid certificate: {path}') + return + + install_certificate(name, cert, is_ca=True) + + if key_path: + if not os.path.exists(key_path): + print(f'File not found: {key_path}') + return + + key = None + if not no_prompt: + passphrase = ask_input('Enter private key passphrase: ') or None + + with open(key_path) as f: + key_data = f.read() + key = load_private_key(key_data, passphrase=passphrase, wrap_tags=False) + + if not key: + print(f'Invalid private key or passphrase: {key_path}') + return + + install_certificate(name, private_key=key, is_ca=True) + +def import_certificate(name, path=None, key_path=None, no_prompt=False, passphrase=None): + if path: + if not os.path.exists(path): + print(f'File not found: {path}') + return + + cert = None + + with open(path) as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if not cert: + print(f'Invalid certificate: {path}') + return + + install_certificate(name, cert, is_ca=False) + + if key_path: + if not os.path.exists(key_path): + print(f'File not found: {key_path}') + return + + key = None + if not no_prompt: + passphrase = ask_input('Enter private key passphrase: ') or None + + with open(key_path) as f: + key_data = f.read() + key = load_private_key(key_data, passphrase=passphrase, wrap_tags=False) + + if not key: + print(f'Invalid private key or passphrase: {key_path}') + return + + install_certificate(name, private_key=key, is_ca=False) + +def import_crl(name, path): + if not os.path.exists(path): + print(f'File not found: {path}') + return + + crl = None + + with open(path) as f: + crl_data = f.read() + crl = load_crl(crl_data, wrap_tags=False) + + if not crl: + print(f'Invalid certificate: {path}') + return + + install_crl(name, crl) + +def import_dh_parameters(name, path): + if not os.path.exists(path): + print(f'File not found: {path}') + return + + dh = None + + with open(path) as f: + dh_data = f.read() + dh = load_dh_parameters(dh_data, wrap_tags=False) + + if not dh: + print(f'Invalid DH parameters: {path}') + return + + install_dh_parameters(name, dh) + +def import_keypair(name, path=None, key_path=None, no_prompt=False, passphrase=None): + if path: + if not os.path.exists(path): + print(f'File not found: {path}') + return + + key = None + + with open(path) as f: + key_data = f.read() + key = load_public_key(key_data, wrap_tags=False) + + if not key: + print(f'Invalid public key: {path}') + return + + install_keypair(name, None, public_key=key, prompt=False) + + if key_path: + if not os.path.exists(key_path): + print(f'File not found: {key_path}') + return + + key = None + if not no_prompt: + passphrase = ask_input('Enter private key passphrase: ') or None + + with open(key_path) as f: + key_data = f.read() + key = load_private_key(key_data, passphrase=passphrase, wrap_tags=False) + + if not key: + print(f'Invalid private key or passphrase: {key_path}') + return + + install_keypair(name, None, private_key=key, prompt=False) + +def import_openvpn_secret(name, path): + if not os.path.exists(path): + print(f'File not found: {path}') + return + + key_data = None + key_version = '1' + + with open(path) as f: + key_lines = f.read().strip().split("\n") + key_lines = list(filter(lambda line: not line.strip().startswith('#'), key_lines)) # Remove commented lines + key_data = "".join(key_lines[1:-1]) # Remove wrapper tags and line endings + + version_search = re.search(r'BEGIN OpenVPN Static key V(\d+)', key_lines[0]) # Future-proofing (hopefully) + if version_search: + key_version = version_search[1] + + install_openvpn_key(name, key_data, key_version) + +# Show functions +def show_certificate_authority(name=None, pem=False): + headers = ['Name', 'Subject', 'Issuer CN', 'Issued', 'Expiry', 'Private Key', 'Parent'] + data = [] + certs = get_config_ca_certificate() + if certs: + for cert_name, cert_dict in certs.items(): + if name and name != cert_name: + continue + if 'certificate' not in cert_dict: + continue + + cert = load_certificate(cert_dict['certificate']) + + if name and pem: + print(encode_certificate(cert)) + return + + parent_ca_name = get_certificate_ca(cert, certs) + cert_issuer_cn = cert.issuer.rfc4514_string().split(",")[0] + + if not parent_ca_name or parent_ca_name == cert_name: + parent_ca_name = 'N/A' + + if not cert: + continue + + have_private = 'Yes' if 'private' in cert_dict and 'key' in cert_dict['private'] else 'No' + data.append([cert_name, cert.subject.rfc4514_string(), cert_issuer_cn, cert.not_valid_before, cert.not_valid_after, have_private, parent_ca_name]) + + print("Certificate Authorities:") + print(tabulate.tabulate(data, headers)) + +def show_certificate(name=None, pem=False, fingerprint_hash=None): + headers = ['Name', 'Type', 'Subject CN', 'Issuer CN', 'Issued', 'Expiry', 'Revoked', 'Private Key', 'CA Present'] + data = [] + certs = get_config_certificate() + if certs: + ca_certs = get_config_ca_certificate() + + for cert_name, cert_dict in certs.items(): + if name and name != cert_name: + continue + if 'certificate' not in cert_dict: + continue + + cert = load_certificate(cert_dict['certificate']) + + if not cert: + continue + + if name and pem: + print(encode_certificate(cert)) + return + elif name and fingerprint_hash: + print(get_certificate_fingerprint(cert, fingerprint_hash)) + return + + ca_name = get_certificate_ca(cert, ca_certs) + cert_subject_cn = cert.subject.rfc4514_string().split(",")[0] + cert_issuer_cn = cert.issuer.rfc4514_string().split(",")[0] + cert_type = 'Unknown' + + try: + ext = cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) + if ext and ExtendedKeyUsageOID.SERVER_AUTH in ext.value: + cert_type = 'Server' + elif ext and ExtendedKeyUsageOID.CLIENT_AUTH in ext.value: + cert_type = 'Client' + except: + pass + + revoked = 'Yes' if 'revoke' in cert_dict else 'No' + have_private = 'Yes' if 'private' in cert_dict and 'key' in cert_dict['private'] else 'No' + have_ca = f'Yes ({ca_name})' if ca_name else 'No' + data.append([ + cert_name, cert_type, cert_subject_cn, cert_issuer_cn, + cert.not_valid_before, cert.not_valid_after, + revoked, have_private, have_ca]) + + print("Certificates:") + print(tabulate.tabulate(data, headers)) + +def show_crl(name=None, pem=False): + headers = ['CA Name', 'Updated', 'Revokes'] + data = [] + certs = get_config_ca_certificate() + if certs: + for cert_name, cert_dict in certs.items(): + if name and name != cert_name: + continue + if 'crl' not in cert_dict: + continue + + crls = cert_dict['crl'] + if isinstance(crls, str): + crls = [crls] + + for crl_data in cert_dict['crl']: + crl = load_crl(crl_data) + + if not crl: + continue + + if name and pem: + print(encode_certificate(crl)) + continue + + certs = get_revoked_by_serial_numbers([revoked.serial_number for revoked in crl]) + data.append([cert_name, crl.last_update, ", ".join(certs)]) + + if name and pem: + return + + print("Certificate Revocation Lists:") + print(tabulate.tabulate(data, headers)) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--action', help='PKI action', required=True) + + # X509 + parser.add_argument('--ca', help='Certificate Authority', required=False) + parser.add_argument('--certificate', help='Certificate', required=False) + parser.add_argument('--crl', help='Certificate Revocation List', required=False) + parser.add_argument('--sign', help='Sign certificate with specified CA', required=False) + parser.add_argument('--self-sign', help='Self-sign the certificate', action='store_true') + parser.add_argument('--pem', help='Output using PEM encoding', action='store_true') + parser.add_argument('--fingerprint', help='Show fingerprint and exit', action='store') + + # SSH + parser.add_argument('--ssh', help='SSH Key', required=False) + + # DH + parser.add_argument('--dh', help='DH Parameters', required=False) + + # Key pair + parser.add_argument('--keypair', help='Key pair', required=False) + + # OpenVPN + parser.add_argument('--openvpn', help='OpenVPN TLS key', required=False) + + # WireGuard + parser.add_argument('--wireguard', help='Wireguard', action='store_true') + group = parser.add_mutually_exclusive_group() + group.add_argument('--key', help='Wireguard key pair', action='store_true', required=False) + group.add_argument('--psk', help='Wireguard pre shared key', action='store_true', required=False) + parser.add_argument('--interface', help='Install generated keys into running-config for named interface', action='store') + parser.add_argument('--peer', help='Install generated keys into running-config for peer', action='store') + + # Global + parser.add_argument('--file', help='Write generated keys into specified filename', action='store_true') + parser.add_argument('--install', help='Install generated keys into running-config', action='store_true') + + parser.add_argument('--filename', help='Write certificate into specified filename', action='store') + parser.add_argument('--key-filename', help='Write key into specified filename', action='store') + + parser.add_argument('--no-prompt', action='store_true', help='Perform action non-interactively') + parser.add_argument('--passphrase', help='A passphrase to decrypt the private key') + + args = parser.parse_args() + + try: + if args.action == 'generate': + if args.ca: + if args.sign: + generate_ca_certificate_sign(args.ca, args.sign, install=args.install, file=args.file) + else: + generate_ca_certificate(args.ca, install=args.install, file=args.file) + elif args.certificate: + if args.sign: + generate_certificate_sign(args.certificate, args.sign, install=args.install, file=args.file) + elif args.self_sign: + generate_certificate_selfsign(args.certificate, install=args.install, file=args.file) + else: + generate_certificate_request(name=args.certificate, install=args.install, file=args.file) + + elif args.crl: + generate_certificate_revocation_list(args.crl, install=args.install, file=args.file) + + elif args.ssh: + generate_ssh_keypair(args.ssh, install=args.install, file=args.file) + + elif args.dh: + generate_dh_parameters(args.dh, install=args.install, file=args.file) + + elif args.keypair: + generate_keypair(args.keypair, install=args.install, file=args.file) + + elif args.openvpn: + generate_openvpn_key(args.openvpn, install=args.install, file=args.file) + + elif args.wireguard: + # WireGuard supports writing key directly into the CLI, but this + # requires the vyos_libexec_dir environment variable to be set + os.environ["vyos_libexec_dir"] = "/usr/libexec/vyos" + + if args.key: + generate_wireguard_key(args.interface, install=args.install) + if args.psk: + generate_wireguard_psk(args.interface, peer=args.peer, install=args.install) + elif args.action == 'import': + if args.ca: + import_ca_certificate(args.ca, path=args.filename, key_path=args.key_filename, + no_prompt=args.no_prompt, passphrase=args.passphrase) + elif args.certificate: + import_certificate(args.certificate, path=args.filename, key_path=args.key_filename, + no_prompt=args.no_prompt, passphrase=args.passphrase) + elif args.crl: + import_crl(args.crl, args.filename) + elif args.dh: + import_dh_parameters(args.dh, args.filename) + elif args.keypair: + import_keypair(args.keypair, path=args.filename, key_path=args.key_filename, + no_prompt=args.no_prompt, passphrase=args.passphrase) + elif args.openvpn: + import_openvpn_secret(args.openvpn, args.filename) + elif args.action == 'show': + if args.ca: + ca_name = None if args.ca == 'all' else args.ca + if ca_name: + if not conf.exists(['pki', 'ca', ca_name]): + print(f'CA "{ca_name}" does not exist!') + exit(1) + show_certificate_authority(ca_name, args.pem) + elif args.certificate: + cert_name = None if args.certificate == 'all' else args.certificate + if cert_name: + if not conf.exists(['pki', 'certificate', cert_name]): + print(f'Certificate "{cert_name}" does not exist!') + exit(1) + if args.fingerprint is None: + show_certificate(None if args.certificate == 'all' else args.certificate, args.pem) + else: + show_certificate(args.certificate, fingerprint_hash=args.fingerprint) + elif args.crl: + show_crl(None if args.crl == 'all' else args.crl, args.pem) + else: + show_certificate_authority() + print('\n') + show_certificate() + print('\n') + show_crl() + except KeyboardInterrupt: + print("Aborted") + sys.exit(0) diff --git a/src/op_mode/policy_route.py b/src/op_mode/policy_route.py new file mode 100644 index 0000000..d124650 --- /dev/null +++ b/src/op_mode/policy_route.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import re +import tabulate + +from vyos.config import Config +from vyos.utils.process import cmd + +def get_config_policy(conf, name=None, ipv6=False): + config_path = ['policy'] + if name: + config_path += ['route6' if ipv6 else 'route', name] + + policy = conf.get_config_dict(config_path, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + return policy + +def get_nftables_details(name, ipv6=False): + suffix = '6' if ipv6 else '' + command = f'sudo nft list chain ip{suffix} mangle VYOS_PBR{suffix}_{name}' + try: + results = cmd(command) + except: + return {} + + out = {} + for line in results.split('\n'): + comment_search = re.search(rf'{name}[\- ](\d+|default-action)', line) + if not comment_search: + continue + + rule = {} + rule_id = comment_search[1] + counter_search = re.search(r'counter packets (\d+) bytes (\d+)', line) + if counter_search: + rule['packets'] = counter_search[1] + rule['bytes'] = counter_search[2] + + rule['conditions'] = re.sub(r'(\b(counter packets \d+ bytes \d+|drop|reject|return|log)\b|comment "[\w\-]+")', '', line).strip() + out[rule_id] = rule + return out + +def output_policy_route(name, route_conf, ipv6=False, single_rule_id=None): + ip_str = 'IPv6' if ipv6 else 'IPv4' + print(f'\n---------------------------------\n{ip_str} Policy Route "{name}"\n') + + if route_conf.get('interface'): + print('Active on: {0}\n'.format(" ".join(route_conf['interface']))) + else: + print('Inactive - Not applied to any interfaces\n') + + details = get_nftables_details(name, ipv6) + rows = [] + + if 'rule' in route_conf: + for rule_id, rule_conf in route_conf['rule'].items(): + if single_rule_id and rule_id != single_rule_id: + continue + + if 'disable' in rule_conf: + continue + + action = rule_conf['action'] if 'action' in rule_conf else 'set' + protocol = rule_conf['protocol'] if 'protocol' in rule_conf else 'all' + + row = [rule_id, action, protocol] + if rule_id in details: + rule_details = details[rule_id] + row.append(rule_details.get('packets', 0)) + row.append(rule_details.get('bytes', 0)) + row.append(rule_details['conditions']) + rows.append(row) + + if 'default_action' in route_conf and not single_rule_id: + row = ['default', route_conf['default_action'], 'all'] + if 'default-action' in details: + rule_details = details['default-action'] + row.append(rule_details.get('packets', 0)) + row.append(rule_details.get('bytes', 0)) + rows.append(row) + + if rows: + header = ['Rule', 'Action', 'Protocol', 'Packets', 'Bytes', 'Conditions'] + print(tabulate.tabulate(rows, header) + '\n') + +def show_policy(ipv6=False): + print('Ruleset Information') + + conf = Config() + policy = get_config_policy(conf) + + if not policy: + return + + if not ipv6 and 'route' in policy: + for route, route_conf in policy['route'].items(): + output_policy_route(route, route_conf, ipv6=False) + + if ipv6 and 'route6' in policy: + for route, route_conf in policy['route6'].items(): + output_policy_route(route, route_conf, ipv6=True) + +def show_policy_name(name, ipv6=False): + print('Ruleset Information') + + conf = Config() + policy = get_config_policy(conf, name, ipv6) + if policy: + output_policy_route(name, policy, ipv6) + +def show_policy_rule(name, rule_id, ipv6=False): + print('Rule Information') + + conf = Config() + policy = get_config_policy(conf, name, ipv6) + if policy: + output_policy_route(name, policy, ipv6, rule_id) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--action', help='Action', required=False) + parser.add_argument('--name', help='Policy name', required=False, action='store', nargs='?', default='') + parser.add_argument('--rule', help='Policy Rule ID', required=False) + parser.add_argument('--ipv6', help='IPv6 toggle', action='store_true') + + args = parser.parse_args() + + if args.action == 'show': + if not args.rule: + show_policy_name(args.name, args.ipv6) + else: + show_policy_rule(args.name, args.rule, args.ipv6) + elif args.action == 'show_all': + show_policy(args.ipv6) diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py new file mode 100644 index 0000000..c32a2be --- /dev/null +++ b/src/op_mode/powerctrl.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import re + +from argparse import ArgumentParser +from datetime import datetime +from sys import exit +from time import time + +from vyos.utils.io import ask_yes_no +from vyos.utils.process import call +from vyos.utils.process import run +from vyos.utils.process import STDOUT + +systemd_sched_file = "/run/systemd/shutdown/scheduled" + +def utc2local(datetime): + now = time() + offs = datetime.fromtimestamp(now) - datetime.utcfromtimestamp(now) + return datetime + offs + +def parse_time(s): + try: + if re.match(r'^\d{1,9999}$', s): + if (int(s) > 59) and (int(s) < 1440): + s = str(int(s)//60) + ":" + str(int(s)%60) + return datetime.strptime(s, "%H:%M").time() + if (int(s) >= 1440): + return s.split() + else: + return datetime.strptime(s, "%M").time() + else: + return datetime.strptime(s, "%H:%M").time() + except ValueError: + return None + + +def parse_date(s): + for fmt in ["%d%m%Y", "%d/%m/%Y", "%d.%m.%Y", "%d:%m:%Y", "%Y-%m-%d"]: + try: + return datetime.strptime(s, fmt).date() + except ValueError: + continue + # If nothing matched... + return None + + +def get_shutdown_status(): + if os.path.exists(systemd_sched_file): + # Get scheduled from systemd file + with open(systemd_sched_file, 'r') as f: + data = f.read().rstrip('\n') + r_data = {} + for line in data.splitlines(): + tmp_split = line.split("=") + if tmp_split[0] == "USEC": + # Convert USEC to human readable format + r_data['DATETIME'] = datetime.utcfromtimestamp( + int(tmp_split[1])/1000000).strftime('%Y-%m-%d %H:%M:%S') + else: + r_data[tmp_split[0]] = tmp_split[1] + return r_data + return None + + +def check_shutdown(): + output = get_shutdown_status() + if output and 'MODE' in output: + dt = datetime.strptime(output['DATETIME'], '%Y-%m-%d %H:%M:%S') + if output['MODE'] == 'reboot': + print("Reboot is scheduled", utc2local(dt)) + elif output['MODE'] == 'poweroff': + print("Poweroff is scheduled", utc2local(dt)) + else: + print("Reboot or poweroff is not scheduled") + + +def cancel_shutdown(): + output = get_shutdown_status() + if output and 'MODE' in output: + timenow = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + try: + run('/sbin/shutdown -c --no-wall') + except OSError as e: + exit(f'Could not cancel a reboot or poweroff: {e}') + + mode = output['MODE'] + message = f'Scheduled {mode} has been cancelled {timenow}' + run(f'wall {message} > /dev/null 2>&1') + else: + print("Reboot or poweroff is not scheduled") + +def check_unsaved_config(): + from vyos.config_mgmt import unsaved_commits + from vyos.utils.boot import boot_configuration_success + + if unsaved_commits(allow_missing_config=True) and boot_configuration_success(): + print("Warning: there are unsaved configuration changes!") + print("Run 'save' command if you do not want to lose those changes after reboot/shutdown.") + else: + pass + +def execute_shutdown(time, reboot=True, ask=True): + from vyos.utils.process import cmd + + check_unsaved_config() + + host = cmd("hostname --fqdn") + + action = "reboot" if reboot else "poweroff" + if not ask: + if not ask_yes_no(f"Are you sure you want to {action} this system ({host})?"): + exit(0) + action_cmd = "-r" if reboot else "-P" + + if len(time) == 0: + # T870 legacy reboot job support + chk_vyatta_based_reboots() + ### + + out = cmd(f'/sbin/shutdown {action_cmd} now', stderr=STDOUT) + print(out.split(",", 1)[0]) + return + elif len(time) == 1: + # Assume the argument is just time + ts = parse_time(time[0]) + if ts: + cmd(f'/sbin/shutdown {action_cmd} {time[0]}', stderr=STDOUT) + # Inform all other logged in users about the reboot/shutdown + wall_msg = f'System {action} is scheduled {time[0]}' + cmd(f'/usr/bin/wall "{wall_msg}"') + else: + exit(f'Invalid time "{time[0]}". The valid format is HH:MM') + elif len(time) == 2: + # Assume it's date and time + ts = parse_time(time[0]) + ds = parse_date(time[1]) + if ts and ds: + t = datetime.combine(ds, ts) + td = t - datetime.now() + t2 = 1 + int(td.total_seconds())//60 # Get total minutes + + cmd(f'/sbin/shutdown {action_cmd} {t2}', stderr=STDOUT) + # Inform all other logged in users about the reboot/shutdown + wall_msg = f'System {action} is scheduled {time[1]} {time[0]}' + cmd(f'/usr/bin/wall "{wall_msg}"') + else: + if not ts: + exit(f'Invalid time "{time[0]}". Uses 24 Hour Clock format') + else: + exit(f'Invalid date "{time[1]}". A valid format is YYYY-MM-DD [HH:MM]') + else: + exit('Could not decode date and time. Valids formats are HH:MM or YYYY-MM-DD HH:MM') + check_shutdown() + + +def chk_vyatta_based_reboots(): + # T870 commit-confirm is still using the vyatta code base, once gone, the code below can be removed + # legacy scheduled reboot s are using at and store the is as /var/run/<name>.job + # name is the node of scheduled the job, commit-confirm checks for that + + f = r'/var/run/confirm.job' + if os.path.exists(f): + jid = open(f).read().strip() + if jid != 0: + call(f'sudo atrm {jid}') + os.remove(f) + + +def main(): + parser = ArgumentParser() + parser.add_argument("--yes", "-y", + help="Do not ask for confirmation", + action="store_true", + dest="yes") + action = parser.add_mutually_exclusive_group(required=True) + action.add_argument("--reboot", "-r", + help="Reboot the system", + nargs="*", + metavar="HH:MM") + + action.add_argument("--reboot-in", "-i", + help="Reboot the system", + nargs="*", + metavar="Minutes") + + action.add_argument("--poweroff", "-p", + help="Poweroff the system", + nargs="*", + metavar="Minutes|HH:MM") + + action.add_argument("--cancel", "-c", + help="Cancel pending shutdown", + action="store_true") + + action.add_argument("--check", + help="Check pending shutdown", + action="store_true") + args = parser.parse_args() + + try: + if args.reboot is not None: + for r in args.reboot: + if ':' not in r and '/' not in r and '.' not in r: + print("Incorrect format! Use HH:MM") + exit(1) + execute_shutdown(args.reboot, reboot=True, ask=args.yes) + if args.reboot_in is not None: + for i in args.reboot_in: + if ':' in i: + print("Incorrect format! Use Minutes") + exit(1) + execute_shutdown(args.reboot_in, reboot=True, ask=args.yes) + if args.poweroff is not None: + execute_shutdown(args.poweroff, reboot=False, ask=args.yes) + if args.cancel: + cancel_shutdown() + if args.check: + check_shutdown() + except KeyboardInterrupt: + exit("Interrupted") + +if __name__ == "__main__": + main() diff --git a/src/op_mode/ppp-server-ctrl.py b/src/op_mode/ppp-server-ctrl.py new file mode 100644 index 0000000..2bae5b3 --- /dev/null +++ b/src/op_mode/ppp-server-ctrl.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import argparse + +from vyos.config import Config +from vyos.utils.process import popen +from vyos.utils.process import DEVNULL + +cmd_dict = { + 'cmd_base' : '/usr/bin/accel-cmd -p {} ', + 'vpn_types' : { + 'pppoe' : 2001, + 'pptp' : 2003, + 'l2tp' : 2004, + 'sstp' : 2005 + }, + 'conf_proto' : { + 'pppoe' : 'service pppoe-server', + 'pptp' : 'vpn pptp remote-access', + 'l2tp' : 'vpn l2tp remote-access', + 'sstp' : 'vpn sstp' + } +} + +def is_service_configured(proto): + if not Config().exists_effective(cmd_dict['conf_proto'][proto]): + print("Service {} is not configured".format(proto)) + sys.exit(1) + +def main(): + #parese args + parser = argparse.ArgumentParser() + parser.add_argument('--proto', help='Possible protocols pppoe|pptp|l2tp|sstp', required=True) + parser.add_argument('--action', help='Action command', required=True) + args = parser.parse_args() + + if args.proto in cmd_dict['vpn_types'] and args.action: + # Check is service configured + is_service_configured(args.proto) + + if args.action == "show sessions": + ses_pattern = " ifname,username,ip,ip6,ip6-dp,calling-sid,rate-limit,state,uptime,rx-bytes,tx-bytes" + else: + ses_pattern = "" + + output, err = popen(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][args.proto]) + args.action + ses_pattern, stderr=DEVNULL, decode='utf-8') + if not err: + try: + print(f' {output}') + except: + sys.exit(0) + else: + print("{} server is not running".format(args.proto)) + + else: + print("Param --proto and --action required") + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/src/op_mode/qos.py b/src/op_mode/qos.py new file mode 100644 index 0000000..b8ca149 --- /dev/null +++ b/src/op_mode/qos.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# This script parses output from the 'tc' command and provides table or list output +import sys +import typing +import json +from tabulate import tabulate + +import vyos.opmode +from vyos.configquery import op_mode_config_dict +from vyos.utils.process import cmd +from vyos.utils.network import interface_exists + +def detailed_output(dataset, headers): + for data in dataset: + adjusted_rule = data + [""] * (len(headers) - len(data)) # account for different header length, like default-action + transformed_rule = [[header, adjusted_rule[i]] for i, header in enumerate(headers) if i < len(adjusted_rule)] # create key-pair list from headers and rules lists; wrap at 100 char + + print(tabulate(transformed_rule, tablefmt="presto")) + print() + +def get_tc_info(interface_dict, interface_name, policy_type): + policy_name = interface_dict.get(interface_name, {}).get('egress') + if not policy_name: + return None, None + + class_dict = op_mode_config_dict(['qos', 'policy', policy_type, policy_name], key_mangling=('-', '_'), + get_first_key=True) + if not class_dict: + return None, None + + return policy_name, class_dict + +def format_data_type(num, suffix): + if num < 10**3: + return f"{num} {suffix}" + elif num < 10**6: + return f"{num / 10**3:.3f} K{suffix}" + elif num < 10**9: + return f"{num / 10**6:.3f} M{suffix}" + elif num < 10**12: + return f"{num / 10**9:.3f} G{suffix}" + elif num < 10**15: + return f"{num / 10**12:.3f} T{suffix}" + elif num < 10**18: + return f"{num / 10**15:.3f} P{suffix}" + else: + return f"{num / 10**18:.3f} E{suffix}" + +def show_shaper(raw: bool, ifname: typing.Optional[str], classn: typing.Optional[str], detail: bool): + # Scope which interfaces will output data + if ifname: + if not interface_exists(ifname): + raise vyos.opmode.Error(f"{ifname} does not exist!") + + interface_dict = {ifname: op_mode_config_dict(['qos', 'interface', ifname], key_mangling=('-', '_'), + get_first_key=True)} + if not interface_dict[ifname]: + raise vyos.opmode.Error(f"QoS is not applied to {ifname}!") + + else: + interface_dict = op_mode_config_dict(['qos', 'interface'], key_mangling=('-', '_'), + get_first_key=True) + if not interface_dict: + raise vyos.opmode.Error(f"QoS is not applied to any interface!") + + + raw_dict = {'qos': {}} + for i in interface_dict.keys(): + interface_name = i + output_list = [] + output_dict = {'classes': {}} + raw_dict['qos'][interface_name] = {} + + # Get configuration node data + policy_name, class_dict = get_tc_info(interface_dict, interface_name, 'shaper') + if not policy_name: + continue + + class_data = json.loads(cmd(f"tc -j -s class show dev {i}")) + qdisc_data = json.loads(cmd(f"tc -j qdisc show dev {i}")) + + if class_dict: + # Gather qdisc information (e.g. Queue Type) + qdisc_dict = {} + for qdisc in qdisc_data: + if qdisc.get('root'): + qdisc_dict['root'] = qdisc + continue + + class_id = int(qdisc.get('parent').split(':')[1], 16) + + if class_dict.get('class', {}).get(str(class_id)): + qdisc_dict[str(class_id)] = qdisc + else: + qdisc_dict['default'] = qdisc + + # Gather class information + for classes in class_data: + if classes.get('rate'): + class_id = int(classes.get('handle').split(':')[1], 16) + + # Get name of class + if classes.get('root'): + class_name = 'root' + output_dict['classes'][class_name] = {} + elif class_dict.get('class', {}).get(str(class_id)): + class_name = str(class_id) + output_dict['classes'][class_name] = {} + else: + class_name = 'default' + output_dict['classes'][class_name] = {} + + if classn: + if classn != class_name and class_name != 'default' and class_name != 'root': + output_dict['classes'].pop(class_name, None) + continue + + tmp = output_dict['classes'][class_name] + + tmp['interface_name'] = interface_name + tmp['policy_name'] = policy_name + tmp['direction'] = 'egress' + tmp['class_name'] = class_name + tmp['queue_type'] = qdisc_dict.get(class_name, {}).get('kind') + tmp['rate'] = str(round(int(classes.get('rate'))*8)) + tmp['ceil'] = str(round(int(classes.get('ceil'))*8)) + tmp['bytes'] = classes.get('stats', {}).get('bytes', 0) + tmp['packets'] = classes.get('stats', {}).get('packets', 0) + tmp['drops'] = classes.get('stats', {}).get('drops', 0) + tmp['queued'] = classes.get('stats', {}).get('backlog', 0) + tmp['overlimits'] = classes.get('stats', {}).get('overlimits', 0) + tmp['requeues'] = classes.get('stats', {}).get('requeues', 0) + tmp['lended'] = classes.get('stats', {}).get('lended', 0) + tmp['borrowed'] = classes.get('stats', {}).get('borrowed', 0) + tmp['giants'] = classes.get('stats', {}).get('giants', 0) + + output_dict['classes'][class_name] = tmp + raw_dict['qos'][interface_name][class_name] = tmp + + # Skip printing of values for this interface. All interfaces will be returned in a single dictionary if 'raw' is called + if raw: + continue + + # Default class may be out of order in original JSON. This moves it to the end + move_default = output_dict.get('classes', {}).pop('default', None) + if move_default: + output_dict.get('classes')['default'] = move_default + + # Create the tables for outputs + for output in output_dict.get('classes'): + data = output_dict.get('classes').get(output) + + # Add values for detailed (list view) output + if detail: + output_list.append([data['interface_name'], + data['policy_name'], + data['direction'], + data['class_name'], + data['queue_type'], + data['rate'], + data['ceil'], + data['bytes'], + data['packets'], + data['drops'], + data['queued'], + data['overlimits'], + data['requeues'], + data['lended'], + data['borrowed'], + data['giants']] + ) + # Add values for normal (table view) output + else: + output_list.append([data['class_name'], + data['queue_type'], + format_data_type(int(data['rate']), 'b'), + format_data_type(int(data['ceil']), 'b'), + format_data_type(int(data['bytes']), 'B'), + data['packets'], + data['drops'], + data['queued']] + ) + + if output_list: + if detail: + # Headers for detailed (list view) output + headers = ['Interface', 'Policy Name', 'Direction', 'Class', 'Type', 'Bandwidth', 'Max. BW', 'Bytes', 'Packets', 'Drops', 'Queued', 'Overlimit', 'Requeue', 'Lended', 'Borrowed', 'Giants'] + + print('-' * 35) + print(f"Interface: {interface_name}") + print(f"Policy Name: {policy_name}\n") + detailed_output(output_list, headers) + else: + # Headers for table output + headers = ['Class', 'Type', 'Bandwidth', 'Max. BW', 'Bytes', 'Pkts', 'Drops', 'Queued'] + align = ('left','left','right','right','right','right','right','right') + + print('-' * 80) + print(f"Interface: {interface_name}") + print(f"Policy Name: {policy_name}\n") + print(tabulate(output_list, headers, colalign=align)) + print(" \n") + + # Return dictionary with all interfaces if 'raw' is called + if raw: + return raw_dict + +def show_cake(raw: bool, ifname: typing.Optional[str]): + if not interface_exists(ifname): + raise vyos.opmode.Error(f"{ifname} does not exist!") + + cake_data = json.loads(cmd(f"tc -j -s qdisc show dev {ifname}"))[0] + if cake_data: + if cake_data.get('kind') == 'cake': + if raw: + return {'qos': {ifname: cake_data}} + else: + print(cmd(f"tc -s qdisc show dev {ifname}")) + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/raid.py b/src/op_mode/raid.py new file mode 100644 index 0000000..fed8ae2 --- /dev/null +++ b/src/op_mode/raid.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# +import sys + +import vyos.opmode +from vyos.raid import add_raid_member +from vyos.raid import delete_raid_member + +def add(raid_set_name: str, member: str, by_id: bool = False): + try: + add_raid_member(raid_set_name, member, by_id) + except ValueError as e: + raise vyos.opmode.IncorrectValue(str(e)) + +def delete(raid_set_name: str, member: str, by_id: bool = False): + try: + delete_raid_member(raid_set_name, member, by_id) + except ValueError as e: + raise vyos.opmode.IncorrectValue(str(e)) + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) + diff --git a/src/op_mode/reset_openvpn.py b/src/op_mode/reset_openvpn.py new file mode 100644 index 0000000..cef5299 --- /dev/null +++ b/src/op_mode/reset_openvpn.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +from sys import argv, exit +from vyos.utils.process import call +from vyos.utils.commit import commit_in_progress + +if __name__ == '__main__': + if (len(argv) < 1): + print('Must specify OpenVPN interface name!') + exit(1) + + interface = argv[1] + if os.path.isfile(f'/run/openvpn/{interface}.conf'): + if commit_in_progress(): + print('Cannot restart OpenVPN while a commit is in progress') + exit(1) + call(f'systemctl restart openvpn@{interface}.service') + else: + print(f'OpenVPN interface "{interface}" does not exist!') + exit(1) diff --git a/src/op_mode/reset_vpn.py b/src/op_mode/reset_vpn.py new file mode 100644 index 0000000..61d7c8c --- /dev/null +++ b/src/op_mode/reset_vpn.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import typing + +from vyos.utils.process import run + +import vyos.opmode + +cmd_dict = { + 'cmd_base': '/usr/bin/accel-cmd -p {} terminate {} {}', + 'vpn_types': { + 'pptp': 2003, + 'l2tp': 2004, + 'sstp': 2005 + } +} + +def reset_conn(protocol: str, username: typing.Optional[str] = None, + interface: typing.Optional[str] = None): + if protocol in cmd_dict['vpn_types']: + # Reset by Interface + if interface: + run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol], + 'if', interface)) + return + # Reset by username + if username: + run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol], + 'username', username)) + # Reset all + else: + run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol], + 'all', + '')) + else: + vyos.opmode.IncorrectValue('Unknown VPN Protocol, aborting') + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/restart.py b/src/op_mode/restart.py new file mode 100644 index 0000000..a83c8b9 --- /dev/null +++ b/src/op_mode/restart.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import typing +import vyos.opmode + +from vyos.configquery import ConfigTreeQuery +from vyos.utils.process import call +from vyos.utils.commit import commit_in_progress + +config = ConfigTreeQuery() + +service_map = { + 'dhcp': { + 'systemd_service': 'kea-dhcp4-server', + 'path': ['service', 'dhcp-server'], + }, + 'dhcpv6': { + 'systemd_service': 'kea-dhcp6-server', + 'path': ['service', 'dhcpv6-server'], + }, + 'dns_dynamic': { + 'systemd_service': 'ddclient', + 'path': ['service', 'dns', 'dynamic'], + }, + 'dns_forwarding': { + 'systemd_service': 'pdns-recursor', + 'path': ['service', 'dns', 'forwarding'], + }, + 'igmp_proxy': { + 'systemd_service': 'igmpproxy', + 'path': ['protocols', 'igmp-proxy'], + }, + 'ipsec': { + 'systemd_service': 'strongswan', + 'path': ['vpn', 'ipsec'], + }, + 'mdns_repeater': { + 'systemd_service': 'avahi-daemon', + 'path': ['service', 'mdns', 'repeater'], + }, + 'reverse_proxy': { + 'systemd_service': 'haproxy', + 'path': ['load-balancing', 'reverse-proxy'], + }, + 'router_advert': { + 'systemd_service': 'radvd', + 'path': ['service', 'router-advert'], + }, + 'snmp': { + 'systemd_service': 'snmpd', + }, + 'ssh': { + 'systemd_service': 'ssh', + }, + 'suricata': { + 'systemd_service': 'suricata', + }, + 'vrrp': { + 'systemd_service': 'keepalived', + 'path': ['high-availability', 'vrrp'], + }, + 'webproxy': { + 'systemd_service': 'squid', + }, +} +services = typing.Literal[ + 'dhcp', + 'dhcpv6', + 'dns_dynamic', + 'dns_forwarding', + 'igmp_proxy', + 'ipsec', + 'mdns_repeater', + 'reverse_proxy', + 'router_advert', + 'snmp', + 'ssh', + 'suricata', + 'vrrp', + 'webproxy', +] + + +def _verify(func): + """Decorator checks if DHCP(v6) config exists""" + from functools import wraps + + @wraps(func) + def _wrapper(*args, **kwargs): + config = ConfigTreeQuery() + name = kwargs.get('name') + human_name = name.replace('_', '-') + + if commit_in_progress(): + print(f'Cannot restart {human_name} service while a commit is in progress') + sys.exit(1) + + # Get optional CLI path from service_mapping dict + # otherwise use "service name" CLI path + path = ['service', name] + if 'path' in service_map[name]: + path = service_map[name]['path'] + + # Check if config does not exist + if not config.exists(path): + raise vyos.opmode.UnconfiguredSubsystem( + f'Service {human_name} is not configured!' + ) + if config.exists(path + ['disable']): + raise vyos.opmode.UnconfiguredSubsystem( + f'Service {human_name} is disabled!' + ) + return func(*args, **kwargs) + + return _wrapper + + +@_verify +def restart_service(raw: bool, name: services, vrf: typing.Optional[str]): + systemd_service = service_map[name]['systemd_service'] + if vrf: + call(f'systemctl restart "{systemd_service}@{vrf}.service"') + else: + call(f'systemctl restart "{systemd_service}.service"') + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/restart_dhcp_relay.py b/src/op_mode/restart_dhcp_relay.py new file mode 100644 index 0000000..42626ca --- /dev/null +++ b/src/op_mode/restart_dhcp_relay.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# File: restart_dhcp_relay.py +# Purpose: +# Restart IPv4 and IPv6 DHCP relay instances of dhcrelay service + +import sys +import argparse + +import vyos.config +from vyos.utils.process import call +from vyos.utils.commit import commit_in_progress + + +parser = argparse.ArgumentParser() +parser.add_argument("--ipv4", action="store_true", help="Restart IPv4 DHCP relay") +parser.add_argument("--ipv6", action="store_true", help="Restart IPv6 DHCP relay") + +if __name__ == '__main__': + args = parser.parse_args() + c = vyos.config.Config() + + if args.ipv4: + # Do nothing if service is not configured + if not c.exists_effective('service dhcp-relay'): + print("DHCP relay service not configured") + else: + if commit_in_progress(): + print('Cannot restart DHCP relay while a commit is in progress') + exit(1) + call('systemctl restart isc-dhcp-relay.service') + + sys.exit(0) + elif args.ipv6: + # Do nothing if service is not configured + if not c.exists_effective('service dhcpv6-relay'): + print("DHCPv6 relay service not configured") + else: + if commit_in_progress(): + print('Cannot restart DHCPv6 relay while commit is in progress') + exit(1) + call('systemctl restart isc-dhcp-relay6.service') + + sys.exit(0) + else: + parser.print_help() + sys.exit(1) diff --git a/src/op_mode/restart_frr.py b/src/op_mode/restart_frr.py new file mode 100644 index 0000000..83146f5 --- /dev/null +++ b/src/op_mode/restart_frr.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import argparse +import logging +import psutil + +from logging.handlers import SysLogHandler +from shutil import rmtree + +from vyos.base import Warning +from vyos.utils.io import ask_yes_no +from vyos.utils.file import makedir +from vyos.utils.process import call +from vyos.utils.process import process_named_running + +# some default values +watchfrr = '/usr/lib/frr/watchfrr.sh' +vtysh = '/usr/bin/vtysh' +frrconfig_tmp = '/tmp/frr_restart' + +# configure logging +logger = logging.getLogger(__name__) +logs_handler = SysLogHandler('/dev/log') +logs_handler.setFormatter(logging.Formatter('%(filename)s: %(message)s')) +logger.addHandler(logs_handler) +logger.setLevel(logging.INFO) + +# check if it is safe to restart FRR +def _check_safety(): + try: + # print warning + if not ask_yes_no('WARNING: This is a potentially unsafe function!\n' \ + 'You may lose the connection to the router or active configuration after\n' \ + 'running this command. Use it at your own risk!\n\n' + 'Continue?'): + return False + + # check if another restart process already running + if len([process for process in psutil.process_iter(attrs=['pid', 'name', 'cmdline']) if 'python' in process.info['name'] and 'restart_frr.py' in process.info['cmdline'][1]]) > 1: + message = 'Another restart_frr.py process is already running!' + logger.error(message) + if not ask_yes_no(f'\n{message} It is unsafe to continue.\n\n' \ + 'Do you want to process anyway?'): + return False + + # check if watchfrr.sh is running + tmp = os.path.basename(watchfrr) + if process_named_running(tmp): + message = f'Another {tmp} process is already running.' + logger.error(message) + if not ask_yes_no(f'{message} It is unsafe to continue.\n\n' \ + 'Do you want to process anyway?'): + return False + + # check if vtysh is running + if process_named_running('vtysh'): + message = 'vtysh process is executed by another task.' + logger.error(message) + if not ask_yes_no(f'{message} It is unsafe to continue.\n\n' \ + 'Do you want to process anyway?'): + return False + + # check if temporary directory exists + if os.path.exists(frrconfig_tmp): + message = f'Temporary directory "{frrconfig_tmp}" already exists!' + logger.error(message) + if not ask_yes_no(f'{message} It is unsafe to continue.\n\n' \ + 'Do you want to process anyway?'): + return False + + except: + logger.error("Something goes wrong in _check_safety()") + return False + + # return True if all check was passed or user confirmed to ignore they results + return True + +# write active config to file +def _write_config(): + # create temporary directory + makedir(frrconfig_tmp) + # save frr.conf to it + command = f'{vtysh} -n -w --config_dir {frrconfig_tmp} 2> /dev/null' + return_code = call(command) + if return_code != 0: + logger.error(f'Failed to save active config: "{command}" returned exit code: {return_code}') + return False + logger.info(f'Active config saved to {frrconfig_tmp}') + return True + +# clear and remove temporary directory +def _cleanup(): + if os.path.isdir(frrconfig_tmp): + rmtree(frrconfig_tmp) + +# restart daemon +def _daemon_restart(daemon): + command = f'{watchfrr} restart {daemon}' + return_code = call(command) + if not return_code == 0: + logger.error(f'Failed to restart daemon "{daemon}"!') + return False + + # return True if restarted successfully + logger.info(f'Daemon "{daemon}" restarted!') + return True + +# reload old config +def _reload_config(daemon): + if daemon != '': + command = f'{vtysh} -n -b --config_dir {frrconfig_tmp} -d {daemon} 2> /dev/null' + else: + command = f'{vtysh} -n -b --config_dir {frrconfig_tmp} 2> /dev/null' + + return_code = call(command) + if not return_code == 0: + logger.error('Failed to re-install configuration!') + return False + + # return True if restarted successfully + logger.info('Configuration re-installed successfully!') + return True + +# define program arguments +cmd_args_parser = argparse.ArgumentParser(description='restart frr daemons') +cmd_args_parser.add_argument('--action', choices=['restart'], required=True, help='action to frr daemons') +cmd_args_parser.add_argument('--daemon', choices=['zebra', 'staticd', 'bgpd', 'eigrpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd', 'isisd', 'pimd', 'pim6d', 'ldpd', 'babeld', 'bfdd', 'fabricd'], required=False, nargs='*', help='select single or multiple daemons') +# parse arguments +cmd_args = cmd_args_parser.parse_args() + +# main logic +# restart daemon +if cmd_args.action == 'restart': + # check if it is safe to restart FRR + if not _check_safety(): + print("\nOne of the safety checks was failed or user aborted command. Exiting.") + exit(1) + + if not _write_config(): + print("Failed to save active config") + _cleanup() + exit(1) + + # a little trick to make further commands more clear + if not cmd_args.daemon: + cmd_args.daemon = [''] + + # check all daemons if they are running + if cmd_args.daemon != ['']: + for daemon in cmd_args.daemon: + if not process_named_running(daemon): + Warning('some of listed daemons are not running!') + + # run command to restart daemon + for daemon in cmd_args.daemon: + if not _daemon_restart(daemon): + print('Failed to restart daemon: {daemon}') + _cleanup() + exit(1) + # reinstall old configuration + _reload_config(daemon) + + # cleanup after all actions + _cleanup() + +exit(0) diff --git a/src/op_mode/reverseproxy.py b/src/op_mode/reverseproxy.py new file mode 100644 index 0000000..1970418 --- /dev/null +++ b/src/op_mode/reverseproxy.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json +import socket +import sys + +from tabulate import tabulate +from vyos.configquery import ConfigTreeQuery + +import vyos.opmode + +socket_path = '/run/haproxy/admin.sock' +timeout = 5 + + +def _execute_haproxy_command(command): + """Execute a command on the HAProxy UNIX socket and retrieve the response. + + Args: + command (str): The command to be executed. + + Returns: + str: The response received from the HAProxy UNIX socket. + + Raises: + socket.error: If there is an error while connecting or communicating with the socket. + + Finally: + Closes the socket connection. + + Notes: + - HAProxy expects a newline character at the end of the command. + - The socket connection is established using the HAProxy UNIX socket. + - The response from the socket is received and decoded. + + Example: + response = _execute_haproxy_command('show stat') + print(response) + """ + try: + # HAProxy expects new line for command + command = f'{command}\n' + + # Connect to the HAProxy UNIX socket + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(socket_path) + + # Set the socket timeout + sock.settimeout(timeout) + + # Send the command + sock.sendall(command.encode()) + + # Receive and decode the response + response = b'' + while True: + data = sock.recv(4096) + if not data: + break + response += data + response = response.decode() + + return (response) + + except socket.error as e: + print(f"Error: {e}") + + finally: + # Close the socket + sock.close() + + +def _convert_seconds(seconds): + """Convert seconds to days, hours, minutes, and seconds. + + Args: + seconds (int): The number of seconds to convert. + + Returns: + tuple: A tuple containing the number of days, hours, minutes, and seconds. + """ + minutes = seconds // 60 + hours = minutes // 60 + days = hours // 24 + + return days, hours % 24, minutes % 60, seconds % 60 + + +def _last_change_format(seconds): + """Format the time components into a string representation. + + Args: + seconds (int): The total number of seconds. + + Returns: + str: The formatted time string with days, hours, minutes, and seconds. + + Examples: + >>> _last_change_format(1434) + '23m54s' + >>> _last_change_format(93734) + '1d0h23m54s' + >>> _last_change_format(85434) + '23h23m54s' + """ + days, hours, minutes, seconds = _convert_seconds(seconds) + time_format = "" + + if days: + time_format += f"{days}d" + if hours: + time_format += f"{hours}h" + if minutes: + time_format += f"{minutes}m" + if seconds: + time_format += f"{seconds}s" + + return time_format + + +def _get_json_data(): + """Get haproxy data format JSON""" + return _execute_haproxy_command('show stat json') + + +def _get_raw_data(): + """Retrieve raw data from JSON and organize it into a dictionary. + + Returns: + dict: A dictionary containing the organized data categorized + into frontend, backend, and server. + """ + + data = json.loads(_get_json_data()) + lb_dict = {'frontend': [], 'backend': [], 'server': []} + + for key in data: + frontend = [] + backend = [] + server = [] + for entry in key: + obj_type = entry['objType'].lower() + position = entry['field']['pos'] + name = entry['field']['name'] + value = entry['value']['value'] + + dict_entry = {'pos': position, 'name': {name: value}} + + if obj_type == 'frontend': + frontend.append(dict_entry) + elif obj_type == 'backend': + backend.append(dict_entry) + elif obj_type == 'server': + server.append(dict_entry) + + if len(frontend) > 0: + lb_dict['frontend'].append(frontend) + if len(backend) > 0: + lb_dict['backend'].append(backend) + if len(server) > 0: + lb_dict['server'].append(server) + + return lb_dict + + +def _get_formatted_output(data): + """ + Format the data into a tabulated output. + + Args: + data (dict): The data to be formatted. + + Returns: + str: The tabulated output representing the formatted data. + """ + table = [] + headers = [ + "Proxy name", "Role", "Status", "Req rate", "Resp time", "Last change" + ] + + for key in data: + for item in data[key]: + row = [None] * len(headers) + + for element in item: + if 'pxname' in element['name']: + row[0] = element['name']['pxname'] + elif 'svname' in element['name']: + row[1] = element['name']['svname'] + elif 'status' in element['name']: + row[2] = element['name']['status'] + elif 'req_rate' in element['name']: + row[3] = element['name']['req_rate'] + elif 'rtime' in element['name']: + row[4] = f"{element['name']['rtime']} ms" + elif 'lastchg' in element['name']: + row[5] = _last_change_format(element['name']['lastchg']) + table.append(row) + + out = tabulate(table, headers, numalign="left") + return out + + +def show(raw: bool): + config = ConfigTreeQuery() + if not config.exists('load-balancing reverse-proxy'): + raise vyos.opmode.UnconfiguredSubsystem('Reverse-proxy is not configured') + + data = _get_raw_data() + if raw: + return data + else: + return _get_formatted_output(data) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/route.py b/src/op_mode/route.py new file mode 100644 index 0000000..4aa57db --- /dev/null +++ b/src/op_mode/route.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Purpose: +# Displays routing table information. +# Used by the "run <ip|ipv6> route *" commands. + +import re +import sys +import typing + +from jinja2 import Template + +import vyos.opmode + +frr_command_template = Template(""" +{% if family == "inet" %} + show ip route +{% else %} + show ipv6 route +{% endif %} + +{% if table %} + table {{table}} +{% endif %} + +{% if vrf %} + vrf {{table}} +{% endif %} + +{% if tag %} + tag {{tag}} +{% elif net %} + {{net}} +{% elif protocol %} + {{protocol}} +{% endif %} + +{% if raw %} + json +{% endif %} +""") + +ArgFamily = typing.Literal['inet', 'inet6'] + +def show_summary(raw: bool, family: ArgFamily, table: typing.Optional[int], vrf: typing.Optional[str]): + from vyos.utils.process import cmd + + if family == 'inet': + family_cmd = 'ip' + elif family == 'inet6': + family_cmd = 'ipv6' + else: + raise ValueError(f"Unsupported address family {family}") + + if (table is not None) and (vrf is not None): + raise ValueError("table and vrf options are mutually exclusive") + + # Replace with Jinja if it ever starts growing + if table: + table_cmd = f"table {table}" + else: + table_cmd = "" + + if vrf: + vrf_cmd = f"vrf {vrf}" + else: + vrf_cmd = "" + + if raw: + from json import loads + + output = cmd(f"vtysh -c 'show {family_cmd} route {vrf_cmd} summary {table_cmd} json'").strip() + + # If there are no routes in a table, its "JSON" output is an empty string, + # as of FRR 8.4.1 + if output: + return loads(output) + else: + return {} + else: + output = cmd(f"vtysh -c 'show {family_cmd} route {vrf_cmd} summary {table_cmd}'") + return output + +def show(raw: bool, + family: ArgFamily, + net: typing.Optional[str], + table: typing.Optional[int], + protocol: typing.Optional[str], + vrf: typing.Optional[str], + tag: typing.Optional[str]): + if net and protocol: + raise ValueError("net and protocol are mutually exclusive") + elif table and vrf: + raise ValueError("table and vrf are mutually exclusive") + elif (family == 'inet6') and (protocol == 'rip'): + raise ValueError("rip is not a valid protocol for family inet6") + elif (family == 'inet') and (protocol == 'ripng'): + raise ValueError("rip is not a valid protocol for family inet6") + else: + if (family == 'inet6') and (protocol == 'ospf'): + protocol = 'ospf6' + + kwargs = dict(locals()) + + frr_command = frr_command_template.render(kwargs) + frr_command = re.sub(r'\s+', ' ', frr_command) + + from vyos.utils.process import cmd + output = cmd(f"vtysh -c '{frr_command}'") + + if raw: + from json import loads + d = loads(output) + collect = [] + for k,_ in d.items(): + for l in d[k]: + collect.append(l) + return collect + else: + return output + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) + diff --git a/src/op_mode/secure_boot.py b/src/op_mode/secure_boot.py new file mode 100644 index 0000000..5f6390a --- /dev/null +++ b/src/op_mode/secure_boot.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import vyos.opmode + +from vyos.utils.boot import is_uefi_system +from vyos.utils.system import get_secure_boot_state + +def _get_raw_data(name=None): + sb_data = { + 'state' : get_secure_boot_state(), + 'uefi' : is_uefi_system() + } + return sb_data + +def _get_formatted_output(raw_data): + if not raw_data['uefi']: + print('System run in legacy BIOS mode!') + state = 'enabled' if raw_data['state'] else 'disabled' + return f'SecureBoot {state}' + +def show(raw: bool): + sb_data = _get_raw_data() + if raw: + return sb_data + else: + return _get_formatted_output(sb_data) + +if __name__ == "__main__": + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/serial.py b/src/op_mode/serial.py new file mode 100644 index 0000000..a586487 --- /dev/null +++ b/src/op_mode/serial.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys, typing + +import vyos.opmode +from vyos.utils.serial import restart_login_consoles as _restart_login_consoles + +def restart_console(device_name: typing.Optional[str]): + # Service control moved to vyos.utils.serial to unify checks and prompts. + # If users are connected, we want to show an informational message and a prompt + # to continue, verifying that the user acknowledges possible interruptions. + if device_name: + _restart_login_consoles(prompt_user=True, quiet=False, devices=[device_name]) + else: + _restart_login_consoles(prompt_user=True, quiet=False) + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/sflow.py b/src/op_mode/sflow.py new file mode 100644 index 0000000..0f3feb3 --- /dev/null +++ b/src/op_mode/sflow.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import dbus +import sys + +from tabulate import tabulate + +from vyos.configquery import ConfigTreeQuery + +import vyos.opmode + + +def _get_raw_sflow(): + bus = dbus.SystemBus() + config = ConfigTreeQuery() + + interfaces = config.values('system sflow interface') + servers = config.list_nodes('system sflow server') + + sflow = bus.get_object('net.sflow.hsflowd', '/net/sflow/hsflowd') + sflow_telemetry = dbus.Interface( + sflow, dbus_interface='net.sflow.hsflowd.telemetry') + agent_address = sflow_telemetry.GetAgent() + samples_dropped = int(sflow_telemetry.Get('dropped_samples')) + packet_drop_sent = int(sflow_telemetry.Get('event_samples')) + samples_packet_sent = int(sflow_telemetry.Get('flow_samples')) + samples_counter_sent = int(sflow_telemetry.Get('counter_samples')) + datagrams_sent = int(sflow_telemetry.Get('datagrams')) + rtmetric_samples = int(sflow_telemetry.Get('rtmetric_samples')) + event_samples_suppressed = int(sflow_telemetry.Get('event_samples_suppressed')) + samples_suppressed = int(sflow_telemetry.Get('flow_samples_suppressed')) + counter_samples_suppressed = int( + sflow_telemetry.Get("counter_samples_suppressed")) + version = sflow_telemetry.GetVersion() + + sflow_dict = { + 'agent_address': agent_address, + 'sflow_interfaces': interfaces, + 'sflow_servers': servers, + 'counter_samples_sent': samples_counter_sent, + 'datagrams_sent': datagrams_sent, + 'packet_drop_sent': packet_drop_sent, + 'packet_samples_dropped': samples_dropped, + 'packet_samples_sent': samples_packet_sent, + 'rtmetric_samples': rtmetric_samples, + 'event_samples_suppressed': event_samples_suppressed, + 'flow_samples_suppressed': samples_suppressed, + 'counter_samples_suppressed': counter_samples_suppressed, + 'hsflowd_version': version + } + return sflow_dict + + +def _get_formatted_sflow(data): + table = [ + ['Agent address', f'{data.get("agent_address")}'], + ['sFlow interfaces', f'{data.get("sflow_interfaces", "n/a")}'], + ['sFlow servers', f'{data.get("sflow_servers", "n/a")}'], + ['Counter samples sent', f'{data.get("counter_samples_sent")}'], + ['Datagrams sent', f'{data.get("datagrams_sent")}'], + ['Packet samples sent', f'{data.get("packet_samples_sent")}'], + ['Packet samples dropped', f'{data.get("packet_samples_dropped")}'], + ['Packet drops sent', f'{data.get("packet_drop_sent")}'], + ['Packet drops suppressed', f'{data.get("event_samples_suppressed")}'], + ['Flow samples suppressed', f'{data.get("flow_samples_suppressed")}'], + ['Counter samples suppressed', f'{data.get("counter_samples_suppressed")}'] + ] + + return tabulate(table) + + +def show(raw: bool): + + config = ConfigTreeQuery() + if not config.exists('system sflow'): + raise vyos.opmode.UnconfiguredSubsystem( + '"system sflow" is not configured!') + + sflow_data = _get_raw_sflow() + if raw: + return sflow_data + else: + return _get_formatted_sflow(sflow_data) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/show-bond.py b/src/op_mode/show-bond.py new file mode 100644 index 0000000..f676e08 --- /dev/null +++ b/src/op_mode/show-bond.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import jinja2 + +from argparse import ArgumentParser +from vyos.ifconfig import Section +from vyos.ifconfig import BondIf +from vyos.utils.file import read_file + +from sys import exit + +parser = ArgumentParser() +parser.add_argument("--slaves", action="store_true", help="Show LLDP neighbors on all interfaces") +parser.add_argument("--interface", action="store", help="Show LLDP neighbors on specific interface") + +args = parser.parse_args() + +all_bonds = Section.interfaces('bonding') +# we are not interested in any bond vlan interface +all_bonds = [x for x in all_bonds if '.' not in x] + +TMPL_BRIEF = """Interface Mode State Link Slaves +{% for interface in data %} +{{ "%-12s" | format(interface.ifname) }} {{ "%-22s" | format(interface.mode) }} {{ "%-8s" | format(interface.admin_state) }} {{ "%-6s" | format(interface.oper_state) }} {{ interface.members | join(' ') }} +{% endfor %} +""" + +TMPL_INDIVIDUAL_BOND = """Interface RX: bytes packets TX: bytes packets +{{ "%-16s" | format(data.ifname) }} {{ "%-10s" | format(data.rx_bytes) }} {{ "%-11s" | format(data.rx_packets) }} {{ "%-10s" | format(data.tx_bytes) }} {{ data.tx_packets }} +{% for member in data.members if data.members is defined %} + {{ "%-12s" | format(member.ifname) }} {{ "%-10s" | format(member.rx_bytes) }} {{ "%-11s" | format(member.rx_packets) }} {{ "%-10s" | format(member.tx_bytes) }} {{ member.tx_packets }} +{% endfor %} +""" + +if args.slaves and args.interface: + exit('Can not use both --slaves and --interfaces option at the same time') + parser.print_help() + +elif args.slaves: + data = [] + template = TMPL_BRIEF + for bond in all_bonds: + tmp = BondIf(bond) + cfg_dict = {} + cfg_dict['ifname'] = bond + cfg_dict['mode'] = tmp.get_mode() + cfg_dict['admin_state'] = tmp.get_admin_state() + cfg_dict['oper_state'] = tmp.operational.get_state() + cfg_dict['members'] = tmp.get_slaves() + data.append(cfg_dict) + +elif args.interface: + template = TMPL_INDIVIDUAL_BOND + data = {} + data['ifname'] = args.interface + data['rx_bytes'] = read_file(f'/sys/class/net/{args.interface}/statistics/rx_bytes') + data['rx_packets'] = read_file(f'/sys/class/net/{args.interface}/statistics/rx_packets') + data['tx_bytes'] = read_file(f'/sys/class/net/{args.interface}/statistics/tx_bytes') + data['tx_packets'] = read_file(f'/sys/class/net/{args.interface}/statistics/tx_packets') + + # each bond member interface has its own statistics + data['members'] = [] + for member in BondIf(args.interface).get_slaves(): + tmp = {} + tmp['ifname'] = member + tmp['rx_bytes'] = read_file(f'/sys/class/net/{member}/statistics/rx_bytes') + tmp['rx_packets'] = read_file(f'/sys/class/net/{member}/statistics/rx_packets') + tmp['tx_bytes'] = read_file(f'/sys/class/net/{member}/statistics/tx_bytes') + tmp['tx_packets'] = read_file(f'/sys/class/net/{member}/statistics/tx_packets') + data['members'].append(tmp) + +else: + parser.print_help() + exit(1) + +tmpl = jinja2.Template(template, trim_blocks=True) +config_text = tmpl.render(data=data) +print(config_text) diff --git a/src/op_mode/show_acceleration.py b/src/op_mode/show_acceleration.py new file mode 100644 index 0000000..1c4831f --- /dev/null +++ b/src/op_mode/show_acceleration.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import os +import re +import argparse + +from vyos.config import Config +from vyos.utils.process import call +from vyos.utils.process import popen + +def detect_qat_dev(): + output, err = popen('lspci -nn', decode='utf-8') + if not err: + data = re.findall('(8086:19e2)|(8086:37c8)|(8086:0435)|(8086:6f54)', output) + # QAT devices found + if data: + return + print("\t No QAT device found") + sys.exit(1) + +def show_qat_status(): + detect_qat_dev() + + # Check QAT service + if not os.path.exists('/etc/init.d/qat_service'): + print("\t QAT service not installed") + sys.exit(1) + + # Show QAT service + call('/etc/init.d/qat_service status') + +# Return QAT devices +def get_qat_devices(): + data_st, err = popen('/etc/init.d/qat_service status', decode='utf-8') + if not err: + elm_lst = re.findall('qat_dev\d', data_st) + print('\n'.join(elm_lst)) + +# Return QAT path in sysfs +def get_qat_proc_path(qat_dev): + q_type = "" + q_bsf = "" + output, err = popen('/etc/init.d/qat_service status', decode='utf-8') + if not err: + # Parse QAT service output + data_st = output.split("\n") + for elm_str in range(len(data_st)): + if re.search(qat_dev, data_st[elm_str]): + elm_list = data_st[elm_str].split(", ") + for elm in range(len(elm_list)): + if re.search('type', elm_list[elm]): + q_list = elm_list[elm].split(": ") + q_type=q_list[1] + elif re.search('bsf', elm_list[elm]): + q_list = elm_list[elm].split(": ") + q_bsf = q_list[1] + return "/sys/kernel/debug/qat_"+q_type+"_"+q_bsf+"/" + +# Check if QAT service confgured +def check_qat_if_conf(): + if not Config().exists_effective('system acceleration qat'): + print("\t system acceleration qat is not configured") + sys.exit(1) + +parser = argparse.ArgumentParser() +group = parser.add_mutually_exclusive_group() +group.add_argument("--hw", action="store_true", help="Show Intel QAT HW") +group.add_argument("--dev_list", action="store_true", help="Return Intel QAT devices") +group.add_argument("--flow", action="store_true", help="Show Intel QAT flows") +group.add_argument("--interrupts", action="store_true", help="Show Intel QAT interrupts") +group.add_argument("--status", action="store_true", help="Show Intel QAT status") +group.add_argument("--conf", action="store_true", help="Show Intel QAT configuration") + +parser.add_argument("--dev", type=str, help="Selected QAT device") + +args = parser.parse_args() + +if args.hw: + detect_qat_dev() + # Show availible Intel QAT devices + call('lspci -nn | egrep -e \'8086:37c8|8086:19e2|8086:0435|8086:6f54\'') +elif args.flow and args.dev: + check_qat_if_conf() + call('cat '+get_qat_proc_path(args.dev)+"fw_counters") +elif args.interrupts: + check_qat_if_conf() + # Delete _dev from args.dev + call('cat /proc/interrupts | grep qat') +elif args.status: + check_qat_if_conf() + show_qat_status() +elif args.conf and args.dev: + check_qat_if_conf() + call('cat '+get_qat_proc_path(args.dev)+"dev_cfg") +elif args.dev_list: + get_qat_devices() +else: + parser.print_help() + sys.exit(1) diff --git a/src/op_mode/show_configuration_files.sh b/src/op_mode/show_configuration_files.sh new file mode 100644 index 0000000..ad8e074 --- /dev/null +++ b/src/op_mode/show_configuration_files.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Wrapper script for the show configuration files command +find ${vyatta_sysconfdir}/config/ \ + -type f \ + -not -name ".*" \ + -not -name "config.boot.*" \ + -printf "%f\t(%Tc)\t%T@\n" \ + | sort -r -k3 \ + | awk -F"\t" '{printf ("%-20s\t%s\n", $1,$2) ;}' diff --git a/src/op_mode/show_configuration_json.py b/src/op_mode/show_configuration_json.py new file mode 100644 index 0000000..fdece53 --- /dev/null +++ b/src/op_mode/show_configuration_json.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import json + +from vyos.configquery import ConfigTreeQuery + + +config = ConfigTreeQuery() +c = config.get_config_dict() + +parser = argparse.ArgumentParser() +parser.add_argument("-p", "--pretty", action="store_true", help="Show pretty configuration in JSON format") + + +if __name__ == '__main__': + args = parser.parse_args() + + if args.pretty: + print(json.dumps(c, indent=4)) + else: + print(json.dumps(c)) diff --git a/src/op_mode/show_current_user.sh b/src/op_mode/show_current_user.sh new file mode 100644 index 0000000..93e6efa --- /dev/null +++ b/src/op_mode/show_current_user.sh @@ -0,0 +1,18 @@ +#! /bin/bash + +echo -n "login : " ; who -m + +if [ -n "$VYATTA_USER_LEVEL_DIR" ] +then + echo -n "level : " + basename $VYATTA_USER_LEVEL_DIR +fi + +echo -n "user : " ; id -un +echo -n "groups : " ; id -Gn + +if id -Z >/dev/null 2>&1 +then + echo -n "context : " + id -Z +fi diff --git a/src/op_mode/show_disk_format.sh b/src/op_mode/show_disk_format.sh new file mode 100644 index 0000000..61b15a5 --- /dev/null +++ b/src/op_mode/show_disk_format.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +disk_dev="/dev/$1" +if [ ! -b "$disk_dev" ];then + echo "$3 is not a disk device" + exit 1 +fi +sudo /sbin/fdisk -l "$disk_dev" diff --git a/src/op_mode/show_ntp.sh b/src/op_mode/show_ntp.sh new file mode 100644 index 0000000..4b59b80 --- /dev/null +++ b/src/op_mode/show_ntp.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +sourcestats=0 +tracking=0 + +while [[ "$#" -gt 0 ]]; do + case $1 in + --sourcestats) sourcestats=1 ;; + --tracking) tracking=1 ;; + *) echo "Unknown parameter passed: $1" ;; + esac + shift +done + +if ! ps -C chronyd &>/dev/null; then + echo NTP daemon disabled + exit 1 +fi + +PID=$(pgrep chronyd | head -n1) +VRF_NAME=$(ip vrf identify ${PID}) + +if [ ! -z ${VRF_NAME} ]; then + VRF_CMD="sudo ip vrf exec ${VRF_NAME}" +fi + +if [ $sourcestats -eq 1 ]; then + $VRF_CMD chronyc sourcestats -v +elif [ $tracking -eq 1 ]; then + $VRF_CMD chronyc tracking -v +else + echo "Unknown option" +fi + diff --git a/src/op_mode/show_openconnect_otp.py b/src/op_mode/show_openconnect_otp.py new file mode 100644 index 0000000..3771fb3 --- /dev/null +++ b/src/op_mode/show_openconnect_otp.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 + +# Copyright 2017-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import os +from base64 import b32encode + +from vyos.config import Config +from vyos.utils.dict import dict_search_args +from vyos.utils.process import popen + +otp_file = '/run/ocserv/users.oath' + +def check_uname_otp(username): + """ + Check if "username" exists and have an OTP key + """ + config = Config() + base_key = ['vpn', 'openconnect', 'authentication', 'local-users', 'username', username, 'otp', 'key'] + if not config.exists(base_key): + return False + return True + +def get_otp_ocserv(username): + config = Config() + base = ['vpn', 'openconnect'] + if not config.exists(base): + return None + + ocserv = config.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + user_path = ['authentication', 'local_users', 'username'] + users = dict_search_args(ocserv, *user_path) + + if users is None: + return None + + # function is called conditionally, if check_uname_otp true, so username + # exists + result = users[username] + + return result + +def display_otp_ocserv(username, params, info): + hostname = os.uname()[1] + key_hex = params['otp']['key'] + otp_length = params['otp']['otp_length'] + interval = params['otp']['interval'] + token_type = params['otp']['token_type'] + if token_type == 'hotp-time': + token_type_acrn = 'totp' + key_base32 = b32encode(bytes.fromhex(key_hex)).decode() + otp_url = ''.join(["otpauth://",token_type_acrn,"/",username,"@",hostname,"?secret=",key_base32,"&digits=",otp_length,"&period=",interval]) + qrcode,err = popen('qrencode -t ansiutf8', input=otp_url) + + if info == 'full': + print("# You can share it with the user, he just needs to scan the QR in his OTP app") + print("# username: ", username) + print("# OTP KEY: ", key_base32) + print("# OTP URL: ", otp_url) + print(qrcode) + print('# To add this OTP key to configuration, run the following commands:') + print(f"set vpn openconnect authentication local-users username {username} otp key '{key_hex}'") + if interval != "30": + print(f"set vpn openconnect authentication local-users username {username} otp interval '{interval}'") + if otp_length != "6": + print(f"set vpn openconnect authentication local-users username {username} otp otp-length '{otp_length}'") + elif info == 'key-hex': + print("# OTP key in hexadecimal: ") + print(key_hex) + elif info == 'key-b32': + print("# OTP key in Base32: ") + print(key_base32) + elif info == 'qrcode': + print(f"# QR code for OpenConnect user '{username}'") + print(qrcode) + elif info == 'uri': + print(f"# URI for OpenConnect user '{username}'") + print(otp_url) + +if __name__ == '__main__': + parser = argparse.ArgumentParser(add_help=False, description='Show OTP authentication information for selected user') + parser.add_argument('--user', action="store", type=str, default='', help='Username') + parser.add_argument('--info', action="store", type=str, default='full', help='Wich information to display') + + args = parser.parse_args() + if check_uname_otp(args.user): + user_otp_params = get_otp_ocserv(args.user) + display_otp_ocserv(args.user, user_otp_params, args.info) + else: + print(f'There is no such user ("{args.user}") with an OTP key configured') diff --git a/src/op_mode/show_openvpn.py b/src/op_mode/show_openvpn.py new file mode 100644 index 0000000..6abafc8 --- /dev/null +++ b/src/op_mode/show_openvpn.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import os +import jinja2 +import argparse + +from sys import exit +from vyos.config import Config + +outp_tmpl = """ +{% if clients %} +OpenVPN status on {{ intf }} + +Client CN Remote Host Tunnel IP Local Host TX bytes RX bytes Connected Since +--------- ----------- --------- ---------- -------- -------- --------------- +{% for c in clients %} +{{ "%-15s"|format(c.name) }} {{ "%-21s"|format(c.remote) }} {{ "%-15s"|format(c.tunnel) }} {{ "%-21s"|format(local) }} {{ "%-9s"|format(c.tx_bytes) }} {{ "%-9s"|format(c.rx_bytes) }} {{ c.online_since }} +{% endfor %} +{% endif %} +""" + +def bytes2HR(size): + # we need to operate in integers + size = int(size) + + suff = ['B', 'KB', 'MB', 'GB', 'TB'] + suffIdx = 0 + + while size > 1024: + # incr. suffix index + suffIdx += 1 + # divide + size = size/1024.0 + + output="{0:.1f} {1}".format(size, suff[suffIdx]) + return output + +def get_vpn_tunnel_address(peer, interface): + lst = [] + status_file = '/var/run/openvpn/{}.status'.format(interface) + + with open(status_file, 'r') as f: + lines = f.readlines() + for line in lines: + if peer in line: + lst.append(line) + + # filter out subnet entries + lst = [l for l in lst[1:] if '/' not in l.split(',')[0]] + + if lst: + tunnel_ip = lst[0].split(',')[0] + return tunnel_ip + + return 'n/a' + +def get_status(mode, interface): + status_file = '/var/run/openvpn/{}.status'.format(interface) + # this is an empirical value - I assume we have no more then 999999 + # current OpenVPN connections + routing_table_line = 999999 + + data = { + 'mode': mode, + 'intf': interface, + 'local': 'N/A', + 'date': '', + 'clients': [], + } + + if not os.path.exists(status_file): + return data + + with open(status_file, 'r') as f: + lines = f.readlines() + for line_no, line in enumerate(lines): + # remove trailing newline character first + line = line.rstrip('\n') + + # check first line header + if line_no == 0: + if mode == 'server': + if not line == 'OpenVPN CLIENT LIST': + raise NameError('Expected "OpenVPN CLIENT LIST"') + else: + if not line == 'OpenVPN STATISTICS': + raise NameError('Expected "OpenVPN STATISTICS"') + + continue + + # second line informs us when the status file has been last updated + if line_no == 1: + data['date'] = line.lstrip('Updated,').rstrip('\n') + continue + + if mode == 'server': + # followed by line3 giving output information and the actual output data + # + # Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since + # client1,172.18.202.10:55904,2880587,2882653,Fri Aug 23 16:25:48 2019 + # client3,172.18.204.10:41328,2850832,2869729,Fri Aug 23 16:25:43 2019 + # client2,172.18.203.10:48987,2856153,2871022,Fri Aug 23 16:25:45 2019 + if (line_no >= 3) and (line_no < routing_table_line): + # indicator that there are no more clients and we will continue with the + # routing table + if line == 'ROUTING TABLE': + routing_table_line = line_no + continue + + client = { + 'name': line.split(',')[0], + 'remote': line.split(',')[1], + 'rx_bytes': bytes2HR(line.split(',')[2]), + 'tx_bytes': bytes2HR(line.split(',')[3]), + 'online_since': line.split(',')[4] + } + client["tunnel"] = get_vpn_tunnel_address(client['remote'], interface) + data['clients'].append(client) + continue + else: + if line_no == 2: + client = { + 'name': 'N/A', + 'remote': 'N/A', + 'rx_bytes': bytes2HR(line.split(',')[1]), + 'tx_bytes': '', + 'online_since': 'N/A' + } + continue + + if line_no == 3: + client['tx_bytes'] = bytes2HR(line.split(',')[1]) + data['clients'].append(client) + break + + return data + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-m', '--mode', help='OpenVPN operation mode (server, client, site-2-site)', required=True) + + args = parser.parse_args() + + # Do nothing if service is not configured + config = Config() + if len(config.list_effective_nodes('interfaces openvpn')) == 0: + print("No OpenVPN interfaces configured") + exit(0) + + # search all OpenVPN interfaces and add those with a matching mode to our + # interfaces list + interfaces = [] + for intf in config.list_effective_nodes('interfaces openvpn'): + # get interface type (server, client, site-to-site) + mode = config.return_effective_value('interfaces openvpn {} mode'.format(intf)) + if args.mode == mode: + interfaces.append(intf) + + for intf in interfaces: + data = get_status(args.mode, intf) + local_host = config.return_effective_value('interfaces openvpn {} local-host'.format(intf)) + local_port = config.return_effective_value('interfaces openvpn {} local-port'.format(intf)) + if local_host and local_port: + data['local'] = local_host + ':' + local_port + + if args.mode in ['client', 'site-to-site']: + for client in data['clients']: + if config.exists_effective('interfaces openvpn {} shared-secret-key-file'.format(intf)): + client['name'] = "None (PSK)" + + remote_host = config.return_effective_values('interfaces openvpn {} remote-host'.format(intf)) + remote_port = config.return_effective_value('interfaces openvpn {} remote-port'.format(intf)) + + if not remote_port: + remote_port = '1194' + + if len(remote_host) >= 1: + client['remote'] = str(remote_host[0]) + ':' + remote_port + + client['tunnel'] = 'N/A' + + tmpl = jinja2.Template(outp_tmpl) + print(tmpl.render(data)) diff --git a/src/op_mode/show_openvpn_mfa.py b/src/op_mode/show_openvpn_mfa.py new file mode 100644 index 0000000..100c421 --- /dev/null +++ b/src/op_mode/show_openvpn_mfa.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# +# Copyright 2017-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import re +import socket +import urllib.parse +import argparse + +from vyos.utils.process import popen + +otp_file = '/config/auth/openvpn/{interface}-otp-secrets' + +def get_mfa_secret(interface, client): + try: + with open(otp_file.format(interface=interface), "r") as f: + users = f.readlines() + for user in users: + if re.search('^' + client + ' ', user): + return user.split(':')[3] + except: + pass + +def get_mfa_uri(client, secret): + hostname = socket.gethostname() + fqdn = socket.getfqdn() + uri = 'otpauth://totp/{hostname}:{client}@{fqdn}?secret={secret}' + + return urllib.parse.quote(uri.format(hostname=hostname, client=client, fqdn=fqdn, secret=secret), safe='/:@?=') + +if __name__ == '__main__': + parser = argparse.ArgumentParser(add_help=False, description='Show two-factor authentication information') + parser.add_argument('--intf', action="store", type=str, default='', help='only show the specified interface') + parser.add_argument('--user', action="store", type=str, default='', help='only show the specified users') + parser.add_argument('--action', action="store", type=str, default='show', help='action to perform') + + args = parser.parse_args() + secret = get_mfa_secret(args.intf, args.user) + + if args.action == "secret" and secret: + print(secret) + + if args.action == "uri" and secret: + uri = get_mfa_uri(args.user, secret) + print(uri) + + if args.action == "qrcode" and secret: + uri = get_mfa_uri(args.user, secret) + qrcode,err = popen('qrencode -t ansiutf8', input=uri) + print(qrcode) + diff --git a/src/op_mode/show_raid.sh b/src/op_mode/show_raid.sh new file mode 100644 index 0000000..ab5d4d5 --- /dev/null +++ b/src/op_mode/show_raid.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +if [ "$EUID" -ne 0 ]; then + # This should work without sudo because we have read + # access to the dev, but for some reason mdadm must be + # run as root in order to succeed. + echo "Please run as root" + exit 1 +fi + +raid_set_name=$1 +raid_sets=`cat /proc/partitions | grep md | awk '{ print $4 }'` +valid_set=`echo $raid_sets | grep $raid_set_name` +if [ -z $valid_set ]; then + echo "$raid_set_name is not a RAID set" +else + if [ -r /dev/${raid_set_name} ]; then + # This should work without sudo because we have read + # access to the dev, but for some reason mdadm must be + # run as root in order to succeed. + mdadm --detail /dev/${raid_set_name} + else + echo "Must be administrator or root to display RAID status" + fi +fi diff --git a/src/op_mode/show_sensors.py b/src/op_mode/show_sensors.py new file mode 100644 index 0000000..5e3084f --- /dev/null +++ b/src/op_mode/show_sensors.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# +# Copyright 2017-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import re +import sys +from vyos.utils.process import popen +from vyos.utils.process import DEVNULL + +output,retcode = popen("sensors --no-adapter", stderr=DEVNULL) +if retcode == 0: + print (output) + sys.exit(0) +else: + output,retcode = popen("sensors-detect --auto",stderr=DEVNULL) + match = re.search(r'#----cut here----(.*)#----cut here----',output, re.DOTALL) + if match: + for module in match.group(0).split('\n'): + if not module.startswith("#"): + popen("modprobe {}".format(module.strip())) + output,retcode = popen("sensors --no-adapter", stderr=DEVNULL) + if retcode == 0: + print (output) + sys.exit(0) + + +print ("No sensors found") +sys.exit(1) diff --git a/src/op_mode/show_techsupport_report.py b/src/op_mode/show_techsupport_report.py new file mode 100644 index 0000000..32cf677 --- /dev/null +++ b/src/op_mode/show_techsupport_report.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys +from typing import List +from vyos.ifconfig import Section +from vyos.ifconfig import Interface +from vyos.utils.process import rc_cmd + + +def print_header(command: str) -> None: + """Prints a command with headers '-'. + + Example: + + % print_header('Example command') + + --------------- + Example command + --------------- + """ + header_length = len(command) * '-' + print(f"\n{header_length}\n{command}\n{header_length}") + + +def execute_command(command: str, header_text: str) -> None: + """Executes a command and prints the output with a header. + + Example: + % execute_command('uptime', "Uptime of the system") + + -------------------- + Uptime of the system + -------------------- + 20:21:57 up 9:04, 5 users, load average: 0.00, 0.00, 0.0 + + """ + print_header(header_text) + try: + rc, output = rc_cmd(command) + # Enable unbuffered print param to improve responsiveness of printed + # output to end user + print(output, flush=True) + # Exit gracefully when user interrupts program output + # Flush standard streams; redirect remaining output to devnull + # Resolves T5633: Bug #1 and 3 + except (BrokenPipeError, KeyboardInterrupt): + os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno()) + sys.exit(1) + except Exception as e: + print(f"Error executing command: {command}") + print(f"Error message: {e}") + + +def op(cmd: str) -> str: + """Returns a command with the VyOS operational mode wrapper.""" + return f'/opt/vyatta/bin/vyatta-op-cmd-wrapper {cmd}' + + +def get_ethernet_interfaces() -> List[Interface]: + """Returns a list of Ethernet interfaces.""" + return Section.interfaces('ethernet') + + +def show_version() -> None: + """Prints the VyOS version and package changes.""" + execute_command(op('show version'), 'VyOS Version and Package Changes') + + +def show_config_file() -> None: + """Prints the contents of a configuration file with a header.""" + execute_command('cat /opt/vyatta/etc/config/config.boot', 'Configuration file') + + +def show_running_config() -> None: + """Prints the running configuration.""" + execute_command(op('show configuration'), 'Running configuration') + + +def show_package_repository_config() -> None: + """Prints the package repository configuration file.""" + execute_command('cat /etc/apt/sources.list', 'Package Repository Configuration File') + execute_command('ls -l /etc/apt/sources.list.d/', 'Repositories') + + +def show_user_startup_scripts() -> None: + """Prints the user startup scripts.""" + execute_command('cat /config/scripts/vyos-preconfig-bootup.script', 'User Startup Scripts (Preconfig)') + execute_command('cat /config/scripts/vyos-postconfig-bootup.script', 'User Startup Scripts (Postconfig)') + + +def show_frr_config() -> None: + """Prints the FRR configuration.""" + execute_command('vtysh -c "show run"', 'FRR configuration') + + +def show_interfaces() -> None: + """Prints the interfaces.""" + execute_command(op('show interfaces'), 'Interfaces') + + +def show_interface_statistics() -> None: + """Prints the interface statistics.""" + execute_command('ip -s link show', 'Interface statistics') + + +def show_physical_interface_statistics() -> None: + """Prints the physical interface statistics.""" + execute_command('/usr/bin/true', 'Physical Interface statistics') + for iface in get_ethernet_interfaces(): + # Exclude vlans + if '.' in iface: + continue + execute_command(f'ethtool --driver {iface}', f'ethtool --driver {iface}') + execute_command(f'ethtool --statistics {iface}', f'ethtool --statistics {iface}') + execute_command(f'ethtool --show-ring {iface}', f'ethtool --show-ring {iface}') + execute_command(f'ethtool --show-coalesce {iface}', f'ethtool --show-coalesce {iface}') + execute_command(f'ethtool --pause {iface}', f'ethtool --pause {iface}') + execute_command(f'ethtool --show-features {iface}', f'ethtool --show-features {iface}') + execute_command(f'ethtool --phy-statistics {iface}', f'ethtool --phy-statistics {iface}') + execute_command('netstat --interfaces', 'netstat --interfaces') + execute_command('netstat --listening', 'netstat --listening') + execute_command('cat /proc/net/dev', 'cat /proc/net/dev') + + +def show_bridge() -> None: + """Show bridge interfaces.""" + execute_command(op('show bridge'), 'Show bridge') + + +def show_arp() -> None: + """Prints ARP entries.""" + execute_command(op('show arp'), 'ARP Table (Total entries)') + execute_command(op('show ipv6 neighbors'), 'show ipv6 neighbors') + + +def show_route() -> None: + """Prints routing information.""" + + cmd_list_route = [ + "show ip route bgp | head -108", + "show ip route cache", + "show ip route connected", + "show ip route forward", + "show ip route isis | head -108", + "show ip route kernel", + "show ip route ospf | head -108", + "show ip route rip", + "show ip route static", + "show ip route summary", + "show ip route supernets-only", + "show ip route table all", + "show ip route vrf all", + "show ipv6 route bgp | head -108", + "show ipv6 route cache", + "show ipv6 route connected", + "show ipv6 route forward", + "show ipv6 route isis", + "show ipv6 route kernel", + "show ipv6 route ospfv3", + "show ipv6 route rip", + "show ipv6 route static", + "show ipv6 route summary", + "show ipv6 route table all", + "show ipv6 route vrf all", + ] + for command in cmd_list_route: + execute_command(op(command), command) + + +def show_firewall() -> None: + """Prints firweall information.""" + execute_command('sudo nft list ruleset', 'nft list ruleset') + + +def show_system() -> None: + """Prints system parameters.""" + execute_command(op('show version'), 'Show System Version') + execute_command(op('show system storage'), 'Show System Storage') + execute_command(op('show system image details'), 'Show System Image Details') + + +def show_date() -> None: + """Print the current date.""" + execute_command('date', 'Current Time') + + +def show_installed_packages() -> None: + """Prints installed packages.""" + execute_command('dpkg --list', 'Installed Packages') + + +def show_loaded_modules() -> None: + """Prints loaded modules /proc/modules""" + execute_command('cat /proc/modules', 'Loaded Modules') + + +def show_cpu_statistics() -> None: + """Prints CPU statistics.""" + execute_command('/usr/bin/true', 'CPU') + execute_command('lscpu', 'Installed CPU\'s') + execute_command('top --iterations 1 --batch-mode --accum-time-toggle', 'Cumulative CPU Time Used by Running Processes') + execute_command('cat /proc/loadavg', 'Load Average') + + +def show_system_interrupts() -> None: + """Prints system interrupts.""" + execute_command('cat /proc/interrupts', 'Hardware Interrupt Counters') + + +def show_soft_irqs() -> None: + """Prints soft IRQ's.""" + execute_command('cat /proc/softirqs', 'Soft IRQ\'s') + + +def show_softnet_statistics() -> None: + """Prints softnet statistics.""" + execute_command('cat /proc/net/softnet_stat', 'cat /proc/net/softnet_stat') + + +def show_running_processes() -> None: + """Prints current running processes""" + execute_command('ps -ef', 'Running Processes') + + +def show_memory_usage() -> None: + """Prints memory usage""" + execute_command('/usr/bin/true', 'Memory') + execute_command('cat /proc/meminfo', 'Installed Memory') + execute_command('free', 'Memory Usage') + + +def list_disks(): + disks = set() + with open('/proc/partitions') as partitions_file: + for line in partitions_file: + fields = line.strip().split() + if len(fields) == 4 and fields[3].isalpha() and fields[3] != 'name': + disks.add(fields[3]) + return disks + + +def show_storage() -> None: + """Prints storage information.""" + execute_command('cat /proc/devices', 'Devices') + execute_command('cat /proc/partitions', 'Partitions') + + for disk in list_disks(): + execute_command(f'fdisk --list /dev/{disk}', f'Partitioning for disk {disk}') + + +def main(): + # Configuration data + show_version() + show_config_file() + show_running_config() + show_package_repository_config() + show_user_startup_scripts() + show_frr_config() + + # Interfaces + show_interfaces() + show_interface_statistics() + show_physical_interface_statistics() + show_bridge() + show_arp() + + # Routing + show_route() + + # Firewall + show_firewall() + + # System + show_system() + show_date() + show_installed_packages() + show_loaded_modules() + + # CPU + show_cpu_statistics() + show_system_interrupts() + show_soft_irqs() + show_softnet_statistics() + + # Memory + show_memory_usage() + + # Storage + show_storage() + + # Processes + show_running_processes() + + # TODO: Get information from clouds + + +if __name__ == "__main__": + main() diff --git a/src/op_mode/show_usb_serial.py b/src/op_mode/show_usb_serial.py new file mode 100644 index 0000000..973bf19 --- /dev/null +++ b/src/op_mode/show_usb_serial.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from jinja2 import Template +from pyudev import Context, Devices +from sys import exit + +OUT_TMPL_SRC = """Device Model Vendor +------ ------ ------ +{% for d in devices %} +{{ "%-16s" | format(d.device) }} {{ "%-19s" | format(d.model)}} {{d.vendor}} +{% endfor %} + +""" + +data = { + 'devices': [] +} + + +base_directory = '/dev/serial/by-bus' +if not os.path.isdir(base_directory): + print("No USB to serial converter connected") + exit(0) + +context = Context() +for root, dirs, files in os.walk(base_directory): + for basename in files: + os.path.join(root, basename) + device = Devices.from_device_file(context, os.path.join(root, basename)) + tmp = { + 'device': basename, + 'model': device.properties.get('ID_MODEL'), + 'vendor': device.properties.get('ID_VENDOR_FROM_DATABASE') + } + data['devices'].append(tmp) + +data['devices'] = sorted(data['devices'], key = lambda i: i['device']) +tmpl = Template(OUT_TMPL_SRC) +print(tmpl.render(data)) + +exit(0) diff --git a/src/op_mode/show_users.py b/src/op_mode/show_users.py new file mode 100644 index 0000000..82bd585 --- /dev/null +++ b/src/op_mode/show_users.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import argparse +import pwd +import struct +import sys +from time import ctime + +from tabulate import tabulate +from vyos.config import Config + + +class UserInfo: + def __init__(self, uid, name, user_type, is_locked, login_time, tty, host): + self.uid = uid + self.name = name + self.user_type = user_type + self.is_locked = is_locked + self.login_time = login_time + self.tty = tty + self.host = host + + +filters = { + 'default': lambda user: not user.is_locked, # Default is everything but locked accounts + 'vyos': lambda user: user.user_type == 'vyos', + 'other': lambda user: user.user_type != 'vyos', + 'locked': lambda user: user.is_locked, + 'all': lambda user: True +} + + +def is_locked(user_name: str) -> bool: + """Check if a given user has password in shadow db""" + + try: + import warnings + with warnings.catch_warnings(): + warnings.filterwarnings("ignore",category=DeprecationWarning) + import spwd + encrypted_password = spwd.getspnam(user_name)[1] + return encrypted_password == '*' or encrypted_password.startswith('!') + except (KeyError, PermissionError): + print('Cannot access shadow database, ensure this script is run with sufficient permissions') + sys.exit(1) + + +def decode_lastlog(lastlog_file, uid: int): + """Decode last login info of a given user uid from the lastlog file""" + + struct_fmt = '=L32s256s' + recordsize = struct.calcsize(struct_fmt) + lastlog_file.seek(recordsize * uid) + buf = lastlog_file.read(recordsize) + if len(buf) < recordsize: + return None + (time, tty, host) = struct.unpack(struct_fmt, buf) + time = 'never logged in' if time == 0 else ctime(time) + tty = tty.strip(b'\x00') + host = host.strip(b'\x00') + return time, tty, host + + +def list_users(): + cfg = Config() + vyos_users = cfg.list_effective_nodes('system login user') + users = [] + with open('/var/log/lastlog', 'rb') as lastlog_file: + for (name, _, uid, _, _, _, _) in pwd.getpwall(): + lastlog_info = decode_lastlog(lastlog_file, uid) + if lastlog_info is None: + continue + user_info = UserInfo( + uid, name, + user_type='vyos' if name in vyos_users else 'other', + is_locked=is_locked(name), + login_time=lastlog_info[0], + tty=lastlog_info[1], + host=lastlog_info[2]) + users.append(user_info) + return users + + +def main(): + parser = argparse.ArgumentParser(prog=sys.argv[0], add_help=False) + parser.add_argument('type', nargs='?', choices=['all', 'vyos', 'other', 'locked']) + args = parser.parse_args() + + filter_type = args.type if args.type is not None else 'default' + filter_expr = filters[filter_type] + + headers = ['Username', 'Type', 'Locked', 'Tty', 'From', 'Last login'] + table_data = [] + for user in list_users(): + if filter_expr(user): + table_data.append([user.name, user.user_type, user.is_locked, user.tty, user.host, user.login_time]) + print(tabulate(table_data, headers, tablefmt='simple')) + + +if __name__ == '__main__': + main() diff --git a/src/op_mode/show_virtual_server.py b/src/op_mode/show_virtual_server.py new file mode 100644 index 0000000..7880edc --- /dev/null +++ b/src/op_mode/show_virtual_server.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from vyos.configquery import ConfigTreeQuery +from vyos.utils.process import call + +def is_configured(): + """ Check if high-availability virtual-server is configured """ + config = ConfigTreeQuery() + if not config.exists(['high-availability', 'virtual-server']): + return False + return True + +if __name__ == '__main__': + + if is_configured() == False: + print('Virtual server not configured!') + exit(0) + + call('sudo ipvsadm --list --numeric') diff --git a/src/op_mode/show_wwan.py b/src/op_mode/show_wwan.py new file mode 100644 index 0000000..bd97bb0 --- /dev/null +++ b/src/op_mode/show_wwan.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse + +from sys import exit +from vyos.configquery import ConfigTreeQuery +from vyos.utils.process import cmd + +parser = argparse.ArgumentParser() +parser.add_argument("--model", help="Get module model", action="store_true") +parser.add_argument("--revision", help="Get module revision", action="store_true") +parser.add_argument("--capabilities", help="Get module capabilities", action="store_true") +parser.add_argument("--imei", help="Get module IMEI/ESN/MEID", action="store_true") +parser.add_argument("--imsi", help="Get module IMSI", action="store_true") +parser.add_argument("--msisdn", help="Get module MSISDN", action="store_true") +parser.add_argument("--sim", help="Get SIM card status", action="store_true") +parser.add_argument("--signal", help="Get current RF signal info", action="store_true") +parser.add_argument("--firmware", help="Get current RF signal info", action="store_true") + +required = parser.add_argument_group('Required arguments') +required.add_argument("--interface", help="WWAN interface name, e.g. wwan0", required=True) + +def qmi_cmd(device, command, silent=False): + try: + tmp = cmd(f'qmicli --device={device} --device-open-proxy {command}') + tmp = tmp.replace(f'[{cdc}] ', '') + if not silent: + # skip first line as this only holds the info headline + for line in tmp.splitlines()[1:]: + print(line.lstrip()) + return tmp + except: + print('Command not supported by Modem') + exit(1) + +if __name__ == '__main__': + args = parser.parse_args() + + tmp = ConfigTreeQuery() + if not tmp.exists(['interfaces', 'wwan', args.interface]): + print(f'Interface "{args.interface}" unconfigured!') + exit(1) + + # remove the WWAN prefix from the interface, required for the CDC interface + if_num = args.interface.replace('wwan','') + cdc = f'/dev/cdc-wdm{if_num}' + + if args.model: + qmi_cmd(cdc, '--dms-get-model') + elif args.capabilities: + qmi_cmd(cdc, '--dms-get-capabilities') + qmi_cmd(cdc, '--dms-get-band-capabilities') + elif args.revision: + qmi_cmd(cdc, '--dms-get-revision') + elif args.imei: + qmi_cmd(cdc, '--dms-get-ids') + elif args.imsi: + qmi_cmd(cdc, '--dms-uim-get-imsi') + elif args.msisdn: + qmi_cmd(cdc, '--dms-get-msisdn') + elif args.sim: + qmi_cmd(cdc, '--uim-get-card-status') + elif args.signal: + qmi_cmd(cdc, '--nas-get-signal-info') + qmi_cmd(cdc, '--nas-get-rf-band-info') + elif args.firmware: + tmp = qmi_cmd(cdc, '--dms-get-manufacturer', silent=True) + if 'Sierra Wireless' in tmp: + qmi_cmd(cdc, '--dms-swi-get-current-firmware') + else: + qmi_cmd(cdc, '--dms-get-software-version') + else: + parser.print_help() + exit(1) diff --git a/src/op_mode/snmp.py b/src/op_mode/snmp.py new file mode 100644 index 0000000..3d6cd22 --- /dev/null +++ b/src/op_mode/snmp.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import argparse + +from vyos.config import Config +from vyos.utils.process import call + +config_file_daemon = r'/etc/snmp/snmpd.conf' + +parser = argparse.ArgumentParser(description='Retrieve infomration from running SNMP daemon') +parser.add_argument('--allowed', action="store_true", help='Show available SNMP communities') +parser.add_argument('--community', action="store", help='Show status of given SNMP community', type=str) +parser.add_argument('--host', action="store", help='SNMP host to connect to', type=str, default='localhost') + +config = { + 'communities': [], +} + +def read_config(): + with open(config_file_daemon, 'r') as f: + for line in f: + # Only get configured SNMP communitie + if line.startswith('rocommunity') or line.startswith('rwcommunity'): + string = line.split(' ') + # append community to the output list only once + c = string[1] + if c not in config['communities']: + config['communities'].append(c) + +def show_all(): + if len(config['communities']) > 0: + print(' '.join(config['communities'])) + +def show_community(c, h): + print('Status of SNMP community {0} on {1}'.format(c, h), flush=True) + call('/usr/bin/snmpstatus -t1 -v1 -c {0} {1}'.format(c, h)) + +if __name__ == '__main__': + args = parser.parse_args() + + # Do nothing if service is not configured + c = Config() + if not c.exists_effective('service snmp'): + print("SNMP service is not configured") + sys.exit(0) + + read_config() + + if args.allowed: + show_all() + sys.exit(1) + elif args.community: + show_community(args.community, args.host) + sys.exit(1) + else: + parser.print_help() + sys.exit(1) diff --git a/src/op_mode/snmp_ifmib.py b/src/op_mode/snmp_ifmib.py new file mode 100644 index 0000000..c71feba --- /dev/null +++ b/src/op_mode/snmp_ifmib.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# File: snmp_ifmib.py +# Purpose: +# Show SNMP MIB information +# Used by the "run show snmp mib" commands. + +import sys +import argparse +import netifaces + +from vyos.config import Config +from vyos.utils.process import popen + +parser = argparse.ArgumentParser(description='Retrieve SNMP interfaces information') +parser.add_argument('--ifindex', action='store', nargs='?', const='all', help='Show interface index') +parser.add_argument('--ifalias', action='store', nargs='?', const='all', help='Show interface aliase') +parser.add_argument('--ifdescr', action='store', nargs='?', const='all', help='Show interface description') + +def show_ifindex(intf): + out, err = popen(f'/bin/ip link show {intf}', decode='utf-8') + index = 'ifIndex = ' + out.split(':')[0] + return index.replace('\n', '') + +def show_ifalias(intf): + out, err = popen(f'/bin/ip link show {intf}', decode='utf-8') + alias = out.split('alias')[1].lstrip() if 'alias' in out else intf + return 'ifAlias = ' + alias.replace('\n', '') + +def show_ifdescr(i): + ven_id = '' + dev_id = '' + + try: + with open(r'/sys/class/net/' + i + '/device/vendor', 'r') as f: + ven_id = f.read().replace('\n', '') + except FileNotFoundError: + pass + + try: + with open(r'/sys/class/net/' + i + '/device/device', 'r') as f: + dev_id = f.read().replace('\n', '') + except FileNotFoundError: + pass + + if ven_id == '' and dev_id == '': + ret = 'ifDescr = {0}'.format(i) + return ret + + device = str(ven_id) + ':' + str(dev_id) + out, err = popen(f'/usr/bin/lspci -mm -d {device}', decode='utf-8') + + vendor = "" + device = "" + + # convert output to string + string = out.split('"') + if len(string) > 3: + vendor = string[3] + + if len(string) > 5: + device = string[5] + + ret = 'ifDescr = {0} {1}'.format(vendor, device) + return ret.replace('\n', '') + +if __name__ == '__main__': + args = parser.parse_args() + + # Do nothing if service is not configured + c = Config() + if not c.exists_effective('service snmp'): + print("SNMP service is not configured") + sys.exit(0) + + if args.ifindex: + if args.ifindex == 'all': + for i in netifaces.interfaces(): + print('{0}: {1}'.format(i, show_ifindex(i))) + else: + print('{0}: {1}'.format(args.ifindex, show_ifindex(args.ifindex))) + + elif args.ifalias: + if args.ifalias == 'all': + for i in netifaces.interfaces(): + print('{0}: {1}'.format(i, show_ifalias(i))) + else: + print('{0}: {1}'.format(args.ifalias, show_ifalias(args.ifalias))) + + elif args.ifdescr: + if args.ifdescr == 'all': + for i in netifaces.interfaces(): + print('{0}: {1}'.format(i, show_ifdescr(i))) + else: + print('{0}: {1}'.format(args.ifdescr, show_ifdescr(args.ifdescr))) + + else: + #eth0: ifIndex = 2 + # ifAlias = NET-MYBLL-MUCI-BACKBONE + # ifDescr = VMware VMXNET3 Ethernet Controller + #lo: ifIndex = 1 + for i in netifaces.interfaces(): + print('{0}:\t{1}'.format(i, show_ifindex(i))) + print('\t{0}'.format(show_ifalias(i))) + print('\t{0}'.format(show_ifdescr(i))) + + sys.exit(1) diff --git a/src/op_mode/snmp_v3.py b/src/op_mode/snmp_v3.py new file mode 100644 index 0000000..abeb524 --- /dev/null +++ b/src/op_mode/snmp_v3.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# File: snmp_v3.py +# Purpose: +# Show SNMP v3 information +# Used by the "run show snmp v3" commands. + +import sys +import jinja2 +import argparse + +from vyos.config import Config + +parser = argparse.ArgumentParser(description='Retrieve SNMP v3 information') +parser.add_argument('--all', action="store_true", help='Show all available information') +parser.add_argument('--group', action="store_true", help='Show the list of configured groups') +parser.add_argument('--trap', action="store_true", help='Show the list of configured targets') +parser.add_argument('--user', action="store_true", help='Show the list of configured users') +parser.add_argument('--view', action="store_true", help='Show the list of configured views') + +GROUP_OUTP_TMPL_SRC = """ +SNMPv3 Groups: + + Group View + ----- ---- + {% if group %}{% for g in group %} + {{ "%-20s" | format(g.name) }}{{ g.view }}({{ g.mode }}) + {% endfor %}{% endif %} +""" + +TRAPTGT_OUTP_TMPL_SRC = """ +SNMPv3 Trap-targets: + + Tpap-target Port Protocol Auth Priv Type EngineID User + ----------- ---- -------- ---- ---- ---- -------- ---- + {% if trap %}{% for t in trap %} + {{ "%-20s" | format(t.name) }} {{ t.port }} {{ t.proto }} {{ t.auth }} {{ t.priv }} {{ t.type }} {{ "%-32s" | format(t.engID) }} {{ t.user }} + {% endfor %}{% endif %} +""" + +USER_OUTP_TMPL_SRC = """ +SNMPv3 Users: + + User Auth Priv Mode Group + ---- ---- ---- ---- ----- + {% if user %}{% for u in user %} + {{ "%-20s" | format(u.name) }}{{ u.auth }} {{ u.priv }} {{ u.mode }} {{ u.group }} + {% endfor %}{% endif %} +""" + +VIEW_OUTP_TMPL_SRC = """ +SNMPv3 Views: + {% if view %}{% for v in view %} + View : {{ v.name }} + OIDs : .{{ v.oids | join("\n .")}} + {% endfor %}{% endif %} +""" + +if __name__ == '__main__': + args = parser.parse_args() + + # Do nothing if service is not configured + c = Config() + if not c.exists_effective('service snmp v3'): + print("SNMP v3 is not configured") + sys.exit(0) + + data = { + 'group': [], + 'trap': [], + 'user': [], + 'view': [] + } + + if c.exists_effective('service snmp v3 group'): + for g in c.list_effective_nodes('service snmp v3 group'): + group = { + 'name': g, + 'mode': '', + 'view': '' + } + group['mode'] = c.return_effective_value('service snmp v3 group {0} mode'.format(g)) + group['view'] = c.return_effective_value('service snmp v3 group {0} view'.format(g)) + + data['group'].append(group) + + if c.exists_effective('service snmp v3 user'): + for u in c.list_effective_nodes('service snmp v3 user'): + user = { + 'name' : u, + 'mode' : '', + 'auth' : '', + 'priv' : '', + 'group': '' + } + user['mode'] = c.return_effective_value('service snmp v3 user {0} mode'.format(u)) + user['auth'] = c.return_effective_value('service snmp v3 user {0} auth type'.format(u)) + user['priv'] = c.return_effective_value('service snmp v3 user {0} privacy type'.format(u)) + user['group'] = c.return_effective_value('service snmp v3 user {0} group'.format(u)) + + data['user'].append(user) + + if c.exists_effective('service snmp v3 view'): + for v in c.list_effective_nodes('service snmp v3 view'): + view = { + 'name': v, + 'oids': [] + } + view['oids'] = c.list_effective_nodes('service snmp v3 view {0} oid'.format(v)) + + data['view'].append(view) + + if c.exists_effective('service snmp v3 trap-target'): + for t in c.list_effective_nodes('service snmp v3 trap-target'): + trap = { + 'name' : t, + 'port' : '', + 'proto': '', + 'auth' : '', + 'priv' : '', + 'type' : '', + 'engID': '', + 'user' : '' + } + trap['port'] = c.return_effective_value('service snmp v3 trap-target {0} port'.format(t)) + trap['proto'] = c.return_effective_value('service snmp v3 trap-target {0} protocol'.format(t)) + trap['auth'] = c.return_effective_value('service snmp v3 trap-target {0} auth type'.format(t)) + trap['priv'] = c.return_effective_value('service snmp v3 trap-target {0} privacy type'.format(t)) + trap['type'] = c.return_effective_value('service snmp v3 trap-target {0} type'.format(t)) + trap['engID'] = c.return_effective_value('service snmp v3 trap-target {0} engineid'.format(t)) + trap['user'] = c.return_effective_value('service snmp v3 trap-target {0} user'.format(t)) + + data['trap'].append(trap) + + if args.all: + # Special case, print all templates ! + tmpl = jinja2.Template(GROUP_OUTP_TMPL_SRC) + print(tmpl.render(data)) + tmpl = jinja2.Template(TRAPTGT_OUTP_TMPL_SRC) + print(tmpl.render(data)) + tmpl = jinja2.Template(USER_OUTP_TMPL_SRC) + print(tmpl.render(data)) + tmpl = jinja2.Template(VIEW_OUTP_TMPL_SRC) + print(tmpl.render(data)) + + elif args.group: + tmpl = jinja2.Template(GROUP_OUTP_TMPL_SRC) + print(tmpl.render(data)) + + elif args.trap: + tmpl = jinja2.Template(TRAPTGT_OUTP_TMPL_SRC) + print(tmpl.render(data)) + + elif args.user: + tmpl = jinja2.Template(USER_OUTP_TMPL_SRC) + print(tmpl.render(data)) + + elif args.view: + tmpl = jinja2.Template(VIEW_OUTP_TMPL_SRC) + print(tmpl.render(data)) + + else: + parser.print_help() + + sys.exit(1) diff --git a/src/op_mode/snmp_v3_showcerts.sh b/src/op_mode/snmp_v3_showcerts.sh new file mode 100644 index 0000000..015b2e6 --- /dev/null +++ b/src/op_mode/snmp_v3_showcerts.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +files=`sudo ls /etc/snmp/tls/certs/ 2> /dev/null`; +if [ -n "$files" ]; then + sudo /usr/bin/net-snmp-cert showcerts --subject --fingerprint +else + echo "You don't have any certificates. Put it in '/etc/snmp/tls/certs/' folder." +fi diff --git a/src/op_mode/ssh.py b/src/op_mode/ssh.py new file mode 100644 index 0000000..0c51576 --- /dev/null +++ b/src/op_mode/ssh.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +# +# Copyright 2017-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import json +import sys +import glob +import vyos.opmode +from vyos.utils.process import cmd +from vyos.configquery import ConfigTreeQuery +from tabulate import tabulate + +def show_fingerprints(raw: bool, ascii: bool): + config = ConfigTreeQuery() + if not config.exists("service ssh"): + raise vyos.opmode.UnconfiguredSubsystem("SSH server is not enabled.") + + publickeys = glob.glob("/etc/ssh/*.pub") + + if publickeys: + keys = [] + for keyfile in publickeys: + try: + if ascii: + keydata = cmd("ssh-keygen -l -v -E sha256 -f " + keyfile).splitlines() + else: + keydata = cmd("ssh-keygen -l -E sha256 -f " + keyfile).splitlines() + type = keydata[0].split(None)[-1].strip("()") + key_size = keydata[0].split(None)[0] + fingerprint = keydata[0].split(None)[1] + comment = keydata[0].split(None)[2:-1][0] + if ascii: + ascii_art = "\n".join(keydata[1:]) + keys.append({"type": type, "key_size": key_size, "fingerprint": fingerprint, "comment": comment, "ascii_art": ascii_art}) + else: + keys.append({"type": type, "key_size": key_size, "fingerprint": fingerprint, "comment": comment}) + except: + # Ignore invalid public keys + pass + if raw: + return keys + else: + headers = {"type": "Type", "key_size": "Key Size", "fingerprint": "Fingerprint", "comment": "Comment", "ascii_art": "ASCII Art"} + output = "SSH server public key fingerprints:\n\n" + tabulate(keys, headers=headers, tablefmt="simple") + return output + else: + if raw: + return [] + else: + return "No SSH server public keys are found." + +def show_dynamic_protection(raw: bool): + config = ConfigTreeQuery() + if not config.exists(['service', 'ssh', 'dynamic-protection']): + raise vyos.opmode.UnconfiguredObject("SSH server dynamic-protection is not enabled.") + + attackers = [] + try: + # IPv4 + attackers = attackers + json.loads(cmd("nft -j list set ip sshguard attackers"))["nftables"][1]["set"]["elem"] + except: + pass + try: + # IPv6 + attackers = attackers + json.loads(cmd("nft -j list set ip6 sshguard attackers"))["nftables"][1]["set"]["elem"] + except: + pass + if attackers: + if raw: + return attackers + else: + output = "Blocked attackers:\n" + "\n".join(attackers) + return output + else: + if raw: + return [] + else: + return "No blocked attackers." + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/storage.py b/src/op_mode/storage.py new file mode 100644 index 0000000..8fd2ffe --- /dev/null +++ b/src/op_mode/storage.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import sys + +import vyos.opmode + +from jinja2 import Template + +output_tmpl = """ +Filesystem: {{filesystem}} +Size: {{size}} +Used: {{used}} ({{use_percentage}}%) +Available: {{avail}} ({{avail_percentage}}%) +""" + +def _get_formatted_output(): + return _get_system_storage() + +def show(raw: bool): + from vyos.utils.disk import get_persistent_storage_stats + + if raw: + res = get_persistent_storage_stats(human_units=False) + if res is None: + raise vyos.opmode.DataUnavailable("Storage statistics are not available") + else: + return res + else: + data = get_persistent_storage_stats(human_units=True) + if data is None: + return "Storage statistics are not available" + else: + data["avail_percentage"] = 100 - int(data["use_percentage"]) + tmpl = Template(output_tmpl) + return tmpl.render(data).strip() + + return output + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) + diff --git a/src/op_mode/system.py b/src/op_mode/system.py new file mode 100644 index 0000000..854b4b6 --- /dev/null +++ b/src/op_mode/system.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import jmespath +import sys + +from vyos.configquery import ConfigTreeQuery + +import vyos.opmode +import vyos.version + +config = ConfigTreeQuery() +base = ['system', 'update-check'] + + +def _compare_version_raw(): + url = config.value(base + ['url']) + local_data = vyos.version.get_full_version_data() + remote_data = vyos.version.get_remote_version(url) + if not remote_data: + return {"error": True, + "reason": "Unable to get remote version"} + if local_data.get('version') and remote_data: + local_version = local_data.get('version') + remote_version = jmespath.search('[0].version', remote_data) + image_url = jmespath.search('[0].url', remote_data) + if local_data.get('version') != remote_version: + return {"error": False, + "update_available": True, + "local_version": local_version, + "remote_version": remote_version, + "url": image_url} + return {"update_available": False, + "local_version": local_version, + "remote_version": remote_version} + + +def _formatted_compare_version(data): + local_version = data.get('local_version') + remote_version = data.get('remote_version') + url = data.get('url') + if {'update_available','local_version', 'remote_version', 'url'} <= set(data): + return f'Current version: {local_version}\n\nUpdate available: {remote_version}\nUpdate URL: {url}' + elif local_version == remote_version and remote_version is not None: + return f'No available updates for your system \n' \ + f'current version: {local_version}\nremote version: {remote_version}' + else: + return 'Update not found' + + +def _verify(): + if not config.exists(base): + return False + return True + + +def show_update(raw: bool): + if not _verify(): + raise vyos.opmode.UnconfiguredSubsystem("system update-check not configured") + data = _compare_version_raw() + if raw: + return data + else: + return _formatted_compare_version(data) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/tcpdump.py b/src/op_mode/tcpdump.py new file mode 100644 index 0000000..607b596 --- /dev/null +++ b/src/op_mode/tcpdump.py @@ -0,0 +1,165 @@ +#! /usr/bin/env python3 + +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys + +from vyos.utils.process import call + +options = { + 'dump': { + 'cmd': '{command} -A', + 'type': 'noarg', + 'help': 'Print each packet (minus its link level header) in ASCII.' + }, + 'hexdump': { + 'cmd': '{command} -X', + 'type': 'noarg', + 'help': 'Print each packet (minus its link level header) in both hex and ASCII.' + }, + 'filter': { + 'cmd': '{command} \'{value}\'', + 'type': '<pcap-filter>', + 'help': 'Match traffic for capture and display with a pcap-filter expression.' + }, + 'numeric': { + 'cmd': '{command} -nn', + 'type': 'noarg', + 'help': 'Do not attempt to resolve addresses, protocols or services to names.' + }, + 'save': { + 'cmd': '{command} -w {value}', + 'type': '<file>', + 'help': 'Write captured raw packets to <file> rather than parsing or printing them out.' + }, + 'verbose': { + 'cmd': '{command} -vvv -ne', + 'type': 'noarg', + 'help': 'Parse packets with increased detail output, including link-level headers and extended decoding protocol sanity checks.' + }, +} + +tcpdump = 'sudo /usr/bin/tcpdump' + +class List(list): + def first(self): + return self.pop(0) if self else '' + + def last(self): + return self.pop() if self else '' + + def prepend(self, value): + self.insert(0, value) + + +def completion_failure(option: str) -> None: + """ + Shows failure message after TAB when option is wrong + :param option: failure option + :type str: + """ + sys.stderr.write('\n\n Invalid option: {}\n\n'.format(option)) + sys.stdout.write('<nocomps>') + sys.exit(1) + + +def expansion_failure(option, completions): + reason = 'Ambiguous' if completions else 'Invalid' + sys.stderr.write( + '\n\n {} command: {} [{}]\n\n'.format(reason, ' '.join(sys.argv), + option)) + if completions: + sys.stderr.write(' Possible completions:\n ') + sys.stderr.write('\n '.join(completions)) + sys.stderr.write('\n') + sys.stdout.write('<nocomps>') + sys.exit(1) + + +def complete(prefix): + return [o for o in options if o.startswith(prefix)] + + +def convert(command, args): + while args: + shortname = args.first() + longnames = complete(shortname) + if len(longnames) != 1: + expansion_failure(shortname, longnames) + longname = longnames[0] + if options[longname]['type'] == 'noarg': + command = options[longname]['cmd'].format( + command=command, value='') + elif not args: + sys.exit(f'monitor traffic: missing argument for {longname} option') + else: + command = options[longname]['cmd'].format( + command=command, value=args.first()) + return command + + +if __name__ == '__main__': + args = List(sys.argv[1:]) + ifname = args.first() + + # Slightly simplified & tweaked version of the code from mtr.py - it may be + # worthwhile to combine and centralise this in a common module. + if ifname == '--get-options-nested': + args.first() # pop monitor + args.first() # pop traffic + args.first() # pop interface + args.first() # pop <ifname> + usedoptionslist = [] + while args: + option = args.first() # pop option + matched = complete(option) # get option parameters + usedoptionslist.append(option) # list of used options + # Select options + if not args: + # remove from Possible completions used options + for o in usedoptionslist: + if o in matched: + matched.remove(o) + if not matched: + sys.stdout.write('<nocomps>') + else: + sys.stdout.write(' '.join(matched)) + sys.exit(0) + + if len(matched) > 1: + sys.stdout.write(' '.join(matched)) + sys.exit(0) + # If option doesn't have value + if matched: + if options[matched[0]]['type'] == 'noarg': + continue + else: + # Unexpected option + completion_failure(option) + + value = args.first() # pop option's value + if not args: + matched = complete(option) + helplines = options[matched[0]]['type'] + # Run helpfunction to get list of possible values + if 'helpfunction' in options[matched[0]]: + result = options[matched[0]]['helpfunction']() + if result: + helplines = '\n' + ' '.join(result) + sys.stdout.write(helplines) + sys.exit(0) + + command = convert(tcpdump, args) + call(f'{command} -i {ifname}') diff --git a/src/op_mode/tech_support.py b/src/op_mode/tech_support.py new file mode 100644 index 0000000..f60bb87 --- /dev/null +++ b/src/op_mode/tech_support.py @@ -0,0 +1,394 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import json + +import vyos.opmode + +from vyos.utils.process import cmd + +def _get_version_data(): + from vyos.version import get_version_data + return get_version_data() + +def _get_uptime(): + from vyos.utils.system import get_uptime_seconds + + return get_uptime_seconds() + +def _get_load_average(): + from vyos.utils.system import get_load_averages + + return get_load_averages() + +def _get_cpus(): + from vyos.utils.cpu import get_cpus + + return get_cpus() + +def _get_process_stats(): + return cmd('top --iterations 1 --batch-mode --accum-time-toggle') + +def _get_storage(): + from vyos.utils.disk import get_persistent_storage_stats + + return get_persistent_storage_stats() + +def _get_devices(): + devices = {} + devices["pci"] = cmd("lspci") + devices["usb"] = cmd("lsusb") + + return devices + +def _get_memory(): + from vyos.utils.file import read_file + + return read_file("/proc/meminfo") + +def _get_processes(): + res = cmd("ps aux") + + return res + +def _get_interrupts(): + from vyos.utils.file import read_file + + interrupts = read_file("/proc/interrupts") + softirqs = read_file("/proc/softirqs") + + return (interrupts, softirqs) + +def _get_partitions(): + # XXX: as of parted 3.5, --json is completely broken + # and cannot be used (outputs malformed JSON syntax) + res = cmd(f"parted --list") + + return res + +def _get_running_config(): + from os import getpid + from vyos.configsession import ConfigSession + from vyos.utils.strip_config import strip_config_source + + c = ConfigSession(getpid()) + return strip_config_source(c.show_config([])) + +def _get_boot_config(): + from vyos.utils.file import read_file + from vyos.utils.strip_config import strip_config_source + + config = read_file('/opt/vyatta/etc/config.boot.default') + + return strip_config_source(config) + +def _get_config_scripts(): + from os import listdir + from os.path import join + from vyos.utils.file import read_file + + scripts = [] + + dir = '/config/scripts' + for f in listdir(dir): + script = {} + path = join(dir, f) + data = read_file(path) + script["path"] = path + script["data"] = data + + scripts.append(script) + + return scripts + +def _get_nic_data(): + from vyos.utils.process import ip_cmd + link_data = ip_cmd("link show") + addr_data = ip_cmd("address show") + + return link_data, addr_data + +def _get_routes(proto): + from json import loads + from vyos.utils.process import ip_cmd + + # Only include complete routing tables if they are not too large + # At the moment "too large" is arbitrarily set to 1000 + MAX_ROUTES = 1000 + + data = {} + + summary = cmd(f"vtysh -c 'show {proto} route summary json'") + summary = loads(summary) + + data["summary"] = summary + + if summary["routesTotal"] < MAX_ROUTES: + rib_routes = cmd(f"vtysh -c 'show {proto} route json'") + data["routes"] = loads(rib_routes) + + if summary["routesTotalFib"] < MAX_ROUTES: + ip_proto = "-4" if proto == "ip" else "-6" + fib_routes = ip_cmd(f"{ip_proto} route show") + data["fib_routes"] = fib_routes + + return data + +def _get_ip_routes(): + return _get_routes("ip") + +def _get_ipv6_routes(): + return _get_routes("ipv6") + +def _get_ospfv2(): + # XXX: OSPF output when it's not configured is an empty string, + # which is not a valid JSON + output = cmd("vtysh -c 'show ip ospf json'") + if output: + return json.loads(output) + else: + return {} + +def _get_ospfv3(): + output = cmd("vtysh -c 'show ipv6 ospf6 json'") + if output: + return json.loads(output) + else: + return {} + +def _get_bgp_summary(): + output = cmd("vtysh -c 'show bgp summary json'") + return json.loads(output) + +def _get_isis(): + output = cmd("vtysh -c 'show isis summary json'") + if output: + return json.loads(output) + else: + return {} + +def _get_arp_table(): + from json import loads + from vyos.utils.process import cmd + + arp_table = cmd("ip --json -4 neighbor show") + return loads(arp_table) + +def _get_ndp_table(): + from json import loads + + arp_table = cmd("ip --json -6 neighbor show") + return loads(arp_table) + +def _get_nftables_rules(): + nft_rules = cmd("nft list ruleset") + return nft_rules + +def _get_connections(): + from vyos.utils.process import cmd + + return cmd("ss -apO") + +def _get_system_packages(): + from re import split + from vyos.utils.process import cmd + + dpkg_out = cmd(''' dpkg-query -W -f='${Package} ${Version} ${Architecture} ${db:Status-Abbrev}\n' ''') + pkg_lines = split(r'\n+', dpkg_out) + + # Discard the header, it's five lines long + pkg_lines = pkg_lines[5:] + + pkgs = [] + + for pl in pkg_lines: + parts = split(r'\s+', pl) + pkg = {} + pkg["name"] = parts[0] + pkg["version"] = parts[1] + pkg["architecture"] = parts[2] + pkg["status"] = parts[3] + + pkgs.append(pkg) + + return pkgs + +def _get_image_info(): + from vyos.system.image import get_images_details + + return get_images_details() + +def _get_kernel_modules(): + from vyos.utils.kernel import lsmod + + return lsmod() + +def _get_last_logs(max): + from systemd import journal + + r = journal.Reader() + + # Set the reader to use logs from the current boot + r.this_boot() + + # Jump to the last logs + r.seek_tail() + + # Only get logs of INFO level or more urgent + r.log_level(journal.LOG_INFO) + + # Retrieve the entries + entries = [] + + # I couldn't find a way to just get last/first N entries, + # so we'll use the cursor directly. + num = max + while num >= 0: + je = r.get_previous() + entry = {} + + # Extract the most useful and serializable fields + entry["timestamp"] = je.get("SYSLOG_TIMESTAMP") + entry["pid"] = je.get("SYSLOG_PID") + entry["identifier"] = je.get("SYSLOG_IDENTIFIER") + entry["facility"] = je.get("SYSLOG_FACILITY") + entry["systemd_unit"] = je.get("_SYSTEMD_UNIT") + entry["message"] = je.get("MESSAGE") + + entries.append(entry) + + num = num - 1 + + return entries + + +def _get_raw_data(): + data = {} + + # VyOS-specific information + data["vyos"] = {} + + ## The equivalent of "show version" + from vyos.version import get_version_data + data["vyos"]["version"] = _get_version_data() + + ## Installed images + data["vyos"]["images"] = _get_image_info() + + # System information + data["system"] = {} + + ## Uptime and load averages + data["system"]["uptime"] = _get_uptime() + data["system"]["load_average"] = _get_load_average() + data["system"]["process_stats"] = _get_process_stats() + + ## Debian packages + data["system"]["packages"] = _get_system_packages() + + ## Kernel modules + data["system"]["kernel"] = {} + data["system"]["kernel"]["modules"] = _get_kernel_modules() + + ## Processes + data["system"]["processes"] = _get_processes() + + ## Interrupts + interrupts, softirqs = _get_interrupts() + data["system"]["interrupts"] = interrupts + data["system"]["softirqs"] = softirqs + + # Hardware + data["hardware"] = {} + data["hardware"]["cpu"] = _get_cpus() + data["hardware"]["storage"] = _get_storage() + data["hardware"]["partitions"] = _get_partitions() + data["hardware"]["devices"] = _get_devices() + data["hardware"]["memory"] = _get_memory() + + # Configuration data + data["vyos"]["config"] = {} + + ## Running config text + ## We do not encode it so that it's possible to + ## see exactly what the user sees and detect any syntax/rendering anomalies — + ## exporting the config to JSON could obscure them + data["vyos"]["config"]["running"] = _get_running_config() + + ## Default boot config, exactly as in /config/config.boot + ## It may be different from the running config + ## _and_ may have its own syntax quirks that may point at bugs + data["vyos"]["config"]["boot"] = _get_boot_config() + + ## Config scripts + data["vyos"]["config"]["scripts"] = _get_config_scripts() + + # Network interfaces + data["network_interfaces"] = {} + + # Interface data from iproute2 + link_data, addr_data = _get_nic_data() + data["network_interfaces"]["links"] = link_data + data["network_interfaces"]["addresses"] = addr_data + + # Routing table data + data["routing"] = {} + data["routing"]["ip"] = _get_ip_routes() + data["routing"]["ipv6"] = _get_ipv6_routes() + + # Routing protocols + data["routing"]["ip"]["ospf"] = _get_ospfv2() + data["routing"]["ipv6"]["ospfv3"] = _get_ospfv3() + + data["routing"]["bgp"] = {} + data["routing"]["bgp"]["summary"] = _get_bgp_summary() + + data["routing"]["isis"] = _get_isis() + + # ARP and NDP neighbor tables + data["neighbor_tables"] = {} + data["neighbor_tables"]["arp"] = _get_arp_table() + data["neighbor_tables"]["ndp"] = _get_ndp_table() + + # nftables config + data["nftables_rules"] = _get_nftables_rules() + + # All connections + data["connections"] = _get_connections() + + # Logs + data["last_logs"] = _get_last_logs(1000) + + return data + +def show(raw: bool): + data = _get_raw_data() + if raw: + return data + else: + raise vyos.opmode.UnsupportedOperation("Formatted output is not implemented yet") + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) + except (KeyboardInterrupt, BrokenPipeError): + sys.exit(1) diff --git a/src/op_mode/toggle_help_binding.sh b/src/op_mode/toggle_help_binding.sh new file mode 100644 index 0000000..a8708f3 --- /dev/null +++ b/src/op_mode/toggle_help_binding.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# +# Copyright (C) 2019 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Script for [un-]binding the question mark key for getting help +if [ "$1" == 'disable' ]; then + sed -i "/^bind '\"?\": .* # vyatta key binding$/d" $HOME/.bashrc + echo "bind '\"?\": self-insert' # vyatta key binding" >> $HOME/.bashrc + bind '"?": self-insert' +else + sed -i "/^bind '\"?\": .* # vyatta key binding$/d" $HOME/.bashrc + bind '"?": possible-completions' +fi diff --git a/src/op_mode/traceroute.py b/src/op_mode/traceroute.py new file mode 100644 index 0000000..d2bac3f --- /dev/null +++ b/src/op_mode/traceroute.py @@ -0,0 +1,238 @@ +#! /usr/bin/env python3 + +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import socket +import ipaddress + +from vyos.utils.network import interface_list +from vyos.utils.network import vrf_list +from vyos.utils.process import call + +options = { + 'backward-hops': { + 'traceroute': '{command} --back', + 'type': 'noarg', + 'help': 'Display number of backward hops when they different from the forwarded path' + }, + 'bypass': { + 'traceroute': '{command} -r', + 'type': 'noarg', + 'help': 'Bypass the normal routing tables and send directly to a host on an attached network' + }, + 'do-not-fragment': { + 'traceroute': '{command} -F', + 'type': 'noarg', + 'help': 'Do not fragment probe packets.' + }, + 'first-ttl': { + 'traceroute': '{command} -f {value}', + 'type': '<ttl>', + 'help': 'Specifies with what TTL to start. Defaults to 1.' + }, + 'icmp': { + 'traceroute': '{command} -I', + 'type': 'noarg', + 'help': 'Use ICMP ECHO for tracerouting' + }, + 'interface': { + 'traceroute': '{command} -i {value}', + 'type': '<interface>', + 'helpfunction': interface_list, + 'help': 'Source interface' + }, + 'lookup-as': { + 'traceroute': '{command} -A', + 'type': 'noarg', + 'help': 'Perform AS path lookups' + }, + 'mark': { + 'traceroute': '{command} --fwmark={value}', + 'type': '<fwmark>', + 'help': 'Set the firewall mark for outgoing packets' + }, + 'no-resolve': { + 'traceroute': '{command} -n', + 'type': 'noarg', + 'help': 'Do not resolve hostnames' + }, + 'port': { + 'traceroute': '{command} -p {value}', + 'type': '<port>', + 'help': 'Destination port' + }, + 'source-address': { + 'traceroute': '{command} -s {value}', + 'type': '<x.x.x.x> <h:h:h:h:h:h:h:h>', + 'help': 'Specify source IP v4/v6 address' + }, + 'tcp': { + 'traceroute': '{command} -T', + 'type': 'noarg', + 'help': 'Use TCP SYN for tracerouting (default port is 80)' + }, + 'tos': { + 'traceroute': '{commad} -t {value}', + 'type': '<tos>', + 'help': 'Mark packets with specified TOS' + }, + 'ttl': { + 'traceroute': '{command} -m {value}', + 'type': '<ttl>', + 'help': 'Maximum number of hops' + }, + 'udp': { + 'traceroute': '{command} -U', + 'type': 'noarg', + 'help': 'Use UDP to particular port for tracerouting (default port is 53)' + }, + 'vrf': { + 'traceroute': 'sudo ip vrf exec {value} {command}', + 'type': '<vrf>', + 'help': 'Use specified VRF table', + 'helpfunction': vrf_list, + 'dflt': 'default'} +} + +traceroute = { + 4: '/bin/traceroute -4', + 6: '/bin/traceroute -6', +} + + +class List(list): + def first(self): + return self.pop(0) if self else '' + + def last(self): + return self.pop() if self else '' + + def prepend(self, value): + self.insert(0, value) + + +def completion_failure(option: str) -> None: + """ + Shows failure message after TAB when option is wrong + :param option: failure option + :type str: + """ + sys.stderr.write('\n\n Invalid option: {}\n\n'.format(option)) + sys.stdout.write('<nocomps>') + sys.exit(1) + + +def expension_failure(option, completions): + reason = 'Ambiguous' if completions else 'Invalid' + sys.stderr.write( + '\n\n {} command: {} [{}]\n\n'.format(reason, ' '.join(sys.argv), + option)) + if completions: + sys.stderr.write(' Possible completions:\n ') + sys.stderr.write('\n '.join(completions)) + sys.stderr.write('\n') + sys.stdout.write('<nocomps>') + sys.exit(1) + + +def complete(prefix): + return [o for o in options if o.startswith(prefix)] + + +def convert(command, args): + while args: + shortname = args.first() + longnames = complete(shortname) + if len(longnames) != 1: + expension_failure(shortname, longnames) + longname = longnames[0] + if options[longname]['type'] == 'noarg': + command = options[longname]['traceroute'].format( + command=command, value='') + elif not args: + sys.exit(f'traceroute: missing argument for {longname} option') + else: + command = options[longname]['traceroute'].format( + command=command, value=args.first()) + return command + + +if __name__ == '__main__': + args = List(sys.argv[1:]) + host = args.first() + + if not host: + sys.exit("traceroute: Missing host") + + if host == '--get-options': + args.first() # pop ping + args.first() # pop IP + usedoptionslist = [] + while args: + option = args.first() # pop option + matched = complete(option) # get option parameters + usedoptionslist.append(option) # list of used options + # Select options + if not args: + # remove from Possible completions used options + for o in usedoptionslist: + if o in matched: + matched.remove(o) + sys.stdout.write(' '.join(matched)) + sys.exit(0) + + if len(matched) > 1: + sys.stdout.write(' '.join(matched)) + sys.exit(0) + # If option doesn't have value + if matched: + if options[matched[0]]['type'] == 'noarg': + continue + else: + # Unexpected option + completion_failure(option) + + value = args.first() # pop option's value + if not args: + matched = complete(option) + helplines = options[matched[0]]['type'] + # Run helpfunction to get list of possible values + if 'helpfunction' in options[matched[0]]: + result = options[matched[0]]['helpfunction']() + if result: + helplines = '\n' + ' '.join(result) + sys.stdout.write(helplines) + sys.exit(0) + + for name, option in options.items(): + if 'dflt' in option and name not in args: + args.append(name) + args.append(option['dflt']) + + try: + ip = socket.gethostbyname(host) + except UnicodeError: + sys.exit(f'tracroute: Unknown host: {host}') + except socket.gaierror: + ip = host + + try: + version = ipaddress.ip_address(ip).version + except ValueError: + sys.exit(f'traceroute: Unknown host: {host}') + + command = convert(traceroute[version], args) + call(f'{command} {host}') diff --git a/src/op_mode/uptime.py b/src/op_mode/uptime.py new file mode 100644 index 0000000..1c1a149 --- /dev/null +++ b/src/op_mode/uptime.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys + +import vyos.opmode + +def _get_raw_data(): + from vyos.utils.system import get_uptime_seconds, get_load_averages + from vyos.utils.convert import seconds_to_human + + res = {} + uptime_seconds = get_uptime_seconds() + res["uptime"] = seconds_to_human(uptime_seconds, separator=' ') + res["load_average"] = get_load_averages() + + return res + +def _get_formatted_output(data): + out = "Uptime: {}\n\n".format(data["uptime"]) + avgs = data["load_average"] + out += "Load averages:\n" + out += "1 minute: {:.01f}%\n".format(avgs[1]*100) + out += "5 minutes: {:.01f}%\n".format(avgs[5]*100) + out += "15 minutes: {:.01f}%\n".format(avgs[15]*100) + + return out + +def show(raw: bool): + uptime_data = _get_raw_data() + + if raw: + return uptime_data + else: + return _get_formatted_output(uptime_data) + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/version.py b/src/op_mode/version.py new file mode 100644 index 0000000..71a40dd --- /dev/null +++ b/src/op_mode/version.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2016-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Purpose: +# Displays image version and system information. +# Used by the "run show version" command. + +import sys +import typing + +import vyos.opmode +import vyos.version +import vyos.limericks + +from vyos.utils.boot import is_uefi_system +from vyos.utils.system import get_secure_boot_state + +from jinja2 import Template + +version_output_tmpl = """ +Version: VyOS {{version}} +Release train: {{release_train}} +Release flavor: {{flavor}} + +Built by: {{built_by}} +Built on: {{built_on}} +Build UUID: {{build_uuid}} +Build commit ID: {{build_git}} +{%- if build_comment %} +Build comment: {{build_comment}} +{% endif %} + +Architecture: {{system_arch}} +Boot via: {{boot_via}} +System type: {{system_type}} +Secure Boot: {{secure_boot}} + +Hardware vendor: {{hardware_vendor}} +Hardware model: {{hardware_model}} +Hardware S/N: {{hardware_serial}} +Hardware UUID: {{hardware_uuid}} + +Copyright: VyOS maintainers and contributors +{%- if limerick %} +{{limerick}} +{% endif -%} +""" + +def _get_raw_data(funny=False): + version_data = vyos.version.get_full_version_data() + version_data["secure_boot"] = "n/a (BIOS)" + if is_uefi_system(): + version_data["secure_boot"] = "disabled" + if get_secure_boot_state(): + version_data["secure_boot"] = "enabled" + + if funny: + version_data["limerick"] = vyos.limericks.get_random() + + return version_data + +def _get_formatted_output(version_data): + tmpl = Template(version_output_tmpl) + return tmpl.render(version_data).strip() + +def show(raw: bool, funny: typing.Optional[bool]): + """ Display neighbor table contents """ + version_data = _get_raw_data(funny=funny) + + if raw: + return version_data + else: + return _get_formatted_output(version_data) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) + diff --git a/src/op_mode/vpn_ike_sa.py b/src/op_mode/vpn_ike_sa.py new file mode 100644 index 0000000..9385bcd --- /dev/null +++ b/src/op_mode/vpn_ike_sa.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import sys +import vici + +from vyos.utils.process import process_named_running + +ike_sa_peer_prefix = """\ +Peer ID / IP Local ID / IP +------------ -------------""" + +ike_sa_tunnel_prefix = """ + + State IKEVer Encrypt Hash D-H Group NAT-T A-Time L-Time + ----- ------ ------- ---- --------- ----- ------ ------""" + +def s(byte_string): + return str(byte_string, 'utf-8') + +def ike_sa(peer, nat): + session = vici.Session() + sas = session.list_sas() + peers = [] + for conn in sas: + for name, sa in conn.items(): + if peer and s(sa['remote-host']) != peer: + continue + if name.startswith('peer_') and name in peers: + continue + if nat and 'nat-local' not in sa: + continue + peers.append(name) + remote_str = f'{s(sa["remote-host"])} {s(sa["remote-id"])}' if s(sa['remote-id']) != '%any' else s(sa["remote-host"]) + local_str = f'{s(sa["local-host"])} {s(sa["local-id"])}' if s(sa['local-id']) != '%any' else s(sa["local-host"]) + print(ike_sa_peer_prefix) + print('%-39s %-39s' % (remote_str, local_str)) + state = 'up' if 'state' in sa and s(sa['state']) == 'ESTABLISHED' else 'down' + version = 'IKEv' + s(sa['version']) + encryption = f'{s(sa["encr-alg"])}' if 'encr-alg' in sa else 'n/a' + if 'encr-keysize' in sa: + encryption += '_' + s(sa["encr-keysize"]) + integrity = s(sa['integ-alg']) if 'integ-alg' in sa else 'n/a' + dh_group = s(sa['dh-group']) if 'dh-group' in sa else 'n/a' + natt = 'yes' if 'nat-local' in sa and s(sa['nat-local']) == 'yes' else 'no' + atime = s(sa['established']) if 'established' in sa else '0' + ltime = s(sa['rekey-time']) if 'rekey-time' in sa else '0' + print(ike_sa_tunnel_prefix) + print(' %-6s %-6s %-12s %-13s %-14s %-6s %-7s %-7s\n' % (state, version, encryption, integrity, dh_group, natt, atime, ltime)) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--peer', help='Peer name', required=False) + parser.add_argument('--nat', help='NAT Traversal', required=False) + + args = parser.parse_args() + + if not process_named_running('charon-systemd'): + print("IPsec Process NOT Running") + sys.exit(0) + + ike_sa(args.peer, args.nat) diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py new file mode 100644 index 0000000..ef89e60 --- /dev/null +++ b/src/op_mode/vpn_ipsec.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import argparse + +from vyos.utils.process import call + +SWANCTL_CONF = '/etc/swanctl/swanctl.conf' + + +def get_peer_connections(peer, tunnel, return_all = False): + search = rf'^[\s]*(peer_{peer}_(tunnel_[\d]+|vti)).*' + matches = [] + with open(SWANCTL_CONF, 'r') as f: + for line in f.readlines(): + result = re.match(search, line) + if result: + suffix = f'tunnel_{tunnel}' if tunnel.isnumeric() else tunnel + if return_all or (result[2] == suffix): + matches.append(result[1]) + return matches + + +def debug_peer(peer, tunnel): + peer = peer.replace(':', '-') + if not peer or peer == "all": + debug_commands = [ + "ipsec statusall", + "swanctl -L", + "swanctl -l", + "swanctl -P", + "ip x sa show", + "ip x policy show", + "ip tunnel show", + "ip address", + "ip rule show", + "ip route | head -100", + "ip route show table 220" + ] + for debug_cmd in debug_commands: + print(f'\n### {debug_cmd} ###') + call(debug_cmd) + return + + if not tunnel or tunnel == 'all': + tunnel = '' + + conns = get_peer_connections(peer, tunnel, return_all = (tunnel == '' or tunnel == 'all')) + + if not conns: + print('Peer not found, aborting') + return + + for conn in conns: + call(f'/usr/sbin/ipsec statusall | grep {conn}') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--action', help='Control action', required=True) + parser.add_argument('--name', help='Name for peer reset', required=False) + parser.add_argument('--tunnel', help='Specific tunnel of peer', required=False) + + args = parser.parse_args() + + + if args.action == "vpn-debug": + debug_peer(args.name, args.tunnel) diff --git a/src/op_mode/vrf.py b/src/op_mode/vrf.py new file mode 100644 index 0000000..51032a4 --- /dev/null +++ b/src/op_mode/vrf.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json +import jmespath +import sys +import typing + +from tabulate import tabulate +from vyos.utils.network import get_vrf_members +from vyos.utils.process import cmd + +import vyos.opmode + +def _get_raw_data(name=None): + """ + If vrf name is not set - get all VRFs + If vrf name is set - get only this name data + If vrf name set and not found - return [] + """ + output = cmd('ip --json --brief link show type vrf') + data = json.loads(output) + if not data: + return [] + if name: + is_vrf_exists = True if [vrf for vrf in data if vrf.get('ifname') == name] else False + if is_vrf_exists: + output = cmd(f'ip --json --brief link show dev {name}') + data = json.loads(output) + return data + return [] + return data + + +def _get_formatted_output(raw_data): + data_entries = [] + for vrf in raw_data: + name = vrf.get('ifname') + state = vrf.get('operstate').lower() + hw_address = vrf.get('address') + flags = ','.join(vrf.get('flags')).lower() + tmp = get_vrf_members(name) + if tmp: members = ','.join(get_vrf_members(name)) + else: members = 'n/a' + data_entries.append([name, state, hw_address, flags, members]) + + headers = ["Name", "State", "MAC address", "Flags", "Interfaces"] + output = tabulate(data_entries, headers, numalign="left") + return output + + +def show(raw: bool, name: typing.Optional[str]): + vrf_data = _get_raw_data(name=name) + if not jmespath.search('[*].ifname', vrf_data): + return "VRF is not configured" + if raw: + return vrf_data + else: + return _get_formatted_output(vrf_data) + + +if __name__ == "__main__": + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/vrrp.py b/src/op_mode/vrrp.py new file mode 100644 index 0000000..60be860 --- /dev/null +++ b/src/op_mode/vrrp.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import argparse + +from vyos.configquery import ConfigTreeQuery +from vyos.ifconfig.vrrp import VRRP +from vyos.ifconfig.vrrp import VRRPNoData + +parser = argparse.ArgumentParser() +group = parser.add_mutually_exclusive_group() +group.add_argument("-s", "--summary", action="store_true", help="Print VRRP summary") +group.add_argument("-t", "--statistics", action="store_true", help="Print VRRP statistics") +group.add_argument("-d", "--data", action="store_true", help="Print detailed VRRP data") + +args = parser.parse_args() + +def is_configured(): + """ Check if VRRP is configured """ + config = ConfigTreeQuery() + if not config.exists(['high-availability', 'vrrp', 'group']): + return False + return True + +# Exit early if VRRP is dead or not configured +if is_configured() == False: + print('VRRP not configured!') + exit(0) +if not VRRP.is_running(): + print('VRRP is not running') + sys.exit(0) + +try: + if args.summary: + print(VRRP.format(VRRP.collect('json'))) + elif args.statistics: + print(VRRP.collect('stats')) + elif args.data: + print(VRRP.collect('state')) + else: + parser.print_help() + sys.exit(1) +except VRRPNoData as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/vtysh_wrapper.sh b/src/op_mode/vtysh_wrapper.sh new file mode 100644 index 0000000..25d09ce --- /dev/null +++ b/src/op_mode/vtysh_wrapper.sh @@ -0,0 +1,6 @@ +#!/bin/sh +declare -a tmp +# FRR uses ospf6 where we use ospfv3, and we use reset over clear for BGP, +# thus alter the commands +tmp=$(echo $@ | sed -e "s/ospfv3/ospf6/" | sed -e "s/^reset bgp/clear bgp/" | sed -e "s/^reset ip bgp/clear ip bgp/") +vtysh -c "$tmp" diff --git a/src/op_mode/vyos-op-cmd-wrapper.sh b/src/op_mode/vyos-op-cmd-wrapper.sh new file mode 100644 index 0000000..a89211b --- /dev/null +++ b/src/op_mode/vyos-op-cmd-wrapper.sh @@ -0,0 +1,6 @@ +#!/bin/vbash +shopt -s expand_aliases +source /etc/default/vyatta +source /etc/bash_completion.d/vyatta-op +_vyatta_op_init +_vyatta_op_run "$@" diff --git a/src/op_mode/webproxy_update_blacklist.sh b/src/op_mode/webproxy_update_blacklist.sh new file mode 100644 index 0000000..05ea86f --- /dev/null +++ b/src/op_mode/webproxy_update_blacklist.sh @@ -0,0 +1,138 @@ +#!/bin/sh +# +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +blacklist_url='ftp://ftp.univ-tlse1.fr/pub/reseau/cache/squidguard_contrib/blacklists.tar.gz' +data_dir="/opt/vyatta/etc/config/url-filtering" +archive="${data_dir}/squidguard/archive" +db_dir="${data_dir}/squidguard/db" +conf_file="/etc/squidguard/squidGuard.conf" +tmp_conf_file="/tmp/sg_update_db.conf" + +#$1-category +#$2-type +#$3-list +create_sg_db () +{ + FILE=$db_dir/$1/$2 + if test -f "$FILE"; then + rm -f ${tmp_conf_file} + printf "dbhome $db_dir\ndest $1 {\n $3 $1/$2\n}\nacl {\n default {\n pass any\n }\n}" >> ${tmp_conf_file} + /usr/bin/squidGuard -b -c ${tmp_conf_file} -C $FILE + rm -f ${tmp_conf_file} + fi + +} + +while [ $# -gt 0 ] +do + case $1 in + --update-blacklist) + update="yes" + ;; + --auto-update-blacklist) + auto="yes" + ;; + --vrf) + vrf="yes" + ;; + (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;; + (*) break;; + esac + shift +done + +if [ ! -d ${db_dir} ]; then + mkdir -p ${db_dir} + getent passwd proxy 2> /dev/null + if [ $? -ne 0 ]; then + echo "proxy system user does not exist" + exit 1 + fi + getent group proxy 2> /dev/null + if [ $? -ne 0 ]; then + echo "proxy system group does not exist" + exit 1 + fi + chown proxy:proxy ${db_dir} +fi + +free_space=$(expr $(df ${db_dir} | grep -v Filesystem | awk '{print $4}') \* 1024) +mb_size="100" +required_space=$(expr $mb_size \* 1024 \* 1024) # 100 MB +if [ ${free_space} -le ${required_space} ]; then + echo "Error: not enough disk space, required ${mb_size} MiB" + exit 1 +fi + +if [[ -n $update ]] && [[ $update -eq "yes" ]]; then + tmp_blacklists='/tmp/blacklists.gz' + if [[ -n $vrf ]] && [[ $vrf -eq "yes" ]]; then + sudo ip vrf exec $1 curl -o $tmp_blacklists $blacklist_url + else + curl -o $tmp_blacklists $blacklist_url + fi + if [ $? -ne 0 ]; then + echo "Unable to download [$blacklist_url]!" + exit 1 + fi + echo "Uncompressing blacklist..." + tar --directory /tmp -xf $tmp_blacklists + if [ $? -ne 0 ]; then + echo "Unable to uncompress [$blacklist_url]!" + fi + + if [ ! -d ${archive} ]; then + mkdir -p ${archive} + fi + + rm -rf ${archive}/* + count_before=$(find ${db_dir} -type f \( -name domains -o -name urls \) | xargs wc -l | tail -n 1 | awk '{print $1}') + mv ${db_dir}/* ${archive} 2> /dev/null + mv /tmp/blacklists/* ${db_dir} + if [ $? -ne 0 ]; then + echo "Unable to install [$blacklist_url]" + exit 1 + fi + mv ${archive}/local-* ${db_dir} 2> /dev/null + rm -rf /tmp/blacklists $tmp_blacklists 2> /dev/null + count_after=$(find ${db_dir} -type f \( -name domains -o -name urls \) | xargs wc -l | tail -n 1 | awk '{print $1}') + + # fix permissions + chown -R proxy:proxy ${db_dir} + + #create db + category_list=(`find $db_dir -type d -exec basename {} \; `) + for category in ${category_list[@]} + do + create_sg_db $category "domains" "domainlist" + create_sg_db $category "urls" "urllist" + create_sg_db $category "expressions" "expressionlist" + done + chown -R proxy:proxy ${db_dir} + chmod 755 ${db_dir} + + logger --priority WARNING "webproxy blacklist entries updated (${count_before}/${count_after})" + +else + echo "SquidGuard blacklist updater" + echo "" + echo "Usage:" + echo "--update-blacklist Download latest version of the SquidGuard blacklist" + echo "--auto-update-blacklist Automatically update" + echo "" + exit 1 +fi + diff --git a/src/op_mode/wireguard_client.py b/src/op_mode/wireguard_client.py new file mode 100644 index 0000000..04d8ce2 --- /dev/null +++ b/src/op_mode/wireguard_client.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import os + +from jinja2 import Template +from ipaddress import ip_interface + +from vyos.ifconfig import Section +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 +from vyos.utils.process import cmd +from vyos.utils.process import popen + +if os.geteuid() != 0: + exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.") + +server_config = """WireGuard client configuration for interface: {{ interface }} + +To enable this configuration on a VyOS router you can use the following commands: + +=== VyOS (server) configurtation === + +{% for addr in address if address is defined %} +set interfaces wireguard {{ interface }} peer {{ name }} allowed-ips '{{ addr }}' +{% endfor %} +set interfaces wireguard {{ interface }} peer {{ name }} public-key '{{ pubkey }}' + +=== RoadWarrior (client) configuration === +""" + +client_config = """ + +[Interface] +PrivateKey = {{ privkey }} +{% if address is defined and address|length > 0 %} +Address = {{ address | join(', ')}} +{% endif %} +DNS = 1.1.1.1 + +[Peer] +PublicKey = {{ system_pubkey }} +Endpoint = {{ server }}:{{ port }} +AllowedIPs = 0.0.0.0/0, ::/0 + +""" + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("-n", "--name", type=str, help='WireGuard peer name', required=True) + parser.add_argument("-i", "--interface", type=str, help='WireGuard interface the client is connecting to', required=True) + parser.add_argument("-s", "--server", type=str, help='WireGuard server IPv4/IPv6 address or FQDN', required=True) + parser.add_argument("-a", "--address", type=str, help='WireGuard client IPv4/IPv6 address', action='append') + args = parser.parse_args() + + interface = args.interface + if interface not in Section.interfaces('wireguard'): + exit(f'WireGuard interface "{interface}" does not exist!') + + wg_pubkey = cmd(f'wg show {interface} | grep "public key"').split(':')[-1].lstrip() + wg_port = cmd(f'wg show {interface} | grep "listening port"').split(':')[-1].lstrip() + + # Generate WireGuard private key + privkey,_ = popen('wg genkey') + # Generate public key portion from given private key + pubkey,_ = popen('wg pubkey', input=privkey) + + config = { + 'name' : args.name, + 'interface' : interface, + 'system_pubkey' : wg_pubkey, + 'privkey': privkey, + 'pubkey' : pubkey, + 'server' : args.server, + 'port' : wg_port, + 'address' : [], + } + + if args.address: + v4_addr = 0 + v6_addr = 0 + for tmp in args.address: + try: + ip = str(ip_interface(tmp).ip) + if is_ipv4(tmp): + config['address'].append(f'{ip}/32') + v4_addr += 1 + elif is_ipv6(tmp): + config['address'].append(f'{ip}/128') + v6_addr += 1 + except: + print(tmp) + exit('Client IP address invalid!') + + if (v4_addr > 1) or (v6_addr > 1): + exit('Client can only have one IPv4 and one IPv6 address.') + + # Clear out terminal first + print('\x1b[2J\x1b[H') + server = Template(server_config, trim_blocks=True).render(config) + print(server) + client = Template(client_config, trim_blocks=True).render(config) + print(client) + qrcode,err = popen('qrencode -t ansiutf8', input=client) + print(qrcode) diff --git a/src/op_mode/zone.py b/src/op_mode/zone.py new file mode 100644 index 0000000..49fecdf --- /dev/null +++ b/src/op_mode/zone.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import typing +import sys +import vyos.opmode + +import tabulate +from vyos.configquery import ConfigTreeQuery +from vyos.utils.dict import dict_search_args +from vyos.utils.dict import dict_search + + +def get_config_zone(conf, name=None): + config_path = ['firewall', 'zone'] + if name: + config_path += [name] + + zone_policy = conf.get_config_dict(config_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + return zone_policy + + +def _convert_one_zone_data(zone: str, zone_config: dict) -> dict: + """ + Convert config dictionary of one zone to API dictionary + :param zone: Zone name + :type zone: str + :param zone_config: config dictionary + :type zone_config: dict + :return: AP dictionary + :rtype: dict + """ + list_of_rules = [] + intrazone_dict = {} + if dict_search('from', zone_config): + for from_zone, from_zone_config in zone_config['from'].items(): + from_zone_dict = {'name': from_zone} + if dict_search('firewall.name', from_zone_config): + from_zone_dict['firewall'] = dict_search('firewall.name', + from_zone_config) + if dict_search('firewall.ipv6_name', from_zone_config): + from_zone_dict['firewall_v6'] = dict_search( + 'firewall.ipv6_name', from_zone_config) + list_of_rules.append(from_zone_dict) + + zone_dict = { + 'name': zone, + 'interface': dict_search('interface', zone_config), + 'type': 'LOCAL' if dict_search('local_zone', + zone_config) is not None else None, + } + if list_of_rules: + zone_dict['from'] = list_of_rules + if dict_search('intra_zone_filtering.firewall.name', zone_config): + intrazone_dict['firewall'] = dict_search( + 'intra_zone_filtering.firewall.name', zone_config) + if dict_search('intra_zone_filtering.firewall.ipv6_name', zone_config): + intrazone_dict['firewall_v6'] = dict_search( + 'intra_zone_filtering.firewall.ipv6_name', zone_config) + if intrazone_dict: + zone_dict['intrazone'] = intrazone_dict + return zone_dict + + +def _convert_zones_data(zone_policies: dict) -> list: + """ + Convert all config dictionary to API list of zone dictionaries + :param zone_policies: config dictionary + :type zone_policies: dict + :return: API list + :rtype: list + """ + zone_list = [] + for zone, zone_config in zone_policies.items(): + zone_list.append(_convert_one_zone_data(zone, zone_config)) + return zone_list + + +def _convert_config(zones_config: dict, zone: str = None) -> list: + """ + convert config to API list + :param zones_config: zones config + :type zones_config: + :param zone: zone name + :type zone: str + :return: API list + :rtype: list + """ + if zone: + if zones_config: + output = [_convert_one_zone_data(zone, zones_config)] + else: + raise vyos.opmode.UnconfiguredObject(f'Zone {zone} not found') + else: + if zones_config: + output = _convert_zones_data(zones_config) + else: + raise vyos.opmode.UnconfiguredSubsystem( + 'Zone entries are not configured') + return output + + +def output_zone_list(zone_conf: dict) -> list: + """ + Format one zone row + :param zone_conf: zone config + :type zone_conf: dict + :return: formatted list of zones + :rtype: list + """ + zone_info = [zone_conf['name']] + if zone_conf['type'] == 'LOCAL': + zone_info.append('LOCAL') + else: + zone_info.append("\n".join(zone_conf['interface'])) + + from_zone = [] + firewall = [] + firewall_v6 = [] + if 'intrazone' in zone_conf: + from_zone.append(zone_conf['name']) + + v4_name = dict_search_args(zone_conf['intrazone'], 'firewall') + v6_name = dict_search_args(zone_conf['intrazone'], 'firewall_v6') + if v4_name: + firewall.append(v4_name) + else: + firewall.append('') + if v6_name: + firewall_v6.append(v6_name) + else: + firewall_v6.append('') + + if 'from' in zone_conf: + for from_conf in zone_conf['from']: + from_zone.append(from_conf['name']) + + v4_name = dict_search_args(from_conf, 'firewall') + v6_name = dict_search_args(from_conf, 'firewall_v6') + if v4_name: + firewall.append(v4_name) + else: + firewall.append('') + if v6_name: + firewall_v6.append(v6_name) + else: + firewall_v6.append('') + + zone_info.append("\n".join(from_zone)) + zone_info.append("\n".join(firewall)) + zone_info.append("\n".join(firewall_v6)) + return zone_info + + +def get_formatted_output(zone_policy: list) -> str: + """ + Formatted output of all zones + :param zone_policy: list of zones + :type zone_policy: list + :return: formatted table with zones + :rtype: str + """ + headers = ["Zone", + "Interfaces", + "From Zone", + "Firewall IPv4", + "Firewall IPv6" + ] + formatted_list = [] + for zone_conf in zone_policy: + formatted_list.append(output_zone_list(zone_conf)) + tabulate.PRESERVE_WHITESPACE = True + output = tabulate.tabulate(formatted_list, headers, numalign="left") + return output + + +def show(raw: bool, zone: typing.Optional[str]): + """ + Show zone-policy command + :param raw: if API + :type raw: bool + :param zone: zone name + :type zone: str + """ + conf: ConfigTreeQuery = ConfigTreeQuery() + zones_config: dict = get_config_zone(conf, zone) + zone_policy_api: list = _convert_config(zones_config, zone) + if raw: + return zone_policy_api + else: + return get_formatted_output(zone_policy_api) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/opt/vyatta/bin/restricted-shell b/src/opt/vyatta/bin/restricted-shell new file mode 100644 index 0000000..ffcbb53 --- /dev/null +++ b/src/opt/vyatta/bin/restricted-shell @@ -0,0 +1,11 @@ +#!/bin/bash + +if [ $# != 0 ]; then + echo "Remote command execution is not allowed for operator level users" + args=($@) + args_str=$(IFS=" " ; echo "${args[*]}") + logger "Operator level user $USER attempted remote command execution: $args_str" + exit 1 +fi + +exec vbash diff --git a/src/opt/vyatta/bin/vyatta-op-cmd-wrapper b/src/opt/vyatta/bin/vyatta-op-cmd-wrapper new file mode 100644 index 0000000..a89211b --- /dev/null +++ b/src/opt/vyatta/bin/vyatta-op-cmd-wrapper @@ -0,0 +1,6 @@ +#!/bin/vbash +shopt -s expand_aliases +source /etc/default/vyatta +source /etc/bash_completion.d/vyatta-op +_vyatta_op_init +_vyatta_op_run "$@" diff --git a/src/opt/vyatta/etc/LICENSE b/src/opt/vyatta/etc/LICENSE new file mode 100644 index 0000000..6d45519 --- /dev/null +++ b/src/opt/vyatta/etc/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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/src/opt/vyatta/etc/shell/level/users/allowed-op b/src/opt/vyatta/etc/shell/level/users/allowed-op new file mode 100644 index 0000000..381fd26 --- /dev/null +++ b/src/opt/vyatta/etc/shell/level/users/allowed-op @@ -0,0 +1,21 @@ +c +cl +cle +clea +clear +connect +delete +disconnect +execute +exit +force +monitor +ping +reset +release +renew +set +show +telnet +traceroute +update diff --git a/src/opt/vyatta/etc/shell/level/users/allowed-op.in b/src/opt/vyatta/etc/shell/level/users/allowed-op.in new file mode 100644 index 0000000..9752f99 --- /dev/null +++ b/src/opt/vyatta/etc/shell/level/users/allowed-op.in @@ -0,0 +1,17 @@ +clear +connect +delete +disconnect +execute +exit +force +monitor +ping +reset +release +renew +set +show +telnet +traceroute +update diff --git a/src/opt/vyatta/sbin/if-mib-alias b/src/opt/vyatta/sbin/if-mib-alias new file mode 100644 index 0000000..bc86f99 --- /dev/null +++ b/src/opt/vyatta/sbin/if-mib-alias @@ -0,0 +1,130 @@ +#! /usr/bin/perl + +# **** License **** +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# This code was originally developed by Vyatta, Inc. +# Portions created by Vyatta are Copyright (C) 2007 Vyatta, Inc. +# All Rights Reserved. +# +# Author: Stephen Hemminger +# Date: October 2010 +# Description: script is run as net-snmp extension to read interface alias +# +# **** End License **** + +use strict; +use warnings; +use feature "switch"; +no warnings 'experimental::smartmatch'; + +# Collect interface all alias values +sub get_alias { + my @interfaces; + + open (my $ip, '-|', 'ip li') + or die "Can't run ip command\n"; + my $index; + while(<$ip>) { + if (/^(\d+): ([^:]*): /) { + $index = $1; + $interfaces[$index] = $2; + } elsif (/^ +alias (.*)$/) { + $interfaces[$index] = $1; + } + } + close $ip; + return @interfaces; +} + +sub get_oid { + my $oid = shift; + die "Not a valid Object ID: $oid" + unless ($oid =~ /.(\d+)$/); + + my $ifindex = $1; + my @interfaces = get_alias(); + + my $ifalias = $interfaces[$ifindex]; + print "$oid\nstring\n$ifalias\n" if $ifalias; +} + +# OID of ifAlias [RFC2863] +my $BASE = '.1.3.6.1.2.1.31.1.1.1.18'; + +sub get_next { + my $oid = shift; + + return get_next("$BASE.0") + if ($oid eq $BASE); + + die "Not a valid Object ID: $oid" + unless ($oid =~ /^(\S*)\.(\d+)$/); + + my $base = $1; + my $ifindex = $2; + my @interfaces = get_alias(); + + while (++$ifindex <= $#interfaces) { + my $ifalias = $interfaces[$ifindex]; + if ($ifalias) { + print "$base.$ifindex\nstring\n$ifalias\n"; + last; + } + } +} + +sub ifindextoname { + my $ifindex = shift; + + open (my $ip, '-|', 'ip li') + or die "Can't run ip command\n"; + my $index; + while(<$ip>) { + next unless (/^(\d+): ([^:]*): /); + return $2 if ($1 == $ifindex); + } + return; +} + +sub set_oid { + my ($oid, $target, $value) = @_; + die "Not a valid Object ID: $oid" + unless ($oid =~ /\.(\d+)$/); + my $ifindex = $1; + unless ($target eq 'string') { + print "wrong-type\n"; + return; + } + + my $ifname = ifindextoname($ifindex); + if ($ifname) { + system("ip li set $ifname alias '$value' >/dev/null 2>&1"); + print "not-writeable\n" if ($? != 0); + } +} + +sub usage { + warn "Usage: $0 {-g|-n} OID\n"; + warn " $0 -s OID TARGET VALUE\n"; + exit 1; +} + +usage unless $#ARGV >= 1; + +given ($ARGV[0]) { + when ('-g') { get_oid ($ARGV[1]); } + when ('-n') { get_next ($ARGV[1]); } + when ('-s') { set_oid ($ARGV[1], $ARGV[2], $ARGV[3]); } + default { + warn "$ARGV[0] unknown flag\n"; + usage; + } +} diff --git a/src/opt/vyatta/sbin/vyos-persistpath b/src/opt/vyatta/sbin/vyos-persistpath new file mode 100644 index 0000000..d7199b0 --- /dev/null +++ b/src/opt/vyatta/sbin/vyos-persistpath @@ -0,0 +1,19 @@ +#!/bin/bash + +if grep -q -e '^overlay.*/filesystem.squashfs' /proc/mounts; then + # Live CD boot + exit 2 + +elif grep -q 'upperdir=/live/persistence/' /proc/mounts && egrep -q 'overlay / overlay ' /proc/mounts; then + # union boot + + boot_device=`grep -o 'upperdir=/live/persistence/[^/]*/boot' /proc/mounts | cut -d / -f 4` + persist_path="/lib/live/mount/persistence/$boot_device" + + echo $persist_path + exit 0 +else + # old style boot + + exit 1 +fi
\ No newline at end of file diff --git a/src/opt/vyatta/share/vyatta-op/functions/interpreter/vyatta-common b/src/opt/vyatta/share/vyatta-op/functions/interpreter/vyatta-common new file mode 100644 index 0000000..e749f02 --- /dev/null +++ b/src/opt/vyatta/share/vyatta-op/functions/interpreter/vyatta-common @@ -0,0 +1,82 @@ +# vyatta bash completion common functions + +# **** License **** +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# A copy of the GNU General Public License is available as +# `/usr/share/common-licenses/GPL' in the Debian GNU/Linux distribution +# or on the World Wide Web at `http://www.gnu.org/copyleft/gpl.html'. +# You can also obtain it by writing to the Free Software Foundation, +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Vyatta +# Description: bash completion common functions +# +# **** End License **** + +get_prefix_filtered_list () +{ + # $1: prefix + # $2: \@list + # $3: \@filtered + declare -a olist + local pfx=$1 + pfx=${pfx#\"} + eval "olist=( \"\${$2[@]}\" )" + local idx=0 + for elem in "${olist[@]}"; do + local sub="${elem#$pfx}" + if [[ "$elem" == "$sub" ]] && [[ -n "$pfx" ]]; then + continue + fi + eval "$3[$idx]=\$elem" + (( idx++ )) + done +} + +get_prefix_filtered_list2 () +{ + # $1: prefix + # $2: \@list + # $3: \@filtered + # $4: \@list2 + # $5: \@filtered2 + declare -a olist + local pfx=$1 + pfx=${pfx#\"} + eval "olist=( \"\${$2[@]}\" )" + eval "local orig_len=\${#$2[@]}" + local orig_idx=0 + local idx=0 + for (( orig_idx = 0; orig_idx < orig_len; orig_idx++ )); do + eval "local elem=\${$2[$orig_idx]}" + eval "local elem2=\${$4[$orig_idx]}" + local sub="${elem#$pfx}" + if [[ "$elem" == "$sub" ]] && [[ -n "$pfx" ]]; then + continue + fi + eval "$3[$idx]=\$elem" + eval "$5[$idx]=\$elem2" + (( idx++ )) + done +} + +is_elem_of () { + local elem="$1" + local -a olist + eval "olist=( \"\${$2[@]}\" )" + for e in "${olist[@]}"; do + if [[ "$e" == "$elem" ]]; then + return 0 + fi + done + return 1 +} diff --git a/src/opt/vyatta/share/vyatta-op/functions/interpreter/vyatta-op-run b/src/opt/vyatta/share/vyatta-op/functions/interpreter/vyatta-op-run new file mode 100644 index 0000000..f0479ae --- /dev/null +++ b/src/opt/vyatta/share/vyatta-op/functions/interpreter/vyatta-op-run @@ -0,0 +1,240 @@ +# **** License **** +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# This code was originally developed by Vyatta, Inc. +# Portions created by Vyatta are Copyright (C) 2006, 2007 Vyatta, Inc. +# All Rights Reserved. +# +# Author: Tom Grennan +# Date: 2007 +# Description: setup bash completion for Vyatta operational commands +# +# **** End License **** + +_vyatta_op_init () +{ + # empty and default line compeletion + complete -E -F _vyatta_op_expand + complete -D -F _vyatta_op_default_expand + + # create the top level aliases for the unambiguous portions of the commands + # this is the only place we need an entire enumerated list of the subcommands + for cmd in $( ls /opt/vyatta/share/vyatta-op/templates/ ); do + for pos in $(seq 1 ${#cmd}); do + case ${cmd:0:$pos} in + for|do|done|if|fi|case|while|tr ) + continue ;; + *) ;; + esac + complete -F _vyatta_op_expand ${cmd:0:$pos} + eval alias ${cmd:0:$pos}=\'_vyatta_op_run ${cmd:0:$pos}\' + done + done + + shopt -s histverify +} + +_vyatta_op_get_node_def_field () +{ + local file=$1 field=$2 + + sed -n '/^'"$field"':/,$ { +# strip field name and hold rest of line + s/[a-z]*: *// + h + :b +# at EOF, print hold buffer and quit + $ { x; p; q } +# input next line + n +# if start of another field def, print hold buf and quit + /^[a-z]*:/ { x; p; q } +# add to hold buf and branch to input next line + H + bb + }' $file +} + +_vyatta_op_conv_node_path () +{ + # is the node ok, ambiguous, or invalid + local node_path + local node + local -a ARR + node_path=$1 + node=$2 + ARR=( $(compgen -d $node_path/$node) ) + if [[ "${#ARR[@]}" == "1" ]]; then + echo ${ARR[0]##*/} + elif [[ "${#ARR[@]}" == "0" ]]; then + if [[ -d "${node_path}/node.tag" ]]; then + echo "$node tag" + else + echo "$node invalid" + fi + elif [[ -d "$node_path/$node" ]]; then + echo $node + elif [[ "$VYATTA_USER_LEVEL_DIR" != "/opt/vyatta/etc/shell/level/admin" ]];then + # special handling for unprivledged completions. + # Since top level commands are different for unprivledged users + # we need a handler to expand them properly. + local -a filtered_cmds=() + local -a allowed=( $(cat $VYATTA_USER_LEVEL_DIR/allowed-op.in) ) + get_prefix_filtered_list $node allowed filtered_cmds + if [[ "${#filtered_cmds[@]}" == "1" ]];then + echo ${filtered_cmds[0]} + else + echo "${node} ambiguous" + fi + else + echo "$node ambiguous" + fi +} + +_vyatta_op_conv_run_cmd () +{ + # Substitue bash positional variables + # for the same value in the expanded array + local restore_shopts=$( shopt -p extglob nullglob | tr \\n \; ) + shopt -s extglob + shopt -u nullglob + local run_cmd="$1" + local line outline + local -i inquote=0; + local outcmd=''; + local OIFS=$IFS + local re="([^']*')(.*)" + + toggle_inquote() + { + if [[ $inquote == 0 ]]; then + inquote=1 + else + inquote=0 + fi + } + + process_subline() + { + if [[ $inquote == 1 ]]; then + outline+="$1" + else + outline+=$(sed -e 's/\$\([0-9]\)/\$\{args\[\1\]\}/g' <<<"$1") + fi + } + + run_cmd="${run_cmd/\"\$\@\"/${args[*]}}" + run_cmd="${run_cmd/\$\*/${args[*]}}" + run_cmd="${run_cmd//\\/\\\\}" + IFS=$'\n' + for line in ${run_cmd[@]}; do + outline='' + while [[ -n "$line" ]]; do + if [[ "$line" =~ $re ]]; then + process_subline "${BASH_REMATCH[1]}" + toggle_inquote + else + process_subline "$line" + fi + line="${BASH_REMATCH[2]}" + done + outcmd+="$outline\n" + done + IFS=$OIFS + eval "$restore_shopts" + echo -ne "$outcmd" +} + +_vyatta_op_run () +{ + # if run with bash builtin "set -/+*" run set and return + # this happens when a different completion script runs eval "set ..." + # (VyOS T1604) + if [[ "$1" == "set" && "$2" =~ ^(-|\+).* ]]; then + set "${@:2}" + return + fi + + local -i estat + local tpath=$vyatta_op_templates + local restore_shopts=$( shopt -p extglob nullglob | tr \\n \; ) + shopt -s extglob nullglob + + _vyatta_op_last_comp=${_vyatta_op_last_comp_init} + false; estat=$? + stty echo 2> /dev/null # turn echo on, this is a workaround for bug 7570 + # not a fix we need to look at why the readline library + # is getting confused on paged help text. + + i=1 + declare -a args # array of expanded arguments + for arg in "$@"; do + local orig_arg=$arg + if [[ $arg == "*" ]]; then + arg="*" #leave user defined wildcards alone + else + arg=( $(_vyatta_op_conv_node_path $tpath $arg) ) # expand the arguments + fi + # output proper error message based on the above expansion + if [[ "${arg[1]}" == "ambiguous" ]]; then + echo -ne "\n Ambiguous command: ${args[@]} [$arg]\n" >&2 + local -a cmds=( $(compgen -d $tpath/$arg) ) + _vyatta_op_node_path=$tpath + local comps=$(_vyatta_op_help $arg ${cmds[@]##*/}) + echo -e "$comps\n" | sed -e 's/^P/ P/' + eval $restore_shopts + return 1 + elif [[ "${arg[1]}" == "invalid" ]]; then + echo -ne "\n Invalid command: ${args[@]} [$arg]\n\n" >&2 + eval $restore_shopts + return 1 + fi + + if [ -f "$tpath/$arg/node.def" ] ; then + tpath+=/$arg + elif [ -f $tpath/node.tag/node.def ] ; then + tpath+=/node.tag + else + echo -ne "\n Invalid command: ${args[@]} [$arg]\n\n" >&2 + eval $restore_shopts + return 1 + fi + if [[ "$arg" == "node.tag" ]]; then + args[$i]=$orig_arg + else + args[$i]=$arg + fi + let "i+=1" + done + + local run_cmd=$(_vyatta_op_get_node_def_field $tpath/node.def run) + run_cmd=$(_vyatta_op_conv_run_cmd "$run_cmd") # convert the positional parameters + local ret=0 + # Exception for the `show file` command + local file_cmd='\$\{vyos_op_scripts_dir\}\/file\.py' + local cmd_regex="^(LESSOPEN=|less|pager|tail|(sudo )?$file_cmd).*" + if [ -n "$run_cmd" ]; then + eval $restore_shopts + if [[ -t 1 && "${args[1]}" == "show" && ! $run_cmd =~ $cmd_regex ]] ; then + eval "($run_cmd) | ${VYATTA_PAGER:-cat}" + else + eval "$run_cmd" + fi + else + echo -ne "\n Incomplete command: ${args[@]}\n\n" >&2 + eval $restore_shopts + ret=1 + fi + return $ret +} + +### Local Variables: +### mode: shell-script +### End: diff --git a/src/opt/vyatta/share/vyatta-op/functions/interpreter/vyatta-unpriv b/src/opt/vyatta/share/vyatta-op/functions/interpreter/vyatta-unpriv new file mode 100644 index 0000000..1507f4f --- /dev/null +++ b/src/opt/vyatta/share/vyatta-op/functions/interpreter/vyatta-unpriv @@ -0,0 +1,97 @@ +#!/bin/bash +# **** License **** +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# This code was originally developed by Vyatta, Inc. +# Portions created by Vyatta are Copyright (C) 2006, 2007 Vyatta, Inc. +# All Rights Reserved. +# +# **** End License **** + +source /opt/vyatta/share/vyatta-op/functions/interpreter/vyatta-common + +declare -a op_allowed +declare -a toplevel + +op_allowed=( $(cat /opt/vyatta/etc/shell/level/users/allowed-op.in) ) +toplevel=( $(ls /opt/vyatta/share/vyatta-op/templates/) ) + +vyatta_unpriv_ambiguous () +{ + local -a filtered_cmds=() + get_prefix_filtered_list $1 op_allowed filtered_cmds + _vyatta_op_node_path=${vyatta_op_templates} + comps=$(_vyatta_op_help $1 ${filtered_cmds[@]}) + echo -ne "\n Ambiguous command: [$1]\n" + echo -e "$comps\n" | sed -e 's/^P/ P/' +} + +vyatta_unpriv_init () +{ + # empty and default line compeletion + complete -E -F _vyatta_op_expand + complete -D -F _vyatta_op_default_expand + + for cmd in "${op_allowed[@]}"; do + if is_elem_of ${cmd} toplevel; then + for pos in $(seq 1 ${#cmd}); do + case ${cmd:0:$pos} in + for|do|done|if|fi|case|while|tr ) + continue ;; + *) ;; + esac + local -a filtered_cmds=() + get_prefix_filtered_list ${cmd:0:$pos} op_allowed filtered_cmds + local found + is_elem_of "${cmd:0:$pos}" op_allowed + found=$? + if [[ "${#filtered_cmds[@]}" == "1" || "${cmd:0:$pos}" == "$cmd" || "$found" == "0" ]]; then + local fcmd + if [[ "${#filtered_cmds[@]}" == "1" ]]; then + fcmd=${filtered_cmds[0]} + elif is_elem_of "${cmd:0:$pos}" op_allowed; then + fcmd=${cmd:0:$pos} + else + fcmd=$cmd + fi + eval alias ${cmd:0:$pos}=\'_vyatta_op_run $fcmd\' + else + eval alias ${cmd:0:$pos}=\'vyatta_unpriv_ambiguous ${cmd:0:$pos}\' + fi + complete -F _vyatta_op_expand ${cmd:0:$pos} + done + fi + done + if [[ "$VYATTA_USER_LEVEL_DIR" == "/opt/vyatta/etc/shell/level/users" ]]; then + PS1='\u@\h> ' + fi +} + +vyatta_unpriv_gen_allowed () { + local -a allowed_cmds=() + rm -rf /opt/vyatta/etc/shell/level/users/allowed-op + for cmd in "${op_allowed[@]}"; do + if is_elem_of ${cmd} toplevel; then + for pos in $(seq 1 ${#cmd}); do + case ${cmd:0:$pos} in + for|do|done|if|fi|case|while|tr ) + continue ;; + *) ;; + esac + if ! is_elem_of ${cmd:0:$pos} allowed_cmds; then + allowed_cmds+=( ${cmd:0:$pos} ) + echo ${cmd:0:$pos} >> /opt/vyatta/etc/shell/level/users/allowed-op + fi + done + else + echo ${cmd} >> /opt/vyatta/etc/shell/level/users/allowed-op + fi + done +} diff --git a/src/pam-configs/mfa-google-authenticator b/src/pam-configs/mfa-google-authenticator new file mode 100644 index 0000000..9e49e5e --- /dev/null +++ b/src/pam-configs/mfa-google-authenticator @@ -0,0 +1,8 @@ +Name: Google Authenticator PAM module (2FA/MFA) +Default: no +Priority: 384 + +Auth-Type: Primary +Auth: + [default=ignore success=ok auth_err=die] pam_google_authenticator.so nullok forward_pass + diff --git a/src/pam-configs/radius-mandatory b/src/pam-configs/radius-mandatory new file mode 100644 index 0000000..3368fe7 --- /dev/null +++ b/src/pam-configs/radius-mandatory @@ -0,0 +1,19 @@ +Name: RADIUS authentication (mandatory mode) +Default: no +Priority: 576 + +Auth-Type: Primary +Auth-Initial: + [default=ignore success=end auth_err=die perm_denied=die user_unknown=die] pam_radius_auth.so +Auth: + [default=ignore success=end auth_err=die perm_denied=die user_unknown=die] pam_radius_auth.so use_first_pass + +Account-Type: Primary +Account: + [default=ignore success=1] pam_succeed_if.so user notingroup radius quiet + [default=ignore success=end] pam_radius_auth.so + +Session-Type: Additional +Session: + [default=ignore success=1] pam_succeed_if.so user notingroup radius quiet + [default=bad success=ok] pam_radius_auth.so diff --git a/src/pam-configs/radius-optional b/src/pam-configs/radius-optional new file mode 100644 index 0000000..7308506 --- /dev/null +++ b/src/pam-configs/radius-optional @@ -0,0 +1,19 @@ +Name: RADIUS authentication (optional mode) +Default: no +Priority: 576 + +Auth-Type: Primary +Auth-Initial: + [default=ignore success=end] pam_radius_auth.so +Auth: + [default=ignore success=end] pam_radius_auth.so use_first_pass + +Account-Type: Primary +Account: + [default=ignore success=1] pam_succeed_if.so user notingroup radius quiet + [default=ignore success=end] pam_radius_auth.so + +Session-Type: Additional +Session: + [default=ignore success=1] pam_succeed_if.so user notingroup radius quiet + [default=ignore success=ok perm_denied=bad user_unknown=bad] pam_radius_auth.so diff --git a/src/pam-configs/tacplus-mandatory b/src/pam-configs/tacplus-mandatory new file mode 100644 index 0000000..ffccece --- /dev/null +++ b/src/pam-configs/tacplus-mandatory @@ -0,0 +1,17 @@ +Name: TACACS+ authentication (mandatory mode) +Default: no +Priority: 576 + +Auth-Type: Primary +Auth: + [default=ignore success=end auth_err=die perm_denied=die user_unknown=die] pam_tacplus.so include=/etc/tacplus_servers login=login + +Account-Type: Primary +Account: + [default=ignore success=1] pam_succeed_if.so user notingroup tacacs quiet + [default=bad success=end] pam_tacplus.so include=/etc/tacplus_servers login=login + +Session-Type: Additional +Session: + [default=ignore success=1] pam_succeed_if.so user notingroup tacacs quiet + [default=bad success=ok] pam_tacplus.so include=/etc/tacplus_servers login=login diff --git a/src/pam-configs/tacplus-optional b/src/pam-configs/tacplus-optional new file mode 100644 index 0000000..095c3a1 --- /dev/null +++ b/src/pam-configs/tacplus-optional @@ -0,0 +1,17 @@ +Name: TACACS+ authentication (optional mode) +Default: no +Priority: 576 + +Auth-Type: Primary +Auth: + [default=ignore success=end] pam_tacplus.so include=/etc/tacplus_servers login=login + +Account-Type: Primary +Account: + [default=ignore success=1] pam_succeed_if.so user notingroup tacacs quiet + [default=ignore success=end auth_err=bad perm_denied=bad user_unknown=bad] pam_tacplus.so include=/etc/tacplus_servers login=login + +Session-Type: Additional +Session: + [default=ignore success=1] pam_succeed_if.so user notingroup tacacs quiet + [default=ignore success=ok session_err=bad user_unknown=bad] pam_tacplus.so include=/etc/tacplus_servers login=login diff --git a/src/services/api/graphql/README.graphql b/src/services/api/graphql/README.graphql new file mode 100644 index 0000000..1133d79 --- /dev/null +++ b/src/services/api/graphql/README.graphql @@ -0,0 +1,218 @@ + +The following examples are in the form as entered in the GraphQL +'playground', which is found at: + +https://{{ host_address }}/graphql + +Example using GraphQL mutations to configure a DHCP server: + +All examples assume that the http-api is running: + +'set service https api' + +One can configure an address on an interface, and configure the DHCP server +to run with that address as default router by requesting these 'mutations' +in the GraphQL playground: + +mutation { + CreateInterfaceEthernet (data: {interface: "eth1", + address: "192.168.0.1/24", + description: "BOB"}) { + success + errors + data { + address + } + } +} + +mutation { + CreateDhcpServer(data: {sharedNetworkName: "BOB", + subnet: "192.168.0.0/24", + defaultRouter: "192.168.0.1", + nameServer: "192.168.0.1", + domainName: "vyos.net", + lease: 86400, + range: 0, + start: "192.168.0.9", + stop: "192.168.0.254", + dnsForwardingAllowFrom: "192.168.0.0/24", + dnsForwardingCacheSize: 0, + dnsForwardingListenAddress: "192.168.0.1"}) { + success + errors + data { + defaultRouter + } + } +} + +To save the configuration, use the following mutation: + +mutation { + SaveConfigFile(data: {fileName: "/config/config.boot"}) { + success + errors + data { + fileName + } + } +} + +N.B. fileName can be empty (fileName: "") or data can be empty (data: {}) to +save to /config/config.boot; to save to an alternative path, specify +fileName. + +Similarly, using an analogous 'endpoint' (meaning the form of the request +and resolver; the actual enpoint for all GraphQL requests is +https://hostname/graphql), one can load an arbitrary config file from a +path. + +mutation { + LoadConfigFile(data: {fileName: "/home/vyos/config.boot"}) { + success + errors + data { + fileName + } + } +} + +Op-mode 'show' commands may be requested by path, e.g.: + +query { + Show (data: {path: ["interfaces", "ethernet", "detail"]}) { + success + errors + data { + result + } + } +} + +N.B. to see the output the 'data' field 'result' must be present in the +request. + +Mutations to manipulate firewall address groups: + +mutation { + CreateFirewallAddressGroup (data: {name: "ADDR-GRP", address: "10.0.0.1"}) { + success + errors + } +} + +mutation { + UpdateFirewallAddressGroupMembers (data: {name: "ADDR-GRP", + address: ["10.0.0.1-10.0.0.8", "192.168.0.1"]}) { + success + errors + } +} + +mutation { + RemoveFirewallAddressGroupMembers (data: {name: "ADDR-GRP", + address: "192.168.0.1"}) { + success + errors + } +} + +N.B. The schema for the above specify that 'address' be of the form 'list of +strings' (SDL type [String!]! for UpdateFirewallAddressGroupMembers, where +the ! indicates that the input is required; SDL type [String] in +CreateFirewallAddressGroup, since a group may be created without any +addresses). However, notice that a single string may be passed without being +a member of a list, in which case the specification allows for 'input +coercion': + +http://spec.graphql.org/October2021/#sec-Scalars.Input-Coercion + +Similarly, IPv6 versions of the above: + +CreateFirewallAddressIpv6Group +UpdateFirewallAddressIpv6GroupMembers +RemoveFirewallAddressIpv6GroupMembers + + +Instead of using the GraphQL playground, an equivalent curl command to the +first example above would be: + +curl -k 'https://192.168.100.168/graphql' -H 'Content-Type: application/json' --data-binary '{"query": "mutation {createInterfaceEthernet (data: {interface: \"eth1\", address: \"192.168.0.1/24\", description: \"BOB\"}) {success errors data {address}}}"}' + +Note that the 'mutation' term is prefaced by 'query' in the curl command. + +Curl equivalents may be read from within the GraphQL playground at the 'copy +curl' button. + +What's here: + +services +├── api +│  └── graphql +│  ├── bindings.py +│  ├── graphql +│  │  ├── directives.py +│  │  ├── __init__.py +│  │  ├── mutations.py +│  │  └── schema +│  │  ├── config_file.graphql +│  │  ├── dhcp_server.graphql +│  │  ├── firewall_group.graphql +│  │  ├── interface_ethernet.graphql +│  │  ├── schema.graphql +│  │  ├── show_config.graphql +│  │  └── show.graphql +│  ├── README.graphql +│  ├── recipes +│  │  ├── __init__.py +│  │  ├── remove_firewall_address_group_members.py +│  │  ├── session.py +│  │  └── templates +│  │  ├── create_dhcp_server.tmpl +│  │  ├── create_firewall_address_group.tmpl +│  │  ├── create_interface_ethernet.tmpl +│  │  ├── remove_firewall_address_group_members.tmpl +│  │  └── update_firewall_address_group_members.tmpl +│  └── state.py +├── vyos-configd +├── vyos-hostsd +└── vyos-http-api-server + +The GraphQL library that we are using, Ariadne, advertises itself as a +'schema-first' implementation: define the schema; define resolvers +(handlers) for declared Query and Mutation types (Subscription types are not +currently used). + +In the current approach to a high-level API, we consider the +Jinja2-templated collection of configuration mode 'set'/'delete' commands as +the Ur-data; the GraphQL schema is produced from those files, located in +'api/graphql/recipes/templates'. + +Resolvers for the schema Mutation fields are dynamically generated using a +'directive' added to the respective schema field. The directive, +'@configure', is handled by the class 'ConfigureDirective' in +'api/graphql/graphql/directives.py', which calls the +'make_configure_resolver' function in 'api/graphql/graphql/mutations.py'; +the produced resolver calls the appropriate wrapper in +'api/graphql/recipes', with base class doing the (overridable) configuration +steps of calling all defined 'set'/'delete' commands. + +Integrating the above with vyos-http-api-server is 4 lines of code. + +What needs to be done: + +• automate generation of schema and wrappers from templated configuration +commands + +• investigate whether the subclassing provided by the named wrappers in +'api/graphql/recipes' is sufficient for use cases which need to modify data + +• encapsulate the manipulation of 'canonical names' which transforms the +prefixed camel-case schema names to various snake-case file/function names + +• consider mechanism for migration of templates: offline vs. on-the-fly + +• define the naming convention for those schema fields that refer to +configuration mode parameters: e.g. how much of the path is needed as prefix +to uniquely define the term diff --git a/src/services/api/graphql/__init__.py b/src/services/api/graphql/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/services/api/graphql/__init__.py diff --git a/src/services/api/graphql/bindings.py b/src/services/api/graphql/bindings.py new file mode 100644 index 0000000..ef49664 --- /dev/null +++ b/src/services/api/graphql/bindings.py @@ -0,0 +1,36 @@ +# Copyright 2021 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +import vyos.defaults +from . graphql.queries import query +from . graphql.mutations import mutation +from . graphql.directives import directives_dict +from . graphql.errors import op_mode_error +from . graphql.auth_token_mutation import auth_token_mutation +from . libs.token_auth import init_secret +from . import state +from ariadne import make_executable_schema, load_schema_from_path, snake_case_fallback_resolvers + +def generate_schema(): + api_schema_dir = vyos.defaults.directories['api_schema'] + + if state.settings['app'].state.vyos_auth_type == 'token': + init_secret() + + type_defs = load_schema_from_path(api_schema_dir) + + schema = make_executable_schema(type_defs, query, op_mode_error, mutation, auth_token_mutation, snake_case_fallback_resolvers, directives=directives_dict) + + return schema diff --git a/src/services/api/graphql/generate/composite_function.py b/src/services/api/graphql/generate/composite_function.py new file mode 100644 index 0000000..d6626fd --- /dev/null +++ b/src/services/api/graphql/generate/composite_function.py @@ -0,0 +1,7 @@ +# typing information for composite functions: those that invoke several +# elementary requests, and return the result as a single dict +def system_status(): + pass + +queries = {'system_status': system_status} +mutations = {} diff --git a/src/services/api/graphql/generate/config_session_function.py b/src/services/api/graphql/generate/config_session_function.py new file mode 100644 index 0000000..4ebb47a --- /dev/null +++ b/src/services/api/graphql/generate/config_session_function.py @@ -0,0 +1,30 @@ +# typing information for native configsession functions; used to generate +# schema definition files +import typing + +def show_config(path: list[str], configFormat: typing.Optional[str]): + pass + +def show(path: list[str]): + pass + +def show_user_info(user: str): + pass + +queries = {'show_config': show_config, + 'show': show, + 'show_user_info': show_user_info} + +def save_config_file(fileName: typing.Optional[str]): + pass +def load_config_file(fileName: str): + pass +def add_system_image(location: str): + pass +def delete_system_image(name: str): + pass + +mutations = {'save_config_file': save_config_file, + 'load_config_file': load_config_file, + 'add_system_image': add_system_image, + 'delete_system_image': delete_system_image} diff --git a/src/services/api/graphql/generate/generate_schema.py b/src/services/api/graphql/generate/generate_schema.py new file mode 100644 index 0000000..dd5e7ea --- /dev/null +++ b/src/services/api/graphql/generate/generate_schema.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +from schema_from_op_mode import generate_op_mode_definitions +from schema_from_config_session import generate_config_session_definitions +from schema_from_composite import generate_composite_definitions + +if __name__ == '__main__': + generate_op_mode_definitions() + generate_config_session_definitions() + generate_composite_definitions() diff --git a/src/services/api/graphql/generate/schema_from_composite.py b/src/services/api/graphql/generate/schema_from_composite.py new file mode 100644 index 0000000..06e7403 --- /dev/null +++ b/src/services/api/graphql/generate/schema_from_composite.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# +# A utility to generate GraphQL schema defintions from typing information of +# composite functions comprising several requests. + +import os +import sys +from inspect import signature +from jinja2 import Template + +from vyos.defaults import directories +if __package__ is None or __package__ == '': + sys.path.append(os.path.join(directories['services'], 'api')) + from graphql.libs.op_mode import snake_to_pascal_case, map_type_name + from composite_function import queries, mutations +else: + from .. libs.op_mode import snake_to_pascal_case, map_type_name + from . composite_function import queries, mutations + +SCHEMA_PATH = directories['api_schema'] +CLIENT_OP_PATH = directories['api_client_op'] + +schema_data: dict = {'schema_name': '', + 'schema_fields': []} + +query_template = """ +input {{ schema_name }}Input { + key: String + {%- for field_entry in schema_fields %} + {{ field_entry }} + {%- endfor %} +} + +type {{ schema_name }} { + result: Generic +} + +type {{ schema_name }}Result { + data: {{ schema_name }} + success: Boolean! + errors: [String] +} + +extend type Query { + {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @compositequery +} +""" + +mutation_template = """ +input {{ schema_name }}Input { + key: String + {%- for field_entry in schema_fields %} + {{ field_entry }} + {%- endfor %} +} + +type {{ schema_name }} { + result: Generic +} + +type {{ schema_name }}Result { + data: {{ schema_name }} + success: Boolean! + errors: [String] +} + +extend type Mutation { + {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @compositemutation +} +""" + +op_query_template = """ +query {{ op_name }} ({{ op_sig }}) { + {{ op_name }} (data: { {{ op_arg }} }) { + success + errors + data { + result + } + } +} +""" + +op_mutation_template = """ +mutation {{ op_name }} ({{ op_sig }}) { + {{ op_name }} (data: { {{ op_arg }} }) { + success + errors + data { + result + } + } +} +""" + +def create_schema(func_name: str, func: callable, template: str) -> str: + sig = signature(func) + + field_dict = {} + for k in sig.parameters: + field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation) + + schema_fields = [] + for k,v in field_dict.items(): + schema_fields.append(k+': '+v) + + schema_data['schema_name'] = snake_to_pascal_case(func_name) + schema_data['schema_fields'] = schema_fields + + j2_template = Template(template) + res = j2_template.render(schema_data) + + return res + +def create_client_op(func_name: str, func: callable, template: str) -> str: + sig = signature(func) + + field_dict = {} + for k in sig.parameters: + field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation) + + op_sig = ['$key: String'] + op_arg = ['key: $key'] + for k,v in field_dict.items(): + op_sig.append('$'+k+': '+v) + op_arg.append(k+': $'+k) + + op_data = {} + op_data['op_name'] = snake_to_pascal_case(func_name) + op_data['op_sig'] = ', '.join(op_sig) + op_data['op_arg'] = ', '.join(op_arg) + + j2_template = Template(template) + + res = j2_template.render(op_data) + + return res + +def generate_composite_definitions(): + schema = [] + client_op = [] + for name,func in queries.items(): + res = create_schema(name, func, query_template) + schema.append(res) + res = create_client_op(name, func, op_query_template) + client_op.append(res) + + for name,func in mutations.items(): + res = create_schema(name, func, mutation_template) + schema.append(res) + res = create_client_op(name, func, op_mutation_template) + client_op.append(res) + + out = '\n'.join(schema) + with open(f'{SCHEMA_PATH}/composite.graphql', 'w') as f: + f.write(out) + + out = '\n'.join(client_op) + with open(f'{CLIENT_OP_PATH}/composite.graphql', 'w') as f: + f.write(out) + +if __name__ == '__main__': + generate_composite_definitions() diff --git a/src/services/api/graphql/generate/schema_from_config_session.py b/src/services/api/graphql/generate/schema_from_config_session.py new file mode 100644 index 0000000..1d5ff1e --- /dev/null +++ b/src/services/api/graphql/generate/schema_from_config_session.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# +# A utility to generate GraphQL schema defintions from typing information of +# (wrappers of) native configsession functions. + +import os +import sys +from inspect import signature +from jinja2 import Template + +from vyos.defaults import directories +if __package__ is None or __package__ == '': + sys.path.append(os.path.join(directories['services'], 'api')) + from graphql.libs.op_mode import snake_to_pascal_case, map_type_name + from config_session_function import queries, mutations +else: + from .. libs.op_mode import snake_to_pascal_case, map_type_name + from . config_session_function import queries, mutations + +SCHEMA_PATH = directories['api_schema'] +CLIENT_OP_PATH = directories['api_client_op'] + +schema_data: dict = {'schema_name': '', + 'schema_fields': []} + +query_template = """ +input {{ schema_name }}Input { + key: String + {%- for field_entry in schema_fields %} + {{ field_entry }} + {%- endfor %} +} + +type {{ schema_name }} { + result: Generic +} + +type {{ schema_name }}Result { + data: {{ schema_name }} + success: Boolean! + errors: [String] +} + +extend type Query { + {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @configsessionquery +} +""" + +mutation_template = """ +input {{ schema_name }}Input { + key: String + {%- for field_entry in schema_fields %} + {{ field_entry }} + {%- endfor %} +} + +type {{ schema_name }} { + result: Generic +} + +type {{ schema_name }}Result { + data: {{ schema_name }} + success: Boolean! + errors: [String] +} + +extend type Mutation { + {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @configsessionmutation +} +""" + +op_query_template = """ +query {{ op_name }} ({{ op_sig }}) { + {{ op_name }} (data: { {{ op_arg }} }) { + success + errors + data { + result + } + } +} +""" + +op_mutation_template = """ +mutation {{ op_name }} ({{ op_sig }}) { + {{ op_name }} (data: { {{ op_arg }} }) { + success + errors + data { + result + } + } +} +""" + +def create_schema(func_name: str, func: callable, template: str) -> str: + sig = signature(func) + + field_dict = {} + for k in sig.parameters: + field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation) + + schema_fields = [] + for k,v in field_dict.items(): + schema_fields.append(k+': '+v) + + schema_data['schema_name'] = snake_to_pascal_case(func_name) + schema_data['schema_fields'] = schema_fields + + j2_template = Template(template) + res = j2_template.render(schema_data) + + return res + +def create_client_op(func_name: str, func: callable, template: str) -> str: + sig = signature(func) + + field_dict = {} + for k in sig.parameters: + field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation) + + op_sig = ['$key: String'] + op_arg = ['key: $key'] + for k,v in field_dict.items(): + op_sig.append('$'+k+': '+v) + op_arg.append(k+': $'+k) + + op_data = {} + op_data['op_name'] = snake_to_pascal_case(func_name) + op_data['op_sig'] = ', '.join(op_sig) + op_data['op_arg'] = ', '.join(op_arg) + + j2_template = Template(template) + + res = j2_template.render(op_data) + + return res + +def generate_config_session_definitions(): + schema = [] + client_op = [] + for name,func in queries.items(): + res = create_schema(name, func, query_template) + schema.append(res) + res = create_client_op(name, func, op_query_template) + client_op.append(res) + + for name,func in mutations.items(): + res = create_schema(name, func, mutation_template) + schema.append(res) + res = create_client_op(name, func, op_mutation_template) + client_op.append(res) + + out = '\n'.join(schema) + with open(f'{SCHEMA_PATH}/configsession.graphql', 'w') as f: + f.write(out) + + out = '\n'.join(client_op) + with open(f'{CLIENT_OP_PATH}/configsession.graphql', 'w') as f: + f.write(out) + +if __name__ == '__main__': + generate_config_session_definitions() diff --git a/src/services/api/graphql/generate/schema_from_op_mode.py b/src/services/api/graphql/generate/schema_from_op_mode.py new file mode 100644 index 0000000..ab7cb69 --- /dev/null +++ b/src/services/api/graphql/generate/schema_from_op_mode.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# +# A utility to generate GraphQL schema defintions from standardized op-mode +# scripts. + +import os +import sys +import json +from inspect import signature, getmembers, isfunction, isclass, getmro +from jinja2 import Template + +from vyos.defaults import directories +from vyos.opmode import _is_op_mode_function_name as is_op_mode_function_name +from vyos.opmode import _get_literal_values as get_literal_values +from vyos.utils.system import load_as_module +if __package__ is None or __package__ == '': + sys.path.append(os.path.join(directories['services'], 'api')) + from graphql.libs.op_mode import is_show_function_name + from graphql.libs.op_mode import snake_to_pascal_case, map_type_name +else: + from .. libs.op_mode import is_show_function_name + from .. libs.op_mode import snake_to_pascal_case, map_type_name + +OP_MODE_PATH = directories['op_mode'] +SCHEMA_PATH = directories['api_schema'] +CLIENT_OP_PATH = directories['api_client_op'] +DATA_DIR = directories['data'] + + +op_mode_include_file = os.path.join(DATA_DIR, 'op-mode-standardized.json') +op_mode_error_schema = 'op_mode_error.graphql' + +schema_data: dict = {'schema_name': '', + 'schema_fields': []} + +query_template = """ +input {{ schema_name }}Input { + key: String + {%- for field_entry in schema_fields %} + {{ field_entry }} + {%- endfor %} +} + +type {{ schema_name }} { + result: Generic +} + +type {{ schema_name }}Result { + data: {{ schema_name }} + op_mode_error: OpModeError + success: Boolean! + errors: [String] +} + +extend type Query { + {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @genopquery +} +""" + +mutation_template = """ +input {{ schema_name }}Input { + key: String + {%- for field_entry in schema_fields %} + {{ field_entry }} + {%- endfor %} +} + +type {{ schema_name }} { + result: Generic +} + +type {{ schema_name }}Result { + data: {{ schema_name }} + op_mode_error: OpModeError + success: Boolean! + errors: [String] +} + +extend type Mutation { + {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @genopmutation +} +""" + +enum_template = """ +enum {{ enum_name }} { + {%- for field_entry in enum_fields %} + {{ field_entry }} + {%- endfor %} +} +""" + +error_template = """ +interface OpModeError { + name: String! + message: String! + vyos_code: Int! +} +{% for name in error_names %} +type {{ name }} implements OpModeError { + name: String! + message: String! + vyos_code: Int! +} +{%- endfor %} +""" + +op_query_template = """ +query {{ op_name }} ({{ op_sig }}) { + {{ op_name }} (data: { {{ op_arg }} }) { + success + errors + op_mode_error { + name + message + vyos_code + } + data { + result + } + } +} +""" + +op_mutation_template = """ +mutation {{ op_name }} ({{ op_sig }}) { + {{ op_name }} (data: { {{ op_arg }} }) { + success + errors + op_mode_error { + name + message + vyos_code + } + data { + result + } + } +} +""" + +def create_schema(func_name: str, base_name: str, func: callable, + enums: dict) -> str: + sig = signature(func) + + for k in sig.parameters: + t = get_literal_values(sig.parameters[k].annotation) + if t: + enums[t] = snake_to_pascal_case(sig.parameters[k].name + '_' + base_name) + + field_dict = {} + for k in sig.parameters: + field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation, enums) + + # It is assumed that if one is generating a schema for a 'show_*' + # function, that 'get_raw_data' is present and 'raw' is desired. + if 'raw' in list(field_dict): + del field_dict['raw'] + + schema_fields = [] + for k,v in field_dict.items(): + schema_fields.append(k+': '+v) + + schema_data['schema_name'] = snake_to_pascal_case(func_name + '_' + base_name) + schema_data['schema_fields'] = schema_fields + + if is_show_function_name(func_name): + j2_template = Template(query_template) + else: + j2_template = Template(mutation_template) + + res = j2_template.render(schema_data) + + return res + +def create_client_op(func_name: str, base_name: str, func: callable, + enums: dict) -> str: + sig = signature(func) + + for k in sig.parameters: + t = get_literal_values(sig.parameters[k].annotation) + if t: + enums[t] = snake_to_pascal_case(sig.parameters[k].name + '_' + base_name) + + field_dict = {} + for k in sig.parameters: + field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation, enums) + + # It is assumed that if one is generating a schema for a 'show_*' + # function, that 'get_raw_data' is present and 'raw' is desired. + if 'raw' in list(field_dict): + del field_dict['raw'] + + op_sig = ['$key: String'] + op_arg = ['key: $key'] + for k,v in field_dict.items(): + op_sig.append('$'+k+': '+v) + op_arg.append(k+': $'+k) + + op_data = {} + op_data['op_name'] = snake_to_pascal_case(func_name + '_' + base_name) + op_data['op_sig'] = ', '.join(op_sig) + op_data['op_arg'] = ', '.join(op_arg) + + if is_show_function_name(func_name): + j2_template = Template(op_query_template) + else: + j2_template = Template(op_mutation_template) + + res = j2_template.render(op_data) + + return res + +def create_enums(enums: dict) -> str: + enum_data = [] + for k, v in enums.items(): + enum = {'enum_name': v, 'enum_fields': list(k)} + enum_data.append(enum) + + out = '' + j2_template = Template(enum_template) + for el in enum_data: + out += j2_template.render(el) + out += '\n' + + return out + +def create_error_schema(): + from vyos import opmode + + e = Exception + err_types = getmembers(opmode, isclass) + err_types = [k for k in err_types if issubclass(k[1], e)] + # drop base class, to be replaced by interface type. Find the class + # programmatically, in case the base class name changes. + for i in range(len(err_types)): + if err_types[i][1] in getmro(err_types[i-1][1]): + del err_types[i] + break + err_names = [k[0] for k in err_types] + error_data = {'error_names': err_names} + j2_template = Template(error_template) + res = j2_template.render(error_data) + + return res + +def generate_op_mode_definitions(): + os.makedirs(CLIENT_OP_PATH, exist_ok=True) + + out = create_error_schema() + with open(f'{SCHEMA_PATH}/{op_mode_error_schema}', 'w') as f: + f.write(out) + + with open(op_mode_include_file) as f: + op_mode_files = json.load(f) + + for file in op_mode_files: + basename = os.path.splitext(file)[0].replace('-', '_') + module = load_as_module(basename, os.path.join(OP_MODE_PATH, file)) + + funcs = getmembers(module, isfunction) + funcs = list(filter(lambda ft: is_op_mode_function_name(ft[0]), funcs)) + + funcs_dict = {} + for (name, thunk) in funcs: + funcs_dict[name] = thunk + + schema = [] + client_op = [] + enums = {} # gather enums from function Literal type args + for name,func in funcs_dict.items(): + res = create_schema(name, basename, func, enums) + schema.append(res) + res = create_client_op(name, basename, func, enums) + client_op.append(res) + + out = create_enums(enums) + out += '\n'.join(schema) + with open(f'{SCHEMA_PATH}/{basename}.graphql', 'w') as f: + f.write(out) + + out = '\n'.join(client_op) + with open(f'{CLIENT_OP_PATH}/{basename}.graphql', 'w') as f: + f.write(out) + +if __name__ == '__main__': + generate_op_mode_definitions() diff --git a/src/services/api/graphql/graphql/__init__.py b/src/services/api/graphql/graphql/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/services/api/graphql/graphql/__init__.py diff --git a/src/services/api/graphql/graphql/auth_token_mutation.py b/src/services/api/graphql/graphql/auth_token_mutation.py new file mode 100644 index 0000000..a53fa4d --- /dev/null +++ b/src/services/api/graphql/graphql/auth_token_mutation.py @@ -0,0 +1,61 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +import datetime +from typing import Any +from typing import Dict +from ariadne import ObjectType +from graphql import GraphQLResolveInfo + +from .. libs.token_auth import generate_token +from .. session.session import get_user_info +from .. import state + +auth_token_mutation = ObjectType("Mutation") + +@auth_token_mutation.field('AuthToken') +def auth_token_resolver(obj: Any, info: GraphQLResolveInfo, data: Dict): + # non-nullable fields + user = data['username'] + passwd = data['password'] + + secret = state.settings['secret'] + exp_interval = int(state.settings['app'].state.vyos_token_exp) + expiration = (datetime.datetime.now(tz=datetime.timezone.utc) + + datetime.timedelta(seconds=exp_interval)) + + res = generate_token(user, passwd, secret, expiration) + try: + res |= get_user_info(user) + except ValueError: + # non-existent user already caught + pass + if 'token' in res: + data['result'] = res + return { + "success": True, + "data": data + } + + if 'errors' in res: + return { + "success": False, + "errors": res['errors'] + } + + return { + "success": False, + "errors": ['token generation failed'] + } diff --git a/src/services/api/graphql/graphql/client_op/auth_token.graphql b/src/services/api/graphql/graphql/client_op/auth_token.graphql new file mode 100644 index 0000000..5ea2ecc --- /dev/null +++ b/src/services/api/graphql/graphql/client_op/auth_token.graphql @@ -0,0 +1,10 @@ + +mutation AuthToken ($username: String!, $password: String!) { + AuthToken (data: { username: $username, password: $password }) { + success + errors + data { + result + } + } +} diff --git a/src/services/api/graphql/graphql/directives.py b/src/services/api/graphql/graphql/directives.py new file mode 100644 index 0000000..3927aee --- /dev/null +++ b/src/services/api/graphql/graphql/directives.py @@ -0,0 +1,87 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from ariadne import SchemaDirectiveVisitor +from . queries import * +from . mutations import * + +def non(arg): + pass + +class VyosDirective(SchemaDirectiveVisitor): + def visit_field_definition(self, field, object_type, make_resolver=non): + name = f'{field.type}' + # field.type contains the return value of the mutation; trim value + # to produce canonical name + name = name.replace('Result', '', 1) + + func = make_resolver(name) + field.resolve = func + return field + +class ConfigSessionQueryDirective(VyosDirective): + """ + Class providing implementation of 'configsessionquery' directive in schema. + """ + def visit_field_definition(self, field, object_type): + super().visit_field_definition(field, object_type, + make_resolver=make_config_session_query_resolver) + +class ConfigSessionMutationDirective(VyosDirective): + """ + Class providing implementation of 'configsessionmutation' directive in schema. + """ + def visit_field_definition(self, field, object_type): + super().visit_field_definition(field, object_type, + make_resolver=make_config_session_mutation_resolver) + +class GenOpQueryDirective(VyosDirective): + """ + Class providing implementation of 'genopquery' directive in schema. + """ + def visit_field_definition(self, field, object_type): + super().visit_field_definition(field, object_type, + make_resolver=make_gen_op_query_resolver) + +class GenOpMutationDirective(VyosDirective): + """ + Class providing implementation of 'genopmutation' directive in schema. + """ + def visit_field_definition(self, field, object_type): + super().visit_field_definition(field, object_type, + make_resolver=make_gen_op_mutation_resolver) + +class CompositeQueryDirective(VyosDirective): + """ + Class providing implementation of 'system_status' directive in schema. + """ + def visit_field_definition(self, field, object_type): + super().visit_field_definition(field, object_type, + make_resolver=make_composite_query_resolver) + +class CompositeMutationDirective(VyosDirective): + """ + Class providing implementation of 'system_status' directive in schema. + """ + def visit_field_definition(self, field, object_type): + super().visit_field_definition(field, object_type, + make_resolver=make_composite_mutation_resolver) + +directives_dict = {"configsessionquery": ConfigSessionQueryDirective, + "configsessionmutation": ConfigSessionMutationDirective, + "genopquery": GenOpQueryDirective, + "genopmutation": GenOpMutationDirective, + "compositequery": CompositeQueryDirective, + "compositemutation": CompositeMutationDirective} diff --git a/src/services/api/graphql/graphql/errors.py b/src/services/api/graphql/graphql/errors.py new file mode 100644 index 0000000..1066300 --- /dev/null +++ b/src/services/api/graphql/graphql/errors.py @@ -0,0 +1,8 @@ + +from ariadne import InterfaceType + +op_mode_error = InterfaceType("OpModeError") + +@op_mode_error.type_resolver +def resolve_op_mode_error(obj, *_): + return obj['name'] diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py new file mode 100644 index 0000000..d115a8e --- /dev/null +++ b/src/services/api/graphql/graphql/mutations.py @@ -0,0 +1,139 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from importlib import import_module +from ariadne import ObjectType, convert_camel_case_to_snake +from makefun import with_signature + +# used below by func_sig +from typing import Any, Dict, Optional # pylint: disable=W0611 +from graphql import GraphQLResolveInfo # pylint: disable=W0611 + +from .. import state +from .. libs import key_auth +from api.graphql.session.session import Session +from api.graphql.session.errors.op_mode_errors import op_mode_err_msg, op_mode_err_code +from vyos.opmode import Error as OpModeError + +mutation = ObjectType("Mutation") + +def make_mutation_resolver(mutation_name, class_name, session_func): + """Dynamically generate a resolver for the mutation named in the + schema by 'mutation_name'. + + Dynamic generation is provided using the package 'makefun' (via the + decorator 'with_signature'), which provides signature-preserving + function wrappers; it provides several improvements over, say, + functools.wraps. + + :raise Exception: + raising ConfigErrors, or internal errors + """ + + func_base_name = convert_camel_case_to_snake(class_name) + resolver_name = f'resolve_{func_base_name}' + func_sig = '(obj: Any, info: GraphQLResolveInfo, data: Optional[Dict]=None)' + + @mutation.field(mutation_name) + @with_signature(func_sig, func_name=resolver_name) + async def func_impl(*args, **kwargs): + try: + auth_type = state.settings['app'].state.vyos_auth_type + + if auth_type == 'key': + data = kwargs['data'] + key = data['key'] + + auth = key_auth.auth_required(key) + if auth is None: + return { + "success": False, + "errors": ['invalid API key'] + } + + # We are finished with the 'key' entry, and may remove so as to + # pass the rest of data (if any) to function. + del data['key'] + + elif auth_type == 'token': + data = kwargs['data'] + if data is None: + data = {} + info = kwargs['info'] + user = info.context.get('user') + if user is None: + error = info.context.get('error') + if error is not None: + return { + "success": False, + "errors": [error] + } + return { + "success": False, + "errors": ['not authenticated'] + } + else: + # AtrributeError will have already been raised if no + # vyos_auth_type; validation and defaultValue ensure it is + # one of the previous cases, so this is never reached. + pass + + session = state.settings['app'].state.vyos_session + + # one may override the session functions with a local subclass + try: + mod = import_module(f'api.graphql.session.override.{func_base_name}') + klass = getattr(mod, class_name) + except ImportError: + # otherwise, dynamically generate subclass to invoke subclass + # name based functions + klass = type(class_name, (Session,), {}) + k = klass(session, data) + method = getattr(k, session_func) + result = method() + data['result'] = result + + return { + "success": True, + "data": data + } + except OpModeError as e: + typename = type(e).__name__ + msg = str(e) + return { + "success": False, + "errore": ['op_mode_error'], + "op_mode_error": {"name": f"{typename}", + "message": msg if msg else op_mode_err_msg.get(typename, "Unknown"), + "vyos_code": op_mode_err_code.get(typename, 9999)} + } + except Exception as error: + return { + "success": False, + "errors": [repr(error)] + } + + return func_impl + +def make_config_session_mutation_resolver(mutation_name): + return make_mutation_resolver(mutation_name, mutation_name, + convert_camel_case_to_snake(mutation_name)) + +def make_gen_op_mutation_resolver(mutation_name): + return make_mutation_resolver(mutation_name, mutation_name, 'gen_op_mutation') + +def make_composite_mutation_resolver(mutation_name): + return make_mutation_resolver(mutation_name, mutation_name, + convert_camel_case_to_snake(mutation_name)) diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py new file mode 100644 index 0000000..7170982 --- /dev/null +++ b/src/services/api/graphql/graphql/queries.py @@ -0,0 +1,139 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from importlib import import_module +from ariadne import ObjectType, convert_camel_case_to_snake +from makefun import with_signature + +# used below by func_sig +from typing import Any, Dict, Optional # pylint: disable=W0611 +from graphql import GraphQLResolveInfo # pylint: disable=W0611 + +from .. import state +from .. libs import key_auth +from api.graphql.session.session import Session +from api.graphql.session.errors.op_mode_errors import op_mode_err_msg, op_mode_err_code +from vyos.opmode import Error as OpModeError + +query = ObjectType("Query") + +def make_query_resolver(query_name, class_name, session_func): + """Dynamically generate a resolver for the query named in the + schema by 'query_name'. + + Dynamic generation is provided using the package 'makefun' (via the + decorator 'with_signature'), which provides signature-preserving + function wrappers; it provides several improvements over, say, + functools.wraps. + + :raise Exception: + raising ConfigErrors, or internal errors + """ + + func_base_name = convert_camel_case_to_snake(class_name) + resolver_name = f'resolve_{func_base_name}' + func_sig = '(obj: Any, info: GraphQLResolveInfo, data: Optional[Dict]=None)' + + @query.field(query_name) + @with_signature(func_sig, func_name=resolver_name) + async def func_impl(*args, **kwargs): + try: + auth_type = state.settings['app'].state.vyos_auth_type + + if auth_type == 'key': + data = kwargs['data'] + key = data['key'] + + auth = key_auth.auth_required(key) + if auth is None: + return { + "success": False, + "errors": ['invalid API key'] + } + + # We are finished with the 'key' entry, and may remove so as to + # pass the rest of data (if any) to function. + del data['key'] + + elif auth_type == 'token': + data = kwargs['data'] + if data is None: + data = {} + info = kwargs['info'] + user = info.context.get('user') + if user is None: + error = info.context.get('error') + if error is not None: + return { + "success": False, + "errors": [error] + } + return { + "success": False, + "errors": ['not authenticated'] + } + else: + # AtrributeError will have already been raised if no + # vyos_auth_type; validation and defaultValue ensure it is + # one of the previous cases, so this is never reached. + pass + + session = state.settings['app'].state.vyos_session + + # one may override the session functions with a local subclass + try: + mod = import_module(f'api.graphql.session.override.{func_base_name}') + klass = getattr(mod, class_name) + except ImportError: + # otherwise, dynamically generate subclass to invoke subclass + # name based functions + klass = type(class_name, (Session,), {}) + k = klass(session, data) + method = getattr(k, session_func) + result = method() + data['result'] = result + + return { + "success": True, + "data": data + } + except OpModeError as e: + typename = type(e).__name__ + msg = str(e) + return { + "success": False, + "errors": ['op_mode_error'], + "op_mode_error": {"name": f"{typename}", + "message": msg if msg else op_mode_err_msg.get(typename, "Unknown"), + "vyos_code": op_mode_err_code.get(typename, 9999)} + } + except Exception as error: + return { + "success": False, + "errors": [repr(error)] + } + + return func_impl + +def make_config_session_query_resolver(query_name): + return make_query_resolver(query_name, query_name, + convert_camel_case_to_snake(query_name)) + +def make_gen_op_query_resolver(query_name): + return make_query_resolver(query_name, query_name, 'gen_op_query') + +def make_composite_query_resolver(query_name): + return make_query_resolver(query_name, query_name, + convert_camel_case_to_snake(query_name)) diff --git a/src/services/api/graphql/graphql/schema/auth_token.graphql b/src/services/api/graphql/graphql/schema/auth_token.graphql new file mode 100644 index 0000000..af53a29 --- /dev/null +++ b/src/services/api/graphql/graphql/schema/auth_token.graphql @@ -0,0 +1,19 @@ + +input AuthTokenInput { + username: String! + password: String! +} + +type AuthToken { + result: Generic +} + +type AuthTokenResult { + data: AuthToken + success: Boolean! + errors: [String] +} + +extend type Mutation { + AuthToken(data: AuthTokenInput) : AuthTokenResult +} diff --git a/src/services/api/graphql/graphql/schema/schema.graphql b/src/services/api/graphql/graphql/schema/schema.graphql new file mode 100644 index 0000000..62b0d30 --- /dev/null +++ b/src/services/api/graphql/graphql/schema/schema.graphql @@ -0,0 +1,16 @@ +schema { + query: Query + mutation: Mutation +} + +directive @compositequery on FIELD_DEFINITION +directive @compositemutation on FIELD_DEFINITION +directive @configsessionquery on FIELD_DEFINITION +directive @configsessionmutation on FIELD_DEFINITION +directive @genopquery on FIELD_DEFINITION +directive @genopmutation on FIELD_DEFINITION + +scalar Generic + +type Query +type Mutation diff --git a/src/services/api/graphql/libs/key_auth.py b/src/services/api/graphql/libs/key_auth.py new file mode 100644 index 0000000..2db0f7d --- /dev/null +++ b/src/services/api/graphql/libs/key_auth.py @@ -0,0 +1,18 @@ + +from .. import state + +def check_auth(key_list, key): + if not key_list: + return None + key_id = None + for k in key_list: + if k['key'] == key: + key_id = k['id'] + return key_id + +def auth_required(key): + api_keys = None + api_keys = state.settings['app'].state.vyos_keys + key_id = check_auth(api_keys, key) + state.settings['app'].state.vyos_id = key_id + return key_id diff --git a/src/services/api/graphql/libs/op_mode.py b/src/services/api/graphql/libs/op_mode.py new file mode 100644 index 0000000..86e38ea --- /dev/null +++ b/src/services/api/graphql/libs/op_mode.py @@ -0,0 +1,103 @@ +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +import re +import typing + +from typing import Union +from typing import Optional +from humps import decamelize + +from vyos.defaults import directories +from vyos.utils.system import load_as_module +from vyos.opmode import _normalize_field_names +from vyos.opmode import _is_literal_type, _get_literal_values + +def load_op_mode_as_module(name: str): + path = os.path.join(directories['op_mode'], name) + name = os.path.splitext(name)[0].replace('-', '_') + return load_as_module(name, path) + +def is_show_function_name(name): + if re.match(r"^show", name): + return True + return False + +def _nth_split(delim: str, n: int, s: str): + groups = s.split(delim) + l = len(groups) + if n > l-1 or n < 1: + return (s, '') + return (delim.join(groups[:n]), delim.join(groups[n:])) + +def _nth_rsplit(delim: str, n: int, s: str): + groups = s.split(delim) + l = len(groups) + if n > l-1 or n < 1: + return (s, '') + return (delim.join(groups[:l-n]), delim.join(groups[l-n:])) + +# Since we have mangled possible hyphens in the file name while constructing +# the snake case of the query/mutation name, we will need to recover the +# file name by searching with mangling: +def _filter_on_mangled(test): + def func(elem): + mangle = os.path.splitext(elem)[0].replace('-', '_') + return test == mangle + return func + +# Find longest name in concatenated string that matches the basename of an +# op-mode script. Should one prefer to concatenate in the reverse order +# (script_name + '_' + function_name), use _nth_rsplit. +def split_compound_op_mode_name(name: str, files: list): + for i in range(1, name.count('_') + 1): + pair = _nth_split('_', i, name) + f = list(filter(_filter_on_mangled(pair[1]), files)) + if f: + pair = (pair[0], f[0]) + return pair + return (name, '') + +def snake_to_pascal_case(name: str) -> str: + res = ''.join(map(str.title, name.split('_'))) + return res + +def map_type_name(type_name: type, enums: Optional[dict] = None, optional: bool = False) -> str: + if type_name == str: + return 'String!' if not optional else 'String = null' + if type_name == int: + return 'Int!' if not optional else 'Int = null' + if type_name == bool: + return 'Boolean = false' + if typing.get_origin(type_name) == list: + if not optional: + return f'[{map_type_name(typing.get_args(type_name)[0], enums=enums)}]!' + return f'[{map_type_name(typing.get_args(type_name)[0], enums=enums)}]' + if _is_literal_type(type_name): + mapped = enums.get(_get_literal_values(type_name), '') + if not mapped: + raise ValueError(typing.get_args(type_name)) + return f'{mapped}!' if not optional else mapped + # typing.Optional is typing.Union[_, NoneType] + if (typing.get_origin(type_name) is typing.Union and + typing.get_args(type_name)[1] == type(None)): + return f'{map_type_name(typing.get_args(type_name)[0], enums=enums, optional=True)}' + + # scalar 'Generic' is defined in schema.graphql + return 'Generic' + +def normalize_output(result: Union[dict, list]) -> Union[dict, list]: + return _normalize_field_names(decamelize(result)) diff --git a/src/services/api/graphql/libs/token_auth.py b/src/services/api/graphql/libs/token_auth.py new file mode 100644 index 0000000..8585485 --- /dev/null +++ b/src/services/api/graphql/libs/token_auth.py @@ -0,0 +1,70 @@ +import jwt +import uuid +import pam +from secrets import token_hex + +from .. import state + +def _check_passwd_pam(username: str, passwd: str) -> bool: + if pam.authenticate(username, passwd): + return True + return False + +def init_secret(): + length = int(state.settings['app'].state.vyos_secret_len) + secret = token_hex(length) + state.settings['secret'] = secret + +def generate_token(user: str, passwd: str, secret: str, exp: int) -> dict: + if user is None or passwd is None: + return {} + if _check_passwd_pam(user, passwd): + app = state.settings['app'] + try: + users = app.state.vyos_token_users + except AttributeError: + app.state.vyos_token_users = {} + users = app.state.vyos_token_users + user_id = uuid.uuid1().hex + payload_data = {'iss': user, 'sub': user_id, 'exp': exp} + secret = state.settings.get('secret') + if secret is None: + return {"errors": ['missing secret']} + token = jwt.encode(payload=payload_data, key=secret, algorithm="HS256") + + users |= {user_id: user} + return {'token': token} + else: + return {"errors": ['failed pam authentication']} + +def get_user_context(request): + context = {} + context['request'] = request + context['user'] = None + if 'Authorization' in request.headers: + auth = request.headers['Authorization'] + scheme, token = auth.split() + if scheme.lower() != 'bearer': + return context + + try: + secret = state.settings.get('secret') + payload = jwt.decode(token, secret, algorithms=["HS256"]) + user_id: str = payload.get('sub') + if user_id is None: + return context + except jwt.exceptions.ExpiredSignatureError: + context['error'] = 'expired token' + return context + except jwt.PyJWTError: + return context + try: + users = state.settings['app'].state.vyos_token_users + except AttributeError: + return context + + user = users.get(user_id) + if user is not None: + context['user'] = user + + return context diff --git a/src/services/api/graphql/session/__init__.py b/src/services/api/graphql/session/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/services/api/graphql/session/__init__.py diff --git a/src/services/api/graphql/session/composite/system_status.py b/src/services/api/graphql/session/composite/system_status.py new file mode 100644 index 0000000..516a4ef --- /dev/null +++ b/src/services/api/graphql/session/composite/system_status.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from api.graphql.libs.op_mode import load_op_mode_as_module + +def get_system_version() -> dict: + show_version = load_op_mode_as_module('version.py') + return show_version.show(raw=True, funny=False) + +def get_system_uptime() -> dict: + show_uptime = load_op_mode_as_module('uptime.py') + return show_uptime._get_raw_data() + +def get_system_ram_usage() -> dict: + show_ram = load_op_mode_as_module('memory.py') + return show_ram.show(raw=True) diff --git a/src/services/api/graphql/session/errors/op_mode_errors.py b/src/services/api/graphql/session/errors/op_mode_errors.py new file mode 100644 index 0000000..8007672 --- /dev/null +++ b/src/services/api/graphql/session/errors/op_mode_errors.py @@ -0,0 +1,19 @@ +op_mode_err_msg = { + "UnconfiguredSubsystem": "subsystem is not configured or not running", + "UnconfiguredObject": "object does not exist in the system configuration", + "DataUnavailable": "data currently unavailable", + "PermissionDenied": "client does not have permission", + "InsufficientResources": "insufficient system resources", + "IncorrectValue": "argument value is incorrect", + "UnsupportedOperation": "operation is not supported (yet)", +} + +op_mode_err_code = { + "UnconfiguredSubsystem": 2000, + "UnconfiguredObject": 2003, + "DataUnavailable": 2001, + "InsufficientResources": 2002, + "PermissionDenied": 1003, + "IncorrectValue": 1002, + "UnsupportedOperation": 1004, +} diff --git a/src/services/api/graphql/session/override/remove_firewall_address_group_members.py b/src/services/api/graphql/session/override/remove_firewall_address_group_members.py new file mode 100644 index 0000000..b91932e --- /dev/null +++ b/src/services/api/graphql/session/override/remove_firewall_address_group_members.py @@ -0,0 +1,35 @@ +# Copyright 2021 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from . session import Session + +class RemoveFirewallAddressGroupMembers(Session): + def __init__(self, session, data): + super().__init__(session, data) + + # Define any custom processing of parameters here by overriding + # configure: + # + # def configure(self): + # self._data = transform_data(self._data) + # super().configure() + # self.clean_up() + + def configure(self): + super().configure() + + group_name = self._data['name'] + path = ['firewall', 'group', 'address-group', group_name] + self.delete_path_if_childless(path) diff --git a/src/services/api/graphql/session/session.py b/src/services/api/graphql/session/session.py new file mode 100644 index 0000000..6ae44b9 --- /dev/null +++ b/src/services/api/graphql/session/session.py @@ -0,0 +1,211 @@ +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +import json + +from ariadne import convert_camel_case_to_snake + +from vyos.config import Config +from vyos.configtree import ConfigTree +from vyos.defaults import directories +from vyos.opmode import Error as OpModeError + +from api.graphql.libs.op_mode import load_op_mode_as_module, split_compound_op_mode_name +from api.graphql.libs.op_mode import normalize_output + +op_mode_include_file = os.path.join(directories['data'], 'op-mode-standardized.json') + +def get_config_dict(path=[], effective=False, key_mangling=None, + get_first_key=False, no_multi_convert=False, + no_tag_node_value_mangle=False): + config = Config() + return config.get_config_dict(path=path, effective=effective, + key_mangling=key_mangling, + get_first_key=get_first_key, + no_multi_convert=no_multi_convert, + no_tag_node_value_mangle=no_tag_node_value_mangle) + +def get_user_info(user): + user_info = {} + info = get_config_dict(['system', 'login', 'user', user], + get_first_key=True) + if not info: + raise ValueError("No such user") + + user_info['user'] = user + user_info['full_name'] = info.get('full-name', '') + + return user_info + +class Session: + """ + Wrapper for calling configsession functions based on GraphQL requests. + Non-nullable fields in the respective schema allow avoiding a key check + in 'data'. + """ + def __init__(self, session, data): + self._session = session + self._data = data + self._name = convert_camel_case_to_snake(type(self).__name__) + + try: + with open(op_mode_include_file) as f: + self._op_mode_list = json.loads(f.read()) + except Exception: + self._op_mode_list = None + + def show_config(self): + session = self._session + data = self._data + out = '' + + try: + out = session.show_config(data['path']) + if data.get('config_format', '') == 'json': + config_tree = ConfigTree(out) + out = json.loads(config_tree.to_json()) + except Exception as error: + raise error + + return out + + def save_config_file(self): + session = self._session + data = self._data + if 'file_name' not in data or not data['file_name']: + data['file_name'] = '/config/config.boot' + + try: + session.save_config(data['file_name']) + except Exception as error: + raise error + + def load_config_file(self): + session = self._session + data = self._data + + try: + session.load_config(data['file_name']) + session.commit() + except Exception as error: + raise error + + def show(self): + session = self._session + data = self._data + out = '' + + try: + out = session.show(data['path']) + except Exception as error: + raise error + + return out + + def add_system_image(self): + session = self._session + data = self._data + + try: + res = session.install_image(data['location']) + except Exception as error: + raise error + + return res + + def delete_system_image(self): + session = self._session + data = self._data + + try: + res = session.remove_image(data['name']) + except Exception as error: + raise error + + return res + + def show_user_info(self): + session = self._session + data = self._data + + user_info = {} + user = data['user'] + try: + user_info = get_user_info(user) + except Exception as error: + raise error + + return user_info + + def system_status(self): + import api.graphql.session.composite.system_status as system_status + + session = self._session + data = self._data + + status = {} + status['host_name'] = session.show(['host', 'name']).strip() + status['version'] = system_status.get_system_version() + status['uptime'] = system_status.get_system_uptime() + status['ram'] = system_status.get_system_ram_usage() + + return status + + def gen_op_query(self): + session = self._session + data = self._data + name = self._name + op_mode_list = self._op_mode_list + + # handle the case that the op-mode file contains underscores: + if op_mode_list is None: + raise FileNotFoundError(f"No op-mode file list at '{op_mode_include_file}'") + (func_name, scriptname) = split_compound_op_mode_name(name, op_mode_list) + if scriptname == '': + raise FileNotFoundError(f"No op-mode file named in string '{name}'") + + mod = load_op_mode_as_module(f'{scriptname}') + func = getattr(mod, func_name) + try: + res = func(True, **data) + except OpModeError as e: + raise e + + res = normalize_output(res) + + return res + + def gen_op_mutation(self): + session = self._session + data = self._data + name = self._name + op_mode_list = self._op_mode_list + + # handle the case that the op-mode file name contains underscores: + if op_mode_list is None: + raise FileNotFoundError(f"No op-mode file list at '{op_mode_include_file}'") + (func_name, scriptname) = split_compound_op_mode_name(name, op_mode_list) + if scriptname == '': + raise FileNotFoundError(f"No op-mode file named in string '{name}'") + + mod = load_op_mode_as_module(f'{scriptname}') + func = getattr(mod, func_name) + try: + res = func(**data) + except OpModeError as e: + raise e + + return res diff --git a/src/services/api/graphql/session/templates/create_dhcp_server.tmpl b/src/services/api/graphql/session/templates/create_dhcp_server.tmpl new file mode 100644 index 0000000..70de431 --- /dev/null +++ b/src/services/api/graphql/session/templates/create_dhcp_server.tmpl @@ -0,0 +1,9 @@ +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} default-router {{ default_router }} +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} name-server {{ name_server }} +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} domain-name {{ domain_name }} +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} lease {{ lease }} +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} range {{ range }} start {{ start }} +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} range {{ range }} stop {{ stop }} +set service dns forwarding allow-from {{ dns_forwarding_allow_from }} +set service dns forwarding cache-size {{ dns_forwarding_cache_size }} +set service dns forwarding listen-address {{ dns_forwarding_listen_address }} diff --git a/src/services/api/graphql/session/templates/create_firewall_address_group.tmpl b/src/services/api/graphql/session/templates/create_firewall_address_group.tmpl new file mode 100644 index 0000000..a890d00 --- /dev/null +++ b/src/services/api/graphql/session/templates/create_firewall_address_group.tmpl @@ -0,0 +1,4 @@ +set firewall group address-group {{ name }} +{% for add in address %} +set firewall group address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/session/templates/create_firewall_address_ipv_6_group.tmpl b/src/services/api/graphql/session/templates/create_firewall_address_ipv_6_group.tmpl new file mode 100644 index 0000000..e9b6607 --- /dev/null +++ b/src/services/api/graphql/session/templates/create_firewall_address_ipv_6_group.tmpl @@ -0,0 +1,4 @@ +set firewall group ipv6-address-group {{ name }} +{% for add in address %} +set firewall group ipv6-address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/session/templates/create_interface_ethernet.tmpl b/src/services/api/graphql/session/templates/create_interface_ethernet.tmpl new file mode 100644 index 0000000..d9d7ed6 --- /dev/null +++ b/src/services/api/graphql/session/templates/create_interface_ethernet.tmpl @@ -0,0 +1,5 @@ +{% if replace %} +delete interfaces ethernet {{ interface }} address +{% endif %} +set interfaces ethernet {{ interface }} address {{ address }} +set interfaces ethernet {{ interface }} description {{ description }} diff --git a/src/services/api/graphql/session/templates/remove_firewall_address_group_members.tmpl b/src/services/api/graphql/session/templates/remove_firewall_address_group_members.tmpl new file mode 100644 index 0000000..458f3e5 --- /dev/null +++ b/src/services/api/graphql/session/templates/remove_firewall_address_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +delete firewall group address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/session/templates/remove_firewall_address_ipv_6_group_members.tmpl b/src/services/api/graphql/session/templates/remove_firewall_address_ipv_6_group_members.tmpl new file mode 100644 index 0000000..0efa0b2 --- /dev/null +++ b/src/services/api/graphql/session/templates/remove_firewall_address_ipv_6_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +delete firewall group ipv6-address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/session/templates/update_firewall_address_group_members.tmpl b/src/services/api/graphql/session/templates/update_firewall_address_group_members.tmpl new file mode 100644 index 0000000..f56c612 --- /dev/null +++ b/src/services/api/graphql/session/templates/update_firewall_address_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +set firewall group address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/session/templates/update_firewall_address_ipv_6_group_members.tmpl b/src/services/api/graphql/session/templates/update_firewall_address_ipv_6_group_members.tmpl new file mode 100644 index 0000000..f98a551 --- /dev/null +++ b/src/services/api/graphql/session/templates/update_firewall_address_ipv_6_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +set firewall group ipv6-address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/state.py b/src/services/api/graphql/state.py new file mode 100644 index 0000000..63db9f4 --- /dev/null +++ b/src/services/api/graphql/state.py @@ -0,0 +1,4 @@ + +def init(): + global settings + settings = {} diff --git a/src/services/vyos-configd b/src/services/vyos-configd new file mode 100644 index 0000000..cb23642 --- /dev/null +++ b/src/services/vyos-configd @@ -0,0 +1,340 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# pylint: disable=redefined-outer-name + +import os +import sys +import grp +import re +import json +import typing +import logging +import signal +import traceback +import importlib.util +import io +from contextlib import redirect_stdout + +import zmq + +from vyos.defaults import directories +from vyos.utils.boot import boot_configuration_complete +from vyos.configsource import ConfigSourceString +from vyos.configsource import ConfigSourceError +from vyos.configdiff import get_commit_scripts +from vyos.config import Config +from vyos import ConfigError + +CFG_GROUP = 'vyattacfg' + +script_stdout_log = '/tmp/vyos-configd-script-stdout' + +debug = True + +logger = logging.getLogger(__name__) +logs_handler = logging.StreamHandler() +logger.addHandler(logs_handler) + +if debug: + logger.setLevel(logging.DEBUG) +else: + logger.setLevel(logging.INFO) + +SOCKET_PATH = 'ipc:///run/vyos-configd.sock' +MAX_MSG_SIZE = 65535 + +# Response error codes +R_SUCCESS = 1 +R_ERROR_COMMIT = 2 +R_ERROR_DAEMON = 4 +R_PASS = 8 + +vyos_conf_scripts_dir = directories['conf_mode'] +configd_include_file = os.path.join(directories['data'], 'configd-include.json') +configd_env_set_file = os.path.join(directories['data'], 'vyos-configd-env-set') +configd_env_unset_file = os.path.join(directories['data'], 'vyos-configd-env-unset') +# sourced on entering config session +configd_env_file = '/etc/default/vyos-configd-env' + +def key_name_from_file_name(f): + return os.path.splitext(f)[0] + +def module_name_from_key(k): + return k.replace('-', '_') + +def path_from_file_name(f): + return os.path.join(vyos_conf_scripts_dir, f) + + +# opt-in to be run by daemon +with open(configd_include_file) as f: + try: + include = json.load(f) + except OSError as e: + logger.critical(f'configd include file error: {e}') + sys.exit(1) + except json.JSONDecodeError as e: + logger.critical(f'JSON load error: {e}') + sys.exit(1) + + +# import conf_mode scripts +(_, _, filenames) = next(iter(os.walk(vyos_conf_scripts_dir))) +filenames.sort() + +load_filenames = [f for f in filenames if f in include] +imports = [key_name_from_file_name(f) for f in load_filenames] +module_names = [module_name_from_key(k) for k in imports] +paths = [path_from_file_name(f) for f in load_filenames] +to_load = list(zip(module_names, paths)) + +modules = [] + +for x in to_load: + spec = importlib.util.spec_from_file_location(x[0], x[1]) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + modules.append(module) + +conf_mode_scripts = dict(zip(imports, modules)) + +exclude_set = {key_name_from_file_name(f) for f in filenames if f not in include} +include_set = {key_name_from_file_name(f) for f in filenames if f in include} + + +def write_stdout_log(file_name, msg): + if boot_configuration_complete(): + return + with open(file_name, 'a') as f: + f.write(msg) + + +def run_script(script_name, config, args) -> tuple[int, str]: + # pylint: disable=broad-exception-caught + + script = conf_mode_scripts[script_name] + script.argv = args + config.set_level([]) + try: + c = script.get_config(config) + script.verify(c) + script.generate(c) + script.apply(c) + except ConfigError as e: + logger.error(e) + return R_ERROR_COMMIT, str(e) + except Exception: + tb = traceback.format_exc() + logger.error(tb) + return R_ERROR_COMMIT, tb + + return R_SUCCESS, '' + + +def initialization(socket): + # pylint: disable=broad-exception-caught,too-many-locals + + # Reset config strings: + active_string = '' + session_string = '' + # check first for resent init msg, in case of client timeout + while True: + msg = socket.recv().decode('utf-8', 'ignore') + try: + message = json.loads(msg) + if message['type'] == 'init': + resp = 'init' + socket.send(resp.encode()) + except Exception: + break + + # zmq synchronous for ipc from single client: + active_string = msg + resp = 'active' + socket.send(resp.encode()) + session_string = socket.recv().decode('utf-8', 'ignore') + resp = 'session' + socket.send(resp.encode()) + pid_string = socket.recv().decode('utf-8', 'ignore') + resp = 'pid' + socket.send(resp.encode()) + sudo_user_string = socket.recv().decode('utf-8', 'ignore') + resp = 'sudo_user' + socket.send(resp.encode()) + temp_config_dir_string = socket.recv().decode('utf-8', 'ignore') + resp = 'temp_config_dir' + socket.send(resp.encode()) + changes_only_dir_string = socket.recv().decode('utf-8', 'ignore') + resp = 'changes_only_dir' + socket.send(resp.encode()) + + logger.debug(f'config session pid is {pid_string}') + logger.debug(f'config session sudo_user is {sudo_user_string}') + + os.environ['SUDO_USER'] = sudo_user_string + if temp_config_dir_string: + os.environ['VYATTA_TEMP_CONFIG_DIR'] = temp_config_dir_string + if changes_only_dir_string: + os.environ['VYATTA_CHANGES_ONLY_DIR'] = changes_only_dir_string + + try: + configsource = ConfigSourceString(running_config_text=active_string, + session_config_text=session_string) + except ConfigSourceError as e: + logger.debug(e) + return None + + config = Config(config_source=configsource) + dependent_func: dict[str, list[typing.Callable]] = {} + setattr(config, 'dependent_func', dependent_func) + + commit_scripts = get_commit_scripts(config) + logger.debug(f'commit_scripts: {commit_scripts}') + + scripts_called = [] + setattr(config, 'scripts_called', scripts_called) + + return config + + +def process_node_data(config, data, _last: bool = False) -> tuple[int, str]: + if not config: + out = 'Empty config' + logger.critical(out) + return R_ERROR_DAEMON, out + + script_name = None + os.environ['VYOS_TAGNODE_VALUE'] = '' + args = [] + config.dependency_list.clear() + + res = re.match(r'^(VYOS_TAGNODE_VALUE=[^/]+)?.*\/([^/]+).py(.*)', data) + if res.group(1): + env = res.group(1).split('=') + os.environ[env[0]] = env[1] + if res.group(2): + script_name = res.group(2) + if not script_name: + out = 'Missing script_name' + logger.critical(out) + return R_ERROR_DAEMON, out + if res.group(3): + args = res.group(3).split() + args.insert(0, f'{script_name}.py') + + tag_value = os.getenv('VYOS_TAGNODE_VALUE', '') + tag_ext = f'_{tag_value}' if tag_value else '' + script_record = f'{script_name}{tag_ext}' + scripts_called = getattr(config, 'scripts_called', []) + scripts_called.append(script_record) + + if script_name not in include_set: + return R_PASS, '' + + with redirect_stdout(io.StringIO()) as o: + result, err_out = run_script(script_name, config, args) + amb_out = o.getvalue() + o.close() + + out = amb_out + err_out + + return result, out + + +def send_result(sock, err, msg): + msg_size = min(MAX_MSG_SIZE, len(msg)) if msg else 0 + + err_rep = err.to_bytes(1, byteorder=sys.byteorder) + logger.debug(f'Sending reply: {err}') + sock.send(err_rep) + + # size req from vyshim client + size_req = sock.recv().decode() + logger.debug(f'Received request: {size_req}') + msg_size_rep = hex(msg_size).encode() + sock.send(msg_size_rep) + logger.debug(f'Sending reply: {msg_size}') + + if msg_size > 0: + # send req is sent from vyshim client only if msg_size > 0 + send_req = sock.recv().decode() + logger.debug(f'Received request: {send_req}') + sock.send(msg.encode()) + logger.debug('Sending reply with output') + + write_stdout_log(script_stdout_log, msg) + + +def remove_if_file(f: str): + try: + os.remove(f) + except FileNotFoundError: + pass + + +def shutdown(): + remove_if_file(configd_env_file) + os.symlink(configd_env_unset_file, configd_env_file) + sys.exit(0) + + +if __name__ == '__main__': + context = zmq.Context() + socket = context.socket(zmq.REP) + + # Set the right permissions on the socket, then change it back + o_mask = os.umask(0) + socket.bind(SOCKET_PATH) + os.umask(o_mask) + + cfg_group = grp.getgrnam(CFG_GROUP) + os.setgid(cfg_group.gr_gid) + + os.environ['VYOS_CONFIGD'] = 't' + + def sig_handler(signum, frame): + # pylint: disable=unused-argument + shutdown() + + signal.signal(signal.SIGTERM, sig_handler) + signal.signal(signal.SIGINT, sig_handler) + + # Define the vyshim environment variable + remove_if_file(configd_env_file) + os.symlink(configd_env_set_file, configd_env_file) + + config = None + + while True: + # Wait for next request from client + msg = socket.recv().decode() + logger.debug(f'Received message: {msg}') + message = json.loads(msg) + + if message['type'] == 'init': + resp = 'init' + socket.send(resp.encode()) + config = initialization(socket) + elif message['type'] == 'node': + res, out = process_node_data(config, message['data'], message['last']) + send_result(socket, res, out) + + if message['last'] and config: + scripts_called = getattr(config, 'scripts_called', []) + logger.debug(f'scripts_called: {scripts_called}') + else: + logger.critical(f'Unexpected message: {message}') diff --git a/src/services/vyos-conntrack-logger b/src/services/vyos-conntrack-logger new file mode 100644 index 0000000..9c31b46 --- /dev/null +++ b/src/services/vyos-conntrack-logger @@ -0,0 +1,458 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import grp +import logging +import multiprocessing +import os +import queue +import signal +import socket +import threading +from datetime import timedelta +from pathlib import Path +from time import sleep +from typing import Dict, AnyStr + +from pyroute2 import conntrack +from pyroute2.netlink import nfnetlink +from pyroute2.netlink.nfnetlink import NFNL_SUBSYS_CTNETLINK +from pyroute2.netlink.nfnetlink.nfctsocket import nfct_msg, \ + IPCTNL_MSG_CT_DELETE, IPCTNL_MSG_CT_NEW, IPS_SEEN_REPLY, \ + IPS_OFFLOAD, IPS_ASSURED + +from vyos.utils.file import read_json + + +shutdown_event = multiprocessing.Event() + +logging.basicConfig(level=logging.INFO, format='%(message)s') +logger = logging.getLogger(__name__) + + +class DebugFormatter(logging.Formatter): + def format(self, record): + self._style._fmt = '[%(asctime)s] %(levelname)s: %(message)s' + return super().format(record) + + +def set_log_level(level: str) -> None: + if level == 'debug': + logger.setLevel(logging.DEBUG) + logger.parent.handlers[0].setFormatter(DebugFormatter()) + else: + logger.setLevel(logging.INFO) + + +EVENT_NAME_TO_GROUP = { + 'new': nfnetlink.NFNLGRP_CONNTRACK_NEW, + 'update': nfnetlink.NFNLGRP_CONNTRACK_UPDATE, + 'destroy': nfnetlink.NFNLGRP_CONNTRACK_DESTROY +} + +# https://github.com/torvalds/linux/blob/1dfe225e9af5bd3399a1dbc6a4df6a6041ff9c23/include/uapi/linux/netfilter/nf_conntrack_tcp.h#L9 +TCP_CONNTRACK_SYN_SENT = 1 +TCP_CONNTRACK_SYN_RECV = 2 +TCP_CONNTRACK_ESTABLISHED = 3 +TCP_CONNTRACK_FIN_WAIT = 4 +TCP_CONNTRACK_CLOSE_WAIT = 5 +TCP_CONNTRACK_LAST_ACK = 6 +TCP_CONNTRACK_TIME_WAIT = 7 +TCP_CONNTRACK_CLOSE = 8 +TCP_CONNTRACK_LISTEN = 9 +TCP_CONNTRACK_MAX = 10 +TCP_CONNTRACK_IGNORE = 11 +TCP_CONNTRACK_RETRANS = 12 +TCP_CONNTRACK_UNACK = 13 +TCP_CONNTRACK_TIMEOUT_MAX = 14 + +TCP_CONNTRACK_TO_NAME = { + TCP_CONNTRACK_SYN_SENT: "SYN_SENT", + TCP_CONNTRACK_SYN_RECV: "SYN_RECV", + TCP_CONNTRACK_ESTABLISHED: "ESTABLISHED", + TCP_CONNTRACK_FIN_WAIT: "FIN_WAIT", + TCP_CONNTRACK_CLOSE_WAIT: "CLOSE_WAIT", + TCP_CONNTRACK_LAST_ACK: "LAST_ACK", + TCP_CONNTRACK_TIME_WAIT: "TIME_WAIT", + TCP_CONNTRACK_CLOSE: "CLOSE", + TCP_CONNTRACK_LISTEN: "LISTEN", + TCP_CONNTRACK_MAX: "MAX", + TCP_CONNTRACK_IGNORE: "IGNORE", + TCP_CONNTRACK_RETRANS: "RETRANS", + TCP_CONNTRACK_UNACK: "UNACK", + TCP_CONNTRACK_TIMEOUT_MAX: "TIMEOUT_MAX", +} + +# https://github.com/torvalds/linux/blob/1dfe225e9af5bd3399a1dbc6a4df6a6041ff9c23/include/uapi/linux/netfilter/nf_conntrack_sctp.h#L8 +SCTP_CONNTRACK_CLOSED = 1 +SCTP_CONNTRACK_COOKIE_WAIT = 2 +SCTP_CONNTRACK_COOKIE_ECHOED = 3 +SCTP_CONNTRACK_ESTABLISHED = 4 +SCTP_CONNTRACK_SHUTDOWN_SENT = 5 +SCTP_CONNTRACK_SHUTDOWN_RECD = 6 +SCTP_CONNTRACK_SHUTDOWN_ACK_SENT = 7 +SCTP_CONNTRACK_HEARTBEAT_SENT = 8 +SCTP_CONNTRACK_HEARTBEAT_ACKED = 9 # no longer used +SCTP_CONNTRACK_MAX = 10 + +SCTP_CONNTRACK_TO_NAME = { + SCTP_CONNTRACK_CLOSED: 'CLOSED', + SCTP_CONNTRACK_COOKIE_WAIT: 'COOKIE_WAIT', + SCTP_CONNTRACK_COOKIE_ECHOED: 'COOKIE_ECHOED', + SCTP_CONNTRACK_ESTABLISHED: 'ESTABLISHED', + SCTP_CONNTRACK_SHUTDOWN_SENT: 'SHUTDOWN_SENT', + SCTP_CONNTRACK_SHUTDOWN_RECD: 'SHUTDOWN_RECD', + SCTP_CONNTRACK_SHUTDOWN_ACK_SENT: 'SHUTDOWN_ACK_SENT', + SCTP_CONNTRACK_HEARTBEAT_SENT: 'HEARTBEAT_SENT', + SCTP_CONNTRACK_HEARTBEAT_ACKED: 'HEARTBEAT_ACKED', + SCTP_CONNTRACK_MAX: 'MAX', +} + +PROTO_CONNTRACK_TO_NAME = { + 'TCP': TCP_CONNTRACK_TO_NAME, + 'SCTP': SCTP_CONNTRACK_TO_NAME +} + +SUPPORTED_PROTO_TO_NAME = { + socket.IPPROTO_ICMP: 'icmp', + socket.IPPROTO_TCP: 'tcp', + socket.IPPROTO_UDP: 'udp', +} + +PROTO_TO_NAME = { + socket.IPPROTO_ICMPV6: 'icmpv6', + socket.IPPROTO_SCTP: 'sctp', + socket.IPPROTO_GRE: 'gre', +} + +PROTO_TO_NAME.update(SUPPORTED_PROTO_TO_NAME) + + +def sig_handler(signum, frame): + process_name = multiprocessing.current_process().name + logger.debug(f'[{process_name}]: {"Shutdown" if signum == signal.SIGTERM else "Reload"} signal received...') + shutdown_event.set() + + +def format_flow_data(data: Dict) -> AnyStr: + """ + Formats the flow event data into a string suitable for logging. + """ + key_format = { + 'SRC_PORT': 'sport', + 'DST_PORT': 'dport' + } + message = f"src={data['ADDR'].get('SRC')} dst={data['ADDR'].get('DST')}" + + for key in ['SRC_PORT', 'DST_PORT', 'TYPE', 'CODE', 'ID']: + tmp = data['PROTO'].get(key) + if tmp is not None: + key = key_format.get(key, key) + message += f" {key.lower()}={tmp}" + + if 'COUNTERS' in data: + for key in ['PACKETS', 'BYTES']: + tmp = data['COUNTERS'].get(key) + if tmp is not None: + message += f" {key.lower()}={tmp}" + + return message + + +def format_event_message(event: Dict) -> AnyStr: + """ + Formats the internal parsed event data into a string suitable for logging. + """ + event_type = f"[{event['COMMON']['EVENT_TYPE'].upper()}]" + message = f"{event_type:<{9}} {event['COMMON']['ID']} " \ + f"{event['ORIG']['PROTO'].get('NAME'):<{8}} " \ + f"{event['ORIG']['PROTO'].get('NUMBER')} " + + tmp = event['COMMON']['TIME_OUT'] + if tmp is not None: message += f"{tmp} " + + if proto_info := event['COMMON'].get('PROTO_INFO'): + message += f"{proto_info.get('STATE_NAME')} " + + for key in ['ORIG', 'REPLY']: + message += f"{format_flow_data(event[key])} " + if key == 'ORIG' and not (event['COMMON']['STATUS'] & IPS_SEEN_REPLY): + message += f"[UNREPLIED] " + + tmp = event['COMMON']['MARK'] + if tmp is not None: message += f"mark={tmp} " + + if event['COMMON']['STATUS'] & IPS_OFFLOAD: message += f" [OFFLOAD] " + elif event['COMMON']['STATUS'] & IPS_ASSURED: message += f" [ASSURED] " + + if tmp := event['COMMON']['PORTID']: message += f"portid={tmp} " + if tstamp := event['COMMON'].get('TIMESTAMP'): + message += f"start={tstamp['START']} stop={tstamp['STOP']} " + delta_ns = tstamp['STOP'] - tstamp['START'] + delta_s = delta_ns // 1e9 + remaining_ns = delta_ns % 1e9 + delta = timedelta(seconds=delta_s, microseconds=remaining_ns / 1000) + message += f"delta={delta.total_seconds()} " + + return message + + +def parse_event_type(header: Dict) -> AnyStr: + """ + Extract event type from nfct_msg. new, update, destroy + """ + event_type = 'unknown' + if header['type'] == IPCTNL_MSG_CT_DELETE | (NFNL_SUBSYS_CTNETLINK << 8): + event_type = 'destroy' + elif header['type'] == IPCTNL_MSG_CT_NEW | (NFNL_SUBSYS_CTNETLINK << 8): + event_type = 'update' + if header['flags']: + event_type = 'new' + return event_type + + +def parse_proto(cta: nfct_msg.cta_tuple) -> Dict: + """ + Extract proto info from nfct_msg. src/dst port, code, type, id + """ + data = dict() + + cta_proto = cta.get_attr('CTA_TUPLE_PROTO') + proto_num = cta_proto.get_attr('CTA_PROTO_NUM') + + data['NUMBER'] = proto_num + data['NAME'] = PROTO_TO_NAME.get(proto_num, 'unknown') + + if proto_num in (socket.IPPROTO_ICMP, socket.IPPROTO_ICMPV6): + pref = 'CTA_PROTO_ICMP' + if proto_num == socket.IPPROTO_ICMPV6: pref += 'V6' + keys = ['TYPE', 'CODE', 'ID'] + else: + pref = 'CTA_PROTO' + keys = ['SRC_PORT', 'DST_PORT'] + + for key in keys: + data[key] = cta_proto.get_attr(f'{pref}_{key}') + + return data + + +def parse_proto_info(cta: nfct_msg.cta_protoinfo) -> Dict: + """ + Extract proto state and state name from nfct_msg + """ + data = dict() + if not cta: + return data + + for proto in ['TCP', 'SCTP']: + if proto_info := cta.get_attr(f'CTA_PROTOINFO_{proto}'): + data['STATE'] = proto_info.get_attr(f'CTA_PROTOINFO_{proto}_STATE') + data['STATE_NAME'] = PROTO_CONNTRACK_TO_NAME.get(proto, {}).get(data['STATE'], 'unknown') + return data + + +def parse_timestamp(cta: nfct_msg.cta_timestamp) -> Dict: + """ + Extract timestamp from nfct_msg + """ + data = dict() + if not cta: + return data + data['START'] = cta.get_attr('CTA_TIMESTAMP_START') + data['STOP'] = cta.get_attr('CTA_TIMESTAMP_STOP') + + return data + + +def parse_ip_addr(family: int, cta: nfct_msg.cta_tuple) -> Dict: + """ + Extract ip adr from nfct_msg + """ + data = dict() + cta_ip = cta.get_attr('CTA_TUPLE_IP') + + if family == socket.AF_INET: + pref = 'CTA_IP_V4' + elif family == socket.AF_INET6: + pref = 'CTA_IP_V6' + else: + logger.error(f'Undefined INET: {family}') + raise NotImplementedError(family) + + for direct in ['SRC', 'DST']: + data[direct] = cta_ip.get_attr(f'{pref}_{direct}') + + return data + + +def parse_counters(cta: nfct_msg.cta_counters) -> Dict: + """ + Extract counters from nfct_msg + """ + data = dict() + if not cta: + return data + + for key in ['PACKETS', 'BYTES']: + tmp = cta.get_attr(f'CTA_COUNTERS_{key}') + if tmp is None: + tmp = cta.get_attr(f'CTA_COUNTERS32_{key}') + data['key'] = tmp + + return data + + +def is_need_to_log(event_type: AnyStr, proto_num: int, conf_event: Dict): + """ + Filter message by event type and protocols + """ + conf = conf_event.get(event_type) + if conf == {} or conf.get(SUPPORTED_PROTO_TO_NAME.get(proto_num, 'other')) is not None: + return True + return False + + +def parse_conntrack_event(msg: nfct_msg, conf_event: Dict) -> Dict: + """ + Convert nfct_msg to internal data dict. + """ + data = dict() + event_type = parse_event_type(msg['header']) + proto_num = msg.get_nested('CTA_TUPLE_ORIG', 'CTA_TUPLE_PROTO', 'CTA_PROTO_NUM') + + if not is_need_to_log(event_type, proto_num, conf_event): + return data + + data = { + 'COMMON': { + 'ID': msg.get_attr('CTA_ID'), + 'EVENT_TYPE': event_type, + 'TIME_OUT': msg.get_attr('CTA_TIMEOUT'), + 'MARK': msg.get_attr('CTA_MARK'), + 'PORTID': msg['header'].get('pid'), + 'PROTO_INFO': parse_proto_info(msg.get_attr('CTA_PROTOINFO')), + 'STATUS': msg.get_attr('CTA_STATUS'), + 'TIMESTAMP': parse_timestamp(msg.get_attr('CTA_TIMESTAMP')) + }, + 'ORIG': {}, + 'REPLY': {}, + } + + for direct in ['ORIG', 'REPLY']: + data[direct]['ADDR'] = parse_ip_addr(msg['nfgen_family'], msg.get_attr(f'CTA_TUPLE_{direct}')) + data[direct]['PROTO'] = parse_proto(msg.get_attr(f'CTA_TUPLE_{direct}')) + data[direct]['COUNTERS'] = parse_counters(msg.get_attr(f'CTA_COUNTERS_{direct}')) + + return data + + +def worker(ct: conntrack.Conntrack, shutdown_event: multiprocessing.Event, conf_event: Dict): + """ + Main function of parser worker process + """ + process_name = multiprocessing.current_process().name + logger.debug(f'[{process_name}] started') + timeout = 0.1 + while not shutdown_event.is_set(): + if not ct.buffer_queue.empty(): + try: + for msg in ct.get(): + parsed_event = parse_conntrack_event(msg, conf_event) + if parsed_event: + message = format_event_message(parsed_event) + if logger.level == logging.DEBUG: + logger.debug(f"[{process_name}]: {message} raw: {msg}") + else: + logger.info(message) + except queue.Full: + logger.error("Conntrack message queue if full.") + except Exception as e: + logger.error(f"Error in queue: {e.__class__} {e}") + else: + sleep(timeout) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-c', + '--config', + action='store', + help='Path to vyos-conntrack-logger configuration', + required=True, + type=Path) + + args = parser.parse_args() + try: + config = read_json(args.config) + except Exception as err: + logger.error(f'Configuration file "{args.config}" does not exist or malformed: {err}') + exit(1) + + set_log_level(config.get('log_level', 'info')) + + signal.signal(signal.SIGHUP, sig_handler) + signal.signal(signal.SIGTERM, sig_handler) + + if 'event' in config: + event_groups = list(config.get('event').keys()) + else: + logger.error(f'Configuration is wrong. Event filter is empty.') + exit(1) + + conf_event = config['event'] + qsize = config.get('queue_size') + ct = conntrack.Conntrack(async_qsize=int(qsize) if qsize else None) + ct.buffer_queue = multiprocessing.Queue(ct.async_qsize) + ct.bind(async_cache=True) + + for name in event_groups: + if group := EVENT_NAME_TO_GROUP.get(name): + ct.add_membership(group) + else: + logger.error(f'Unexpected event group {name}') + processes = list() + try: + for _ in range(multiprocessing.cpu_count()): + p = multiprocessing.Process(target=worker, args=(ct, + shutdown_event, + conf_event)) + processes.append(p) + p.start() + logger.info('Conntrack socket bound and listening for messages.') + + while not shutdown_event.is_set(): + if not ct.pthread.is_alive(): + if ct.buffer_queue.qsize()/ct.async_qsize < 0.9: + if not shutdown_event.is_set(): + logger.debug('Restart listener thread') + # restart listener thread after queue overloaded when queue size low than 90% + ct.pthread = threading.Thread( + name="Netlink async cache", target=ct.async_recv + ) + ct.pthread.daemon = True + ct.pthread.start() + else: + sleep(0.1) + finally: + for p in processes: + p.join() + if not p.is_alive(): + logger.debug(f"[{p.name}]: finished") + ct.close() + logging.info("Conntrack socket closed.") + exit() diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd new file mode 100644 index 0000000..1ba9047 --- /dev/null +++ b/src/services/vyos-hostsd @@ -0,0 +1,651 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +######### +# USAGE # +######### +# This daemon listens on its socket for JSON messages. +# The received message format is: +# +# { 'type': '<message type>', +# 'op': '<message operation>', +# 'data': <data list or dict> +# } +# +# For supported message types, see below. +# 'op' can be 'add', delete', 'get', 'set' or 'apply'. +# Different message types support different sets of operations and different +# data formats. +# +# Changes to configuration made via add or delete don't take effect immediately, +# they are remembered in a state variable and saved to disk to a state file. +# State is remembered across daemon restarts but not across system reboots +# as it's saved in a temporary filesystem (/run). +# +# 'apply' is a special operation that applies the configuration from the cached +# state, rendering all config files and reloading relevant daemons (currently +# just pdns-recursor via rec-control). +# +# note: 'add' operation also acts as 'update' as it uses dict.update, if the +# 'data' dict item value is a dict. If it is a list, it uses list.append. +# +### tags +# Tags can be arbitrary, but they are generally in this format: +# 'static', 'system', 'dhcp(v6)-<intf>' or 'dhcp-server-<client ip>' +# They are used to distinguish entries created by different scripts so they can +# be removed and recreated without having to track what needs to be changed. +# They are also used as a way to control which tags settings (e.g. nameservers) +# get added to various config files via name_server_tags_(recursor|system) +# +### name_server_tags_(recursor|system) +# A list of tags whose nameservers and search domains is used to generate +# /etc/resolv.conf and pdns-recursor config. +# system list is used to generate resolv.conf. +# recursor list is used to generate pdns-rec forward-zones. +# When generating each file, the order of nameservers is as per the order of +# name_server_tags (the order in which tags were added), then the order in +# which the name servers for each tag were added. +# +#### Message types +# +### name_servers +# +# { 'type': 'name_servers', +# 'op': 'add', +# 'data': { +# '<str tag>': ['<str nameserver>', ...], +# ... +# } +# } +# +# { 'type': 'name_servers', +# 'op': 'delete', +# 'data': ['<str tag>', ...] +# } +# +# { 'type': 'name_servers', +# 'op': 'get', +# 'tag_regex': '<str regex>' +# } +# response: +# { 'data': { +# '<str tag>': ['<str nameserver>', ...], +# ... +# } +# } +# +### name_server_tags +# +# { 'type': 'name_server_tags', +# 'op': 'add', +# 'data': ['<str tag>', ...] +# } +# +# { 'type': 'name_server_tags', +# 'op': 'delete', +# 'data': ['<str tag>', ...] +# } +# +# { 'type': 'name_server_tags', +# 'op': 'get', +# } +# response: +# { 'data': ['<str tag>', ...] } +# +### forward_zones +## Additional zones added to pdns-recursor forward-zones-file. +## If recursion_desired is true, '+' will be prepended to the zone line. +## If addnta is true, a NTA (Negative Trust Anchor) will be added via +## lua-config-file. +# +# { 'type': 'forward_zones', +# 'op': 'add', +# 'data': { +# '<str zone>': { +# 'server': ['<str nameserver>', ...], +# 'addnta': <bool>, +# 'recursion_desired': <bool> +# } +# ... +# } +# } +# +# { 'type': 'forward_zones', +# 'op': 'delete', +# 'data': ['<str zone>', ...] +# } +# +# { 'type': 'forward_zones', +# 'op': 'get', +# } +# response: +# { 'data': { +# '<str zone>': { ... }, +# ... +# } +# } +# +# +### authoritative_zones +## Additional zones hosted authoritatively by pdns-recursor. +## We add NTAs for these zones but do not do much else here. +# +# { 'type': 'authoritative_zones', +# 'op': 'add', +# 'data': ['<str zone>', ...] +# } +# +# { 'type': 'authoritative_zones', +# 'op': 'delete', +# 'data': ['<str zone>', ...] +# } +# +# { 'type': 'authoritative_zones', +# 'op': 'get', +# } +# response: +# { 'data': ['<str zone>', ...] } +# +# +### search_domains +# +# { 'type': 'search_domains', +# 'op': 'add', +# 'data': { +# '<str tag>': ['<str domain>', ...], +# ... +# } +# } +# +# { 'type': 'search_domains', +# 'op': 'delete', +# 'data': ['<str tag>', ...] +# } +# +# { 'type': 'search_domains', +# 'op': 'get', +# } +# response: +# { 'data': { +# '<str tag>': ['<str domain>', ...], +# ... +# } +# } +# +### hosts +# +# { 'type': 'hosts', +# 'op': 'add', +# 'data': { +# '<str tag>': { +# '<str host>': { +# 'address': '<str address>', +# 'aliases': ['<str alias>, ...] +# }, +# ... +# }, +# ... +# } +# } +# +# { 'type': 'hosts', +# 'op': 'delete', +# 'data': ['<str tag>', ...] +# } +# +# { 'type': 'hosts', +# 'op': 'get' +# 'tag_regex': '<str regex>' +# } +# response: +# { 'data': { +# '<str tag>': { +# '<str host>': { +# 'address': '<str address>', +# 'aliases': ['<str alias>, ...] +# }, +# ... +# }, +# ... +# } +# } +### host_name +# +# { 'type': 'host_name', +# 'op': 'set', +# 'data': { +# 'host_name': '<str hostname>' +# 'domain_name': '<str domainname>' +# } +# } + +import os +import sys +import time +import json +import signal +import traceback +import re +import logging +import zmq + +from voluptuous import Schema, MultipleInvalid, Required, Any +from collections import OrderedDict +from vyos.utils.file import makedir +from vyos.utils.permission import chown +from vyos.utils.permission import chmod_755 +from vyos.utils.process import popen +from vyos.utils.process import process_named_running +from vyos.template import render + +debug = True + +# Configure logging +logger = logging.getLogger(__name__) +# set stream as output +logs_handler = logging.StreamHandler() +logger.addHandler(logs_handler) + +if debug: + logger.setLevel(logging.DEBUG) +else: + logger.setLevel(logging.INFO) + +RUN_DIR = "/run/vyos-hostsd" +STATE_FILE = os.path.join(RUN_DIR, "vyos-hostsd.state") +SOCKET_PATH = "ipc://" + os.path.join(RUN_DIR, 'vyos-hostsd.sock') + +RESOLV_CONF_FILE = '/etc/resolv.conf' +HOSTS_FILE = '/etc/hosts' + +PDNS_REC_USER_GROUP = 'pdns' +PDNS_REC_RUN_DIR = '/run/pdns-recursor' +PDNS_REC_LUA_CONF_FILE = f'{PDNS_REC_RUN_DIR}/recursor.vyos-hostsd.conf.lua' +PDNS_REC_ZONES_FILE = f'{PDNS_REC_RUN_DIR}/recursor.forward-zones.conf' + +STATE = { + "name_servers": {}, + "name_server_tags_recursor": [], + "name_server_tags_system": [], + "forward_zones": {}, + "authoritative_zones": [], + "hosts": {}, + "host_name": "vyos", + "domain_name": "", + "search_domains": {}, + "changes": 0 + } + +# the base schema that every received message must be in +base_schema = Schema({ + Required('op'): Any('add', 'delete', 'set', 'get', 'apply'), + 'type': Any('name_servers', + 'name_server_tags_recursor', 'name_server_tags_system', + 'forward_zones', 'authoritative_zones', 'search_domains', + 'hosts', 'host_name'), + 'data': Any(list, dict), + 'tag': str, + 'tag_regex': str + }) + +# more specific schemas +op_schema = Schema({ + 'op': str, + }, required=True) + +op_type_schema = op_schema.extend({ + 'type': str, + }, required=True) + +host_name_add_schema = op_type_schema.extend({ + 'data': { + 'host_name': str, + 'domain_name': Any(str, None) + } + }, required=True) + +data_dict_list_schema = op_type_schema.extend({ + 'data': { + str: [str] + } + }, required=True) + +data_list_schema = op_type_schema.extend({ + 'data': [str] + }, required=True) + +tag_regex_schema = op_type_schema.extend({ + 'tag_regex': str + }, required=True) + +forward_zone_add_schema = op_type_schema.extend({ + 'data': { + str: { + 'name_server': [str], + 'addnta': Any({}, None), + 'recursion_desired': Any({}, None), + } + } + }, required=False) + +hosts_add_schema = op_type_schema.extend({ + 'data': { + str: { + str: { + 'address': [str], + 'aliases': [str] + } + } + } + }, required=True) + + +# op and type to schema mapping +msg_schema_map = { + 'name_servers': { + 'add': data_dict_list_schema, + 'delete': data_list_schema, + 'get': tag_regex_schema + }, + 'name_server_tags_recursor': { + 'add': data_list_schema, + 'delete': data_list_schema, + 'get': op_type_schema + }, + 'name_server_tags_system': { + 'add': data_list_schema, + 'delete': data_list_schema, + 'get': op_type_schema + }, + 'forward_zones': { + 'add': forward_zone_add_schema, + 'delete': data_list_schema, + 'get': op_type_schema + }, + 'authoritative_zones': { + 'add': data_list_schema, + 'delete': data_list_schema, + 'get': op_type_schema + }, + 'search_domains': { + 'add': data_dict_list_schema, + 'delete': data_list_schema, + 'get': tag_regex_schema + }, + 'hosts': { + 'add': hosts_add_schema, + 'delete': data_list_schema, + 'get': tag_regex_schema + }, + 'host_name': { + 'set': host_name_add_schema + }, + None: { + 'apply': op_schema + } + } + +def validate_schema(data): + base_schema(data) + + try: + schema = msg_schema_map[data['type'] if 'type' in data else None][data['op']] + schema(data) + except KeyError: + raise ValueError(( + 'Invalid or unknown combination: ' + f'op: "{data["op"]}", type: "{data["type"]}"')) + + +def pdns_rec_control(command): + if not process_named_running('pdns_recursor'): + logger.info(f'pdns_recursor not running, not sending "{command}"') + return + + logger.info(f'Running "rec_control {command}"') + (ret,ret_code) = popen(( + f"rec_control --socket-dir={PDNS_REC_RUN_DIR} {command}")) + if ret_code > 0: + logger.exception(( + f'"rec_control {command}" failed with exit status {ret_code}, ' + f'output: "{ret}"')) + +def make_resolv_conf(state): + logger.info(f"Writing {RESOLV_CONF_FILE}") + render(RESOLV_CONF_FILE, 'vyos-hostsd/resolv.conf.j2', state, + user='root', group='root') + +def make_hosts(state): + logger.info(f"Writing {HOSTS_FILE}") + render(HOSTS_FILE, 'vyos-hostsd/hosts.j2', state, + user='root', group='root') + +def make_pdns_rec_conf(state): + logger.info(f"Writing {PDNS_REC_LUA_CONF_FILE}") + + # on boot, /run/pdns-recursor does not exist, so create it + makedir(PDNS_REC_RUN_DIR, user=PDNS_REC_USER_GROUP, group=PDNS_REC_USER_GROUP) + chmod_755(PDNS_REC_RUN_DIR) + + render(PDNS_REC_LUA_CONF_FILE, + 'dns-forwarding/recursor.vyos-hostsd.conf.lua.j2', + state, user=PDNS_REC_USER_GROUP, group=PDNS_REC_USER_GROUP) + + logger.info(f"Writing {PDNS_REC_ZONES_FILE}") + render(PDNS_REC_ZONES_FILE, + 'dns-forwarding/recursor.forward-zones.conf.j2', + state, user=PDNS_REC_USER_GROUP, group=PDNS_REC_USER_GROUP) + +def set_host_name(state, data): + if data['host_name']: + state['host_name'] = data['host_name'] + if 'domain_name' in data: + state['domain_name'] = data['domain_name'] + +def add_items_to_dict(_dict, items): + """ + Dedupes and preserves sort order. + """ + assert isinstance(_dict, dict) + assert isinstance(items, dict) + + if not items: + return + + _dict.update(items) + +def add_items_to_dict_as_keys(_dict, items): + """ + Added item values are converted to OrderedDict with the value as keys + and null values. This is to emulate a list but with inherent deduplication. + Dedupes and preserves sort order. + """ + assert isinstance(_dict, dict) + assert isinstance(items, dict) + + if not items: + return + + for item, item_val in items.items(): + if item not in _dict: + _dict[item] = OrderedDict({}) + _dict[item].update(OrderedDict.fromkeys(item_val)) + +def add_items_to_list(_list, items): + """ + Dedupes and preserves sort order. + """ + assert isinstance(_list, list) + assert isinstance(items, list) + + if not items: + return + + for item in items: + if item not in _list: + _list.append(item) + +def delete_items_from_dict(_dict, items): + """ + items is a list of keys to delete. + Doesn't error if the key doesn't exist. + """ + assert isinstance(_dict, dict) + assert isinstance(items, list) + + for item in items: + if item in _dict: + del _dict[item] + +def delete_items_from_list(_list, items): + """ + items is a list of items to remove. + Doesn't error if the key doesn't exist. + """ + assert isinstance(_list, list) + assert isinstance(items, list) + + for item in items: + if item in _list: + _list.remove(item) + +def get_items_from_dict_regex(_dict, item_regex_string): + """ + Returns the items whose keys match item_regex_string. + """ + assert isinstance(_dict, dict) + assert isinstance(item_regex_string, str) + + tmp = {} + regex = re.compile(item_regex_string) + for item in _dict: + if regex.match(item): + tmp[item] = _dict[item] + return tmp + +def get_option(msg, key): + if key in msg: + return msg[key] + else: + raise ValueError("Missing required option \"{0}\"".format(key)) + +def handle_message(msg): + result = None + op = get_option(msg, 'op') + + if op in ['add', 'delete', 'set']: + STATE['changes'] += 1 + + if op == 'delete': + _type = get_option(msg, 'type') + data = get_option(msg, 'data') + if _type in ['name_servers', 'forward_zones', 'search_domains', 'hosts']: + delete_items_from_dict(STATE[_type], data) + elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'authoritative_zones']: + delete_items_from_list(STATE[_type], data) + else: + raise ValueError(f'Operation "{op}" unknown data type "{_type}"') + elif op == 'add': + _type = get_option(msg, 'type') + data = get_option(msg, 'data') + if _type in ['name_servers', 'search_domains']: + add_items_to_dict_as_keys(STATE[_type], data) + elif _type in ['forward_zones', 'hosts']: + add_items_to_dict(STATE[_type], data) + # maybe we need to rec_control clear-nta each domain that was removed here? + elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'authoritative_zones']: + add_items_to_list(STATE[_type], data) + else: + raise ValueError(f'Operation "{op}" unknown data type "{_type}"') + elif op == 'set': + _type = get_option(msg, 'type') + data = get_option(msg, 'data') + if _type == 'host_name': + set_host_name(STATE, data) + else: + raise ValueError(f'Operation "{op}" unknown data type "{_type}"') + elif op == 'get': + _type = get_option(msg, 'type') + if _type in ['name_servers', 'search_domains', 'hosts']: + tag_regex = get_option(msg, 'tag_regex') + result = get_items_from_dict_regex(STATE[_type], tag_regex) + elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'forward_zones', 'authoritative_zones']: + result = STATE[_type] + else: + raise ValueError(f'Operation "{op}" unknown data type "{_type}"') + elif op == 'apply': + logger.info(f"Applying {STATE['changes']} changes") + make_resolv_conf(STATE) + make_hosts(STATE) + make_pdns_rec_conf(STATE) + pdns_rec_control('reload-lua-config') + pdns_rec_control('reload-zones') + logger.info("Success") + result = {'message': f'Applied {STATE["changes"]} changes'} + STATE['changes'] = 0 + + else: + raise ValueError(f"Unknown operation {op}") + + logger.debug(f"Saving state to {STATE_FILE}") + with open(STATE_FILE, 'w') as f: + json.dump(STATE, f) + + return result + +if __name__ == '__main__': + # Create a directory for state checkpoints + os.makedirs(RUN_DIR, exist_ok=True) + if os.path.exists(STATE_FILE): + with open(STATE_FILE, 'r') as f: + try: + STATE = json.load(f) + except: + logger.exception(traceback.format_exc()) + logger.exception("Failed to load the state file, using default") + + context = zmq.Context() + socket = context.socket(zmq.REP) + + # Set the right permissions on the socket, then change it back + o_mask = os.umask(0o000) + socket.bind(SOCKET_PATH) + os.umask(o_mask) + + while True: + # Wait for next request from client + msg_json = socket.recv().decode() + logger.debug(f"Request data: {msg_json}") + + try: + msg = json.loads(msg_json) + validate_schema(msg) + + resp = {} + resp['data'] = handle_message(msg) + except ValueError as e: + resp['error'] = str(e) + except MultipleInvalid as e: + # raised by schema + resp['error'] = f'Invalid message: {str(e)}' + logger.exception(resp['error']) + except: + logger.exception(traceback.format_exc()) + resp['error'] = "Internal error" + + # Send reply back to client + socket.send(json.dumps(resp).encode()) + logger.debug(f"Sent response: {resp}") diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server new file mode 100644 index 0000000..9110041 --- /dev/null +++ b/src/services/vyos-http-api-server @@ -0,0 +1,1036 @@ +#!/usr/share/vyos-http-api-tools/bin/python3 +# +# Copyright (C) 2019-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys +import grp +import copy +import json +import logging +import signal +import traceback +import threading +from enum import Enum + +from time import sleep +from typing import List, Union, Callable, Dict, Self + +from fastapi import FastAPI, Depends, Request, Response, HTTPException +from fastapi import BackgroundTasks +from fastapi.responses import HTMLResponse +from fastapi.exceptions import RequestValidationError +from fastapi.routing import APIRoute +from pydantic import BaseModel, StrictStr, validator, model_validator +from starlette.middleware.cors import CORSMiddleware +from starlette.datastructures import FormData +from starlette.formparsers import FormParser, MultiPartParser +from multipart.multipart import parse_options_header +from uvicorn import Config as UvicornConfig +from uvicorn import Server as UvicornServer + +from ariadne.asgi import GraphQL + +from vyos.config import Config +from vyos.configtree import ConfigTree +from vyos.configdiff import get_config_diff +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.defaults import api_config_state + +import api.graphql.state + +CFG_GROUP = 'vyattacfg' + +debug = True + +logger = logging.getLogger(__name__) +logs_handler = logging.StreamHandler() +logger.addHandler(logs_handler) + +if debug: + logger.setLevel(logging.DEBUG) +else: + logger.setLevel(logging.INFO) + +# Giant lock! +lock = threading.Lock() + +def load_server_config(): + with open(api_config_state) as f: + config = json.load(f) + return config + +def check_auth(key_list, key): + key_id = None + for k in key_list: + if k['key'] == key: + key_id = k['id'] + return key_id + +def error(code, msg): + resp = {"success": False, "error": msg, "data": None} + resp = json.dumps(resp) + return HTMLResponse(resp, status_code=code) + +def success(data): + resp = {"success": True, "data": data, "error": None} + resp = json.dumps(resp) + return HTMLResponse(resp) + +# Pydantic models for validation +# Pydantic will cast when possible, so use StrictStr +# validators added as needed for additional constraints +# schema_extra adds anotations to OpenAPI, to add examples + +class ApiModel(BaseModel): + key: StrictStr + +class BasePathModel(BaseModel): + op: StrictStr + path: List[StrictStr] + + @validator("path") + def check_non_empty(cls, path): + if not len(path) > 0: + raise ValueError('path must be non-empty') + return path + +class BaseConfigureModel(BasePathModel): + value: StrictStr = None + +class ConfigureModel(ApiModel, BaseConfigureModel): + class Config: + schema_extra = { + "example": { + "key": "id_key", + "op": "set | delete | comment", + "path": ['config', 'mode', 'path'], + } + } + +class ConfigureListModel(ApiModel): + commands: List[BaseConfigureModel] + + class Config: + schema_extra = { + "example": { + "key": "id_key", + "commands": "list of commands", + } + } + +class BaseConfigSectionModel(BasePathModel): + section: Dict + +class ConfigSectionModel(ApiModel, BaseConfigSectionModel): + pass + +class ConfigSectionListModel(ApiModel): + commands: List[BaseConfigSectionModel] + +class BaseConfigSectionTreeModel(BaseModel): + op: StrictStr + mask: Dict + config: Dict + +class ConfigSectionTreeModel(ApiModel, BaseConfigSectionTreeModel): + pass + +class RetrieveModel(ApiModel): + op: StrictStr + path: List[StrictStr] + configFormat: StrictStr = None + + class Config: + schema_extra = { + "example": { + "key": "id_key", + "op": "returnValue | returnValues | exists | showConfig", + "path": ['config', 'mode', 'path'], + "configFormat": "json (default) | json_ast | raw", + + } + } + +class ConfigFileModel(ApiModel): + op: StrictStr + file: StrictStr = None + + class Config: + schema_extra = { + "example": { + "key": "id_key", + "op": "save | load", + "file": "filename", + } + } + + +class ImageOp(str, Enum): + add = "add" + delete = "delete" + show = "show" + set_default = "set_default" + + +class ImageModel(ApiModel): + op: ImageOp + url: StrictStr = None + name: StrictStr = None + + @model_validator(mode='after') + def check_data(self) -> Self: + if self.op == 'add': + if not self.url: + raise ValueError("Missing required field \"url\"") + elif self.op in ['delete', 'set_default']: + if not self.name: + raise ValueError("Missing required field \"name\"") + + return self + + class Config: + schema_extra = { + "example": { + "key": "id_key", + "op": "add | delete | show | set_default", + "url": "imagelocation", + "name": "imagename", + } + } + +class ImportPkiModel(ApiModel): + op: StrictStr + path: List[StrictStr] + passphrase: StrictStr = None + + class Config: + schema_extra = { + "example": { + "key": "id_key", + "op": "import_pki", + "path": ["op", "mode", "path"], + "passphrase": "passphrase", + } + } + + +class ContainerImageModel(ApiModel): + op: StrictStr + name: StrictStr = None + + class Config: + schema_extra = { + "example": { + "key": "id_key", + "op": "add | delete | show", + "name": "imagename", + } + } + +class GenerateModel(ApiModel): + op: StrictStr + path: List[StrictStr] + + class Config: + schema_extra = { + "example": { + "key": "id_key", + "op": "generate", + "path": ["op", "mode", "path"], + } + } + +class ShowModel(ApiModel): + op: StrictStr + path: List[StrictStr] + + class Config: + schema_extra = { + "example": { + "key": "id_key", + "op": "show", + "path": ["op", "mode", "path"], + } + } + +class RebootModel(ApiModel): + op: StrictStr + path: List[StrictStr] + + class Config: + schema_extra = { + "example": { + "key": "id_key", + "op": "reboot", + "path": ["op", "mode", "path"], + } + } + +class ResetModel(ApiModel): + op: StrictStr + path: List[StrictStr] + + class Config: + schema_extra = { + "example": { + "key": "id_key", + "op": "reset", + "path": ["op", "mode", "path"], + } + } + +class PoweroffModel(ApiModel): + op: StrictStr + path: List[StrictStr] + + class Config: + schema_extra = { + "example": { + "key": "id_key", + "op": "poweroff", + "path": ["op", "mode", "path"], + } + } + + +class Success(BaseModel): + success: bool + data: Union[str, bool, Dict] + error: str + +class Error(BaseModel): + success: bool = False + data: Union[str, bool, Dict] + error: str + +responses = { + 200: {'model': Success}, + 400: {'model': Error}, + 422: {'model': Error, 'description': 'Validation Error'}, + 500: {'model': Error} +} + +def auth_required(data: ApiModel): + key = data.key + api_keys = app.state.vyos_keys + key_id = check_auth(api_keys, key) + if not key_id: + raise HTTPException(status_code=401, detail="Valid API key is required") + app.state.vyos_id = key_id + +# override Request and APIRoute classes in order to convert form request to json; +# do all explicit validation here, for backwards compatability of error messages; +# the explicit validation may be dropped, if desired, in favor of native +# validation by FastAPI/Pydantic, as is used for application/json requests +class MultipartRequest(Request): + _form_err = () + @property + def form_err(self): + return self._form_err + + @form_err.setter + def form_err(self, val): + if not self._form_err: + self._form_err = val + + @property + def orig_headers(self): + self._orig_headers = super().headers + return self._orig_headers + + @property + def headers(self): + self._headers = super().headers.mutablecopy() + self._headers['content-type'] = 'application/json' + return self._headers + + async def form(self) -> FormData: + if self._form is None: + assert ( + parse_options_header is not None + ), "The `python-multipart` library must be installed to use form parsing." + content_type_header = self.orig_headers.get("Content-Type") + content_type, options = parse_options_header(content_type_header) + if content_type == b"multipart/form-data": + multipart_parser = MultiPartParser(self.orig_headers, self.stream()) + self._form = await multipart_parser.parse() + elif content_type == b"application/x-www-form-urlencoded": + form_parser = FormParser(self.orig_headers, self.stream()) + self._form = await form_parser.parse() + else: + self._form = FormData() + return self._form + + async def body(self) -> bytes: + if not hasattr(self, "_body"): + forms = {} + merge = {} + body = await super().body() + self._body = body + + form_data = await self.form() + if form_data: + endpoint = self.url.path + logger.debug("processing form data") + for k, v in form_data.multi_items(): + forms[k] = v + + if 'data' not in forms: + self.form_err = (422, "Non-empty data field is required") + return self._body + else: + try: + tmp = json.loads(forms['data']) + except json.JSONDecodeError as e: + self.form_err = (400, f'Failed to parse JSON: {e}') + return self._body + if isinstance(tmp, list): + merge['commands'] = tmp + else: + merge = tmp + + if 'commands' in merge: + cmds = merge['commands'] + else: + cmds = copy.deepcopy(merge) + cmds = [cmds] + + for c in cmds: + if not isinstance(c, dict): + self.form_err = (400, + f"Malformed command '{c}': any command must be JSON of dict") + return self._body + if 'op' not in c: + self.form_err = (400, + f"Malformed command '{c}': missing 'op' field") + if endpoint not in ('/config-file', '/container-image', + '/image', '/configure-section'): + if 'path' not in c: + self.form_err = (400, + f"Malformed command '{c}': missing 'path' field") + elif not isinstance(c['path'], list): + self.form_err = (400, + f"Malformed command '{c}': 'path' field must be a list") + elif not all(isinstance(el, str) for el in c['path']): + self.form_err = (400, + f"Malformed command '{0}': 'path' field must be a list of strings") + if endpoint in ('/configure'): + if not c['path']: + self.form_err = (400, + f"Malformed command '{c}': 'path' list must be non-empty") + if 'value' in c and not isinstance(c['value'], str): + self.form_err = (400, + f"Malformed command '{c}': 'value' field must be a string") + if endpoint in ('/configure-section'): + if 'section' not in c and 'config' not in c: + self.form_err = (400, + f"Malformed command '{c}': missing 'section' or 'config' field") + + if 'key' not in forms and 'key' not in merge: + self.form_err = (401, "Valid API key is required") + if 'key' in forms and 'key' not in merge: + merge['key'] = forms['key'] + + new_body = json.dumps(merge) + new_body = new_body.encode() + self._body = new_body + + return self._body + +class MultipartRoute(APIRoute): + def get_route_handler(self) -> Callable: + original_route_handler = super().get_route_handler() + + async def custom_route_handler(request: Request) -> Response: + request = MultipartRequest(request.scope, request.receive) + try: + response: Response = await original_route_handler(request) + except HTTPException as e: + return error(e.status_code, e.detail) + except Exception as e: + form_err = request.form_err + if form_err: + return error(*form_err) + raise e + + return response + + return custom_route_handler + +app = FastAPI(debug=True, + title="VyOS API", + version="0.1.0", + responses={**responses}, + dependencies=[Depends(auth_required)]) + +app.router.route_class = MultipartRoute + +@app.exception_handler(RequestValidationError) +async def validation_exception_handler(request, exc): + return error(400, str(exc.errors()[0])) + +self_ref_msg = "Requested HTTP API server configuration change; commit will be called in the background" + +def call_commit(s: ConfigSession): + try: + s.commit() + except ConfigSessionError as e: + s.discard() + if app.state.vyos_debug: + logger.warning(f"ConfigSessionError:\n {traceback.format_exc()}") + else: + logger.warning(f"ConfigSessionError: {e}") + +def _configure_op(data: Union[ConfigureModel, ConfigureListModel, + ConfigSectionModel, ConfigSectionListModel, + ConfigSectionTreeModel], + request: Request, background_tasks: BackgroundTasks): + session = app.state.vyos_session + env = session.get_session_env() + + endpoint = request.url.path + + # Allow users to pass just one command + if not isinstance(data, (ConfigureListModel, ConfigSectionListModel)): + data = [data] + else: + data = data.commands + + # We don't want multiple people/apps to be able to commit at once, + # or modify the shared session while someone else is doing the same, + # so the lock is really global + lock.acquire() + + config = Config(session_env=env) + + status = 200 + msg = None + error_msg = None + try: + for c in data: + op = c.op + if not isinstance(c, BaseConfigSectionTreeModel): + path = c.path + + if isinstance(c, BaseConfigureModel): + if c.value: + value = c.value + else: + value = "" + # For vyos.configsession calls that have no separate value arguments, + # and for type checking too + cfg_path = " ".join(path + [value]).strip() + + elif isinstance(c, BaseConfigSectionModel): + section = c.section + + elif isinstance(c, BaseConfigSectionTreeModel): + mask = c.mask + config = c.config + + if isinstance(c, BaseConfigureModel): + if op == 'set': + session.set(path, value=value) + elif op == 'delete': + if app.state.vyos_strict and not config.exists(cfg_path): + raise ConfigSessionError(f"Cannot delete [{cfg_path}]: path/value does not exist") + session.delete(path, value=value) + elif op == 'comment': + session.comment(path, value=value) + else: + raise ConfigSessionError(f"'{op}' is not a valid operation") + + elif isinstance(c, BaseConfigSectionModel): + if op == 'set': + session.set_section(path, section) + elif op == 'load': + session.load_section(path, section) + else: + raise ConfigSessionError(f"'{op}' is not a valid operation") + + elif isinstance(c, BaseConfigSectionTreeModel): + if op == 'set': + session.set_section_tree(config) + elif op == 'load': + session.load_section_tree(mask, config) + else: + raise ConfigSessionError(f"'{op}' is not a valid operation") + # end for + config = Config(session_env=env) + d = get_config_diff(config) + + if d.is_node_changed(['service', 'https']): + background_tasks.add_task(call_commit, session) + msg = self_ref_msg + else: + # capture non-fatal warnings + out = session.commit() + msg = out if out else msg + + logger.info(f"Configuration modified via HTTP API using key '{app.state.vyos_id}'") + except ConfigSessionError as e: + session.discard() + status = 400 + if app.state.vyos_debug: + logger.critical(f"ConfigSessionError:\n {traceback.format_exc()}") + error_msg = str(e) + except Exception as e: + session.discard() + logger.critical(traceback.format_exc()) + status = 500 + + # Don't give the details away to the outer world + error_msg = "An internal error occured. Check the logs for details." + finally: + lock.release() + + if status != 200: + return error(status, error_msg) + + return success(msg) + +def create_path_import_pki_no_prompt(path): + correct_paths = ['ca', 'certificate', 'key-pair'] + if path[1] not in correct_paths: + return False + path[1] = '--' + path[1].replace('-', '') + path[3] = '--key-filename' + return path[1:] + +@app.post('/configure') +def configure_op(data: Union[ConfigureModel, + ConfigureListModel], + request: Request, background_tasks: BackgroundTasks): + return _configure_op(data, request, background_tasks) + +@app.post('/configure-section') +def configure_section_op(data: Union[ConfigSectionModel, + ConfigSectionListModel, + ConfigSectionTreeModel], + request: Request, background_tasks: BackgroundTasks): + return _configure_op(data, request, background_tasks) + +@app.post("/retrieve") +async def retrieve_op(data: RetrieveModel): + session = app.state.vyos_session + env = session.get_session_env() + config = Config(session_env=env) + + op = data.op + path = " ".join(data.path) + + try: + if op == 'returnValue': + res = config.return_value(path) + elif op == 'returnValues': + res = config.return_values(path) + elif op == 'exists': + res = config.exists(path) + elif op == 'showConfig': + config_format = 'json' + if data.configFormat: + config_format = data.configFormat + + res = session.show_config(path=data.path) + if config_format == 'json': + config_tree = ConfigTree(res) + res = json.loads(config_tree.to_json()) + elif config_format == 'json_ast': + config_tree = ConfigTree(res) + res = json.loads(config_tree.to_json_ast()) + elif config_format == 'raw': + pass + else: + return error(400, f"'{config_format}' is not a valid config format") + else: + return error(400, f"'{op}' is not a valid operation") + except ConfigSessionError as e: + return error(400, str(e)) + except Exception as e: + logger.critical(traceback.format_exc()) + return error(500, "An internal error occured. Check the logs for details.") + + return success(res) + +@app.post('/config-file') +def config_file_op(data: ConfigFileModel, background_tasks: BackgroundTasks): + session = app.state.vyos_session + env = session.get_session_env() + op = data.op + msg = None + + try: + if op == 'save': + if data.file: + path = data.file + else: + path = '/config/config.boot' + msg = session.save_config(path) + elif op == 'load': + if data.file: + path = data.file + else: + return error(400, "Missing required field \"file\"") + + session.migrate_and_load_config(path) + + config = Config(session_env=env) + d = get_config_diff(config) + + if d.is_node_changed(['service', 'https']): + background_tasks.add_task(call_commit, session) + msg = self_ref_msg + else: + session.commit() + else: + return error(400, f"'{op}' is not a valid operation") + except ConfigSessionError as e: + return error(400, str(e)) + except Exception as e: + logger.critical(traceback.format_exc()) + return error(500, "An internal error occured. Check the logs for details.") + + return success(msg) + +@app.post('/image') +def image_op(data: ImageModel): + session = app.state.vyos_session + + op = data.op + + try: + if op == 'add': + res = session.install_image(data.url) + elif op == 'delete': + res = session.remove_image(data.name) + elif op == 'show': + res = session.show(["system", "image"]) + elif op == 'set_default': + res = session.set_default_image(data.name) + except ConfigSessionError as e: + return error(400, str(e)) + except Exception as e: + logger.critical(traceback.format_exc()) + return error(500, "An internal error occured. Check the logs for details.") + + return success(res) + +@app.post('/container-image') +def container_image_op(data: ContainerImageModel): + session = app.state.vyos_session + + op = data.op + + try: + if op == 'add': + if data.name: + name = data.name + else: + return error(400, "Missing required field \"name\"") + res = session.add_container_image(name) + elif op == 'delete': + if data.name: + name = data.name + else: + return error(400, "Missing required field \"name\"") + res = session.delete_container_image(name) + elif op == 'show': + res = session.show_container_image() + else: + return error(400, f"'{op}' is not a valid operation") + except ConfigSessionError as e: + return error(400, str(e)) + except Exception as e: + logger.critical(traceback.format_exc()) + return error(500, "An internal error occured. Check the logs for details.") + + return success(res) + +@app.post('/generate') +def generate_op(data: GenerateModel): + session = app.state.vyos_session + + op = data.op + path = data.path + + try: + if op == 'generate': + res = session.generate(path) + else: + return error(400, f"'{op}' is not a valid operation") + except ConfigSessionError as e: + return error(400, str(e)) + except Exception as e: + logger.critical(traceback.format_exc()) + return error(500, "An internal error occured. Check the logs for details.") + + return success(res) + +@app.post('/show') +def show_op(data: ShowModel): + session = app.state.vyos_session + + op = data.op + path = data.path + + try: + if op == 'show': + res = session.show(path) + else: + return error(400, f"'{op}' is not a valid operation") + except ConfigSessionError as e: + return error(400, str(e)) + except Exception as e: + logger.critical(traceback.format_exc()) + return error(500, "An internal error occured. Check the logs for details.") + + return success(res) + +@app.post('/reboot') +def reboot_op(data: RebootModel): + session = app.state.vyos_session + + op = data.op + path = data.path + + try: + if op == 'reboot': + res = session.reboot(path) + else: + return error(400, f"'{op}' is not a valid operation") + except ConfigSessionError as e: + return error(400, str(e)) + except Exception as e: + logger.critical(traceback.format_exc()) + return error(500, "An internal error occured. Check the logs for details.") + + return success(res) + +@app.post('/reset') +def reset_op(data: ResetModel): + session = app.state.vyos_session + + op = data.op + path = data.path + + try: + if op == 'reset': + res = session.reset(path) + else: + return error(400, f"'{op}' is not a valid operation") + except ConfigSessionError as e: + return error(400, str(e)) + except Exception as e: + logger.critical(traceback.format_exc()) + return error(500, "An internal error occured. Check the logs for details.") + + return success(res) + +@app.post('/import-pki') +def import_pki(data: ImportPkiModel): + session = app.state.vyos_session + + op = data.op + path = data.path + + lock.acquire() + + try: + if op == 'import-pki': + # need to get rid or interactive mode for private key + if len(path) == 5 and path[3] in ['key-file', 'private-key']: + path_no_prompt = create_path_import_pki_no_prompt(path) + if not path_no_prompt: + return error(400, f"Invalid command: {' '.join(path)}") + if data.passphrase: + path_no_prompt += ['--passphrase', data.passphrase] + res = session.import_pki_no_prompt(path_no_prompt) + else: + res = session.import_pki(path) + if not res[0].isdigit(): + return error(400, res) + # commit changes + session.commit() + res = res.split('. ')[0] + else: + return error(400, f"'{op}' is not a valid operation") + except ConfigSessionError as e: + return error(400, str(e)) + except Exception as e: + logger.critical(traceback.format_exc()) + return error(500, "An internal error occured. Check the logs for details.") + finally: + lock.release() + + return success(res) + +@app.post('/poweroff') +def poweroff_op(data: PoweroffModel): + session = app.state.vyos_session + + op = data.op + path = data.path + + try: + if op == 'poweroff': + res = session.poweroff(path) + else: + return error(400, f"'{op}' is not a valid operation") + except ConfigSessionError as e: + return error(400, str(e)) + except Exception as e: + logger.critical(traceback.format_exc()) + return error(500, "An internal error occured. Check the logs for details.") + + return success(res) + + +### +# GraphQL integration +### + +def graphql_init(app: FastAPI = app): + from api.graphql.libs.token_auth import get_user_context + api.graphql.state.init() + api.graphql.state.settings['app'] = app + + # import after initializaion of state + from api.graphql.bindings import generate_schema + schema = generate_schema() + + in_spec = app.state.vyos_introspection + + if app.state.vyos_origins: + origins = app.state.vyos_origins + app.add_route('/graphql', CORSMiddleware(GraphQL(schema, + context_value=get_user_context, + debug=True, + introspection=in_spec), + allow_origins=origins, + allow_methods=("GET", "POST", "OPTIONS"), + allow_headers=("Authorization",))) + else: + app.add_route('/graphql', GraphQL(schema, + context_value=get_user_context, + debug=True, + introspection=in_spec)) +### +# Modify uvicorn to allow reloading server within the configsession +### + +server = None +shutdown = False + +class ApiServerConfig(UvicornConfig): + pass + +class ApiServer(UvicornServer): + def install_signal_handlers(self): + pass + +def reload_handler(signum, frame): + global server + logger.debug('Reload signal received...') + if server is not None: + server.handle_exit(signum, frame) + server = None + logger.info('Server stopping for reload...') + else: + logger.warning('Reload called for non-running server...') + +def shutdown_handler(signum, frame): + global shutdown + logger.debug('Shutdown signal received...') + server.handle_exit(signum, frame) + logger.info('Server shutdown...') + shutdown = True + +def flatten_keys(d: dict) -> list[dict]: + keys_list = [] + for el in list(d['keys'].get('id', {})): + key = d['keys']['id'][el].get('key', '') + if key: + keys_list.append({'id': el, 'key': key}) + return keys_list + +def initialization(session: ConfigSession, app: FastAPI = app): + global server + try: + server_config = load_server_config() + except Exception as e: + logger.critical(f'Failed to load the HTTP API server config: {e}') + sys.exit(1) + + app.state.vyos_session = session + app.state.vyos_keys = [] + + if 'keys' in server_config: + app.state.vyos_keys = flatten_keys(server_config) + + app.state.vyos_debug = bool('debug' in server_config) + app.state.vyos_strict = bool('strict' in server_config) + app.state.vyos_origins = server_config.get('cors', {}).get('allow_origin', []) + if 'graphql' in server_config: + app.state.vyos_graphql = True + if isinstance(server_config['graphql'], dict): + if 'introspection' in server_config['graphql']: + app.state.vyos_introspection = True + else: + app.state.vyos_introspection = False + # default values if not set explicitly + app.state.vyos_auth_type = server_config['graphql']['authentication']['type'] + app.state.vyos_token_exp = server_config['graphql']['authentication']['expiration'] + app.state.vyos_secret_len = server_config['graphql']['authentication']['secret_length'] + else: + app.state.vyos_graphql = False + + if app.state.vyos_graphql: + graphql_init(app) + + config = ApiServerConfig(app, uds="/run/api.sock", proxy_headers=True) + server = ApiServer(config) + +def run_server(): + try: + server.run() + except OSError as e: + logger.critical(e) + sys.exit(1) + +if __name__ == '__main__': + # systemd's user and group options don't work, do it by hand here, + # else no one else will be able to commit + cfg_group = grp.getgrnam(CFG_GROUP) + os.setgid(cfg_group.gr_gid) + + # Need to set file permissions to 775 too so that every vyattacfg group member + # has write access to the running config + os.umask(0o002) + + signal.signal(signal.SIGHUP, reload_handler) + signal.signal(signal.SIGTERM, shutdown_handler) + + config_session = ConfigSession(os.getpid()) + + while True: + logger.debug('Enter main loop...') + if shutdown: + break + if server is None: + initialization(config_session) + server.run() + sleep(1) diff --git a/src/shim/Makefile b/src/shim/Makefile new file mode 100644 index 0000000..c8487e3 --- /dev/null +++ b/src/shim/Makefile @@ -0,0 +1,20 @@ +DEBUG = 0 + +CC := gcc +CFLAGS := -I./mkjson -L./mkjson/lib -DDEBUG=${DEBUG} +LIBS := -lmkjson -lzmq + +.PHONY: vyshim +vyshim: vyshim.c libmkjson + $(CC) $(CFLAGS) -o $@ $< $(LIBS) + +.PHONY: libmkjson +libmkjson: + $(MAKE) -C mkjson + +all: vyshim + +.PHONY: clean +clean: + $(MAKE) -C mkjson clean + rm -f vyshim diff --git a/src/shim/mkjson/LICENSE b/src/shim/mkjson/LICENSE new file mode 100644 index 0000000..8c4284c --- /dev/null +++ b/src/shim/mkjson/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Jacek Wieczorek + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/shim/mkjson/makefile b/src/shim/mkjson/makefile new file mode 100644 index 0000000..ba75399 --- /dev/null +++ b/src/shim/mkjson/makefile @@ -0,0 +1,30 @@ +CFLAGS = -Wall -Os -I. +CC = gcc +AR = ar + +#USE_ASPRINTF make flag can be used in order to encourage asprintf use inside the library +ifeq ($(USE_ASPRINTF),1) +CFLAGS += -D_GNU_SOURCE +endif + +#Builds object and a static library file +all: clean force + $(CC) $(CFLAGS) -c mkjson.c -o obj/mkjson.o + $(AR) -cvq lib/libmkjson.a obj/mkjson.o + $(AR) -t lib/libmkjson.a + +#Normal cleanup +clean: + -rm -rf obj + -rm -rf lib + +#Environment init +force: + -mkdir obj + -mkdir lib + +#Build the example snippet +example: all + gcc -o example examples/example.c -I. -Llib -lmkjson + + diff --git a/src/shim/mkjson/mkjson.c b/src/shim/mkjson/mkjson.c new file mode 100644 index 0000000..1172664 --- /dev/null +++ b/src/shim/mkjson/mkjson.c @@ -0,0 +1,307 @@ +/* mkjson.c - a part of mkjson library + * + * Copyright (C) 2018 Jacek Wieczorek + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#include <mkjson.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> + +// Works like asprintf, but it's always there +// I don't want the name to collide with anything +static int allsprintf( char **strp, const char *fmt, ... ) +{ + int len; + va_list ap; + va_start( ap, fmt ); + + #ifdef _GNU_SOURCE + // Just hand everything to vasprintf, if it's available + len = vasprintf( strp, fmt, ap ); + #else + // Or do it the manual way + char *buf; + len = vsnprintf( NULL, 0, fmt, ap ); + if ( len >= 0 ) + { + buf = malloc( ++len ); + if ( buf != NULL ) + { + // Hopefully, that's the right way to do it + va_end( ap ); + va_start( ap, fmt ); + + // Write and return the data + len = vsnprintf( buf, len, fmt, ap ); + if ( len >= 0 ) + { + *strp = buf; + } + else + { + free( buf ); + } + } + } + #endif + + va_end( ap ); + return len; +} + +// Return JSON string built from va_arg arguments +// If no longer needed, should be passed to free() by user +char *mkjson( enum mkjson_container_type otype, int count, ... ) +{ + int i, len, goodchunks = 0, failure = 0; + char *json, *prefix, **chunks, ign; + + // Value - type and data + enum mkjson_value_type vtype; + const char *key; + long long int intval; + long double dblval; + const char *strval; + + // Since v0.9 count cannot be a negative value and datatype is indicated by a separate argument + // Since I'm not sure whether it's right to put assertions in libraries, the next line is commented out + // assert( count >= 0 && "After v0.9 negative count is prohibited; please use otype argument instead" ); + if ( count < 0 || ( otype != MKJSON_OBJ && otype != MKJSON_ARR ) ) return NULL; + + // Allocate chunk pointer array - on standard platforms each one should be NULL + chunks = calloc( count, sizeof( char* ) ); + if ( chunks == NULL ) return NULL; + + // This should rather be at the point of no return + va_list ap; + va_start( ap, count ); + + // Create chunks + for ( i = 0; i < count && !failure; i++ ) + { + // Get value type + vtype = va_arg( ap, enum mkjson_value_type ); + + // Get key + if ( otype == MKJSON_OBJ ) + { + key = va_arg( ap, char* ); + if ( key == NULL ) + { + failure = 1; + break; + } + } + else key = ""; + + // Generate prefix + if ( allsprintf( &prefix, "%s%s%s", + otype == MKJSON_OBJ ? "\"" : "", // Quote before key + key, // Key + otype == MKJSON_OBJ ? "\": " : "" ) == -1 ) // Quote and colon after key + { + failure = 1; + break; + } + + // Depending on value type + ign = 0; + switch ( vtype ) + { + // Ignore string / JSON data + case MKJSON_IGN_STRING: + case MKJSON_IGN_JSON: + (void) va_arg( ap, const char* ); + ign = 1; + break; + + // Ignore string / JSON data and pass the pointer to free + case MKJSON_IGN_STRING_FREE: + case MKJSON_IGN_JSON_FREE: + free( va_arg( ap, char* ) ); + ign = 1; + break; + + // Ignore int / long long int + case MKJSON_IGN_INT: + case MKJSON_IGN_LLINT: + if ( vtype == MKJSON_IGN_INT ) + (void) va_arg( ap, int ); + else + (void) va_arg( ap, long long int ); + ign = 1; + break; + + // Ignore double / long double + case MKJSON_IGN_DOUBLE: + case MKJSON_IGN_LDOUBLE: + if ( vtype == MKJSON_IGN_DOUBLE ) + (void) va_arg( ap, double ); + else + (void) va_arg( ap, long double ); + ign = 1; + break; + + // Ignore boolean + case MKJSON_IGN_BOOL: + (void) va_arg( ap, int ); + ign = 1; + break; + + // Ignore null value + case MKJSON_IGN_NULL: + ign = 1; + break; + + // A null-terminated string + case MKJSON_STRING: + case MKJSON_STRING_FREE: + strval = va_arg( ap, const char* ); + + // If the pointer points to NULL, the string will be replaced with JSON null value + if ( strval == NULL ) + { + if ( allsprintf( chunks + i, "%snull", prefix ) == -1 ) + chunks[i] = NULL; + } + else + { + if ( allsprintf( chunks + i, "%s\"%s\"", prefix, strval ) == -1 ) + chunks[i] = NULL; + } + + // Optional free + if ( vtype == MKJSON_STRING_FREE ) + free( (char*) strval ); + break; + + // Embed JSON data + case MKJSON_JSON: + case MKJSON_JSON_FREE: + strval = va_arg( ap, const char* ); + + // If the pointer points to NULL, the JSON data is replaced with null value + if ( allsprintf( chunks + i, "%s%s", prefix, strval == NULL ? "null" : strval ) == -1 ) + chunks[i] = NULL; + + // Optional free + if ( vtype == MKJSON_JSON_FREE ) + free( (char*) strval ); + break; + + // int / long long int + case MKJSON_INT: + case MKJSON_LLINT: + if ( vtype == MKJSON_INT ) + intval = va_arg( ap, int ); + else + intval = va_arg( ap, long long int ); + + if ( allsprintf( chunks + i, "%s%Ld", prefix, intval ) == -1 ) chunks[i] = NULL; + break; + + // double / long double + case MKJSON_DOUBLE: + case MKJSON_LDOUBLE: + if ( vtype == MKJSON_DOUBLE ) + dblval = va_arg( ap, double ); + else + dblval = va_arg( ap, long double ); + + if ( allsprintf( chunks + i, "%s%Lf", prefix, dblval ) == -1 ) chunks[i] = NULL; + break; + + // double / long double + case MKJSON_SCI_DOUBLE: + case MKJSON_SCI_LDOUBLE: + if ( vtype == MKJSON_SCI_DOUBLE ) + dblval = va_arg( ap, double ); + else + dblval = va_arg( ap, long double ); + + if ( allsprintf( chunks + i, "%s%Le", prefix, dblval ) == -1 ) chunks[i] = NULL; + break; + + // Boolean + case MKJSON_BOOL: + intval = va_arg( ap, int ); + if ( allsprintf( chunks + i, "%s%s", prefix, intval ? "true" : "false" ) == -1 ) chunks[i] = NULL; + break; + + // JSON null + case MKJSON_NULL: + if ( allsprintf( chunks + i, "%snull", prefix ) == -1 ) chunks[i] = NULL; + break; + + // Bad type specifier + default: + chunks[i] = NULL; + break; + } + + // Free prefix memory + free( prefix ); + + // NULL chunk without ignore flag indicates failure + if ( !ign && chunks[i] == NULL ) failure = 1; + + // NULL chunk now indicates ignore flag + if ( ign ) chunks[i] = NULL; + else goodchunks++; + } + + // We won't use ap anymore + va_end( ap ); + + // If everything is fine, merge chunks and create full JSON table + if ( !failure ) + { + // Get total length (this is without NUL byte) + len = 0; + for ( i = 0; i < count; i++ ) + if ( chunks[i] != NULL ) + len += strlen( chunks[i] ); + + // Total length = Chunks length + 2 brackets + separators + if ( goodchunks == 0 ) goodchunks = 1; + len = len + 2 + ( goodchunks - 1 ) * 2; + + // Allocate memory for the whole thing + json = calloc( len + 1, sizeof( char ) ); + if ( json != NULL ) + { + // Merge chunks (and do not overwrite the first bracket) + for ( i = 0; i < count; i++ ) + { + // Add separators: + // - not on the begining + // - always after valid chunk + // - between two valid chunks + // - between valid and ignored chunk if the latter isn't the last one + if ( i != 0 && chunks[i - 1] != NULL && ( chunks[i] != NULL || ( chunks[i] == NULL && i != count - 1 ) ) ) + strcat( json + 1, ", "); + + if ( chunks[i] != NULL ) + strcat( json + 1, chunks[i] ); + } + + // Add proper brackets + json[0] = otype == MKJSON_OBJ ? '{' : '['; + json[len - 1] = otype == MKJSON_OBJ ? '}' : ']'; + } + } + else json = NULL; + + // Free chunks + for ( i = 0; i < count; i++ ) + free( chunks[i] ); + free( chunks ); + + return json; +} + diff --git a/src/shim/mkjson/mkjson.h b/src/shim/mkjson/mkjson.h new file mode 100644 index 0000000..38cc07b --- /dev/null +++ b/src/shim/mkjson/mkjson.h @@ -0,0 +1,50 @@ +/* mkjson.h - a part of mkjson library + * + * Copyright (C) 2018 Jacek Wieczorek + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#ifndef MKJSON_H +#define MKJSON_H + +// JSON container types +enum mkjson_container_type +{ + MKJSON_ARR = 0, // An array + MKJSON_OBJ = 1 // An object (hash or whatever you call it) +}; + +// JSON data types +enum mkjson_value_type +{ + MKJSON_STRING = (int)('s'), // const char* - String data + MKJSON_STRING_FREE = (int)('f'), // char* - String data, but pointer is freed + MKJSON_JSON = (int)('r'), // const char* - JSON data (like string, but no quotes) + MKJSON_JSON_FREE = (int)('j'), // char* - JSON data, but pointer is freed + MKJSON_INT = (int)('i'), // int - An integer + MKJSON_LLINT = (int)('I'), // long long int - A long integer + MKJSON_DOUBLE = (int)('d'), // double - A double + MKJSON_LDOUBLE = (int)('D'), // long double - A long double + MKJSON_SCI_DOUBLE = (int)('e'), // double - A double with scientific notation + MKJSON_SCI_LDOUBLE = (int)('E'), // long double - A long double with scientific notation + MKJSON_BOOL = (int)('b'), // int - A boolean value + MKJSON_NULL = (int)('n'), // -- - JSON null value + + // These cause one argument of certain type to be ignored + MKJSON_IGN_STRING = (-MKJSON_STRING), + MKJSON_IGN_STRING_FREE = (-MKJSON_STRING_FREE), + MKJSON_IGN_JSON = (-MKJSON_JSON), + MKJSON_IGN_JSON_FREE = (-MKJSON_JSON_FREE), + MKJSON_IGN_INT = (-MKJSON_INT), + MKJSON_IGN_LLINT = (-MKJSON_LLINT), + MKJSON_IGN_DOUBLE = (-MKJSON_DOUBLE), + MKJSON_IGN_LDOUBLE = (-MKJSON_LDOUBLE), + MKJSON_IGN_BOOL = (-MKJSON_BOOL), + MKJSON_IGN_NULL = (-MKJSON_NULL) +}; + +extern char *mkjson( enum mkjson_container_type otype, int count, ... ); + +#endif diff --git a/src/shim/vyshim.c b/src/shim/vyshim.c new file mode 100644 index 0000000..68e6c40 --- /dev/null +++ b/src/shim/vyshim.c @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2020-2024 VyOS maintainers and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 or later as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <string.h> +#include <sys/time.h> +#include <time.h> +#include <stdint.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <zmq.h> +#include "mkjson.h" + +/* + * + * + */ + +#if DEBUG +#define DEBUG_ON 1 +#else +#define DEBUG_ON 0 +#endif +#define debug_print(fmt, ...) \ + do { if (DEBUG_ON) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0) +#define debug_call(f) \ + do { if (DEBUG_ON) f; } while (0) + +#define SOCKET_PATH "ipc:///run/vyos-configd.sock" + +#define GET_ACTIVE "cli-shell-api --show-active-only --show-show-defaults --show-ignore-edit showConfig" +#define GET_SESSION "cli-shell-api --show-working-only --show-show-defaults --show-ignore-edit showConfig" + +#define COMMIT_MARKER "/var/tmp/initial_in_commit" +#define QUEUE_MARKER "/var/tmp/last_in_queue" + +enum { + SUCCESS = 1 << 0, + ERROR_COMMIT = 1 << 1, + ERROR_DAEMON = 1 << 2, + PASS = 1 << 3 +}; + +volatile int init_alarm = 0; +volatile int timeout = 0; + +int initialization(void *); +int pass_through(char **, int); +void timer_handler(int); + +double get_posix_clock_time(void); + +static char * s_recv_string (void *, int); + +int main(int argc, char* argv[]) +{ + // string for node data: conf_mode script and tagnode, if applicable + char string_node_data[256]; + string_node_data[0] = '\0'; + + void *context = zmq_ctx_new(); + void *requester = zmq_socket(context, ZMQ_REQ); + + int ex_index; + int init_timeout = 0; + int last = 0; + + debug_print("Connecting to vyos-configd ...\n"); + zmq_connect(requester, SOCKET_PATH); + + for (int i = 1; i < argc ; i++) { + strncat(&string_node_data[0], argv[i], 127); + } + + debug_print("data to send: %s\n", string_node_data); + + char *test = strstr(string_node_data, "VYOS_TAGNODE_VALUE"); + ex_index = test ? 2 : 1; + + if (access(COMMIT_MARKER, F_OK) != -1) { + init_timeout = initialization(requester); + if (!init_timeout) remove(COMMIT_MARKER); + } + + // if initial communication failed, pass through execution of script + if (init_timeout) { + int ret = pass_through(argv, ex_index); + return ret; + } + + if (access(QUEUE_MARKER, F_OK) != -1) { + last = 1; + remove(QUEUE_MARKER); + } + + char error_code[1]; + debug_print("Sending node data ...\n"); + char *string_node_data_msg = mkjson(MKJSON_OBJ, 3, + MKJSON_STRING, "type", "node", + MKJSON_BOOL, "last", last, + MKJSON_STRING, "data", &string_node_data[0]); + + zmq_send(requester, string_node_data_msg, strlen(string_node_data_msg), 0); + zmq_recv(requester, error_code, 1, 0); + debug_print("Received node data receipt\n"); + + char msg_size_str[7]; + zmq_send(requester, "msg_size", 8, 0); + zmq_recv(requester, msg_size_str, 6, 0); + msg_size_str[6] = '\0'; + int msg_size = (int)strtol(msg_size_str, NULL, 16); + debug_print("msg_size: %d\n", msg_size); + + if (msg_size > 0) { + zmq_send(requester, "send", 4, 0); + char *msg = s_recv_string(requester, msg_size); + printf("%s", msg); + free(msg); + } + + free(string_node_data_msg); + + int err = (int)error_code[0]; + int ret = 0; + + if (err & PASS) { + debug_print("Received PASS\n"); + ret = pass_through(argv, ex_index); + } + + if (err & ERROR_DAEMON) { + debug_print("Received ERROR_DAEMON\n"); + ret = pass_through(argv, ex_index); + } + + if (err & ERROR_COMMIT) { + debug_print("Received ERROR_COMMIT\n"); + ret = -1; + } + + zmq_close(requester); + zmq_ctx_destroy(context); + + return ret; +} + +int initialization(void* Requester) +{ + char *active_str = NULL; + size_t active_len = 0; + + char *session_str = NULL; + size_t session_len = 0; + + char *empty_string = "\n"; + + char buffer[16]; + + struct sigaction sa; + struct itimerval timer, none_timer; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = &timer_handler; + sigaction(SIGALRM, &sa, NULL); + + timer.it_value.tv_sec = 0; + timer.it_value.tv_usec = 10000; + timer.it_interval.tv_sec = timer.it_interval.tv_usec = 0; + none_timer.it_value.tv_sec = none_timer.it_value.tv_usec = 0; + none_timer.it_interval.tv_sec = none_timer.it_interval.tv_usec = 0; + + double prev_time_value, time_value; + double time_diff; + + char *pid_val = getenv("VYATTA_CONFIG_TMP"); + strsep(&pid_val, "_"); + debug_print("config session pid: %s\n", pid_val); + + char *sudo_user = getenv("SUDO_USER"); + if (!sudo_user) { + char nobody[] = "nobody"; + sudo_user = nobody; + } + debug_print("sudo_user is %s\n", sudo_user); + + char *temp_config_dir = getenv("VYATTA_TEMP_CONFIG_DIR"); + if (!temp_config_dir) { + char none[] = ""; + temp_config_dir = none; + } + debug_print("temp_config_dir is %s\n", temp_config_dir); + + char *changes_only_dir = getenv("VYATTA_CHANGES_ONLY_DIR"); + if (!changes_only_dir) { + char none[] = ""; + changes_only_dir = none; + } + debug_print("changes_only_dir is %s\n", changes_only_dir); + + debug_print("Sending init announcement\n"); + char *init_announce = mkjson(MKJSON_OBJ, 1, + MKJSON_STRING, "type", "init"); + + // check for timeout on initial contact + while (!init_alarm) { + debug_call(prev_time_value = get_posix_clock_time()); + + setitimer(ITIMER_REAL, &timer, NULL); + + zmq_send(Requester, init_announce, strlen(init_announce), 0); + zmq_recv(Requester, buffer, 16, 0); + + setitimer(ITIMER_REAL, &none_timer, &timer); + + debug_call(time_value = get_posix_clock_time()); + + debug_print("Received init receipt\n"); + debug_call(time_diff = time_value - prev_time_value); + debug_print("time elapse %f\n", time_diff); + + break; + } + + free(init_announce); + + if (timeout) return -1; + + FILE *fp_a = popen(GET_ACTIVE, "r"); + getdelim(&active_str, &active_len, '\0', fp_a); + int ret = pclose(fp_a); + + if (!ret) { + debug_print("Sending active config\n"); + zmq_send(Requester, active_str, active_len - 1, 0); + zmq_recv(Requester, buffer, 16, 0); + debug_print("Received active receipt\n"); + } else { + debug_print("Sending empty active config\n"); + zmq_send(Requester, empty_string, 0, 0); + zmq_recv(Requester, buffer, 16, 0); + debug_print("Received active receipt\n"); + } + + free(active_str); + + FILE *fp_s = popen(GET_SESSION, "r"); + getdelim(&session_str, &session_len, '\0', fp_s); + pclose(fp_s); + + debug_print("Sending session config\n"); + zmq_send(Requester, session_str, session_len - 1, 0); + zmq_recv(Requester, buffer, 16, 0); + debug_print("Received session receipt\n"); + + free(session_str); + + debug_print("Sending config session pid\n"); + zmq_send(Requester, pid_val, strlen(pid_val), 0); + zmq_recv(Requester, buffer, 16, 0); + debug_print("Received pid receipt\n"); + + debug_print("Sending config session sudo_user\n"); + zmq_send(Requester, sudo_user, strlen(sudo_user), 0); + zmq_recv(Requester, buffer, 16, 0); + debug_print("Received sudo_user receipt\n"); + + debug_print("Sending config session temp_config_dir\n"); + zmq_send(Requester, temp_config_dir, strlen(temp_config_dir), 0); + zmq_recv(Requester, buffer, 16, 0); + debug_print("Received temp_config_dir receipt\n"); + + debug_print("Sending config session changes_only_dir\n"); + zmq_send(Requester, changes_only_dir, strlen(changes_only_dir), 0); + zmq_recv(Requester, buffer, 16, 0); + debug_print("Received changes_only_dir receipt\n"); + + return 0; +} + +int pass_through(char **argv, int ex_index) +{ + char **newargv = NULL; + pid_t child_pid; + + newargv = &argv[ex_index]; + if (ex_index > 1) { + putenv(argv[ex_index - 1]); + } + + debug_print("pass-through invoked\n"); + + if ((child_pid=fork()) < 0) { + debug_print("fork() failed\n"); + return -1; + } else if (child_pid == 0) { + if (-1 == execv(argv[ex_index], newargv)) { + debug_print("pass_through execve failed %s: %s\n", + argv[ex_index], strerror(errno)); + return -1; + } + } else if (child_pid > 0) { + int status; + pid_t wait_pid = waitpid(child_pid, &status, 0); + if (wait_pid < 0) { + debug_print("waitpid() failed\n"); + return -1; + } else if (wait_pid == child_pid) { + if (WIFEXITED(status)) { + debug_print("child exited with code %d\n", + WEXITSTATUS(status)); + return WEXITSTATUS(status); + } + } + } + + return 0; +} + +void timer_handler(int signum) +{ + debug_print("timer_handler invoked\n"); + timeout = 1; + init_alarm = 1; + + return; +} + +#ifdef _POSIX_MONOTONIC_CLOCK +double get_posix_clock_time(void) +{ + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { + return (double) (ts.tv_sec + ts.tv_nsec / 1000000000.0); + } else { + return 0; + } +} +#else +double get_posix_clock_time(void) +{return (double)0;} +#endif + +// Receive string from socket and convert into C string +static char * s_recv_string (void *socket, int bufsize) { + char * buffer = (char *)malloc(bufsize+1); + int size = zmq_recv(socket, buffer, bufsize, 0); + if (size == -1) + return NULL; + if (size > bufsize) + size = bufsize; + buffer[size] = '\0'; + return buffer; +} diff --git a/src/system/grub_update.py b/src/system/grub_update.py new file mode 100644 index 0000000..5a05341 --- /dev/null +++ b/src/system/grub_update.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This file is part of VyOS. +# +# VyOS is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# VyOS is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# VyOS. If not, see <https://www.gnu.org/licenses/>. + +from pathlib import Path +from sys import exit + +from vyos.system import disk, grub, image, compat, SYSTEM_CFG_VER +from vyos.template import render + + +def cfg_check_update() -> bool: + """Check if GRUB structure update is required + + Returns: + bool: False if not required, True if required + """ + current_ver = grub.get_cfg_ver() + if current_ver and current_ver >= SYSTEM_CFG_VER: + return False + + return True + + +if __name__ == '__main__': + if image.is_live_boot(): + exit(0) + + if image.is_running_as_container(): + exit(0) + + # Skip everything if update is not required + if not cfg_check_update(): + exit(0) + + # find root directory of persistent storage + root_dir = disk.find_persistence() + + # read current GRUB config + grub_cfg_main = f'{root_dir}/{grub.GRUB_CFG_MAIN}' + vars = grub.vars_read(grub_cfg_main) + modules = grub.modules_read(grub_cfg_main) + vyos_menuentries = compat.parse_menuentries(grub_cfg_main) + vyos_versions = compat.find_versions(vyos_menuentries) + unparsed_items = compat.filter_unparsed(grub_cfg_main) + # compatibilty for raid installs + search_root = compat.get_search_root(unparsed_items) + common_dict = {} + common_dict['search_root'] = search_root + # find default values + default_entry = vyos_menuentries[int(vars['default'])] + default_settings = { + 'default': grub.gen_version_uuid(default_entry['version']), + 'bootmode': default_entry['bootmode'], + 'console_type': default_entry['console_type'], + 'console_num': default_entry['console_num'], + 'console_speed': default_entry['console_speed'] + } + vars.update(default_settings) + + # create new files + grub_cfg_vars = f'{root_dir}/{grub.CFG_VYOS_VARS}' + grub_cfg_modules = f'{root_dir}/{grub.CFG_VYOS_MODULES}' + grub_cfg_platform = f'{root_dir}/{grub.CFG_VYOS_PLATFORM}' + grub_cfg_menu = f'{root_dir}/{grub.CFG_VYOS_MENU}' + grub_cfg_options = f'{root_dir}/{grub.CFG_VYOS_OPTIONS}' + + Path(image.GRUB_DIR_VYOS).mkdir(exist_ok=True) + grub.vars_write(grub_cfg_vars, vars) + grub.modules_write(grub_cfg_modules, modules) + grub.common_write(grub_common=common_dict) + render(grub_cfg_menu, grub.TMPL_GRUB_MENU, {}) + render(grub_cfg_options, grub.TMPL_GRUB_OPTS, {}) + + # create menu entries + for vyos_ver in vyos_versions: + boot_opts = None + for entry in vyos_menuentries: + if entry.get('version') == vyos_ver and entry.get( + 'bootmode') == 'normal': + boot_opts = entry.get('boot_opts') + grub.version_add(vyos_ver, root_dir, boot_opts) + + # update structure version + cfg_ver = compat.update_cfg_ver(root_dir) + grub.write_cfg_ver(cfg_ver, root_dir) + + if compat.mode(): + compat.render_grub_cfg(root_dir) + else: + render(grub_cfg_main, grub.TMPL_GRUB_MAIN, {}) + + # sort inodes (to make GRUB read config files in alphabetical order) + grub.sort_inodes(f'{root_dir}/{grub.GRUB_DIR_VYOS}') + grub.sort_inodes(f'{root_dir}/{grub.GRUB_DIR_VYOS_VERS}') + + exit(0) diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py new file mode 100644 index 0000000..2473380 --- /dev/null +++ b/src/system/keepalived-fifo.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import time +import signal +import argparse +import threading +import re +import logging + +from queue import Queue +from logging.handlers import SysLogHandler + +from vyos.configquery import ConfigTreeQuery +from vyos.utils.process import cmd +from vyos.utils.dict import dict_search +from vyos.utils.commit import commit_in_progress + +# configure logging +logger = logging.getLogger(__name__) +logs_format = logging.Formatter('%(filename)s: %(message)s') +logs_handler_syslog = SysLogHandler('/dev/log') +logs_handler_syslog.setFormatter(logs_format) +logger.addHandler(logs_handler_syslog) +logger.setLevel(logging.DEBUG) + +mdns_running_file = '/run/mdns_vrrp_active' +mdns_update_command = 'sudo /usr/libexec/vyos/conf_mode/service_mdns_repeater.py' + +# class for all operations +class KeepalivedFifo: + # init - read command arguments + def __init__(self): + logger.info('Starting FIFO pipe for Keepalived') + # define program arguments + cmd_args_parser = argparse.ArgumentParser(description='Create FIFO pipe for keepalived and process notify events', add_help=False) + cmd_args_parser.add_argument('PIPE', help='path to the FIFO pipe') + # parse arguments + cmd_args = cmd_args_parser.parse_args() + + self._config_load() + self.pipe_path = cmd_args.PIPE + + # create queue for messages and events for syncronization + self.message_queue = Queue(maxsize=100) + self.stopme = threading.Event() + self.message_event = threading.Event() + + # load configuration + def _config_load(self): + # For VRRP configuration to be read, the commit must be finished + count = 1 + while commit_in_progress(): + if ( count <= 20 ): + logger.debug(f'Attempt to load keepalived configuration aborted due to a commit in progress (attempt {count}/20)') + else: + logger.error(f'Forced keepalived configuration loading despite a commit in progress ({count} wait time expired, not waiting further)') + break + count += 1 + time.sleep(1) + + try: + base = ['high-availability', 'vrrp'] + conf = ConfigTreeQuery() + if not conf.exists(base): + raise ValueError() + + # Read VRRP configuration directly from CLI + self.vrrp_config_dict = conf.get_config_dict(base, + key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + logger.debug(f'Loaded configuration: {self.vrrp_config_dict}') + except Exception as err: + logger.error(f'Unable to load configuration: {err}') + + # run command + def _run_command(self, command): + logger.debug(f'Running the command: {command}') + try: + cmd(command) + except OSError as err: + logger.error(f'Unable to execute command "{command}": {err}') + + # create FIFO pipe + def pipe_create(self): + if os.path.exists(self.pipe_path): + logger.info(f'PIPE already exist: {self.pipe_path}') + else: + os.mkfifo(self.pipe_path) + + # process message from pipe + def pipe_process(self): + logger.debug('Message processing start') + regex_notify = re.compile(r'^(?P<type>\w+) "(?P<name>[\w-]+)" (?P<state>\w+) (?P<priority>\d+)$', re.MULTILINE) + while self.stopme.is_set() is False: + # wait for a new message event from pipe_wait + self.message_event.wait() + try: + # clear mesage event flag + self.message_event.clear() + # get all messages from queue and try to process them + while self.message_queue.empty() is not True: + message = self.message_queue.get() + logger.debug(f'Received message: {message}') + notify_message = regex_notify.search(message) + # try to process a message if it looks valid + if notify_message: + n_type = notify_message.group('type') + n_name = notify_message.group('name') + n_state = notify_message.group('state') + logger.info(f'{n_type} {n_name} changed state to {n_state}') + # check and run commands for VRRP instances + if n_type == 'INSTANCE': + if os.path.exists(mdns_running_file): + cmd(mdns_update_command) + + tmp = dict_search(f'group.{n_name}.transition_script.{n_state.lower()}', self.vrrp_config_dict) + if tmp != None: + self._run_command(tmp) + # check and run commands for VRRP sync groups + elif n_type == 'GROUP': + if os.path.exists(mdns_running_file): + cmd(mdns_update_command) + + tmp = dict_search(f'sync_group.{n_name}.transition_script.{n_state.lower()}', self.vrrp_config_dict) + if tmp != None: + self._run_command(tmp) + # mark task in queue as done + self.message_queue.task_done() + except Exception as err: + logger.error(f'Error processing message: {err}') + logger.debug('Terminating messages processing thread') + + # wait for messages + def pipe_wait(self): + logger.debug('Message reading start') + self.pipe_read = os.open(self.pipe_path, os.O_RDONLY | os.O_NONBLOCK) + while self.stopme.is_set() is False: + # sleep a bit to not produce 100% CPU load + time.sleep(0.250) + try: + # try to read a message from PIPE + message = os.read(self.pipe_read, 500) + if message: + # split PIPE content by lines and put them into queue + for line in message.decode().strip().splitlines(): + self.message_queue.put(line) + # set new message flag to start processing + self.message_event.set() + except Exception as err: + # ignore the "Resource temporarily unavailable" error + if err.errno != 11: + logger.error(f'Error receiving message: {err}') + + logger.debug('Closing FIFO pipe') + os.close(self.pipe_read) + +# handle SIGTERM signal to allow finish all messages processing +def sigterm_handle(signum, frame): + logger.info('Ending processing: Received SIGTERM signal') + fifo.stopme.set() + thread_wait_message.join() + fifo.message_event.set() + thread_process_message.join() + +signal.signal(signal.SIGTERM, sigterm_handle) + +# init our class +fifo = KeepalivedFifo() +# try to create PIPE if it is not exist yet +# It looks like keepalived do it before the script will be running, but if we +# will decide to run this not from keepalived config, then we may get in +# trouble. So it is betteer to leave this here. +fifo.pipe_create() +# create and run dedicated threads for reading and processing messages +thread_wait_message = threading.Thread(target=fifo.pipe_wait) +thread_process_message = threading.Thread(target=fifo.pipe_process) +thread_wait_message.start() +thread_process_message.start() diff --git a/src/system/normalize-ip b/src/system/normalize-ip new file mode 100644 index 0000000..08f922a --- /dev/null +++ b/src/system/normalize-ip @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +# Normalizes IPv6 addresses so that they can be passed to iproute2, +# since iproute2 will not take an address with leading zeroes for an argument + +import re +import sys +import ipaddress + + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("Argument required") + sys.exit(1) + + address_string, prefix_length = re.match(r'(.+)/(.+)', sys.argv[1]).groups() + + try: + address = ipaddress.IPv6Address(address_string) + normalized_address = address.compressed + except ipaddress.AddressValueError: + # It's likely an IPv4 address, do nothing + normalized_address = address_string + + print("{0}/{1}".format(normalized_address, prefix_length)) + sys.exit(0) + diff --git a/src/system/on-dhcp-event.sh b/src/system/on-dhcp-event.sh new file mode 100644 index 0000000..47c2762 --- /dev/null +++ b/src/system/on-dhcp-event.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +if [ $# -lt 1 ]; then + echo Invalid args + logger -s -t on-dhcp-event "Invalid args \"$@\"" + exit 1 +fi + +action=$1 +hostsd_client="/usr/bin/vyos-hostsd-client" + +get_subnet_domain_name () { + python3 <<EOF +from vyos.kea import kea_get_active_config +from vyos.utils.dict import dict_search_args + +config = kea_get_active_config('4') +shared_networks = dict_search_args(config, 'arguments', f'Dhcp4', 'shared-networks') + +found = False + +if shared_networks: + for network in shared_networks: + for subnet in network[f'subnet4']: + if subnet['id'] == $1: + for option in subnet['option-data']: + if option['name'] == 'domain-name': + print(option['data']) + found = True + + if not found: + for option in network['option-data']: + if option['name'] == 'domain-name': + print(option['data']) +EOF +} + +case "$action" in + lease4_renew|lease4_recover) + exit 0 + ;; + + lease4_release|lease4_expire|lease4_decline) # delete mapping for released/declined address + client_ip=$LEASE4_ADDRESS + $hostsd_client --delete-hosts --tag "dhcp-server-$client_ip" --apply + exit 0 + ;; + + leases4_committed) # process committed leases (added/renewed/recovered) + for ((i = 0; i < $LEASES4_SIZE; i++)); do + client_ip_var="LEASES4_AT${i}_ADDRESS" + client_mac_var="LEASES4_AT${i}_HWADDR" + client_name_var="LEASES4_AT${i}_HOSTNAME" + client_subnet_id_var="LEASES4_AT${i}_SUBNET_ID" + + client_ip=${!client_ip_var} + client_mac=${!client_mac_var} + client_name=${!client_name_var%.} + client_subnet_id=${!client_subnet_id_var} + + if [ -z "$client_name" ]; then + logger -s -t on-dhcp-event "Client name was empty, using MAC \"$client_mac\" instead" + client_name=$(echo "host-$client_mac" | tr : -) + fi + + client_domain=$(get_subnet_domain_name $client_subnet_id) + + if [[ -n "$client_domain" ]] && ! [[ $client_name =~ .*$client_domain$ ]]; then + client_name="$client_name.$client_domain" + fi + + $hostsd_client --add-hosts "$client_name,$client_ip" --tag "dhcp-server-$client_ip" --apply + done + + exit 0 + ;; + + *) + logger -s -t on-dhcp-event "Invalid command \"$1\"" + exit 1 + ;; +esac diff --git a/src/system/on-dhcpv6-event.sh b/src/system/on-dhcpv6-event.sh new file mode 100644 index 0000000..cbb3709 --- /dev/null +++ b/src/system/on-dhcpv6-event.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +if [ $# -lt 1 ]; then + echo Invalid args + logger -s -t on-dhcpv6-event "Invalid args \"$@\"" + exit 1 +fi + +action=$1 + +case "$action" in + lease6_renew|lease6_recover) + exit 0 + ;; + + lease6_release|lease6_expire|lease6_decline) + ifname=$QUERY6_IFACE_NAME + lease_addr=$LEASE6_ADDRESS + lease_prefix_len=$LEASE6_PREFIX_LEN + + if [[ "$LEASE6_TYPE" != "IA_PD" ]]; then + exit 0 + fi + + logger -s -t on-dhcpv6-event "Processing route deletion for ${lease_addr}/${lease_prefix_len}" + route_cmd="sudo -n /sbin/ip -6 route del ${lease_addr}/${lease_prefix_len}" + + # the ifname is not always present, like in LEASE6_VALID_LIFETIME=0 updates, + # but 'route del' works either way. Use interface only if there is one. + if [[ "$ifname" != "" ]]; then + route_cmd+=" dev ${ifname}" + fi + route_cmd+=" proto static" + eval "$route_cmd" + + exit 0 + ;; + + leases6_committed) + for ((i = 0; i < $LEASES6_SIZE; i++)); do + ifname=$QUERY6_IFACE_NAME + requester_link_local=$QUERY6_REMOTE_ADDR + lease_type_var="LEASES6_AT${i}_TYPE" + lease_ip_var="LEASES6_AT${i}_ADDRESS" + lease_prefix_len_var="LEASES6_AT${i}_PREFIX_LEN" + + lease_type=${!lease_type_var} + + if [[ "$lease_type" != "IA_PD" ]]; then + continue + fi + + lease_ip=${!lease_ip_var} + lease_prefix_len=${!lease_prefix_len_var} + + logger -s -t on-dhcpv6-event "Processing PD route for ${lease_addr}/${lease_prefix_len}. Link local: ${requester_link_local} ifname: ${ifname}" + + sudo -n /sbin/ip -6 route replace ${lease_ip}/${lease_prefix_len} \ + via ${requester_link_local} \ + dev ${ifname} \ + proto static + done + + exit 0 + ;; + + *) + logger -s -t on-dhcpv6-event "Invalid command \"$1\"" + exit 1 + ;; +esac diff --git a/src/system/post-upgrade b/src/system/post-upgrade new file mode 100644 index 0000000..41b7c01 --- /dev/null +++ b/src/system/post-upgrade @@ -0,0 +1,3 @@ +#!/bin/sh + +chown -R root:vyattacfg /config diff --git a/src/system/standalone_root_pw_reset b/src/system/standalone_root_pw_reset new file mode 100644 index 0000000..c82cea3 --- /dev/null +++ b/src/system/standalone_root_pw_reset @@ -0,0 +1,178 @@ +#!/bin/bash +# **** License **** +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# This code was originally developed by Vyatta, Inc. +# Portions created by Vyatta are Copyright (C) 2007 Vyatta, Inc. +# All Rights Reserved. +# +# Author: Bob Gilligan <gilligan@vyatta.com> +# Description: Standalone script to set the admin passwd to new value +# value. Note: This script can ONLY be run as a standalone +# init program by grub. +# +# **** End License **** + +# The Vyatta config file: +CF=/opt/vyatta/etc/config/config.boot + +# Admin user name +ADMIN=vyos + +set_encrypted_password() { + sed -i \ + -e "/ user $1 {/,/encrypted-password/s/encrypted-password .*\$/encrypted-password \"$2\"/" $3 +} + + +# How long to wait for user to respond, in seconds +TIME_TO_WAIT=30 + +change_password() { + local user=$1 + local pwd1="1" + local pwd2="2" + + until [ "$pwd1" == "$pwd2" ] + do + read -p "Enter $user password: " -r -s pwd1 + echo + read -p "Retype $user password: " -r -s pwd2 + echo + + if [ "$pwd1" != "$pwd2" ] + then echo "Passwords do not match" + fi + done + + # set the password for the user then store it in the config + # so the user is recreated on the next full system boot. + local epwd=$(mkpasswd --method=sha-512 "$pwd1") + # escape any slashes in resulting password + local eepwd=$(sed 's:/:\\/:g' <<< $epwd) + set_encrypted_password $user $eepwd $CF +} + +# System is so messed up that doing anything would be a mistake +dead() { + echo $* + echo + echo "This tool can only recover missing admininistrator password." + echo "It is not a full system restore" + echo + echo -n "Hit return to reboot system: " + read + /sbin/reboot -f +} + +echo "Standalone root password recovery tool." +echo +# +# Check to see if we are running in standalone mode. We'll +# know that we are if our pid is 1. +# +if [ "$$" != "1" ]; then + echo "This tool can only be run in standalone mode." + exit 1 +fi + +# +# OK, now we know we are running in standalone mode. Talk to the +# user. +# +echo -n "Do you wish to reset the admin password? (y or n) " +read -t $TIME_TO_WAIT response +if [ "$?" != "0" ]; then + echo + echo "Response not received in time." + echo "The admin password will not be reset." + echo "Rebooting in 5 seconds..." + sleep 5 + echo + /sbin/reboot -f +fi + +response=${response:0:1} +if [ "$response" != "y" -a "$response" != "Y" ]; then + echo "OK, the admin password will not be reset." + echo -n "Rebooting in 5 seconds..." + sleep 5 + echo + /sbin/reboot -f +fi + +echo -en "Which admin account do you want to reset? [$ADMIN] " +read admin_user +ADMIN=${admin_user:-$ADMIN} + +echo "Starting process to reset the admin password..." + +echo "Re-mounting root filesystem read/write..." +mount -o remount,rw / + +if [ ! -f /etc/passwd ] +then dead "Missing password file" +fi + +if [ ! -d /opt/vyatta/etc/config ] +then dead "Missing VyOS config directory /opt/vyatta/etc/config" +fi + +# Leftover from V3.0 +if grep -q /opt/vyatta/etc/config /etc/fstab +then + echo "Mounting the config filesystem..." + mount /opt/vyatta/etc/config/ +fi + +if [ ! -f $CF ] +then dead "$CF file not found" +fi + +if ! grep -q 'system {' $CF +then dead "$CF file does not contain system settings" +fi + +if ! grep -q ' login {' $CF +then + # Recreate login section of system + sed -i -e '/system {/a\ + login {\ + }' $CF +fi + +if ! grep -q " user $ADMIN " $CF +then + echo "Recreating administrator $ADMIN in $CF..." + sed -i -e "/ login {/a\\ + user $ADMIN {\\ + authentication {\\ + encrypted-password \$6$IhbXHdwgYkLnt/$VRIsIN5c2f2v4L2l4F9WPDrRDEtWXzH75yBswmWGERAdX7oBxmq6m.sWON6pO6mi6mrVgYBxdVrFcCP5bI.nt.\\ + plaintext-password \"\"\\ + }\\ + level admin\\ + }" $CF +fi + +echo "Saving backup copy of config.boot..." +cp $CF ${CF}.before_pwrecovery +sync + +echo "Setting the administrator ($ADMIN) password..." +change_password $ADMIN + +echo $(date "+%b%e %T") $(hostname) "Admin password changed" \ + | tee -a /var/log/auth.log >>/var/log/messages + +sync + +echo "System will reboot in 10 seconds..." +sleep 10 +/sbin/reboot -f diff --git a/src/system/uacctd_stop.py b/src/system/uacctd_stop.py new file mode 100644 index 0000000..a1b5733 --- /dev/null +++ b/src/system/uacctd_stop.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Control pmacct daemons in a tricky way. +# Pmacct has signal processing in a main loop, together with packet +# processing. Because of this, while it is waiting for packets, it cannot +# handle the control signal. We need to start the systemctl command and then +# send some packets to pmacct to wake it up + +from argparse import ArgumentParser +from socket import socket, AF_INET, SOCK_DGRAM +from sys import exit +from time import sleep + +from psutil import Process + + +def stop_process(pid: int, timeout: int) -> None: + """Send a signal to uacctd + and then send packets to special address predefined in a firewall + to unlock main loop in uacctd and finish the process properly + + Args: + pid (int): uacctd PID + timeout (int): seconds to wait for a process end + """ + # find a process + uacctd = Process(pid) + uacctd.terminate() + + # create a socket + trigger = socket(AF_INET, SOCK_DGRAM) + + first_cycle: bool = True + while uacctd.is_running() and timeout: + print('sending a packet to uacctd...') + trigger.sendto(b'WAKEUP', ('127.0.254.0', 1)) + # do not sleep during first attempt + if not first_cycle: + sleep(1) + timeout -= 1 + first_cycle = False + + +if __name__ == '__main__': + parser = ArgumentParser() + parser.add_argument('process_id', + type=int, + help='PID file of uacctd core process') + parser.add_argument('timeout', + type=int, + help='time to wait for process end') + args = parser.parse_args() + stop_process(args.process_id, args.timeout) + exit() diff --git a/src/system/vyos-config-cloud-init.py b/src/system/vyos-config-cloud-init.py new file mode 100644 index 0000000..0a6c1f9 --- /dev/null +++ b/src/system/vyos-config-cloud-init.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging +from concurrent.futures import ProcessPoolExecutor +from pathlib import Path +from subprocess import run, TimeoutExpired +from sys import exit + +from psutil import net_if_addrs, AF_LINK +from systemd.journal import JournalHandler +from yaml import safe_load + +from vyos.template import render + +# define a path to the configuration file and template +config_file = '/etc/cloud/cloud.cfg.d/20_vyos_network.cfg' +template_file = 'system/cloud_init_networking.j2' + + +def check_interface_dhcp(iface_name: str) -> bool: + """Check DHCP client can work on an interface + + Args: + iface_name (str): interface name + + Returns: + bool: check result + """ + dhclient_command: list[str] = [ + 'dhclient', '-4', '-1', '-q', '--no-pid', '-sf', '/bin/true', iface_name + ] + check_result = False + # try to get an IP address + # we use dhclient behavior here to speedup detection + # if dhclient receives a configuration and configure an interface + # it switch to background + # If no - it will keep running in foreground + try: + run(['ip', 'l', 'set', iface_name, 'up']) + run(dhclient_command, timeout=5) + check_result = True + except TimeoutExpired: + pass + finally: + run(['ip', 'l', 'set', iface_name, 'down']) + + logger.info(f'DHCP server was found on {iface_name}: {check_result}') + return check_result + + +def dhclient_cleanup() -> None: + """Clean up after dhclients + """ + run(['killall', 'dhclient']) + leases_file: Path = Path('/var/lib/dhcp/dhclient.leases') + leases_file.unlink(missing_ok=True) + logger.debug('cleaned up after dhclients') + + +def dict_interfaces() -> dict[str, str]: + """Return list of available network interfaces except loopback + + Returns: + list[str]: a list of interfaces + """ + interfaces_dict: dict[str, str] = {} + ifaces = net_if_addrs() + for iface_name, iface_addresses in ifaces.items(): + # we do not need loopback interface + if iface_name == 'lo': + continue + # check other interfaces for MAC addresses + for iface_addr in iface_addresses: + if iface_addr.family == AF_LINK and iface_addr.address: + interfaces_dict[iface_name] = iface_addr.address + continue + + logger.debug(f'found interfaces: {interfaces_dict}') + return interfaces_dict + + +def need_to_check() -> bool: + """Check if we need to perform DHCP checks + + Returns: + bool: check result + """ + # if cloud-init config does not exist, we do not need to do anything + ci_config_vyos = Path('/etc/cloud/cloud.cfg.d/20_vyos_custom.cfg') + if not ci_config_vyos.exists(): + logger.info( + 'No need to check interfaces: Cloud-init config file was not found') + return False + + # load configuration file + try: + config = safe_load(ci_config_vyos.read_text()) + except: + logger.error('Cloud-init config file has a wrong format') + return False + + # check if we have in config configured option + # vyos_config_options: + # network_preconfigure: true + if not config.get('vyos_config_options', {}).get('network_preconfigure'): + logger.info( + 'No need to check interfaces: Cloud-init config option "network_preconfigure" is not set' + ) + return False + + return True + + +if __name__ == '__main__': + # prepare logger + logger = logging.getLogger(__name__) + logger.addHandler(JournalHandler(SYSLOG_IDENTIFIER=Path(__file__).name)) + logger.setLevel(logging.INFO) + + # we need to give udev some time to rename all interfaces + # this is placed before need_to_check() call, because we are not always + # need to preconfigure cloud-init, but udev always need to finish its work + # before cloud-init start + run(['udevadm', 'settle']) + logger.info('udev finished its work, we continue') + + # do not perform any checks if this is not required + if not need_to_check(): + exit() + + # get list of interfaces and check them + interfaces_dhcp: list[dict[str, str]] = [] + interfaces_dict: dict[str, str] = dict_interfaces() + + with ProcessPoolExecutor(max_workers=len(interfaces_dict)) as executor: + iface_check_results = [{ + 'dhcp': executor.submit(check_interface_dhcp, iface_name), + 'append': { + 'name': iface_name, + 'mac': iface_mac + } + } for iface_name, iface_mac in interfaces_dict.items()] + + dhclient_cleanup() + + for iface_check_result in iface_check_results: + if iface_check_result.get('dhcp').result(): + interfaces_dhcp.append(iface_check_result.get('append')) + + # render cloud-init config + if interfaces_dhcp: + logger.debug('rendering cloud-init network configuration') + render(config_file, template_file, {'ifaces_list': interfaces_dhcp}) + + exit() diff --git a/src/system/vyos-event-handler.py b/src/system/vyos-event-handler.py new file mode 100644 index 0000000..dd27930 --- /dev/null +++ b/src/system/vyos-event-handler.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import json +import re +import select + +from copy import deepcopy +from os import getpid, environ +from pathlib import Path +from signal import signal, SIGTERM, SIGINT +from sys import exit +from systemd import journal + +from vyos.utils.dict import dict_search +from vyos.utils.process import run + +# Identify this script +my_pid = getpid() +my_name = Path(__file__).stem + +# handle termination signal +def handle_signal(signal_type, frame): + if signal_type == SIGTERM: + journal.send('Received SIGTERM signal, stopping normally', + SYSLOG_IDENTIFIER=my_name) + if signal_type == SIGINT: + journal.send('Received SIGINT signal, stopping normally', + SYSLOG_IDENTIFIER=my_name) + exit(0) + + +# Class for analyzing and process messages +class Analyzer: + # Initialize settings + def __init__(self, config: dict) -> None: + self.config = {} + # Prepare compiled regex objects + for event_id, event_config in config.items(): + script = dict_search('script.path', event_config) + # Check for arguments + if dict_search('script.arguments', event_config): + script_arguments = dict_search('script.arguments', event_config) + script = f'{script} {script_arguments}' + # Prepare environment + environment = deepcopy(environ) + # Check for additional environment options + if dict_search('script.environment', event_config): + for env_variable, env_value in dict_search( + 'script.environment', event_config).items(): + environment[env_variable] = env_value.get('value') + # Create final config dictionary + pattern_raw = event_config['filter']['pattern'] + pattern_compiled = re.compile( + rf'{event_config["filter"]["pattern"]}') + pattern_config = { + pattern_compiled: { + 'pattern_raw': + pattern_raw, + 'syslog_id': + dict_search('filter.syslog-identifier', event_config), + 'pattern_script': { + 'path': script, + 'environment': environment + } + } + } + self.config.update(pattern_config) + + # Execute script safely + def script_run(self, pattern: str, script_path: str, + script_env: dict) -> None: + try: + run(script_path, env=script_env) + journal.send( + f'Pattern found: "{pattern}", script executed: "{script_path}"', + SYSLOG_IDENTIFIER=my_name) + except Exception as err: + journal.send( + f'Pattern found: "{pattern}", failed to execute script "{script_path}": {err}', + SYSLOG_IDENTIFIER=my_name) + + # Analyze a message + def process_message(self, message: dict) -> None: + for pattern_compiled, pattern_config in self.config.items(): + # Check if syslog id is presented in config and matches + syslog_id = pattern_config.get('syslog_id') + if syslog_id and message['SYSLOG_IDENTIFIER'] != syslog_id: + continue + if pattern_compiled.fullmatch(message['MESSAGE']): + # Add message to environment variables + pattern_config['pattern_script']['environment'][ + 'message'] = message['MESSAGE'] + # Run script + self.script_run( + pattern=pattern_config['pattern_raw'], + script_path=pattern_config['pattern_script']['path'], + script_env=pattern_config['pattern_script']['environment']) + + +if __name__ == '__main__': + # Parse command arguments and get config + parser = argparse.ArgumentParser() + parser.add_argument('-c', + '--config', + action='store', + help='Path to even-handler configuration', + required=True, + type=Path) + + args = parser.parse_args() + try: + config_path = Path(args.config) + config = json.loads(config_path.read_text()) + # Create an object for analazyng messages + analyzer = Analyzer(config) + except Exception as err: + print( + f'Configuration file "{config_path}" does not exist or malformed: {err}' + ) + exit(1) + + # Prepare for proper exitting + signal(SIGTERM, handle_signal) + signal(SIGINT, handle_signal) + + # Set up journal connection + data = journal.Reader() + data.seek_tail() + data.get_previous() + p = select.poll() + p.register(data, data.get_events()) + + journal.send(f'Started with configuration: {config}', + SYSLOG_IDENTIFIER=my_name) + + while p.poll(): + if data.process() != journal.APPEND: + continue + for entry in data: + message = entry['MESSAGE'] + pid = -1 + try: + pid = entry['_PID'] + except Exception as ex: + journal.send(f'Unable to extract PID from message entry: {entry}', SYSLOG_IDENTIFIER=my_name) + continue + # Skip empty messages and messages from this process + if message and pid != my_pid: + try: + analyzer.process_message(entry) + except Exception as err: + journal.send(f'Unable to process message: {err}', + SYSLOG_IDENTIFIER=my_name) diff --git a/src/system/vyos-system-update-check.py b/src/system/vyos-system-update-check.py new file mode 100644 index 0000000..c874f1e --- /dev/null +++ b/src/system/vyos-system-update-check.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import json +import jmespath + +from pathlib import Path +from sys import exit +from time import sleep + +from vyos.utils.process import call + +import vyos.version + +motd_file = Path('/run/motd.d/10-vyos-update') + + +if __name__ == '__main__': + # Parse command arguments and get config + parser = argparse.ArgumentParser() + parser.add_argument('-c', + '--config', + action='store', + help='Path to system-update-check configuration', + required=True, + type=Path) + + args = parser.parse_args() + try: + config_path = Path(args.config) + config = json.loads(config_path.read_text()) + except Exception as err: + print( + f'Configuration file "{config_path}" does not exist or malformed: {err}' + ) + exit(1) + + url_json = config.get('url') + local_data = vyos.version.get_full_version_data() + local_version = local_data.get('version') + + while True: + remote_data = vyos.version.get_remote_version(url_json) + if remote_data: + url = jmespath.search('[0].url', remote_data) + remote_version = jmespath.search('[0].version', remote_data) + if local_version != remote_version and remote_version: + call(f'wall -n "Update available: {remote_version} \nUpdate URL: {url}"') + # MOTD used in /run/motd.d/10-update + motd_file.parent.mkdir(exist_ok=True) + motd_file.write_text(f'---\n' + f'Current version: {local_version}\n' + f'Update available: \033[1;34m{remote_version}\033[0m\n' + f'---\n') + # Check every 12 hours + sleep(43200) diff --git a/src/systemd/LCDd.service b/src/systemd/LCDd.service new file mode 100644 index 0000000..233c1e2 --- /dev/null +++ b/src/systemd/LCDd.service @@ -0,0 +1,14 @@ +[Unit] +Description=LCD display daemon +Documentation=man:LCDd(8) http://www.lcdproc.org/ +RequiresMountsFor=/run +ConditionPathExists=/run/LCDd/LCDd.conf +After=vyos-router.service + + +[Service] +User=root +ExecStart=/usr/sbin/LCDd -s 1 -f -c /run/LCDd/LCDd.conf + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/accel-ppp@.service b/src/systemd/accel-ppp@.service new file mode 100644 index 0000000..2561127 --- /dev/null +++ b/src/systemd/accel-ppp@.service @@ -0,0 +1,16 @@ +[Unit] +Description=Accel-PPP - High performance VPN server application for Linux +RequiresMountsFor=/run +ConditionPathExists=/run/accel-pppd/%i.conf +After=vyos-router.service + +[Service] +WorkingDirectory=/run/accel-pppd +ExecStart=/usr/sbin/accel-pppd -d -p /run/accel-pppd/%i.pid -c /run/accel-pppd/%i.conf +ExecReload=/bin/kill -SIGUSR1 $MAINPID +PIDFile=/run/accel-pppd/%i.pid +Type=forking +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/aws-gwlbtun.service b/src/systemd/aws-gwlbtun.service new file mode 100644 index 0000000..97d772d --- /dev/null +++ b/src/systemd/aws-gwlbtun.service @@ -0,0 +1,11 @@ +[Unit] +Description=Description=AWS Gateway Load Balancer Tunnel Handler +Documentation=https://github.com/aws-samples/aws-gateway-load-balancer-tunnel-handler +After=network.target + +[Service] +ExecStart= +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/dhclient@.service b/src/systemd/dhclient@.service new file mode 100644 index 0000000..d430d88 --- /dev/null +++ b/src/systemd/dhclient@.service @@ -0,0 +1,21 @@ +[Unit] +Description=DHCP client on %i +Documentation=man:dhclient(8) +StartLimitIntervalSec=0 +After=vyos-router.service +ConditionPathExists=/run/dhclient/dhclient_%i.conf + +[Service] +Type=exec +ExecStart=/sbin/dhclient -4 -d $DHCLIENT_OPTS +ExecStop=/sbin/dhclient -4 -r $DHCLIENT_OPTS +Restart=always +RestartPreventExitStatus= +RestartSec=10 +RuntimeDirectoryPreserve=yes +TimeoutStopSec=20 +SendSIGKILL=true +FinalKillSignal=SIGABRT + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/dhcp6c@.service b/src/systemd/dhcp6c@.service new file mode 100644 index 0000000..f634bd9 --- /dev/null +++ b/src/systemd/dhcp6c@.service @@ -0,0 +1,19 @@ +[Unit] +Description=WIDE DHCPv6 client on %i +Documentation=man:dhcp6c(8) man:dhcp6c.conf(5) +StartLimitIntervalSec=0 +After=vyos-router.service + +[Service] +Type=forking +WorkingDirectory=/run/dhcp6c +EnvironmentFile=-/run/dhcp6c/dhcp6c.%i.options +PIDFile=/run/dhcp6c/dhcp6c.%i.pid +ExecStart=/usr/sbin/dhcp6c $DHCP6C_OPTS +Restart=always +RestartPreventExitStatus= +RestartSec=10 +RuntimeDirectoryPreserve=yes + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/dropbear@.service b/src/systemd/dropbear@.service new file mode 100644 index 0000000..acf926a --- /dev/null +++ b/src/systemd/dropbear@.service @@ -0,0 +1,16 @@ +[Unit] +Description=Dropbear SSH per-connection server +Requires=dropbearkey.service +Wants=conserver-server.service +ConditionPathExists=/run/conserver/conserver.cf +After=dropbearkey.service vyos-router.service conserver-server.service +StartLimitIntervalSec=0 + +[Service] +Type=forking +ExecStart=/usr/sbin/dropbear -w -j -k -r /etc/dropbear/dropbear_rsa_host_key -P /run/dropbear/dropbear.%I.pid -p %I +PIDFile=/run/dropbear/dropbear.%I.pid +KillMode=process +Restart=always +RestartSec=10 +RuntimeDirectoryPreserve=yes diff --git a/src/systemd/dropbearkey.service b/src/systemd/dropbearkey.service new file mode 100644 index 0000000..770641c --- /dev/null +++ b/src/systemd/dropbearkey.service @@ -0,0 +1,11 @@ +[Unit] +Description=Dropbear SSH Key Generation +ConditionPathExists=|!/etc/dropbear/dropbear_rsa_host_key + +[Service] +ExecStart=/usr/bin/dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target + diff --git a/src/systemd/isc-dhcp-relay.service b/src/systemd/isc-dhcp-relay.service new file mode 100644 index 0000000..de2e51a --- /dev/null +++ b/src/systemd/isc-dhcp-relay.service @@ -0,0 +1,20 @@ +[Unit] +Description=ISC DHCP IPv4 relay +Documentation=man:dhcrelay(8) +Wants=network-online.target +RequiresMountsFor=/run +ConditionPathExists=/run/dhcp-relay/dhcrelay.conf +After=vyos-router.service + +[Service] +Type=forking +WorkingDirectory=/run/dhcp-relay +RuntimeDirectory=dhcp-relay +RuntimeDirectoryPreserve=yes +EnvironmentFile=/run/dhcp-relay/dhcrelay.conf +PIDFile=/run/dhcp-relay/dhcrelay.pid +ExecStart=/usr/sbin/dhcrelay -4 -pf /run/dhcp-relay/dhcrelay.pid $OPTIONS +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/isc-dhcp-relay6.service b/src/systemd/isc-dhcp-relay6.service new file mode 100644 index 0000000..a365ae4 --- /dev/null +++ b/src/systemd/isc-dhcp-relay6.service @@ -0,0 +1,20 @@ +[Unit] +Description=ISC DHCP IPv6 relay +Documentation=man:dhcrelay(8) +Wants=network-online.target +RequiresMountsFor=/run +ConditionPathExists=/run/dhcp-relay/dhcrelay6.conf +After=vyos-router.service +StartLimitIntervalSec=0 +[Service] +Type=forking +WorkingDirectory=/run/dhcp-relay +RuntimeDirectory=dhcp-relay +RuntimeDirectoryPreserve=yes +EnvironmentFile=/run/dhcp-relay/dhcrelay6.conf +PIDFile=/run/dhcp-relay/dhcrelay6.pid +ExecStart=/usr/sbin/dhcrelay -6 -pf /run/dhcp-relay/dhcrelay6.pid $OPTIONS +Restart=always +RestartSec=10 +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/lcdproc.service b/src/systemd/lcdproc.service new file mode 100644 index 0000000..ef71766 --- /dev/null +++ b/src/systemd/lcdproc.service @@ -0,0 +1,13 @@ +[Unit] +Description=LCDproc system status information viewer on %I +Documentation=man:lcdproc(8) http://www.lcdproc.org/ +After=vyos-router.service LCDd.service +Requires=LCDd.service + +[Service] +User=root +ExecStart=/usr/bin/lcdproc -f -c /run/lcdproc/lcdproc.conf +PIDFile=/run/lcdproc/lcdproc.pid + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/ndppd.service b/src/systemd/ndppd.service new file mode 100644 index 0000000..5790d37 --- /dev/null +++ b/src/systemd/ndppd.service @@ -0,0 +1,15 @@ +[Unit] +Description=NDP Proxy Daemon +After=vyos-router.service +ConditionPathExists=/run/ndppd/ndppd.conf +StartLimitIntervalSec=0 + +[Service] +Type=forking +ExecStart=/usr/sbin/ndppd -d -p /run/ndppd/ndppd.pid -c /run/ndppd/ndppd.conf +PIDFile=/run/ndppd/ndppd.pid +Restart=on-failure +RestartSec=20 + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/opennhrp.service b/src/systemd/opennhrp.service new file mode 100644 index 0000000..c9a44de --- /dev/null +++ b/src/systemd/opennhrp.service @@ -0,0 +1,13 @@ +[Unit] +Description=OpenNHRP +After=vyos-router.service +ConditionPathExists=/run/opennhrp/opennhrp.conf +StartLimitIntervalSec=0 + +[Service] +Type=forking +ExecStart=/usr/sbin/opennhrp -d -v -a /run/opennhrp.socket -c /run/opennhrp/opennhrp.conf -s /etc/opennhrp/opennhrp-script.py -p /run/opennhrp/opennhrp.pid +ExecReload=/usr/bin/kill -HUP $MAINPID +PIDFile=/run/opennhrp/opennhrp.pid +Restart=on-failure +RestartSec=20 diff --git a/src/systemd/podman.service b/src/systemd/podman.service new file mode 100644 index 0000000..20a1630 --- /dev/null +++ b/src/systemd/podman.service @@ -0,0 +1,16 @@ +[Unit] +Description=Podman API Service +Requires=podman.socket +After=podman.socket +Documentation=man:podman-system-service(1) +StartLimitIntervalSec=0 + +[Service] +Delegate=true +Type=exec +KillMode=process +Environment=LOGGING="--log-level=info" +ExecStart=/usr/bin/podman $LOGGING system service + +[Install] +WantedBy=default.target diff --git a/src/systemd/podman.socket b/src/systemd/podman.socket new file mode 100644 index 0000000..397058e --- /dev/null +++ b/src/systemd/podman.socket @@ -0,0 +1,10 @@ +[Unit] +Description=Podman API Socket +Documentation=man:podman-system-service(1) + +[Socket] +ListenStream=%t/podman/podman.sock +SocketMode=0660 + +[Install] +WantedBy=sockets.target diff --git a/src/systemd/ppp@.service b/src/systemd/ppp@.service new file mode 100644 index 0000000..bb46220 --- /dev/null +++ b/src/systemd/ppp@.service @@ -0,0 +1,11 @@ +[Unit] +Description=Dialing PPP connection %I +After=vyos-router.service + +[Service] +ExecStart=/usr/sbin/pppd call %I nodetach nolog +Restart=on-failure +RestartSec=5s + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/root-partition-auto-resize.service b/src/systemd/root-partition-auto-resize.service new file mode 100644 index 0000000..a57fbc3 --- /dev/null +++ b/src/systemd/root-partition-auto-resize.service @@ -0,0 +1,12 @@ +[Unit] +Description=VyOS root partition auto resizing +After=multi-user.target + +[Service] +Type=oneshot +User=root +Group=root +ExecStart=/usr/libexec/vyos/op_mode/force_root-partition-auto-resize.sh + +[Install] +WantedBy=vyos.target
\ No newline at end of file diff --git a/src/systemd/stunnel.service b/src/systemd/stunnel.service new file mode 100644 index 0000000..b260e29 --- /dev/null +++ b/src/systemd/stunnel.service @@ -0,0 +1,15 @@ +[Unit] +Description=SSL tunneling service +Documentation=http://man.he.net/man8/stunnel4 +After=network.target + +[Service] +ExecStart=/usr/bin/stunnel /run/stunnel/stunnel.conf +ExecReload=/bin/kill -HUP $MAINPID +KillMode=process +PIDFile=/run/stunnel/stunnel.pid +Type=forking +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/telegraf.service b/src/systemd/telegraf.service new file mode 100644 index 0000000..553942a --- /dev/null +++ b/src/systemd/telegraf.service @@ -0,0 +1,15 @@ +[Unit] +Description=The plugin-driven server agent for reporting metrics into InfluxDB +Documentation=https://github.com/influxdata/telegraf +After=network.target + +[Service] +EnvironmentFile=-/etc/default/telegraf +ExecStart=/usr/bin/telegraf --config /run/telegraf/vyos-telegraf.conf --config-directory /etc/telegraf/telegraf.d +ExecReload=/bin/kill -HUP $MAINPID +Restart=on-failure +RestartForceExitStatus=SIGPIPE +KillMode=control-group + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/tftpd@.service b/src/systemd/tftpd@.service new file mode 100644 index 0000000..a674bf5 --- /dev/null +++ b/src/systemd/tftpd@.service @@ -0,0 +1,14 @@ +[Unit] +Description=TFTP server +After=vyos-router.service +RequiresMountsFor=/run + +[Service] +Type=forking +#NotifyAccess=main +EnvironmentFile=-/etc/default/tftpd%I +ExecStart=/bin/sh -c "${VRF_ARGS} /usr/sbin/in.tftpd ${DAEMON_ARGS}" +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/vyos-beep.service b/src/systemd/vyos-beep.service new file mode 100644 index 0000000..78baa54 --- /dev/null +++ b/src/systemd/vyos-beep.service @@ -0,0 +1,11 @@ +[Unit] +Description=Beep after system start +DefaultDependencies=no +After=vyos.target + +[Service] +Type=oneshot +ExecStart=/usr/bin/beep -f 130 -l 100 -n -f 262 -l 100 -n -f 330 -l 100 -n -f 392 -l 100 -n -f 523 -l 100 -n -f 660 -l 100 -n -f 784 -l 300 -n -f 660 -l 300 + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/vyos-config-cloud-init.service b/src/systemd/vyos-config-cloud-init.service new file mode 100644 index 0000000..ba6f90e --- /dev/null +++ b/src/systemd/vyos-config-cloud-init.service @@ -0,0 +1,19 @@ +[Unit] +Description=Pre-configure Cloud-init +DefaultDependencies=no +Requires=systemd-remount-fs.service +Requires=systemd-udevd.service +Wants=network-pre.target +After=systemd-remount-fs.service +After=systemd-udevd.service +Before=cloud-init-local.service + +[Service] +Type=oneshot +ExecStart=/usr/libexec/vyos/system/vyos-config-cloud-init.py +TimeoutSec=120 +KillMode=process +StandardOutput=journal+console + +[Install] +WantedBy=cloud-init-local.service diff --git a/src/systemd/vyos-configd.service b/src/systemd/vyos-configd.service new file mode 100644 index 0000000..274ccc7 --- /dev/null +++ b/src/systemd/vyos-configd.service @@ -0,0 +1,27 @@ +[Unit] +Description=VyOS configuration daemon + +# Without this option, lots of default dependencies are added, +# among them network.target, which creates a dependency cycle +DefaultDependencies=no + +# Seemingly sensible way to say "as early as the system is ready" +# All vyos-configd needs is read/write mounted root +After=systemd-remount-fs.service +Before=vyos-router.service + +[Service] +ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-configd +Type=idle + +SyslogIdentifier=vyos-configd +SyslogFacility=daemon + +Restart=on-failure + +# Does't work in Jessie but leave it here +User=root +Group=vyattacfg + +[Install] +WantedBy=vyos.target diff --git a/src/systemd/vyos-conntrack-logger.service b/src/systemd/vyos-conntrack-logger.service new file mode 100644 index 0000000..9bc1d85 --- /dev/null +++ b/src/systemd/vyos-conntrack-logger.service @@ -0,0 +1,21 @@ +[Unit] +Description=VyOS conntrack logger daemon + +# Seemingly sensible way to say "as early as the system is ready" +# All vyos-configd needs is read/write mounted root +After=conntrackd.service + +[Service] +ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-conntrack-logger -c /run/vyos-conntrack-logger.conf +Type=idle + +SyslogIdentifier=vyos-conntrack-logger +SyslogFacility=daemon + +Restart=on-failure + +User=root +Group=vyattacfg + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/vyos-domain-resolver.service b/src/systemd/vyos-domain-resolver.service new file mode 100644 index 0000000..c56b51f --- /dev/null +++ b/src/systemd/vyos-domain-resolver.service @@ -0,0 +1,13 @@ +[Unit] +Description=VyOS firewall domain resolver +After=vyos-router.service + +[Service] +Type=simple +Restart=always +ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/vyos-domain-resolver.py +StandardError=journal +StandardOutput=journal + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/vyos-event-handler.service b/src/systemd/vyos-event-handler.service new file mode 100644 index 0000000..6afe4f9 --- /dev/null +++ b/src/systemd/vyos-event-handler.service @@ -0,0 +1,11 @@ +[Unit] +Description=VyOS event handler +After=network.target vyos-router.service + +[Service] +Type=simple +Restart=always +ExecStart=/usr/bin/python3 /usr/libexec/vyos/system/vyos-event-handler.py --config /run/vyos-event-handler.conf + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/vyos-grub-update.service b/src/systemd/vyos-grub-update.service new file mode 100644 index 0000000..7b67ae1 --- /dev/null +++ b/src/systemd/vyos-grub-update.service @@ -0,0 +1,14 @@ +[Unit] +Description=Update GRUB loader configuration structure +After=local-fs.target +Before=vyos-router.service + +[Service] +Type=oneshot +ExecStart=/usr/libexec/vyos/system/grub_update.py +TimeoutSec=60 +KillMode=process +StandardOutput=journal+console + +[Install] +WantedBy=vyos-router.service diff --git a/src/systemd/vyos-hostsd.service b/src/systemd/vyos-hostsd.service new file mode 100644 index 0000000..4da55f5 --- /dev/null +++ b/src/systemd/vyos-hostsd.service @@ -0,0 +1,34 @@ +[Unit] +Description=VyOS DNS configuration keeper + +# Without this option, lots of default dependencies are added, +# among them network.target, which creates a dependency cycle +DefaultDependencies=no + +# Seemingly sensible way to say "as early as the system is ready" +# All vyos-hostsd needs is read/write mounted root +After=systemd-remount-fs.service cloud-init.service + +[Service] +WorkingDirectory=/run/vyos-hostsd +RuntimeDirectory=vyos-hostsd +RuntimeDirectoryPreserve=yes +ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-hostsd +Type=idle +KillMode=process + +SyslogIdentifier=vyos-hostsd +SyslogFacility=daemon + +Restart=on-failure + +# Does't work in Jessie but leave it here +User=root +Group=hostsd + +[Install] + +# Note: After= doesn't actually create a dependency, +# it just sets order for the case when both services are to start, +# and without RequiredBy it *does not* set vyos-hostsd to start. +RequiredBy=cloud-init-local.service vyos-router.service diff --git a/src/systemd/vyos-router.service b/src/systemd/vyos-router.service new file mode 100644 index 0000000..7a1638f --- /dev/null +++ b/src/systemd/vyos-router.service @@ -0,0 +1,18 @@ +[Unit] +Description=VyOS Router +After=systemd-journald-dev-log.socket time-sync.target local-fs.target cloud-config.service +Conflicts=shutdown.target +Before=systemd-user-sessions.service + +[Service] +Type=simple +Restart=no +TimeoutSec=20min +KillMode=process +RemainAfterExit=yes +ExecStart=/usr/libexec/vyos/init/vyos-router start +ExecStop=/usr/libexec/vyos/init/vyos-router stop +StandardOutput=journal+console + +[Install] +WantedBy=vyos.target diff --git a/src/systemd/vyos-system-update.service b/src/systemd/vyos-system-update.service new file mode 100644 index 0000000..032e5a1 --- /dev/null +++ b/src/systemd/vyos-system-update.service @@ -0,0 +1,11 @@ +[Unit] +Description=VyOS system udpate-check service +After=network.target vyos-router.service + +[Service] +Type=simple +Restart=always +ExecStart=/usr/bin/python3 /usr/libexec/vyos/system/vyos-system-update-check.py --config /run/vyos-system-update.conf + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/vyos-wan-load-balance.service b/src/systemd/vyos-wan-load-balance.service new file mode 100644 index 0000000..7d62a2f --- /dev/null +++ b/src/systemd/vyos-wan-load-balance.service @@ -0,0 +1,15 @@ +[Unit] +Description=VyOS WAN load-balancing service +After=vyos-router.service + +[Service] +ExecStart=/opt/vyatta/sbin/wan_lb -f /run/load-balance/wlb.conf -d -i /var/run/vyatta/wlb.pid +ExecReload=/bin/kill -s SIGTERM $MAINPID && sleep 5 && /opt/vyatta/sbin/wan_lb -f /run/load-balance/wlb.conf -d -i /var/run/vyatta/wlb.pid +ExecStop=/bin/kill -s SIGTERM $MAINPID +PIDFile=/var/run/vyatta/wlb.pid +KillMode=process +Restart=on-failure +RestartSec=5s + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/vyos.target b/src/systemd/vyos.target new file mode 100644 index 0000000..47c91c1 --- /dev/null +++ b/src/systemd/vyos.target @@ -0,0 +1,3 @@ +[Unit] +Description=VyOS target +After=multi-user.target diff --git a/src/systemd/wpa_supplicant-macsec@.service b/src/systemd/wpa_supplicant-macsec@.service new file mode 100644 index 0000000..ffb4fe3 --- /dev/null +++ b/src/systemd/wpa_supplicant-macsec@.service @@ -0,0 +1,18 @@ +[Unit] +Description=WPA supplicant daemon (MACsec-specific version) +Requires=sys-subsystem-net-devices-%i.device +ConditionPathExists=/run/wpa_supplicant/%I.conf +After=vyos-router.service +RequiresMountsFor=/run + +[Service] +Type=simple +WorkingDirectory=/run/wpa_supplicant +PIDFile=/run/wpa_supplicant/%I.pid +ExecStart=/sbin/wpa_supplicant -c/run/wpa_supplicant/%I.conf -Dmacsec_linux -P/run/wpa_supplicant/%I.pid -i%I +ExecReload=/bin/kill -HUP $MAINPID +Restart=always +RestartSec=2 + +[Install] +WantedBy=multi-user.target diff --git a/src/tests/test_configd_inspect.py b/src/tests/test_configd_inspect.py new file mode 100644 index 0000000..ccd6318 --- /dev/null +++ b/src/tests/test_configd_inspect.py @@ -0,0 +1,104 @@ +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import re +import json + +import warnings +import importlib.util +from inspect import signature +from inspect import getsource +from functools import wraps +from unittest import TestCase + +INC_FILE = 'data/configd-include.json' +CONF_DIR = 'src/conf_mode' + +f_list = ['get_config', 'verify', 'generate', 'apply'] + +def import_script(s): + path = os.path.join(CONF_DIR, s) + name = os.path.splitext(s)[0].replace('-', '_') + spec = importlib.util.spec_from_file_location(name, path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + +# importing conf_mode scripts imports jinja2 with deprecation warning +def ignore_deprecation_warning(f): + @wraps(f) + def decorated_function(*args, **kwargs): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + f(*args, **kwargs) + return decorated_function + +class TestConfigdInspect(TestCase): + def setUp(self): + with open(INC_FILE) as f: + self.inc_list = json.load(f) + + @ignore_deprecation_warning + def test_signatures(self): + for s in self.inc_list: + m = import_script(s) + for i in f_list: + f = getattr(m, i, None) + self.assertIsNotNone(f, f"'{s}': missing function '{i}'") + sig = signature(f) + par = sig.parameters + l = len(par) + self.assertEqual(l, 1, + f"'{s}': '{i}' incorrect signature") + if i == 'get_config': + for p in par.values(): + self.assertTrue(p.default is None, + f"'{s}': '{i}' incorrect signature") + + @ignore_deprecation_warning + def test_function_instance(self): + for s in self.inc_list: + m = import_script(s) + for i in f_list: + f = getattr(m, i, None) + if not f: + continue + str_f = getsource(f) + # Regex not XXXConfig() T3108 + n = len(re.findall(r'[^a-zA-Z]Config\(\)', str_f)) + if i == 'get_config': + self.assertEqual(n, 1, + f"'{s}': '{i}' no instance of Config") + if i != 'get_config': + self.assertEqual(n, 0, + f"'{s}': '{i}' instance of Config") + + @ignore_deprecation_warning + def test_file_instance(self): + for s in self.inc_list: + m = import_script(s) + str_m = getsource(m) + # Regex not XXXConfig T3108 + n = len(re.findall(r'[^a-zA-Z]Config\(\)', str_m)) + self.assertEqual(n, 1, + f"'{s}' more than one instance of Config") + + @ignore_deprecation_warning + def test_config_modification(self): + for s in self.inc_list: + m = import_script(s) + str_m = getsource(m) + n = str_m.count('my_set') + self.assertEqual(n, 0, f"'{s}' modifies config") diff --git a/src/validators/port-range-exclude b/src/validators/port-range-exclude new file mode 100644 index 0000000..4c049e9 --- /dev/null +++ b/src/validators/port-range-exclude @@ -0,0 +1,7 @@ +#!/bin/sh +arg="$1" +if [ "${arg:0:1}" != "!" ]; then + exit 1 +fi +path=$(dirname "$0") +${path}/port-range "${arg:1}" diff --git a/src/validators/psk-secret b/src/validators/psk-secret new file mode 100644 index 0000000..c91aa95 --- /dev/null +++ b/src/validators/psk-secret @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +from sys import argv,exit + +if __name__ == '__main__': + if len(argv) != 2: + exit(1) + + input = argv[1] + is_valid = True + try: + # Convert hexadecimal input to binary form + key_bytes = bytes.fromhex(input) + except ValueError: + is_valid = False + + if is_valid and len(key_bytes) < 16: + is_valid = False + + if not is_valid: + print(f'Error: {input} is not valid psk secret.') + exit(1) + + exit(0)
\ No newline at end of file diff --git a/tests/data/config.boot.default b/tests/data/config.boot.default new file mode 100644 index 0000000..0a75716 --- /dev/null +++ b/tests/data/config.boot.default @@ -0,0 +1,40 @@ +system { + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$QxPS.uk6mfo$9QBSo8u1FkH16gMyAVhus6fU3LOzvLR9Z9.82m3tiHFAxTtIkhaZSWssSgzt4v4dGAL8rhVQxTg0oAG9/q11h/ + plaintext-password "" + } + level admin + } + } + syslog { + global { + facility all { + level notice + } + facility protocols { + level debug + } + } + } + ntp { + server "0.pool.ntp.org" + server "1.pool.ntp.org" + server "2.pool.ntp.org" + } + console { + device ttyS0 { + speed 9600 + } + } + config-management { + commit-revisions 100 + } +} + +interfaces { + loopback lo { + } +} diff --git a/tests/data/config.left b/tests/data/config.left new file mode 100644 index 0000000..e57c403 --- /dev/null +++ b/tests/data/config.left @@ -0,0 +1,36 @@ +node1 { + tag_node foo { + valueless + multi_node 'v2' + multi_node 'v1' + single 'left_val' + } + tag_node bar { + node { + single 'v0' + } + } + tag_node other { + leaf 'leaf_l' + } +} + +node3 { +} + +node2 { + sub_node_other { + single 'val' + } + sub_node { + tag_node other { + single 'val' + } + tag_node bob { + valued 'baz' + } + tag_node duff { + valued 'buz' + } + } +} diff --git a/tests/data/config.right b/tests/data/config.right new file mode 100644 index 0000000..48defeb --- /dev/null +++ b/tests/data/config.right @@ -0,0 +1,25 @@ +node1 { + tag_node baz { + other_node { + multi_node 'some_val' + multe_node 'other_val' + } + } + tag_node foo { + valueless + multi_node 'v3' + multi_node 'v1' + single 'right_val' + } + tag_node other { + leaf 'leaf_r' + } +} + +node2 { + sub_node { + tag_node other { + multi 'mv' + } + } +} diff --git a/tests/data/config.valid b/tests/data/config.valid new file mode 100644 index 0000000..1fbdd15 --- /dev/null +++ b/tests/data/config.valid @@ -0,0 +1,39 @@ +/* top level leaf node */ +top-level-leaf-node foo + +top-level-valueless-node + +top-level-tag-node foo { + top-level-tag-node-child some-value +} + +top-level-tag-node bar { + top-level-tag-node-child another-value +} + +normal-node { + normal-node-child { + valueless-node + multi-node value1 + /* valueless node comment */ + another-valueless-node + multi-node value1 + tag-node foo { + } + one-more-valueless-node + tag-node bar { + some-option some-value + } + } + option-with-quoted-value "some-value" +} + +trailing-leaf-node-option some-value + +empty-node { +} + +trailing-leaf-node-without-value + +// Trailing comment +// Another trailing comment diff --git a/tests/data/interface-definitions/test-op.xml b/tests/data/interface-definitions/test-op.xml new file mode 100644 index 0000000..50bd686 --- /dev/null +++ b/tests/data/interface-definitions/test-op.xml @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="foo"> + <properties> + <help>foo</help> + </properties> + <children> + <leafNode name="bar"> + <command>/usr/bin/bar</command> + <properties> + <help>bar</help> + <completionHelp> + <list>foo bar</list> + <path>interfaces tunnel</path> + <script>/usr/bin/foo</script> + </completionHelp> + </properties> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/tests/data/interface-definitions/test.xml b/tests/data/interface-definitions/test.xml new file mode 100644 index 0000000..fbb302e --- /dev/null +++ b/tests/data/interface-definitions/test.xml @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="foo"> + <properties> + <help>foo</help> + </properties> + <children> + <leafNode name="bar"> + <properties> + <help>bar</help> + <valueHelp> + <format>bar</format> + <description>bar</description> + </valueHelp> + <completionHelp> + <list>foo bar</list> + <path>interfaces tunnel</path> + <script>/usr/bin/foo</script> + </completionHelp> + </properties> + </leafNode> + </children> + </node> +</interfaceDefinition> |